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

Add support for Ed25519 #1900

Merged
merged 6 commits into from
Dec 17, 2023
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
81 changes: 81 additions & 0 deletions crypto/ed25519.go
Original file line number Diff line number Diff line change
@@ -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
}
47 changes: 47 additions & 0 deletions crypto/ed25519_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
50 changes: 50 additions & 0 deletions docs-src/content/functions/crypto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,56 @@ funcs:
+hIz6+EUt/Db51awO7iCuRly5L4TZ+CnMAsIbtUOqsqwSQDtv0AclAuogmCst75o
aztsmrD79OXXnhUlURI=
-----END PUBLIC KEY-----
- name: crypto.Ed25519GenerateKey
experimental: true
# released: v4.0.0
description: |
Generate a new Ed25519 Private Key and output in
PEM-encoded PKCS#8 ASN.1 DER form.
examples:
- |
$ gomplate -i '{{ crypto.Ed25519GenerateKey }}'
-----BEGIN PRIVATE KEY-----
...
- name: crypto.Ed25519GenerateKeyFromSeed
experimental: true
# released: v4.0.0
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
required: 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: v4.0.0
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: |
Expand Down
94 changes: 94 additions & 0 deletions docs/content/functions/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,100 @@ aztsmrD79OXXnhUlURI=
-----END PUBLIC KEY-----
```

## `crypto.Ed25519GenerateKey`_(unreleased)_ _(experimental)_
**Unreleased:** _This function is in development, and not yet available in released builds of gomplate._
**Experimental:** This function is [_experimental_][experimental] and may be enabled with the [`--experimental`][experimental] flag.

[experimental]: ../config/#experimental

Generate a new Ed25519 Private Key and output in
PEM-encoded PKCS#8 ASN.1 DER form.

### Usage

```
crypto.Ed25519GenerateKey
```


### Examples

```console
$ gomplate -i '{{ crypto.Ed25519GenerateKey }}'
-----BEGIN PRIVATE KEY-----
...
```

## `crypto.Ed25519GenerateKeyFromSeed`_(unreleased)_ _(experimental)_
**Unreleased:** _This function is in development, and not yet available in released builds of gomplate._
**Experimental:** This function is [_experimental_][experimental] and may be enabled with the [`--experimental`][experimental] flag.

[experimental]: ../config/#experimental

Generate a new Ed25519 Private Key from a random seed and output in
PEM-encoded PKCS#8 ASN.1 DER form.

### Usage

```
crypto.Ed25519GenerateKeyFromSeed encoding seed
```
```
seed | crypto.Ed25519GenerateKeyFromSeed encoding
```

### Arguments

| name | description |
|------|-------------|
| `encoding` | _(required)_ the encoding that the seed is in (`hex` or `base64`) |
| `seed` | _(required)_ the random seed encoded in either base64 or hex |

### Examples

```console
$ gomplate -i '{{ crypto.Ed25519GenerateKeyFromSeed "base64" "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=" }}'
-----BEGIN PRIVATE KEY-----
...
```

## `crypto.Ed25519DerivePublicKey`_(unreleased)_ _(experimental)_
**Unreleased:** _This function is in development, and not yet available in released builds of gomplate._
**Experimental:** This function is [_experimental_][experimental] and may be enabled with the [`--experimental`][experimental] flag.

[experimental]: ../config/#experimental

Derive a public key from an Ed25519 private key and output in PKIX
ASN.1 DER form.

### Usage

```
crypto.Ed25519DerivePublicKey key
```
```
key | crypto.Ed25519DerivePublicKey
```

### Arguments

| name | description |
|------|-------------|
| `key` | _(required)_ the private key to derive a public key from |

### Examples

```console
$ gomplate -i '{{ crypto.Ed25519GenerateKey | crypto.Ed25519DerivePublicKey }}'
-----BEGIN PUBLIC KEY-----
...
```
```console
$ gomplate -d key=priv.pem -i '{{ crypto.Ed25519DerivePublicKey (include "key") }}'
-----BEGIN PUBLIC KEY-----
...PK
```

## `crypto.PBKDF2`

Run the Password-Based Key Derivation Function #2 as defined in
Expand Down
49 changes: 49 additions & 0 deletions funcs/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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) {
Expand Down
Loading
Loading