From 9087bd10bcedb896eacd82a7d33d4466c32a1ae1 Mon Sep 17 00:00:00 2001 From: horvski Date: Tue, 14 Nov 2023 13:51:16 -0700 Subject: [PATCH] Added changes from Dave's review --- crypto/eddsa.go | 21 ++++++----- docs-src/content/functions/crypto.yml | 51 +++++++++++++++++++++++++++ funcs/crypto.go | 49 +++++++++++++++++++++++++ funcs/crypto_test.go | 50 ++++++++++++++++++++++++++ 4 files changed, 162 insertions(+), 9 deletions(-) diff --git a/crypto/eddsa.go b/crypto/eddsa.go index 7f0e368ef..fcb5dd816 100644 --- a/crypto/eddsa.go +++ b/crypto/eddsa.go @@ -19,9 +19,12 @@ func Ed25519GenerateKey() ([]byte, error) { } // Ed25519GenerateKeyFromSeed returns a PEM encoded Ed25519 Private Key from -// `seed`. Panics if len(seed) is not SeedSize. +// `seed`. Returns error if len(seed) is not ed25519.SeedSize (32). func Ed25519GenerateKeyFromSeed(seed []byte) ([]byte, error) { - return pemEncodeEdPrivateKey(ed25519.NewKeyFromSeed(seed)) // Panics + if len(seed) != ed25519.SeedSize { + return nil, fmt.Errorf("Ed25519GenerateKeyFromSeed: 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 @@ -29,11 +32,11 @@ func Ed25519GenerateKeyFromSeed(seed []byte) ([]byte, error) { func Ed25519DerivePublicKey(privatekey []byte) ([]byte, error) { secret, err := ed25519DecodeFromPEM(privatekey) if err != nil { - return nil, fmt.Errorf("EDDSADerivePublicKey: could not decode private key") + 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("EDDSADerivePublicKey: failed to marshal PKIX public key: %w", err) + return nil, fmt.Errorf("MarshalPKIXPublicKey: failed to marshal PKIX public key: %w", err) } return pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", @@ -45,7 +48,7 @@ func Ed25519DerivePublicKey(privatekey []byte) ([]byte, error) { func pemEncodeEdPrivateKey(secret ed25519.PrivateKey) ([]byte, error) { der, err := x509.MarshalPKCS8PrivateKey(secret) if err != nil { - return nil, fmt.Errorf("pemEncodeEdPrivateKey: failed to marshal ECDSA private key: %w", err) + return nil, fmt.Errorf("MarshalPKCS8PrivateKey: failed to marshal ed25519 private key: %w", err) } block := &pem.Block{ Type: "PRIVATE KEY", @@ -54,7 +57,7 @@ func pemEncodeEdPrivateKey(secret ed25519.PrivateKey) ([]byte, error) { buf := &bytes.Buffer{} err = pem.Encode(buf, block) if err != nil { - return nil, fmt.Errorf("pemEncodeEdPrivateKey: failed to encode generated EDDSA private key: pem encoding failed: %w", err) + return nil, fmt.Errorf("Encode: failed to encode generated ed25519 private key: PEM encoding failed: %w", err) } return buf.Bytes(), nil } @@ -64,15 +67,15 @@ func pemEncodeEdPrivateKey(secret ed25519.PrivateKey) ([]byte, error) { func ed25519DecodeFromPEM(privatekey []byte) (ed25519.PrivateKey, error) { block, _ := pem.Decode(privatekey) if block == nil { - return nil, fmt.Errorf("ed25519DecodeFromPEM: failed to read key: no key found") + return nil, fmt.Errorf("Decode: failed to read key") } priv, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { - return nil, fmt.Errorf("ed25519DecodeFromPEM: invalid private key: %w", err) + return nil, fmt.Errorf("ParsePKCS8PrivateKey: invalid private key: %w", err) } secret, ok := priv.(ed25519.PrivateKey) if !ok { - return nil, fmt.Errorf("ed25519DecodeFromPEM: invalid private key: %w", err) + return nil, fmt.Errorf("ed25519DecodeFromPEM: invalid ed25519 Private Key - given type: %T", priv) } return secret, nil } diff --git a/docs-src/content/functions/crypto.yml b/docs-src/content/functions/crypto.yml index f7640baf3..233c7c666 100644 --- a/docs-src/content/functions/crypto.yml +++ b/docs-src/content/functions/crypto.yml @@ -158,6 +158,57 @@ funcs: +hIz6+EUt/Db51awO7iCuRly5L4TZ+CnMAsIbtUOqsqwSQDtv0AclAuogmCst75o aztsmrD79OXXnhUlURI= -----END PUBLIC KEY----- + - name: crypto.Ed25519GenerateKey + experimental: true + released: v3.11.5 + description: | + Generate a new Ed25519 Private Key and output in + PEM-encoded PKCS#8 ASN.1 DER form. + pipeline: true + examples: + - | + $ gomplate -i '{{ crypto.Ed25519GenerateKey }}' + -----BEGIN PRIVATE KEY----- + ... + - name: crypto.Ed25519GenerateKeyFromSeed + experimental: true + released: v3.11.5 + description: | + Generate a new Ed25519 Private Key from a random seed and output in + PEM-encoded PKCS#8 ASN.1 DER form. + pipeline: true + arguments: + - name: encoding + required: true + description: the encoding that the seed is in (`hex` or `base64`) + - name: seed + reqruired: true + description: the random seed encoded in either base64 or hex + examples: + - | + $ gomplate -i '{{ crypto.Ed25519GenerateKeyFromSeed base64 MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA= }}' + -----BEGIN PRIVATE KEY----- + ... + - name: crypto.Ed25519DerivePublicKey + experimental: true + released: v3.11.5 + description: | + Derive a public key from an Ed25519 private key and output in PKIX + ASN.1 DER form. + pipeline: true + arguments: + - name: key + required: true + description: the private key to derive a public key from + examples: + - | + $ gomplate -i '{{ crypto.Ed25519GenerateKey | crypto.Ed25519DerivePublicKey }}' + -----BEGIN PUBLIC KEY----- + ... + - | + $ gomplate -d key=priv.pem -i '{{ crypto.Ed25519DerivePublicKey (include "key") }}' + -----BEGIN PUBLIC KEY----- + ...PK - name: crypto.PBKDF2 released: v2.3.0 description: | diff --git a/funcs/crypto.go b/funcs/crypto.go index 41c752c78..e3628fadf 100644 --- a/funcs/crypto.go +++ b/funcs/crypto.go @@ -7,8 +7,11 @@ import ( "crypto/sha1" //nolint: gosec "crypto/sha256" "crypto/sha512" + "encoding/base64" + "encoding/hex" "fmt" "strings" + "unicode/utf8" "golang.org/x/crypto/bcrypt" @@ -287,6 +290,52 @@ func (f *CryptoFuncs) ECDSADerivePublicKey(privateKey string) (string, error) { return string(out), err } +// Ed25519GenerateKey - +// Experimental! +func (f *CryptoFuncs) Ed25519GenerateKey() (string, error) { + if err := checkExperimental(f.ctx); err != nil { + return "", err + } + out, err := crypto.Ed25519GenerateKey() + return string(out), err +} + +// Ed25519GenerateKeyFromSeed - +// Experimental! +func (f *CryptoFuncs) Ed25519GenerateKeyFromSeed(encoding, seed string) (string, error) { + if err := checkExperimental(f.ctx); err != nil { + return "", err + } + if !utf8.ValidString(seed) { + return "", fmt.Errorf("given seed is not valid UTF-8") // Don't print out seed (private). + } + var seedB []byte + var err error + switch encoding { + case "base64": + seedB, err = base64.StdEncoding.DecodeString(seed) + case "hex": + seedB, err = hex.DecodeString(seed) + default: + return "", fmt.Errorf("invalid encoding given: %s - only 'hex' or 'base64' are valid options", encoding) + } + if err != nil { + return "", fmt.Errorf("could not decode given seed: %w", err) + } + out, err := crypto.Ed25519GenerateKeyFromSeed(seedB) + return string(out), err +} + +// Ed25519DerivePublicKey - +// Experimental! +func (f *CryptoFuncs) Ed25519DerivePublicKey(privateKey string) (string, error) { + if err := checkExperimental(f.ctx); err != nil { + return "", err + } + out, err := crypto.Ed25519DerivePublicKey([]byte(privateKey)) + return string(out), err +} + // EncryptAES - // Experimental! func (f *CryptoFuncs) EncryptAES(key string, args ...interface{}) ([]byte, error) { diff --git a/funcs/crypto_test.go b/funcs/crypto_test.go index 90f6bdff5..4d1b56608 100644 --- a/funcs/crypto_test.go +++ b/funcs/crypto_test.go @@ -2,6 +2,7 @@ package funcs import ( "context" + "encoding/base64" "strconv" "strings" "testing" @@ -167,6 +168,55 @@ func TestECDSADerivePublicKey(t *testing.T) { "-----END PUBLIC KEY-----\n")) } +func TestEd25519GenerateKey(t *testing.T) { + c := testCryptoNS() + key, err := c.Ed25519GenerateKey() + require.NoError(t, err) + + assert.True(t, strings.HasPrefix(key, + "-----BEGIN PRIVATE KEY-----")) + assert.True(t, strings.HasSuffix(key, + "-----END PRIVATE KEY-----\n")) +} + +func TestEd25519GenerateKeyFromSeed(t *testing.T) { + c := testCryptoNS() + enc := "" + seed := "" + _, err := c.Ed25519GenerateKeyFromSeed(enc, seed) + assert.Error(t, err) + + enc = "base64" + seed = "0000000000000000000000000000000" // 31 bytes, instead of wanted 32. + _, err = c.Ed25519GenerateKeyFromSeed(enc, seed) + assert.Error(t, err) + + seed += "0" // 32 bytes. + b64seed := base64.StdEncoding.EncodeToString([]byte(seed)) + key, err := c.Ed25519GenerateKeyFromSeed(enc, b64seed) + require.NoError(t, err) + + assert.True(t, strings.HasPrefix(key, + "-----BEGIN PRIVATE KEY-----")) + assert.True(t, strings.HasSuffix(key, + "-----END PRIVATE KEY-----\n")) +} + +func TestEd25519DerivePublicKey(t *testing.T) { + c := testCryptoNS() + + _, err := c.Ed25519DerivePublicKey("") + assert.Error(t, err) + + key, _ := c.Ed25519GenerateKey() + pub, err := c.Ed25519DerivePublicKey(key) + require.NoError(t, err) + assert.True(t, strings.HasPrefix(pub, + "-----BEGIN PUBLIC KEY-----")) + assert.True(t, strings.HasSuffix(pub, + "-----END PUBLIC KEY-----\n")) +} + func TestRSACrypt(t *testing.T) { t.Parallel()