Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c37987fe authored by Eric Biggers's avatar Eric Biggers
Browse files

Update synthetic password terminology to match new design doc

Update terminology to eliminate ambiguity and to match
http://go/android-locksettings-design :

- The class that represents a synthetic password is now called
  SyntheticPassword instead of AuthenticationToken.  This eliminates an
  inconsistency and avoids ambiguity with the other types of
  authentication tokens (HardwareAuthTokens and escrow tokens).

- "LSKF" is now used in preference to "password", which could be
  confused with LSKFs of type password and with the many other types of
  password (synthetic, Keystore, Gatekeeper).  "Password" is still used
  in places like "password data", "password metrics", and "password
  history"; renaming those in the design doc and code is left for later.

- The things that protect the SP are now called "SP protectors", or just
  "protectors" when SP is clear from context.  Previously these were
  called "synthetic passwords" (ambiguous with the SP) or "SP blobs"
  (ambiguous with the spblob file, which is just part of a protector).

- The 64-bit integers that identify protectors are now called "protector
  IDs" instead of "synthetic password handles".  This avoids ambiguity
  with the SP's Gatekeeper password handle (which in the code is just
  called a "synthetic password handle"; a later CL might clarify that),
  and it clarifies that the identified items are SP protectors, not SPs.

- The secret that each protector uses to protect the SP is now called
  the "protector secret" instead of the application ID.  This avoids
  ambiguity with the Keystore application ID, which isn't being used and
  is a less intuitive name.

No behavior changes intended, except for some changed log messages.

Test: atest com.android.server.locksettings
Test: Basic manual test of locksettings core functionality: upgraded a
      device that has a pattern set, without wiping userdata; unlocked;
      changed to PIN; rebooted; unlocked; changed to swipe; rebooted;
      changed to password; rebooted; and unlocked.
Change-Id: I564a738119a47a31b4822d26c6405249f8ce1c06
parent 600bc074
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -170,7 +170,7 @@ public class LockPatternUtils {
    public static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
    public static final String SYNTHETIC_PASSWORD_KEY_PREFIX = "synthetic_password_";

    public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle";
    public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
    public static final String PASSWORD_HISTORY_DELIMITER = ",";

    @UnsupportedAppUsage
+6 −6
Original line number Diff line number Diff line
@@ -40,8 +40,8 @@ import java.util.List;
import java.util.Objects;

/**
 * A class representing a lockscreen credential. It can be either an empty password, a pattern
 * or a password (or PIN).
 * A class representing a lockscreen credential, also called a Lock Screen Knowledge Factor (LSKF).
 * It can be a PIN, pattern, password, or none (a.k.a. empty).
 *
 * <p> As required by some security certification, the framework tries its best to
 * remove copies of the lockscreen credential bytes from memory. In this regard, this class
@@ -52,10 +52,10 @@ import java.util.Objects;
 *     // Process the credential in some way
 * }
 * </pre>
 * With this construct, we can guarantee that there will be no copies of the password left in
 * memory when the credential goes out of scope. This should help mitigate certain class of
 * attacks where the attcker gains read-only access to full device memory (cold boot attack,
 * unsecured software/hardware memory dumping interfaces such as JTAG).
 * With this construct, we can guarantee that there will be no copies of the credential left in
 * memory when the object goes out of scope. This should help mitigate certain class of attacks
 * where the attacker gains read-only access to full device memory (cold boot attack, unsecured
 * software/hardware memory dumping interfaces such as JTAG).
 */
public class LockscreenCredential implements Parcelable, AutoCloseable {

+149 −174

File changed.

Preview size limit exceeded, changes collapsed.

+13 −12
Original line number Diff line number Diff line
@@ -380,29 +380,30 @@ class LockSettingsStorage {
        }
    }

    public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) {
    public void writeSyntheticPasswordState(int userId, long protectorId, String name,
            byte[] data) {
        ensureSyntheticPasswordDirectoryForUser(userId);
        writeFile(getSyntheticPasswordStateFileForUser(userId, handle, name), data);
        writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data);
    }

    public byte[] readSyntheticPasswordState(int userId, long handle, String name) {
        return readFile(getSyntheticPasswordStateFileForUser(userId, handle, name));
    public byte[] readSyntheticPasswordState(int userId, long protectorId, String name) {
        return readFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name));
    }

    public void deleteSyntheticPasswordState(int userId, long handle, String name) {
        deleteFile(getSyntheticPasswordStateFileForUser(userId, handle, name));
    public void deleteSyntheticPasswordState(int userId, long protectorId, String name) {
        deleteFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name));
    }

    public Map<Integer, List<Long>> listSyntheticPasswordHandlesForAllUsers(String stateName) {
    public Map<Integer, List<Long>> listSyntheticPasswordProtectorsForAllUsers(String stateName) {
        Map<Integer, List<Long>> result = new ArrayMap<>();
        final UserManager um = UserManager.get(mContext);
        for (UserInfo user : um.getUsers()) {
            result.put(user.id, listSyntheticPasswordHandlesForUser(stateName, user.id));
            result.put(user.id, listSyntheticPasswordProtectorsForUser(stateName, user.id));
        }
        return result;
    }

    public List<Long> listSyntheticPasswordHandlesForUser(String stateName, int userId) {
    public List<Long> listSyntheticPasswordProtectorsForUser(String stateName, int userId) {
        File baseDir = getSyntheticPasswordDirectoryForUser(userId);
        List<Long> result = new ArrayList<>();
        File[] files = baseDir.listFiles();
@@ -415,7 +416,7 @@ class LockSettingsStorage {
                try {
                    result.add(Long.parseUnsignedLong(parts[0], 16));
                } catch (NumberFormatException e) {
                    Slog.e(TAG, "Failed to parse handle " + parts[0]);
                    Slog.e(TAG, "Failed to parse protector ID " + parts[0]);
                }
            }
        }
@@ -435,8 +436,8 @@ class LockSettingsStorage {
        }
    }

    private File getSyntheticPasswordStateFileForUser(int userId, long handle, String name) {
        String fileName = formatSimple("%016x.%s", handle, name);
    private File getSyntheticPasswordStateFileForUser(int userId, long protectorId, String name) {
        String fileName = formatSimple("%016x.%s", protectorId, name);
        return new File(getSyntheticPasswordDirectoryForUser(userId), fileName);
    }

+34 −16
Original line number Diff line number Diff line
@@ -52,7 +52,7 @@ public class SyntheticPasswordCrypto {
    private static final int PROFILE_KEY_IV_SIZE = 12;
    private static final int DEFAULT_TAG_LENGTH_BITS = 128;
    private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
    private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
    private static final byte[] PROTECTOR_SECRET_PERSONALIZATION = "application-id".getBytes();
    // Time between the user credential is verified with GK and the decryption of synthetic password
    // under the auth-bound key. This should always happen one after the other, but give it 15
    // seconds just to be sure.
@@ -127,15 +127,19 @@ public class SyntheticPasswordCrypto {
        }
    }

    public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
    /**
     * Decrypt a legacy SP blob which did the Keystore and software encryption layers in the wrong
     * order.
     */
    public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] protectorSecret) {
        try {
            KeyStore keyStore = getKeyStore();
            SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
            if (decryptionKey == null) {
            SecretKey keyStoreKey = (SecretKey) keyStore.getKey(keyAlias, null);
            if (keyStoreKey == null) {
                throw new IllegalStateException("SP key is missing: " + keyAlias);
            }
            byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
            return decrypt(decryptionKey, intermediate);
            byte[] intermediate = decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, blob);
            return decrypt(keyStoreKey, intermediate);
        } catch (Exception e) {
            Slog.e(TAG, "Failed to decrypt V1 blob", e);
            throw new IllegalStateException("Failed to decrypt blob", e);
@@ -157,16 +161,19 @@ public class SyntheticPasswordCrypto {
        return keyStore;
    }

    public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
    /**
     * Decrypts an SP blob that was created by {@link #createBlob}.
     */
    public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] protectorSecret) {
        try {
            final KeyStore keyStore = getKeyStore();

            SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
            if (decryptionKey == null) {
            SecretKey keyStoreKey = (SecretKey) keyStore.getKey(keyAlias, null);
            if (keyStoreKey == null) {
                throw new IllegalStateException("SP key is missing: " + keyAlias);
            }
            byte[] intermediate = decrypt(decryptionKey, blob);
            return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
            byte[] intermediate = decrypt(keyStoreKey, blob);
            return decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, intermediate);
        } catch (CertificateException | IOException | BadPaddingException
                | IllegalBlockSizeException
                | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
@@ -177,11 +184,22 @@ public class SyntheticPasswordCrypto {
        }
    }

    public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
    /**
     * Creates a new SP blob by encrypting the given data.  Two encryption layers are applied: an
     * inner layer using a hash of protectorSecret as the key, and an outer layer using a new
     * Keystore key with the given alias and optionally bound to a SID.
     *
     * The reason we use a layer of software encryption, instead of using protectorSecret as the
     * applicationId of the Keystore key, is to work around buggy KeyMint implementations that don't
     * cryptographically bind the applicationId to the key.  The Keystore layer has to be the outer
     * layer, so that LSKF verification is ratelimited by Gatekeeper when Weaver is unavailable.
     */
    public static byte[] createBlob(String keyAlias, byte[] data, byte[] protectorSecret,
            long sid) {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
            keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
            SecretKey secretKey = keyGenerator.generateKey();
            SecretKey keyStoreKey = keyGenerator.generateKey();
            final KeyStore keyStore = getKeyStore();
            KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
@@ -194,10 +212,10 @@ public class SyntheticPasswordCrypto {
            }

            keyStore.setEntry(keyAlias,
                    new KeyStore.SecretKeyEntry(secretKey),
                    new KeyStore.SecretKeyEntry(keyStoreKey),
                    builder.build());
            byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
            return encrypt(secretKey, intermediate);
            byte[] intermediate = encrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, data);
            return encrypt(keyStoreKey, intermediate);
        } catch (CertificateException | IOException | BadPaddingException
                | IllegalBlockSizeException
                | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
Loading