| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- package crypto
- import (
- "crypto"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rand"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/pem"
- "errors"
- "math/big"
- "time"
- )
- // nodeCertValidity is how long the node-auth CA and the leaves it issues stay
- // valid. It is deliberately long: v1 has no rotation flow, so expiry would mean
- // a fleet-wide outage. Rotation is tracked as follow-up work.
- const nodeCertValidity = 10 * 365 * 24 * time.Hour
- // CertKeyPEM is a PEM-encoded certificate together with its private key.
- type CertKeyPEM struct {
- CertPEM []byte
- KeyPEM []byte
- }
- func randomSerial() (*big.Int, error) {
- return rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
- }
- func marshalCertKey(certDER []byte, key *ecdsa.PrivateKey) (CertKeyPEM, error) {
- keyDER, err := x509.MarshalECPrivateKey(key)
- if err != nil {
- return CertKeyPEM{}, err
- }
- return CertKeyPEM{
- CertPEM: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
- KeyPEM: pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}),
- }, nil
- }
- // GenerateNodeCA mints a self-signed ECDSA P-256 CA used to authenticate node
- // API traffic. It can sign leaf certificates only (path length 0).
- func GenerateNodeCA(commonName string) (CertKeyPEM, error) {
- key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- return CertKeyPEM{}, err
- }
- serial, err := randomSerial()
- if err != nil {
- return CertKeyPEM{}, err
- }
- now := time.Now()
- tmpl := &x509.Certificate{
- SerialNumber: serial,
- Subject: pkix.Name{CommonName: commonName},
- NotBefore: now.Add(-time.Hour),
- NotAfter: now.Add(nodeCertValidity),
- KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
- BasicConstraintsValid: true,
- IsCA: true,
- MaxPathLenZero: true,
- }
- der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
- if err != nil {
- return CertKeyPEM{}, err
- }
- return marshalCertKey(der, key)
- }
- // IssueClientCert signs a client-auth leaf (ExtKeyUsageClientAuth) with the
- // given CA. The leaf authenticates the managing panel to a node.
- func IssueClientCert(ca CertKeyPEM, commonName string) (CertKeyPEM, error) {
- caCert, caKey, err := LoadCAFromPEM(ca)
- if err != nil {
- return CertKeyPEM{}, err
- }
- key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- return CertKeyPEM{}, err
- }
- serial, err := randomSerial()
- if err != nil {
- return CertKeyPEM{}, err
- }
- now := time.Now()
- tmpl := &x509.Certificate{
- SerialNumber: serial,
- Subject: pkix.Name{CommonName: commonName},
- NotBefore: now.Add(-time.Hour),
- NotAfter: now.Add(nodeCertValidity),
- KeyUsage: x509.KeyUsageDigitalSignature,
- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
- }
- der, err := x509.CreateCertificate(rand.Reader, tmpl, caCert, key.Public(), caKey)
- if err != nil {
- return CertKeyPEM{}, err
- }
- return marshalCertKey(der, key)
- }
- // LoadCAFromPEM parses a CA cert+key pair into a certificate and its signer,
- // ready to issue or to populate a trust pool.
- func LoadCAFromPEM(ca CertKeyPEM) (*x509.Certificate, crypto.Signer, error) {
- certBlock, _ := pem.Decode(ca.CertPEM)
- if certBlock == nil || certBlock.Type != "CERTIFICATE" {
- return nil, nil, errors.New("invalid CA certificate PEM")
- }
- cert, err := x509.ParseCertificate(certBlock.Bytes)
- if err != nil {
- return nil, nil, err
- }
- keyBlock, _ := pem.Decode(ca.KeyPEM)
- if keyBlock == nil {
- return nil, nil, errors.New("invalid CA key PEM")
- }
- key, err := x509.ParseECPrivateKey(keyBlock.Bytes)
- if err != nil {
- return nil, nil, err
- }
- return cert, key, nil
- }
|