From a71027ff4c86d0ad1cfc5a95b50c66d65ad7e8eb Mon Sep 17 00:00:00 2001 From: harlo Date: Thu, 15 Oct 2015 13:13:57 -0400 Subject: [PATCH 1/2] implemented keystore with backwards-compatibility --- .gitignore | 5 + cameraVApp/src/main/AndroidManifest.xml | 3 +- informaCam/src/main/AndroidManifest.xml | 2 +- .../witness/informacam/crypto/KeyUtility.java | 172 +++++++++++++----- .../informacam/crypto/SignatureService.java | 23 ++- .../witness/informacam/utils/Constants.java | 1 + 6 files changed, 141 insertions(+), 65 deletions(-) diff --git a/.gitignore b/.gitignore index 17aef84..c420b67 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ app/project.properties app/build.xml app/local.properties bin/* +local.properties +.gradle/ +.idea/ +*.iml +*/build/ diff --git a/cameraVApp/src/main/AndroidManifest.xml b/cameraVApp/src/main/AndroidManifest.xml index 3bfcf91..c2872fd 100644 --- a/cameraVApp/src/main/AndroidManifest.xml +++ b/cameraVApp/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ android:versionName="0.2.4"> @@ -22,7 +22,6 @@ - diff --git a/informaCam/src/main/AndroidManifest.xml b/informaCam/src/main/AndroidManifest.xml index 7deffd8..13def3e 100644 --- a/informaCam/src/main/AndroidManifest.xml +++ b/informaCam/src/main/AndroidManifest.xml @@ -4,7 +4,7 @@ android:versionName="1.0" > diff --git a/informaCam/src/main/java/org/witness/informacam/crypto/KeyUtility.java b/informaCam/src/main/java/org/witness/informacam/crypto/KeyUtility.java index 9da708a..e5f39e8 100644 --- a/informaCam/src/main/java/org/witness/informacam/crypto/KeyUtility.java +++ b/informaCam/src/main/java/org/witness/informacam/crypto/KeyUtility.java @@ -5,15 +5,20 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.math.BigInteger; +import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.KeyStore; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; +import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -45,33 +50,37 @@ import org.spongycastle.util.encoders.Hex; import org.witness.informacam.InformaCam; import org.witness.informacam.json.JSONArray; -import org.witness.informacam.json.JSONException; import org.witness.informacam.json.JSONObject; import org.witness.informacam.json.JSONTokener; import org.witness.informacam.models.credentials.IKeyStore; import org.witness.informacam.models.credentials.ISecretKey; -import org.witness.informacam.models.notifications.INotification; import org.witness.informacam.models.organizations.IOrganization; import org.witness.informacam.storage.FormUtility; import org.witness.informacam.storage.IOUtility; +import org.witness.informacam.utils.Constants.Logger; import org.witness.informacam.utils.Constants.App; import org.witness.informacam.utils.Constants.App.Storage; import org.witness.informacam.utils.Constants.App.Storage.Type; import org.witness.informacam.utils.Constants.Codes; import org.witness.informacam.utils.Constants.IManifest; -import org.witness.informacam.utils.Constants.Models; import org.witness.informacam.utils.Constants.Models.ICredentials; import org.witness.informacam.utils.Constants.Models.IUser; import android.os.Bundle; import android.os.Message; +import android.security.KeyPairGeneratorSpec; import android.util.Base64; import android.util.Log; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.security.auth.x500.X500Principal; + public class KeyUtility { private final static String LOG = App.Crypto.LOG; - + public static String getFingerprintFromKey(byte[] keyblock) throws IOException, PGPException { PGPPublicKey key = extractPublicKeyFromBytes(keyblock); return new String(Hex.encode(key.getFingerprint())); @@ -112,7 +121,7 @@ public static PGPPublicKey extractPublicKeyFromBytes(byte[] keyBlock) throws IOE key = k; } } - + if(key == null) { throw new IllegalArgumentException("there isn't an encryption key here."); } @@ -132,14 +141,14 @@ public static String generatePassword(byte[] baseBytes) throws NoSuchAlgorithmEx product[b] = (byte) (baseBytes[b] ^ randomBytes[b]); } - // digest to SHA1 string, voila password. - MessageDigest md = MessageDigest.getInstance("SHA-1"); - return Base64.encodeToString(md.digest(product), Base64.DEFAULT); + // digest to SHA-256 string, voila password. + MessageDigest md = MessageDigest.getInstance("SHA-256"); + return new String(Hex.encode(md.digest(product))); } @SuppressWarnings("deprecation") public static boolean initDevice() { - + int progress = 1; Bundle data = new Bundle(); data.putInt(Codes.Extras.MESSAGE_CODE, Codes.Messages.UI.UPDATE); @@ -152,7 +161,7 @@ public static boolean initDevice() { @Override public void onCacheWordUninitialized() { if(firstUse) { - + Log.d(LOG, "INIT: onCacheWordUninitialized()"); try { @@ -166,17 +175,17 @@ public void onCacheWordUninitialized() { } } - + @Override public void onCacheWordOpened() { // there is not credential block, so override this. if(firstUse) { Log.d(LOG, "INIT: onCacheWordOpened()"); - + cacheWord.setTimeout(0); - + informaCam.ioService.initIOCipher(cacheWord.getEncryptionKey()); - + new Thread () { public void run () @@ -190,10 +199,10 @@ public void run () } } }); - + return true; } - + private static void initDeviceAsync (InformaCam informaCam, CredentialManager credMgr) { try { @@ -203,32 +212,32 @@ private static void initDeviceAsync (InformaCam informaCam, CredentialManager cr byte[] baseImageBytes = informaCam.ioService.getBytes(basePath, Storage.Type.INTERNAL_STORAGE); authToken = generatePassword(baseImageBytes); - + String authTokenBlobBytes = new String(credMgr.setAuthToken(authToken)); JSONObject authTokenBlob = (JSONObject) new JSONTokener(authTokenBlobBytes).nextValue(); authTokenBlob.put(ICredentials.PASSWORD_BLOCK, authTokenBlob.getString("value")); authTokenBlob.remove("value"); - + initDeviceKeys(authToken, baseImageBytes); - + if(informaCam.ioService.saveBlob(authTokenBlob.toString().getBytes(), new java.io.File(IUser.CREDENTIALS))) { informaCam.user.setHasCredentials(true); - + } informaCam.initData(); - + for(String s : informaCam.getAssets().list("includedOrganizations")) { - + InputStream ictdIS = informaCam.ioService.getStream("includedOrganizations/" + s, Type.APPLICATION_ASSET); - + byte[] ictdBytes = new byte[ictdIS.available()]; ictdIS.read(ictdBytes); - - + + IOrganization organization = informaCam.installICTD((JSONObject) new JSONTokener(new String(ictdBytes)).nextValue(), informaCam.h, informaCam); if(organization != null && !informaCam.user.isInOfflineMode) { - + /* INotification notification = new INotification(informaCam.getResources().getString(R.string.key_sent), informaCam.getResources().etResources().getString(R.string.you_have_sent_your_credentials_to_x, organization.organizationName), Models.INotification.Type.NEW_KEY); notification.taskComplete = false; @@ -239,8 +248,8 @@ private static void initDeviceAsync (InformaCam informaCam, CredentialManager cr // TransportUtility.initTransport(transportStub); } } - - + + try { for(String s : informaCam.getAssets().list("includedForms")) { InputStream formXML = informaCam.ioService.getStream("includedForms/" + s, Type.APPLICATION_ASSET); @@ -250,22 +259,87 @@ private static void initDeviceAsync (InformaCam informaCam, CredentialManager cr Log.e(LOG, e.toString()); e.printStackTrace(); } - - + + // Tell others we are done! Bundle data = new Bundle(); data.putInt(Codes.Extras.MESSAGE_CODE, org.witness.informacam.utils.Constants.Codes.Messages.UI.REPLACE); - + Message message = new Message(); message.setData(data); - + informaCam.update(data); - + } catch (Exception e) { Log.e(LOG, e.toString(),e); } } - + + public static String unwrapSecretAuthToken(String secretAuthToken) throws GeneralSecurityException, IOException { + final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + + final KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry) keyStore.getEntry(ICredentials.PASSWORD_ALIAS, null); + final KeyPair keyPair = new KeyPair(pke.getCertificate().getPublicKey(), pke.getPrivateKey()); + + final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL"); + cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); + + CipherInputStream cis = new CipherInputStream( + new ByteArrayInputStream(Base64.decode(secretAuthToken.getBytes("UTF-8"), Base64.DEFAULT)), cipher); + + ArrayList a_bytes = new ArrayList(); + int next; + while ((next = cis.read()) != -1) { + a_bytes.add((byte) next); + } + + byte[] bytes = new byte[a_bytes.size()]; + for(int b=0; b publicCredentials = new HashMap(); JSONArray baseImages = informaCam.user.getJSONArray(IUser.PATH_TO_BASE_IMAGE); for(int j=0; j Date: Thu, 15 Oct 2015 13:16:35 -0400 Subject: [PATCH 2/2] backwards-compatibility --- .../witness/informacam/crypto/KeyUtility.java | 18 ++++-- .../informacam/crypto/SignatureService.java | 61 ++++++++++--------- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/informaCam/src/main/java/org/witness/informacam/crypto/KeyUtility.java b/informaCam/src/main/java/org/witness/informacam/crypto/KeyUtility.java index e5f39e8..90cb711 100644 --- a/informaCam/src/main/java/org/witness/informacam/crypto/KeyUtility.java +++ b/informaCam/src/main/java/org/witness/informacam/crypto/KeyUtility.java @@ -150,7 +150,7 @@ public static String generatePassword(byte[] baseBytes) throws NoSuchAlgorithmEx public static boolean initDevice() { int progress = 1; - Bundle data = new Bundle(); + Bundle data = new Bundle(); data.putInt(Codes.Extras.MESSAGE_CODE, Codes.Messages.UI.UPDATE); data.putInt(Codes.Keys.UI.PROGRESS, progress); @@ -302,7 +302,15 @@ public static String unwrapSecretAuthToken(String secretAuthToken) throws Genera return new String(bytes, 0, bytes.length, "UTF-8"); } - private static String wrapSecretAuthToken(String secretAuthToken) throws GeneralSecurityException, IOException { + public static boolean checkForLegacyAuthToken(String secretAuthToken) { + if(secretAuthToken.length() <= 44) { + return true; + } + + return false; + } + + public static String wrapSecretAuthToken(String secretAuthToken) throws GeneralSecurityException, IOException { // encrypt to Android Keystore first. final Calendar start = new GregorianCalendar(); @@ -349,9 +357,9 @@ private static boolean initDeviceKeys (String authToken, byte[] baseImageBytes) data.putInt(Codes.Extras.MESSAGE_CODE, Codes.Messages.UI.UPDATE); data.putInt(Codes.Keys.UI.PROGRESS, progress); - try { + try { - String secretAuthToken, keyStorePassword; + String secretAuthToken, keyStorePassword; progress += 10; data.putInt(Codes.Keys.UI.PROGRESS, progress); @@ -445,7 +453,7 @@ private static boolean initDeviceKeys (String authToken, byte[] baseImageBytes) ISecretKey secretKeyPackage = new ISecretKey(); secretKeyPackage.pgpKeyFingerprint = pgpKeyFingerprint; secretKeyPackage.secretAuthToken = wrapSecretAuthToken(secretAuthToken); - secretKeyPackage.secretKey = Base64.encodeToString(secret.getEncoded(), Base64.DEFAULT); + secretKeyPackage.secretKey = Base64.encodeToString(secret.getEncoded(), Base64.DEFAULT); progress += 10; data.putInt(Codes.Keys.UI.PROGRESS, progress); diff --git a/informaCam/src/main/java/org/witness/informacam/crypto/SignatureService.java b/informaCam/src/main/java/org/witness/informacam/crypto/SignatureService.java index 195f5e9..89441b2 100644 --- a/informaCam/src/main/java/org/witness/informacam/crypto/SignatureService.java +++ b/informaCam/src/main/java/org/witness/informacam/crypto/SignatureService.java @@ -21,8 +21,10 @@ import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSignatureList; import org.spongycastle.openpgp.PGPUtil; +import org.witness.informacam.InformaCam; import org.witness.informacam.models.credentials.ISecretKey; import org.witness.informacam.models.j3m.ILogPack; +import org.witness.informacam.utils.Constants.Models.IUser; import org.witness.informacam.utils.Constants.App.Crypto; import org.witness.informacam.utils.Constants.App.Crypto.Signatures; @@ -40,59 +42,66 @@ public class SignatureService { public SignatureService (Context context) {} - @SuppressWarnings({"deprecation" }) public void initKey(ISecretKey sk) throws PGPException, GeneralSecurityException, IOException { + // if users had a legacy authToken, fix this. Somewhat of a horse-out-of-the-barn situation, but it helps. + if(KeyUtility.checkForLegacyAuthToken(sk.secretAuthToken)) { + sk.secretAuthToken = KeyUtility.wrapSecretAuthToken(sk.secretAuthToken); + + InformaCam.getInstance().ioService.saveBlob( + sk.asJson().toString().getBytes(), + new info.guardianproject.iocipher.File(IUser.SECRET)); + } + // decrypt secretAuthToken with Android Keystore first. authKey = KeyUtility.unwrapSecretAuthToken(sk.secretAuthToken); - secretKey = KeyUtility.extractSecretKey(sk.secretKey.getBytes()); privateKey = secretKey.extractPrivateKey(authKey.toCharArray(), new BouncyCastleProvider()); publicKey = secretKey.getPublicKey(); - - sk = null; + + sk = null; } - + @SuppressWarnings("deprecation") public boolean isVerified(final ILogPack data) throws IOException { try { byte[] signedData = (byte[]) data.remove(Signatures.Keys.SIGNATURE); - ByteArrayInputStream sd = new ByteArrayInputStream(signedData); - + ByteArrayInputStream sd = new ByteArrayInputStream(signedData); + InputStream is = PGPUtil.getDecoderStream(sd); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - + PGPObjectFactory objFactory = new PGPObjectFactory(is); PGPCompressedData cd = (PGPCompressedData) objFactory.nextObject(); - + objFactory = new PGPObjectFactory(cd.getDataStream()); - + PGPOnePassSignatureList sigList_o = (PGPOnePassSignatureList) objFactory.nextObject(); PGPOnePassSignature sig = sigList_o.get(0); - + PGPLiteralData ld = (PGPLiteralData) objFactory.nextObject(); InputStream literalIn = ld.getInputStream(); - + sig.initVerify(publicKey, new BouncyCastleProvider()); - + int read; while((read = literalIn.read()) > 0) { sig.update((byte) read); baos.write(read); } - + PGPSignatureList sigList = (PGPSignatureList) objFactory.nextObject(); - + if(sig.verify(sigList.get(0)) && data.toString().equals(new String(baos.toByteArray()))) { - baos.close(); + baos.close(); return true; } else { baos.close(); return false; } - + } catch (PGPException e) { @@ -102,22 +111,14 @@ public boolean isVerified(final ILogPack data) throws IOException { Log.d(LOG,"SignatureException: " + e.getMessage(),e); return false; } - + } - + public void signData(InputStream is, OutputStream os) throws NoSuchAlgorithmException, SignatureException, PGPException, IOException { - KeyUtility.applySignature(is, os, secretKey, publicKey, privateKey); + KeyUtility.applySignature(is, os, secretKey, publicKey, privateKey); } - + public byte[] signData(final byte[] data) throws NoSuchAlgorithmException, SignatureException, PGPException, IOException { - return KeyUtility.applySignature(data, secretKey, publicKey, privateKey); + return KeyUtility.applySignature(data, secretKey, publicKey, privateKey); } - - public boolean hasSecretKey () - { - return secretKey != null; - } - - - }