SignCSR signs the given PEM-encoded CSR producing a node client certificate whose CommonName is forced to nodeID. The CSR's self-reported subject is not trusted; only its public key and signature are used.
(csrPEM []byte, nodeID string, ttl time.Duration)
| 184 | // whose CommonName is forced to nodeID. The CSR's self-reported subject is not |
| 185 | // trusted; only its public key and signature are used. |
| 186 | func (ca *NodeCA) SignCSR(csrPEM []byte, nodeID string, ttl time.Duration) ([]byte, error) { |
| 187 | if nodeID == "" { |
| 188 | return nil, fmt.Errorf("node id is required") |
| 189 | } |
| 190 | if ttl <= 0 { |
| 191 | return nil, fmt.Errorf("ttl must be positive") |
| 192 | } |
| 193 | |
| 194 | block, _ := pem.Decode(csrPEM) |
| 195 | if block == nil || block.Type != "CERTIFICATE REQUEST" { |
| 196 | return nil, fmt.Errorf("decode CSR PEM: invalid block") |
| 197 | } |
| 198 | csr, err := x509.ParseCertificateRequest(block.Bytes) |
| 199 | if err != nil { |
| 200 | return nil, fmt.Errorf("parse CSR: %w", err) |
| 201 | } |
| 202 | if err := csr.CheckSignature(); err != nil { |
| 203 | return nil, fmt.Errorf("verify CSR signature: %w", err) |
| 204 | } |
| 205 | |
| 206 | serial, err := randomSerial() |
| 207 | if err != nil { |
| 208 | return nil, err |
| 209 | } |
| 210 | |
| 211 | uri := &url.URL{Scheme: "pulse-node", Host: nodeID} |
| 212 | |
| 213 | now := time.Now() |
| 214 | template := &x509.Certificate{ |
| 215 | SerialNumber: serial, |
| 216 | Subject: pkix.Name{ |
| 217 | CommonName: nodeID, |
| 218 | Organization: []string{"pulse-node"}, |
| 219 | }, |
| 220 | NotBefore: now.Add(-time.Minute), |
| 221 | NotAfter: now.Add(ttl), |
| 222 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, |
| 223 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, |
| 224 | BasicConstraintsValid: true, |
| 225 | IsCA: false, |
| 226 | URIs: []*url.URL{uri}, |
| 227 | } |
| 228 | |
| 229 | der, err := x509.CreateCertificate(rand.Reader, template, ca.cert, csr.PublicKey, ca.key) |
| 230 | if err != nil { |
| 231 | return nil, fmt.Errorf("sign certificate: %w", err) |
| 232 | } |
| 233 | |
| 234 | return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), nil |
| 235 | } |
| 236 | |
| 237 | // CertPEM returns the CA's own certificate in PEM form. Safe to share with |
| 238 | // nodes (used as RootCA for verifying the server when establishing mTLS). |