ProyectoGrupal/VerifactuMidAPI/internal/cert/validator.go

214 lines
5.3 KiB
Go
Raw Permalink Normal View History

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
}