diff options
| author | Jared Horvat <horvski@gmail.com> | 2023-12-17 13:25:56 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-12-17 15:25:56 -0500 |
| commit | 914960f60a3c8ccb56d018b2d3fe8484ee9976d7 (patch) | |
| tree | 30369f20cb0206e2e31dd1618a230bd0cd39a614 /crypto | |
| parent | 36fb3f05006bdab3f9b96400a9043cfe7430cc1d (diff) | |
Add support for Ed25519 (#1900)
* Add support for Ed25519
* Amended naming and added additional testing
* Added changes from Dave's review
* Next review: Fixed casing on error messages for linter | Fixed version number
* Added Dave's suggestions in docs and updated built docs
* Final push from Dave's review | Wrap crypto example in docs in quotes
Diffstat (limited to 'crypto')
| -rw-r--r-- | crypto/ed25519.go | 81 | ||||
| -rw-r--r-- | crypto/ed25519_test.go | 47 |
2 files changed, 128 insertions, 0 deletions
diff --git a/crypto/ed25519.go b/crypto/ed25519.go new file mode 100644 index 00000000..a6630ad7 --- /dev/null +++ b/crypto/ed25519.go @@ -0,0 +1,81 @@ +package crypto + +import ( + "bytes" + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "fmt" +) + +// Ed25519GenerateKey returns a random PEM encoded Ed25519 Private Key. +func Ed25519GenerateKey() ([]byte, error) { + _, secret, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("generateKey: %w", err) + } + return pemEncodeEdPrivateKey(secret) +} + +// Ed25519GenerateKeyFromSeed returns a PEM encoded Ed25519 Private Key from +// `seed`. Returns error if len(seed) is not ed25519.SeedSize (32). +func Ed25519GenerateKeyFromSeed(seed []byte) ([]byte, error) { + if len(seed) != ed25519.SeedSize { + return nil, fmt.Errorf("generateKeyFromSeed: incorrect seed size - given: %d wanted %d", len(seed), ed25519.SeedSize) + } + return pemEncodeEdPrivateKey(ed25519.NewKeyFromSeed(seed)) +} + +// Ed25519DerivePublicKey returns an ed25519 Public Key from given PEM encoded +// `privatekey`. +func Ed25519DerivePublicKey(privatekey []byte) ([]byte, error) { + secret, err := ed25519DecodeFromPEM(privatekey) + if err != nil { + return nil, fmt.Errorf("ed25519DecodeFromPEM: could not decode private key: %w", err) + } + b, err := x509.MarshalPKIXPublicKey(secret.Public()) + if err != nil { + return nil, fmt.Errorf("marshalPKIXPublicKey: failed to marshal PKIX public key: %w", err) + } + return pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: b, + }), nil +} + +// pemEncodeEdPrivateKey is a convenience function for PEM encoding `secret`. +func pemEncodeEdPrivateKey(secret ed25519.PrivateKey) ([]byte, error) { + der, err := x509.MarshalPKCS8PrivateKey(secret) + if err != nil { + return nil, fmt.Errorf("marshalPKCS8PrivateKey: failed to marshal ed25519 private key: %w", err) + } + block := &pem.Block{ + Type: "PRIVATE KEY", + Bytes: der, + } + buf := &bytes.Buffer{} + err = pem.Encode(buf, block) + if err != nil { + return nil, fmt.Errorf("encode: PEM encoding: %w", err) + } + return buf.Bytes(), nil +} + +// ed25519DecodeFromPEM returns an ed25519.PrivateKey from given PEM encoded +// `privatekey`. +func ed25519DecodeFromPEM(privatekey []byte) (ed25519.PrivateKey, error) { + block, _ := pem.Decode(privatekey) + if block == nil { + return nil, fmt.Errorf("decode: failed to read key") + } + priv, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parsePKCS8PrivateKey: invalid private key: %w", err) + } + secret, ok := priv.(ed25519.PrivateKey) + if !ok { + return nil, fmt.Errorf("ed25519DecodeFromPEM: invalid ed25519 Private Key - given type: %T", priv) + } + return secret, nil +} diff --git a/crypto/ed25519_test.go b/crypto/ed25519_test.go new file mode 100644 index 00000000..027c4697 --- /dev/null +++ b/crypto/ed25519_test.go @@ -0,0 +1,47 @@ +package crypto + +import ( + "crypto/ed25519" + "crypto/x509" + "encoding/pem" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEd25519GenerateKey(t *testing.T) { + key, err := Ed25519GenerateKey() + require.NoError(t, err) + assert.True(t, strings.HasPrefix(string(key), + "-----BEGIN PRIVATE KEY-----")) + assert.True(t, strings.HasSuffix(string(key), + "-----END PRIVATE KEY-----\n")) +} + +func TestEd25519DerivePublicKey(t *testing.T) { + _, err := Ed25519DerivePublicKey(nil) + assert.Error(t, err) + _, err = Ed25519DerivePublicKey([]byte(`-----BEGIN FOO----- + -----END FOO-----`)) + assert.Error(t, err) + + priv, err := Ed25519GenerateKey() + require.NoError(t, err) + pub, err := Ed25519DerivePublicKey(priv) + require.NoError(t, err) + block, _ := pem.Decode(pub) + assert.True(t, block != nil) + secret, err := ed25519DecodeFromPEM(priv) + require.NoError(t, err) + p, err := x509.ParsePKIXPublicKey(block.Bytes) + require.NoError(t, err) + pubKey, ok := p.(ed25519.PublicKey) + assert.True(t, ok) + assert.True(t, fmt.Sprintf("%x", p) == fmt.Sprintf("%x", secret.Public())) + msg := []byte("ed25519") + sig := ed25519.Sign(secret, msg) // Panics + assert.True(t, ed25519.Verify(pubKey, msg, sig)) +} |
