From ef61c170b166212e14d91d5238cb4f4c824a688c Mon Sep 17 00:00:00 2001 From: paul cannon Date: Thu, 7 Feb 2019 12:40:28 -0600 Subject: [PATCH] Consolidate key/cert/signature encoding and decoding (#1243) --- cmd/identity/batch.go | 4 +- cmd/identity/main.go | 2 +- cmd/storj-sim/inmemory.go | 7 +- internal/testplanet/gen_identities.go | 13 +- pkg/auth/signedMessage.go | 4 +- pkg/bwagreement/server_test.go | 3 +- pkg/certificates/certificates_test.go | 5 +- pkg/identity/certificate_authority.go | 12 +- pkg/identity/identity.go | 29 +-- pkg/identity/identity_test.go | 24 +- pkg/identity/utils.go | 103 --------- pkg/peertls/peertls.go | 20 +- pkg/pkcrypto/common.go | 22 +- pkg/pkcrypto/encoding.go | 320 ++++++++++++++++++++++---- pkg/pkcrypto/signing.go | 16 +- pkg/server/options.go | 3 +- 16 files changed, 340 insertions(+), 247 deletions(-) diff --git a/cmd/identity/batch.go b/cmd/identity/batch.go index 717c81684..159005fd4 100644 --- a/cmd/identity/batch.go +++ b/cmd/identity/batch.go @@ -129,9 +129,9 @@ func saveIdentityTar(path string, key *ecdsa.PrivateKey, id storj.NodeID) error tw := tar.NewWriter(tarData) caCertBytes, caCertErr := peertls.ChainBytes(ca.Cert) - caKeyBytes, caKeyErr := pkcrypto.KeyBytes(ca.Key) + caKeyBytes, caKeyErr := pkcrypto.PrivateKeyToPEM(ca.Key) identCertBytes, identCertErr := peertls.ChainBytes(ident.Leaf, ident.CA) - identKeyBytes, identKeyErr := pkcrypto.KeyBytes(ident.Key) + identKeyBytes, identKeyErr := pkcrypto.PrivateKeyToPEM(ident.Key) if err := errs.Combine(caCertErr, caKeyErr, identCertErr, identKeyErr); err != nil { return err } diff --git a/cmd/identity/main.go b/cmd/identity/main.go index 7b9cb4802..37ef388da 100644 --- a/cmd/identity/main.go +++ b/cmd/identity/main.go @@ -159,7 +159,7 @@ func cmdAuthorize(cmd *cobra.Command, args []string) error { return errs.New("error occurred while signing certificate: %s\n(identity files were still generated and saved, if you try again existing files will be loaded)", err) } - signedChain, err := identity.ParseCertChain(signedChainBytes) + signedChain, err := pkcrypto.CertsFromDER(signedChainBytes) if err != nil { return nil } diff --git a/cmd/storj-sim/inmemory.go b/cmd/storj-sim/inmemory.go index e739f18da..bada163d4 100644 --- a/cmd/storj-sim/inmemory.go +++ b/cmd/storj-sim/inmemory.go @@ -7,7 +7,6 @@ import ( "bytes" "context" "encoding/base64" - "encoding/pem" "fmt" "os" "os/exec" @@ -101,14 +100,14 @@ func inmemoryTest(flags *Flags, command string, args []string) error { } var chainPEM bytes.Buffer - errLeaf := pem.Encode(&chainPEM, pkcrypto.NewCertBlock(identity.Leaf.Raw)) - errCA := pem.Encode(&chainPEM, pkcrypto.NewCertBlock(identity.CA.Raw)) + errLeaf := pkcrypto.WriteCertPEM(&chainPEM, identity.Leaf) + errCA := pkcrypto.WriteCertPEM(&chainPEM, identity.CA) if errLeaf != nil || errCA != nil { return errs.Combine(errLeaf, errCA, planet.Shutdown()) } var key bytes.Buffer - errKey := pkcrypto.WriteKey(&key, identity.Key) + errKey := pkcrypto.WritePrivateKeyPEM(&key, identity.Key) if errKey != nil { return errs.Combine(errKey, planet.Shutdown()) } diff --git a/internal/testplanet/gen_identities.go b/internal/testplanet/gen_identities.go index 3dc8bb488..3be0c8d1d 100644 --- a/internal/testplanet/gen_identities.go +++ b/internal/testplanet/gen_identities.go @@ -9,7 +9,6 @@ package main import ( "bytes" "context" - "encoding/pem" "flag" "fmt" "go/format" @@ -58,7 +57,7 @@ func main() { } var keys bytes.Buffer - err = peertls.WriteKey(&keys, identity.Key) + err = peertls.WriteKeyPEM(&keys, identity.Key) if err != nil { panic(err) } @@ -86,13 +85,3 @@ func main() { panic(err) } } - -func encodeBlocks(blocks ...*pem.Block) ([]byte, error) { - var buf bytes.Buffer - for _, block := range blocks { - if err := pem.Encode(&buf, block); err != nil { - return nil, err - } - } - return buf.Bytes(), nil -} diff --git a/pkg/auth/signedMessage.go b/pkg/auth/signedMessage.go index 73450426c..e79ddb830 100644 --- a/pkg/auth/signedMessage.go +++ b/pkg/auth/signedMessage.go @@ -5,7 +5,6 @@ package auth import ( "crypto/ecdsa" - "crypto/x509" "github.com/gogo/protobuf/proto" "github.com/gtank/cryptopasta" @@ -13,6 +12,7 @@ import ( "storj.io/storj/pkg/identity" "storj.io/storj/pkg/peertls" + "storj.io/storj/pkg/pkcrypto" "storj.io/storj/pkg/storj" ) @@ -128,7 +128,7 @@ func VerifyMsg(msg SignableMessage, signer storj.NodeID) error { } func parseECDSA(rawCert []byte) (*ecdsa.PublicKey, error) { - cert, err := x509.ParseCertificate(rawCert) + cert, err := pkcrypto.CertFromDER(rawCert) if err != nil { return nil, ErrVerify.Wrap(err) } diff --git a/pkg/bwagreement/server_test.go b/pkg/bwagreement/server_test.go index aa0491515..cccd6e0ec 100644 --- a/pkg/bwagreement/server_test.go +++ b/pkg/bwagreement/server_test.go @@ -293,7 +293,8 @@ func testDatabase(ctx context.Context, t *testing.T, bwdb bwagreement.DB) { assert.NoError(t, err) rba.Signature = []byte("invalid") reply, err := satellite.BandwidthAgreements(ctxSN1, rba) - assert.True(t, auth.ErrSigLen.Has(err) && pb.ErrRenter.Has(err), err.Error()) + assert.Error(t, err) + assert.True(t, pb.ErrRenter.Has(err), err.Error()) assert.Equal(t, pb.AgreementsSummary_REJECTED, reply.Status) } diff --git a/pkg/certificates/certificates_test.go b/pkg/certificates/certificates_test.go index 788061637..eb7ddfbb9 100644 --- a/pkg/certificates/certificates_test.go +++ b/pkg/certificates/certificates_test.go @@ -26,6 +26,7 @@ import ( "storj.io/storj/internal/testplanet" "storj.io/storj/pkg/identity" "storj.io/storj/pkg/pb" + "storj.io/storj/pkg/pkcrypto" "storj.io/storj/pkg/server" "storj.io/storj/pkg/transport" "storj.io/storj/pkg/utils" @@ -663,7 +664,7 @@ func TestCertificateSigner_Sign_E2E(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, signedChainBytes) - signedChain, err := identity.ParseCertChain(signedChainBytes) + signedChain, err := pkcrypto.CertsFromDER(signedChainBytes) require.NoError(t, err) assert.Equal(t, clientIdent.CA.RawTBSCertificate, signedChain[0].RawTBSCertificate) @@ -829,7 +830,7 @@ func TestCertificateSigner_Sign(t *testing.T) { require.NotNil(t, res) require.NotEmpty(t, res.Chain) - signedChain, err := identity.ParseCertChain(res.Chain) + signedChain, err := pkcrypto.CertsFromDER(res.Chain) require.NoError(t, err) assert.Equal(t, clientIdent.CA.RawTBSCertificate, signedChain[0].RawTBSCertificate) diff --git a/pkg/identity/certificate_authority.go b/pkg/identity/certificate_authority.go index 81b6b5e1e..141ca2ffb 100644 --- a/pkg/identity/certificate_authority.go +++ b/pkg/identity/certificate_authority.go @@ -10,7 +10,6 @@ import ( "crypto/ecdsa" "crypto/rand" "crypto/x509" - "encoding/pem" "fmt" "io" "io/ioutil" @@ -238,10 +237,9 @@ func (fc FullCAConfig) Load() (*FullCertificateAuthority, error) { if err != nil { return nil, peertls.ErrNotExist.Wrap(err) } - kp, _ := pem.Decode(kb) - k, err := x509.ParseECPrivateKey(kp.Bytes) + k, err := pkcrypto.PrivateKeyFromPEM(kb) if err != nil { - return nil, errs.New("unable to parse EC private key: %v", err) + return nil, err } return &FullCertificateAuthority{ @@ -271,7 +269,7 @@ func (fc FullCAConfig) Save(ca *FullCertificateAuthority) error { } if fc.KeyPath != "" { - if err := pkcrypto.WriteKey(&keyData, ca.Key); err != nil { + if err := pkcrypto.WritePrivateKeyPEM(&keyData, ca.Key); err != nil { writeErrs.Add(err) return writeErrs.Err() } @@ -299,7 +297,7 @@ func (pc PeerCAConfig) Load() (*PeerCertificateAuthority, error) { return nil, peertls.ErrNotExist.Wrap(err) } - chain, err := DecodeAndParseChainPEM(chainPEM) + chain, err := pkcrypto.CertsFromPEM(chainPEM) if err != nil { return nil, errs.New("failed to load identity %#v: %v", pc.CertPath, err) @@ -408,7 +406,7 @@ func (ca *FullCertificateAuthority) Sign(cert *x509.Certificate) (*x509.Certific return nil, errs.Wrap(err) } - signedCert, err := x509.ParseCertificate(signedCertBytes) + signedCert, err := pkcrypto.CertFromDER(signedCertBytes) if err != nil { return nil, errs.Wrap(err) } diff --git a/pkg/identity/identity.go b/pkg/identity/identity.go index 113cc177e..765e318ca 100644 --- a/pkg/identity/identity.go +++ b/pkg/identity/identity.go @@ -85,15 +85,11 @@ func FullIdentityFromPEM(chainPEM, keyPEM []byte) (*FullIdentity, error) { return nil, err } - keysBytes, err := decodePEM(keyPEM) - if err != nil { - return nil, errs.Wrap(err) - } // NB: there shouldn't be multiple keys in the key file but if there // are, this uses the first one - key, err := x509.ParseECPrivateKey(keysBytes[0]) + key, err := pkcrypto.PrivateKeyFromPEM(keyPEM) if err != nil { - return nil, errs.New("unable to parse EC private key: %v", err) + return nil, err } return &FullIdentity{ @@ -108,12 +104,12 @@ func FullIdentityFromPEM(chainPEM, keyPEM []byte) (*FullIdentity, error) { // PeerIdentityFromPEM loads a PeerIdentity from a certificate chain and // private key PEM-encoded bytes func PeerIdentityFromPEM(chainPEM []byte) (*PeerIdentity, error) { - chain, err := DecodeAndParseChainPEM(chainPEM) + chain, err := pkcrypto.CertsFromPEM(chainPEM) if err != nil { return nil, errs.Wrap(err) } if len(chain) < peertls.CAIndex+1 { - return nil, ErrChainLength.New("identity chain does not contain a CA certificate") + return nil, pkcrypto.ErrChainLength.New("identity chain does not contain a CA certificate") } nodeID, err := NodeIDFromKey(chain[peertls.CAIndex].PublicKey) if err != nil { @@ -128,19 +124,6 @@ func PeerIdentityFromPEM(chainPEM []byte) (*PeerIdentity, error) { }, nil } -// ParseCertChain converts a chain of certificate bytes into x509 certs -func ParseCertChain(chain [][]byte) ([]*x509.Certificate, error) { - c := make([]*x509.Certificate, len(chain)) - for i, ct := range chain { - cp, err := x509.ParseCertificate(ct) - if err != nil { - return nil, errs.Wrap(err) - } - c[i] = cp - } - return c, nil -} - // PeerIdentityFromCerts loads a PeerIdentity from a pair of leaf and ca x509 certificates func PeerIdentityFromCerts(leaf, ca *x509.Certificate, rest []*x509.Certificate) (*PeerIdentity, error) { i, err := NodeIDFromKey(ca.PublicKey) @@ -192,7 +175,7 @@ func NodeIDFromCertPath(certPath string) (storj.NodeID, error) { // NodeIDFromPEM loads a node ID from certificate bytes func NodeIDFromPEM(pemBytes []byte) (storj.NodeID, error) { - chain, err := DecodeAndParseChainPEM(pemBytes) + chain, err := pkcrypto.CertsFromPEM(pemBytes) if err != nil { return storj.NodeID{}, Error.New("invalid identity certificate") } @@ -299,7 +282,7 @@ func (ic Config) Save(fi *FullIdentity) error { } if ic.KeyPath != "" { - writeKeyErr = pkcrypto.WriteKey(&keyData, fi.Key) + writeKeyErr = pkcrypto.WritePrivateKeyPEM(&keyData, fi.Key) writeKeyDataErr = writeKeyData(ic.KeyPath, keyData.Bytes()) } diff --git a/pkg/identity/identity_test.go b/pkg/identity/identity_test.go index d9d836d67..ef1699e1c 100644 --- a/pkg/identity/identity_test.go +++ b/pkg/identity/identity_test.go @@ -7,8 +7,6 @@ import ( "bytes" "context" "crypto/ecdsa" - "crypto/x509" - "encoding/pem" "os" "runtime" "testing" @@ -70,15 +68,11 @@ func TestFullIdentityFromPEM(t *testing.T) { assert.NotEmpty(t, leafCert) chainPEM := bytes.NewBuffer([]byte{}) - assert.NoError(t, pem.Encode(chainPEM, pkcrypto.NewCertBlock(leafCert.Raw))) - assert.NoError(t, pem.Encode(chainPEM, pkcrypto.NewCertBlock(caCert.Raw))) - - leafKeyBytes, err := x509.MarshalECPrivateKey(leafKey) - assert.NoError(t, err) - assert.NotEmpty(t, leafKeyBytes) + assert.NoError(t, pkcrypto.WriteCertPEM(chainPEM, leafCert)) + assert.NoError(t, pkcrypto.WriteCertPEM(chainPEM, caCert)) keyPEM := bytes.NewBuffer([]byte{}) - assert.NoError(t, pem.Encode(keyPEM, pkcrypto.NewKeyBlock(leafKeyBytes))) + assert.NoError(t, pkcrypto.WritePrivateKeyPEM(keyPEM, leafKey)) fullIdent, err := identity.FullIdentityFromPEM(chainPEM.Bytes(), keyPEM.Bytes()) assert.NoError(t, err) @@ -98,22 +92,18 @@ func TestConfig_SaveIdentity(t *testing.T) { fi := pregeneratedIdentity(t) chainPEM := bytes.NewBuffer([]byte{}) - assert.NoError(t, pem.Encode(chainPEM, pkcrypto.NewCertBlock(fi.Leaf.Raw))) - assert.NoError(t, pem.Encode(chainPEM, pkcrypto.NewCertBlock(fi.CA.Raw))) + assert.NoError(t, pkcrypto.WriteCertPEM(chainPEM, fi.Leaf)) + assert.NoError(t, pkcrypto.WriteCertPEM(chainPEM, fi.CA)) privateKey, ok := fi.Key.(*ecdsa.PrivateKey) assert.True(t, ok) assert.NotEmpty(t, privateKey) - keyBytes, err := x509.MarshalECPrivateKey(privateKey) - assert.NoError(t, err) - assert.NotEmpty(t, keyBytes) - keyPEM := bytes.NewBuffer([]byte{}) - assert.NoError(t, pem.Encode(keyPEM, pkcrypto.NewKeyBlock(keyBytes))) + assert.NoError(t, pkcrypto.WritePrivateKeyPEM(keyPEM, privateKey)) { // test saving - err = ic.Save(fi) + err := ic.Save(fi) assert.NoError(t, err) certInfo, err := os.Stat(ic.CertPath) diff --git a/pkg/identity/utils.go b/pkg/identity/utils.go index a886f2f06..e6ee46da7 100644 --- a/pkg/identity/utils.go +++ b/pkg/identity/utils.go @@ -4,18 +4,11 @@ package identity import ( - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/pem" "io/ioutil" "os" "path/filepath" "github.com/zeebo/errs" - - "storj.io/storj/pkg/pkcrypto" - "storj.io/storj/pkg/utils" ) // TLSFilesStatus is the status of keys @@ -30,66 +23,10 @@ const ( ) var ( - // ErrChainLength is used when the length of a cert chain isn't what was expected - ErrChainLength = errs.Class("cert chain length error") // ErrZeroBytes is returned for zero slice ErrZeroBytes = errs.New("byte slice was unexpectedly empty") ) -type encodedChain struct { - chain [][]byte - extensions [][][]byte -} - -// DecodeAndParseChainPEM parses a PEM chain -func DecodeAndParseChainPEM(PEMBytes []byte) ([]*x509.Certificate, error) { - var ( - encChain encodedChain - blockErrs utils.ErrorGroup - ) - for { - var pemBlock *pem.Block - pemBlock, PEMBytes = pem.Decode(PEMBytes) - if pemBlock == nil { - break - } - switch pemBlock.Type { - case pkcrypto.BlockTypeCertificate: - encChain.AddCert(pemBlock.Bytes) - case pkcrypto.BlockTypeExtension: - if err := encChain.AddExtension(pemBlock.Bytes); err != nil { - blockErrs.Add(err) - } - } - } - if err := blockErrs.Finish(); err != nil { - return nil, err - } - - return encChain.Parse() -} - -func decodePEM(PEMBytes []byte) ([][]byte, error) { - var DERBytes [][]byte - - for { - var DERBlock *pem.Block - - DERBlock, PEMBytes = pem.Decode(PEMBytes) - if DERBlock == nil { - break - } - - DERBytes = append(DERBytes, DERBlock.Bytes) - } - - if len(DERBytes) == 0 || len(DERBytes[0]) == 0 { - return nil, ErrZeroBytes - } - - return DERBytes, nil -} - // writeChainData writes data to path ensuring permissions are appropriate for a cert func writeChainData(path string, data []byte) error { err := writeFile(path, 0744, 0644, data) @@ -150,43 +87,3 @@ func (t TLSFilesStatus) String() string { } return "" } - -func (e *encodedChain) AddCert(b []byte) { - e.chain = append(e.chain, b) - e.extensions = append(e.extensions, [][]byte{}) -} - -func (e *encodedChain) AddExtension(b []byte) error { - chainLen := len(e.chain) - if chainLen < 1 { - return ErrChainLength.New("expected: >= 1; actual: %d", chainLen) - } - - i := chainLen - 1 - e.extensions[i] = append(e.extensions[i], b) - return nil -} - -func (e *encodedChain) Parse() ([]*x509.Certificate, error) { - chain, err := ParseCertChain(e.chain) - if err != nil { - return nil, err - } - - var extErrs utils.ErrorGroup - for i, cert := range chain { - for _, ee := range e.extensions[i] { - ext := pkix.Extension{} - _, err := asn1.Unmarshal(ee, &ext) - if err != nil { - extErrs.Add(err) - } - cert.ExtraExtensions = append(cert.ExtraExtensions, ext) - } - } - if err := extErrs.Finish(); err != nil { - return nil, err - } - - return chain, nil -} diff --git a/pkg/peertls/peertls.go b/pkg/peertls/peertls.go index e351a68a2..9e3f25f9a 100644 --- a/pkg/peertls/peertls.go +++ b/pkg/peertls/peertls.go @@ -10,8 +10,6 @@ import ( "crypto/rand" "crypto/tls" "crypto/x509" - "encoding/asn1" - "encoding/pem" "io" "github.com/zeebo/errs" @@ -23,10 +21,10 @@ import ( var ( // ErrNotExist is used when a file or directory doesn't exist. ErrNotExist = errs.Class("file or directory not found error") - // ErrTLSTemplate is used when an error occurs during tls template generation. - ErrTLSTemplate = errs.Class("tls template error") // ErrGenerate is used when an error occurred during cert/key generation. ErrGenerate = errs.Class("tls generation error") + // ErrTLSTemplate is used when an error occurs during tls template generation. + ErrTLSTemplate = errs.Class("tls template error") // ErrVerifyPeerCert is used when an error occurs during `VerifyPeerCertificate`. ErrVerifyPeerCert = errs.Class("tls peer certificate verification error") // ErrVerifyCertificateChain is used when a certificate chain can't be verified from leaf to root @@ -44,7 +42,7 @@ type PeerCertVerificationFunc func([][]byte, [][]*x509.Certificate) error // functions and adds certificate parsing. func VerifyPeerFunc(next ...PeerCertVerificationFunc) PeerCertVerificationFunc { return func(chain [][]byte, _ [][]*x509.Certificate) error { - c, err := pkcrypto.ParseCertificates(chain) + c, err := pkcrypto.CertsFromDER(chain) if err != nil { return ErrVerifyPeerCert.Wrap(err) } @@ -87,7 +85,7 @@ func VerifyCAWhitelist(cas []*x509.Certificate) PeerCertVerificationFunc { func TLSCert(chain [][]byte, leaf *x509.Certificate, key crypto.PrivateKey) (*tls.Certificate, error) { var err error if leaf == nil { - leaf, err = x509.ParseCertificate(chain[0]) + leaf, err = pkcrypto.CertFromDER(chain[0]) if err != nil { return nil, err } @@ -108,16 +106,12 @@ func WriteChain(w io.Writer, chain ...*x509.Certificate) error { var extErrs utils.ErrorGroup for _, c := range chain { - if err := pem.Encode(w, pkcrypto.NewCertBlock(c.Raw)); err != nil { + if err := pkcrypto.WriteCertPEM(w, c); err != nil { return errs.Wrap(err) } for _, e := range c.ExtraExtensions { - extBytes, err := asn1.Marshal(e) - if err != nil { - extErrs.Add(errs.Wrap(err)) - } - if err := pem.Encode(w, pkcrypto.NewExtensionBlock(extBytes)); err != nil { + if err := pkcrypto.WritePKIXExtensionPEM(w, &e); err != nil { extErrs.Add(errs.Wrap(err)) } } @@ -162,7 +156,7 @@ func NewCert(key, parentKey crypto.PrivateKey, template, parent *x509.Certificat return nil, errs.Wrap(err) } - cert, err := x509.ParseCertificate(cb) + cert, err := pkcrypto.CertFromDER(cb) if err != nil { return nil, errs.Wrap(err) } diff --git a/pkg/pkcrypto/common.go b/pkg/pkcrypto/common.go index a153254ba..63147f53d 100644 --- a/pkg/pkcrypto/common.go +++ b/pkg/pkcrypto/common.go @@ -8,12 +8,20 @@ import ( ) const ( - // BlockTypeEcPrivateKey is the value to define a block type of private key - BlockTypeEcPrivateKey = "EC PRIVATE KEY" - // BlockTypeCertificate is the value to define a block type of certificates - BlockTypeCertificate = "CERTIFICATE" - // BlockTypeExtension is the value to define a block type of certificate extensions - BlockTypeExtension = "EXTENSION" + // BlockLabelEcPrivateKey is the value to define a block label of EC private key + // (which is used here only for backwards compatibility). Use a general PKCS#8 + // encoding instead. + BlockLabelEcPrivateKey = "EC PRIVATE KEY" + // BlockLabelPrivateKey is the value to define a block label of general private key + // (used for PKCS#8-encoded private keys of type RSA, ECDSA, and others). + BlockLabelPrivateKey = "PRIVATE KEY" + // BlockLabelPublicKey is the value to define a block label of general public key + // (used for PKIX-encoded public keys of type RSA, ECDSA, and others). + BlockLabelPublicKey = "PUBLIC KEY" + // BlockLabelCertificate is the value to define a block label of certificates + BlockLabelCertificate = "CERTIFICATE" + // BlockLabelExtension is the value to define a block label of certificate extensions + BlockLabelExtension = "EXTENSION" ) var ( @@ -25,4 +33,6 @@ var ( ErrSign = errs.Class("unable to generate signature") // ErrVerifySignature is used when a cert-chain signature verificaion error occurs. ErrVerifySignature = errs.Class("tls certificate signature verification error") + // ErrChainLength is used when the length of a cert chain isn't what was expected + ErrChainLength = errs.Class("cert chain length error") ) diff --git a/pkg/pkcrypto/encoding.go b/pkg/pkcrypto/encoding.go index b596dc14c..d020b8617 100644 --- a/pkg/pkcrypto/encoding.go +++ b/pkg/pkcrypto/encoding.go @@ -4,75 +4,313 @@ package pkcrypto import ( - "bytes" "crypto" - "crypto/ecdsa" "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "encoding/pem" "io" + "math/big" "github.com/zeebo/errs" + + "storj.io/storj/pkg/utils" ) -// WriteKey writes the private key, PEM-encoded. -func WriteKey(w io.Writer, key crypto.PrivateKey) error { - var ( - kb []byte - err error - ) - - switch k := key.(type) { - case *ecdsa.PrivateKey: - kb, err = x509.MarshalECPrivateKey(k) - if err != nil { - return errs.Wrap(err) - } - default: - return ErrUnsupportedKey.New("%T", k) +// WritePublicKeyPEM writes the public key, in a PEM-enveloped +// PKIX form. +func WritePublicKeyPEM(w io.Writer, key crypto.PublicKey) error { + kb, err := PublicKeyToPKIX(key) + if err != nil { + return err } + err = pem.Encode(w, &pem.Block{Type: BlockLabelPublicKey, Bytes: kb}) + return errs.Wrap(err) +} - if err := pem.Encode(w, NewKeyBlock(kb)); err != nil { +// PublicKeyToPEM encodes a public key to a PEM-enveloped PKIX form. +func PublicKeyToPEM(key crypto.PublicKey) ([]byte, error) { + kb, err := PublicKeyToPKIX(key) + if err != nil { + return nil, err + } + return pem.EncodeToMemory(&pem.Block{Type: BlockLabelPublicKey, Bytes: kb}), nil +} + +// PublicKeyToPKIX serializes a public key to a PKIX-encoded form. +func PublicKeyToPKIX(key crypto.PublicKey) ([]byte, error) { + return x509.MarshalPKIXPublicKey(key) +} + +// PublicKeyFromPKIX parses a public key from its PKIX encoding. +func PublicKeyFromPKIX(pkixData []byte) (crypto.PublicKey, error) { + return x509.ParsePKIXPublicKey(pkixData) +} + +// PublicKeyFromPEM parses a public key from its PEM-enveloped PKIX +// encoding. +func PublicKeyFromPEM(pemData []byte) (crypto.PublicKey, error) { + pb, _ := pem.Decode(pemData) + if pb == nil { + return nil, ErrParseCerts.New("could not parse PEM encoding") + } + if pb.Type != BlockLabelPublicKey { + return nil, ErrParseCerts.New("can not parse public key from PEM block labeled %q", pb.Type) + } + return PublicKeyFromPKIX(pb.Bytes) +} + +// WritePrivateKeyPEM writes the private key to the writer, in a PEM-enveloped +// PKCS#8 form. +func WritePrivateKeyPEM(w io.Writer, key crypto.PrivateKey) error { + kb, err := PrivateKeyToPKCS8(key) + if err != nil { return errs.Wrap(err) } - return nil + err = pem.Encode(w, &pem.Block{Type: BlockLabelPrivateKey, Bytes: kb}) + return errs.Wrap(err) } -// KeyBytes returns bytes of the private key, PEM-encoded. -func KeyBytes(key crypto.PrivateKey) ([]byte, error) { - var data bytes.Buffer - err := WriteKey(&data, key) - return data.Bytes(), err +// PrivateKeyToPEM serializes a private key to a PEM-enveloped PKCS#8 form. +func PrivateKeyToPEM(key crypto.PrivateKey) ([]byte, error) { + kb, err := PrivateKeyToPKCS8(key) + if err != nil { + return nil, errs.Wrap(err) + } + return pem.EncodeToMemory(&pem.Block{Type: BlockLabelPrivateKey, Bytes: kb}), nil } -// NewKeyBlock converts an ASN1/DER-encoded byte-slice of a private key into -// a `pem.Block` pointer. -func NewKeyBlock(b []byte) *pem.Block { - return &pem.Block{Type: BlockTypeEcPrivateKey, Bytes: b} +// PrivateKeyToPKCS8 serializes a private key to a PKCS#8-encoded form. +func PrivateKeyToPKCS8(key crypto.PrivateKey) ([]byte, error) { + return x509.MarshalPKCS8PrivateKey(key) } -// NewCertBlock converts an ASN1/DER-encoded byte-slice of a tls certificate -// into a `pem.Block` pointer. -func NewCertBlock(b []byte) *pem.Block { - return &pem.Block{Type: BlockTypeCertificate, Bytes: b} +// PrivateKeyFromPKCS8 parses a private key from its PKCS#8 encoding. +func PrivateKeyFromPKCS8(keyBytes []byte) (crypto.PrivateKey, error) { + key, err := x509.ParsePKCS8PrivateKey(keyBytes) + if err != nil { + return nil, err + } + return crypto.PrivateKey(key), nil } -// NewExtensionBlock converts an ASN1/DER-encoded byte-slice of a tls certificate -// extension into a `pem.Block` pointer. -func NewExtensionBlock(b []byte) *pem.Block { - return &pem.Block{Type: BlockTypeExtension, Bytes: b} +// PrivateKeyFromPEM parses a private key from its PEM-enveloped PKCS#8 +// encoding. +func PrivateKeyFromPEM(keyBytes []byte) (crypto.PrivateKey, error) { + pb, _ := pem.Decode(keyBytes) + if pb == nil { + return nil, ErrParseCerts.New("could not parse PEM encoding") + } + switch pb.Type { + case BlockLabelEcPrivateKey: + return ecPrivateKeyFromASN1(pb.Bytes) + case BlockLabelPrivateKey: + return PrivateKeyFromPKCS8(pb.Bytes) + } + return nil, ErrParseCerts.New("can not parse private key from PEM block labeled %q", pb.Type) } -// ParseCertificates parses an x509 certificate from each of the given byte -// slices, which should be encoded in DER. (Unwrap PEM encoding first if -// necessary.) -func ParseCertificates(rawCerts [][]byte) ([]*x509.Certificate, error) { +// WriteCertPEM writes the certificate to the writer, in a PEM-enveloped DER +// encoding. +func WriteCertPEM(w io.Writer, cert *x509.Certificate) error { + err := pem.Encode(w, &pem.Block{Type: BlockLabelCertificate, Bytes: cert.Raw}) + return errs.Wrap(err) +} + +// CertToPEM returns the bytes of the certificate, in a PEM-enveloped DER +// encoding. +func CertToPEM(cert *x509.Certificate) []byte { + return pem.EncodeToMemory(&pem.Block{Type: BlockLabelCertificate, Bytes: cert.Raw}) +} + +// CertToDER returns the bytes of the certificate, in a DER encoding. +// +// Note that this is fairly useless, as x509.Certificate objects are always +// supposed to have a member containing the raw DER encoding. But this is +// included for completeness with the rest of this module's API. +func CertToDER(cert *x509.Certificate) ([]byte, error) { + return cert.Raw, nil +} + +// CertFromDER parses an X.509 certificate from its DER encoding. +func CertFromDER(certDER []byte) (*x509.Certificate, error) { + return x509.ParseCertificate(certDER) +} + +// CertFromPEM parses an X.509 certificate from its PEM-enveloped DER encoding. +func CertFromPEM(certPEM []byte) (*x509.Certificate, error) { + kb, _ := pem.Decode(certPEM) + if kb == nil { + return nil, ErrParseCerts.New("could not decode certificate as PEM") + } + if kb.Type != BlockLabelCertificate { + return nil, ErrParseCerts.New("can not parse certificate from PEM block labeled %q", kb.Type) + } + return CertFromDER(kb.Bytes) +} + +// CertsFromDER parses an x509 certificate from each of the given byte +// slices, which should be encoded in DER. +func CertsFromDER(rawCerts [][]byte) ([]*x509.Certificate, error) { certs := make([]*x509.Certificate, len(rawCerts)) for i, c := range rawCerts { var err error - certs[i], err = x509.ParseCertificate(c) + certs[i], err = CertFromDER(c) if err != nil { return nil, ErrParseCerts.New("unable to parse certificate at index %d", i) } } return certs, nil } + +// CertsFromPEM parses a PEM chain from a single byte string (the PEM-enveloped +// certificates should be concatenated). The PEM blocks may include PKIX +// extensions. +func CertsFromPEM(pemBytes []byte) ([]*x509.Certificate, error) { + var ( + encChain encodedChain + blockErrs utils.ErrorGroup + ) + for { + var pemBlock *pem.Block + pemBlock, pemBytes = pem.Decode(pemBytes) + if pemBlock == nil { + break + } + switch pemBlock.Type { + case BlockLabelCertificate: + encChain.AddCert(pemBlock.Bytes) + case BlockLabelExtension: + if err := encChain.AddExtension(pemBlock.Bytes); err != nil { + blockErrs.Add(err) + } + } + } + if err := blockErrs.Finish(); err != nil { + return nil, err + } + + return encChain.Parse() +} + +type encodedChain struct { + chain [][]byte + extensions [][][]byte +} + +func (e *encodedChain) AddCert(b []byte) { + e.chain = append(e.chain, b) + e.extensions = append(e.extensions, [][]byte{}) +} + +func (e *encodedChain) AddExtension(b []byte) error { + chainLen := len(e.chain) + if chainLen < 1 { + return ErrChainLength.New("expected: >= 1; actual: %d", chainLen) + } + + i := chainLen - 1 + e.extensions[i] = append(e.extensions[i], b) + return nil +} + +func (e *encodedChain) Parse() ([]*x509.Certificate, error) { + chain, err := CertsFromDER(e.chain) + if err != nil { + return nil, err + } + + var extErrs utils.ErrorGroup + for i, cert := range chain { + for _, ee := range e.extensions[i] { + ext, err := PKIXExtensionFromASN1(ee) + if err != nil { + extErrs.Add(err) + } + cert.ExtraExtensions = append(cert.ExtraExtensions, *ext) + } + } + if err := extErrs.Finish(); err != nil { + return nil, err + } + + return chain, nil +} + +// WritePKIXExtensionPEM writes the certificate extension to the writer, in a PEM- +// enveloped PKIX form. +func WritePKIXExtensionPEM(w io.Writer, extension *pkix.Extension) error { + extBytes, err := PKIXExtensionToASN1(extension) + if err != nil { + return errs.Wrap(err) + } + err = pem.Encode(w, &pem.Block{Type: BlockLabelExtension, Bytes: extBytes}) + return errs.Wrap(err) +} + +// PKIXExtensionToPEM serializes a PKIX certificate extension to PEM- +// enveloped ASN.1 bytes. +func PKIXExtensionToPEM(extension *pkix.Extension) ([]byte, error) { + asn, err := PKIXExtensionToASN1(extension) + if err != nil { + return nil, err + } + return pem.EncodeToMemory(&pem.Block{Type: BlockLabelExtension, Bytes: asn}), nil +} + +// PKIXExtensionToASN1 serializes a PKIX certificate extension to the +// appropriate ASN.1 structure for such things. See RFC 5280, section 4.1.1.2. +func PKIXExtensionToASN1(extension *pkix.Extension) ([]byte, error) { + extBytes, err := asn1.Marshal(extension) + return extBytes, errs.Wrap(err) +} + +// PKIXExtensionFromASN1 deserializes a PKIX certificate extension from +// the appropriate ASN.1 structure for such things. +func PKIXExtensionFromASN1(extData []byte) (*pkix.Extension, error) { + var extension pkix.Extension + if _, err := asn1.Unmarshal(extData, &extension); err != nil { + return nil, ErrParseCerts.New("unable to unmarshal PKIX extension: %v", err) + } + return &extension, nil +} + +// PKIXExtensionFromPEM parses a PKIX certificate extension from +// PEM-enveloped ASN.1 bytes. +func PKIXExtensionFromPEM(pemBytes []byte) (*pkix.Extension, error) { + pb, _ := pem.Decode(pemBytes) + if pb == nil { + return nil, ErrParseCerts.New("unable to parse PEM block") + } + if pb.Type != BlockLabelExtension { + return nil, ErrParseCerts.New("can not parse PKIX cert extension from PEM block labeled %q", pb.Type) + } + return PKIXExtensionFromASN1(pb.Bytes) +} + +type ecdsaSignature struct { + R, S *big.Int +} + +func marshalECDSASignature(r, s *big.Int) ([]byte, error) { + return asn1.Marshal(ecdsaSignature{R: r, S: s}) +} + +func unmarshalECDSASignature(signatureBytes []byte) (r, s *big.Int, err error) { + var signature ecdsaSignature + if _, err = asn1.Unmarshal(signatureBytes, &signature); err != nil { + return nil, nil, err + } + return signature.R, signature.S, nil +} + +// ecPrivateKeyFromASN1 parses a private key from the special Elliptic Curve +// Private Key ASN.1 structure. This is here only for backward compatibility. +// Use PKCS#8 instead. +func ecPrivateKeyFromASN1(privKeyData []byte) (crypto.PrivateKey, error) { + key, err := x509.ParseECPrivateKey(privKeyData) + if err != nil { + return nil, err + } + return crypto.PrivateKey(key), nil +} diff --git a/pkg/pkcrypto/signing.go b/pkg/pkcrypto/signing.go index 3c7655650..4ebf8cacc 100644 --- a/pkg/pkcrypto/signing.go +++ b/pkg/pkcrypto/signing.go @@ -8,16 +8,8 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" - "encoding/asn1" - "math/big" ) -// ECDSASignature holds the `r` and `s` values in an ecdsa signature -// (see https://golang.org/pkg/crypto/ecdsa) -type ECDSASignature struct { - R, S *big.Int -} - var authECCurve = elliptic.P256() // GeneratePrivateKey returns a new PrivateKey for signing messages @@ -32,12 +24,12 @@ func VerifySignature(signedData, data []byte, pubKey crypto.PublicKey) error { return ErrUnsupportedKey.New("%T", key) } - signature := new(ECDSASignature) - if _, err := asn1.Unmarshal(signedData, signature); err != nil { + r, s, err := unmarshalECDSASignature(signedData) + if err != nil { return ErrVerifySignature.New("unable to unmarshal ecdsa signature: %v", err) } digest := SHA256Hash(data) - if !ecdsa.Verify(key, digest, signature.R, signature.S) { + if !ecdsa.Verify(key, digest, r, s) { return ErrVerifySignature.New("signature is not valid") } return nil @@ -56,7 +48,7 @@ func SignBytes(key crypto.PrivateKey, data []byte) ([]byte, error) { return nil, ErrSign.Wrap(err) } - return asn1.Marshal(ECDSASignature{R: r, S: s}) + return marshalECDSASignature(r, s) } // SignHashOf signs a SHA-256 digest of the given data and returns the new diff --git a/pkg/server/options.go b/pkg/server/options.go index fbabc26f9..e8184aa7a 100644 --- a/pkg/server/options.go +++ b/pkg/server/options.go @@ -10,6 +10,7 @@ import ( "storj.io/storj/pkg/identity" "storj.io/storj/pkg/peertls" + "storj.io/storj/pkg/pkcrypto" ) // Options holds config, identity, and peer verification function data for use with a grpc server. @@ -53,7 +54,7 @@ func (opts *Options) configure(c Config) (err error) { return Error.New("unable to find whitelist file %v: %v", c.PeerCAWhitelistPath, err) } } - parsed, err := identity.DecodeAndParseChainPEM(whitelist) + parsed, err := pkcrypto.CertsFromPEM(whitelist) if err != nil { return Error.Wrap(err) }