diff --git a/src/SignedDataVerifier.cs b/src/SignedDataVerifier.cs index e78458f..676a50b 100644 --- a/src/SignedDataVerifier.cs +++ b/src/SignedDataVerifier.cs @@ -9,12 +9,21 @@ namespace Mimo.AppStoreServerLibrary; public class SignedDataVerifier( - byte[] appleRootCertificates, + byte[][] appleRootCertificates, bool enableOnlineChecks, AppStoreEnvironment environment, string bundleId ) { + // To compatibility with the previous version (<= 0.1.0) of the constructor + public SignedDataVerifier( + byte[] appleRootCertificate, + bool enableOnlineChecks, + AppStoreEnvironment environment, + string bundleId + ) + : this([appleRootCertificate], enableOnlineChecks, environment, bundleId) { } + // It's recommended to reuse the JsonSerializerOptions instance. // https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/configure-options?pivots=dotnet-8-0#reuse-jsonserializeroptions-instances private readonly JsonSerializerOptions jsonSerializerOptions = new() @@ -198,9 +207,6 @@ private bool CheckCertificateChain(X509Certificate2Collection certificates) { var chain = new X509Chain(); - // Apple root intermediate certificate https://www.apple.com/certificateauthority/AppleRootCA-G3.cer - var rootCertificate = new X509Certificate2(appleRootCertificates); - // Configure the chain to check for certificate revocation online. // Online check can result in a longer delay while the certificate authority is contacted. // It's still unclear if OCSP is supported by Apple, see following comment in the Java Library code base : @@ -209,28 +215,31 @@ private bool CheckCertificateChain(X509Certificate2Collection certificates) // The default value for DisableOnlineCertificateRevocationCheck is false chain.ChainPolicy.RevocationMode = enableOnlineChecks ? X509RevocationMode.Online : X509RevocationMode.NoCheck; - // By allowing unknown certificates we can avoid adding the Apple root certificate to the trust store - // It's one less step to take when setting up the environment (docker file) - // Not setting this flag will result in a "Chain validation failed. self-signed certificate - Status : UntrustedRoot" error when the app is running in a container - // See following issue for more details about this flag behavior : https://github.com/dotnet/runtime/issues/26449#issue-558387269 - chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + // We need to set the trust mode to custom root trust so we can add our own root certificate + // This is needed because the root certificate is not in the default trust store + chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; - foreach (X509Certificate2 cert in certificates) + // Add the root certificates to the trust store + foreach (var rootCert in appleRootCertificates) { - //Add the intermediate certificates to the extra store so they can be used to build the chain - chain.ChainPolicy.ExtraStore.Add(cert); + chain.ChainPolicy.CustomTrustStore.Add(new X509Certificate2(rootCert)); } - bool isValid = chain.Build(rootCertificate); + //Add the intermediate certificates to the extra store so they can be used to build the chain + chain.ChainPolicy.ExtraStore.Add(certificates[1]); + + bool isValid = chain.Build(certificates[0]); if (!isValid) { - string message = "Chain validation failed. "; - foreach (X509ChainStatus chainStatus in chain.ChainStatus) - { - message += chainStatus.StatusInformation + " - Status : " + chainStatus.Status; - } - + var message = + "Chain validation failed. " + + string.Join( + " -> ", + chain.ChainStatus.Select(chainStatus => + chainStatus.StatusInformation + " - Status : " + chainStatus.Status + ) + ); throw new VerificationException(message); } diff --git a/tests/SignedDataVerifierTest.cs b/tests/SignedDataVerifierTest.cs index fc583ae..3d5e307 100644 --- a/tests/SignedDataVerifierTest.cs +++ b/tests/SignedDataVerifierTest.cs @@ -51,7 +51,7 @@ public async Task VerifyAndDecode_TestNotification_Success() var dataVerifier = new SignedDataVerifier( Convert.FromBase64String(RootCaBase64Encoded), - true, + false, AppStoreEnvironment.Sandbox, BundleId ); @@ -74,7 +74,7 @@ public async Task VerifyAndDecode_AlgParameterIsUnsupported_Fails() var dataVerifier = new SignedDataVerifier( Convert.FromBase64String(RootCaBase64Encoded), - true, + false, AppStoreEnvironment.Sandbox, BundleId ); @@ -98,7 +98,7 @@ public async Task VerifyAndDecode_JWSIsMissingAPart_Fails() var dataVerifier = new SignedDataVerifier( Convert.FromBase64String(RootCaBase64Encoded), - true, + false, AppStoreEnvironment.Sandbox, BundleId ); @@ -122,7 +122,7 @@ public async Task VerifyAndDecode_Nox5cParameter_Fails() var dataVerifier = new SignedDataVerifier( Convert.FromBase64String(RootCaBase64Encoded), - true, + false, AppStoreEnvironment.Sandbox, BundleId ); @@ -146,7 +146,7 @@ public async Task VerifyAndDecode_ChainCertificateCompromised_Fails() var dataVerifier = new SignedDataVerifier( Convert.FromBase64String(RootCaBase64Encoded), - true, + false, AppStoreEnvironment.Sandbox, BundleId ); @@ -155,7 +155,7 @@ public async Task VerifyAndDecode_ChainCertificateCompromised_Fails() () => dataVerifier.VerifyAndDecodeNotification(testNotificationPayload) ); - Assert.Contains("Payload signature could not be verified", exception.Message); + Assert.Contains("Chain validation failed", exception.Message); } [Fact] @@ -169,7 +169,7 @@ public async Task VerifyAndDecode_InvalidSignature_Fails() var dataVerifier = new SignedDataVerifier( Convert.FromBase64String(RootCaBase64Encoded), - true, + false, AppStoreEnvironment.Sandbox, BundleId ); @@ -198,7 +198,7 @@ public async Task VerifyAndDecode_RenewalInfo_Success() var dataVerifier = new SignedDataVerifier( Convert.FromBase64String(RootCaBase64Encoded), - true, + false, AppStoreEnvironment.Sandbox, BundleId ); @@ -222,12 +222,12 @@ public async Task VerifyAndDecode_TransactionInfo_Success() } */ string didRenewNotificationPayload = await File.ReadAllTextAsync( - "./MockedSignedData/InputFor_VerifyAndDecode_RenewalInfo_Success.txt" + "./MockedSignedData/InputFor_VerifyAndDecode_TransactionInfo_Success.txt" ); var dataVerifier = new SignedDataVerifier( Convert.FromBase64String(RootCaBase64Encoded), - true, + false, AppStoreEnvironment.Sandbox, BundleId ); @@ -250,7 +250,7 @@ public async Task VerifyAndDecode_WrongBundleId_Fails() var dataVerifier = new SignedDataVerifier( Convert.FromBase64String(RootCaBase64Encoded), - true, + false, AppStoreEnvironment.Sandbox, wrongBundleId ); @@ -273,7 +273,7 @@ public async Task VerifyAndDecode_WrongEnvironment_Fails() var dataVerifier = new SignedDataVerifier( Convert.FromBase64String(RootCaBase64Encoded), - true, + false, wrongEnvironment, BundleId );