Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ if err != nil {
client, err := goph.New("root", "192.1.1.3", auth)
```

#### 🔐 Start Connection With In-Memory Private Key Content:
```go
privateKey := `-----BEGIN RSA PRIVATE KEY-----
...key content...
-----END RSA PRIVATE KEY-----`

auth, err := goph.Key(privateKey, "")
if err != nil {
// handle error
}

client, err := goph.New("root", "192.1.1.3", auth)
```

#### 🔑 Start Connection With Password:
```go
client, err := goph.New("root", "192.1.1.3", goph.Password("you_password_here"))
Expand Down
20 changes: 18 additions & 2 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ func Key(prvFile string, passphrase string) (Auth, error) {
}, nil
}

func RawKey(privateKey string, passphrase string) (Auth, error) {
signer, err := GetSignerForRawKey([]byte(privateKey), passphrase)
// KeyContent returns auth method from private key content (byte slice) with optional passphrase.
// This is useful when the private key is stored in memory rather than a file.
func KeyContent(privateKey []byte, passphrase string) (Auth, error) {
signer, err := GetSignerForRawKey(privateKey, passphrase)
if err != nil {
return nil, err
}
Expand All @@ -66,6 +68,12 @@ func RawKey(privateKey string, passphrase string) (Auth, error) {
}, nil
}

// RawKey returns auth method from private key string content with optional passphrase.
// Deprecated: Use KeyContent instead which accepts []byte directly.
func RawKey(privateKey string, passphrase string) (Auth, error) {
return KeyContent([]byte(privateKey), passphrase)
}

// HasAgent checks if ssh agent exists.
func HasAgent() bool {
return os.Getenv("SSH_AUTH_SOCK") != ""
Expand All @@ -84,6 +92,9 @@ func UseAgent() (Auth, error) {

// GetSigner returns ssh signer from private key file.
func GetSigner(prvFile string, passphrase string) (ssh.Signer, error) {
if isRawPrivateKey(prvFile) {
return GetSignerForRawKey([]byte(prvFile), passphrase)
}

var (
err error
Expand All @@ -108,6 +119,11 @@ func GetSigner(prvFile string, passphrase string) (ssh.Signer, error) {
return signer, err
}

func isRawPrivateKey(input string) bool {
trimmed := strings.TrimSpace(input)
return strings.HasPrefix(trimmed, "-----BEGIN") && strings.Contains(trimmed, "PRIVATE KEY-----")
}

// GetSignerForRawKey returns ssh signer from private key file.
func GetSignerForRawKey(privateKey []byte, passphrase string) (ssh.Signer, error) {

Expand Down
75 changes: 75 additions & 0 deletions auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package goph

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"os"
"path/filepath"
"testing"
)

func generateTestPrivateKeyPEM(t *testing.T) string {
t.Helper()

key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("failed to generate rsa key: %v", err)
}

keyBytes := x509.MarshalPKCS1PrivateKey(key)
block := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: keyBytes,
}

return string(pem.EncodeToMemory(block))
}

func TestGetSignerFromRawPrivateKeyContent(t *testing.T) {
privateKey := generateTestPrivateKeyPEM(t)

signer, err := GetSigner(privateKey, "")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if signer == nil {
t.Fatalf("expected non-nil signer")
}
}

func TestGetSignerFromMissingPrivateKeyFile(t *testing.T) {
_, err := GetSigner(filepath.Join(t.TempDir(), "missing-id-rsa"), "")
if err == nil {
t.Fatalf("expected error for missing key file")
}
}

func TestKeyFromRawPrivateKeyContent(t *testing.T) {
privateKey := generateTestPrivateKeyPEM(t)

auth, err := Key(privateKey, "")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(auth) == 0 {
t.Fatalf("expected at least one auth method")
}
}

func TestGetSignerFromPrivateKeyFile(t *testing.T) {
privateKey := generateTestPrivateKeyPEM(t)
keyPath := filepath.Join(t.TempDir(), "id_rsa")
if err := os.WriteFile(keyPath, []byte(privateKey), 0o600); err != nil {
t.Fatalf("failed to write private key file: %v", err)
}

signer, err := GetSigner(keyPath, "")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if signer == nil {
t.Fatalf("expected non-nil signer")
}
}