github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/tlsutil/generate.go (about) 1 package tlsutil 2 3 import ( 4 "bytes" 5 "crypto" 6 "crypto/ecdsa" 7 "crypto/elliptic" 8 "crypto/rand" 9 "crypto/rsa" 10 "crypto/sha256" 11 "crypto/x509" 12 "crypto/x509/pkix" 13 "encoding/pem" 14 "fmt" 15 "math/big" 16 "net" 17 "time" 18 ) 19 20 // GenerateSerialNumber returns random bigint generated with crypto/rand 21 func 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 31 func GeneratePrivateKey() (crypto.Signer, string, error) { 32 curve := elliptic.P256() 33 34 pk, err := ecdsa.GenerateKey(curve, rand.Reader) 35 if err != nil { 36 return nil, "", fmt.Errorf("error generating ECDSA private key: %s", err) 37 } 38 39 bs, err := x509.MarshalECPrivateKey(pk) 40 if err != nil { 41 return nil, "", fmt.Errorf("error marshaling ECDSA private key: %s", err) 42 } 43 44 pemBlock, err := pemEncodeKey(bs, "EC PRIVATE KEY") 45 if err != nil { 46 return nil, "", err 47 } 48 49 return pk, pemBlock, nil 50 } 51 52 func pemEncodeKey(key []byte, blockType string) (string, error) { 53 var buf bytes.Buffer 54 55 if err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: key}); err != nil { 56 return "", fmt.Errorf("error encoding private key: %s", err) 57 } 58 return buf.String(), nil 59 } 60 61 type CAOpts struct { 62 Signer crypto.Signer 63 Serial *big.Int 64 Days int 65 PermittedDNSDomains []string 66 Domain string 67 Name string 68 } 69 70 type CertOpts struct { 71 Signer crypto.Signer 72 CA string 73 Serial *big.Int 74 Name string 75 Days int 76 DNSNames []string 77 IPAddresses []net.IP 78 ExtKeyUsage []x509.ExtKeyUsage 79 } 80 81 // GenerateCA generates a new CA for agent TLS (not to be confused with Connect TLS) 82 func GenerateCA(opts CAOpts) (string, string, error) { 83 signer := opts.Signer 84 var pk string 85 if signer == nil { 86 var err error 87 signer, pk, err = GeneratePrivateKey() 88 if err != nil { 89 return "", "", err 90 } 91 } 92 93 id, err := keyID(signer.Public()) 94 if err != nil { 95 return "", "", err 96 } 97 98 sn := opts.Serial 99 if sn == nil { 100 var err error 101 sn, err = GenerateSerialNumber() 102 if err != nil { 103 return "", "", err 104 } 105 } 106 name := opts.Name 107 if name == "" { 108 name = fmt.Sprintf("Nomad Agent CA %d", sn) 109 } 110 111 days := opts.Days 112 if opts.Days == 0 { 113 days = 365 114 } 115 116 // Create the CA cert 117 template := x509.Certificate{ 118 SerialNumber: sn, 119 Subject: pkix.Name{ 120 Country: []string{"US"}, 121 PostalCode: []string{"94105"}, 122 Province: []string{"CA"}, 123 Locality: []string{"San Francisco"}, 124 StreetAddress: []string{"101 Second Street"}, 125 Organization: []string{"HashiCorp Inc."}, 126 CommonName: name, 127 }, 128 BasicConstraintsValid: true, 129 KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, 130 IsCA: true, 131 NotAfter: time.Now().AddDate(0, 0, days), 132 NotBefore: time.Now(), 133 AuthorityKeyId: id, 134 SubjectKeyId: id, 135 } 136 137 if len(opts.PermittedDNSDomains) > 0 { 138 template.PermittedDNSDomainsCritical = true 139 template.PermittedDNSDomains = opts.PermittedDNSDomains 140 } 141 bs, err := x509.CreateCertificate( 142 rand.Reader, &template, &template, signer.Public(), signer) 143 if err != nil { 144 return "", "", fmt.Errorf("error generating CA certificate: %s", err) 145 } 146 147 var buf bytes.Buffer 148 err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs}) 149 if err != nil { 150 return "", "", fmt.Errorf("error encoding private key: %s", err) 151 } 152 153 return buf.String(), pk, nil 154 } 155 156 // GenerateCert generates a new certificate for agent TLS (not to be confused with Connect TLS) 157 func GenerateCert(opts CertOpts) (string, string, error) { 158 parent, err := parseCert(opts.CA) 159 if err != nil { 160 return "", "", err 161 } 162 163 signee, pk, err := GeneratePrivateKey() 164 if err != nil { 165 return "", "", err 166 } 167 168 id, err := keyID(signee.Public()) 169 if err != nil { 170 return "", "", err 171 } 172 173 sn := opts.Serial 174 if sn == nil { 175 var err error 176 sn, err = GenerateSerialNumber() 177 if err != nil { 178 return "", "", err 179 } 180 } 181 182 template := x509.Certificate{ 183 SerialNumber: sn, 184 Subject: pkix.Name{CommonName: opts.Name}, 185 BasicConstraintsValid: true, 186 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, 187 ExtKeyUsage: opts.ExtKeyUsage, 188 IsCA: false, 189 NotAfter: time.Now().AddDate(0, 0, opts.Days), 190 NotBefore: time.Now(), 191 SubjectKeyId: id, 192 DNSNames: opts.DNSNames, 193 IPAddresses: opts.IPAddresses, 194 } 195 196 bs, err := x509.CreateCertificate(rand.Reader, &template, parent, signee.Public(), opts.Signer) 197 if err != nil { 198 return "", "", err 199 } 200 201 var buf bytes.Buffer 202 err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs}) 203 if err != nil { 204 return "", "", fmt.Errorf("error encoding private key: %s", err) 205 } 206 207 return buf.String(), pk, nil 208 } 209 210 // KeyId returns a x509 KeyId from the given signing key. 211 func keyID(raw interface{}) ([]byte, error) { 212 switch raw.(type) { 213 case *ecdsa.PublicKey: 214 case *rsa.PublicKey: 215 default: 216 return nil, fmt.Errorf("invalid key type: %T", raw) 217 } 218 219 // This is not standard; RFC allows any unique identifier as long as they 220 // match in subject/authority chains but suggests specific hashing of DER 221 // bytes of public key including DER tags. 222 bs, err := x509.MarshalPKIXPublicKey(raw) 223 if err != nil { 224 return nil, err 225 } 226 227 // String formatted 228 kID := sha256.Sum256(bs) 229 return kID[:], nil 230 } 231 232 // ParseCert parses the x509 certificate from a PEM-encoded value. 233 func ParseCert(pemValue string) (*x509.Certificate, error) { 234 // The _ result below is not an error but the remaining PEM bytes. 235 block, _ := pem.Decode([]byte(pemValue)) 236 if block == nil { 237 return nil, fmt.Errorf("no PEM-encoded data found") 238 } 239 240 if block.Type != "CERTIFICATE" { 241 return nil, fmt.Errorf("first PEM-block should be CERTIFICATE type") 242 } 243 244 return x509.ParseCertificate(block.Bytes) 245 } 246 247 func parseCert(pemValue string) (*x509.Certificate, error) { 248 // The _ result below is not an error but the remaining PEM bytes. 249 block, _ := pem.Decode([]byte(pemValue)) 250 if block == nil { 251 return nil, fmt.Errorf("no PEM-encoded data found") 252 } 253 254 if block.Type != "CERTIFICATE" { 255 return nil, fmt.Errorf("first PEM-block should be CERTIFICATE type") 256 } 257 258 return x509.ParseCertificate(block.Bytes) 259 } 260 261 // ParseSigner parses a crypto.Signer from a PEM-encoded key. The private key 262 // is expected to be the first block in the PEM value. 263 func ParseSigner(pemValue string) (crypto.Signer, error) { 264 // The _ result below is not an error but the remaining PEM bytes. 265 block, _ := pem.Decode([]byte(pemValue)) 266 if block == nil { 267 return nil, fmt.Errorf("no PEM-encoded data found") 268 } 269 270 switch block.Type { 271 case "EC PRIVATE KEY": 272 return x509.ParseECPrivateKey(block.Bytes) 273 274 case "RSA PRIVATE KEY": 275 return x509.ParsePKCS1PrivateKey(block.Bytes) 276 277 case "PRIVATE KEY": 278 signer, err := x509.ParsePKCS8PrivateKey(block.Bytes) 279 if err != nil { 280 return nil, err 281 } 282 pk, ok := signer.(crypto.Signer) 283 if !ok { 284 return nil, fmt.Errorf("private key is not a valid format") 285 } 286 287 return pk, nil 288 289 default: 290 return nil, fmt.Errorf("unknown PEM block type for signing key: %s", block.Type) 291 } 292 } 293 294 func Verify(caString, certString, dns string) error { 295 roots := x509.NewCertPool() 296 ok := roots.AppendCertsFromPEM([]byte(caString)) 297 if !ok { 298 return fmt.Errorf("failed to parse root certificate") 299 } 300 301 cert, err := parseCert(certString) 302 if err != nil { 303 return fmt.Errorf("failed to parse certificate") 304 } 305 306 opts := x509.VerifyOptions{ 307 DNSName: fmt.Sprint(dns), 308 Roots: roots, 309 } 310 311 _, err = cert.Verify(opts) 312 return err 313 }