package server import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "errors" "fmt" "math/big" "time" ) // localCA is the insecure built-in issuer for development and self-hosting, // the equivalent of ISSUER=local in the reference server. The CA and every // certificate it issues use ECDSA P-256, like the ACME-issued ones. type localCA struct { key *ecdsa.PrivateKey cert *x509.Certificate certPEM []byte } func newLocalCA() (*localCA, error) { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, err } template := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "tunnel local CA"}, NotBefore: time.Now().Add(-time.Minute), NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour), IsCA: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, BasicConstraintsValid: true, } der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) if err != nil { return nil, err } cert, err := x509.ParseCertificate(der) if err != nil { return nil, err } return &localCA{ key: key, cert: cert, certPEM: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), }, nil } func loadLocalCA(keyPEM, certPEM []byte) (*localCA, error) { keyBlock, _ := pem.Decode(keyPEM) if keyBlock == nil { return nil, errors.New("no PEM block in CA key") } key, err := x509.ParsePKCS8PrivateKey(keyBlock.Bytes) if err != nil { return nil, err } ecKey, ok := key.(*ecdsa.PrivateKey) if !ok { return nil, errors.New("CA key is not ECDSA") } certBlock, _ := pem.Decode(certPEM) if certBlock == nil { return nil, errors.New("no PEM block in CA certificate") } cert, err := x509.ParseCertificate(certBlock.Bytes) if err != nil { return nil, err } return &localCA{key: ecKey, cert: cert, certPEM: pem.EncodeToMemory(certBlock)}, nil } func (ca *localCA) keyPEM() ([]byte, error) { der, err := x509.MarshalPKCS8PrivateKey(ca.key) if err != nil { return nil, err } return pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: der}), nil } // issue signs a certificate for names with a fresh P-256 key. Used for the // server's own API certificate. func (ca *localCA) issue(names ...string) (certPEM, keyPEM []byte, err error) { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, err } der, err := ca.sign(&x509.Certificate{ SerialNumber: newSerial(), Subject: pkix.Name{CommonName: names[0]}, DNSNames: names, NotBefore: time.Now().Add(-time.Minute), NotAfter: time.Now().Add(90 * 24 * time.Hour), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, }, &key.PublicKey) if err != nil { return nil, nil, err } keyDER, err := x509.MarshalPKCS8PrivateKey(key) if err != nil { return nil, nil, err } return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), nil } // errCSRHostname reports a CSR whose names are not the tunnel's own. type errCSRHostname struct { provided string expected string } func (e errCSRHostname) Error() string { return "CSR hostname " + e.provided + " does not match tunnel hostname " + e.expected } // parseCSR decodes and validates a tunnel CSR: the common name must be the // tunnel hostname and every DNS name must be the hostname or its wildcard. func parseCSR(csrPEM, hostname string) (*x509.CertificateRequest, error) { block, _ := pem.Decode([]byte(csrPEM)) if block == nil || block.Type != "CERTIFICATE REQUEST" { return nil, errors.New("not a PEM certificate request") } csr, err := x509.ParseCertificateRequest(block.Bytes) if err != nil { return nil, err } if err := csr.CheckSignature(); err != nil { return nil, fmt.Errorf("CSR signature: %w", err) } if csr.Subject.CommonName != hostname { return nil, errCSRHostname{provided: csr.Subject.CommonName, expected: hostname} } for _, name := range csr.DNSNames { if name != hostname && name != "*."+hostname { return nil, errCSRHostname{provided: name, expected: hostname} } } return csr, nil } // signCSR validates a tunnel CSR and issues the certificate. The rules mirror // the reference's job runner: the common name must be the tunnel hostname and // every DNS name must be the hostname or its wildcard. func (ca *localCA) signCSR(csrPEM, hostname string) (certPEM, chainPEM []byte, expiry time.Time, err error) { csr, err := parseCSR(csrPEM, hostname) if err != nil { return nil, nil, time.Time{}, err } expiry = time.Now().Add(90 * 24 * time.Hour) der, err := ca.sign(&x509.Certificate{ SerialNumber: newSerial(), Subject: pkix.Name{CommonName: hostname}, DNSNames: csr.DNSNames, NotBefore: time.Now().Add(-time.Minute), NotAfter: expiry, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, }, csr.PublicKey) if err != nil { return nil, nil, time.Time{}, err } return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), ca.certPEM, expiry, nil } func (ca *localCA) sign(template *x509.Certificate, pub any) ([]byte, error) { return x509.CreateCertificate(rand.Reader, template, ca.cert, pub, ca.key) } var serialLimit = new(big.Int).Lsh(big.NewInt(1), 128) func newSerial() *big.Int { n, err := rand.Int(rand.Reader, serialLimit) if err != nil { panic(err) } return n }