TLS extension processing (#771)

This commit is contained in:
Bryan White 2018-12-07 14:44:25 +01:00 committed by GitHub
parent 482ed7f4e2
commit 228aa34ff6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 537 additions and 378 deletions

View File

@ -53,7 +53,7 @@ check-copyrights: ## Check source files for copyright headers
.PHONY: goimports-fix .PHONY: goimports-fix
goimports-fix: ## Applies goimports to every go file (excluding vendored files) 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 .PHONY: proto
proto: ## Rebuild protobuf files proto: ## Rebuild protobuf files

View File

@ -96,14 +96,14 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) {
// start satellite // start satellite
go func() { go func() {
_, _ = fmt.Printf("starting satellite on %s\n", _, _ = fmt.Printf("starting satellite on %s\n",
runCfg.Satellite.Identity.Address) runCfg.Satellite.Identity.Server.Address)
if runCfg.Satellite.Audit.SatelliteAddr == "" { 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 == "" { 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) database, err := satellitedb.NewDB(runCfg.Satellite.Database)
@ -148,7 +148,7 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) {
return return
} }
address := v.Identity.Address address := v.Identity.Server.Address
storagenode := fmt.Sprintf("%s:%s", identity.ID.String(), address) storagenode := fmt.Sprintf("%s:%s", identity.ID.String(), address)
_, _ = fmt.Printf("starting storage node %d %s (kad on %s)\n", i, storagenode, 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 // start s3 uplink
go func() { go func() {
_, _ = fmt.Printf("Starting s3-gateway on %s\nAccess key: %s\nSecret key: %s\n", _, _ = 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.AccessKey,
runCfg.Uplink.Minio.SecretKey) runCfg.Uplink.Minio.SecretKey)
errch <- runCfg.Uplink.Run(ctx) errch <- runCfg.Uplink.Run(ctx)

View File

@ -137,7 +137,7 @@ func cmdSetup(cmd *cobra.Command, args []string) (err error) {
overrides := map[string]interface{}{ overrides := map[string]interface{}{
"satellite.identity.cert-path": setupCfg.HCIdentity.CertPath, "satellite.identity.cert-path": setupCfg.HCIdentity.CertPath,
"satellite.identity.key-path": setupCfg.HCIdentity.KeyPath, "satellite.identity.key-path": setupCfg.HCIdentity.KeyPath,
"satellite.identity.address": joinHostPort( "satellite.identity.server.address": joinHostPort(
setupCfg.ListenHost, startingPort+1), setupCfg.ListenHost, startingPort+1),
"satellite.kademlia.bootstrap-addr": joinHostPort( "satellite.kademlia.bootstrap-addr": joinHostPort(
setupCfg.ListenHost, startingPort+1), setupCfg.ListenHost, startingPort+1),
@ -175,7 +175,7 @@ func cmdSetup(cmd *cobra.Command, args []string) (err error) {
storagenodePath, "identity.cert") storagenodePath, "identity.cert")
overrides[storagenode+"identity.key-path"] = filepath.Join( overrides[storagenode+"identity.key-path"] = filepath.Join(
storagenodePath, "identity.key") storagenodePath, "identity.key")
overrides[storagenode+"identity.address"] = joinHostPort( overrides[storagenode+"identity.server.address"] = joinHostPort(
setupCfg.ListenHost, startingPort+i*2+3) setupCfg.ListenHost, startingPort+i*2+3)
overrides[storagenode+"kademlia.bootstrap-addr"] = joinHostPort( overrides[storagenode+"kademlia.bootstrap-addr"] = joinHostPort(
setupCfg.ListenHost, startingPort+1) setupCfg.ListenHost, startingPort+1)

View File

@ -26,7 +26,7 @@ func cmdRun(cmd *cobra.Command, args []string) (err error) {
return fmt.Errorf("Invalid argument %#v. Try 'uplink run'", flagname) 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) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return err return err

View File

@ -44,7 +44,7 @@ func (identities *Identities) NewIdentity() (*provider.FullIdentity, error) {
// mustParsePEM parses pem encoded chain and key strings. // mustParsePEM parses pem encoded chain and key strings.
func mustParsePEM(chain, key string) *provider.FullIdentity { func mustParsePEM(chain, key string) *provider.FullIdentity {
// TODO: add whitelist handling somehow // 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 { if err != nil {
panic(err) panic(err)
} }

View File

@ -61,7 +61,12 @@ func (planet *Planet) newNode(name string, nodeType pb.NodeType) (*Node, error)
node.Transport = transport.NewClient(identity) 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 { if err != nil {
return nil, utils.CombineErrors(err, listener.Close()) return nil, utils.CombineErrors(err, listener.Close())
} }

View File

@ -106,7 +106,7 @@ func (c Config) Run(ctx context.Context) (err error) {
} }
minio.Main([]string{"storj", "gateway", "storj", 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") return Error.New("unexpected minio exit")
} }

78
pkg/peertls/extensions.go Normal file
View File

@ -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
}
}

View File

@ -9,6 +9,7 @@ import (
"crypto/rand" "crypto/rand"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"encoding/pem" "encoding/pem"
"io" "io"
@ -26,30 +27,43 @@ const (
BlockTypeIDOptions = "ID OPTIONS" 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 ( 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) // ExtensionIDs is a map from an enum to extension object identifiers.
// This extension allows for an additional signature per certificate ExtensionIDs = map[int]asn1.ObjectIdentifier{
AuthoritySignatureExtID = asn1.ObjectIdentifier{2, 999, 1} SignedCertExtID: {2, 999, 1, 1},
// ErrNotExist is used when a file or directory doesn't exist 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") 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") 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") 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") 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") 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") 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") ErrVerifySignature = errs.Class("tls certificate signature verification error")
// ErrVerifyCertificateChain is used when a certificate chain can't be verified from leaf to root // 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") 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 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") ErrVerifyCAWhitelist = errs.Class("not signed by any CA in the whitelist")
// ErrSign is used when something goes wrong while generating a signature // ErrSign is used when something goes wrong while generating a signature.
ErrSign = errs.Class("unable to generate signature") ErrSign = errs.Class("unable to generate signature")
) )
@ -67,36 +81,6 @@ func NewKey() (crypto.PrivateKey, error) {
return k, nil 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` // VerifyPeerFunc combines multiple `*tls.Config#VerifyPeerCertificate`
// functions and adds certificate parsing. // functions and adds certificate parsing.
func VerifyPeerFunc(next ...PeerCertVerificationFunc) PeerCertVerificationFunc { func VerifyPeerFunc(next ...PeerCertVerificationFunc) PeerCertVerificationFunc {
@ -118,63 +102,41 @@ func VerifyPeerFunc(next ...PeerCertVerificationFunc) PeerCertVerificationFunc {
} }
// VerifyPeerCertChains verifies that the first certificate chain contains certificates // 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 { func VerifyPeerCertChains(_ [][]byte, parsedChains [][]*x509.Certificate) error {
return verifyChainSignatures(parsedChains[0]) return verifyChainSignatures(parsedChains[0])
} }
// VerifyCAWhitelist verifies that the peer identity's CA and leaf-extension was signed // 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 // by any one of the (certificate authority) certificates in the provided whitelist.
func VerifyCAWhitelist(cas []*x509.Certificate, verifyExtension bool) PeerCertVerificationFunc { func VerifyCAWhitelist(cas []*x509.Certificate) PeerCertVerificationFunc {
if cas == nil { if cas == nil {
return nil return nil
} }
return func(_ [][]byte, parsedChains [][]*x509.Certificate) error { 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 { for _, ca := range cas {
err = verifyCertSignature(ca, parsedChains[0][1]) err := verifyCertSignature(ca, parsedChains[0][1])
if err == nil { if err == nil {
if !verifyExtension { return nil
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 ErrVerifyCAWhitelist.New("extension signature doesn't match any CA in the whitelist")
return ErrVerifyCAWhitelist.Wrap(err)
} }
} }
// NewKeyBlock converts an ASN1/DER-encoded byte-slice of a private key into // 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 { func NewKeyBlock(b []byte) *pem.Block {
return &pem.Block{Type: BlockTypeEcPrivateKey, Bytes: b} return &pem.Block{Type: BlockTypeEcPrivateKey, Bytes: b}
} }
// NewCertBlock converts an ASN1/DER-encoded byte-slice of a tls certificate // 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 { func NewCertBlock(b []byte) *pem.Block {
return &pem.Block{Type: BlockTypeCertificate, Bytes: b} 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) { func TLSCert(chain [][]byte, leaf *x509.Certificate, key crypto.PrivateKey) (*tls.Certificate, error) {
var err error var err error
if leaf == nil { if leaf == nil {
@ -227,3 +189,70 @@ func WriteKey(w io.Writer, key crypto.PrivateKey) error {
} }
return nil 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
}

View File

@ -14,214 +14,282 @@ import (
) )
func TestNewCert_CA(t *testing.T) { func TestNewCert_CA(t *testing.T) {
k, err := NewKey() caKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
ct, err := CATemplate() caTemplate, err := CATemplate()
assert.NoError(t, err) assert.NoError(t, err)
p, _ := k.(*ecdsa.PrivateKey) caCert, err := NewCert(caKey, nil, caTemplate, nil)
c, err := NewCert(ct, nil, &p.PublicKey, k)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, k.(*ecdsa.PrivateKey)) assert.NotEmpty(t, caKey.(*ecdsa.PrivateKey))
assert.NotEmpty(t, c) assert.NotEmpty(t, caCert)
assert.NotEmpty(t, c.PublicKey.(*ecdsa.PublicKey)) assert.NotEmpty(t, caCert.PublicKey.(*ecdsa.PublicKey))
err = c.CheckSignatureFrom(c) err = caCert.CheckSignatureFrom(caCert)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestNewCert_Leaf(t *testing.T) { func TestNewCert_Leaf(t *testing.T) {
k, err := NewKey() caKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
ct, err := CATemplate() caTemplate, err := CATemplate()
assert.NoError(t, err) assert.NoError(t, err)
cp, _ := k.(*ecdsa.PrivateKey) caCert, err := NewCert(caKey, nil, caTemplate, nil)
c, err := NewCert(ct, nil, &cp.PublicKey, k)
assert.NoError(t, err) assert.NoError(t, err)
lt, err := LeafTemplate() leafKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
lp, _ := k.(*ecdsa.PrivateKey) leafTemplate, err := LeafTemplate()
l, err := NewCert(lt, ct, &lp.PublicKey, k)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, k.(*ecdsa.PrivateKey)) leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert)
assert.NotEmpty(t, l)
assert.NotEmpty(t, l.PublicKey.(*ecdsa.PublicKey))
err = c.CheckSignatureFrom(c)
assert.NoError(t, err) 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) assert.NoError(t, err)
} }
func TestVerifyPeerFunc(t *testing.T) { func TestVerifyPeerFunc(t *testing.T) {
k, err := NewKey() caKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
ct, err := CATemplate() caTemplate, err := CATemplate()
assert.NoError(t, err) assert.NoError(t, err)
cp, _ := k.(*ecdsa.PrivateKey) caCert, err := NewCert(caKey, nil, caTemplate, nil)
c, err := NewCert(ct, nil, &cp.PublicKey, k)
assert.NoError(t, err) assert.NoError(t, err)
lt, err := LeafTemplate() leafKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
lp, _ := k.(*ecdsa.PrivateKey) leafTemplate, err := LeafTemplate()
l, err := NewCert(lt, ct, &lp.PublicKey, k) assert.NoError(t, err)
leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert)
assert.NoError(t, err) assert.NoError(t, err)
testFunc := func(chain [][]byte, parsedChains [][]*x509.Certificate) error { testFunc := func(chain [][]byte, parsedChains [][]*x509.Certificate) error {
switch { switch {
case !bytes.Equal(chain[1], c.Raw): case !bytes.Equal(chain[1], caCert.Raw):
return errs.New("CA cert doesn't match") 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") 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") 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") 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") 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") 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 errs.New("parsed leaf cert doesn't match")
} }
return nil return nil
} }
err = VerifyPeerFunc(testFunc)([][]byte{l.Raw, c.Raw}, nil) err = VerifyPeerFunc(testFunc)([][]byte{leafCert.Raw, caCert.Raw}, nil)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestVerifyPeerCertChains(t *testing.T) { func TestVerifyPeerCertChains(t *testing.T) {
k, err := NewKey() caKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
ct, err := CATemplate() caTemplate, err := CATemplate()
assert.NoError(t, err) assert.NoError(t, err)
cp, ok := k.(*ecdsa.PrivateKey) caCert, err := NewCert(caKey, nil, caTemplate, nil)
assert.True(t, ok)
c, err := NewCert(ct, nil, &cp.PublicKey, k)
assert.NoError(t, err) assert.NoError(t, err)
lt, err := LeafTemplate() leafKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
lp, ok := k.(*ecdsa.PrivateKey) leafTemplate, err := LeafTemplate()
assert.True(t, ok)
l, err := NewCert(lt, ct, &lp.PublicKey, k)
assert.NoError(t, err) assert.NoError(t, err)
err = VerifyPeerFunc(VerifyPeerCertChains)([][]byte{l.Raw, c.Raw}, nil) leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert)
assert.NoError(t, err) 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) assert.NoError(t, err)
k2, err := NewKey() wrongKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
l, err = NewCert(lt, nil, &lp.PublicKey, k2) leafCert, err = NewCert(leafKey, wrongKey, leafTemplate, caCert)
assert.NoError(t, err) 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, ErrVerifyPeerCert.Has(err))
assert.True(t, ErrVerifyCertificateChain.Has(err)) assert.True(t, ErrVerifyCertificateChain.Has(err))
} }
func TestVerifyCAWhitelist(t *testing.T) { func TestVerifyCAWhitelist(t *testing.T) {
k, err := NewKey() caKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
ct, err := CATemplate() caTemplate, err := CATemplate()
assert.NoError(t, err) assert.NoError(t, err)
cp, ok := k.(*ecdsa.PrivateKey) caCert, err := NewCert(caKey, nil, caTemplate, nil)
assert.True(t, ok)
c, err := NewCert(ct, nil, &cp.PublicKey, k)
assert.NoError(t, err) assert.NoError(t, err)
lt, err := LeafTemplate() leafKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
lk, err := NewKey() leafTemplate, err := LeafTemplate()
assert.NoError(t, err) assert.NoError(t, err)
lp, ok := lk.(*ecdsa.PrivateKey) leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert)
assert.True(t, ok)
l, err := NewCert(lt, ct, &lp.PublicKey, k)
assert.NoError(t, err) 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) 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) assert.NoError(t, err)
zk, err := NewKey() rootKey, err := NewKey()
assert.NoError(t, err) assert.NoError(t, err)
zt, err := CATemplate() rootTemplate, err := CATemplate()
assert.NoError(t, err) assert.NoError(t, err)
zp, ok := zk.(*ecdsa.PrivateKey) rootCert, err := NewCert(rootKey, nil, rootTemplate, nil)
assert.True(t, ok)
z, err := NewCert(zt, nil, &zp.PublicKey, zk)
assert.NoError(t, err) 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, 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) 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) assert.NoError(t, err)
xt, err := LeafTemplate() ca2Cert, err := NewCert(caKey, rootKey, caTemplate, rootCert)
assert.NoError(t, err) assert.NoError(t, err)
xk, err := NewKey() leaf2Cert, err := NewCert(leafKey, caKey, leafTemplate, ca2Cert)
assert.NoError(t, err) assert.NoError(t, err)
xp, ok := xk.(*ecdsa.PrivateKey) // length 3 chain; first cert in whitelist is signer
assert.True(t, ok) err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{rootCert, caCert}))([][]byte{leaf2Cert.Raw, ca2Cert.Raw, rootCert.Raw}, nil)
x, err := NewCert(xt, zt, &xp.PublicKey, zk)
assert.NoError(t, err) assert.NoError(t, err)
yt, err := LeafTemplate() // length 3 chain; last cert in whitelist is signer
assert.NoError(t, err) err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{caCert, rootCert}))([][]byte{leaf2Cert.Raw, ca2Cert.Raw, rootCert.Raw}, nil)
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)
assert.NoError(t, err) 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)
}
})
}
}

View File

@ -75,10 +75,11 @@ func verifyChainSignatures(certs []*x509.Certificate) error {
} }
func verifyCertSignature(parentCert, childCert *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) key, ok := pubKey.(*ecdsa.PublicKey)
if !ok { if !ok {
return ErrUnsupportedKey.New("%T", key) return ErrUnsupportedKey.New("%T", key)

View File

@ -13,10 +13,10 @@ import (
"os" "os"
"github.com/zeebo/errs" "github.com/zeebo/errs"
"storj.io/storj/pkg/utils"
"storj.io/storj/pkg/peertls" "storj.io/storj/pkg/peertls"
"storj.io/storj/pkg/storj" "storj.io/storj/pkg/storj"
"storj.io/storj/pkg/utils"
) )
// PeerCertificateAuthority represents the CA which is used to validate peer identities // 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 // Save saves a CA with the given configuration
func (fc FullCAConfig) Save(ca *FullCertificateAuthority) error { func (fc FullCAConfig) Save(ca *FullCertificateAuthority) error {
f := os.O_WRONLY | os.O_CREATE mode := os.O_WRONLY | os.O_CREATE
c, err := openCert(fc.CertPath, f) certFile, err := openCert(fc.CertPath, mode)
if err != nil { if err != nil {
return err return err
} }
defer utils.LogClose(c)
k, err := openKey(fc.KeyPath, f) keyFile, err := openKey(fc.KeyPath, mode)
if err != nil { if err != nil {
return err return utils.CombineErrors(err, certFile.Close())
} }
defer utils.LogClose(k)
chain := []*x509.Certificate{ca.Cert} chain := []*x509.Certificate{ca.Cert}
chain = append(chain, ca.RestChain...) chain = append(chain, ca.RestChain...)
if err = peertls.WriteChain(c, chain...); err != nil { certWriteErr := peertls.WriteChain(certFile, chain...)
return err keyWriteErr := peertls.WriteKey(keyFile, ca.Key)
}
if err = peertls.WriteKey(k, ca.Key); err != nil { return utils.CombineErrors(certWriteErr, keyWriteErr, certFile.Close(), keyFile.Close())
return err
}
return nil
} }
// NewIdentity generates a new `FullIdentity` based on the CA. The CA // 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 // cert is included in the identity's cert chain and the identity's leaf cert
// is signed by the CA. // is signed by the CA.
func (ca FullCertificateAuthority) NewIdentity() (*FullIdentity, error) { func (ca FullCertificateAuthority) NewIdentity() (*FullIdentity, error) {
lT, err := peertls.LeafTemplate() leafTemplate, err := peertls.LeafTemplate()
if err != nil { if err != nil {
return nil, err return nil, err
} }
k, err := peertls.NewKey() leafKey, err := peertls.NewKey()
if err != nil { if err != nil {
return nil, err return nil, err
} }
pk, ok := k.(*ecdsa.PrivateKey) pk, ok := leafKey.(*ecdsa.PrivateKey)
if !ok { 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 { if err != nil {
return nil, err 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{ return &FullIdentity{
RestChain: ca.RestChain, RestChain: ca.RestChain,
CA: ca.Cert, CA: ca.Cert,
Leaf: l, Leaf: leafCert,
Key: k, Key: leafKey,
ID: ca.ID, ID: ca.ID,
}, nil }, nil
} }

View File

@ -54,12 +54,6 @@ type FullIdentity struct {
ID storj.NodeID ID storj.NodeID
// Key is the key this identity uses with the leaf for communication. // Key is the key this identity uses with the leaf for communication.
Key crypto.PrivateKey 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 // 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 // IdentityConfig allows you to run a set of Responsibilities with the given
// identity. You can also just load an Identity from disk. // identity. You can also just load an Identity from disk.
type IdentityConfig struct { type IdentityConfig struct {
CertPath string `help:"path to the certificate chain for this identity" default:"$CONFDIR/identity.cert"` 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"` 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)"` 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"` 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 // FullIdentityFromPEM loads a FullIdentity from a certificate chain and
// private key file // private key file
func FullIdentityFromPEM(chainPEM, keyPEM, CAWhitelistPEM []byte) (*FullIdentity, error) { func FullIdentityFromPEM(chainPEM, keyPEM []byte) (*FullIdentity, error) {
cb, err := decodePEM(chainPEM) cb, err := decodePEM(chainPEM)
if err != nil { if err != nil {
return nil, errs.Wrap(err) return nil, errs.Wrap(err)
@ -110,28 +130,12 @@ func FullIdentityFromPEM(chainPEM, keyPEM, CAWhitelistPEM []byte) (*FullIdentity
return nil, err 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{ return &FullIdentity{
RestChain: ch[2:], RestChain: ch[2:],
CA: ch[1], CA: ch[1],
Leaf: ch[0], Leaf: ch[0],
Key: k, Key: k,
ID: i, ID: i,
PeerCAWhitelist: whitelist,
}, nil }, nil
} }
@ -217,12 +221,7 @@ func (ic IdentityConfig) Load() (*FullIdentity, error) {
if err != nil { if err != nil {
return nil, peertls.ErrNotExist.Wrap(err) return nil, peertls.ErrNotExist.Wrap(err)
} }
w, err := ioutil.ReadFile(ic.PeerCAWhitelistPath) fi, err := FullIdentityFromPEM(c, k)
if err != nil && !os.IsNotExist(err) {
return nil, err
}
fi, err := FullIdentityFromPEM(c, k, w)
if err != nil { if err != nil {
return nil, errs.New("failed to load identity %#v, %#v: %v", return nil, errs.New("failed to load identity %#v, %#v: %v",
ic.CertPath, ic.KeyPath, err) 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) { func (ic IdentityConfig) Run(ctx context.Context, interceptor grpc.UnaryServerInterceptor, responsibilities ...Responsibility) (err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)
pi, err := ic.Load() ident, err := ic.Load()
if err != nil { if err != nil {
return err return err
} }
lis, err := net.Listen("tcp", ic.Address) lis, err := net.Listen("tcp", ic.Server.Address)
if err != nil { if err != nil {
return err return err
} }
defer func() { _ = lis.Close() }() 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 { if err != nil {
return err return err
} }
@ -331,22 +334,7 @@ func (fi *FullIdentity) DialOption(id storj.NodeID) (grpc.DialOption, error) {
InsecureSkipVerify: true, InsecureSkipVerify: true,
VerifyPeerCertificate: peertls.VerifyPeerFunc( VerifyPeerCertificate: peertls.VerifyPeerFunc(
peertls.VerifyPeerCertChains, peertls.VerifyPeerCertChains,
func(_ [][]byte, parsedChains [][]*x509.Certificate) error { verifyIdentity(id),
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
},
), ),
} }
@ -379,3 +367,66 @@ func NewFullIdentity(ctx context.Context, difficulty uint16, concurrency uint) (
} }
return identity, err 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
}

View File

@ -9,10 +9,8 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"testing" "testing"
@ -23,77 +21,73 @@ import (
) )
func TestPeerIdentityFromCertChain(t *testing.T) { func TestPeerIdentityFromCertChain(t *testing.T) {
k, err := peertls.NewKey() caKey, err := peertls.NewKey()
assert.NoError(t, err) assert.NoError(t, err)
caT, err := peertls.CATemplate() caTemplate, err := peertls.CATemplate()
assert.NoError(t, err) assert.NoError(t, err)
cp, _ := k.(*ecdsa.PrivateKey) caCert, err := peertls.NewCert(caKey, caKey, caTemplate, nil)
c, err := peertls.NewCert(caT, nil, &cp.PublicKey, k)
assert.NoError(t, err) assert.NoError(t, err)
lT, err := peertls.LeafTemplate() leafTemplate, err := peertls.LeafTemplate()
assert.NoError(t, err) assert.NoError(t, err)
lk, err := peertls.NewKey() leafKey, err := peertls.NewKey()
assert.NoError(t, err) assert.NoError(t, err)
lp, _ := lk.(*ecdsa.PrivateKey) leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caTemplate)
l, err := peertls.NewCert(lT, caT, &lp.PublicKey, k)
assert.NoError(t, err) assert.NoError(t, err)
pi, err := PeerIdentityFromCerts(l, c, nil) peerIdent, err := PeerIdentityFromCerts(leafCert, caCert, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c, pi.CA) assert.Equal(t, caCert, peerIdent.CA)
assert.Equal(t, l, pi.Leaf) assert.Equal(t, leafCert, peerIdent.Leaf)
assert.NotEmpty(t, pi.ID) assert.NotEmpty(t, peerIdent.ID)
} }
func TestFullIdentityFromPEM(t *testing.T) { func TestFullIdentityFromPEM(t *testing.T) {
ck, err := peertls.NewKey() caKey, err := peertls.NewKey()
assert.NoError(t, err) assert.NoError(t, err)
caT, err := peertls.CATemplate() caTemplate, err := peertls.CATemplate()
assert.NoError(t, err) assert.NoError(t, err)
cp, _ := ck.(*ecdsa.PrivateKey) caCert, err := peertls.NewCert(caKey, caKey, caTemplate, nil)
c, err := peertls.NewCert(caT, nil, &cp.PublicKey, ck)
assert.NoError(t, err) assert.NoError(t, err)
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) assert.NoError(t, err)
lk, err := peertls.NewKey() leafKey, err := peertls.NewKey()
assert.NoError(t, err) assert.NoError(t, err)
lp, _ := lk.(*ecdsa.PrivateKey) leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caTemplate)
l, err := peertls.NewCert(lT, caT, &lp.PublicKey, ck)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, l) assert.NotEmpty(t, leafCert)
chainPEM := bytes.NewBuffer([]byte{}) chainPEM := bytes.NewBuffer([]byte{})
assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(l.Raw))) assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(leafCert.Raw)))
assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(c.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.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.NoError(t, err)
assert.NotEmpty(t, lkB) assert.NotEmpty(t, leafKeyBytes)
keyPEM := bytes.NewBuffer([]byte{}) 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.NoError(t, err)
assert.Equal(t, l.Raw, fi.Leaf.Raw) assert.Equal(t, leafCert.Raw, fullIdent.Leaf.Raw)
assert.Equal(t, c.Raw, fi.CA.Raw) assert.Equal(t, caCert.Raw, fullIdent.CA.Raw)
assert.Equal(t, lk, fi.Key) assert.Equal(t, leafKey, fullIdent.Key)
} }
func TestIdentityConfig_SaveIdentity(t *testing.T) { func TestIdentityConfig_SaveIdentity(t *testing.T) {
@ -185,7 +179,7 @@ AwEHoUQDQgAEoLy/0hs5deTXZunRumsMkiHpF0g8wAc58aXANmr7Mxx9tzoIYFnx
ic, cleanup, err := tempIdentityConfig() ic, cleanup, err := tempIdentityConfig()
assert.NoError(t, err) assert.NoError(t, err)
fi, err := FullIdentityFromPEM([]byte(chain), []byte(key), nil) fi, err := FullIdentityFromPEM([]byte(chain), []byte(key))
assert.NoError(t, err) assert.NoError(t, err)
return cleanup, ic, fi, difficulty return cleanup, ic, fi, difficulty
@ -200,7 +194,6 @@ func TestIdentityConfig_LoadIdentity(t *testing.T) {
fi, err := ic.Load() fi, err := ic.Load()
assert.NoError(t, err) assert.NoError(t, err)
assert.Nil(t, fi.PeerCAWhitelist)
assert.NotEmpty(t, fi) assert.NotEmpty(t, fi)
assert.NotEmpty(t, fi.Key) assert.NotEmpty(t, fi.Key)
assert.NotEmpty(t, fi.Leaf) 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.Leaf, fi.Leaf)
assert.Equal(t, expectedFI.CA, fi.CA) assert.Equal(t, expectedFI.CA, fi.CA)
assert.Equal(t, expectedFI.ID.Bytes(), fi.ID.Bytes()) assert.Equal(t, expectedFI.ID.Bytes(), fi.ID.Bytes())
}
tmp := path.Join(os.TempDir(), "temp-ca-whitelist.pem") func TestNewI(t *testing.T) {
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())
}
}()
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) { func TestNodeID_Difficulty(t *testing.T) {

View File

@ -15,7 +15,6 @@ import (
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"storj.io/storj/pkg/peertls"
"storj.io/storj/storage" "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 // NewProvider creates a Provider out of an Identity, a net.Listener, a UnaryInterceptorProvider and
// a set of responsibilities. // 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) { responsibilities ...Responsibility) (*Provider, error) {
// NB: talk to anyone with an identity grpcOpts, err := opts.grpcOpts()
ident, err := identity.ServerOption(peertls.VerifyCAWhitelist(
identity.PeerCAWhitelist,
identity.VerifyAuthExtSig,
))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -63,10 +58,10 @@ func NewProvider(identity *FullIdentity, lis net.Listener, interceptor grpc.Unar
grpc: grpc.NewServer( grpc: grpc.NewServer(
grpc.StreamInterceptor(streamInterceptor), grpc.StreamInterceptor(streamInterceptor),
grpc.UnaryInterceptor(unaryInterceptor), grpc.UnaryInterceptor(unaryInterceptor),
ident, grpcOpts,
), ),
next: responsibilities, next: responsibilities,
identity: identity, identity: opts.Ident,
}, nil }, nil
} }

View File

@ -7,10 +7,7 @@ import (
"context" "context"
"crypto" "crypto"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rand"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem" "encoding/pem"
"os" "os"
"path/filepath" "path/filepath"
@ -103,7 +100,7 @@ func newCAWorker(ctx context.Context, difficulty uint16, parentCert *x509.Certif
return return
} }
c, err := newCACert(k, parentKey, ct, parentCert) c, err := peertls.NewCert(k, parentKey, ct, parentCert)
if err != nil { if err != nil {
eC <- err eC <- err
return return
@ -120,53 +117,6 @@ func newCAWorker(ctx context.Context, difficulty uint16, parentCert *x509.Certif
caC <- ca 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) { func openCert(path string, flag int) (*os.File, error) {
if err := os.MkdirAll(filepath.Dir(path), 0744); err != nil { if err := os.MkdirAll(filepath.Dir(path), 0744); err != nil {
return nil, errs.Wrap(err) return nil, errs.Wrap(err)