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