Skip to content

Commit

Permalink
feat: add 'PEMEncodedKey' which allows to transport keys in YAML
Browse files Browse the repository at this point in the history
Key can be restored back into *RSAKey, *Ed25519Key with public key
recovered for further handling.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
  • Loading branch information
smira authored and talos-bot committed Jan 22, 2021
1 parent 562c3b6 commit 751c95a
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 0 deletions.
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
module github.com/talos-systems/crypto

go 1.14

require (
github.com/stretchr/testify v1.7.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
109 changes: 109 additions & 0 deletions x509/x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ type PEMEncodedCertificateAndKey struct {
Key []byte
}

// PEMEncodedKey represents a PEM encoded private key.
type PEMEncodedKey struct {
Key []byte
}

// Options is the functional options struct.
type Options struct {
CommonName string
Expand Down Expand Up @@ -749,6 +754,110 @@ func (p *PEMEncodedCertificateAndKey) GetEd25519Key() (ed25519.PrivateKey, error
return ed25519Key, nil
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for
// PEMEncodedKey. It is expected that the Key is a base64
// encoded string in the YAML file. This function decodes the strings into byte
// slices.
func (p *PEMEncodedKey) UnmarshalYAML(unmarshal func(interface{}) error) error {
var aux struct {
Key string `yaml:"key"`
}

if err := unmarshal(&aux); err != nil {
return err
}

decodedKey, err := base64.StdEncoding.DecodeString(aux.Key)
if err != nil {
return err
}

p.Key = decodedKey

return nil
}

// MarshalYAML implements the yaml.Marshaler interface for
// PEMEncodedCertificateAndKey. It is expected that the Crt and Key are a base64
// encoded string in the YAML file. This function encodes the byte slices into
// strings.
func (p *PEMEncodedKey) MarshalYAML() (interface{}, error) {
var aux struct {
Key string `yaml:"key"`
}

aux.Key = base64.StdEncoding.EncodeToString(p.Key)

return aux, nil
}

// GetRSAKey parses PEM-encoded RSA key.
func (p *PEMEncodedKey) GetRSAKey() (*RSAKey, error) {
block, _ := pem.Decode(p.Key)
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block")
}

key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse RSA key: %w", err)
}

publicKeyBytes, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
if err != nil {
return nil, fmt.Errorf("failed to encode public key: %w", err)
}

publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
})

return &RSAKey{
keyRSA: key,
KeyPEM: p.Key,
PublicKeyPEM: publicKeyPEM,
}, nil
}

// GetEd25519Key parses PEM-encoded Ed25519 key.
func (p *PEMEncodedKey) GetEd25519Key() (*Ed25519Key, error) {
block, _ := pem.Decode(p.Key)
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block")
}

key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse Ed25519 key: %w", err)
}

ed25519Key, ok := key.(ed25519.PrivateKey)

if !ok {
return nil, fmt.Errorf("failed parsing Ed25519 key, got wrong key type")
}

publicKey := ed25519Key.Public()

pubBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return nil, fmt.Errorf("failed encoding public key: %w", err)
}

pubPEM := pem.EncodeToMemory(&pem.Block{
Type: "ED25519 PUBLIC KEY",
Bytes: pubBytes,
})

return &Ed25519Key{
PrivateKey: ed25519Key,
PublicKey: publicKey.(ed25519.PublicKey),
PrivateKeyPEM: p.Key,
PublicKeyPEM: pubPEM,
}, nil
}

// NewCertficateAndKey generates a new key and certificate signed by a CA.
//
//nolint: gocyclo
Expand Down
50 changes: 50 additions & 0 deletions x509/x509_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ package x509_test
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"

"github.com/talos-systems/crypto/x509"
)

Expand Down Expand Up @@ -61,3 +65,49 @@ func TestNewKeyPair(t *testing.T) {
})
}
}

func TestPEMEncodedKeyRSA(t *testing.T) {
key, err := x509.NewRSAKey()
require.NoError(t, err)

encoded := &x509.PEMEncodedKey{
Key: key.KeyPEM,
}

marshaled, err := yaml.Marshal(encoded)
require.NoError(t, err)

var decoded x509.PEMEncodedKey

require.NoError(t, yaml.Unmarshal(marshaled, &decoded))

decodedKey, err := decoded.GetRSAKey()
require.NoError(t, err)

assert.Equal(t, key.KeyPEM, decodedKey.KeyPEM)
assert.Equal(t, key.PublicKeyPEM, decodedKey.PublicKeyPEM)
}

func TestPEMEncodedKeyEd25519(t *testing.T) {
key, err := x509.NewEd25519Key()
require.NoError(t, err)

encoded := &x509.PEMEncodedKey{
Key: key.PrivateKeyPEM,
}

marshaled, err := yaml.Marshal(encoded)
require.NoError(t, err)

var decoded x509.PEMEncodedKey

require.NoError(t, yaml.Unmarshal(marshaled, &decoded))

decodedKey, err := decoded.GetEd25519Key()
require.NoError(t, err)

assert.Equal(t, key.PrivateKey, decodedKey.PrivateKey)
assert.Equal(t, key.PublicKey, decodedKey.PublicKey)
assert.Equal(t, key.PrivateKeyPEM, decodedKey.PrivateKeyPEM)
assert.Equal(t, key.PublicKeyPEM, decodedKey.PublicKeyPEM)
}

0 comments on commit 751c95a

Please sign in to comment.