package cert import ( "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "math/big" "testing" "time" "software.sslmate.com/src/go-pkcs12" ) const testPassword = "test-password-123" // generarP12 crea un certificado autofirmado y lo empaqueta en P12. // notBefore y notAfter controlan la validez del certificado. func generarP12(t *testing.T, cn string, notBefore, notAfter time.Time) []byte { t.Helper() key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatalf("generando clave RSA: %v", err) } template := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: cn}, NotBefore: notBefore, NotAfter: notAfter, } certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) if err != nil { t.Fatalf("creando certificado: %v", err) } x509Cert, err := x509.ParseCertificate(certDER) if err != nil { t.Fatalf("parseando certificado: %v", err) } p12Data, err := pkcs12.Modern.Encode(key, x509Cert, nil, testPassword) if err != nil { t.Fatalf("codificando P12: %v", err) } return p12Data } func TestValidateP12Bytes_CertificadoValido365Dias(t *testing.T) { now := time.Now() p12 := generarP12(t, "Valid 365 days", now.Add(-time.Hour), now.Add(365*24*time.Hour)) result := ValidateP12Bytes(p12, testPassword) if !result.Valid { t.Errorf("esperado válido, obtuvo error: %s", result.Error) } if result.Error != "" { t.Errorf("no debería haber error, obtuvo: %s", result.Error) } if len(result.Warnings) > 0 { t.Errorf("no debería haber warnings para cert de 365 días, obtuvo: %v", result.Warnings) } if result.CertInfo == nil { t.Fatal("CertInfo no debería ser nil para cert válido") } if result.CertInfo.DaysUntilExpiry < 360 { t.Errorf("días hasta expiración: esperado ~365, obtuvo %d", result.CertInfo.DaysUntilExpiry) } } func TestValidateP12Bytes_CertificadoValido60Dias(t *testing.T) { now := time.Now() p12 := generarP12(t, "Valid 60 days", now.Add(-time.Hour), now.Add(60*24*time.Hour)) result := ValidateP12Bytes(p12, testPassword) if !result.Valid { t.Errorf("esperado válido, obtuvo error: %s", result.Error) } if len(result.Warnings) > 0 { t.Errorf("no debería haber warnings para cert de 60 días, obtuvo: %v", result.Warnings) } if result.CertInfo.DaysUntilExpiry < 55 || result.CertInfo.DaysUntilExpiry > 65 { t.Errorf("días hasta expiración: esperado ~60, obtuvo %d", result.CertInfo.DaysUntilExpiry) } } func TestValidateP12Bytes_CertificadoExpirado(t *testing.T) { now := time.Now() p12 := generarP12(t, "Expired", now.Add(-20*24*time.Hour), now.Add(-5*24*time.Hour)) result := ValidateP12Bytes(p12, testPassword) if result.Valid { t.Error("esperado inválido para certificado expirado") } if result.Error != "certificate_expired" { t.Errorf("error esperado: certificate_expired, obtuvo: %s", result.Error) } if result.CertInfo == nil || !result.CertInfo.Expired { t.Error("CertInfo.Expired debería ser true") } } func TestValidateP12Bytes_CertificadoCaducaProximamente(t *testing.T) { now := time.Now() p12 := generarP12(t, "Expiring Soon", now.Add(-time.Hour), now.Add(15*24*time.Hour)) result := ValidateP12Bytes(p12, testPassword) if !result.Valid { t.Errorf("esperado válido con warning, obtuvo error: %s", result.Error) } if len(result.Warnings) == 0 { t.Error("esperado warning certificate_expiring_soon para cert que caduca en 15 días") } if len(result.Warnings) > 0 && result.Warnings[0] != "certificate_expiring_soon" { t.Errorf("warning esperado: certificate_expiring_soon, obtuvo: %s", result.Warnings[0]) } if result.CertInfo == nil || !result.CertInfo.ExpiringSoon { t.Error("CertInfo.ExpiringSoon debería ser true") } } func TestValidateP12Bytes_CertificadoAunNoValido(t *testing.T) { now := time.Now() p12 := generarP12(t, "Not Yet Valid", now.Add(30*24*time.Hour), now.Add(395*24*time.Hour)) result := ValidateP12Bytes(p12, testPassword) if result.Valid { t.Error("esperado inválido para certificado aún no vigente") } if result.Error != "certificate_not_yet_valid" { t.Errorf("error esperado: certificate_not_yet_valid, obtuvo: %s", result.Error) } } func TestValidateP12Bytes_ContraseñaIncorrecta(t *testing.T) { now := time.Now() p12 := generarP12(t, "Valid Cert", now.Add(-time.Hour), now.Add(365*24*time.Hour)) result := ValidateP12Bytes(p12, "contraseña-incorrecta") if result.Valid { t.Error("esperado inválido con contraseña incorrecta") } if result.Error != "invalid_password_or_format" { t.Errorf("error esperado: invalid_password_or_format, obtuvo: %s", result.Error) } } func TestValidateP12Bytes_BytesCorruptos(t *testing.T) { result := ValidateP12Bytes([]byte("esto no es un P12 valido"), testPassword) if result.Valid { t.Error("esperado inválido para bytes corruptos") } if result.Error != "invalid_password_or_format" { t.Errorf("error esperado: invalid_password_or_format, obtuvo: %s", result.Error) } } func TestValidateP12Bytes_UmbralWarning30Dias(t *testing.T) { now := time.Now() // Exactamente en el umbral: 30 días → debe dar warning p12Umbral := generarP12(t, "At threshold", now.Add(-time.Hour), now.Add(30*24*time.Hour)) resultUmbral := ValidateP12Bytes(p12Umbral, testPassword) if !resultUmbral.Valid { t.Errorf("cert en umbral debe ser válido, obtuvo error: %s", resultUmbral.Error) } if len(resultUmbral.Warnings) == 0 { t.Error("cert con exactamente 30 días debe tener warning") } // 31 días → NO debe dar warning p12Ok := generarP12(t, "Just above threshold", now.Add(-time.Hour), now.Add(31*24*time.Hour)) resultOk := ValidateP12Bytes(p12Ok, testPassword) if !resultOk.Valid { t.Errorf("cert con 31 días debe ser válido, obtuvo error: %s", resultOk.Error) } if len(resultOk.Warnings) > 0 { t.Error("cert con 31 días NO debe tener warning") } } func TestValidateP12_ArchivoNoExiste(t *testing.T) { result := ValidateP12("/ruta/que/no/existe.p12", testPassword) if result.Valid { t.Error("esperado inválido para archivo inexistente") } if result.Error != "file_not_found" { t.Errorf("error esperado: file_not_found, obtuvo: %s", result.Error) } }