KeySyncTask.java revision 4a534ecdd326db6557ac5fc275c11e091c306a44
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; 18 19import android.annotation.NonNull; 20import android.content.Context; 21import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; 22import android.util.Log; 23 24import com.android.internal.annotations.VisibleForTesting; 25import com.android.internal.widget.LockPatternUtils; 26import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; 27 28import java.nio.ByteBuffer; 29import java.nio.ByteOrder; 30import java.nio.charset.StandardCharsets; 31import java.security.InvalidKeyException; 32import java.security.KeyStoreException; 33import java.security.MessageDigest; 34import java.security.NoSuchAlgorithmException; 35import java.security.PublicKey; 36import java.security.SecureRandom; 37 38import javax.crypto.KeyGenerator; 39import javax.crypto.SecretKey; 40 41/** 42 * Task to sync application keys to a remote vault service. 43 * 44 * TODO: implement fully 45 */ 46public class KeySyncTask implements Runnable { 47 private static final String TAG = "KeySyncTask"; 48 49 private static final String RECOVERY_KEY_ALGORITHM = "AES"; 50 private static final int RECOVERY_KEY_SIZE_BITS = 256; 51 private static final int SALT_LENGTH_BYTES = 16; 52 private static final int LENGTH_PREFIX_BYTES = Integer.BYTES; 53 private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256"; 54 55 private final Context mContext; 56 private final RecoverableKeyStoreDb mRecoverableKeyStoreDb; 57 private final int mUserId; 58 private final int mCredentialType; 59 private final String mCredential; 60 61 public static KeySyncTask newInstance( 62 Context context, 63 RecoverableKeyStoreDb recoverableKeyStoreDb, 64 int userId, 65 int credentialType, 66 String credential 67 ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException { 68 return new KeySyncTask( 69 context.getApplicationContext(), 70 recoverableKeyStoreDb, 71 userId, 72 credentialType, 73 credential); 74 } 75 76 /** 77 * A new task. 78 * 79 * @param recoverableKeyStoreDb Database where the keys are stored. 80 * @param userId The uid of the user whose profile has been unlocked. 81 * @param credentialType The type of credential - i.e., pattern or password. 82 * @param credential The credential, encoded as a {@link String}. 83 */ 84 @VisibleForTesting 85 KeySyncTask( 86 Context context, 87 RecoverableKeyStoreDb recoverableKeyStoreDb, 88 int userId, 89 int credentialType, 90 String credential) { 91 mContext = context; 92 mRecoverableKeyStoreDb = recoverableKeyStoreDb; 93 mUserId = userId; 94 mCredentialType = credentialType; 95 mCredential = credential; 96 } 97 98 @Override 99 public void run() { 100 try { 101 syncKeys(); 102 } catch (Exception e) { 103 Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e); 104 } 105 } 106 107 private void syncKeys() { 108 byte[] salt = generateSalt(); 109 byte[] localLskfHash = hashCredentials(salt, mCredential); 110 111 // TODO: decrypt local wrapped application keys, ready for sync 112 113 SecretKey recoveryKey; 114 try { 115 recoveryKey = generateRecoveryKey(); 116 } catch (NoSuchAlgorithmException e) { 117 Log.wtf("AES should never be unavailable", e); 118 return; 119 } 120 121 // TODO: encrypt each application key with recovery key 122 123 PublicKey vaultKey = getVaultPublicKey(); 124 125 // TODO: construct vault params and vault metadata 126 byte[] vaultParams = {}; 127 128 byte[] locallyEncryptedRecoveryKey; 129 try { 130 locallyEncryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey( 131 vaultKey, 132 localLskfHash, 133 vaultParams, 134 recoveryKey); 135 } catch (NoSuchAlgorithmException e) { 136 Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e); 137 return; 138 } catch (InvalidKeyException e) { 139 Log.e(TAG,"Could not encrypt with recovery key", e); 140 return; 141 } 142 143 // TODO: send RECOVERABLE_KEYSTORE_SNAPSHOT intent 144 } 145 146 private PublicKey getVaultPublicKey() { 147 // TODO: fill this in 148 throw new UnsupportedOperationException("TODO: get vault public key."); 149 } 150 151 /** 152 * The UI best suited to entering the given lock screen. This is synced with the vault so the 153 * user can be shown the same UI when recovering the vault on another device. 154 * 155 * @return The format - either pattern, pin, or password. 156 */ 157 @VisibleForTesting 158 @KeyStoreRecoveryMetadata.LockScreenUiFormat static int getUiFormat( 159 int credentialType, String credential) { 160 if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { 161 return KeyStoreRecoveryMetadata.TYPE_PATTERN; 162 } else if (isPin(credential)) { 163 return KeyStoreRecoveryMetadata.TYPE_PIN; 164 } else { 165 return KeyStoreRecoveryMetadata.TYPE_PASSWORD; 166 } 167 } 168 169 /** 170 * Generates a salt to include with the lock screen hash. 171 * 172 * @return The salt. 173 */ 174 private byte[] generateSalt() { 175 byte[] salt = new byte[SALT_LENGTH_BYTES]; 176 new SecureRandom().nextBytes(salt); 177 return salt; 178 } 179 180 /** 181 * Returns {@code true} if {@code credential} looks like a pin. 182 */ 183 @VisibleForTesting 184 static boolean isPin(@NonNull String credential) { 185 int length = credential.length(); 186 for (int i = 0; i < length; i++) { 187 if (!Character.isDigit(credential.charAt(i))) { 188 return false; 189 } 190 } 191 return true; 192 } 193 194 /** 195 * Hashes {@code credentials} with the given {@code salt}. 196 * 197 * @return The SHA-256 hash. 198 */ 199 @VisibleForTesting 200 static byte[] hashCredentials(byte[] salt, String credentials) { 201 byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8); 202 ByteBuffer byteBuffer = ByteBuffer.allocate( 203 salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2); 204 byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 205 byteBuffer.putInt(salt.length); 206 byteBuffer.put(salt); 207 byteBuffer.putInt(credentialsBytes.length); 208 byteBuffer.put(credentialsBytes); 209 byte[] bytes = byteBuffer.array(); 210 211 try { 212 return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes); 213 } catch (NoSuchAlgorithmException e) { 214 // Impossible, SHA-256 must be supported on Android. 215 throw new RuntimeException(e); 216 } 217 } 218 219 private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException { 220 KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM); 221 keyGenerator.init(RECOVERY_KEY_SIZE_BITS); 222 return keyGenerator.generateKey(); 223 } 224} 225