Add all other cert details

This commit is contained in:
2026-04-05 21:56:21 +02:00
parent a36e913e27
commit 80fcac77d8

View File

@@ -5,6 +5,7 @@ package utils
import ( import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"encoding/base64" "encoding/base64"
@@ -74,11 +75,63 @@ type IssuerInfo struct {
SerialNumber string `json:"serial_number"` SerialNumber string `json:"serial_number"`
} }
// CertDetails holds fields parsed from the certificate beyond what TrimmedEntry provides.
type CertDetails struct {
NotBefore string `json:"not_before"`
NotAfter string `json:"not_after"`
SerialNumber string `json:"serial_number"`
Issuer string `json:"issuer"`
EmailSANs []string `json:"email_sans,omitempty"`
URISANs []string `json:"uri_sans,omitempty"`
SubjectKeyID string `json:"subject_key_id,omitempty"`
AuthorityKeyID string `json:"authority_key_id,omitempty"`
OCSPServers []string `json:"ocsp_servers,omitempty"`
IssuingCertURLs []string `json:"issuing_cert_urls,omitempty"`
CRLDistributionPoints []string `json:"crl_distribution_points,omitempty"`
KeyUsage []string `json:"key_usage,omitempty"`
ExtKeyUsage []string `json:"ext_key_usage,omitempty"`
IsCA bool `json:"is_ca"`
}
var keyUsageNames = []struct {
usage x509.KeyUsage
name string
}{
{x509.KeyUsageDigitalSignature, "DigitalSignature"},
{x509.KeyUsageContentCommitment, "ContentCommitment"},
{x509.KeyUsageKeyEncipherment, "KeyEncipherment"},
{x509.KeyUsageDataEncipherment, "DataEncipherment"},
{x509.KeyUsageKeyAgreement, "KeyAgreement"},
{x509.KeyUsageCertSign, "CertSign"},
{x509.KeyUsageCRLSign, "CRLSign"},
{x509.KeyUsageEncipherOnly, "EncipherOnly"},
{x509.KeyUsageDecipherOnly, "DecipherOnly"},
}
var extKeyUsageNames = map[x509.ExtKeyUsage]string{
x509.ExtKeyUsageAny: "Any",
x509.ExtKeyUsageServerAuth: "ServerAuth",
x509.ExtKeyUsageClientAuth: "ClientAuth",
x509.ExtKeyUsageCodeSigning: "CodeSigning",
x509.ExtKeyUsageEmailProtection: "EmailProtection",
x509.ExtKeyUsageIPSECEndSystem: "IPSECEndSystem",
x509.ExtKeyUsageIPSECTunnel: "IPSECTunnel",
x509.ExtKeyUsageIPSECUser: "IPSECUser",
x509.ExtKeyUsageTimeStamping: "TimeStamping",
x509.ExtKeyUsageOCSPSigning: "OCSPSigning",
x509.ExtKeyUsageMicrosoftServerGatedCrypto: "MicrosoftServerGatedCrypto",
x509.ExtKeyUsageNetscapeServerGatedCrypto: "NetscapeServerGatedCrypto",
x509.ExtKeyUsageMicrosoftCommercialCodeSigning: "MicrosoftCommercialCodeSigning",
x509.ExtKeyUsageMicrosoftKernelCodeSigning: "MicrosoftKernelCodeSigning",
}
// Entry represents a CT log entry in JSON format. // Entry represents a CT log entry in JSON format.
type Entry struct { type Entry struct {
EntryNumber int `json:"entry_number"` EntryNumber int `json:"entry_number"`
LeafIndex int64 `json:"leaf_index"` LeafIndex int64 `json:"leaf_index"`
MerkleLeafHash string `json:"merkle_leaf_hash"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
TimestampHuman string `json:"timestamp_human"`
IsPrecert bool `json:"is_precert"` IsPrecert bool `json:"is_precert"`
IssuerKeyHash string `json:"issuer_key_hash,omitempty"` IssuerKeyHash string `json:"issuer_key_hash,omitempty"`
CertificateSize int `json:"certificate_size"` CertificateSize int `json:"certificate_size"`
@@ -86,6 +139,7 @@ type Entry struct {
ChainFingerprints []string `json:"chain_fingerprints"` ChainFingerprints []string `json:"chain_fingerprints"`
Issuers []IssuerInfo `json:"issuers,omitempty"` Issuers []IssuerInfo `json:"issuers,omitempty"`
SCTs []SCT `json:"scts,omitempty"` SCTs []SCT `json:"scts,omitempty"`
CertDetails *CertDetails `json:"cert_details,omitempty"`
ParsedCertInfo json.RawMessage `json:"parsed_cert_info,omitempty"` ParsedCertInfo json.RawMessage `json:"parsed_cert_info,omitempty"`
} }
@@ -414,11 +468,64 @@ func fetchIssuer(logURL, fingerprint string) (*IssuerInfo, error) {
return info, nil return info, nil
} }
// parseCertDetails extracts certificate fields not covered by TrimmedEntry.
func parseCertDetails(certDER []byte) *CertDetails {
cert, err := x509.ParseCertificate(certDER)
if err != nil {
return nil
}
d := &CertDetails{
NotBefore: cert.NotBefore.UTC().Format(time.RFC3339),
NotAfter: cert.NotAfter.UTC().Format(time.RFC3339),
SerialNumber: cert.SerialNumber.String(),
Issuer: cert.Issuer.String(),
OCSPServers: cert.OCSPServer,
IssuingCertURLs: cert.IssuingCertificateURL,
CRLDistributionPoints: cert.CRLDistributionPoints,
IsCA: cert.IsCA,
}
for _, e := range cert.EmailAddresses {
d.EmailSANs = append(d.EmailSANs, e)
}
for _, u := range cert.URIs {
d.URISANs = append(d.URISANs, u.String())
}
if len(cert.SubjectKeyId) > 0 {
d.SubjectKeyID = hex.EncodeToString(cert.SubjectKeyId)
}
if len(cert.AuthorityKeyId) > 0 {
d.AuthorityKeyID = hex.EncodeToString(cert.AuthorityKeyId)
}
for _, ku := range keyUsageNames {
if cert.KeyUsage&ku.usage != 0 {
d.KeyUsage = append(d.KeyUsage, ku.name)
}
}
for _, eku := range cert.ExtKeyUsage {
if name, ok := extKeyUsageNames[eku]; ok {
d.ExtKeyUsage = append(d.ExtKeyUsage, name)
}
}
return d
}
// merkleLeafHash computes the RFC 6962 Merkle leaf hash: SHA-256(0x00 || leaf).
func merkleLeafHash(leaf []byte) string {
h := sha256.New()
h.Write([]byte{0x00})
h.Write(leaf)
return hex.EncodeToString(h.Sum(nil))
}
func convertEntry(e *sunlight.LogEntry, entryNum int, opts Options) Entry { func convertEntry(e *sunlight.LogEntry, entryNum int, opts Options) Entry {
entry := Entry{ entry := Entry{
EntryNumber: entryNum, EntryNumber: entryNum,
LeafIndex: e.LeafIndex, LeafIndex: e.LeafIndex,
MerkleLeafHash: merkleLeafHash(e.MerkleTreeLeaf()),
Timestamp: e.Timestamp, Timestamp: e.Timestamp,
TimestampHuman: time.UnixMilli(e.Timestamp).UTC().Format(time.RFC3339),
IsPrecert: e.IsPrecert, IsPrecert: e.IsPrecert,
CertificateSize: len(e.Certificate), CertificateSize: len(e.Certificate),
} }
@@ -462,6 +569,15 @@ func convertEntry(e *sunlight.LogEntry, entryNum int, opts Options) Entry {
} }
} }
// Parse extended certificate details.
certDER := e.Certificate
if e.IsPrecert {
certDER = e.PreCertificate
}
if len(certDER) > 0 {
entry.CertDetails = parseCertDetails(certDER)
}
// Try to extract parsed certificate info // Try to extract parsed certificate info
if trimmed, err := e.TrimmedEntry(); err == nil { if trimmed, err := e.TrimmedEntry(); err == nil {
if data, err := json.Marshal(trimmed); err == nil { if data, err := json.Marshal(trimmed); err == nil {