package cert import ( "crypto/rsa" "crypto/x509" "math" "os" "time" goPkcs12 "software.sslmate.com/src/go-pkcs12" stdPkcs12 "golang.org/x/crypto/pkcs12" ) type ValidationResult struct { Valid bool `json:"valid"` CertInfo *CertInfo `json:"cert_info,omitempty"` Warnings []string `json:"warnings,omitempty"` Error string `json:"error,omitempty"` } type CertInfo struct { Subject string `json:"subject"` Issuer string `json:"issuer"` NotBefore string `json:"not_before"` NotAfter string `json:"not_after"` Expired bool `json:"expired"` ExpiringSoon bool `json:"expiring_soon"` DaysUntilExpiry int `json:"days_until_expiry"` } const WarningDaysThreshold = 30 func ValidateP12(filePath, password string) *ValidationResult { p12Data, err := os.ReadFile(filePath) if err != nil { result := &ValidationResult{Valid: true} if os.IsNotExist(err) { result.Valid = false result.Error = "file_not_found" return result } result.Valid = false result.Error = "invalid_password_or_format" return result } return ValidateP12Bytes(p12Data, password) } // ValidateP12Bytes valida un P12 a partir de sus bytes (util para tests sin fichero). // Soporta tanto P12 simples (go-pkcs12) como P12 con cadena de certificados (x/crypto/pkcs12). func ValidateP12Bytes(p12Data []byte, password string) *ValidationResult { result := &ValidationResult{Valid: true} x509Cert, err := decodeLeafCert(p12Data, password) if err != nil { result.Valid = false result.Error = "invalid_password_or_format" return result } now := time.Now() if now.Before(x509Cert.NotBefore) { result.Valid = false result.Error = "certificate_not_yet_valid" return result } if now.After(x509Cert.NotAfter) { result.Valid = false result.Error = "certificate_expired" result.CertInfo = &CertInfo{ Subject: x509Cert.Subject.CommonName, Issuer: x509Cert.Issuer.CommonName, NotBefore: x509Cert.NotBefore.Format("2006-01-02"), NotAfter: x509Cert.NotAfter.Format("2006-01-02"), Expired: true, } return result } daysUntilExpiry := int(math.Ceil(x509Cert.NotAfter.Sub(now).Hours() / 24)) result.CertInfo = &CertInfo{ Subject: x509Cert.Subject.CommonName, Issuer: x509Cert.Issuer.CommonName, NotBefore: x509Cert.NotBefore.Format("2006-01-02"), NotAfter: x509Cert.NotAfter.Format("2006-01-02"), DaysUntilExpiry: daysUntilExpiry, } if daysUntilExpiry <= WarningDaysThreshold { result.Warnings = append(result.Warnings, "certificate_expiring_soon") result.CertInfo.ExpiringSoon = true } return result } // decodeLeafCert extrae el certificado hoja del P12. // Intenta primero go-pkcs12 (P12 simples) y luego x/crypto/pkcs12 (P12 con cadena FNMT/ACCV). func decodeLeafCert(p12Data []byte, password string) (*x509.Certificate, error) { // Intento 1: go-pkcs12 — soporta P12 simples (1 clave + 1 cert) _, cert, err := goPkcs12.Decode(p12Data, password) if err == nil { return cert, nil } // Intento 2: x/crypto/pkcs12 — soporta P12 con cadena de certificados (FNMT, ACCV, AEAT) blocks, err := stdPkcs12.ToPEM(p12Data, password) if err != nil { return nil, err } var privateKey *rsa.PrivateKey var certs []*x509.Certificate for _, b := range blocks { switch b.Type { case "PRIVATE KEY": key, err := x509.ParsePKCS8PrivateKey(b.Bytes) if err != nil { key2, err2 := x509.ParsePKCS1PrivateKey(b.Bytes) if err2 == nil { privateKey = key2 } } else if rsaKey, ok := key.(*rsa.PrivateKey); ok { privateKey = rsaKey } case "CERTIFICATE": cert, err := x509.ParseCertificate(b.Bytes) if err == nil { certs = append(certs, cert) } } } if privateKey == nil || len(certs) == 0 { return nil, stdPkcs12.ErrIncorrectPassword } for _, c := range certs { if rsaPub, ok := c.PublicKey.(*rsa.PublicKey); ok { if rsaPub.N.Cmp(privateKey.PublicKey.N) == 0 { return c, nil } } } return certs[0], nil } // DecodeLeafAndKey extrae la clave privada y el certificado hoja de un P12. // Usado por el cliente TLS para establecer conexiones mTLS. func DecodeLeafAndKey(p12Data []byte, password string) (interface{}, *x509.Certificate, []*x509.Certificate, error) { key, cert, err := goPkcs12.Decode(p12Data, password) if err == nil { return key, cert, nil, nil } blocks, err := stdPkcs12.ToPEM(p12Data, password) if err != nil { return nil, nil, nil, err } var privateKey interface{} var leafCert *x509.Certificate var chain []*x509.Certificate for _, b := range blocks { switch b.Type { case "PRIVATE KEY": k, err := x509.ParsePKCS8PrivateKey(b.Bytes) if err != nil { k2, err2 := x509.ParsePKCS1PrivateKey(b.Bytes) if err2 == nil { privateKey = k2 } } else { privateKey = k } case "CERTIFICATE": c, err := x509.ParseCertificate(b.Bytes) if err == nil { chain = append(chain, c) } } } if privateKey == nil || len(chain) == 0 { return nil, nil, nil, stdPkcs12.ErrIncorrectPassword } if rsaKey, ok := privateKey.(*rsa.PrivateKey); ok { for i, c := range chain { if rsaPub, ok := c.PublicKey.(*rsa.PublicKey); ok { if rsaPub.N.Cmp(rsaKey.PublicKey.N) == 0 { leafCert = c chain = append(chain[:i], chain[i+1:]...) break } } } } if leafCert == nil { leafCert = chain[0] chain = chain[1:] } return privateKey, leafCert, chain, nil }