/* * Copyright (C) 2015 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.anhonesteffort.flock.test.crypto; import android.content.Context; import android.test.AndroidTestCase; import org.anhonesteffort.flock.util.guava.Optional; import org.anhonesteffort.flock.crypto.KeyHelper; import org.anhonesteffort.flock.crypto.KeyStore; import org.anhonesteffort.flock.crypto.MasterCipher; import org.anhonesteffort.flock.util.Base64; import org.anhonesteffort.flock.util.Util; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * rhodey. */ public class KeyHelperTest extends AndroidTestCase { private Context context; @Override protected void setUp() throws Exception { context = this.getContext(); } private byte[] encryptAndEncode(byte[] iv, SecretKey cipherKey, SecretKey macKey, byte[] data) throws IOException, GeneralSecurityException { IvParameterSpec ivSpec = new IvParameterSpec(iv); Cipher encryptingCipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); Mac hmac = Mac.getInstance("HmacSHA256"); encryptingCipher.init(Cipher.ENCRYPT_MODE, cipherKey, ivSpec); hmac.init(macKey); byte[] ciphertext = encryptingCipher.doFinal(data); byte[] mac = hmac.doFinal(Util.combine(new byte[]{MasterCipher.CURRENT_CIPHER_VERSION}, iv, ciphertext)); return Base64.encodeBytesToBytes(Util.combine(new byte[]{MasterCipher.CURRENT_CIPHER_VERSION}, iv, ciphertext, mac)); } public void testKeyHelperGenerateKeyMaterial() throws Exception { KeyHelper.generateAndSaveSaltAndKeyMaterial(context); Optional<byte[]> resultCipherKeyBytes = KeyStore.getCipherKey(context); Optional<byte[]> resultMacKeyBytes = KeyStore.getMacKey(context); Optional<byte[]> resultSaltBytes = KeyStore.getKeyMaterialSalt(context); assertTrue("KeyHelper can generate key material.", resultCipherKeyBytes.get().length > 0 && resultMacKeyBytes.get().length > 0 && resultSaltBytes.get().length > 0 && !Arrays.equals(resultCipherKeyBytes.get(), resultMacKeyBytes.get()) && !Arrays.equals(resultCipherKeyBytes.get(), resultSaltBytes.get()) && !Arrays.equals(resultMacKeyBytes.get(), resultSaltBytes.get())); } public void testKeyHelperMasterCipher() throws Exception { final byte[] cipherKeyBytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; final byte[] macKeyBytes = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; final byte[] plaintext = new byte[] {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; final SecretKey testCipherKey = new SecretKeySpec(cipherKeyBytes, "AES"); final SecretKey testMacKey = new SecretKeySpec(macKeyBytes, "SHA256"); KeyStore.saveCipherKey(context, cipherKeyBytes); KeyStore.saveMacKey(context, macKeyBytes); byte[] encodedKeyHelperResult = KeyHelper.getMasterCipher(context).get().encryptAndEncode(plaintext); byte[] keyHelperResult = Base64.decode(encodedKeyHelperResult); byte[] keyHelperIv = Arrays.copyOfRange(keyHelperResult, 1, 1 + 16); byte[] encodedTestResult = encryptAndEncode(keyHelperIv, testCipherKey, testMacKey, plaintext); byte[] testResult = Base64.decode(encodedTestResult); assertTrue("KeyHelper's MasterCipher works.", new String(testResult).equals(new String(keyHelperResult))); } public void testBuildAndImportEncryptedKeyMaterial() throws Exception { final String masterPassphrase = "oioioi"; final byte[] cipherKeyBytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; final byte[] macKeyBytes = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; final byte[] saltBytes = new byte[] {2, 2, 2, 2, 2, 2, 2, 2}; KeyStore.saveCipherKey(context, cipherKeyBytes); KeyStore.saveMacKey(context, macKeyBytes); KeyStore.saveKeyMaterialSalt(context, saltBytes); KeyStore.saveMasterPassphrase(context, masterPassphrase); Optional<String> encodedSalt = KeyHelper.buildEncodedSalt(context); Optional<String> encryptedKeyMaterial = KeyHelper.buildEncryptedKeyMaterial(context); String[] saltAndEncryptedKeyMaterial = new String[] { encodedSalt.get(), encryptedKeyMaterial.get() }; KeyStore.invalidateKeyMaterial(context); KeyStore.saveMasterPassphrase(context, masterPassphrase); KeyHelper.importSaltAndEncryptedKeyMaterial(context, saltAndEncryptedKeyMaterial); Optional<byte[]> resultCipherKeyBytes = KeyStore.getCipherKey(context); Optional<byte[]> resultMacKeyBytes = KeyStore.getMacKey(context); Optional<byte[]> resultSaltBytes = KeyStore.getKeyMaterialSalt(context); assertTrue("KeyHelper can export and import encrypted key material.", Arrays.equals(resultCipherKeyBytes.get(), cipherKeyBytes) && Arrays.equals(resultMacKeyBytes.get(), macKeyBytes) && Arrays.equals(resultSaltBytes.get(), saltBytes)); } public void testMasterPassphraseIsValid() throws Exception { final String masterPassphrase = "oioioi"; KeyStore.saveMasterPassphrase(context, masterPassphrase); KeyHelper.generateAndSaveSaltAndKeyMaterial(context); assertTrue(KeyHelper.masterPassphraseIsValid(context)); } public void testMasterPassphraseIsInvalid() throws Exception { final String masterPassphrase = "oioioi"; KeyStore.saveMasterPassphrase(context, masterPassphrase); KeyHelper.generateAndSaveSaltAndKeyMaterial(context); KeyStore.saveMasterPassphrase(context, masterPassphrase.concat("nope")); assertTrue(!KeyHelper.masterPassphraseIsValid(context)); } }