github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/pki/pki.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package pki provides a simple way to generate a CA and signed server keypair. 5 package pki 6 7 import ( 8 "crypto/rand" 9 "crypto/rsa" 10 "crypto/x509" 11 "crypto/x509/pkix" 12 "encoding/pem" 13 "math/big" 14 "net" 15 "time" 16 17 "github.com/Racer159/jackal/src/pkg/k8s" 18 "github.com/Racer159/jackal/src/pkg/message" 19 "github.com/defenseunicorns/pkg/helpers" 20 ) 21 22 // Based off of https://github.com/dmcgowan/quicktls/blob/master/main.go 23 24 // Use 2048 because we are aiming for low-resource / max-compatibility. 25 const rsaBits = 2048 26 const org = "Jackal Cluster" 27 28 // 13 months is the max length allowed by browsers. 29 const validFor = time.Hour * 24 * 375 30 31 // GeneratePKI create a CA and signed server keypair. 32 func GeneratePKI(host string, dnsNames ...string) k8s.GeneratedPKI { 33 results := k8s.GeneratedPKI{} 34 35 ca, caKey, err := generateCA(validFor) 36 if err != nil { 37 message.Fatal(err, "Unable to generate the ephemeral CA") 38 } 39 40 hostCert, hostKey, err := generateCert(host, ca, caKey, validFor, dnsNames...) 41 if err != nil { 42 message.Fatalf(err, "Unable to generate the cert for %s", host) 43 } 44 45 results.CA = pem.EncodeToMemory(&pem.Block{ 46 Type: "CERTIFICATE", 47 Bytes: ca.Raw, 48 }) 49 50 results.Cert = pem.EncodeToMemory(&pem.Block{ 51 Type: "CERTIFICATE", 52 Bytes: hostCert.Raw, 53 }) 54 55 results.Key = pem.EncodeToMemory(&pem.Block{ 56 Type: "RSA PRIVATE KEY", 57 Bytes: x509.MarshalPKCS1PrivateKey(hostKey), 58 }) 59 60 return results 61 } 62 63 // newCertificate creates a new template. 64 func newCertificate(validFor time.Duration) *x509.Certificate { 65 notBefore := time.Now() 66 notAfter := notBefore.Add(validFor) 67 68 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 69 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 70 if err != nil { 71 message.Fatalf(err, "failed to generate the certificate serial number") 72 } 73 74 return &x509.Certificate{ 75 SerialNumber: serialNumber, 76 Subject: pkix.Name{ 77 Organization: []string{org}, 78 }, 79 NotBefore: notBefore, 80 NotAfter: notAfter, 81 82 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 83 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 84 BasicConstraintsValid: true, 85 } 86 } 87 88 // newPrivateKey creates a new private key. 89 func newPrivateKey() (*rsa.PrivateKey, error) { 90 return rsa.GenerateKey(rand.Reader, rsaBits) 91 } 92 93 // generateCA creates a new CA certificate, saves the certificate 94 // and returns the x509 certificate and crypto private key. This 95 // private key should never be saved to disk, but rather used to 96 // immediately generate further certificates. 97 func generateCA(validFor time.Duration) (*x509.Certificate, *rsa.PrivateKey, error) { 98 template := newCertificate(validFor) 99 template.IsCA = true 100 template.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature 101 template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} 102 template.Subject.CommonName = "ca.private.jackal.dev" 103 template.Subject.Organization = []string{"Jackal Community"} 104 105 priv, err := newPrivateKey() 106 if err != nil { 107 return nil, nil, err 108 } 109 110 derBytes, err := x509.CreateCertificate(rand.Reader, template, template, priv.Public(), priv) 111 if err != nil { 112 return nil, nil, err 113 } 114 115 ca, err := x509.ParseCertificate(derBytes) 116 if err != nil { 117 return nil, nil, err 118 } 119 120 return ca, priv, nil 121 } 122 123 // generateCert generates a new certificate for the given host using the 124 // provided certificate authority. The cert and key files are stored in 125 // the provided files. 126 func generateCert(host string, ca *x509.Certificate, caKey *rsa.PrivateKey, validFor time.Duration, dnsNames ...string) (*x509.Certificate, *rsa.PrivateKey, error) { 127 template := newCertificate(validFor) 128 129 template.IPAddresses = append(template.IPAddresses, net.ParseIP(helpers.IPV4Localhost)) 130 131 // Only use SANs to keep golang happy, https://go-review.googlesource.com/c/go/+/231379 132 if ip := net.ParseIP(host); ip != nil { 133 template.IPAddresses = append(template.IPAddresses, ip) 134 } else { 135 template.DNSNames = append(template.DNSNames, host) 136 template.DNSNames = append(template.DNSNames, dnsNames...) 137 } 138 139 template.Subject.CommonName = host 140 141 privateKey, err := newPrivateKey() 142 if err != nil { 143 return nil, nil, err 144 } 145 146 derBytes, err := x509.CreateCertificate(rand.Reader, template, ca, privateKey.Public(), caKey) 147 if err != nil { 148 return nil, nil, err 149 } 150 151 cert, err := x509.ParseCertificate(derBytes) 152 if err != nil { 153 return nil, nil, err 154 } 155 156 return cert, privateKey, nil 157 }