package tool import ( "crypto/x509" "encoding/hex" "encoding/pem" "errors" "fmt" "math/big" "net" "net/url" "strings" "time" ) // CertInfo holds the relevant data extracted from an X.509 certificate. type CertInfo struct { Subject string Issuer string SerialNumber string NotBefore time.Time NotAfter time.Time SignatureAlgorithm string PublicKeyAlgorithm string KeyUsage []string ExtendedKeyUsage []string DNSNames []string IPAddresses []string EmailAddresses []string URIs []*url.URL IsCA bool AuthorityKeyId string SubjectKeyId string BasicConstraintsValid bool } // CertificateParser represents the "class" responsible for parsing certificates. type CertificateParser struct{} // mapKeyUsage translates the x509.KeyUsage bitmask into a slice of descriptive strings. func mapKeyUsage(ku x509.KeyUsage) []string { var usages []string if ku&x509.KeyUsageDigitalSignature != 0 { usages = append(usages, "Digital Signature") } if ku&x509.KeyUsageContentCommitment != 0 { usages = append(usages, "Content Commitment") } if ku&x509.KeyUsageKeyEncipherment != 0 { usages = append(usages, "Key Encipherment") } if ku&x509.KeyUsageDataEncipherment != 0 { usages = append(usages, "Data Encipherment") } if ku&x509.KeyUsageKeyAgreement != 0 { usages = append(usages, "Key Agreement") } if ku&x509.KeyUsageCertSign != 0 { usages = append(usages, "Cert Sign") } if ku&x509.KeyUsageCRLSign != 0 { usages = append(usages, "CRL Sign") } if ku&x509.KeyUsageEncipherOnly != 0 { usages = append(usages, "Encipher Only") } if ku&x509.KeyUsageDecipherOnly != 0 { usages = append(usages, "Decipher Only") } return usages } // mapExtKeyUsage translates the x509.ExtKeyUsage slice into a slice of descriptive strings. func mapExtKeyUsage(eku []x509.ExtKeyUsage) []string { var usages []string for _, u := range eku { switch u { case x509.ExtKeyUsageAny: usages = append(usages, "Any") case x509.ExtKeyUsageServerAuth: usages = append(usages, "Server Auth") case x509.ExtKeyUsageClientAuth: usages = append(usages, "Client Auth") case x509.ExtKeyUsageCodeSigning: usages = append(usages, "Code Signing") case x509.ExtKeyUsageEmailProtection: usages = append(usages, "Email Protection") case x509.ExtKeyUsageTimeStamping: usages = append(usages, "Time Stamping") case x509.ExtKeyUsageOCSPSigning: usages = append(usages, "OCSP Signing") case x509.ExtKeyUsageMicrosoftServerGatedCrypto: usages = append(usages, "Microsoft Server Gated Crypto") case x509.ExtKeyUsageNetscapeServerGatedCrypto: usages = append(usages, "Netscape Server Gated Crypto") case x509.ExtKeyUsageMicrosoftCommercialCodeSigning: usages = append(usages, "Microsoft Commercial Code Signing") case x509.ExtKeyUsageMicrosoftKernelCodeSigning: usages = append(usages, "Microsoft Kernel Code Signing") default: usages = append(usages, fmt.Sprintf("Unknown (%d)", u)) } } return usages } // formatSerialNumber formats a big.Int serial number into a hexadecimal string. func formatSerialNumber(sn *big.Int) string { if sn == nil { return "" } return strings.ToUpper(hex.EncodeToString(sn.Bytes())) } // formatIPAddresses formats a slice of net.IP into a slice of strings. func formatIPAddresses(ips []net.IP) []string { var ipStrs []string for _, ip := range ips { ipStrs = append(ipStrs, ip.String()) } return ipStrs } // mapCertificateToInfo takes an x509.Certificate and maps its fields to the CertInfo struct. func mapCertificateToInfo(cert *x509.Certificate) *CertInfo { return &CertInfo{ Subject: cert.Subject.String(), Issuer: cert.Issuer.String(), SerialNumber: formatSerialNumber(cert.SerialNumber), NotBefore: cert.NotBefore, NotAfter: cert.NotAfter, SignatureAlgorithm: cert.SignatureAlgorithm.String(), PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(), IsCA: cert.IsCA, KeyUsage: mapKeyUsage(cert.KeyUsage), ExtendedKeyUsage: mapExtKeyUsage(cert.ExtKeyUsage), DNSNames: cert.DNSNames, IPAddresses: formatIPAddresses(cert.IPAddresses), EmailAddresses: cert.EmailAddresses, URIs: cert.URIs, BasicConstraintsValid: cert.BasicConstraintsValid, AuthorityKeyId: strings.ToUpper(hex.EncodeToString(cert.AuthorityKeyId)), SubjectKeyId: strings.ToUpper(hex.EncodeToString(cert.SubjectKeyId)), } } // parseCertificates extracts and returns all x509.Certificate objects from the raw PEM-encoded data. func (cp *CertificateParser) parseCertificates(certData []byte) ([]*x509.Certificate, error) { var certs []*x509.Certificate data := certData for len(data) > 0 { block, rest := pem.Decode(data) if block == nil { break } if block.Type == "CERTIFICATE" { // x509.ParseCertificates handles both single and multiple certificates in the block bytes parsedCerts, err := x509.ParseCertificates(block.Bytes) if err != nil { // Skip malformed blocks but continue fmt.Printf("Warning: skipping malformed certificate (%v)\n", err) data = rest continue } certs = append(certs, parsedCerts...) } data = rest } if len(certs) == 0 { return nil, errors.New("no valid CERTIFICATE blocks found") } return certs, nil } // Parse extracts information from one or more PEM-encoded certificates into CertInfo structs. func (cp *CertificateParser) Parse(certData []byte) ([]*CertInfo, error) { certs, err := cp.parseCertificates(certData) if err != nil { return nil, err } var certsInfo []*CertInfo for _, cert := range certs { certsInfo = append(certsInfo, mapCertificateToInfo(cert)) } return certsInfo, nil } // IsValid checks if all certificates in the provided data are currently valid. // It returns true if all certificates are valid, or false otherwise. // If the certificates are parseable but one or more are expired or not yet valid, it returns false. func (cp *CertificateParser) IsValid(certData []byte) (bool, error) { certs, err := cp.parseCertificates(certData) if err != nil { // Handle parsing errors by returning false. The requirement was to return // false with no error if the certificate is *parseable* but expired. // A parsing error means it's not even parseable, so it's not valid. return false, fmt.Errorf("failed to parse certificates: %w", err) } now := time.Now() for _, cert := range certs { // Check NotBefore if now.Before(cert.NotBefore) { // Certificate is not yet valid (future-dated) return false, nil } // Check NotAfter if now.After(cert.NotAfter) { // Certificate has expired return false, nil } } // If the loop completes without returning false, all certificates are currently valid. return true, nil }