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 ECC to the pkix package #130

Merged
merged 3 commits into from
Dec 2, 2021
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
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this something we need this 3p library for? (so nothing in stdlib or x/crypto that we can use?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately not, see this discussion: golang/go#8860 (comment)

That thread is where I found this package, which looks like it may get considered for inclusion into x/crypto.

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