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
|
.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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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
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/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
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user