1package tlsutil
2
3import (
4	"bytes"
5	"crypto"
6	"crypto/ecdsa"
7	"crypto/rand"
8	"crypto/sha256"
9	"crypto/x509"
10	"crypto/x509/pkix"
11	"encoding/pem"
12	"fmt"
13	"math/big"
14	"net"
15	"time"
16
17	"github.com/hashicorp/consul/agent/connect"
18)
19
20// GenerateSerialNumber returns random bigint generated with crypto/rand
21func GenerateSerialNumber() (*big.Int, error) {
22	l := new(big.Int).Lsh(big.NewInt(1), 128)
23	s, err := rand.Int(rand.Reader, l)
24	if err != nil {
25		return nil, err
26	}
27	return s, nil
28}
29
30// GeneratePrivateKey generates a new ecdsa private key
31func GeneratePrivateKey() (crypto.Signer, string, error) {
32	return connect.GeneratePrivateKey()
33}
34
35// GenerateCA generates a new CA for agent TLS (not to be confused with Connect TLS)
36func GenerateCA(signer crypto.Signer, sn *big.Int, days int, constraints []string) (string, error) {
37	id, err := keyID(signer.Public())
38	if err != nil {
39		return "", err
40	}
41
42	name := fmt.Sprintf("Consul Agent CA %d", sn)
43
44	// Create the CA cert
45	template := x509.Certificate{
46		SerialNumber: sn,
47		Subject: pkix.Name{
48			Country:       []string{"US"},
49			PostalCode:    []string{"94105"},
50			Province:      []string{"CA"},
51			Locality:      []string{"San Francisco"},
52			StreetAddress: []string{"101 Second Street"},
53			Organization:  []string{"HashiCorp Inc."},
54			CommonName:    name,
55		},
56		BasicConstraintsValid: true,
57		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
58		IsCA:                  true,
59		NotAfter:              time.Now().AddDate(0, 0, days),
60		NotBefore:             time.Now(),
61		AuthorityKeyId:        id,
62		SubjectKeyId:          id,
63	}
64
65	if len(constraints) > 0 {
66		template.PermittedDNSDomainsCritical = true
67		template.PermittedDNSDomains = constraints
68	}
69	bs, err := x509.CreateCertificate(
70		rand.Reader, &template, &template, signer.Public(), signer)
71	if err != nil {
72		return "", fmt.Errorf("error generating CA certificate: %s", err)
73	}
74
75	var buf bytes.Buffer
76	err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
77	if err != nil {
78		return "", fmt.Errorf("error encoding private key: %s", err)
79	}
80
81	return buf.String(), nil
82}
83
84// GenerateCert generates a new certificate for agent TLS (not to be confused with Connect TLS)
85func GenerateCert(signer crypto.Signer, ca string, sn *big.Int, name string, days int, DNSNames []string, IPAddresses []net.IP, extKeyUsage []x509.ExtKeyUsage) (string, string, error) {
86	parent, err := parseCert(ca)
87	if err != nil {
88		return "", "", err
89	}
90
91	signee, pk, err := GeneratePrivateKey()
92	if err != nil {
93		return "", "", err
94	}
95
96	id, err := keyID(signee.Public())
97	if err != nil {
98		return "", "", err
99	}
100
101	template := x509.Certificate{
102		SerialNumber:          sn,
103		Subject:               pkix.Name{CommonName: name},
104		BasicConstraintsValid: true,
105		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
106		ExtKeyUsage:           extKeyUsage,
107		IsCA:                  false,
108		NotAfter:              time.Now().AddDate(0, 0, days),
109		NotBefore:             time.Now(),
110		SubjectKeyId:          id,
111		DNSNames:              DNSNames,
112		IPAddresses:           IPAddresses,
113	}
114
115	bs, err := x509.CreateCertificate(rand.Reader, &template, parent, signee.Public(), signer)
116	if err != nil {
117		return "", "", err
118	}
119
120	var buf bytes.Buffer
121	err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
122	if err != nil {
123		return "", "", fmt.Errorf("error encoding private key: %s", err)
124	}
125
126	return buf.String(), pk, nil
127}
128
129// KeyId returns a x509 KeyId from the given signing key.
130func keyID(raw interface{}) ([]byte, error) {
131	switch raw.(type) {
132	case *ecdsa.PublicKey:
133	default:
134		return nil, fmt.Errorf("invalid key type: %T", raw)
135	}
136
137	// This is not standard; RFC allows any unique identifier as long as they
138	// match in subject/authority chains but suggests specific hashing of DER
139	// bytes of public key including DER tags.
140	bs, err := x509.MarshalPKIXPublicKey(raw)
141	if err != nil {
142		return nil, err
143	}
144
145	// String formatted
146	kID := sha256.Sum256(bs)
147	return kID[:], nil
148}
149
150func parseCert(pemValue string) (*x509.Certificate, error) {
151	// The _ result below is not an error but the remaining PEM bytes.
152	block, _ := pem.Decode([]byte(pemValue))
153	if block == nil {
154		return nil, fmt.Errorf("no PEM-encoded data found")
155	}
156
157	if block.Type != "CERTIFICATE" {
158		return nil, fmt.Errorf("first PEM-block should be CERTIFICATE type")
159	}
160
161	return x509.ParseCertificate(block.Bytes)
162}
163
164// ParseSigner parses a crypto.Signer from a PEM-encoded key. The private key
165// is expected to be the first block in the PEM value.
166func ParseSigner(pemValue string) (crypto.Signer, error) {
167	// The _ result below is not an error but the remaining PEM bytes.
168	block, _ := pem.Decode([]byte(pemValue))
169	if block == nil {
170		return nil, fmt.Errorf("no PEM-encoded data found")
171	}
172
173	switch block.Type {
174	case "EC PRIVATE KEY":
175		return x509.ParseECPrivateKey(block.Bytes)
176	default:
177		return nil, fmt.Errorf("unknown PEM block type for signing key: %s", block.Type)
178	}
179}
180
181func Verify(caString, certString, dns string) error {
182	roots := x509.NewCertPool()
183	ok := roots.AppendCertsFromPEM([]byte(caString))
184	if !ok {
185		return fmt.Errorf("failed to parse root certificate")
186	}
187
188	cert, err := parseCert(certString)
189	if err != nil {
190		return fmt.Errorf("failed to parse certificate")
191	}
192
193	opts := x509.VerifyOptions{
194		DNSName: fmt.Sprint(dns),
195		Roots:   roots,
196	}
197
198	_, err = cert.Verify(opts)
199	return err
200}
201