1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.locksettings.recoverablekeystore.certificate; 18 19import static com.google.common.truth.Truth.assertThat; 20 21import static java.nio.charset.StandardCharsets.UTF_8; 22 23import static org.testng.Assert.assertThrows; 24import static org.testng.Assert.expectThrows; 25 26import android.support.test.filters.SmallTest; 27import android.support.test.runner.AndroidJUnit4; 28 29import java.io.InputStream; 30import java.security.KeyPairGenerator; 31import java.security.PublicKey; 32import java.security.cert.CertPath; 33import java.security.cert.CertificateFactory; 34import java.security.cert.X509Certificate; 35import java.security.spec.ECGenParameterSpec; 36import java.util.ArrayList; 37import java.util.Arrays; 38import java.util.Base64; 39import java.util.Collections; 40import java.util.List; 41 42import org.junit.Test; 43import org.junit.runner.RunWith; 44import org.w3c.dom.Element; 45 46@SmallTest 47@RunWith(AndroidJUnit4.class) 48public final class CertUtilsTest { 49 50 private static final String XML_STR = "" 51 + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" 52 + "<!-- comment 1 -->" 53 + "<root>\n\n\n\r\r\r" 54 + " <node1>\r\n\r\n" 55 + " node1-1</node1>" 56 + " <!-- comment 2 -->" 57 + " <node1>node1-2" 58 + " \n\r\n\r</node1>" 59 + " <node2>" 60 + " <node1> node2-node1-1</node1>" 61 + " <node1>node2-node1-2 </node1>" 62 + " <!-- comment 3 -->" 63 + " <node1> node2-node1-3 </node1>" 64 + " </node2>" 65 + "</root>"; 66 67 private static final String SIGNED_STR = "abcdefg\n"; 68 private static final String SIGNATURE_BASE64 = "" 69 + "KxBt9B3pwL3/59SrjTJTpuhc9JRxLOUNwNr3J4EEdXj5BqkYOUeXIOjyBGp8XaOnmuW8WmBxhko3" 70 + "yTR3/M9x0/pJuKDgqQSSFG+I56O/IAri7DmMBfY8QqcgiF8RaR86G7mWXUIdu8ixEtpKa//T4bN7" 71 + "c8Txvt96ApAcW0wJDihfCqDEXyi56pFCp+qEZuL4fS8iZtZTUkvxim1tb2/IsZ9OyDd9BWxp+JTs" 72 + "zihzH6xqnUCa1aELSUZnU8OzWGeuKpVDQDbDMtQpcxJ9o+6L6wO5vmQutZAulgw5gRPGhYWVs8+0" 73 + "ATdNEbv8TSomkXkZ3/lMYnmPXKmaHxcP4330DA=="; 74 private static final PublicKey SIGNER_PUBLIC_KEY = TestData.INTERMEDIATE_CA_2.getPublicKey(); 75 76 @Test 77 public void decodeCert_readPemFile_succeeds_singleBlock() throws Exception { 78 InputStream f = TestData.openTestFile("pem/valid-cert.pem"); 79 X509Certificate cert = CertUtils.decodeCert(f); 80 assertThat(cert).isEqualTo(TestData.ROOT_CA_TRUSTED); 81 } 82 83 @Test 84 public void decodeCert_readPemFile_succeeds_multipleBlocks() throws Exception { 85 InputStream in = TestData.openTestFile("pem/valid-cert-multiple-blocks.pem"); 86 X509Certificate cert = CertUtils.decodeCert(in); 87 assertThat(cert).isEqualTo(TestData.ROOT_CA_TRUSTED); 88 } 89 90 @Test 91 public void decodeCert_readPemFile_throwsIfNoBeginEndLines() throws Exception { 92 InputStream in = TestData.openTestFile("pem/invalid-cert-1-no-begin-end.pem"); 93 assertThrows(CertParsingException.class, () -> CertUtils.decodeCert(in)); 94 } 95 96 @Test 97 public void decodeCert_readPemFile_throwsIfEmptyBlock() throws Exception { 98 InputStream in = TestData.openTestFile("pem/invalid-cert-2-empty-block.pem"); 99 assertThrows(CertParsingException.class, () -> CertUtils.decodeCert(in)); 100 } 101 102 @Test 103 public void decodeCert_readPemFile_throwsIfInvalidCert() throws Exception { 104 InputStream in = TestData.openTestFile("pem/invalid-cert-3-invalid-key.pem"); 105 assertThrows(CertParsingException.class, () -> CertUtils.decodeCert(in)); 106 } 107 108 @Test 109 public void decodeCert_readBytes_succeeds() throws Exception { 110 X509Certificate cert = CertUtils.decodeCert(TestData.INTERMEDIATE_CA_2.getEncoded()); 111 assertThat(cert.getIssuerX500Principal().getName()) 112 .isEqualTo("CN=Google CryptAuthVault Intermediate"); 113 } 114 115 @Test 116 public void decodeCert_readBytes_throwsIfInvalidCert() throws Exception { 117 byte[] modifiedCertBytes = TestData.INTERMEDIATE_CA_1.getEncoded(); 118 modifiedCertBytes[0] ^= (byte) 1; 119 assertThrows(CertParsingException.class, () -> CertUtils.decodeCert(modifiedCertBytes)); 120 } 121 122 @Test 123 public void decodeBase64_succeeds() throws Exception { 124 assertThat(CertUtils.decodeBase64("VEVTVA==")).isEqualTo("TEST".getBytes(UTF_8)); 125 } 126 127 @Test 128 public void decodeBase64_succeedsIfEmptyInput() throws Exception { 129 assertThat(CertUtils.decodeBase64("")).hasLength(0); 130 } 131 132 @Test 133 public void decodeBase64_throwsIfInvalidInput() throws Exception { 134 assertThrows(CertParsingException.class, () -> CertUtils.decodeBase64("EVTVA==")); 135 } 136 137 @Test 138 public void getXmlRootNode_succeeds() throws Exception { 139 Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8)); 140 assertThat(root.getTagName()).isEqualTo("root"); 141 } 142 143 @Test 144 public void getXmlRootNode_throwsIfEmptyInput() throws Exception { 145 assertThrows(CertParsingException.class, () -> CertUtils.getXmlRootNode(new byte[0])); 146 } 147 148 @Test 149 public void getXmlNodeContents_singleLevel_succeeds() throws Exception { 150 Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8)); 151 assertThat(CertUtils.getXmlNodeContents(CertUtils.MUST_EXIST_UNENFORCED, root, "node1")) 152 .containsExactly("node1-1", "node1-2"); 153 } 154 155 @Test 156 public void getXmlNodeContents_multipleLevels_succeeds() throws Exception { 157 Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8)); 158 assertThat(CertUtils.getXmlNodeContents(CertUtils.MUST_EXIST_UNENFORCED, root, "node2", "node1")) 159 .containsExactly("node2-node1-1", "node2-node1-2", "node2-node1-3"); 160 } 161 162 @Test 163 public void getXmlNodeContents_mustExistFalse_succeedsIfNotExist() throws Exception { 164 Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8)); 165 assertThat( 166 CertUtils.getXmlNodeContents( 167 CertUtils.MUST_EXIST_UNENFORCED, root, "node2", "node-not-exist")) 168 .isEmpty(); 169 } 170 171 @Test 172 public void getXmlNodeContents_mustExistAtLeastOne_throwsIfNotExist() throws Exception { 173 Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8)); 174 CertParsingException expected = 175 expectThrows( 176 CertParsingException.class, 177 () -> 178 CertUtils.getXmlNodeContents( 179 CertUtils.MUST_EXIST_AT_LEAST_ONE, root, "node2", 180 "node-not-exist")); 181 assertThat(expected.getMessage()).contains("must contain at least one"); 182 } 183 184 @Test 185 public void getXmlNodeContents_mustExistExactlyOne_throwsIfNotExist() throws Exception { 186 Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8)); 187 CertParsingException expected = 188 expectThrows( 189 CertParsingException.class, 190 () -> 191 CertUtils.getXmlNodeContents( 192 CertUtils.MUST_EXIST_EXACTLY_ONE, root, "node-not-exist", 193 "node1")); 194 assertThat(expected.getMessage()).contains("must contain exactly one"); 195 } 196 197 @Test 198 public void getXmlNodeContents_mustExistExactlyOne_throwsIfMultipleExist() throws Exception { 199 Element root = CertUtils.getXmlRootNode(XML_STR.getBytes(UTF_8)); 200 CertParsingException expected = 201 expectThrows( 202 CertParsingException.class, 203 () -> 204 CertUtils.getXmlNodeContents( 205 CertUtils.MUST_EXIST_EXACTLY_ONE, root, "node2", "node1")); 206 assertThat(expected.getMessage()).contains("must contain exactly one"); 207 } 208 209 @Test 210 public void verifyRsaSha256Signature_succeeds() throws Exception { 211 CertUtils.verifyRsaSha256Signature( 212 SIGNER_PUBLIC_KEY, 213 Base64.getDecoder().decode(SIGNATURE_BASE64), 214 SIGNED_STR.getBytes(UTF_8)); 215 } 216 217 @Test 218 public void verifyRsaSha256Signature_throwsIfMismatchSignature() throws Exception { 219 byte[] modifiedBytes = SIGNED_STR.getBytes(UTF_8); 220 modifiedBytes[0] ^= (byte) 1; 221 assertThrows( 222 CertValidationException.class, 223 () -> 224 CertUtils.verifyRsaSha256Signature( 225 SIGNER_PUBLIC_KEY, Base64.getDecoder().decode(SIGNATURE_BASE64), 226 modifiedBytes)); 227 } 228 229 @Test 230 public void verifyRsaSha256Signature_throwsIfWrongKeyType() throws Exception { 231 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); 232 keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1")); 233 PublicKey publicKey = keyPairGenerator.generateKeyPair().getPublic(); 234 assertThrows( 235 CertValidationException.class, 236 () -> 237 CertUtils.verifyRsaSha256Signature( 238 publicKey, 239 Base64.getDecoder().decode(SIGNATURE_BASE64), 240 SIGNED_STR.getBytes(UTF_8))); 241 } 242 243 @Test 244 public void buildCertPath_succeedsWithoutIntermediates() throws Exception { 245 X509Certificate rootCert = TestData.ROOT_CA_TRUSTED; 246 X509Certificate leafCert = TestData.INTERMEDIATE_CA_1; 247 CertPath certPath = CertUtils.buildCertPath( 248 CertUtils.buildPkixParams( 249 TestData.DATE_ALL_CERTS_VALID, rootCert, Collections.emptyList(), 250 leafCert)); 251 assertThat(certPath.getCertificates()).containsExactly( 252 TestData.INTERMEDIATE_CA_1).inOrder(); 253 } 254 255 @Test 256 public void buildCertPath_succeedsWithIntermediates() throws Exception { 257 X509Certificate rootCert = TestData.ROOT_CA_TRUSTED; 258 List<X509Certificate> intermediateCerts = 259 Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2); 260 X509Certificate leafCert = TestData.LEAF_CERT_2; 261 CertPath certPath = 262 CertUtils.buildCertPath( 263 CertUtils.buildPkixParams( 264 TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts, 265 leafCert)); 266 assertThat(certPath.getCertificates()) 267 .containsExactly( 268 TestData.LEAF_CERT_2, TestData.INTERMEDIATE_CA_2, 269 TestData.INTERMEDIATE_CA_1) 270 .inOrder(); 271 } 272 273 @Test 274 public void buildCertPath_succeedsWithIntermediates_ignoreUnrelatedIntermedateCert() 275 throws Exception { 276 X509Certificate rootCert = TestData.ROOT_CA_TRUSTED; 277 List<X509Certificate> intermediateCerts = 278 Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2); 279 X509Certificate leafCert = TestData.LEAF_CERT_1; 280 CertPath certPath = 281 CertUtils.buildCertPath( 282 CertUtils.buildPkixParams( 283 TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts, 284 leafCert)); 285 assertThat(certPath.getCertificates()) 286 .containsExactly(TestData.LEAF_CERT_1, TestData.INTERMEDIATE_CA_1) 287 .inOrder(); 288 } 289 290 @Test 291 public void buildCertPath_throwsIfWrongRootCommonName() throws Exception { 292 X509Certificate rootCert = TestData.ROOT_CA_DIFFERENT_COMMON_NAME; 293 List<X509Certificate> intermediateCerts = 294 Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2); 295 X509Certificate leafCert = TestData.LEAF_CERT_1; 296 297 assertThrows( 298 CertValidationException.class, 299 () -> 300 CertUtils.buildCertPath( 301 CertUtils.buildPkixParams( 302 TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts, 303 leafCert))); 304 } 305 306 @Test 307 public void buildCertPath_throwsIfMissingIntermediateCert() throws Exception { 308 X509Certificate rootCert = TestData.ROOT_CA_DIFFERENT_COMMON_NAME; 309 List<X509Certificate> intermediateCerts = Collections.emptyList(); 310 X509Certificate leafCert = TestData.LEAF_CERT_1; 311 312 assertThrows( 313 CertValidationException.class, 314 () -> 315 CertUtils.buildCertPath( 316 CertUtils.buildPkixParams( 317 TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts, 318 leafCert))); 319 } 320 321 @Test 322 public void validateCertPath_succeeds() throws Exception { 323 X509Certificate rootCert = TestData.ROOT_CA_TRUSTED; 324 List<X509Certificate> intermediateCerts = 325 Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2); 326 X509Certificate leafCert = TestData.LEAF_CERT_2; 327 CertPath certPath = 328 CertUtils.buildCertPath( 329 CertUtils.buildPkixParams( 330 TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts, 331 leafCert)); 332 CertUtils.validateCertPath( 333 TestData.DATE_ALL_CERTS_VALID, TestData.ROOT_CA_TRUSTED, certPath); 334 } 335 336 @Test 337 public void validateCertPath_throwsIfEmptyCertPath() throws Exception { 338 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 339 CertPath emptyCertPath = certFactory.generateCertPath(new ArrayList<X509Certificate>()); 340 CertValidationException expected = 341 expectThrows( 342 CertValidationException.class, 343 () -> CertUtils.validateCertPath(TestData.DATE_ALL_CERTS_VALID, 344 TestData.ROOT_CA_TRUSTED, emptyCertPath)); 345 assertThat(expected.getMessage()).contains("empty"); 346 } 347 348 @Test 349 public void validateCertPath_throwsIfNotValidated() throws Exception { 350 assertThrows( 351 CertValidationException.class, 352 () -> CertUtils.validateCertPath(TestData.DATE_ALL_CERTS_VALID, 353 TestData.ROOT_CA_DIFFERENT_COMMON_NAME, 354 com.android.server.locksettings.recoverablekeystore.TestData.CERT_PATH_1)); 355 } 356 357 @Test 358 public void validateCert_succeeds() throws Exception { 359 X509Certificate rootCert = TestData.ROOT_CA_TRUSTED; 360 List<X509Certificate> intermediateCerts = 361 Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2); 362 X509Certificate leafCert = TestData.LEAF_CERT_2; 363 CertUtils.validateCert(TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts, 364 leafCert); 365 } 366 367 @Test 368 public void validateCert_throwsIfExpired() throws Exception { 369 X509Certificate rootCert = TestData.ROOT_CA_TRUSTED; 370 List<X509Certificate> intermediateCerts = 371 Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2); 372 X509Certificate leafCert = TestData.LEAF_CERT_2; 373 assertThrows( 374 CertValidationException.class, 375 () -> 376 CertUtils.validateCert( 377 TestData.DATE_LEAF_CERT_2_EXPIRED, rootCert, intermediateCerts, 378 leafCert)); 379 } 380 381 @Test 382 public void validateCert_throwsIfWrongRootWithTheSameCommonName() throws Exception { 383 X509Certificate rootCert = TestData.ROOT_CA_DIFFERENT_KEY; 384 List<X509Certificate> intermediateCerts = 385 Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2); 386 X509Certificate leafCert = TestData.LEAF_CERT_2; 387 assertThrows( 388 CertValidationException.class, 389 () -> 390 CertUtils.validateCert( 391 TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts, 392 leafCert)); 393 } 394} 395