Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.gecko.sync.crypto;
7 import java.security.GeneralSecurityException;
8 import java.security.InvalidKeyException;
9 import java.security.NoSuchAlgorithmException;
10 import java.util.Arrays;
12 import javax.crypto.BadPaddingException;
13 import javax.crypto.Cipher;
14 import javax.crypto.IllegalBlockSizeException;
15 import javax.crypto.Mac;
16 import javax.crypto.NoSuchPaddingException;
17 import javax.crypto.spec.IvParameterSpec;
18 import javax.crypto.spec.SecretKeySpec;
20 import org.mozilla.apache.commons.codec.binary.Base64;
22 /*
23 * All info in these objects should be decoded (i.e. not BaseXX encoded).
24 */
25 public class CryptoInfo {
26 private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
27 private static final String KEY_ALGORITHM_SPEC = "AES";
29 private byte[] message;
30 private byte[] iv;
31 private byte[] hmac;
32 private KeyBundle keys;
34 /**
35 * Return a CryptoInfo with given plaintext encrypted using given keys.
36 */
37 public static CryptoInfo encrypt(byte[] plaintextBytes, KeyBundle keys) throws CryptoException {
38 CryptoInfo info = new CryptoInfo(plaintextBytes, keys);
39 info.encrypt();
40 return info;
41 }
43 /**
44 * Return a CryptoInfo with given plaintext encrypted using given keys and initial vector.
45 */
46 public static CryptoInfo encrypt(byte[] plaintextBytes, byte[] iv, KeyBundle keys) throws CryptoException {
47 CryptoInfo info = new CryptoInfo(plaintextBytes, iv, null, keys);
48 info.encrypt();
49 return info;
50 }
52 /**
53 * Return a CryptoInfo with given ciphertext decrypted using given keys and initial vector, verifying that given HMAC validates.
54 */
55 public static CryptoInfo decrypt(byte[] ciphertext, byte[] iv, byte[] hmac, KeyBundle keys) throws CryptoException {
56 CryptoInfo info = new CryptoInfo(ciphertext, iv, hmac, keys);
57 info.decrypt();
58 return info;
59 }
61 /*
62 * Constructor typically used when encrypting.
63 */
64 public CryptoInfo(byte[] message, KeyBundle keys) {
65 this.setMessage(message);
66 this.setKeys(keys);
67 }
69 /*
70 * Constructor typically used when decrypting.
71 */
72 public CryptoInfo(byte[] message, byte[] iv, byte[] hmac, KeyBundle keys) {
73 this.setMessage(message);
74 this.setIV(iv);
75 this.setHMAC(hmac);
76 this.setKeys(keys);
77 }
79 public byte[] getMessage() {
80 return message;
81 }
83 public void setMessage(byte[] message) {
84 this.message = message;
85 }
87 public byte[] getIV() {
88 return iv;
89 }
91 public void setIV(byte[] iv) {
92 this.iv = iv;
93 }
95 public byte[] getHMAC() {
96 return hmac;
97 }
99 public void setHMAC(byte[] hmac) {
100 this.hmac = hmac;
101 }
103 public KeyBundle getKeys() {
104 return keys;
105 }
107 public void setKeys(KeyBundle keys) {
108 this.keys = keys;
109 }
111 /*
112 * Generate HMAC for given cipher text.
113 */
114 public static byte[] generatedHMACFor(byte[] message, KeyBundle keys) throws NoSuchAlgorithmException, InvalidKeyException {
115 Mac hmacHasher = HKDF.makeHMACHasher(keys.getHMACKey());
116 return hmacHasher.doFinal(Base64.encodeBase64(message));
117 }
119 /*
120 * Return true if generated HMAC is the same as the specified HMAC.
121 */
122 public boolean generatedHMACIsHMAC() throws NoSuchAlgorithmException, InvalidKeyException {
123 byte[] generatedHMAC = generatedHMACFor(getMessage(), getKeys());
124 byte[] expectedHMAC = getHMAC();
125 return Arrays.equals(generatedHMAC, expectedHMAC);
126 }
128 /**
129 * Performs functionality common to both encryption and decryption.
130 *
131 * @param cipher
132 * @param inputMessage non-BaseXX-encoded message
133 * @return encrypted/decrypted message
134 * @throws CryptoException
135 */
136 private static byte[] commonCrypto(Cipher cipher, byte[] inputMessage)
137 throws CryptoException {
138 byte[] outputMessage = null;
139 try {
140 outputMessage = cipher.doFinal(inputMessage);
141 } catch (IllegalBlockSizeException e) {
142 throw new CryptoException(e);
143 } catch (BadPaddingException e) {
144 throw new CryptoException(e);
145 }
146 return outputMessage;
147 }
149 /**
150 * Encrypt a CryptoInfo in-place.
151 *
152 * @throws CryptoException
153 */
154 public void encrypt() throws CryptoException {
156 Cipher cipher = CryptoInfo.getCipher(TRANSFORMATION);
157 try {
158 byte[] encryptionKey = getKeys().getEncryptionKey();
159 SecretKeySpec spec = new SecretKeySpec(encryptionKey, KEY_ALGORITHM_SPEC);
161 // If no IV is provided, we allow the cipher to provide one.
162 if (getIV() == null || getIV().length == 0) {
163 cipher.init(Cipher.ENCRYPT_MODE, spec);
164 } else {
165 cipher.init(Cipher.ENCRYPT_MODE, spec, new IvParameterSpec(getIV()));
166 }
167 } catch (GeneralSecurityException ex) {
168 throw new CryptoException(ex);
169 }
171 // Encrypt.
172 byte[] encryptedBytes = commonCrypto(cipher, getMessage());
173 byte[] iv = cipher.getIV();
175 byte[] hmac;
176 // Generate HMAC.
177 try {
178 hmac = generatedHMACFor(encryptedBytes, keys);
179 } catch (NoSuchAlgorithmException e) {
180 throw new CryptoException(e);
181 } catch (InvalidKeyException e) {
182 throw new CryptoException(e);
183 }
185 // Update in place. keys is already set.
186 this.setHMAC(hmac);
187 this.setIV(iv);
188 this.setMessage(encryptedBytes);
189 }
191 /**
192 * Decrypt a CryptoInfo in-place.
193 *
194 * @throws CryptoException
195 */
196 public void decrypt() throws CryptoException {
198 // Check HMAC.
199 try {
200 if (!generatedHMACIsHMAC()) {
201 throw new HMACVerificationException();
202 }
203 } catch (NoSuchAlgorithmException e) {
204 throw new CryptoException(e);
205 } catch (InvalidKeyException e) {
206 throw new CryptoException(e);
207 }
209 Cipher cipher = CryptoInfo.getCipher(TRANSFORMATION);
210 try {
211 byte[] encryptionKey = getKeys().getEncryptionKey();
212 SecretKeySpec spec = new SecretKeySpec(encryptionKey, KEY_ALGORITHM_SPEC);
213 cipher.init(Cipher.DECRYPT_MODE, spec, new IvParameterSpec(getIV()));
214 } catch (GeneralSecurityException ex) {
215 throw new CryptoException(ex);
216 }
217 byte[] decryptedBytes = commonCrypto(cipher, getMessage());
218 byte[] iv = cipher.getIV();
220 // Update in place. keys is already set.
221 this.setHMAC(null);
222 this.setIV(iv);
223 this.setMessage(decryptedBytes);
224 }
226 /**
227 * Helper to get a Cipher object.
228 *
229 * @param transformation The type of Cipher to get.
230 */
231 private static Cipher getCipher(String transformation) throws CryptoException {
232 try {
233 return Cipher.getInstance(transformation);
234 } catch (NoSuchAlgorithmException e) {
235 throw new CryptoException(e);
236 } catch (NoSuchPaddingException e) {
237 throw new CryptoException(e);
238 }
239 }
240 }