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