Loading services/core/java/com/android/server/locksettings/SP800Derive.java 0 → 100644 +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(); } } services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +40 −16 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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) { Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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) { Loading @@ -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); Loading services/tests/servicestests/src/com/android/server/locksettings/SP800DeriveTests.java 0 → 100644 +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)); } } Loading
services/core/java/com/android/server/locksettings/SP800Derive.java 0 → 100644 +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(); } }
services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +40 −16 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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) { Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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) { Loading @@ -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); Loading
services/tests/servicestests/src/com/android/server/locksettings/SP800DeriveTests.java 0 → 100644 +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)); } }