From eb4693f11d3b35d6b360e56b1fab125fdd8306d7 Mon Sep 17 00:00:00 2001 From: Peter Dennis Bartok Date: Tue, 23 Jun 2026 18:06:50 -0600 Subject: [PATCH] Improved signer findings and selection. --- src/ScepWright.Core/ScepClient.cs | 11 +- .../BcPkiMessage.cs | 110 ++++++++++++++--- .../BouncyCastleScepCrypto.cs | 4 +- src/ScepWright.Crypto/IScepCrypto.cs | 9 +- src/ScepWright.Crypto/PkiMessage.cs | 21 +++- tests/ScepWright.Tests/BcCrlDecodeTests.cs | 2 +- tests/ScepWright.Tests/BcDecodeTests.cs | 2 +- .../CertRepSignatureDiagnosticsTests.cs | 113 ++++++++++++++++++ tests/ScepWright.Tests/CodecOptionsTests.cs | 14 +-- tests/ScepWright.Tests/DomainObjectTests.cs | 2 +- tests/ScepWright.Tests/ProviderLoadTests.cs | 2 +- 11 files changed, 255 insertions(+), 35 deletions(-) create mode 100644 tests/ScepWright.Tests/CertRepSignatureDiagnosticsTests.cs diff --git a/src/ScepWright.Core/ScepClient.cs b/src/ScepWright.Core/ScepClient.cs index eafdd2f..baa3182 100644 --- a/src/ScepWright.Core/ScepClient.cs +++ b/src/ScepWright.Core/ScepClient.cs @@ -20,6 +20,9 @@ namespace ScepWright.Core; public sealed class ScepClient { private readonly ScepHttpTransport _transport; private X509Certificate2? _recipient_cert_cache; + // The most recent GetCACert bundle, used as extra candidates when verifying a CertRep signature whose + // signer cert the server did not embed in the response. + private IReadOnlyList? _ca_bundle_cache; /// Gets the crypto provider backing this client. public IScepCrypto Crypto { get; } @@ -124,6 +127,7 @@ public ScepResult> GetCaCert() { return ScepResult>.Fail(ScepClientResult.CryptoError, error); } + _ca_bundle_cache = certs; return ScepResult>.Ok(certs); } @@ -144,6 +148,7 @@ public async Task>> GetCaCertAsync() return ScepResult>.Fail(ScepClientResult.CryptoError, error); } + _ca_bundle_cache = certs; return ScepResult>.Ok(certs); } @@ -612,7 +617,7 @@ private ScepResult SendDecodedSync(PkiMessage message) { if (!raw.IsOk) { return ScepResult.Fail(raw.Status, raw.Error); } - if (!PkiMessage.Decode(Crypto, raw.Value, message.SignerKey!, CodecOptions.LenientParsing, out decoded, out decode_error)) { + if (!PkiMessage.Decode(Crypto, raw.Value, message.SignerKey!, CodecOptions.LenientParsing, out decoded, out decode_error, known_certs: _ca_bundle_cache)) { return ScepResult.Fail(ScepClientResult.CryptoError, decode_error); } return ScepResult.Ok(decoded); @@ -634,7 +639,7 @@ private async Task> SendDecodedAsync(PkiMessage message) if (!raw.IsOk) { return ScepResult.Fail(raw.Status, raw.Error); } - if (!PkiMessage.Decode(Crypto, raw.Value, message.SignerKey!, CodecOptions.LenientParsing, out decoded, out decode_error)) { + if (!PkiMessage.Decode(Crypto, raw.Value, message.SignerKey!, CodecOptions.LenientParsing, out decoded, out decode_error, known_certs: _ca_bundle_cache)) { return ScepResult.Fail(ScepClientResult.CryptoError, decode_error); } return ScepResult.Ok(decoded); @@ -964,7 +969,7 @@ private ScepResult DecodeResponse(byte[] response_bytes, IScepKey X509Certificate2? cert; EnrollOutcome outcome; - if (!PkiMessage.Decode(Crypto, response_bytes, recipient_key, CodecOptions.LenientParsing, out decoded, out decode_error)) { + if (!PkiMessage.Decode(Crypto, response_bytes, recipient_key, CodecOptions.LenientParsing, out decoded, out decode_error, known_certs: _ca_bundle_cache)) { return ScepResult.Fail(ScepClientResult.CryptoError, decode_error); } diff --git a/src/ScepWright.Crypto.BouncyCastle/BcPkiMessage.cs b/src/ScepWright.Crypto.BouncyCastle/BcPkiMessage.cs index 7c0838f..3819cab 100644 --- a/src/ScepWright.Crypto.BouncyCastle/BcPkiMessage.cs +++ b/src/ScepWright.Crypto.BouncyCastle/BcPkiMessage.cs @@ -138,7 +138,7 @@ public static byte[] BuildIssuerAndSubject(string issuer_dn, string subject_dn) private const string Sha1DigestOid = "1.3.14.3.2.26"; public static PkiMessage Decode(byte[] der, BcKey recipient_key, CodecOptions options) { - return Decode(der, recipient_key, options, out _); + return Decode(der, recipient_key, options, known_certs: null, out _); } // Honors CodecOptions. Strict (0) enforces both a valid CMS signature and a non-legacy signer digest; @@ -146,14 +146,17 @@ public static PkiMessage Decode(byte[] der, BcKey recipient_key, CodecOptions op // LenientParsing relaxes both (today's tolerant behavior). On a strict-mode violation the message is // still returned (so callers can inspect it), but decode_error is set non-empty so the provider can // surface a clean false + error. - public static PkiMessage Decode(byte[] der, BcKey recipient_key, CodecOptions options, out string decode_error) { + public static PkiMessage Decode(byte[] der, BcKey recipient_key, CodecOptions options, + System.Collections.Generic.IReadOnlyList? known_certs, out string decode_error) { CmsSignedData signed_data; IStore cert_store; ICollection signer_collection; System.Collections.IEnumerator signer_enumerator; SignerInformation signer; - System.Collections.Generic.IEnumerable matching_certs; + Org.BouncyCastle.X509.X509Certificate? embedded_match; Org.BouncyCastle.X509.X509Certificate signer_cert; + string verified_source; + int candidate_count; bool signature_ok; bool lenient; string digest_oid; @@ -182,25 +185,63 @@ public static PkiMessage Decode(byte[] der, BcKey recipient_key, CodecOptions op signer_enumerator.MoveNext(); signer = (SignerInformation)signer_enumerator.Current; - matching_certs = cert_store.EnumerateMatches(signer.SignerID); - signer_cert = null!; - foreach (Org.BouncyCastle.X509.X509Certificate c in matching_certs) { - signer_cert = c; + // Record who the response *claims* signed it (issuer+serial or subjectKeyIdentifier), so a failed + // verification can be diagnosed: genuinely invalid vs. "the signer cert wasn't where we looked". + result.SignerClaimedIdentity = FormatSignerId(signer.SignerID); + + // The cert the CertRep itself offered for the claimed signer (matched by SignerIdentifier). + embedded_match = null; + foreach (Org.BouncyCastle.X509.X509Certificate c in cert_store.EnumerateMatches(signer.SignerID)) { + embedded_match = c; break; } + // Verify against a candidate pool — the CertRep's own certs first, then the GetCACert bundle — so a + // valid signature whose signer cert was simply not embedded is confirmed, and a "claimed cert X but + // cert Y actually signed" mismatch is detected rather than reported as a bare failure. signature_ok = false; - if (signer_cert != null) { - try { - signature_ok = signer.Verify(signer_cert); - } catch { - signature_ok = false; + signer_cert = null!; + verified_source = string.Empty; + candidate_count = 0; + if (embedded_match != null && TryVerify(signer, embedded_match)) { + signature_ok = true; + signer_cert = embedded_match; + verified_source = "CertRep"; + } else { + foreach (System.ValueTuple candidate in VerificationCandidates(cert_store, known_certs)) { + candidate_count++; + if (!signature_ok && TryVerify(signer, candidate.Item1)) { + signature_ok = true; + signer_cert = candidate.Item1; + verified_source = candidate.Item2; + } } } result.SignatureValid = signature_ok; - if (!signature_ok) { - result.ConformanceNotes.Add(new ConformanceNote(NoteSeverity.Warning, "signature verification failed", "SignedData", "RFC 8894 §3.2")); + if (signature_ok && (verified_source != "CertRep" || !ReferenceEquals(signer_cert, embedded_match))) { + // Verified, but not by the cert the CertRep presented for the claimed signer — surface what + // actually signed, since a peer relying on the CertRep's own bag would call this invalid. + result.SignerVerifiedWith = $"{DescribeCert(signer_cert)} (from {verified_source})"; + result.ConformanceNotes.Add(new ConformanceNote(NoteSeverity.Warning, + $"signature is VALID but was verified using the {verified_source} cert [{DescribeCert(signer_cert)}], not the cert the CertRep presented for the claimed signer ({result.SignerClaimedIdentity})" + + (embedded_match == null + ? " — the CertRep embedded no cert matching the claimed signer; the server should include its RA/CA signing cert in the CertRep" + : $" — the embedded cert [{DescribeCert(embedded_match)}] did not verify the signature"), + "SignedData", "RFC 8894 §3.2")); + } else if (signature_ok) { + result.SignerVerifiedWith = $"{DescribeCert(signer_cert)} (from CertRep)"; + } else { + // Nothing verified: report the claimed signer, the cert we checked, and how many we tried, so a + // server-implementor can tell a truly bad signature from a wrong-cert / missing-cert situation. + result.SignerVerifiedWith = null; + result.ConformanceNotes.Add(new ConformanceNote(NoteSeverity.Warning, + $"signature verification FAILED — claimed signer: {result.SignerClaimedIdentity}; " + + (embedded_match != null + ? $"the cert the CertRep presented for that signer [{DescribeCert(embedded_match)}] did not verify; " + : "no cert embedded in the CertRep matched the claimed signer; ") + + $"tried {candidate_count} candidate cert(s) from the CertRep bag and the GetCACert bundle and none produced a valid signature — the signature is invalid against every available cert (wrong signing key, altered message, or the real signing cert was provided by neither GetCACert nor the CertRep)", + "SignedData", "RFC 8894 §3.2")); } // Strict-mode gate 1 — signature integrity. Fail unless the caller opted into tolerance @@ -307,6 +348,47 @@ private static List ExtractCertsFromDegeneratePkcs7(byte[] der return certs; } + private static bool TryVerify(SignerInformation signer, Org.BouncyCastle.X509.X509Certificate cert) { + try { + return signer.Verify(cert); + } catch { + return false; + } + } + + // The candidate certificates the response signature is checked against: every cert embedded in the + // CertRep, then the caller-supplied GetCACert bundle (so a signer cert the server didn't embed is found). + private static System.Collections.Generic.IEnumerable> VerificationCandidates( + IStore embedded, + System.Collections.Generic.IReadOnlyList? known_certs) { + Org.BouncyCastle.X509.X509CertificateParser parser; + + foreach (Org.BouncyCastle.X509.X509Certificate c in embedded.EnumerateMatches(new Org.BouncyCastle.X509.Store.X509CertStoreSelector())) { + yield return new System.ValueTuple(c, "CertRep"); + } + if (known_certs != null) { + parser = new Org.BouncyCastle.X509.X509CertificateParser(); + foreach (System.Security.Cryptography.X509Certificates.X509Certificate2 kc in known_certs) { + yield return new System.ValueTuple(parser.ReadCertificate(kc.RawData), "GetCACert"); + } + } + } + + // "issuer 'CN=..', serial 0A" for an issuerAndSerialNumber signer, or "subjectKeyIdentifier ". + private static string FormatSignerId(Org.BouncyCastle.Cms.SignerID id) { + if (id.Issuer != null && id.SerialNumber != null) { + return $"issuer '{id.Issuer}', serial {id.SerialNumber.ToString(16)}"; + } + if (id.SubjectKeyIdentifier != null) { + return $"subjectKeyIdentifier {Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(id.SubjectKeyIdentifier)}"; + } + return "(unspecified signer identifier)"; + } + + private static string DescribeCert(Org.BouncyCastle.X509.X509Certificate cert) { + return $"subject '{cert.SubjectDN}', serial {cert.SerialNumber.ToString(16)}"; + } + private static IReadOnlyList ExtractCrlsFromDegeneratePkcs7(byte[] der) { CmsSignedData signed_data; IStore crl_store; diff --git a/src/ScepWright.Crypto.BouncyCastle/BouncyCastleScepCrypto.cs b/src/ScepWright.Crypto.BouncyCastle/BouncyCastleScepCrypto.cs index 631de7e..bbd0f98 100644 --- a/src/ScepWright.Crypto.BouncyCastle/BouncyCastleScepCrypto.cs +++ b/src/ScepWright.Crypto.BouncyCastle/BouncyCastleScepCrypto.cs @@ -179,7 +179,7 @@ public bool EncodePkiMessage(PkiMessage message, FaultDirectives? faults, out by } /// - public bool DecodePkiMessage(byte[] der, IScepKey recipient_key, CodecOptions options, out PkiMessage message, out string error) { + public bool DecodePkiMessage(byte[] der, IScepKey recipient_key, CodecOptions options, System.Collections.Generic.IReadOnlyList? known_certs, out PkiMessage message, out string error) { message = null!; error = string.Empty; @@ -191,7 +191,7 @@ public bool DecodePkiMessage(byte[] der, IScepKey recipient_key, CodecOptions op try { string decode_error; - message = BcPkiMessage.Decode(der, bc_key, options, out decode_error); + message = BcPkiMessage.Decode(der, bc_key, options, known_certs, out decode_error); if (decode_error.Length > 0) { error = decode_error; return false; diff --git a/src/ScepWright.Crypto/IScepCrypto.cs b/src/ScepWright.Crypto/IScepCrypto.cs index 210a34e..0cb0aeb 100644 --- a/src/ScepWright.Crypto/IScepCrypto.cs +++ b/src/ScepWright.Crypto/IScepCrypto.cs @@ -22,8 +22,13 @@ public interface IScepCrypto { /// Encodes a SCEP PKI message to DER, optionally injecting the given fault directives for negative testing. bool EncodePkiMessage(PkiMessage message, FaultDirectives? faults, out byte[] der, out string error); - /// Decodes a SCEP PKI message from DER, decrypting with the recipient key and applying the given codec options. - bool DecodePkiMessage(byte[] der, IScepKey recipient_key, CodecOptions options, out PkiMessage message, out string error); + /// + /// Decodes a SCEP PKI message from DER, decrypting with the recipient key and applying the given codec + /// options. (e.g. the GetCACert bundle) are added to the pool of + /// certificates the response signature is verified against, so a valid signature whose signer cert was + /// not embedded in the message can still be confirmed and diagnosed. + /// + bool DecodePkiMessage(byte[] der, IScepKey recipient_key, CodecOptions options, IReadOnlyList? known_certs, out PkiMessage message, out string error); /// Parses a CA certificate bundle (degenerate PKCS#7 or raw cert) from DER. bool ParseCaCertificates(byte[] der, out IReadOnlyList certs, out string error); diff --git a/src/ScepWright.Crypto/PkiMessage.cs b/src/ScepWright.Crypto/PkiMessage.cs index 7ed1ed3..5e95103 100644 --- a/src/ScepWright.Crypto/PkiMessage.cs +++ b/src/ScepWright.Crypto/PkiMessage.cs @@ -44,6 +44,16 @@ public sealed class PkiMessage { public byte[]? RecipientNonce { get; set; } /// Gets or sets whether the outer signature verified (decode output). public bool SignatureValid { get; set; } + /// + /// Gets or sets the signer identity the response *claimed* (the CMS SignerIdentifier — issuer+serial or + /// subjectKeyIdentifier), so a diagnostic can compare who signed against which cert was checked (decode output). + /// + public string? SignerClaimedIdentity { get; set; } + /// + /// Gets or sets a description of the certificate whose public key actually verified the signature, and + /// where it came from (the CertRep's own bag or the GetCACert bundle), or null if none verified (decode output). + /// + public string? SignerVerifiedWith { get; set; } /// Gets or sets the decrypted inner content (decode output). public byte[]? DecryptedContent { get; set; } /// Gets or sets the certificates returned in a successful CertRep (decode output). @@ -68,7 +78,12 @@ public bool Encode(IScepCrypto crypto, FaultDirectives? faults, out byte[] der, return crypto.EncodePkiMessage(this, faults, out der, out error); } - /// Decodes a SCEP PKI message from DER, decrypting with the given recipient key. - public static bool Decode(IScepCrypto crypto, byte[] der, IScepKey key, CodecOptions options, out PkiMessage message, out string error) => - crypto.DecodePkiMessage(der, key, options, out message, out error); + /// + /// Decodes a SCEP PKI message from DER, decrypting with the given recipient key. Pass + /// (e.g. the GetCACert bundle) so a response whose signer cert is not + /// embedded can still have its signature verified and diagnosed. + /// + public static bool Decode(IScepCrypto crypto, byte[] der, IScepKey key, CodecOptions options, out PkiMessage message, out string error, + IReadOnlyList? known_certs = null) => + crypto.DecodePkiMessage(der, key, options, known_certs, out message, out error); } diff --git a/tests/ScepWright.Tests/BcCrlDecodeTests.cs b/tests/ScepWright.Tests/BcCrlDecodeTests.cs index 7b40b8e..b0489b2 100644 --- a/tests/ScepWright.Tests/BcCrlDecodeTests.cs +++ b/tests/ScepWright.Tests/BcCrlDecodeTests.cs @@ -28,7 +28,7 @@ public void Decodes_crl_from_certrep() { rep = ca.BuildSuccessCrlRep(ca.GenerateCrl(), recipient_cert, "tx", new byte[16]); - Assert.True(crypto.DecodePkiMessage(rep, recipient_key, CodecOptions.LenientParsing, out decoded, out error), error); + Assert.True(crypto.DecodePkiMessage(rep, recipient_key, CodecOptions.LenientParsing, null, out decoded, out error), error); Assert.Single(decoded.IssuedCrls); parsed = new Org.BouncyCastle.X509.X509CrlParser().ReadCrl(decoded.IssuedCrls[0]); Assert.NotNull(parsed); diff --git a/tests/ScepWright.Tests/BcDecodeTests.cs b/tests/ScepWright.Tests/BcDecodeTests.cs index f225fd2..c62a7ec 100644 --- a/tests/ScepWright.Tests/BcDecodeTests.cs +++ b/tests/ScepWright.Tests/BcDecodeTests.cs @@ -40,7 +40,7 @@ public void Decodes_success_certrep_with_issued_cert() { cert_rep = ca.BuildSuccessCertRep(issued, client_cert, "abc123", new byte[16]); - Assert.True(crypto.DecodePkiMessage(cert_rep, key, CodecOptions.LenientParsing, out decoded, out error), error); + Assert.True(crypto.DecodePkiMessage(cert_rep, key, CodecOptions.LenientParsing, null, out decoded, out error), error); Assert.Equal(PkiStatus.Success, decoded.PkiStatus); Assert.True(decoded.SignatureValid); Assert.Single(decoded.IssuedCerts); diff --git a/tests/ScepWright.Tests/CertRepSignatureDiagnosticsTests.cs b/tests/ScepWright.Tests/CertRepSignatureDiagnosticsTests.cs new file mode 100644 index 0000000..a3700dd --- /dev/null +++ b/tests/ScepWright.Tests/CertRepSignatureDiagnosticsTests.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using Org.BouncyCastle.Cms; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Utilities.Collections; +using ScepWright.Crypto; +using ScepWright.Crypto.BouncyCastle; +using ScepWright.Server; +using Xunit; + +namespace ScepWright.Tests; + +// When a CertRep signature can't be verified the client must say WHO claimed to sign it and WHAT cert it +// checked — and try the GetCACert bundle — so a server-implementor can tell a genuinely invalid signature +// from "the signer cert simply wasn't embedded in the CertRep, so we looked in the wrong place". +public class CertRepSignatureDiagnosticsTests { + private static byte[] BuildCertRep(out BouncyCastleScepCrypto crypto, out ScepCa ca, out IScepKey client_key) { + KeySpec spec; + Pkcs10 csr; + byte[] csr_der; + Pkcs10CertificationRequest parsed; + Org.BouncyCastle.X509.X509Certificate issued; + X509Certificate2 client_cert; + + crypto = new BouncyCastleScepCrypto(); + ca = ScepCa.Create(); + KeySpec.Parse("rsa:2048", out spec, out _); + crypto.GenerateKey(spec, out client_key, out _); + csr = new Pkcs10 { Key = client_key }; + csr.SetSubject("CN=poodle", out _); + crypto.EncodeCsr(csr, out csr_der, out _); + parsed = new Pkcs10CertificationRequest(csr_der); + issued = ca.Issue(parsed.GetPublicKey(), "CN=poodle"); + client_cert = new X509Certificate2(issued.GetEncoded()); + return ca.BuildSuccessCertRep(issued, client_cert, "tx", new byte[16]); + } + + private static byte[] StripCertificates(byte[] cert_rep) { + CmsSignedData signed; + IStore empty; + + signed = new CmsSignedData(cert_rep); + empty = CollectionUtilities.CreateStore(new List()); + return CmsSignedData.ReplaceCertificatesAndCrls(signed, empty, null).GetEncoded(); + } + + [Fact] + public void Decode_reports_the_claimed_signer_and_verifies_a_normal_certrep() { + BouncyCastleScepCrypto crypto; + ScepCa ca; + IScepKey client_key; + byte[] cert_rep; + PkiMessage msg; + string error; + + cert_rep = BuildCertRep(out crypto, out ca, out client_key); + + Assert.True(crypto.DecodePkiMessage(cert_rep, client_key, CodecOptions.LenientParsing, null, out msg, out error), error); + Assert.True(msg.SignatureValid); + Assert.False(string.IsNullOrEmpty(msg.SignerClaimedIdentity)); + } + + [Fact] + public void Verification_falls_back_to_the_GetCACert_bundle_when_the_signer_cert_is_absent() { + BouncyCastleScepCrypto crypto; + ScepCa ca; + IScepKey client_key; + byte[] cert_rep; + byte[] stripped; + PkiMessage without; + PkiMessage with; + string error; + string notes; + + cert_rep = BuildCertRep(out crypto, out ca, out client_key); + stripped = StripCertificates(cert_rep); + + // No certs embedded and no known certs -> can't verify (the original false-negative finding). + Assert.True(crypto.DecodePkiMessage(stripped, client_key, CodecOptions.LenientParsing, null, out without, out error), error); + Assert.False(without.SignatureValid); + + // Hand it the GetCACert bundle -> it finds the real signer and verifies. + Assert.True(crypto.DecodePkiMessage(stripped, client_key, CodecOptions.LenientParsing, new[] { ca.CertificateBcl }, out with, out error), error); + Assert.True(with.SignatureValid); + Assert.Equal(without.SignerClaimedIdentity, with.SignerClaimedIdentity); + + notes = string.Join(" ", with.ConformanceNotes.ConvertAll(n => n.What)); + Assert.Contains("GetCACert", notes); + } + + [Fact] + public void Unverifiable_signature_note_names_the_claimed_signer_and_what_was_tried() { + BouncyCastleScepCrypto crypto; + ScepCa ca; + IScepKey client_key; + byte[] cert_rep; + byte[] stripped; + PkiMessage msg; + string error; + string notes; + + cert_rep = BuildCertRep(out crypto, out ca, out client_key); + stripped = StripCertificates(cert_rep); + + // Stripped of certs, with an unrelated cert as the only candidate -> nothing verifies. + Assert.True(crypto.DecodePkiMessage(stripped, client_key, CodecOptions.LenientParsing, null, out msg, out error), error); + Assert.False(msg.SignatureValid); + + notes = string.Join(" ", msg.ConformanceNotes.ConvertAll(n => n.What)); + Assert.Contains("claimed signer", notes); + Assert.Contains(msg.SignerClaimedIdentity!, notes); + } +} diff --git a/tests/ScepWright.Tests/CodecOptionsTests.cs b/tests/ScepWright.Tests/CodecOptionsTests.cs index 022bb4e..99b95ed 100644 --- a/tests/ScepWright.Tests/CodecOptionsTests.cs +++ b/tests/ScepWright.Tests/CodecOptionsTests.cs @@ -52,7 +52,7 @@ public void Valid_certrep_decodes_under_strict() { client_cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(issued.GetEncoded()); cert_rep = ca.BuildSuccessCertRep(issued, client_cert, "abc123", new byte[16]); - Assert.True(crypto.DecodePkiMessage(cert_rep, key, CodecOptions.Strict, out decoded, out error), error); + Assert.True(crypto.DecodePkiMessage(cert_rep, key, CodecOptions.Strict, null, out decoded, out error), error); Assert.Equal(PkiStatus.Success, decoded.PkiStatus); } @@ -73,11 +73,11 @@ public void Bad_signature_fails_strict_but_passes_when_relaxed() { bad_sig = BuildFailureCertRep("SHA256WITHRSA", use_wrong_signing_key: true); - Assert.False(crypto.DecodePkiMessage(bad_sig, key, CodecOptions.Strict, out decoded, out strict_error)); + Assert.False(crypto.DecodePkiMessage(bad_sig, key, CodecOptions.Strict, null, out decoded, out strict_error)); Assert.Contains("signature", strict_error.ToLowerInvariant()); - Assert.True(crypto.DecodePkiMessage(bad_sig, key, CodecOptions.SkipSignatureVerification, out decoded, out skip_error), skip_error); - Assert.True(crypto.DecodePkiMessage(bad_sig, key, CodecOptions.LenientParsing, out decoded, out lenient_error), lenient_error); + Assert.True(crypto.DecodePkiMessage(bad_sig, key, CodecOptions.SkipSignatureVerification, null, out decoded, out skip_error), skip_error); + Assert.True(crypto.DecodePkiMessage(bad_sig, key, CodecOptions.LenientParsing, null, out decoded, out lenient_error), lenient_error); } // A SHA-1-signed response: FAILS under Strict (legacy digest), SUCCEEDS with AllowLegacyAlgorithms or @@ -98,11 +98,11 @@ public void Legacy_digest_fails_strict_but_passes_when_relaxed() { sha1_rep = BuildFailureCertRep("SHA1WITHRSA", use_wrong_signing_key: false); - Assert.False(crypto.DecodePkiMessage(sha1_rep, key, CodecOptions.Strict, out decoded, out strict_error)); + Assert.False(crypto.DecodePkiMessage(sha1_rep, key, CodecOptions.Strict, null, out decoded, out strict_error)); Assert.Contains("legacy", strict_error.ToLowerInvariant()); - Assert.True(crypto.DecodePkiMessage(sha1_rep, key, CodecOptions.AllowLegacyAlgorithms, out decoded, out allow_error), allow_error); - Assert.True(crypto.DecodePkiMessage(sha1_rep, key, CodecOptions.LenientParsing, out decoded, out lenient_error), lenient_error); + Assert.True(crypto.DecodePkiMessage(sha1_rep, key, CodecOptions.AllowLegacyAlgorithms, null, out decoded, out allow_error), allow_error); + Assert.True(crypto.DecodePkiMessage(sha1_rep, key, CodecOptions.LenientParsing, null, out decoded, out lenient_error), lenient_error); } // Builds a signed (but NOT enveloped) failure CertRep with a chosen signature algorithm and, optionally, diff --git a/tests/ScepWright.Tests/DomainObjectTests.cs b/tests/ScepWright.Tests/DomainObjectTests.cs index f2c55d0..7a51da9 100644 --- a/tests/ScepWright.Tests/DomainObjectTests.cs +++ b/tests/ScepWright.Tests/DomainObjectTests.cs @@ -17,7 +17,7 @@ private sealed class FakeCrypto : IScepCrypto { public bool EncodePkiMessage(PkiMessage message, FaultDirectives? faults, out byte[] der, out string error) { EncodeCalls++; der = new byte[] { 9, 9 }; error = string.Empty; return true; } - public bool DecodePkiMessage(byte[] der, IScepKey recipientKey, CodecOptions options, out PkiMessage message, out string error) { DecodeCalls++; message = new PkiMessage { MessageType = MessageType.CertRep }; error = string.Empty; return true; } + public bool DecodePkiMessage(byte[] der, IScepKey recipientKey, CodecOptions options, System.Collections.Generic.IReadOnlyList? known_certs, out PkiMessage message, out string error) { DecodeCalls++; message = new PkiMessage { MessageType = MessageType.CertRep }; error = string.Empty; return true; } public bool ParseCaCertificates(byte[] der, out IReadOnlyList certs, out string error) { certs = System.Array.Empty(); error = string.Empty; return true; } diff --git a/tests/ScepWright.Tests/ProviderLoadTests.cs b/tests/ScepWright.Tests/ProviderLoadTests.cs index 7f211fb..80041c0 100644 --- a/tests/ScepWright.Tests/ProviderLoadTests.cs +++ b/tests/ScepWright.Tests/ProviderLoadTests.cs @@ -100,7 +100,7 @@ private abstract class DummyCryptoBase : IScepCrypto { public bool GenerateKey(KeySpec spec, out IScepKey key, out string error) => throw new NotImplementedException(); public bool EncodeCsr(Pkcs10 csr, out byte[] der, out string error) => throw new NotImplementedException(); public bool EncodePkiMessage(PkiMessage message, FaultDirectives? faults, out byte[] der, out string error) => throw new NotImplementedException(); - public bool DecodePkiMessage(byte[] der, IScepKey recipient_key, CodecOptions options, out PkiMessage message, out string error) => throw new NotImplementedException(); + public bool DecodePkiMessage(byte[] der, IScepKey recipient_key, CodecOptions options, System.Collections.Generic.IReadOnlyList? known_certs, out PkiMessage message, out string error) => throw new NotImplementedException(); public bool ParseCaCertificates(byte[] der, out System.Collections.Generic.IReadOnlyList certs, out string error) => throw new NotImplementedException(); public bool ExportPrivateKeyPkcs8(IScepKey key, out byte[] der, out string error) => throw new NotImplementedException(); public bool ImportPrivateKeyPkcs8(byte[] der, out IScepKey key, out string error) => throw new NotImplementedException();