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  }