diff --git a/Makefile b/Makefile index 7b9335397..f533c29b7 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ check-copyrights: ## Check source files for copyright headers .PHONY: goimports-fix goimports-fix: ## Applies goimports to every go file (excluding vendored files) - goimports -w $$(find . -type f -name '*.go' -not -path "*/vendor/*") + goimports -w -local storj.io $$(find . -type f -name '*.go' -not -path "*/vendor/*") .PHONY: proto proto: ## Rebuild protobuf files diff --git a/cmd/captplanet/run.go b/cmd/captplanet/run.go index 220a4208f..4a91f3299 100644 --- a/cmd/captplanet/run.go +++ b/cmd/captplanet/run.go @@ -96,14 +96,14 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) { // start satellite go func() { _, _ = fmt.Printf("starting satellite on %s\n", - runCfg.Satellite.Identity.Address) + runCfg.Satellite.Identity.Server.Address) if runCfg.Satellite.Audit.SatelliteAddr == "" { - runCfg.Satellite.Audit.SatelliteAddr = runCfg.Satellite.Identity.Address + runCfg.Satellite.Audit.SatelliteAddr = runCfg.Satellite.Identity.Server.Address } if runCfg.Satellite.Web.SatelliteAddr == "" { - runCfg.Satellite.Web.SatelliteAddr = runCfg.Satellite.Identity.Address + runCfg.Satellite.Web.SatelliteAddr = runCfg.Satellite.Identity.Server.Address } database, err := satellitedb.NewDB(runCfg.Satellite.Database) @@ -148,7 +148,7 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) { return } - address := v.Identity.Address + address := v.Identity.Server.Address storagenode := fmt.Sprintf("%s:%s", identity.ID.String(), address) _, _ = fmt.Printf("starting storage node %d %s (kad on %s)\n", i, storagenode, address) @@ -159,7 +159,7 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) { // start s3 uplink go func() { _, _ = fmt.Printf("Starting s3-gateway on %s\nAccess key: %s\nSecret key: %s\n", - runCfg.Uplink.Identity.Address, + runCfg.Uplink.Identity.Server.Address, runCfg.Uplink.Minio.AccessKey, runCfg.Uplink.Minio.SecretKey) errch <- runCfg.Uplink.Run(ctx) diff --git a/cmd/captplanet/setup.go b/cmd/captplanet/setup.go index c22556a2a..d66661e81 100644 --- a/cmd/captplanet/setup.go +++ b/cmd/captplanet/setup.go @@ -137,7 +137,7 @@ func cmdSetup(cmd *cobra.Command, args []string) (err error) { overrides := map[string]interface{}{ "satellite.identity.cert-path": setupCfg.HCIdentity.CertPath, "satellite.identity.key-path": setupCfg.HCIdentity.KeyPath, - "satellite.identity.address": joinHostPort( + "satellite.identity.server.address": joinHostPort( setupCfg.ListenHost, startingPort+1), "satellite.kademlia.bootstrap-addr": joinHostPort( setupCfg.ListenHost, startingPort+1), @@ -175,7 +175,7 @@ func cmdSetup(cmd *cobra.Command, args []string) (err error) { storagenodePath, "identity.cert") overrides[storagenode+"identity.key-path"] = filepath.Join( storagenodePath, "identity.key") - overrides[storagenode+"identity.address"] = joinHostPort( + overrides[storagenode+"identity.server.address"] = joinHostPort( setupCfg.ListenHost, startingPort+i*2+3) overrides[storagenode+"kademlia.bootstrap-addr"] = joinHostPort( setupCfg.ListenHost, startingPort+1) diff --git a/cmd/uplink/cmd/run.go b/cmd/uplink/cmd/run.go index 3d94b0c45..42a2e1004 100644 --- a/cmd/uplink/cmd/run.go +++ b/cmd/uplink/cmd/run.go @@ -26,7 +26,7 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) { return fmt.Errorf("Invalid argument %#v. Try 'uplink run'", flagname) } - address := cfg.Identity.Address + address := cfg.Identity.Server.Address host, port, err := net.SplitHostPort(address) if err != nil { return err diff --git a/internal/testplanet/identities.go b/internal/testplanet/identities.go index 48404e75f..77d4ecfd1 100644 --- a/internal/testplanet/identities.go +++ b/internal/testplanet/identities.go @@ -44,7 +44,7 @@ func (identities *Identities) NewIdentity() (*provider.FullIdentity, error) { // mustParsePEM parses pem encoded chain and key strings. func mustParsePEM(chain, key string) *provider.FullIdentity { // TODO: add whitelist handling somehow - fi, err := provider.FullIdentityFromPEM([]byte(chain), []byte(key), nil) + fi, err := provider.FullIdentityFromPEM([]byte(chain), []byte(key)) if err != nil { panic(err) } diff --git a/internal/testplanet/node.go b/internal/testplanet/node.go index 25b0aa956..357d5a9c1 100644 --- a/internal/testplanet/node.go +++ b/internal/testplanet/node.go @@ -61,7 +61,12 @@ func (planet *Planet) newNode(name string, nodeType pb.NodeType) (*Node, error) node.Transport = transport.NewClient(identity) - node.Provider, err = provider.NewProvider(node.Identity, node.Listener, grpcauth.NewAPIKeyInterceptor()) + serverConfig := provider.ServerConfig{Address: node.Listener.Addr().String()} + opts, err := provider.NewServerOptions(node.Identity, serverConfig) + if err != nil { + return nil, err + } + node.Provider, err = provider.NewProvider(opts, node.Listener, grpcauth.NewAPIKeyInterceptor()) if err != nil { return nil, utils.CombineErrors(err, listener.Close()) } diff --git a/pkg/miniogw/config.go b/pkg/miniogw/config.go index 5850fb289..7d6c236d9 100644 --- a/pkg/miniogw/config.go +++ b/pkg/miniogw/config.go @@ -106,7 +106,7 @@ func (c Config) Run(ctx context.Context) (err error) { } minio.Main([]string{"storj", "gateway", "storj", - "--address", c.Identity.Address, "--config-dir", c.Minio.Dir, "--quiet"}) + "--address", c.Identity.Server.Address, "--config-dir", c.Minio.Dir, "--quiet"}) return Error.New("unexpected minio exit") } diff --git a/pkg/peertls/extensions.go b/pkg/peertls/extensions.go new file mode 100644 index 000000000..001dbe8f5 --- /dev/null +++ b/pkg/peertls/extensions.go @@ -0,0 +1,78 @@ +// Copyright (C) 2018 Storj Labs, Inc. +// See LICENSE for copying information. + +package peertls + +import ( + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + + "github.com/zeebo/errs" + + "storj.io/storj/pkg/utils" +) + +// TLSExtConfig is used to bind cli flags for determining which extensions will +// be used by the server +type TLSExtConfig struct { + Revocation bool `help:"if true, client leafs may contain the most recent certificate revocation for the current certificate" default:"true"` + WhitelistSignedLeaf bool `help:"if true, client leafs must contain a valid \"signed certificate extension\" (NB: verified against certs in the peer ca whitelist; i.e. if true, a whitelist must be provided)" default:"false"` +} + +// Extensions is a collection of `extension`s for convenience (see `VerifyFunc`) +type Extensions []extension + +type extension struct { + id asn1.ObjectIdentifier + f func(pkix.Extension, [][]*x509.Certificate) (bool, error) + err error +} + +// ParseExtensions an extension config into a slice of extensions with their +// respective ids (`asn1.ObjectIdentifier`) and a function (`f`) which can be +// used in the context of peer certificate verification. +func ParseExtensions(c TLSExtConfig, caWhitelist []*x509.Certificate) (exts Extensions) { + if c.WhitelistSignedLeaf { + exts = append(exts, extension{ + id: ExtensionIDs[SignedCertExtID], + f: func(certExt pkix.Extension, chains [][]*x509.Certificate) (bool, error) { + if caWhitelist == nil { + return false, errs.New("whitelist required for leaf whitelist signature verification") + } + leaf := chains[0][0] + for _, ca := range caWhitelist { + err := VerifySignature(certExt.Value, leaf.RawTBSCertificate, ca.PublicKey) + if err == nil { + return true, nil + } + } + return false, nil + }, + err: ErrVerifyCAWhitelist.New("leaf whitelist signature extension verification error"), + }) + } + + return exts +} + +// VerifyFunc returns a peer certificate verification function which iterates +// over all the leaf cert's extensions and receiver extensions and calls +// `extension#f` when it finds a match by id (`asn1.ObjectIdentifier`) +func (e Extensions) VerifyFunc() PeerCertVerificationFunc { + return func(_ [][]byte, parsedChains [][]*x509.Certificate) error { + for _, ext := range parsedChains[0][0].Extensions { + for _, v := range e { + if v.id.Equal(ext.Id) { + ok, err := v.f(ext, parsedChains) + if err != nil { + return ErrExtension.Wrap(utils.CombineErrors(v.err, err)) + } else if !ok { + return v.err + } + } + } + } + return nil + } +} diff --git a/pkg/peertls/peertls.go b/pkg/peertls/peertls.go index 6fd61b674..52d43cf15 100644 --- a/pkg/peertls/peertls.go +++ b/pkg/peertls/peertls.go @@ -9,6 +9,7 @@ import ( "crypto/rand" "crypto/tls" "crypto/x509" + "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "io" @@ -26,30 +27,43 @@ const ( BlockTypeIDOptions = "ID OPTIONS" ) +const ( + // SignedCertExtID is the asn1 object ID for a pkix extension holding a signature of the cert it's extending, signed by some CA (e.g. the root cert chain). + // This extension allows for an additional signature per certificate. + SignedCertExtID = iota + // RevocationExtID is the asn1 object ID for a pkix extension containing the most recent certificate revocation data + // for the current TLS cert chain. + RevocationExtID +) + var ( - // AuthoritySignatureExtID is the asn1 object ID for a pkix extension holding a signature of the leaf cert, signed by some CA (e.g. the root cert) - // This extension allows for an additional signature per certificate - AuthoritySignatureExtID = asn1.ObjectIdentifier{2, 999, 1} - // ErrNotExist is used when a file or directory doesn't exist + // ExtensionIDs is a map from an enum to extension object identifiers. + ExtensionIDs = map[int]asn1.ObjectIdentifier{ + SignedCertExtID: {2, 999, 1, 1}, + RevocationExtID: {2, 999, 2, 1}, + } + // ErrExtension is used when an error occurs while processing an extension. + ErrExtension = errs.Class("extension error") + // ErrNotExist is used when a file or directory doesn't exist. ErrNotExist = errs.Class("file or directory not found error") - // ErrGenerate is used when an error occurred during cert/key generation + // ErrGenerate is used when an error occurred during cert/key generation. ErrGenerate = errs.Class("tls generation error") - // ErrUnsupportedKey is used when key type is not supported + // ErrUnsupportedKey is used when key type is not supported. ErrUnsupportedKey = errs.Class("unsupported key type") - // ErrTLSTemplate is used when an error occurs during tls template generation + // 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 is used when an error occurs during `VerifyPeerCertificate`. ErrVerifyPeerCert = errs.Class("tls peer certificate verification error") - // ErrParseCerts is used when an error occurs while parsing a certificate or cert chain + // ErrParseCerts is used when an error occurs while parsing a certificate or cert chain. ErrParseCerts = errs.Class("unable to parse certificate") - // ErrVerifySignature is used when a cert-chain signature verificaion error occurs + // ErrVerifySignature is used when a cert-chain signature verificaion error occurs. ErrVerifySignature = errs.Class("tls certificate signature verification error") // ErrVerifyCertificateChain is used when a certificate chain can't be verified from leaf to root - // (i.e.: each cert in the chain should be signed by the preceding cert and the root should be self-signed) + // (i.e.: each cert in the chain should be signed by the preceding cert and the root should be self-signed). ErrVerifyCertificateChain = errs.Class("certificate chain signature verification failed") - // ErrVerifyCAWhitelist is used when the leaf of a peer certificate isn't signed by any CA in the whitelist - ErrVerifyCAWhitelist = errs.Class("certificate isn't signed by any CA in the whitelist") - // ErrSign is used when something goes wrong while generating a signature + // ErrVerifyCAWhitelist is used when the leaf of a peer certificate isn't signed by any CA in the whitelist. + ErrVerifyCAWhitelist = errs.Class("not signed by any CA in the whitelist") + // ErrSign is used when something goes wrong while generating a signature. ErrSign = errs.Class("unable to generate signature") ) @@ -67,36 +81,6 @@ func NewKey() (crypto.PrivateKey, error) { return k, nil } -// NewCert returns a new x509 certificate using the provided templates and -// signed by the `signer` key -func NewCert(template, parentTemplate *x509.Certificate, pubKey crypto.PublicKey, signer crypto.PrivateKey) (*x509.Certificate, error) { - k, ok := signer.(*ecdsa.PrivateKey) - if !ok { - return nil, ErrUnsupportedKey.New("%T", k) - } - - if parentTemplate == nil { - parentTemplate = template - } - - cb, err := x509.CreateCertificate( - rand.Reader, - template, - parentTemplate, - pubKey, - k, - ) - if err != nil { - return nil, errs.Wrap(err) - } - - c, err := x509.ParseCertificate(cb) - if err != nil { - return nil, errs.Wrap(err) - } - return c, nil -} - // VerifyPeerFunc combines multiple `*tls.Config#VerifyPeerCertificate` // functions and adds certificate parsing. func VerifyPeerFunc(next ...PeerCertVerificationFunc) PeerCertVerificationFunc { @@ -118,63 +102,41 @@ func VerifyPeerFunc(next ...PeerCertVerificationFunc) PeerCertVerificationFunc { } // VerifyPeerCertChains verifies that the first certificate chain contains certificates -// which are signed by their respective parents, ending with a self-signed root +// which are signed by their respective parents, ending with a self-signed root. func VerifyPeerCertChains(_ [][]byte, parsedChains [][]*x509.Certificate) error { return verifyChainSignatures(parsedChains[0]) } // VerifyCAWhitelist verifies that the peer identity's CA and leaf-extension was signed -// by any one of the (certificate authority) certificates in the provided whitelist -func VerifyCAWhitelist(cas []*x509.Certificate, verifyExtension bool) PeerCertVerificationFunc { +// by any one of the (certificate authority) certificates in the provided whitelist. +func VerifyCAWhitelist(cas []*x509.Certificate) PeerCertVerificationFunc { if cas == nil { return nil } - return func(_ [][]byte, parsedChains [][]*x509.Certificate) error { - var ( - leaf = parsedChains[0][0] - err error - ) - - // Leaf extension must contain leaf signature, signed by a CA in the whitelist. - // That *same* CA must also have signed the leaf's parent cert (regular cert chain signature, not extension). for _, ca := range cas { - err = verifyCertSignature(ca, parsedChains[0][1]) + err := verifyCertSignature(ca, parsedChains[0][1]) if err == nil { - if !verifyExtension { - break - } - - for _, ext := range leaf.Extensions { - if ext.Id.Equal(AuthoritySignatureExtID) { - err = verifySignature(ext.Value, leaf.RawTBSCertificate, leaf.PublicKey) - if err != nil { - return ErrVerifyCAWhitelist.New("authority signature extension verification error: %s", err.Error()) - } - return nil - } - } - break + return nil } } - - return ErrVerifyCAWhitelist.Wrap(err) + return ErrVerifyCAWhitelist.New("extension signature doesn't match any CA in the whitelist") } } // NewKeyBlock converts an ASN1/DER-encoded byte-slice of a private key into -// a `pem.Block` pointer +// a `pem.Block` pointer. func NewKeyBlock(b []byte) *pem.Block { return &pem.Block{Type: BlockTypeEcPrivateKey, Bytes: b} } // NewCertBlock converts an ASN1/DER-encoded byte-slice of a tls certificate -// into a `pem.Block` pointer +// into a `pem.Block` pointer. func NewCertBlock(b []byte) *pem.Block { return &pem.Block{Type: BlockTypeCertificate, Bytes: b} } -// TLSCert creates a tls.Certificate from chains, key and leaf +// TLSCert creates a tls.Certificate from chains, key and leaf. func TLSCert(chain [][]byte, leaf *x509.Certificate, key crypto.PrivateKey) (*tls.Certificate, error) { var err error if leaf == nil { @@ -227,3 +189,70 @@ func WriteKey(w io.Writer, key crypto.PrivateKey) error { } return nil } + +// NewCert returns a new x509 certificate using the provided templates and key, +// signed by the parent cert if provided; otherwise, self-signed. +func NewCert(key, parentKey crypto.PrivateKey, template, parent *x509.Certificate) (*x509.Certificate, error) { + p, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, ErrUnsupportedKey.New("%T", key) + } + + var signingKey crypto.PrivateKey + if parentKey != nil { + signingKey = parentKey + } else { + signingKey = key + } + + if parent == nil { + parent = template + } + + cb, err := x509.CreateCertificate( + rand.Reader, + template, + parent, + &p.PublicKey, + signingKey, + ) + if err != nil { + return nil, errs.Wrap(err) + } + + cert, err := x509.ParseCertificate(cb) + if err != nil { + return nil, errs.Wrap(err) + } + return cert, nil +} + +// AddSignedLeafExt adds a "signed certificate extension" to the passed cert, +// using the passed private key. +func AddSignedLeafExt(key crypto.PrivateKey, cert *x509.Certificate) error { + ecKey, ok := key.(*ecdsa.PrivateKey) + if !ok { + return ErrUnsupportedKey.New("%T", key) + } + + hash := crypto.SHA256.New() + _, err := hash.Write(cert.RawTBSCertificate) + if err != nil { + return ErrSign.Wrap(err) + } + r, s, err := ecdsa.Sign(rand.Reader, ecKey, hash.Sum(nil)) + if err != nil { + return ErrSign.Wrap(err) + } + + signature, err := asn1.Marshal(ECDSASignature{R: r, S: s}) + if err != nil { + return ErrSign.Wrap(err) + } + + cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{ + Id: ExtensionIDs[SignedCertExtID], + Value: signature, + }) + return nil +} diff --git a/pkg/peertls/peertls_test.go b/pkg/peertls/peertls_test.go index e45fef034..df3ab09a7 100644 --- a/pkg/peertls/peertls_test.go +++ b/pkg/peertls/peertls_test.go @@ -14,214 +14,282 @@ import ( ) func TestNewCert_CA(t *testing.T) { - k, err := NewKey() + caKey, err := NewKey() assert.NoError(t, err) - ct, err := CATemplate() + caTemplate, err := CATemplate() assert.NoError(t, err) - p, _ := k.(*ecdsa.PrivateKey) - c, err := NewCert(ct, nil, &p.PublicKey, k) + caCert, err := NewCert(caKey, nil, caTemplate, nil) assert.NoError(t, err) - assert.NotEmpty(t, k.(*ecdsa.PrivateKey)) - assert.NotEmpty(t, c) - assert.NotEmpty(t, c.PublicKey.(*ecdsa.PublicKey)) + assert.NotEmpty(t, caKey.(*ecdsa.PrivateKey)) + assert.NotEmpty(t, caCert) + assert.NotEmpty(t, caCert.PublicKey.(*ecdsa.PublicKey)) - err = c.CheckSignatureFrom(c) + err = caCert.CheckSignatureFrom(caCert) assert.NoError(t, err) } func TestNewCert_Leaf(t *testing.T) { - k, err := NewKey() + caKey, err := NewKey() assert.NoError(t, err) - ct, err := CATemplate() + caTemplate, err := CATemplate() assert.NoError(t, err) - cp, _ := k.(*ecdsa.PrivateKey) - c, err := NewCert(ct, nil, &cp.PublicKey, k) + caCert, err := NewCert(caKey, nil, caTemplate, nil) assert.NoError(t, err) - lt, err := LeafTemplate() + leafKey, err := NewKey() assert.NoError(t, err) - lp, _ := k.(*ecdsa.PrivateKey) - l, err := NewCert(lt, ct, &lp.PublicKey, k) + leafTemplate, err := LeafTemplate() assert.NoError(t, err) - assert.NotEmpty(t, k.(*ecdsa.PrivateKey)) - assert.NotEmpty(t, l) - assert.NotEmpty(t, l.PublicKey.(*ecdsa.PublicKey)) - - err = c.CheckSignatureFrom(c) + leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert) assert.NoError(t, err) - err = l.CheckSignatureFrom(c) + + assert.NotEmpty(t, caKey.(*ecdsa.PrivateKey)) + assert.NotEmpty(t, leafCert) + assert.NotEmpty(t, leafCert.PublicKey.(*ecdsa.PublicKey)) + + err = caCert.CheckSignatureFrom(caCert) + assert.NoError(t, err) + err = leafCert.CheckSignatureFrom(caCert) assert.NoError(t, err) } func TestVerifyPeerFunc(t *testing.T) { - k, err := NewKey() + caKey, err := NewKey() assert.NoError(t, err) - ct, err := CATemplate() + caTemplate, err := CATemplate() assert.NoError(t, err) - cp, _ := k.(*ecdsa.PrivateKey) - c, err := NewCert(ct, nil, &cp.PublicKey, k) + caCert, err := NewCert(caKey, nil, caTemplate, nil) assert.NoError(t, err) - lt, err := LeafTemplate() + leafKey, err := NewKey() assert.NoError(t, err) - lp, _ := k.(*ecdsa.PrivateKey) - l, err := NewCert(lt, ct, &lp.PublicKey, k) + leafTemplate, err := LeafTemplate() + assert.NoError(t, err) + + leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert) assert.NoError(t, err) testFunc := func(chain [][]byte, parsedChains [][]*x509.Certificate) error { switch { - case !bytes.Equal(chain[1], c.Raw): + case !bytes.Equal(chain[1], caCert.Raw): return errs.New("CA cert doesn't match") - case !bytes.Equal(chain[0], l.Raw): + case !bytes.Equal(chain[0], leafCert.Raw): return errs.New("leaf's CA cert doesn't match") - case l.PublicKey.(*ecdsa.PublicKey).Curve != parsedChains[0][0].PublicKey.(*ecdsa.PublicKey).Curve: + case leafCert.PublicKey.(*ecdsa.PublicKey).Curve != parsedChains[0][0].PublicKey.(*ecdsa.PublicKey).Curve: return errs.New("leaf public key doesn't match") - case l.PublicKey.(*ecdsa.PublicKey).X.Cmp(parsedChains[0][0].PublicKey.(*ecdsa.PublicKey).X) != 0: + case leafCert.PublicKey.(*ecdsa.PublicKey).X.Cmp(parsedChains[0][0].PublicKey.(*ecdsa.PublicKey).X) != 0: return errs.New("leaf public key doesn't match") - case l.PublicKey.(*ecdsa.PublicKey).Y.Cmp(parsedChains[0][0].PublicKey.(*ecdsa.PublicKey).Y) != 0: + case leafCert.PublicKey.(*ecdsa.PublicKey).Y.Cmp(parsedChains[0][0].PublicKey.(*ecdsa.PublicKey).Y) != 0: return errs.New("leaf public key doesn't match") - case !bytes.Equal(parsedChains[0][1].Raw, c.Raw): + case !bytes.Equal(parsedChains[0][1].Raw, caCert.Raw): return errs.New("parsed CA cert doesn't match") - case !bytes.Equal(parsedChains[0][0].Raw, l.Raw): + case !bytes.Equal(parsedChains[0][0].Raw, leafCert.Raw): return errs.New("parsed leaf cert doesn't match") } return nil } - err = VerifyPeerFunc(testFunc)([][]byte{l.Raw, c.Raw}, nil) + err = VerifyPeerFunc(testFunc)([][]byte{leafCert.Raw, caCert.Raw}, nil) assert.NoError(t, err) } func TestVerifyPeerCertChains(t *testing.T) { - k, err := NewKey() + caKey, err := NewKey() assert.NoError(t, err) - ct, err := CATemplate() + caTemplate, err := CATemplate() assert.NoError(t, err) - cp, ok := k.(*ecdsa.PrivateKey) - assert.True(t, ok) - c, err := NewCert(ct, nil, &cp.PublicKey, k) + caCert, err := NewCert(caKey, nil, caTemplate, nil) assert.NoError(t, err) - lt, err := LeafTemplate() + leafKey, err := NewKey() assert.NoError(t, err) - lp, ok := k.(*ecdsa.PrivateKey) - assert.True(t, ok) - l, err := NewCert(lt, ct, &lp.PublicKey, k) + leafTemplate, err := LeafTemplate() assert.NoError(t, err) - err = VerifyPeerFunc(VerifyPeerCertChains)([][]byte{l.Raw, c.Raw}, nil) + leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert) assert.NoError(t, err) - c, err = NewCert(ct, nil, &cp.PublicKey, k) + err = VerifyPeerFunc(VerifyPeerCertChains)([][]byte{leafCert.Raw, caCert.Raw}, nil) assert.NoError(t, err) - k2, err := NewKey() + wrongKey, err := NewKey() assert.NoError(t, err) - l, err = NewCert(lt, nil, &lp.PublicKey, k2) + leafCert, err = NewCert(leafKey, wrongKey, leafTemplate, caCert) assert.NoError(t, err) - err = VerifyPeerFunc(VerifyPeerCertChains)([][]byte{l.Raw, c.Raw}, nil) + err = VerifyPeerFunc(VerifyPeerCertChains)([][]byte{leafCert.Raw, caCert.Raw}, nil) assert.True(t, ErrVerifyPeerCert.Has(err)) assert.True(t, ErrVerifyCertificateChain.Has(err)) } func TestVerifyCAWhitelist(t *testing.T) { - k, err := NewKey() + caKey, err := NewKey() assert.NoError(t, err) - ct, err := CATemplate() + caTemplate, err := CATemplate() assert.NoError(t, err) - cp, ok := k.(*ecdsa.PrivateKey) - assert.True(t, ok) - c, err := NewCert(ct, nil, &cp.PublicKey, k) + caCert, err := NewCert(caKey, nil, caTemplate, nil) assert.NoError(t, err) - lt, err := LeafTemplate() + leafKey, err := NewKey() assert.NoError(t, err) - lk, err := NewKey() + leafTemplate, err := LeafTemplate() assert.NoError(t, err) - lp, ok := lk.(*ecdsa.PrivateKey) - assert.True(t, ok) - - l, err := NewCert(lt, ct, &lp.PublicKey, k) + leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert) assert.NoError(t, err) - err = VerifyPeerFunc(VerifyCAWhitelist(nil, false))([][]byte{l.Raw, c.Raw}, nil) + // empty whitelist + err = VerifyPeerFunc(VerifyCAWhitelist(nil))([][]byte{leafCert.Raw, caCert.Raw}, nil) assert.NoError(t, err) - err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{c}, false))([][]byte{l.Raw, c.Raw}, nil) + // whitelist contains ca + err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{caCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil) assert.NoError(t, err) - zk, err := NewKey() + rootKey, err := NewKey() assert.NoError(t, err) - zt, err := CATemplate() + rootTemplate, err := CATemplate() assert.NoError(t, err) - zp, ok := zk.(*ecdsa.PrivateKey) - assert.True(t, ok) - z, err := NewCert(zt, nil, &zp.PublicKey, zk) + rootCert, err := NewCert(rootKey, nil, rootTemplate, nil) assert.NoError(t, err) - err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{z}, false))([][]byte{l.Raw, c.Raw}, nil) + // no valid signed extension, non-empty whitelist + err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{rootCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil) assert.True(t, ErrVerifyCAWhitelist.Has(err)) - assert.True(t, ErrVerifySignature.Has(err)) - err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{z, c}, false))([][]byte{l.Raw, c.Raw}, nil) + // last cert in whitelist is signer + err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{rootCert, caCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil) assert.NoError(t, err) - err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{c, z}, false))([][]byte{l.Raw, c.Raw}, nil) + // first cert in whitelist is signer + err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{caCert, rootCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil) assert.NoError(t, err) - xt, err := LeafTemplate() + ca2Cert, err := NewCert(caKey, rootKey, caTemplate, rootCert) assert.NoError(t, err) - xk, err := NewKey() + leaf2Cert, err := NewCert(leafKey, caKey, leafTemplate, ca2Cert) assert.NoError(t, err) - xp, ok := xk.(*ecdsa.PrivateKey) - assert.True(t, ok) - - x, err := NewCert(xt, zt, &xp.PublicKey, zk) + // length 3 chain; first cert in whitelist is signer + err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{rootCert, caCert}))([][]byte{leaf2Cert.Raw, ca2Cert.Raw, rootCert.Raw}, nil) assert.NoError(t, err) - yt, err := LeafTemplate() - assert.NoError(t, err) - - yk, err := NewKey() - assert.NoError(t, err) - - yp, ok := yk.(*ecdsa.PrivateKey) - assert.True(t, ok) - y, err := NewCert(yt, xt, &yp.PublicKey, xk) - assert.NoError(t, err) - - err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{z, c}, false))([][]byte{z.Raw, x.Raw, y.Raw}, nil) - assert.NoError(t, err) - - err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{c, z}, false))([][]byte{z.Raw, x.Raw, y.Raw}, nil) - assert.NoError(t, err) - - err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{z, c}, true))([][]byte{z.Raw, x.Raw, y.Raw}, nil) - assert.NoError(t, err) - - err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{c, z}, true))([][]byte{z.Raw, x.Raw, y.Raw}, nil) + // length 3 chain; last cert in whitelist is signer + err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{caCert, rootCert}))([][]byte{leaf2Cert.Raw, ca2Cert.Raw, rootCert.Raw}, nil) assert.NoError(t, err) } + +func TestSignLeafExt(t *testing.T) { + caKey, err := NewKey() + assert.NoError(t, err) + + caTemplate, err := CATemplate() + assert.NoError(t, err) + + caCert, err := NewCert(caKey, nil, caTemplate, nil) + assert.NoError(t, err) + + leafKey, err := NewKey() + assert.NoError(t, err) + + leafTemplate, err := LeafTemplate() + assert.NoError(t, err) + + leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert) + assert.NoError(t, err) + + err = AddSignedLeafExt(caKey, leafCert) + assert.NoError(t, err) + assert.Equal(t, 1, len(leafCert.ExtraExtensions)) + assert.True(t, ExtensionIDs[SignedCertExtID].Equal(leafCert.ExtraExtensions[0].Id)) + + caECKey, ok := caKey.(*ecdsa.PrivateKey) + if !assert.True(t, ok) { + t.FailNow() + } + + err = VerifySignature(leafCert.ExtraExtensions[0].Value, leafCert.RawTBSCertificate, &caECKey.PublicKey) + assert.NoError(t, err) +} + +func TestParseExtensions(t *testing.T) { + type result struct { + ok bool + err error + } + + rootKey, err := NewKey() + assert.NoError(t, err) + + caKey, err := NewKey() + assert.NoError(t, err) + + caTemplate, err := CATemplate() + assert.NoError(t, err) + + rootCert, err := NewCert(rootKey, nil, caTemplate, nil) + assert.NoError(t, err) + + caCert, err := NewCert(caKey, rootKey, caTemplate, rootCert) + assert.NoError(t, err) + + leafKey, err := NewKey() + assert.NoError(t, err) + + leafTemplate, err := LeafTemplate() + assert.NoError(t, err) + + leafCert, err := NewCert(leafKey, rootKey, leafTemplate, caCert) + assert.NoError(t, err) + + err = AddSignedLeafExt(rootKey, leafCert) + assert.NoError(t, err) + + whitelist := []*x509.Certificate{rootCert} + + cases := []struct { + testID string + config TLSExtConfig + whitelist []*x509.Certificate + expected []result + }{ + { + "leaf whitelist signature", + TLSExtConfig{WhitelistSignedLeaf: true}, + whitelist, + []result{{true, nil}}, + }, + } + + for _, c := range cases { + t.Run(c.testID, func(t *testing.T) { + exts := ParseExtensions(c.config, c.whitelist) + assert.Equal(t, 1, len(exts)) + for i, e := range exts { + ok, err := e.f(leafCert.ExtraExtensions[0], [][]*x509.Certificate{{leafCert, caCert, rootCert}}) + assert.Equal(t, c.expected[i].err, err) + assert.Equal(t, c.expected[i].ok, ok) + } + }) + } +} diff --git a/pkg/peertls/utils.go b/pkg/peertls/utils.go index 4cd54b19f..b4ba92abf 100644 --- a/pkg/peertls/utils.go +++ b/pkg/peertls/utils.go @@ -75,10 +75,11 @@ func verifyChainSignatures(certs []*x509.Certificate) error { } func verifyCertSignature(parentCert, childCert *x509.Certificate) error { - return verifySignature(childCert.Signature, childCert.RawTBSCertificate, parentCert.PublicKey) + return VerifySignature(childCert.Signature, childCert.RawTBSCertificate, parentCert.PublicKey) } -func verifySignature(signedData []byte, data []byte, pubKey crypto.PublicKey) error { +// VerifySignature checks the signature against the passed data and public key +func VerifySignature(signedData []byte, data []byte, pubKey crypto.PublicKey) error { key, ok := pubKey.(*ecdsa.PublicKey) if !ok { return ErrUnsupportedKey.New("%T", key) diff --git a/pkg/provider/certificate_authority.go b/pkg/provider/certificate_authority.go index 1d0ce8e9c..49c130eb9 100644 --- a/pkg/provider/certificate_authority.go +++ b/pkg/provider/certificate_authority.go @@ -13,10 +13,10 @@ import ( "os" "github.com/zeebo/errs" + "storj.io/storj/pkg/utils" "storj.io/storj/pkg/peertls" "storj.io/storj/pkg/storj" - "storj.io/storj/pkg/utils" ) // PeerCertificateAuthority represents the CA which is used to validate peer identities @@ -206,55 +206,58 @@ func NewCA(ctx context.Context, opts NewCAOptions) (*FullCertificateAuthority, e // Save saves a CA with the given configuration func (fc FullCAConfig) Save(ca *FullCertificateAuthority) error { - f := os.O_WRONLY | os.O_CREATE - c, err := openCert(fc.CertPath, f) + mode := os.O_WRONLY | os.O_CREATE + certFile, err := openCert(fc.CertPath, mode) if err != nil { return err } - defer utils.LogClose(c) - k, err := openKey(fc.KeyPath, f) + + keyFile, err := openKey(fc.KeyPath, mode) if err != nil { - return err + return utils.CombineErrors(err, certFile.Close()) } - defer utils.LogClose(k) chain := []*x509.Certificate{ca.Cert} chain = append(chain, ca.RestChain...) - if err = peertls.WriteChain(c, chain...); err != nil { - return err - } - if err = peertls.WriteKey(k, ca.Key); err != nil { - return err - } - return nil + certWriteErr := peertls.WriteChain(certFile, chain...) + keyWriteErr := peertls.WriteKey(keyFile, ca.Key) + + return utils.CombineErrors(certWriteErr, keyWriteErr, certFile.Close(), keyFile.Close()) } // NewIdentity generates a new `FullIdentity` based on the CA. The CA // cert is included in the identity's cert chain and the identity's leaf cert // is signed by the CA. func (ca FullCertificateAuthority) NewIdentity() (*FullIdentity, error) { - lT, err := peertls.LeafTemplate() + leafTemplate, err := peertls.LeafTemplate() if err != nil { return nil, err } - k, err := peertls.NewKey() + leafKey, err := peertls.NewKey() if err != nil { return nil, err } - pk, ok := k.(*ecdsa.PrivateKey) + pk, ok := leafKey.(*ecdsa.PrivateKey) if !ok { - return nil, peertls.ErrUnsupportedKey.New("%T", k) + return nil, peertls.ErrUnsupportedKey.New("%T", leafKey) } - l, err := peertls.NewCert(lT, ca.Cert, &pk.PublicKey, ca.Key) + leafCert, err := peertls.NewCert(pk, ca.Key, leafTemplate, ca.Cert) if err != nil { return nil, err } + if ca.RestChain != nil && len(ca.RestChain) > 0 { + err := peertls.AddSignedLeafExt(ca.Key, leafCert) + if err != nil { + return nil, err + } + } + return &FullIdentity{ RestChain: ca.RestChain, CA: ca.Cert, - Leaf: l, - Key: k, + Leaf: leafCert, + Key: leafKey, ID: ca.ID, }, nil } diff --git a/pkg/provider/identity.go b/pkg/provider/identity.go index 2898cfa78..124e353a0 100644 --- a/pkg/provider/identity.go +++ b/pkg/provider/identity.go @@ -54,12 +54,6 @@ type FullIdentity struct { ID storj.NodeID // Key is the key this identity uses with the leaf for communication. Key crypto.PrivateKey - // PeerCAWhitelist is a whitelist of CA certs which, if present, restricts which peers this identity will verify as valid; - // peer certs must be signed by a CA in this list to pass peer certificate verification. - PeerCAWhitelist []*x509.Certificate - // VerfyAuthExtSig if true, client leafs which handshake with this identity must contain a valid "authority signature extension" - // (NB: authority signature extensions are verified against certs in the `PeerCAWhitelist`; i.e. if true, a whitelist must be provided) - VerifyAuthExtSig bool } // IdentitySetupConfig allows you to run a set of Responsibilities with the given @@ -74,16 +68,42 @@ type IdentitySetupConfig struct { // IdentityConfig allows you to run a set of Responsibilities with the given // identity. You can also just load an Identity from disk. type IdentityConfig struct { - CertPath string `help:"path to the certificate chain for this identity" default:"$CONFDIR/identity.cert"` - KeyPath string `help:"path to the private key for this identity" default:"$CONFDIR/identity.key"` + CertPath string `help:"path to the certificate chain for this identity" default:"$CONFDIR/identity.cert"` + KeyPath string `help:"path to the private key for this identity" default:"$CONFDIR/identity.key"` + Server ServerConfig +} + +// ServerConfig holds server specific configuration parameters +type ServerConfig struct { PeerCAWhitelistPath string `help:"path to the CA cert whitelist (peer identities must be signed by one these to be verified)"` - VerifyAuthExtSig bool `help:"if true, client leafs must contain a valid \"authority signature extension\" (NB: authority signature extensions are verified against certs in the peer ca whitelist; i.e. if true, a whitelist must be provided)" default:"false"` Address string `help:"address to listen on" default:":7777"` + Extensions peertls.TLSExtConfig +} + +// ServerOptions holds config, identity, and peer verification function data for use with a grpc server. +type ServerOptions struct { + Config ServerConfig + Ident *FullIdentity + PCVFuncs []peertls.PeerCertVerificationFunc +} + +// NewServerOptions is a constructor for `serverOptions` given an identity and config +func NewServerOptions(i *FullIdentity, c ServerConfig) (*ServerOptions, error) { + pcvFuncs, err := c.PCVFuncs() + if err != nil { + return nil, err + } + + return &ServerOptions{ + Config: c, + Ident: i, + PCVFuncs: pcvFuncs, + }, nil } // FullIdentityFromPEM loads a FullIdentity from a certificate chain and // private key file -func FullIdentityFromPEM(chainPEM, keyPEM, CAWhitelistPEM []byte) (*FullIdentity, error) { +func FullIdentityFromPEM(chainPEM, keyPEM []byte) (*FullIdentity, error) { cb, err := decodePEM(chainPEM) if err != nil { return nil, errs.Wrap(err) @@ -110,28 +130,12 @@ func FullIdentityFromPEM(chainPEM, keyPEM, CAWhitelistPEM []byte) (*FullIdentity return nil, err } - var ( - wb [][]byte - whitelist []*x509.Certificate - ) - if CAWhitelistPEM != nil { - wb, err = decodePEM(CAWhitelistPEM) - if err != nil { - return nil, errs.Wrap(err) - } - whitelist, err = ParseCertChain(wb) - if err != nil { - return nil, errs.Wrap(err) - } - } - return &FullIdentity{ - RestChain: ch[2:], - CA: ch[1], - Leaf: ch[0], - Key: k, - ID: i, - PeerCAWhitelist: whitelist, + RestChain: ch[2:], + CA: ch[1], + Leaf: ch[0], + Key: k, + ID: i, }, nil } @@ -217,12 +221,7 @@ func (ic IdentityConfig) Load() (*FullIdentity, error) { if err != nil { return nil, peertls.ErrNotExist.Wrap(err) } - w, err := ioutil.ReadFile(ic.PeerCAWhitelistPath) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - - fi, err := FullIdentityFromPEM(c, k, w) + fi, err := FullIdentityFromPEM(c, k) if err != nil { return nil, errs.New("failed to load identity %#v, %#v: %v", ic.CertPath, ic.KeyPath, err) @@ -259,18 +258,22 @@ func (ic IdentityConfig) Save(fi *FullIdentity) error { func (ic IdentityConfig) Run(ctx context.Context, interceptor grpc.UnaryServerInterceptor, responsibilities ...Responsibility) (err error) { defer mon.Task()(&ctx)(&err) - pi, err := ic.Load() + ident, err := ic.Load() if err != nil { return err } - lis, err := net.Listen("tcp", ic.Address) + lis, err := net.Listen("tcp", ic.Server.Address) if err != nil { return err } defer func() { _ = lis.Close() }() - s, err := NewProvider(pi, lis, interceptor, responsibilities...) + opts, err := NewServerOptions(ident, ic.Server) + if err != nil { + return err + } + s, err := NewProvider(opts, lis, interceptor, responsibilities...) if err != nil { return err } @@ -331,22 +334,7 @@ func (fi *FullIdentity) DialOption(id storj.NodeID) (grpc.DialOption, error) { InsecureSkipVerify: true, VerifyPeerCertificate: peertls.VerifyPeerFunc( peertls.VerifyPeerCertChains, - func(_ [][]byte, parsedChains [][]*x509.Certificate) error { - if id == (storj.NodeID{}) { - return nil - } - - peer, err := PeerIdentityFromCerts(parsedChains[0][0], parsedChains[0][1], parsedChains[0][2:]) - if err != nil { - return err - } - - if peer.ID.String() != id.String() { - return Error.New("peer ID did not match requested ID") - } - - return nil - }, + verifyIdentity(id), ), } @@ -379,3 +367,66 @@ func NewFullIdentity(ctx context.Context, difficulty uint16, concurrency uint) ( } return identity, err } + +// PCVFuncs returns a slice of peer certificate verification functions based on the config. +func (c ServerConfig) PCVFuncs() (pcvs []peertls.PeerCertVerificationFunc, err error) { + var caWhitelist []*x509.Certificate + if c.PeerCAWhitelistPath != "" { + caWhitelist, err = loadWhitelist(c.PeerCAWhitelistPath) + if err != nil { + return nil, err + } + pcvs = append(pcvs, peertls.VerifyCAWhitelist(caWhitelist)) + } + + exts := peertls.ParseExtensions(c.Extensions, caWhitelist) + pcvs = append(pcvs, exts.VerifyFunc()) + + return pcvs, nil +} + +func (so *ServerOptions) grpcOpts() (grpc.ServerOption, error) { + return so.Ident.ServerOption(so.PCVFuncs...) +} + +func verifyIdentity(id storj.NodeID) peertls.PeerCertVerificationFunc { + return func(_ [][]byte, parsedChains [][]*x509.Certificate) error { + if id == (storj.NodeID{}) { + return nil + } + + peer, err := PeerIdentityFromCerts(parsedChains[0][0], parsedChains[0][1], parsedChains[0][2:]) + if err != nil { + return err + } + + if peer.ID.String() != id.String() { + return Error.New("peer ID did not match requested ID") + } + + return nil + } +} + +func loadWhitelist(path string) ([]*x509.Certificate, error) { + w, err := ioutil.ReadFile(path) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + + var ( + wb [][]byte + whitelist []*x509.Certificate + ) + if w != nil { + wb, err = decodePEM(w) + if err != nil { + return nil, errs.Wrap(err) + } + whitelist, err = ParseCertChain(wb) + if err != nil { + return nil, errs.Wrap(err) + } + } + return whitelist, nil +} diff --git a/pkg/provider/identity_test.go b/pkg/provider/identity_test.go index 5b48c4378..b37db2a5e 100644 --- a/pkg/provider/identity_test.go +++ b/pkg/provider/identity_test.go @@ -9,10 +9,8 @@ import ( "crypto/ecdsa" "crypto/x509" "encoding/pem" - "fmt" "io/ioutil" "os" - "path" "path/filepath" "runtime" "testing" @@ -23,77 +21,73 @@ import ( ) func TestPeerIdentityFromCertChain(t *testing.T) { - k, err := peertls.NewKey() + caKey, err := peertls.NewKey() assert.NoError(t, err) - caT, err := peertls.CATemplate() + caTemplate, err := peertls.CATemplate() assert.NoError(t, err) - cp, _ := k.(*ecdsa.PrivateKey) - c, err := peertls.NewCert(caT, nil, &cp.PublicKey, k) + caCert, err := peertls.NewCert(caKey, caKey, caTemplate, nil) assert.NoError(t, err) - lT, err := peertls.LeafTemplate() + leafTemplate, err := peertls.LeafTemplate() assert.NoError(t, err) - lk, err := peertls.NewKey() + leafKey, err := peertls.NewKey() assert.NoError(t, err) - lp, _ := lk.(*ecdsa.PrivateKey) - l, err := peertls.NewCert(lT, caT, &lp.PublicKey, k) + leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caTemplate) assert.NoError(t, err) - pi, err := PeerIdentityFromCerts(l, c, nil) + peerIdent, err := PeerIdentityFromCerts(leafCert, caCert, nil) assert.NoError(t, err) - assert.Equal(t, c, pi.CA) - assert.Equal(t, l, pi.Leaf) - assert.NotEmpty(t, pi.ID) + assert.Equal(t, caCert, peerIdent.CA) + assert.Equal(t, leafCert, peerIdent.Leaf) + assert.NotEmpty(t, peerIdent.ID) } func TestFullIdentityFromPEM(t *testing.T) { - ck, err := peertls.NewKey() + caKey, err := peertls.NewKey() assert.NoError(t, err) - caT, err := peertls.CATemplate() + caTemplate, err := peertls.CATemplate() assert.NoError(t, err) - cp, _ := ck.(*ecdsa.PrivateKey) - c, err := peertls.NewCert(caT, nil, &cp.PublicKey, ck) + caCert, err := peertls.NewCert(caKey, caKey, caTemplate, nil) assert.NoError(t, err) assert.NoError(t, err) - assert.NotEmpty(t, c) + assert.NotEmpty(t, caCert) - lT, err := peertls.LeafTemplate() + leafTemplate, err := peertls.LeafTemplate() assert.NoError(t, err) - lk, err := peertls.NewKey() + leafKey, err := peertls.NewKey() assert.NoError(t, err) - lp, _ := lk.(*ecdsa.PrivateKey) - l, err := peertls.NewCert(lT, caT, &lp.PublicKey, ck) + leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caTemplate) assert.NoError(t, err) - assert.NotEmpty(t, l) + assert.NotEmpty(t, leafCert) chainPEM := bytes.NewBuffer([]byte{}) - assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(l.Raw))) - assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(c.Raw))) + assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(leafCert.Raw))) + assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(caCert.Raw))) - lkE, ok := lk.(*ecdsa.PrivateKey) + leafECKey, ok := leafKey.(*ecdsa.PrivateKey) assert.True(t, ok) - assert.NotEmpty(t, lkE) + assert.NotEmpty(t, leafECKey) - lkB, err := x509.MarshalECPrivateKey(lkE) + leafKeyBytes, err := x509.MarshalECPrivateKey(leafECKey) assert.NoError(t, err) - assert.NotEmpty(t, lkB) + assert.NotEmpty(t, leafKeyBytes) keyPEM := bytes.NewBuffer([]byte{}) - assert.NoError(t, pem.Encode(keyPEM, peertls.NewKeyBlock(lkB))) + assert.NoError(t, pem.Encode(keyPEM, peertls.NewKeyBlock(leafKeyBytes))) - fi, err := FullIdentityFromPEM(chainPEM.Bytes(), keyPEM.Bytes(), nil) + fullIdent, err := FullIdentityFromPEM(chainPEM.Bytes(), keyPEM.Bytes()) assert.NoError(t, err) - assert.Equal(t, l.Raw, fi.Leaf.Raw) - assert.Equal(t, c.Raw, fi.CA.Raw) - assert.Equal(t, lk, fi.Key) + assert.Equal(t, leafCert.Raw, fullIdent.Leaf.Raw) + assert.Equal(t, caCert.Raw, fullIdent.CA.Raw) + assert.Equal(t, leafKey, fullIdent.Key) } func TestIdentityConfig_SaveIdentity(t *testing.T) { @@ -185,7 +179,7 @@ AwEHoUQDQgAEoLy/0hs5deTXZunRumsMkiHpF0g8wAc58aXANmr7Mxx9tzoIYFnx ic, cleanup, err := tempIdentityConfig() assert.NoError(t, err) - fi, err := FullIdentityFromPEM([]byte(chain), []byte(key), nil) + fi, err := FullIdentityFromPEM([]byte(chain), []byte(key)) assert.NoError(t, err) return cleanup, ic, fi, difficulty @@ -200,7 +194,6 @@ func TestIdentityConfig_LoadIdentity(t *testing.T) { fi, err := ic.Load() assert.NoError(t, err) - assert.Nil(t, fi.PeerCAWhitelist) assert.NotEmpty(t, fi) assert.NotEmpty(t, fi.Key) assert.NotEmpty(t, fi.Leaf) @@ -211,24 +204,10 @@ func TestIdentityConfig_LoadIdentity(t *testing.T) { assert.Equal(t, expectedFI.Leaf, fi.Leaf) assert.Equal(t, expectedFI.CA, fi.CA) assert.Equal(t, expectedFI.ID.Bytes(), fi.ID.Bytes()) +} - tmp := path.Join(os.TempDir(), "temp-ca-whitelist.pem") - w, err := os.Create(tmp) - assert.NoError(t, err) - defer func() { - err := os.RemoveAll(tmp) - if err != nil { - fmt.Printf("unable to cleanup temp ca whitelist at \"%s\": %s", tmp, err.Error()) - } - }() +func TestNewI(t *testing.T) { - err = peertls.WriteChain(w, fi.CA) - assert.NoError(t, err) - - ic.PeerCAWhitelistPath = tmp - fi, err = ic.Load() - assert.NoError(t, err) - assert.NotEmpty(t, fi.PeerCAWhitelist) } func TestNodeID_Difficulty(t *testing.T) { diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index ba9188d67..aa56d8900 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -15,7 +15,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "storj.io/storj/pkg/peertls" "storj.io/storj/storage" ) @@ -42,13 +41,9 @@ type Provider struct { // NewProvider creates a Provider out of an Identity, a net.Listener, a UnaryInterceptorProvider and // a set of responsibilities. -func NewProvider(identity *FullIdentity, lis net.Listener, interceptor grpc.UnaryServerInterceptor, +func NewProvider(opts *ServerOptions, lis net.Listener, interceptor grpc.UnaryServerInterceptor, responsibilities ...Responsibility) (*Provider, error) { - // NB: talk to anyone with an identity - ident, err := identity.ServerOption(peertls.VerifyCAWhitelist( - identity.PeerCAWhitelist, - identity.VerifyAuthExtSig, - )) + grpcOpts, err := opts.grpcOpts() if err != nil { return nil, err } @@ -63,10 +58,10 @@ func NewProvider(identity *FullIdentity, lis net.Listener, interceptor grpc.Unar grpc: grpc.NewServer( grpc.StreamInterceptor(streamInterceptor), grpc.UnaryInterceptor(unaryInterceptor), - ident, + grpcOpts, ), next: responsibilities, - identity: identity, + identity: opts.Ident, }, nil } diff --git a/pkg/provider/utils.go b/pkg/provider/utils.go index 4c78f9244..6d78a5723 100644 --- a/pkg/provider/utils.go +++ b/pkg/provider/utils.go @@ -7,10 +7,7 @@ import ( "context" "crypto" "crypto/ecdsa" - "crypto/rand" "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" "encoding/pem" "os" "path/filepath" @@ -103,7 +100,7 @@ func newCAWorker(ctx context.Context, difficulty uint16, parentCert *x509.Certif return } - c, err := newCACert(k, parentKey, ct, parentCert) + c, err := peertls.NewCert(k, parentKey, ct, parentCert) if err != nil { eC <- err return @@ -120,53 +117,6 @@ func newCAWorker(ctx context.Context, difficulty uint16, parentCert *x509.Certif caC <- ca } -func newCACert(key, parentKey crypto.PrivateKey, template, parentCert *x509.Certificate) (*x509.Certificate, error) { - p, ok := key.(*ecdsa.PrivateKey) - if !ok { - return nil, peertls.ErrUnsupportedKey.New("%T", key) - } - - var signingKey crypto.PrivateKey - if parentKey != nil { - signingKey = parentKey - } else { - signingKey = key - } - - cert, err := peertls.NewCert(template, parentCert, &p.PublicKey, signingKey) - if err != nil { - return nil, err - } - - if parentKey != nil { - p, ok := parentKey.(*ecdsa.PrivateKey) - if !ok { - return nil, peertls.ErrUnsupportedKey.New("%T", key) - } - - hash := crypto.SHA256.New() - _, err := hash.Write(cert.RawTBSCertificate) - if err != nil { - return nil, peertls.ErrSign.Wrap(err) - } - r, s, err := ecdsa.Sign(rand.Reader, p, hash.Sum(nil)) - if err != nil { - return nil, peertls.ErrSign.Wrap(err) - } - - signature, err := asn1.Marshal(peertls.ECDSASignature{R: r, S: s}) - if err != nil { - return nil, peertls.ErrSign.Wrap(err) - } - - cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{ - Id: peertls.AuthoritySignatureExtID, - Value: signature, - }) - } - return cert, nil -} - func openCert(path string, flag int) (*os.File, error) { if err := os.MkdirAll(filepath.Dir(path), 0744); err != nil { return nil, errs.Wrap(err)