diff --git a/src/main/java/com/apple/itunes/storekit/verification/ChainVerifier.java b/src/main/java/com/apple/itunes/storekit/verification/ChainVerifier.java index 0b989725..fcca6fb7 100644 --- a/src/main/java/com/apple/itunes/storekit/verification/ChainVerifier.java +++ b/src/main/java/com/apple/itunes/storekit/verification/ChainVerifier.java @@ -62,6 +62,9 @@ public ChainVerifier(Set rootCertificates) { } public PublicKey verifyChain(String[] certificates, boolean performRevocationChecking, Date effectiveDate) throws VerificationException { + if (certificates == null) { + throw new VerificationException(VerificationStatus.INVALID_CHAIN_LENGTH); + } if (performRevocationChecking && certificates.length > 0) { // If revocation checking is enabled (which also implies effectiveDate is now), check the cache PublicKey cachedKey = getCachedPublicKey(Arrays.asList(certificates)); @@ -77,6 +80,10 @@ public PublicKey verifyChain(String[] certificates, boolean performRevocationChe } PublicKey verifyChainWithoutCaching(String[] certificates, boolean performRevocationChecking, Date effectiveDate) throws VerificationException { + if (certificates.length != EXPECTED_CHAIN_LENGTH) { + throw new VerificationException(VerificationStatus.INVALID_CHAIN_LENGTH); + } + CertificateFactory certificateFactory; CertPathValidator certPathValidator; try { @@ -95,10 +102,6 @@ PublicKey verifyChainWithoutCaching(String[] certificates, boolean performRevoca throw new VerificationException(VerificationStatus.INVALID_CERTIFICATE, e); } - if (parsedCertificates.size() != EXPECTED_CHAIN_LENGTH) { - throw new VerificationException(VerificationStatus.INVALID_CHAIN_LENGTH); - } - try { //We don't include the root cert in the path, due to OCSP not being supported CertPath certPath = certificateFactory.generateCertPath(parsedCertificates.subList(0, EXPECTED_CHAIN_LENGTH - 1)); diff --git a/src/test/java/com/apple/itunes/storekit/verification/ChainVerifierTest.java b/src/test/java/com/apple/itunes/storekit/verification/ChainVerifierTest.java index 34fbea44..c83b0a04 100644 --- a/src/test/java/com/apple/itunes/storekit/verification/ChainVerifierTest.java +++ b/src/test/java/com/apple/itunes/storekit/verification/ChainVerifierTest.java @@ -14,10 +14,13 @@ import java.security.PublicKey; import java.security.cert.CertPathValidator; import java.security.cert.CertPathValidatorException; +import java.security.cert.CertificateFactory; import java.time.Clock; import java.time.Instant; +import java.util.Arrays; import java.util.Base64; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.Set; @@ -105,6 +108,32 @@ public void testInvalidChainLength() { Assertions.assertEquals(VerificationStatus.INVALID_CHAIN_LENGTH, exception.getStatus()); } + @Test + public void testInvalidChainLengthsAreRejectedBeforeParsingCertificates() throws Exception { + ChainVerifier verifier = getChainVerifier(ROOT_CA_BASE64_ENCODED); + CertificateFactory certificateFactory = Mockito.mock(CertificateFactory.class); + String[] oversizedChain = new String[1_000]; + Arrays.fill(oversizedChain, ROOT_CA_BASE64_ENCODED); + + try (MockedStatic mockedCertificateFactory = Mockito.mockStatic(CertificateFactory.class)) { + mockedCertificateFactory.when(() -> CertificateFactory.getInstance("X.509")).thenReturn(certificateFactory); + + for (String[] invalidChain : List.of( + new String[0], + new String[] {ROOT_CA_BASE64_ENCODED}, + new String[] {ROOT_CA_BASE64_ENCODED, ROOT_CA_BASE64_ENCODED}, + new String[] {ROOT_CA_BASE64_ENCODED, ROOT_CA_BASE64_ENCODED, ROOT_CA_BASE64_ENCODED, ROOT_CA_BASE64_ENCODED}, + oversizedChain)) { + VerificationException exception = Assertions.assertThrows( + VerificationException.class, + () -> verifier.verifyChain(invalidChain, false, EFFECTIVE_DATE)); + Assertions.assertEquals(VerificationStatus.INVALID_CHAIN_LENGTH, exception.getStatus()); + } + + Mockito.verifyNoInteractions(certificateFactory); + } + } + @Test public void testMalformedBase64InCertificateList() { ChainVerifier verifier = getChainVerifier(ROOT_CA_BASE64_ENCODED);