summaryrefslogtreecommitdiff
path: root/crypto
diff options
context:
space:
mode:
authorJared Horvat <horvski@gmail.com>2023-12-17 13:25:56 -0700
committerGitHub <noreply@github.com>2023-12-17 15:25:56 -0500
commit914960f60a3c8ccb56d018b2d3fe8484ee9976d7 (patch)
tree30369f20cb0206e2e31dd1618a230bd0cd39a614 /crypto
parent36fb3f05006bdab3f9b96400a9043cfe7430cc1d (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.go81
-rw-r--r--crypto/ed25519_test.go47
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))
+}