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

Commit 8c52865a authored by Rubin Xu's avatar Rubin Xu Committed by Paul Crowley
Browse files

Swap the order of synthetic password wrapping

Synthetic password is double encrypted by both a random auth-bound keymaster
key and a secret derived from user password. In order to avoid a password
verification oracle without rate limiting, synthetic password needs to be
encrypted by the derived secret first, and then the auth-bound key. This
change corrects the order of encryptions, as well as adds an upgrade path to
refresh existing credentials.

Test: Running an old build with existing password, flash to new build,
      verify the device unlocks successfully.
Bug: 68694819

Change-Id: Ifdaa01f3f4ddd5bb3f3d808d38f440ced729034f
parent 680130d1
Loading
Loading
Loading
Loading
+17 −4
Original line number Diff line number Diff line
@@ -112,7 +112,7 @@ public class SyntheticPasswordCrypto {
        }
    }

    public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
    public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
        try {
            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);
@@ -120,6 +120,20 @@ public class SyntheticPasswordCrypto {
            SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
            byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
            return decrypt(decryptionKey, intermediate);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Failed to decrypt blob", e);
        }
    }

    public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
        try {
            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);

            SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
            byte[] intermediate = decrypt(decryptionKey, blob);
            return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
        } catch (CertificateException | IOException | BadPaddingException
                | IllegalBlockSizeException
                | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
@@ -150,9 +164,8 @@ public class SyntheticPasswordCrypto {
            keyStore.setEntry(keyAlias,
                    new KeyStore.SecretKeyEntry(secretKey),
                    builder.build());
            byte[] intermediate = encrypt(secretKey, data);
            return encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);

            byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
            return encrypt(secretKey, intermediate);
        } catch (CertificateException | IOException | BadPaddingException
                | IllegalBlockSizeException
                | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
+21 −6
Original line number Diff line number Diff line
@@ -101,7 +101,8 @@ public class SyntheticPasswordManager {
    private static final byte WEAVER_VERSION = 1;
    private static final int INVALID_WEAVER_SLOT = -1;

    private static final byte SYNTHETIC_PASSWORD_VERSION = 1;
    private static final byte SYNTHETIC_PASSWORD_VERSION_V1 = 1;
    private static final byte SYNTHETIC_PASSWORD_VERSION = 2;
    private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
    private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;

@@ -792,6 +793,7 @@ public class SyntheticPasswordManager {
        byte[] pwdToken = computePasswordToken(credential, pwd);

        final byte[] applicationId;
        final long sid;
        int weaverSlot = loadWeaverSlot(handle, userId);
        if (weaverSlot != INVALID_WEAVER_SLOT) {
            // Weaver based user password
@@ -804,6 +806,7 @@ public class SyntheticPasswordManager {
            if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
                return result;
            }
            sid = GateKeeper.INVALID_SECURE_USER_ID;
            applicationId = transformUnderWeaverSecret(pwdToken, result.gkResponse.getPayload());
        } else {
            byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
@@ -836,12 +839,13 @@ public class SyntheticPasswordManager {
                result.gkResponse = VerifyCredentialResponse.ERROR;
                return result;
            }
            sid = sidFromPasswordHandle(pwd.passwordHandle);
            applicationId = transformUnderSecdiscardable(pwdToken,
                    loadSecdiscardable(handle, userId));
        }

        result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
                applicationId, userId);
                applicationId, sid, userId);

        // Perform verifyChallenge to refresh auth tokens for GK if user password exists.
        result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
@@ -877,7 +881,7 @@ public class SyntheticPasswordManager {
        }
        byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
        result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
                applicationId, userId);
                applicationId, 0L, userId);
        if (result.authToken != null) {
            result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
            if (result.gkResponse == null) {
@@ -892,19 +896,26 @@ public class SyntheticPasswordManager {
    }

    private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
            byte[] applicationId, int userId) {
            byte[] applicationId, long sid, int userId) {
        byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
        if (blob == null) {
            return null;
        }
        if (blob[0] != SYNTHETIC_PASSWORD_VERSION) {
        final byte version = blob[0];
        if (version != SYNTHETIC_PASSWORD_VERSION && version != SYNTHETIC_PASSWORD_VERSION_V1) {
            throw new RuntimeException("Unknown blob version");
        }
        if (blob[1] != type) {
            throw new RuntimeException("Invalid blob type");
        }
        byte[] secret = decryptSPBlob(getHandleName(handle),
        final byte[] secret;
        if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
            secret = SyntheticPasswordCrypto.decryptBlobV1(getHandleName(handle),
                    Arrays.copyOfRange(blob, 2, blob.length), applicationId);
        } else {
            secret = decryptSPBlob(getHandleName(handle),
                Arrays.copyOfRange(blob, 2, blob.length), applicationId);
        }
        if (secret == null) {
            Log.e(TAG, "Fail to decrypt SP for user " + userId);
            return null;
@@ -919,6 +930,10 @@ public class SyntheticPasswordManager {
        } else {
            result.syntheticPassword = new String(secret);
        }
        if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
            Log.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
            createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
        }
        return result;
    }