Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(exp): add ssh key functions #441

Merged
merged 5 commits into from
May 24, 2024
Merged
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/prometheus/client_golang v1.19.1
github.com/stretchr/testify v1.9.0
github.com/vburenin/ifacemaker v1.2.1
golang.org/x/crypto v0.23.0
golang.org/x/net v0.25.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vburenin/ifacemaker v1.2.1 h1:3Vq8B/bfBgjWTkv+jDg4dVL1KHt3k1K4lO7XRxYA2sk=
github.com/vburenin/ifacemaker v1.2.1/go.mod h1:5WqrzX2aD7/hi+okBjcaEQJMg4lDGrpuEX3B8L4Wgrs=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
Expand Down
86 changes: 86 additions & 0 deletions hcloud/exp/kit/ssh/ssh_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package sshkey

import (
"crypto"
"crypto/ed25519"
"encoding/pem"
"fmt"

"golang.org/x/crypto/ssh"
)

// GenerateKeyPair generates a new ed25519 ssh key pair, and returns the private key and
// the public key respectively.
func GenerateKeyPair() ([]byte, []byte, error) {
pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
return nil, nil, fmt.Errorf("could not generate key pair: %w", err)
}

privBytes, err := encodePrivateKey(priv)
if err != nil {
return nil, nil, fmt.Errorf("could not encode private key: %w", err)
}

pubBytes, err := encodePublicKey(pub)
if err != nil {
return nil, nil, fmt.Errorf("could not encode public key: %w", err)
}

return privBytes, pubBytes, nil
}

func encodePrivateKey(priv crypto.PrivateKey) ([]byte, error) {
privPem, err := ssh.MarshalPrivateKey(priv, "")
if err != nil {
return nil, err
}

return pem.EncodeToMemory(privPem), nil
}

func encodePublicKey(pub crypto.PublicKey) ([]byte, error) {
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
return nil, err
}

return ssh.MarshalAuthorizedKey(sshPub), nil
}

type privateKeyWithPublicKey interface {
crypto.PrivateKey
Public() crypto.PublicKey
}

// GeneratePublicKey generate a public key from the provided private key.
func GeneratePublicKey(privBytes []byte) ([]byte, error) {
priv, err := ssh.ParseRawPrivateKey(privBytes)
if err != nil {
return nil, fmt.Errorf("could not decode private key: %w", err)
}

key, ok := priv.(privateKeyWithPublicKey)
if !ok {
return nil, fmt.Errorf("private key doesn't export Public() crypto.PublicKey")
}

pubBytes, err := encodePublicKey(key.Public())
if err != nil {
return nil, fmt.Errorf("could not encode public key: %w", err)
}

return pubBytes, nil
}

// GetPublicKeyFingerprint generate the finger print for the provided public key.
func GetPublicKeyFingerprint(pubBytes []byte) (string, error) {
pub, _, _, _, err := ssh.ParseAuthorizedKey(pubBytes)
if err != nil {
return "", fmt.Errorf("could not decode public key: %w", err)
}

fingerprint := ssh.FingerprintLegacyMD5(pub)

return fingerprint, nil
}
54 changes: 54 additions & 0 deletions hcloud/exp/kit/ssh/ssh_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package sshkey

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGenerateKeyPair(t *testing.T) {
privBytes, pubBytes, err := GenerateKeyPair()
assert.Nil(t, err)

priv := string(privBytes)
pub := string(pubBytes)

if !(strings.HasPrefix(priv, "-----BEGIN OPENSSH PRIVATE KEY-----\n") &&
strings.HasSuffix(priv, "-----END OPENSSH PRIVATE KEY-----\n")) {
assert.Fail(t, "private key is invalid", priv)
}

if !strings.HasPrefix(pub, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA") {
assert.Fail(t, "public key is invalid", pub)
}
}

func TestGeneratePublicKey(t *testing.T) {
privBytes, pubBytesOrig, err := GenerateKeyPair()
require.NoError(t, err)

pubBytes, err := GeneratePublicKey(privBytes)
require.NoError(t, err)

pub := string(pubBytes)
priv := string(privBytes)

if !(strings.HasPrefix(priv, "-----BEGIN OPENSSH PRIVATE KEY-----\n") &&
strings.HasSuffix(priv, "-----END OPENSSH PRIVATE KEY-----\n")) {
assert.Fail(t, "private key is invalid", priv)
}

if !strings.HasPrefix(pub, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA") {
assert.Fail(t, "public key is invalid", pub)
}

assert.Equal(t, pubBytesOrig, pubBytes)
}

func TestGetPublicKeyFingerprint(t *testing.T) {
fingerprint, err := GetPublicKeyFingerprint([]byte(`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIccHCW76xx2rrPAUrjnuT6IjpEF1O+/U4IByVgv99Oi`))
require.NoError(t, err)
assert.Equal(t, "77:79:69:b1:4d:c6:b6:45:6a:e9:52:29:04:3e:59:48", fingerprint)
}
Loading