Files
Sanaei f727d04f65
Release 3X-UI for Docker / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
v3.7.0
2026-08-24 15:07:15 +02:00

123 lines
3.6 KiB
Go

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
}