storj/pkg/peertls/peertls.go
paul cannon c35b93766d
Unite all cryptographic signing and verifying (#1244)
this change removes the cryptopasta dependency.

a couple possible sources of problem with this change:

 * the encoding used for ECDSA signatures on SignedMessage has changed.
   the encoding employed by cryptopasta was workable, but not the same
   as the encoding used for such signatures in the rest of the world
   (most particularly, on ECDSA signatures in X.509 certificates). I
   think we'll be best served by using one ECDSA signature encoding from
   here on, but if we need to use the old encoding for backwards
   compatibility with existing nodes, that can be arranged.

 * since there's already a breaking change in SignedMessage, I changed
   it to send and receive public keys in raw PKIX format, instead of
   PEM. PEM just adds unhelpful overhead for this case.
2019-02-07 14:39:20 -06:00

159 lines
4.6 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package peertls
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"io"
"github.com/zeebo/errs"
"storj.io/storj/pkg/pkcrypto"
"storj.io/storj/pkg/utils"
)
var (
// 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 = errs.Class("tls generation error")
// ErrTLSTemplate is used when an error occurs during tls template generation.
ErrTLSTemplate = errs.Class("tls template error")
// ErrVerifyPeerCert is used when an error occurs during `VerifyPeerCertificate`.
ErrVerifyPeerCert = errs.Class("tls peer certificate verification error")
// ErrVerifyCertificateChain is used when a certificate chain can't be verified from leaf to root
// (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 a signature wasn't produced by any CA in the whitelist.
ErrVerifyCAWhitelist = errs.Class("not signed by any CA in the whitelist")
)
// PeerCertVerificationFunc is the signature for a `*tls.Config{}`'s
// `VerifyPeerCertificate` function.
type PeerCertVerificationFunc func([][]byte, [][]*x509.Certificate) error
// VerifyPeerFunc combines multiple `*tls.Config#VerifyPeerCertificate`
// functions and adds certificate parsing.
func VerifyPeerFunc(next ...PeerCertVerificationFunc) PeerCertVerificationFunc {
return func(chain [][]byte, _ [][]*x509.Certificate) error {
c, err := pkcrypto.CertsFromDER(chain)
if err != nil {
return ErrVerifyPeerCert.Wrap(err)
}
for _, n := range next {
if n != nil {
if err := n(chain, [][]*x509.Certificate{c}); err != nil {
return ErrVerifyPeerCert.Wrap(err)
}
}
}
return nil
}
}
// VerifyPeerCertChains verifies that the first certificate chain contains certificates
// 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 was signed 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 {
for _, ca := range cas {
err := verifyCertSignature(ca, parsedChains[0][CAIndex])
if err == nil {
return nil
}
}
return ErrVerifyCAWhitelist.New("CA cert")
}
}
// 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 {
leaf, err = pkcrypto.CertFromDER(chain[0])
if err != nil {
return nil, err
}
}
return &tls.Certificate{
Leaf: leaf,
Certificate: chain,
PrivateKey: key,
}, nil
}
// WriteChain writes the certificate chain (leaf-first) to the writer, PEM-encoded.
func WriteChain(w io.Writer, chain ...*x509.Certificate) error {
if len(chain) < 1 {
return errs.New("expected at least one certificate for writing")
}
var extErrs utils.ErrorGroup
for _, c := range chain {
if err := pkcrypto.WriteCertPEM(w, c); err != nil {
return errs.Wrap(err)
}
for _, e := range c.ExtraExtensions {
if err := pkcrypto.WritePKIXExtensionPEM(w, &e); err != nil {
extErrs.Add(errs.Wrap(err))
}
}
}
return extErrs.Finish()
}
// ChainBytes returns bytes of the certificate chain (leaf-first) to the writer, PEM-encoded.
func ChainBytes(chain ...*x509.Certificate) ([]byte, error) {
var data bytes.Buffer
err := WriteChain(&data, chain...)
return data.Bytes(), err
}
// 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) {
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,
pkcrypto.PublicKeyFromPrivate(key),
signingKey,
)
if err != nil {
return nil, errs.Wrap(err)
}
cert, err := pkcrypto.CertFromDER(cb)
if err != nil {
return nil, errs.Wrap(err)
}
return cert, nil
}