TLS extension processing (#771)
This commit is contained in:
parent
482ed7f4e2
commit
228aa34ff6
2
Makefile
2
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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
78
pkg/peertls/extensions.go
Normal file
78
pkg/peertls/extensions.go
Normal 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
|
||||
}
|
||||
}
|
@ -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 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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
@ -76,14 +70,40 @@ type IdentitySetupConfig struct {
|
||||
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"`
|
||||
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,
|
||||
}, 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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user