traefik/pkg/api/certificate.go
2026-04-07 10:10:05 +02:00

173 lines
5.3 KiB
Go

package api
import (
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"fmt"
"sort"
"time"
)
const (
certStatusEnabled = "enabled"
certStatusWarning = "warning"
certStatusExpired = "expired"
)
// certificateRepresentation represents a certificate in the API.
type certificateRepresentation struct {
Name string `json:"name"` // SHA-256 fingerprint of the DER-encoded certificate.
SANs []string `json:"sans"`
NotAfter time.Time `json:"notAfter"`
NotBefore time.Time `json:"notBefore"`
SerialNumber string `json:"serialNumber"`
CommonName string `json:"commonName"`
IssuerOrg string `json:"issuerOrg,omitempty"`
IssuerCN string `json:"issuerCN,omitempty"`
IssuerCountry string `json:"issuerCountry,omitempty"`
Organization string `json:"organization,omitempty"`
Country string `json:"country,omitempty"`
Version string `json:"version"`
KeyType string `json:"keyType"`
KeySize int `json:"keySize,omitempty"`
SignatureAlgorithm string `json:"signatureAlgorithm"`
CertFingerprint string `json:"certFingerprint"`
PublicKeyFingerprint string `json:"publicKeyFingerprint"`
Status string `json:"status"`
}
// Interface methods for sort.go compatibility.
func (c certificateRepresentation) name() string {
return c.CommonName
}
func (c certificateRepresentation) status() string {
return c.Status
}
func (c certificateRepresentation) issuer() string {
if c.IssuerOrg != "" {
return c.IssuerOrg
}
return c.IssuerCN
}
func (c certificateRepresentation) validUntil() time.Time {
return c.NotAfter
}
// buildCertificateRepresentation builds a certificateRepresentation from an x509 certificate.
func buildCertificateRepresentation(cert *x509.Certificate) certificateRepresentation {
keyType, keySize := extractKeyInfo(cert)
certFingerprint, pubKeyFingerprint := extractFingerprints(cert)
issuerOrg, issuerCN, issuerCountry := extractIssuerInfo(cert)
organization, country := extractSubjectInfo(cert)
return certificateRepresentation{
Name: certFingerprint,
SANs: extractSANs(cert),
NotAfter: cert.NotAfter,
NotBefore: cert.NotBefore,
SerialNumber: cert.SerialNumber.String(),
CommonName: cert.Subject.CommonName,
IssuerOrg: issuerOrg,
IssuerCN: issuerCN,
IssuerCountry: issuerCountry,
Organization: organization,
Country: country,
Version: formatVersion(cert.Version),
KeyType: keyType,
KeySize: keySize,
SignatureAlgorithm: cert.SignatureAlgorithm.String(),
CertFingerprint: certFingerprint,
PublicKeyFingerprint: pubKeyFingerprint,
Status: getCertificateStatus(cert.NotAfter),
}
}
// extractSANs extracts Subject Alternative Names from a certificate.
func extractSANs(cert *x509.Certificate) []string {
sans := make([]string, 0, len(cert.DNSNames)+len(cert.IPAddresses))
sans = append(sans, cert.DNSNames...)
for _, ip := range cert.IPAddresses {
sans = append(sans, ip.String())
}
sort.Strings(sans)
return sans
}
// extractKeyInfo determines the key type and size from a certificate.
func extractKeyInfo(cert *x509.Certificate) (keyType string, keySize int) {
keyType = "Unknown"
keySize = 0
switch pubKey := cert.PublicKey.(type) {
case *rsa.PublicKey:
keyType = "RSA"
keySize = pubKey.N.BitLen()
case *ecdsa.PublicKey:
keyType = "ECDSA"
keySize = pubKey.Curve.Params().BitSize
}
return keyType, keySize
}
// extractFingerprints calculates SHA-256 fingerprints for certificate and public key.
func extractFingerprints(cert *x509.Certificate) (certFingerprint, pubKeyFingerprint string) {
certHash := sha256.Sum256(cert.Raw)
certFingerprint = hex.EncodeToString(certHash[:])
pubKeyBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
if err == nil {
pubKeyHash := sha256.Sum256(pubKeyBytes)
pubKeyFingerprint = hex.EncodeToString(pubKeyHash[:])
}
return certFingerprint, pubKeyFingerprint
}
// extractIssuerInfo extracts issuer information from a certificate.
func extractIssuerInfo(cert *x509.Certificate) (org, cn, country string) {
if len(cert.Issuer.Organization) > 0 {
org = cert.Issuer.Organization[0]
}
cn = cert.Issuer.CommonName
if len(cert.Issuer.Country) > 0 {
country = cert.Issuer.Country[0]
}
return org, cn, country
}
// extractSubjectInfo extracts subject information from a certificate.
func extractSubjectInfo(cert *x509.Certificate) (organization, country string) {
if len(cert.Subject.Organization) > 0 {
organization = cert.Subject.Organization[0]
}
if len(cert.Subject.Country) > 0 {
country = cert.Subject.Country[0]
}
return organization, country
}
// formatVersion formats the X.509 version for display.
func formatVersion(version int) string {
return fmt.Sprintf("v%d", version)
}
// getCertificateStatus returns the status of a certificate based on its expiry.
func getCertificateStatus(notAfter time.Time) string {
remaining := time.Until(notAfter)
if remaining < 0 {
return certStatusExpired
}
// Show warning for certificates with validity less than 30 days left.
if remaining < 30*24*time.Hour {
return certStatusWarning
}
return certStatusEnabled
}