blob: c8d82cf3c3a2eb0a6cc65ff42c6bb23ef545b1f8 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.appsecurity.cts.v3rotationtests;
import static org.junit.Assert.assertArrayEquals;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.test.AndroidTestCase;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* On-device tests for APK Signature Scheme v3 based signing certificate rotation
*/
public class V3RotationTest extends AndroidTestCase {
private static final String PKG = "android.appsecurity.cts.tinyapp";
private static final String COMPANION_PKG = "android.appsecurity.cts.tinyapp_companion";
private static final String PERMISSION_NAME = "android.appsecurity.cts.tinyapp.perm";
private static final String FIRST_CERT_HEX =
"308203773082025fa0030201020204357f7a97300d06092a864886f70d01010b0500306c310b3009060355"
+ "040613025553310b3009060355040813024341311630140603550407130d4d6f756e7461696e20566965"
+ "773110300e060355040a1307416e64726f69643110300e060355040b1307556e6b6e6f776e3114301206"
+ "03550403130b477265656e2044726f6964301e170d3138303133313138303331345a170d343530363138"
+ "3138303331345a306c310b3009060355040613025553310b300906035504081302434131163014060355"
+ "0407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355"
+ "040b1307556e6b6e6f776e311430120603550403130b477265656e2044726f696430820122300d06092a"
+ "864886f70d01010105000382010f003082010a0282010100bce0517978db6e1eb0255b4704c10fca31b8"
+ "d8b7465d1512a36712f425009143d418743cbde9c9f7df219907fff376e44298462fb14f5dd7d0edd46e"
+ "d764f93e82b277146f100616c4d93e97279aefdcf449a5ff000eb72445816039c7afdcd9340f39fc5f0b"
+ "b4d0e6c4d67a0ec876bf28007cf709667250142385248af89c5fa87b8ef9cc1270577b1721c235bdde97"
+ "87c04536706947193d38d244c8c6590e54c90b3843506b3e19c5f2c22c38a1a2893c749ad4ddce67b86f"
+ "f5dcd7dd0d63265fa50036f4bf31f28f2b4c067fb58c95e09ab6c1686c257c3730616659b352b966ed66"
+ "d93952c60563c9d863aa37c097f646d55c825b450f511eca39198cd70203010001a321301f301d060355"
+ "1d0e04160414831074e3173e51fbd2ac8ed8c6681f61eca81318300d06092a864886f70d01010b050003"
+ "820101001fa412618659f40395e38698cd8b7ca9425f63e231e39a6c8e2909af951bbdc5e6307813e25e"
+ "31502578a56b6e6f08442a400de19449f1c7f87357e55191fd7f30b8735e1298d4c668b9cde563e143d4"
+ "72bf8005443d8ee386f955136ad0aa16dda7f5b977dc0dee858f954aff2ae4d7473259fb1b1acc1d6161"
+ "49b12659371ae0298222974be10817156279c9dd8f6cae5265bf15b63fa9f084e789d06b3a9b86d82749"
+ "c95793acded24b321a31cc20fd12774c7b207325df50c2efcb78a5cf9749f62aafd86d303254880a4476"
+ "a67801452e82d1e6c91aeb97ca837a91bcefd8324fca491de4ef648d80c6d74f4b66533684040ddad550"
+ "0ff140edcdacf0a4";
private static final String SECOND_CERT_HEX =
"3082037b30820263a00302010202044c4a644a300d06092a864886f70d01010b0500306e310b3009060355"
+ "040613025553310b3009060355040813024341311630140603550407130d4d6f756e7461696e20566965"
+ "773110300e060355040a1307416e64726f69643110300e060355040b1307556e6b6e6f776e3116301406"
+ "03550403130d477265656e65722044726f6964301e170d3138303133313138303533385a170d34353036"
+ "31383138303533385a306e310b3009060355040613025553310b30090603550408130243413116301406"
+ "03550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e06"
+ "0355040b1307556e6b6e6f776e311630140603550403130d477265656e65722044726f69643082012230"
+ "0d06092a864886f70d01010105000382010f003082010a0282010100a4f3741edf04e32e7047aaadc2ce"
+ "de0164a2847149aa7fad054dab59e70c1078dd2d5946cec64809f79551395478b08f3e0286d452fa39b9"
+ "e6804e965a44210a61bd5cfe3c8ac0ec86017c0e91260248daa1fd7a11b1b1b519216a40d02db49e754a"
+ "6380357c45ef1531996e4c37c13e2f507f3a9296eb52235248d0172efe4ed7bac537fe2435f03d66c5e3"
+ "e5cbf60d77f3088bc22718ded09009d6414106d1301d1a5abf71aa5dc6469bb73b29b2cc9c09b9c42e8f"
+ "8e81e7fc9b24ad1cb0c3e2e2832d0570bdeb87a3ff8f49aae05b03164fb3c225745b70134c6b7aaf081f"
+ "514058776bd28a0c0a152bf45b3eddc7f2c4aaed4eace0cab67ef4650213303f0203010001a321301f30"
+ "1d0603551d0e04160414f0c3dacf02d9583200d4d386a40341aee82dd859300d06092a864886f70d0101"
+ "0b050003820101003a63a93be986740158574239310e987cdcdc86f735867789c2b56f8ffa7ce901a9f2"
+ "2b0c374d345aa996a37c7474824dbf72186d1eedaa6aae0574fd1996f501def32e62703ee4010a7058df"
+ "f0d608df1176bdbe93ebd3910172fc146307d0961a57d95806eeabc1e54b81273d9449f0f68511288377"
+ "c82f87a032d1379260365ef83c93f1bf4c0af959b426794c558c0357fe61271ed0fb054e916e0bab8600"
+ "3bcc16ce53f7d2c8d05b83072115b0fb2c671c2266813f1a407ca911c47f27665debfa63bc4e4071730a"
+ "a477acbe6eecc8d575511b2b77a3551f040ccf21792f74cd95c84e3ff87ad03851db81d164c836830e31"
+ "8208a52dcdc77e376f73b96d";
// These are the hex encodings of the der form of the pkgsigverify/ec-p256[_2] certs used to
// sign APKs that are part of this test.
private static final String EC_P256_FIRST_CERT_HEX =
"3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a86"
+ "48ce3d04030230123110300e06035504030c0765632d70323536301e170d"
+ "3136303333313134353830365a170d3433303831373134353830365a3012"
+ "3110300e06035504030c0765632d703235363059301306072a8648ce3d02"
+ "0106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2b"
+ "a0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991e"
+ "f0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e"
+ "04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d"
+ "23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c06"
+ "03551d13040530030101ff300a06082a8648ce3d04030203490030460221"
+ "00f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16"
+ "db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0f"
+ "dbb8042cb655aadd";
private static final String EC_P256_SECOND_CERT_HEX =
"3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a86"
+ "48ce3d04030230123110300e06035504030c0765632d70323536301e170d"
+ "3138303731333137343135315a170d3238303731303137343135315a3014"
+ "3112301006035504030c0965632d703235365f323059301306072a8648ce"
+ "3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da9"
+ "3d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc"
+ "271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d060355"
+ "1d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603"
+ "551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc830"
+ "0c0603551d13040530030101ff300a06082a8648ce3d0403020348003045"
+ "02202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb"
+ "99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda1"
+ "00a6fe1a2ab19ff09e";
private static final String EC_P256_THIRD_CERT_HEX =
"3082016e30820115a0030201020209008394f5cad16a89a7300a06082a86"
+ "48ce3d04030230143112301006035504030c0965632d703235365f32301e"
+ "170d3138303731343030303532365a170d3238303731313030303532365a"
+ "30143112301006035504030c0965632d703235365f333059301306072a86"
+ "48ce3d020106082a8648ce3d03010703420004f31e62430e9db6fc5928d9"
+ "75fc4e47419bacfcb2e07c89299e6cd7e344dd21adfd308d58cb49a1a2a3"
+ "fecacceea4862069f30be1643bcc255040d8089dfb3743a350304e301d06"
+ "03551d0e041604146f8d0828b13efaf577fc86b0e99fa3e54bcbcff0301f"
+ "0603551d230418301680147991d92b0208fc448bf506d4efc9fff428cb5e"
+ "5f300c0603551d13040530030101ff300a06082a8648ce3d040302034700"
+ "30440220256bdaa2784c273e4cc291a595a46779dee9de9044dc9f7ab820"
+ "309567df9fe902201a4ad8c69891b5a8c47434fe9540ed1f4979b5fad348"
+ "3f3fa04d5677355a579e";
private static final String EC_P256_FOURTH_CERT_HEX =
"3082017b30820120a00302010202146c8cb8a818433c1e6431fb16fb3ae0"
+ "fb5ad60aa7300a06082a8648ce3d04030230143112301006035504030c09"
+ "65632d703235365f33301e170d3230303531333139313532385a170d3330"
+ "303531313139313532385a30143112301006035504030c0965632d703235"
+ "365f343059301306072a8648ce3d020106082a8648ce3d03010703420004"
+ "db4a60031e79ad49cb759007d6855d4469b91c8bab065434f2fba971ade7"
+ "e4d19599a0f67b5e708cfda7543e5630c3769d37e093640d7c768a15144c"
+ "d0e5dcf4a350304e301d0603551d0e041604146e78970332554336b6ee89"
+ "24eaa70230e393f678301f0603551d230418301680146f8d0828b13efaf5"
+ "77fc86b0e99fa3e54bcbcff0300c0603551d13040530030101ff300a0608"
+ "2a8648ce3d0403020349003046022100ce786e79ec7547446082e9caf910"
+ "614ff80758f9819fb0f148695067abe0fcd4022100a4881e332ddec2116a"
+ "d2b59cf891d0f331ff7e27e77b7c6206c7988d9b539330";
private static final String EC_P256_FIFTH_CERT_HEX =
"3082017930820120a003020102021450e1ee31d9f9259eadd3514a988dfa"
+ "4bf0e7153a300a06082a8648ce3d04030230143112301006035504030c09"
+ "65632d703235365f34301e170d3232303331353031303530385a170d3332"
+ "303331323031303530385a30143112301006035504030c0965632d703235"
+ "365f353059301306072a8648ce3d020106082a8648ce3d03010703420004"
+ "75703c54a432df580e86848817b491ee028324257dc31e891fc4af93d9bd"
+ "4bf026b39c7a145213753c344c2a12056ce7ccc21b40be8f9fad28639dca"
+ "dbe63b4ea350304e301d0603551d0e04160414e8cc32db6a21f86c75f3c1"
+ "96c0b199885498b73b301f0603551d230418301680146e78970332554336"
+ "b6ee8924eaa70230e393f678300c0603551d13040530030101ff300a0608"
+ "2a8648ce3d040302034700304402202ded97f7ddcd3229ad26783436186f"
+ "1e74247a4422baf99f1eeb715dfe7e895502207814248b1b7742f3009602"
+ "bdc96f66529884fc605a070ff25c84648c8fccb44b";
public void testHasPerm() throws Exception {
PackageManager pm = getContext().getPackageManager();
assertTrue(PERMISSION_NAME + " not granted to " + COMPANION_PKG,
pm.checkPermission(PERMISSION_NAME, COMPANION_PKG)
== PackageManager.PERMISSION_GRANTED);
}
public void testHasNoPerm() throws Exception {
PackageManager pm = getContext().getPackageManager();
assertFalse(PERMISSION_NAME + " granted to " + COMPANION_PKG,
pm.checkPermission(PERMISSION_NAME, COMPANION_PKG)
== PackageManager.PERMISSION_GRANTED);
}
public void testGetSignaturesShowsOld() throws Exception {
// to prevent breakage due to signing certificate rotation, we report the oldest signing
// certificate in the now-deprecated PackageInfo signatures field. Make sure that when
// rotating, it still displays the older cert.
PackageManager pm = getContext().getPackageManager();
PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNATURES);
assertNotNull("Failed to get signatures in PackageInfo of " + PKG, pi.signatures);
assertEquals("PackageInfo for " + PKG + "contains multiple entries",
1, pi.signatures.length);
assertEquals("signature mismatch for " + PKG + ", expected old signing certificate",
pi.signatures[0].toCharsString(), FIRST_CERT_HEX);
}
public void testGetSigningCertificatesShowsAll() throws Exception {
// make sure our shiny new GET_SIGNATURES replacement does its job. It should return all of
// the certificates in an app's provided history, including its current one
PackageManager pm = getContext().getPackageManager();
PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
assertNotNull("Failed to get signatures in PackageInfo of " + PKG,
pi.signingInfo);
assertFalse("Multiple signing certificates found in signing certificate history for " + PKG,
pi.signingInfo.hasMultipleSigners());
Signature[] signingHistory = pi.signingInfo.getSigningCertificateHistory();
int numSigningSets = signingHistory.length;
assertEquals("PackageInfo for " + PKG + "contains the wrong number of signing certificat "
+ " rotations. Expected 2 (corresponding to one rotation). Found: "
+ numSigningSets, 2, numSigningSets);
// check to see if we match the certs for which we're looking
boolean matchedFirst = false;
boolean matchedSecond = false;
for (int i = 0; i < numSigningSets; i++) {
String reportedCert = signingHistory[i].toCharsString();
if (FIRST_CERT_HEX.equals(reportedCert)) {
matchedFirst = true;
} else if (SECOND_CERT_HEX.equals(reportedCert)) {
matchedSecond = true;
}
}
assertTrue("Old signing certificate not found for " + PKG + " expected "
+ FIRST_CERT_HEX, matchedFirst);
assertTrue("Current signing certificate not found for " + PKG + " expected "
+ SECOND_CERT_HEX, matchedSecond);
}
public void testGetApkContentsSignersShowsCurrent() throws Exception {
// The SigningInfo instance returned from GET_SIGNING_CERTIFICATES provides an option to
// obtain only the current signer through getApkContentsSigners.
PackageManager pm = getContext().getPackageManager();
PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
assertNotNull("Failed to get signatures in Package Info of " + PKG, pi.signingInfo);
assertFalse("Multiple signing certificates found in signing certificate history for " + PKG,
pi.signingInfo.hasMultipleSigners());
assertExpectedSignatures(pi.signingInfo.getApkContentsSigners(), EC_P256_SECOND_CERT_HEX);
}
public void testGetApkContentsSignersShowsMultipleSigners() throws Exception {
// Similar to the test above when GET_SIGNING_CERTIFICATES is used to obtain the signers
// getApkContentSigners should return all of the current signatures when there are multiple
// V1 / V2 signers.
PackageManager pm = getContext().getPackageManager();
PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
assertNotNull("Failed to get signatures in PackageInfo of " + PKG,
pi.signingInfo);
assertTrue("Multiple signing certificates should have been reported for " + PKG,
pi.signingInfo.hasMultipleSigners());
assertExpectedSignatures(pi.signingInfo.getApkContentsSigners(), EC_P256_FIRST_CERT_HEX,
EC_P256_SECOND_CERT_HEX);
}
public void testGetSigningCertificateHistoryReturnsSignersInOrder() throws Exception {
// The test package used for this should be signed with five keys in the signing lineage,
// and the signatures returned from SigningInfo#getSigningCertificateHistory should be
// returned in their rotated order.
final String[] expectedSignatures = new String[]{
EC_P256_FIRST_CERT_HEX,
EC_P256_SECOND_CERT_HEX,
EC_P256_THIRD_CERT_HEX,
EC_P256_FOURTH_CERT_HEX,
EC_P256_FIFTH_CERT_HEX,
};
PackageManager pm = getContext().getPackageManager();
PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
assertNotNull("Failed to get signatures in PackageInfo of " + PKG,
pi.signingInfo);
String[] actualSignatures = Arrays.stream(pi.signingInfo.getSigningCertificateHistory())
.map(Signature::toCharsString)
.toArray(String[]::new);
assertArrayEquals(expectedSignatures, actualSignatures);
}
public void testHasSigningCertificate() throws Exception {
// make sure that hasSigningCertificate() reports that both certificates in the signing
// history are present
PackageManager pm = getContext().getPackageManager();
byte[] firstCertBytes = fromHexToByteArray(FIRST_CERT_HEX);
assertTrue("Old signing certificate not found for " + PKG,
pm.hasSigningCertificate(PKG, firstCertBytes, PackageManager.CERT_INPUT_RAW_X509));
byte[] secondCertBytes = fromHexToByteArray(SECOND_CERT_HEX);
assertTrue("Current signing certificate not found for " + PKG,
pm.hasSigningCertificate(PKG, secondCertBytes, PackageManager.CERT_INPUT_RAW_X509));
}
public void testHasSigningCertificateSha256() throws Exception {
// make sure that hasSigningCertificate() reports that both certificates in the signing
// history are present
PackageManager pm = getContext().getPackageManager();
byte[] firstCertBytes = computeSha256DigestBytes(fromHexToByteArray(FIRST_CERT_HEX));
assertTrue("Old signing certificate not found for " + PKG,
pm.hasSigningCertificate(PKG, firstCertBytes, PackageManager.CERT_INPUT_SHA256));
byte[] secondCertBytes = computeSha256DigestBytes(fromHexToByteArray(SECOND_CERT_HEX));
assertTrue("Current signing certificate not found for " + PKG,
pm.hasSigningCertificate(PKG, secondCertBytes, PackageManager.CERT_INPUT_SHA256));
}
public void testHasSigningCertificateByUid() throws Exception {
// make sure that hasSigningCertificate() reports that both certificates in the signing
// history are present
PackageManager pm = getContext().getPackageManager();
int uid = pm.getPackageUid(PKG, 0);
byte[] firstCertBytes = fromHexToByteArray(FIRST_CERT_HEX);
assertTrue("Old signing certificate not found for " + PKG,
pm.hasSigningCertificate(uid, firstCertBytes, PackageManager.CERT_INPUT_RAW_X509));
byte[] secondCertBytes = fromHexToByteArray(SECOND_CERT_HEX);
assertTrue("Current signing certificate not found for " + PKG,
pm.hasSigningCertificate(uid, secondCertBytes, PackageManager.CERT_INPUT_RAW_X509));
}
public void testHasSigningCertificateByUidSha256() throws Exception {
// make sure that hasSigningCertificate() reports that both certificates in the signing
// history are present
PackageManager pm = getContext().getPackageManager();
int uid = pm.getPackageUid(PKG, 0);
byte[] firstCertBytes = computeSha256DigestBytes(fromHexToByteArray(FIRST_CERT_HEX));
assertTrue("Old signing certificate not found for " + PKG,
pm.hasSigningCertificate(uid, firstCertBytes, PackageManager.CERT_INPUT_SHA256));
byte[] secondCertBytes = computeSha256DigestBytes(fromHexToByteArray(SECOND_CERT_HEX));
assertTrue("Current signing certificate not found for " + PKG,
pm.hasSigningCertificate(uid, secondCertBytes, PackageManager.CERT_INPUT_SHA256));
}
public void testUsingOriginalSigner() throws Exception {
// Verifies the platform only recognized the original signing key during installation.
PackageManager pm = getContext().getPackageManager();
PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
assertFalse(
"APK is expected to use the original signing key, but past signing certificates "
+ "were reported",
pi.signingInfo.hasPastSigningCertificates());
assertExpectedSignatures(pi.signingInfo.getApkContentsSigners(), EC_P256_FIRST_CERT_HEX);
byte[] secondCertBytes = fromHexToByteArray(EC_P256_SECOND_CERT_HEX);
assertFalse("APK is not expected to have the rotated key in its signing lineage",
pm.hasSigningCertificate(PKG, secondCertBytes, PackageManager.CERT_INPUT_RAW_X509));
}
public void testUsingRotatedSigner() throws Exception {
// Verifies the platform recognized the rotated signing key during installation.
PackageManager pm = getContext().getPackageManager();
PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
assertTrue(
"APK is expected to be signed with the rotated signing key, but past signing "
+ "certificates were not reported",
pi.signingInfo.hasPastSigningCertificates());
assertExpectedSignatures(pi.signingInfo.getApkContentsSigners(), EC_P256_SECOND_CERT_HEX);
assertExpectedSignatures(pi.signingInfo.getSigningCertificateHistory(),
EC_P256_FIRST_CERT_HEX, EC_P256_SECOND_CERT_HEX);
byte[] firstCertBytes = fromHexToByteArray(EC_P256_FIRST_CERT_HEX);
assertTrue("APK is expected to have the original key in its signing lineage",
pm.hasSigningCertificate(PKG, firstCertBytes, PackageManager.CERT_INPUT_RAW_X509));
}
private static byte[] fromHexToByteArray(String str) {
if (str == null || str.length() == 0 || str.length() % 2 != 0) {
return null;
}
final char[] chars = str.toCharArray();
final int charLength = chars.length;
final byte[] bytes = new byte[charLength / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] =
(byte)(((getIndex(chars[i * 2]) << 4) & 0xF0)
| (getIndex(chars[i * 2 + 1]) & 0x0F));
}
return bytes;
}
// copy of ByteStringUtils - lowercase version (to match inputs)
private static int getIndex(char c) {
final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
for (int i = 0; i < HEX_ARRAY.length; i++) {
if (HEX_ARRAY[i] == c) {
return i;
}
}
return -1;
}
private static byte[] computeSha256DigestBytes(byte[] data) throws NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
messageDigest.update(data);
return messageDigest.digest();
}
private static void assertExpectedSignatures(Signature[] signatures,
String... expectedSignatures) throws Exception {
int numSigners = signatures.length;
assertEquals("An unexpected number of signatures was returned, expected "
+ expectedSignatures.length + ", but received " + signatures.length,
expectedSignatures.length, numSigners);
Set<String> expectedSignatureSet = new HashSet<>(Arrays.asList(expectedSignatures));
for (int i = 0; i < numSigners; i++) {
String reportedCert = signatures[i].toCharsString();
// Remove the reported certificate from the set to ensure duplicates are not matched.
if (!expectedSignatureSet.remove(reportedCert)) {
fail("Received an unexpected signature during the test: " + reportedCert);
}
}
}
}