Add all other cert details
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user