Skip to content

Commit

Permalink
Add support for ECC to the pkix package (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdtw authored and mbyczkowski committed May 12, 2023
1 parent 1928e04 commit fe92c91
Show file tree
Hide file tree
Showing 4 changed files with 319 additions and 58 deletions.
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ module github.com/square/certstrap
require (
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
github.com/urfave/cli v1.22.5
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 // indirect
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
go.step.sm/crypto v0.13.0
)

go 1.13
62 changes: 52 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,25 +1,67 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c h1:kQWxfPIHVLbgLzphqk3QUflDy9QdksZR4ygR807bpy0=
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.3 h1:FpNT6zq26xNpHZy08emi755QwzLPs6Pukqjlc7RfOMU=
github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
go.step.sm/crypto v0.13.0 h1:mQuP9Uu2FNmqCJNO0OTbvolnYXzONy4wdUBtUVcP1s8=
go.step.sm/crypto v0.13.0/go.mod h1:5YzQ85BujYBu6NH18jw7nFjwuRnDch35nLzH0ES5sKg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 h1:3erb+vDS8lU1sxfDHF4/hhWyaXnhIaO+7RgL4fDZORA=
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 h1:7ZDGnxgHAMw7thfC5bEos0RDAccZKxioiWBhfIe+tvw=
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
159 changes: 113 additions & 46 deletions pkix/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,27 @@
package pkix

import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"math/big"

"go.step.sm/crypto/pemutil"
)

const (
rsaPrivateKeyPEMBlockType = "RSA PRIVATE KEY"
rsaPrivateKeyPEMBlockType = "RSA PRIVATE KEY"
pkcs8PrivateKeyPEMBlockType = "PRIVATE KEY"
encryptedPKCS8PrivateKeyPEMBLockType = "ENCRYPTED PRIVATE KEY"
)

// CreateRSAKey creates a new Key using RSA algorithm
Expand All @@ -44,12 +51,36 @@ func CreateRSAKey(rsaBits int) (*Key, error) {
return NewKey(&priv.PublicKey, priv), nil
}

// CreateECDSAKey creates a new ECDSA key on the given curve
func CreateECDSAKey(c elliptic.Curve) (*Key, error) {
priv, err := ecdsa.GenerateKey(c, rand.Reader)
if err != nil {
return nil, err
}

return NewKey(&priv.PublicKey, priv), nil
}

// CreateEd25519Key creates a new Ed25519 key
func CreateEd25519Key() (*Key, error) {
_, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}

return NewKey(priv.Public(), priv), nil
}

// Key contains a public-private keypair
type Key struct {
Public crypto.PublicKey
Private crypto.PrivateKey
}

func NewKeyFromSigner(signer crypto.Signer) *Key {
return &Key{Public: signer.Public(), Private: signer}
}

// NewKey returns a new public-private keypair Key type
func NewKey(pub crypto.PublicKey, priv crypto.PrivateKey) *Key {
return &Key{Public: pub, Private: priv}
Expand All @@ -61,42 +92,63 @@ func NewKeyFromPrivateKeyPEM(data []byte) (*Key, error) {
if pemBlock == nil {
return nil, errors.New("cannot find the next PEM formatted block")
}
if pemBlock.Type != rsaPrivateKeyPEMBlockType || len(pemBlock.Headers) != 0 {
return nil, errors.New("unmatched type or headers")
}

priv, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
if err != nil {
return nil, err
var signer crypto.Signer
switch pemBlock.Type {
case rsaPrivateKeyPEMBlockType:
priv, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
if err != nil {
return nil, err
}
signer = priv
case pkcs8PrivateKeyPEMBlockType:
priv, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
if err != nil {
return nil, err
}
signer = priv.(crypto.Signer)
default:
return nil, fmt.Errorf("unknown PEM block type %q", pemBlock.Type)
}

return NewKey(&priv.PublicKey, priv), nil
return NewKeyFromSigner(signer), nil
}

// NewKeyFromEncryptedPrivateKeyPEM inits Key from encrypted PEM-format rsa private key bytes
// NewKeyFromEncryptedPrivateKeyPEM inits Key from encrypted PEM-format private key bytes
func NewKeyFromEncryptedPrivateKeyPEM(data []byte, password []byte) (*Key, error) {
pemBlock, _ := pem.Decode(data)
if pemBlock == nil {
return nil, errors.New("cannot find the next PEM formatted block")
}
if pemBlock.Type != rsaPrivateKeyPEMBlockType {
return nil, errors.New("unmatched type or headers")
}

b, err := x509.DecryptPEMBlock(pemBlock, password)
if err != nil {
return nil, err
}

priv, err := x509.ParsePKCS1PrivateKey(b)
if err != nil {
return nil, err
var signer crypto.Signer
switch pemBlock.Type {
case rsaPrivateKeyPEMBlockType:
b, err := x509.DecryptPEMBlock(pemBlock, password)
if err != nil {
return nil, err
}
priv, err := x509.ParsePKCS1PrivateKey(b)
if err != nil {
return nil, err
}
signer = priv
case encryptedPKCS8PrivateKeyPEMBLockType:
b, err := pemutil.DecryptPKCS8PrivateKey(pemBlock.Bytes, password)
if err != nil {
return nil, err
}
priv, err := x509.ParsePKCS8PrivateKey(b)
if err != nil {
return nil, err
}
signer = priv.(crypto.Signer)
default:
return nil, fmt.Errorf("unsupported PEM block type %q", pemBlock.Type)
}

return NewKey(&priv.PublicKey, priv), nil
return NewKeyFromSigner(signer), nil
}

// ExportPrivate exports PEM-format private key
// ExportPrivate exports PEM-format private key. RSA keys are exported
// as PKCS#1, ECDSA and Ed25519 keys are exported as PKCS#8.
func (k *Key) ExportPrivate() ([]byte, error) {
var privPEMBlock *pem.Block
switch priv := k.Private.(type) {
Expand All @@ -106,37 +158,48 @@ func (k *Key) ExportPrivate() ([]byte, error) {
Type: rsaPrivateKeyPEMBlockType,
Bytes: privBytes,
}
case *ecdsa.PrivateKey, ed25519.PrivateKey:
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return nil, err
}
privPEMBlock = &pem.Block{
Type: pkcs8PrivateKeyPEMBlockType,
Bytes: privBytes,
}
default:
return nil, errors.New("only RSA private key is supported")
return nil, fmt.Errorf("unsupported key type %T", k.Private)
}

buf := new(bytes.Buffer)
if err := pem.Encode(buf, privPEMBlock); err != nil {
return nil, err
}
return buf.Bytes(), nil
return pem.EncodeToMemory(privPEMBlock), nil
}

// ExportEncryptedPrivate exports encrypted PEM-format private key
func (k *Key) ExportEncryptedPrivate(password []byte) ([]byte, error) {
var privBytes []byte
var privPEMBlock *pem.Block
switch priv := k.Private.(type) {
case *rsa.PrivateKey:
privBytes = x509.MarshalPKCS1PrivateKey(priv)
privBytes := x509.MarshalPKCS1PrivateKey(priv)
block, err := x509.EncryptPEMBlock(rand.Reader, rsaPrivateKeyPEMBlockType, privBytes, password, x509.PEMCipher3DES)
if err != nil {
return nil, err
}
privPEMBlock = block
case *ecdsa.PrivateKey, ed25519.PrivateKey:
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return nil, err
}
block, err := pemutil.EncryptPKCS8PrivateKey(rand.Reader, privBytes, password, x509.PEMCipherAES256)
if err != nil {
return nil, err
}
privPEMBlock = block
default:
return nil, errors.New("only RSA private key is supported")
return nil, fmt.Errorf("unsupported key type %T", k.Private)
}

privPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, rsaPrivateKeyPEMBlockType, privBytes, password, x509.PEMCipher3DES)
if err != nil {
return nil, err
}

buf := new(bytes.Buffer)
if err := pem.Encode(buf, privPEMBlock); err != nil {
return nil, err
}
return buf.Bytes(), nil
return pem.EncodeToMemory(privPEMBlock), nil
}

// rsaPublicKey reflects the ASN.1 structure of a PKCS#1 public key.
Expand All @@ -159,8 +222,12 @@ func GenerateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) {
if err != nil {
return nil, err
}
case *ecdsa.PublicKey:
pubBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
case ed25519.PublicKey:
pubBytes = pub
default:
return nil, errors.New("only RSA public key is supported")
return nil, fmt.Errorf("unsupported key type %T", pub)
}

hash := sha1.Sum(pubBytes)
Expand Down
Loading

0 comments on commit fe92c91

Please sign in to comment.