/* * Copyright 2012 The Stanford MobiSocial Laboratory * * 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 mobisocial.musubi.ui; import java.io.IOException; import java.security.spec.AlgorithmParameterSpec; import java.util.List; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import mobisocial.crypto.IBHashedIdentity; import mobisocial.crypto.IBHashedIdentity.Authority; import mobisocial.musubi.App; import mobisocial.musubi.encoding.NeedsKey; import mobisocial.musubi.identity.AphidIdentityProvider; import mobisocial.musubi.identity.IdentityProvider; import mobisocial.musubi.model.MEncryptionUserKey; import mobisocial.musubi.model.MIdentity; import mobisocial.musubi.model.MPendingIdentity; import mobisocial.musubi.model.MSignatureUserKey; import mobisocial.musubi.model.helpers.IdentitiesManager; import mobisocial.musubi.model.helpers.PendingIdentityManager; import mobisocial.musubi.model.helpers.UserKeyManager; import mobisocial.musubi.service.MusubiService; import mobisocial.musubi.ui.fragments.AccountLinkDialog; import mobisocial.musubi.util.Base64; import mobisocial.musubi.util.Util; import android.content.Intent; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Toast; /** * Adds user keys for the account verified by the provided uri. */ public class ClaimKeyActivity extends MusubiBaseActivity { public static final String TAG = "ClaimKeyActivity"; private Uri mUri; private IdentitiesManager mIdentitiesManager; private PendingIdentityManager mPendingIdentityManager; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); View view = new View(this); view.setBackgroundColor(Color.TRANSPARENT); setContentView(view); SQLiteOpenHelper databaseSource = App.getDatabaseSource(this); mIdentitiesManager = new IdentitiesManager(databaseSource); mPendingIdentityManager = new PendingIdentityManager(databaseSource); } @Override public void onResume() { super.onResume(); if(getIntent() == null || getIntent().getData() == null) { Toast.makeText(this, "No data.", Toast.LENGTH_SHORT).show(); finish(); return; } // Get and validate uri mUri = getIntent().getData(); Log.i(TAG, "Received intent: " + mUri.toString()); List<String> pathSegments = mUri.getPathSegments(); if (pathSegments.size() < 8) { Toast.makeText(this, "Incomplete data.", Toast.LENGTH_SHORT).show(); finish(); return; } // Parse the data uri for relevant parameters try { int requestId = Integer.parseInt(pathSegments.get(1)); Log.d(TAG, "Request ID: " + requestId); Authority authority = Authority.values()[Integer.parseInt(pathSegments.get(2))]; byte[] hashed = Util.convertToByteArray(pathSegments.get(3)); long timestamp = Long.parseLong(pathSegments.get(4)); IBHashedIdentity hid = new IBHashedIdentity(authority, hashed, timestamp); Log.d(TAG, "Parsed hashed identity: " + hid); byte[] iv = Util.convertToByteArray(pathSegments.get(5)); Log.d(TAG, "iv: " + pathSegments.get(5)); // Base64 can encode slashes, breaking segmentation behavior String combinedKeys = pathSegments.get(7); for (int i = 8; i < pathSegments.size(); i++) { combinedKeys += '/'; combinedKeys += pathSegments.get(i); } int dividerIndex = combinedKeys.indexOf('|'); if (dividerIndex == -1) { throw new IllegalArgumentException(); } String encryptedEk = combinedKeys.substring(0, dividerIndex); String encryptedSk = combinedKeys.substring(dividerIndex + 1, combinedKeys.length()); Log.d(TAG, "Encrypted encryption key: " + encryptedEk); Log.d(TAG, "Encrypted signature key: " + encryptedSk); byte[] key = getKeyForIdentity(hid, requestId); if (key != null) { // Decrypt the keys Cipher cipher = getCipher(key, iv); byte[] ek = decryptClaimedKey(cipher, encryptedEk); byte[] sk = decryptClaimedKey(cipher, encryptedSk); Log.d(TAG, "Encryption key: " + Base64.encodeToString(ek, false)); Log.d(TAG, "Signature key: " + Base64.encodeToString(sk, false)); // Store them somewhere clearPendingIdentity(hid); claimKeys(hid, sk, ek); // Present a familiar UI Intent launch = new Intent(this, SettingsActivity.class); launch.putExtra(SettingsActivity.ACTION, SettingsActivity.SettingsAction.ACCOUNT.toString()); startActivity(launch); } else { Log.w(TAG, "No key available to handle this request"); } } catch (IllegalArgumentException e) { Log.w(TAG, "Could not parse input URL for key claiming"); Toast.makeText(this, "Invalid URL.", Toast.LENGTH_SHORT); finish(); return; } catch (IOException e) { Log.w(TAG, "Could not decrypt user keys"); Toast.makeText(this, "Invalid credentials", Toast.LENGTH_SHORT); } } private void claimKeys(final IBHashedIdentity hid, final byte[] sk, final byte[] ek) { // Do this asynchronously since it's slow and doesn't require user interaction new Thread() { @Override public void run() { MIdentity mid = mIdentitiesManager.getIdentityForIBHashedIdentity(hid); if (mid != null) { // Store user keys if necessary IdentityProvider identityProvider = new AphidIdentityProvider( ClaimKeyActivity.this); UserKeyManager userKeyManager = new UserKeyManager( identityProvider.getEncryptionScheme(), identityProvider.getSignatureScheme(), App.getDatabaseSource(ClaimKeyActivity.this)); // If the identity was not known as owned, it should be now if (!mid.owned_ && mid.principal_ != null) { AccountLinkDialog.addAccountToDatabase( ClaimKeyActivity.this, new AccountLinkDialog.AccountDetails( mid.principal_, mid.principal_, AccountLinkDialog.ACCOUNT_TYPE_PHONE, true)); } // Add keys if they haven't been already try { userKeyManager.getEncryptionKey(mid, hid); } catch (NeedsKey.Encryption e) { MEncryptionUserKey key = new MEncryptionUserKey(); key.identityId_ = mid.id_; key.userKey_ = ek; key.when_ = hid.temporalFrame_; userKeyManager.insertEncryptionUserKey(key); getContentResolver().notifyChange(MusubiService.ENCODED_RECEIVED, null); } try { userKeyManager.getSignatureKey(mid, hid); } catch (NeedsKey.Signature e) { MSignatureUserKey key = new MSignatureUserKey(); key.identityId_ = mid.id_; key.userKey_ = sk; key.when_ = hid.temporalFrame_; userKeyManager.insertSignatureUserKey(key); getContentResolver().notifyChange(MusubiService.PLAIN_OBJ_READY, null); } } } }.start(); } private Cipher getCipher(byte[] aesKey, byte[] iv) throws IOException { Cipher cipher; try { //since the length of the message is not included in the format, we have //to use a normal padding scheme that preserves length cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); } catch (Exception e) { Log.e(TAG, "AES not supported", e); throw new IOException(e); } AlgorithmParameterSpec ivSpec; SecretKeySpec sks; try { ivSpec = new IvParameterSpec(iv); sks = new SecretKeySpec(aesKey, "AES"); cipher.init(Cipher.DECRYPT_MODE, sks, ivSpec); } catch (Exception e) { Log.e(TAG, "Bad iv or key", e); throw new IOException(e); } return cipher; } private byte[] getKeyForIdentity(IBHashedIdentity hid, int requestId) { // Return a key if we were expecting this request MIdentity mid = mIdentitiesManager.getIdentityForIBHashedIdentity(hid); if (mid != null) { MPendingIdentity id = mPendingIdentityManager.lookupIdentity( mid.id_, hid.temporalFrame_, requestId); if (id != null) { return Util.convertToByteArray(id.key_); } } return null; } private void clearPendingIdentity(IBHashedIdentity hid) { MIdentity mid = mIdentitiesManager.getIdentityForIBHashedIdentity(hid); if (mid != null) { mPendingIdentityManager.deleteIdentity(mid.id_, hid.temporalFrame_); } } private byte[] decryptClaimedKey(Cipher cipher, String original) throws IOException { return decryptClaimedKey(cipher, Base64.decode(original)); } private byte[] decryptClaimedKey(Cipher cipher, byte[] original) throws IOException { try { return cipher.doFinal(original); } catch (Exception e) { Log.w(TAG, "Decryption failed", e); throw new IOException(e); } } }