1 /*
2  * Copyright (C) 2018 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 
17 package com.android.server.locksettings.recoverablekeystore;
18 
19 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.RemoteException;
24 import android.os.ServiceSpecificException;
25 import android.security.keystore.recovery.TrustedRootCertificates;
26 import android.util.Log;
27 import android.util.Pair;
28 
29 import com.android.internal.widget.LockPatternUtils;
30 
31 import java.security.cert.X509Certificate;
32 import java.util.Date;
33 import java.util.HashMap;
34 import java.util.Map;
35 
36 import javax.crypto.SecretKey;
37 
38 /**
39  * The class provides helper methods to support end-to-end test with insecure certificate.
40  */
41 public class TestOnlyInsecureCertificateHelper {
42     private static final String TAG = "TestCertHelper";
43 
44     /**
45      * Constructor for the helper class.
46      */
TestOnlyInsecureCertificateHelper()47     public TestOnlyInsecureCertificateHelper() {
48     }
49 
50     /**
51      * Returns a root certificate installed in the system for given alias.
52      * Returns default secure certificate if alias is empty or null.
53      * Can return insecure certificate for its alias.
54      */
55     public @NonNull X509Certificate
getRootCertificate(String rootCertificateAlias)56             getRootCertificate(String rootCertificateAlias) throws RemoteException {
57         rootCertificateAlias = getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
58         if (isTestOnlyCertificateAlias(rootCertificateAlias)) {
59             return TrustedRootCertificates.getTestOnlyInsecureCertificate();
60         }
61 
62         X509Certificate rootCertificate =
63                 TrustedRootCertificates.getRootCertificate(rootCertificateAlias);
64         if (rootCertificate == null) {
65             throw new ServiceSpecificException(
66                     ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid");
67         }
68         return rootCertificate;
69     }
70 
71     /**
72      * Returns hardcoded validation date for e2e tests.
73      */
getValidationDate(String rootCertificateAlias)74     public @Nullable Date getValidationDate(String rootCertificateAlias) {
75         if (isTestOnlyCertificateAlias(rootCertificateAlias)) {
76             // Certificate used for e2e test is expired.
77             return new Date(2019 - 1900, 1, 30);
78         } else {
79             return null; // Use current time
80         }
81     }
82 
getDefaultCertificateAliasIfEmpty( @ullable String rootCertificateAlias)83     public @NonNull String getDefaultCertificateAliasIfEmpty(
84             @Nullable String rootCertificateAlias) {
85         if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
86             Log.e(TAG, "rootCertificateAlias is null or empty - use secure default value");
87             // Use the default Google Key Vault Service CA certificate if the alias is not provided
88             rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
89         }
90         return rootCertificateAlias;
91     }
92 
isTestOnlyCertificateAlias(String rootCertificateAlias)93     public boolean isTestOnlyCertificateAlias(String rootCertificateAlias) {
94         return TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS
95                 .equals(rootCertificateAlias);
96     }
97 
isValidRootCertificateAlias(String rootCertificateAlias)98     public boolean isValidRootCertificateAlias(String rootCertificateAlias) {
99         return TrustedRootCertificates.getRootCertificates().containsKey(rootCertificateAlias)
100                 || isTestOnlyCertificateAlias(rootCertificateAlias);
101     }
102 
103     /**
104      * Checks whether a password is in "Insecure mode"
105      * @param credentialType the type of credential, e.g. pattern and password
106      * @param credential the pattern or password
107      * @return true, if the credential is in "Insecure mode"
108      */
doesCredentialSupportInsecureMode(int credentialType, byte[] credential)109     public boolean doesCredentialSupportInsecureMode(int credentialType, byte[] credential) {
110         if (credential == null) {
111             return false;
112         }
113         if (credentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
114                 && credentialType != LockPatternUtils.CREDENTIAL_TYPE_PIN) {
115             return false;
116         }
117         byte[] insecurePasswordPrefixBytes =
118                 TrustedRootCertificates.INSECURE_PASSWORD_PREFIX.getBytes();
119         if (credential.length < insecurePasswordPrefixBytes.length) {
120             return false;
121         }
122         for (int i = 0; i < insecurePasswordPrefixBytes.length; i++) {
123             if (credential[i] != insecurePasswordPrefixBytes[i]) {
124                 return false;
125             }
126         }
127         return true;
128     }
129 
keepOnlyWhitelistedInsecureKeys( Map<String, Pair<SecretKey, byte[]>> rawKeys)130     public Map<String, Pair<SecretKey, byte[]>> keepOnlyWhitelistedInsecureKeys(
131             Map<String, Pair<SecretKey, byte[]>> rawKeys) {
132         if (rawKeys == null) {
133             return null;
134         }
135         Map<String, Pair<SecretKey, byte[]>> filteredKeys = new HashMap<>();
136         for (Map.Entry<String, Pair<SecretKey, byte[]>> entry : rawKeys.entrySet()) {
137             String alias = entry.getKey();
138             if (alias != null
139                     && alias.startsWith(TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX)) {
140                 filteredKeys.put(entry.getKey(),
141                         Pair.create(entry.getValue().first, entry.getValue().second));
142                 Log.d(TAG, "adding key with insecure alias " + alias + " to the recovery snapshot");
143             }
144         }
145         return filteredKeys;
146     }
147 }
148