certs.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package crypto
  2. import (
  3. "crypto"
  4. "crypto/ecdsa"
  5. "crypto/elliptic"
  6. "crypto/rand"
  7. "crypto/x509"
  8. "crypto/x509/pkix"
  9. "encoding/pem"
  10. "errors"
  11. "math/big"
  12. "time"
  13. )
  14. // nodeCertValidity is how long the node-auth CA and the leaves it issues stay
  15. // valid. It is deliberately long: v1 has no rotation flow, so expiry would mean
  16. // a fleet-wide outage. Rotation is tracked as follow-up work.
  17. const nodeCertValidity = 10 * 365 * 24 * time.Hour
  18. // CertKeyPEM is a PEM-encoded certificate together with its private key.
  19. type CertKeyPEM struct {
  20. CertPEM []byte
  21. KeyPEM []byte
  22. }
  23. func randomSerial() (*big.Int, error) {
  24. return rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
  25. }
  26. func marshalCertKey(certDER []byte, key *ecdsa.PrivateKey) (CertKeyPEM, error) {
  27. keyDER, err := x509.MarshalECPrivateKey(key)
  28. if err != nil {
  29. return CertKeyPEM{}, err
  30. }
  31. return CertKeyPEM{
  32. CertPEM: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
  33. KeyPEM: pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}),
  34. }, nil
  35. }
  36. // GenerateNodeCA mints a self-signed ECDSA P-256 CA used to authenticate node
  37. // API traffic. It can sign leaf certificates only (path length 0).
  38. func GenerateNodeCA(commonName string) (CertKeyPEM, error) {
  39. key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  40. if err != nil {
  41. return CertKeyPEM{}, err
  42. }
  43. serial, err := randomSerial()
  44. if err != nil {
  45. return CertKeyPEM{}, err
  46. }
  47. now := time.Now()
  48. tmpl := &x509.Certificate{
  49. SerialNumber: serial,
  50. Subject: pkix.Name{CommonName: commonName},
  51. NotBefore: now.Add(-time.Hour),
  52. NotAfter: now.Add(nodeCertValidity),
  53. KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
  54. BasicConstraintsValid: true,
  55. IsCA: true,
  56. MaxPathLenZero: true,
  57. }
  58. der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
  59. if err != nil {
  60. return CertKeyPEM{}, err
  61. }
  62. return marshalCertKey(der, key)
  63. }
  64. // IssueClientCert signs a client-auth leaf (ExtKeyUsageClientAuth) with the
  65. // given CA. The leaf authenticates the managing panel to a node.
  66. func IssueClientCert(ca CertKeyPEM, commonName string) (CertKeyPEM, error) {
  67. caCert, caKey, err := LoadCAFromPEM(ca)
  68. if err != nil {
  69. return CertKeyPEM{}, err
  70. }
  71. key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  72. if err != nil {
  73. return CertKeyPEM{}, err
  74. }
  75. serial, err := randomSerial()
  76. if err != nil {
  77. return CertKeyPEM{}, err
  78. }
  79. now := time.Now()
  80. tmpl := &x509.Certificate{
  81. SerialNumber: serial,
  82. Subject: pkix.Name{CommonName: commonName},
  83. NotBefore: now.Add(-time.Hour),
  84. NotAfter: now.Add(nodeCertValidity),
  85. KeyUsage: x509.KeyUsageDigitalSignature,
  86. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
  87. }
  88. der, err := x509.CreateCertificate(rand.Reader, tmpl, caCert, key.Public(), caKey)
  89. if err != nil {
  90. return CertKeyPEM{}, err
  91. }
  92. return marshalCertKey(der, key)
  93. }
  94. // LoadCAFromPEM parses a CA cert+key pair into a certificate and its signer,
  95. // ready to issue or to populate a trust pool.
  96. func LoadCAFromPEM(ca CertKeyPEM) (*x509.Certificate, crypto.Signer, error) {
  97. certBlock, _ := pem.Decode(ca.CertPEM)
  98. if certBlock == nil || certBlock.Type != "CERTIFICATE" {
  99. return nil, nil, errors.New("invalid CA certificate PEM")
  100. }
  101. cert, err := x509.ParseCertificate(certBlock.Bytes)
  102. if err != nil {
  103. return nil, nil, err
  104. }
  105. keyBlock, _ := pem.Decode(ca.KeyPEM)
  106. if keyBlock == nil {
  107. return nil, nil, errors.New("invalid CA key PEM")
  108. }
  109. key, err := x509.ParseECPrivateKey(keyBlock.Bytes)
  110. if err != nil {
  111. return nil, nil, err
  112. }
  113. return cert, key, nil
  114. }