Bug 722430 - Propagate NoSuchAlgorithmException and InvalidKeyException; wrap in CryptoException higher up the call stack. r=rnewman

This commit is contained in:
Richard Newman 2012-02-03 13:09:28 -08:00
parent 0cca0b16db
commit 91bb310434
8 changed files with 176 additions and 218 deletions

View File

@ -1,102 +1,66 @@
/* ***** BEGIN LICENSE BLOCK ***** /* This Source Code Form is subject to the terms of the Mozilla Public
* Version: MPL 1.1/GPL 2.0/LGPL 2.1 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
* * You can obtain one at http://mozilla.org/MPL/2.0/. */
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Android Sync Client.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jason Voll
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.sync.crypto; package org.mozilla.gecko.sync.crypto;
/* /*
* All info in these objects should be decoded (i.e. not BaseXX encoded). * All info in these objects should be decoded (i.e. not BaseXX encoded).
*/ */
public class CryptoInfo { public class CryptoInfo {
private byte[] message; private byte[] message;
private byte[] iv; private byte[] iv;
private byte[] hmac; private byte[] hmac;
private KeyBundle keys; private KeyBundle keys;
/* /*
* Constructor typically used when encrypting * Constructor typically used when encrypting.
*/ */
public CryptoInfo(byte[] message, KeyBundle keys) { public CryptoInfo(byte[] message, KeyBundle keys) {
this.setMessage(message); this.setMessage(message);
this.setKeys(keys); this.setKeys(keys);
} }
/* /*
* Constructor typically used when decrypting * Constructor typically used when decrypting.
*/ */
public CryptoInfo(byte[] message, byte[] iv, byte[] hmac, KeyBundle keys) { public CryptoInfo(byte[] message, byte[] iv, byte[] hmac, KeyBundle keys) {
this.setMessage(message); this.setMessage(message);
this.setIV(iv); this.setIV(iv);
this.setHMAC(hmac); this.setHMAC(hmac);
this.setKeys(keys); this.setKeys(keys);
} }
public byte[] getMessage() { public byte[] getMessage() {
return message; return message;
} }
public void setMessage(byte[] message) { public void setMessage(byte[] message) {
this.message = message; this.message = message;
} }
public byte[] getIV() { public byte[] getIV() {
return iv; return iv;
} }
public void setIV(byte[] iv) { public void setIV(byte[] iv) {
this.iv = iv; this.iv = iv;
} }
public byte[] getHMAC() { public byte[] getHMAC() {
return hmac; return hmac;
} }
public void setHMAC(byte[] hmac) { public void setHMAC(byte[] hmac) {
this.hmac = hmac; this.hmac = hmac;
} }
public KeyBundle getKeys() { public KeyBundle getKeys() {
return keys; return keys;
} }
public void setKeys(KeyBundle keys) {
this.keys = keys;
}
public void setKeys(KeyBundle keys) {
this.keys = keys;
}
} }

View File

@ -56,6 +56,7 @@ import javax.crypto.spec.SecretKeySpec;
import org.mozilla.apache.commons.codec.binary.Base32; import org.mozilla.apache.commons.codec.binary.Base32;
import org.mozilla.apache.commons.codec.binary.Base64; import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.sync.Utils; import org.mozilla.gecko.sync.Utils;
import java.security.InvalidKeyException;
/* /*
* Implements the basic required cryptography options. * Implements the basic required cryptography options.
@ -82,7 +83,6 @@ public class Cryptographer {
cipher.init(Cipher.ENCRYPT_MODE, spec, new IvParameterSpec(info.getIV())); cipher.init(Cipher.ENCRYPT_MODE, spec, new IvParameterSpec(info.getIV()));
} }
} catch (GeneralSecurityException ex) { } catch (GeneralSecurityException ex) {
ex.printStackTrace();
throw new CryptoException(ex); throw new CryptoException(ex);
} }
@ -94,7 +94,13 @@ public class Cryptographer {
info.setIV(cipher.getIV()); info.setIV(cipher.getIV());
// Generate HMAC. // Generate HMAC.
info.setHMAC(generateHMAC(info)); try {
info.setHMAC(generateHMAC(info));
} catch (NoSuchAlgorithmException e) {
throw new CryptoException(e);
} catch (InvalidKeyException e) {
throw new CryptoException(e);
}
return info; return info;
@ -112,8 +118,14 @@ public class Cryptographer {
public static byte[] decrypt(CryptoInfo info) throws CryptoException { public static byte[] decrypt(CryptoInfo info) throws CryptoException {
// Check HMAC. // Check HMAC.
if (!verifyHMAC(info)) { try {
throw new HMACVerificationException(); if (!verifyHMAC(info)) {
throw new HMACVerificationException();
}
} catch (NoSuchAlgorithmException e) {
throw new CryptoException(e);
} catch (InvalidKeyException e) {
throw new CryptoException(e);
} }
Cipher cipher = getCipher(); Cipher cipher = getCipher();
@ -190,7 +202,7 @@ public class Cryptographer {
/* /*
* Helper to verify HMAC Input: CryptoInfo Output: true if HMAC is correct * Helper to verify HMAC Input: CryptoInfo Output: true if HMAC is correct
*/ */
private static boolean verifyHMAC(CryptoInfo bundle) { private static boolean verifyHMAC(CryptoInfo bundle) throws NoSuchAlgorithmException, InvalidKeyException {
byte[] generatedHMAC = generateHMAC(bundle); byte[] generatedHMAC = generateHMAC(bundle);
byte[] expectedHMAC = bundle.getHMAC(); byte[] expectedHMAC = bundle.getHMAC();
boolean eq = Arrays.equals(generatedHMAC, expectedHMAC); boolean eq = Arrays.equals(generatedHMAC, expectedHMAC);
@ -206,7 +218,7 @@ public class Cryptographer {
* Helper to generate HMAC Input: CryptoInfo Output: a generated HMAC for * Helper to generate HMAC Input: CryptoInfo Output: a generated HMAC for
* given cipher text * given cipher text
*/ */
private static byte[] generateHMAC(CryptoInfo bundle) { private static byte[] generateHMAC(CryptoInfo bundle) throws NoSuchAlgorithmException, InvalidKeyException {
Mac hmacHasher = HKDF.makeHMACHasher(bundle.getKeys().getHMACKey()); Mac hmacHasher = HKDF.makeHMACHasher(bundle.getKeys().getHMACKey());
return hmacHasher.doFinal(Base64.encodeBase64(bundle.getMessage())); return hmacHasher.doFinal(Base64.encodeBase64(bundle.getMessage()));
} }

View File

@ -1,39 +1,6 @@
/* ***** BEGIN LICENSE BLOCK ***** /* This Source Code Form is subject to the terms of the Mozilla Public
* Version: MPL 1.1/GPL 2.0/LGPL 2.1 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
* * You can obtain one at http://mozilla.org/MPL/2.0/. */
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Android Sync Client.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jason Voll
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.sync.crypto; package org.mozilla.gecko.sync.crypto;
@ -46,105 +13,99 @@ import javax.crypto.spec.SecretKeySpec;
import org.mozilla.gecko.sync.Utils; import org.mozilla.gecko.sync.Utils;
/* /*
* A standards-compliant implementation of RFC 5869 * A standards-compliant implementation of RFC 5869
* for HMAC-based Key Derivation Function. * for HMAC-based Key Derivation Function.
* HMAC uses HMAC SHA256 standard. * HMAC uses HMAC SHA256 standard.
*/ */
public class HKDF { public class HKDF {
public static String HMAC_ALGORITHM = "hmacSHA256";
/** /**
* Used for conversion in cases in which you *know* the encoding exists. * Used for conversion in cases in which you *know* the encoding exists.
*/ */
public static final byte[] bytes(String in) { public static final byte[] bytes(String in) {
try { try {
return in.getBytes("UTF-8"); return in.getBytes("UTF-8");
} catch (java.io.UnsupportedEncodingException e) { } catch (java.io.UnsupportedEncodingException e) {
return null; return null;
} }
}
public static final int BLOCKSIZE = 256 / 8;
public static final byte[] HMAC_INPUT = bytes("Sync-AES_256_CBC-HMAC256");
/*
* Step 1 of RFC 5869
* Get sha256HMAC Bytes
* Input: salt (message), IKM (input keyring material)
* Output: PRK (pseudorandom key)
*/
public static byte[] hkdfExtract(byte[] salt, byte[] IKM) throws NoSuchAlgorithmException, InvalidKeyException {
return digestBytes(IKM, makeHMACHasher(salt));
}
/*
* Step 2 of RFC 5869.
* Input: PRK from step 1, info, length.
* Output: OKM (output keyring material).
*/
public static byte[] hkdfExpand(byte[] prk, byte[] info, int len) throws NoSuchAlgorithmException, InvalidKeyException {
Mac hmacHasher = makeHMACHasher(prk);
byte[] T = {};
byte[] Tn = {};
int iterations = (int) Math.ceil(((double)len) / ((double)BLOCKSIZE));
for (int i = 0; i < iterations; i++) {
Tn = digestBytes(Utils.concatAll(Tn, info, Utils.hex2Byte(Integer.toHexString(i + 1))),
hmacHasher);
T = Utils.concatAll(T, Tn);
} }
public static final int BLOCKSIZE = 256 / 8; byte[] result = new byte[len];
public static final byte[] HMAC_INPUT = bytes("Sync-AES_256_CBC-HMAC256"); System.arraycopy(T, 0, result, 0, len);
return result;
}
/* /*
* Step 1 of RFC 5869 * Make HMAC key
* Get sha256HMAC Bytes * Input: key (salt)
* Input: salt (message), IKM (input keyring material) * Output: Key HMAC-Key
* Output: PRK (pseudorandom key) */
*/ public static Key makeHMACKey(byte[] key) {
public static byte[] hkdfExtract(byte[] salt, byte[] IKM) { if (key.length == 0) {
return digestBytes(IKM, makeHMACHasher(salt)); key = new byte[BLOCKSIZE];
} }
return new SecretKeySpec(key, HMAC_ALGORITHM);
}
/* /*
* Step 2 of RFC 5869. * Make an HMAC hasher
* Input: PRK from step 1, info, length. * Input: Key hmacKey
* Output: OKM (output keyring material). * Ouput: An HMAC Hasher
*/ */
public static byte[] hkdfExpand(byte[] prk, byte[] info, int len) { public static Mac makeHMACHasher(byte[] key) throws NoSuchAlgorithmException, InvalidKeyException {
Mac hmacHasher = null;
hmacHasher = Mac.getInstance(HMAC_ALGORITHM);
Mac hmacHasher = makeHMACHasher(prk); // If Mac.getInstance doesn't throw NoSuchAlgorithmException, hmacHasher is
// non-null.
assert(hmacHasher != null);
byte[] T = {}; hmacHasher.init(makeHMACKey(key));
byte[] Tn = {}; return hmacHasher;
}
int iterations = (int) Math.ceil(((double)len) / ((double)BLOCKSIZE)); /*
for (int i = 0; i < iterations; i++) { * Hash bytes with given hasher
Tn = digestBytes(Utils.concatAll * Input: message to hash, HMAC hasher
(Tn, info, Utils.hex2Byte(Integer.toHexString(i + 1))), hmacHasher); * Output: hashed byte[].
T = Utils.concatAll(T, Tn); */
} public static byte[] digestBytes(byte[] message, Mac hasher) {
hasher.update(message);
byte[] result = new byte[len]; byte[] ret = hasher.doFinal();
System.arraycopy(T, 0, result, 0, len); hasher.reset();
return result; return ret;
} }
/*
* Make HMAC key
* Input: key (salt)
* Output: Key HMAC-Key
*/
public static Key makeHMACKey(byte[] key) {
if (key.length == 0) {
key = new byte[BLOCKSIZE];
}
return new SecretKeySpec(key, "HmacSHA256");
}
/*
* Make an HMAC hasher
* Input: Key hmacKey
* Ouput: An HMAC Hasher
*/
public static Mac makeHMACHasher(byte[] key) {
Mac hmacHasher = null;
try {
hmacHasher = Mac.getInstance("hmacSHA256");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
try {
hmacHasher.init(makeHMACKey(key));
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return hmacHasher;
}
/*
* Hash bytes with given hasher
* Input: message to hash, HMAC hasher
* Output: hashed byte[].
*/
public static byte[] digestBytes(byte[] message, Mac hasher) {
hasher.update(message);
byte[] ret = hasher.doFinal();
hasher.reset();
return ret;
}
} }

View File

@ -45,6 +45,8 @@ import javax.crypto.Mac;
import org.mozilla.apache.commons.codec.binary.Base64; import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.sync.Utils; import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.crypto.CryptoException;
import java.security.InvalidKeyException;
public class KeyBundle { public class KeyBundle {
@ -86,7 +88,7 @@ public class KeyBundle {
* encryption key and the second iteration the HMAC key. * encryption key and the second iteration the HMAC key.
* *
*/ */
public KeyBundle(String username, String base32SyncKey) { public KeyBundle(String username, String base32SyncKey) throws CryptoException {
if (base32SyncKey == null) { if (base32SyncKey == null) {
throw new IllegalArgumentException("No sync key provided."); throw new IllegalArgumentException("No sync key provided.");
} }
@ -105,7 +107,15 @@ public class KeyBundle {
byte[] syncKey = Utils.decodeFriendlyBase32(base32SyncKey); byte[] syncKey = Utils.decodeFriendlyBase32(base32SyncKey);
byte[] user = username.getBytes(); byte[] user = username.getBytes();
Mac hmacHasher = HKDF.makeHMACHasher(syncKey); Mac hmacHasher;
try {
hmacHasher = HKDF.makeHMACHasher(syncKey);
} catch (NoSuchAlgorithmException e) {
throw new CryptoException(e);
} catch (InvalidKeyException e) {
throw new CryptoException(e);
}
assert(hmacHasher != null); // If makeHMACHasher doesn't throw, then hmacHasher is non-null.
byte[] encrBytes = Utils.concatAll(EMPTY_BYTES, HKDF.HMAC_INPUT, user, ENCR_INPUT_BYTES); byte[] encrBytes = Utils.concatAll(EMPTY_BYTES, HKDF.HMAC_INPUT, user, ENCR_INPUT_BYTES);
byte[] encrKey = HKDF.digestBytes(encrBytes, hmacHasher); byte[] encrKey = HKDF.digestBytes(encrBytes, hmacHasher);

View File

@ -344,7 +344,7 @@ public class SyncCryptographer {
/* /*
* Get the keys needed to encrypt the crypto/keys bundle. * Get the keys needed to encrypt the crypto/keys bundle.
*/ */
public KeyBundle getCryptoKeysBundleKeys() { public KeyBundle getCryptoKeysBundleKeys() throws CryptoException {
return new KeyBundle(username, syncKey); return new KeyBundle(username, syncKey);
} }

View File

@ -74,6 +74,8 @@ import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
import ch.boye.httpclientandroidlib.entity.StringEntity; import ch.boye.httpclientandroidlib.entity.StringEntity;
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient; import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
import ch.boye.httpclientandroidlib.message.BasicHeader; import ch.boye.httpclientandroidlib.message.BasicHeader;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
public class JPakeClient implements JPakeRequestDelegate { public class JPakeClient implements JPakeRequestDelegate {
private static String LOG_TAG = "JPakeClient"; private static String LOG_TAG = "JPakeClient";
@ -474,6 +476,14 @@ public class JPakeClient implements JPakeRequestDelegate {
Log.e(LOG_TAG, "ZKP mismatch"); Log.e(LOG_TAG, "ZKP mismatch");
abort(Constants.JPAKE_ERROR_WRONGMESSAGE); abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
e.printStackTrace(); e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
Log.e(LOG_TAG, "NoSuchAlgorithmException", e);
abort(Constants.JPAKE_ERROR_INTERNAL);
e.printStackTrace();
} catch (InvalidKeyException e) {
Log.e(LOG_TAG, "InvalidKeyException", e);
abort(Constants.JPAKE_ERROR_INTERNAL);
e.printStackTrace();
} }
if (pairWithPin) { // Wait for other device to send verification of keys. if (pairWithPin) { // Wait for other device to send verification of keys.

View File

@ -51,6 +51,7 @@ import org.mozilla.gecko.sync.crypto.HKDF;
import org.mozilla.gecko.sync.crypto.KeyBundle; import org.mozilla.gecko.sync.crypto.KeyBundle;
import android.util.Log; import android.util.Log;
import java.security.InvalidKeyException;
public class JPakeCrypto { public class JPakeCrypto {
private static final String LOG_TAG = "JPakeCrypto"; private static final String LOG_TAG = "JPakeCrypto";
@ -174,7 +175,7 @@ public class JPakeCrypto {
* @throws IncorrectZkpException * @throws IncorrectZkpException
*/ */
public static KeyBundle finalRound(String secret, JPakeParty jp) public static KeyBundle finalRound(String secret, JPakeParty jp)
throws IncorrectZkpException { throws IncorrectZkpException, NoSuchAlgorithmException, InvalidKeyException {
Log.d(LOG_TAG, "Final round started."); Log.d(LOG_TAG, "Final round started.");
BigInteger gb = jp.gx1.multiply(jp.gx2).mod(P).multiply(jp.gx3) BigInteger gb = jp.gx1.multiply(jp.gx2).mod(P).multiply(jp.gx3)
.mod(P); .mod(P);
@ -321,12 +322,12 @@ public class JPakeCrypto {
/* /*
* Helper function to generate encryption key and HMAC from a byte array. * Helper function to generate encryption key and HMAC from a byte array.
*/ */
public static void generateKeyAndHmac(BigInteger k, byte[] encOut, byte[] hmacOut) { public static void generateKeyAndHmac(BigInteger k, byte[] encOut, byte[] hmacOut) throws NoSuchAlgorithmException, InvalidKeyException {
// Generate HMAC and Encryption keys from synckey. // Generate HMAC and Encryption keys from synckey.
byte[] zerokey = new byte[32]; byte[] zerokey = new byte[32];
byte[] prk = HMACSHA256(BigIntegerHelper.BigIntegerToByteArrayWithoutSign(k), zerokey); byte[] prk = HMACSHA256(BigIntegerHelper.BigIntegerToByteArrayWithoutSign(k), zerokey);
byte[] okm = HKDF.hkdfExpand(prk, HKDF.HMAC_INPUT, 32 * 2); byte[] okm = HKDF.hkdfExpand(prk, HKDF.HMAC_INPUT, 32 * 2);
System.arraycopy(okm, 0, encOut, 0, 32); System.arraycopy(okm, 0, encOut, 0, 32);
System.arraycopy(okm, 32, hmacOut, 0, 32); System.arraycopy(okm, 32, hmacOut, 0, 32);
} }

View File

@ -101,7 +101,7 @@ public class BaseResource implements Resource {
} }
public BaseResource(String uri, boolean rewrite) throws URISyntaxException { public BaseResource(String uri, boolean rewrite) throws URISyntaxException {
this(new URI(uri), rewrite); this(new URI(uri), rewrite);
} }
public BaseResource(URI uri, boolean rewrite) { public BaseResource(URI uri, boolean rewrite) {