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

Commit 284f8e25 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "v3 keys use SP800 derivation"

parents 626a9730 0d40d6e7
Loading
Loading
Loading
Loading
+82 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.locksettings;

import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

/**
 * Implementation of NIST SP800-108
 * "Recommendation for Key Derivation Using Pseudorandom Functions"
 * Hardcoded:
 * [PRF=HMAC_SHA256]
 * [CTRLOCATION=BEFORE_FIXED]
 * [RLEN=32_BITS]
 * L = 256
 * L suffix: 32 bits
 */
class SP800Derive {
    private final byte[] mKeyBytes;

    SP800Derive(byte[] keyBytes) {
        mKeyBytes = keyBytes;
    }

    private Mac getMac() {
        try {
            final Mac m = Mac.getInstance("HmacSHA256");
            m.init(new SecretKeySpec(mKeyBytes, m.getAlgorithm()));
            return m;
        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    private static void update32(Mac m, int v) {
        m.update(ByteBuffer.allocate(Integer.BYTES).putInt(v).array());
    }

    /**
     *  Generate output from a single, fixed input.
     */
    public byte[] fixedInput(byte[] fixedInput) {
        final Mac m = getMac();
        update32(m, 1); // Hardwired counter value
        m.update(fixedInput);
        return m.doFinal();
    }

    /**
     * Generate output from a label and context. We add a length field at the end of the context to
     * disambiguate it from the length even in the presence of zero bytes.
     */
    public byte[] withContext(byte[] label, byte[] context) {
        final Mac m = getMac();
        // Hardwired counter value: 1
        update32(m, 1); // Hardwired counter value
        m.update(label);
        m.update((byte) 0);
        m.update(context);
        update32(m, context.length * 8); // Disambiguate context
        update32(m, 256); // Hardwired output length
        return m.doFinal();
    }
}
+40 −16
Original line number Diff line number Diff line
@@ -26,9 +26,9 @@ import android.hardware.weaver.V1_0.WeaverConfig;
import android.hardware.weaver.V1_0.WeaverReadResponse;
import android.hardware.weaver.V1_0.WeaverReadStatus;
import android.hardware.weaver.V1_0.WeaverStatus;
import android.security.GateKeeper;
import android.os.RemoteException;
import android.os.UserManager;
import android.security.GateKeeper;
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.util.ArrayMap;
@@ -102,7 +102,8 @@ public class SyntheticPasswordManager {
    private static final int INVALID_WEAVER_SLOT = -1;

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

@@ -128,6 +129,8 @@ public class SyntheticPasswordManager {
    private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
    private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes();
    private static final byte[] PERSONALISATION_WEAVER_TOKEN = "weaver-token".getBytes();
    private static final byte[] PERSONALISATION_CONTEXT =
        "android-synthetic-password-personalization-context".getBytes();

    static class AuthenticationResult {
        public AuthenticationToken authToken;
@@ -136,6 +139,7 @@ public class SyntheticPasswordManager {
    }

    static class AuthenticationToken {
        private final byte mVersion;
        /*
         * Here is the relationship between all three fields:
         * P0 and P1 are two randomly-generated blocks. P1 is stored on disk but P0 is not.
@@ -146,29 +150,38 @@ public class SyntheticPasswordManager {
        private @Nullable byte[] P1;
        private @NonNull String syntheticPassword;

        AuthenticationToken(byte version) {
            mVersion = version;
        }

        private byte[] derivePassword(byte[] personalization) {
            if (mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
                return (new SP800Derive(syntheticPassword.getBytes()))
                    .withContext(personalization, PERSONALISATION_CONTEXT);
            } else {
                return SyntheticPasswordCrypto.personalisedHash(personalization,
                        syntheticPassword.getBytes());
            }
        }

        public String deriveKeyStorePassword() {
            return bytesToHex(SyntheticPasswordCrypto.personalisedHash(
                    PERSONALIZATION_KEY_STORE_PASSWORD, syntheticPassword.getBytes()));
            return bytesToHex(derivePassword(PERSONALIZATION_KEY_STORE_PASSWORD));
        }

        public byte[] deriveGkPassword() {
            return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_SP_GK_AUTH,
                    syntheticPassword.getBytes());
            return derivePassword(PERSONALIZATION_SP_GK_AUTH);
        }

        public byte[] deriveDiskEncryptionKey() {
            return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_FBE_KEY,
                    syntheticPassword.getBytes());
            return derivePassword(PERSONALIZATION_FBE_KEY);
        }

        public byte[] deriveVendorAuthSecret() {
            return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_AUTHSECRET_KEY,
                    syntheticPassword.getBytes());
            return derivePassword(PERSONALIZATION_AUTHSECRET_KEY);
        }

        public byte[] derivePasswordHashFactor() {
            return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_PASSWORD_HASH,
                    syntheticPassword.getBytes());
            return derivePassword(PERSONALIZATION_PASSWORD_HASH);
        }

        private void initialize(byte[] P0, byte[] P1) {
@@ -185,7 +198,7 @@ public class SyntheticPasswordManager {
        }

        protected static AuthenticationToken create() {
            AuthenticationToken result = new AuthenticationToken();
            AuthenticationToken result = new AuthenticationToken(SYNTHETIC_PASSWORD_VERSION_V3);
            result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH),
                    secureRandom(SYNTHETIC_PASSWORD_LENGTH));
            return result;
@@ -802,7 +815,16 @@ public class SyntheticPasswordManager {
        }
        byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid);
        byte[] blob = new byte[content.length + 1 + 1];
        blob[0] = SYNTHETIC_PASSWORD_VERSION;
        /*
         * We can upgrade from v1 to v2 because that's just a change in the way that
         * the SP is stored. However, we can't upgrade to v3 because that is a change
         * in the way that passwords are derived from the SP.
         */
        if (authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
            blob[0] = SYNTHETIC_PASSWORD_VERSION_V3;
        } else {
            blob[0] = SYNTHETIC_PASSWORD_VERSION_V2;
        }
        blob[1] = type;
        System.arraycopy(content, 0, blob, 2, content.length);
        saveState(SP_BLOB_NAME, blob, handle, userId);
@@ -940,7 +962,9 @@ public class SyntheticPasswordManager {
            return null;
        }
        final byte version = blob[0];
        if (version != SYNTHETIC_PASSWORD_VERSION && version != SYNTHETIC_PASSWORD_VERSION_V1) {
        if (version != SYNTHETIC_PASSWORD_VERSION_V3
                && version != SYNTHETIC_PASSWORD_VERSION_V2
                && version != SYNTHETIC_PASSWORD_VERSION_V1) {
            throw new RuntimeException("Unknown blob version");
        }
        if (blob[1] != type) {
@@ -958,7 +982,7 @@ public class SyntheticPasswordManager {
            Log.e(TAG, "Fail to decrypt SP for user " + userId);
            return null;
        }
        AuthenticationToken result = new AuthenticationToken();
        AuthenticationToken result = new AuthenticationToken(version);
        if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
            if (!loadEscrowData(result, userId)) {
                Log.e(TAG, "User is not escrowable: " + userId);
+40 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.locksettings;

import android.test.AndroidTestCase;

import com.android.internal.util.HexDump;

public class SP800DeriveTests extends AndroidTestCase {
    public void testFixedInput() throws Exception {
        // CAVP: https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/key-derivation
        byte[] keyBytes = HexDump.hexStringToByteArray(
            "e204d6d466aad507ffaf6d6dab0a5b26"
            + "152c9e21e764370464e360c8fbc765c6");
        SP800Derive sk = new SP800Derive(keyBytes);
        byte[] fixedInput = HexDump.hexStringToByteArray(
            "7b03b98d9f94b899e591f3ef264b71b1"
            + "93fba7043c7e953cde23bc5384bc1a62"
            + "93580115fae3495fd845dadbd02bd645"
            + "5cf48d0f62b33e62364a3a80");
        byte[] res = sk.fixedInput(fixedInput);
        assertEquals((
                "770dfab6a6a4a4bee0257ff335213f78"
                + "d8287b4fd537d5c1fffa956910e7c779").toUpperCase(), HexDump.toHexString(res));
    }
}