1// Copyright (C) 2019 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package peertls
5
6import (
7	"bytes"
8	"crypto"
9	"crypto/rand"
10	"crypto/tls"
11	"crypto/x509"
12	"io"
13
14	"github.com/zeebo/errs"
15
16	"storj.io/common/pkcrypto"
17)
18
19const (
20	// LeafIndex is the index of the leaf certificate in a cert chain (0).
21	LeafIndex = iota
22	// CAIndex is the index of the CA certificate in a cert chain (1).
23	CAIndex
24)
25
26var (
27	// ErrNotExist is used when a file or directory doesn't exist.
28	ErrNotExist = errs.Class("file or directory not found")
29	// ErrGenerate is used when an error occurred during cert/key generation.
30	ErrGenerate = errs.Class("tls generation")
31	// ErrTLSTemplate is used when an error occurs during tls template generation.
32	ErrTLSTemplate = errs.Class("tls template")
33	// ErrVerifyPeerCert is used when an error occurs during `VerifyPeerCertificate`.
34	ErrVerifyPeerCert = errs.Class("tls peer certificate verification")
35	// ErrVerifyCertificateChain is used when a certificate chain can't be verified from leaf to root
36	// (i.e.: each cert in the chain should be signed by the preceding cert and the root should be self-signed).
37	ErrVerifyCertificateChain = errs.Class("certificate chain signature verification failed")
38	// ErrVerifyCAWhitelist is used when a signature wasn't produced by any CA in the whitelist.
39	ErrVerifyCAWhitelist = errs.Class("not signed by any CA in the whitelist")
40)
41
42// PeerCertVerificationFunc is the signature for a `*tls.Config{}`'s
43// `VerifyPeerCertificate` function.
44type PeerCertVerificationFunc func([][]byte, [][]*x509.Certificate) error
45
46// VerifyPeerFunc combines multiple `*tls.Config#VerifyPeerCertificate`
47// functions and adds certificate parsing.
48func VerifyPeerFunc(next ...PeerCertVerificationFunc) PeerCertVerificationFunc {
49	return func(chain [][]byte, _ [][]*x509.Certificate) error {
50		c, err := pkcrypto.CertsFromDER(chain)
51		if err != nil {
52			return NewNonTemporaryError(ErrVerifyPeerCert.Wrap(err))
53		}
54
55		for _, n := range next {
56			if n != nil {
57				if err := n(chain, [][]*x509.Certificate{c}); err != nil {
58					return NewNonTemporaryError(ErrVerifyPeerCert.Wrap(err))
59				}
60			}
61		}
62		return nil
63	}
64}
65
66// VerifyPeerCertChains verifies that the first certificate chain contains certificates
67// which are signed by their respective parents, ending with a self-signed root.
68func VerifyPeerCertChains(_ [][]byte, parsedChains [][]*x509.Certificate) error {
69	return verifyChainSignatures(parsedChains[0])
70}
71
72// VerifyCAWhitelist verifies that the peer identity's CA was signed by any one
73// of the (certificate authority) certificates in the provided whitelist.
74func VerifyCAWhitelist(cas []*x509.Certificate) PeerCertVerificationFunc {
75	if cas == nil {
76		return nil
77	}
78	return func(_ [][]byte, parsedChains [][]*x509.Certificate) error {
79		for _, ca := range cas {
80			err := verifyCertSignature(ca, parsedChains[0][CAIndex])
81			if err == nil {
82				return nil
83			}
84		}
85		return ErrVerifyCAWhitelist.New("CA cert")
86	}
87}
88
89// TLSCert creates a tls.Certificate from chains, key and leaf.
90func TLSCert(chain [][]byte, leaf *x509.Certificate, key crypto.PrivateKey) (*tls.Certificate, error) {
91	var err error
92	if leaf == nil {
93		leaf, err = pkcrypto.CertFromDER(chain[LeafIndex])
94		if err != nil {
95			return nil, err
96		}
97	}
98
99	return &tls.Certificate{
100		Leaf:        leaf,
101		Certificate: chain,
102		PrivateKey:  key,
103	}, nil
104}
105
106// WriteChain writes the certificate chain (leaf-first) and extensions to the writer, PEM-encoded.
107func WriteChain(w io.Writer, chain ...*x509.Certificate) error {
108	if len(chain) < 1 {
109		return errs.New("expected at least one certificate for writing")
110	}
111
112	var extErrs errs.Group
113	for _, c := range chain {
114		if err := pkcrypto.WriteCertPEM(w, c); err != nil {
115			return errs.Wrap(err)
116		}
117	}
118	return extErrs.Err()
119}
120
121// ChainBytes returns bytes of the certificate chain (leaf-first) to the writer, PEM-encoded.
122func ChainBytes(chain ...*x509.Certificate) ([]byte, error) {
123	var data bytes.Buffer
124	err := WriteChain(&data, chain...)
125	return data.Bytes(), err
126}
127
128// CreateSelfSignedCertificate creates a new self-signed X.509v3 certificate
129// using fields from the given template.
130//
131// A part of the errors that CreateCertificate can return it can return
132// pkcrypto.ErrUnsuportedKey error.
133func CreateSelfSignedCertificate(key crypto.PrivateKey, template *x509.Certificate) (*x509.Certificate, error) {
134	pubKey, err := pkcrypto.PublicKeyFromPrivate(key)
135	if err != nil {
136		return nil, err
137	}
138	return CreateCertificate(pubKey, key, template, template)
139}
140
141// CreateCertificate creates a new X.509v3 certificate based on a template.
142// The new certificate:
143//
144//  * will have the public key given as 'signee'
145//  * will be signed by 'signer' (which should be the private key of 'issuer')
146//  * will be issued by 'issuer'
147//  * will have metadata fields copied from 'template'
148//
149// Returns the new Certificate object.
150func CreateCertificate(signee crypto.PublicKey, signer crypto.PrivateKey, template, issuer *x509.Certificate) (*x509.Certificate, error) {
151	if _, ok := signer.(crypto.Signer); !ok {
152		// x509.CreateCertificate will panic in this case, so check here and make debugging easier
153		return nil, errs.New("can't sign certificate with signer key of type %T", signer)
154	}
155
156	// TODO: should we check for uniqueness?
157	template.ExtraExtensions = append(template.ExtraExtensions, template.Extensions...)
158	cb, err := x509.CreateCertificate(
159		rand.Reader,
160		template,
161		issuer,
162		signee,
163		signer,
164	)
165	if err != nil {
166		return nil, errs.Wrap(err)
167	}
168	return pkcrypto.CertFromDER(cb)
169}
170