1 package org.bouncycastle.crypto.engines; 2 3 import java.security.SecureRandom; 4 5 import org.bouncycastle.crypto.CipherParameters; 6 import org.bouncycastle.crypto.Digest; 7 import org.bouncycastle.crypto.InvalidCipherTextException; 8 import org.bouncycastle.crypto.Wrapper; 9 import org.bouncycastle.crypto.modes.CBCBlockCipher; 10 import org.bouncycastle.crypto.params.KeyParameter; 11 import org.bouncycastle.crypto.params.ParametersWithIV; 12 import org.bouncycastle.crypto.params.ParametersWithRandom; 13 // Android-changed: Use Android digests 14 // import org.bouncycastle.crypto.util.DigestFactory; 15 import org.bouncycastle.crypto.digests.AndroidDigestFactory; 16 import org.bouncycastle.util.Arrays; 17 18 /** 19 * Wrap keys according to 20 * <A HREF="https://www.ietf.org/rfc/rfc3217.txt"> 21 * RFC 3217</A>. 22 * <p> 23 * Note: 24 * <ul> 25 * <li>if you are using this to wrap triple-des keys you need to set the 26 * parity bits on the key and, if it's a two-key triple-des key, pad it 27 * yourself. 28 * </ul> 29 */ 30 public class DESedeWrapEngine 31 implements Wrapper 32 { 33 /** Field engine */ 34 private CBCBlockCipher engine; 35 36 /** Field param */ 37 private KeyParameter param; 38 39 /** Field paramPlusIV */ 40 private ParametersWithIV paramPlusIV; 41 42 /** Field iv */ 43 private byte[] iv; 44 45 /** Field forWrapping */ 46 private boolean forWrapping; 47 48 /** Field IV2 */ 49 private static final byte[] IV2 = { (byte) 0x4a, (byte) 0xdd, (byte) 0xa2, 50 (byte) 0x2c, (byte) 0x79, (byte) 0xe8, 51 (byte) 0x21, (byte) 0x05 }; 52 53 // 54 // checksum digest 55 // 56 // Android-changed: Use Android digests 57 // Digest sha1 = DigestFactory.createSHA1(); 58 Digest sha1 = AndroidDigestFactory.getSHA1(); 59 byte[] digest = new byte[20]; 60 61 /** 62 * Method init 63 * 64 * @param forWrapping true if for wrapping, false otherwise. 65 * @param param necessary parameters, may include KeyParameter, ParametersWithRandom, and ParametersWithIV 66 */ 67 public void init(boolean forWrapping, CipherParameters param) 68 { 69 70 this.forWrapping = forWrapping; 71 this.engine = new CBCBlockCipher(new DESedeEngine()); 72 73 SecureRandom sr; 74 if (param instanceof ParametersWithRandom) 75 { 76 ParametersWithRandom pr = (ParametersWithRandom) param; 77 param = pr.getParameters(); 78 sr = pr.getRandom(); 79 } 80 else 81 { 82 sr = new SecureRandom(); 83 } 84 85 if (param instanceof KeyParameter) 86 { 87 this.param = (KeyParameter)param; 88 89 if (this.forWrapping) 90 { 91 92 // Hm, we have no IV but we want to wrap ?!? 93 // well, then we have to create our own IV. 94 this.iv = new byte[8]; 95 sr.nextBytes(iv); 96 97 this.paramPlusIV = new ParametersWithIV(this.param, this.iv); 98 } 99 } 100 else if (param instanceof ParametersWithIV) 101 { 102 this.paramPlusIV = (ParametersWithIV)param; 103 this.iv = this.paramPlusIV.getIV(); 104 this.param = (KeyParameter)this.paramPlusIV.getParameters(); 105 106 if (this.forWrapping) 107 { 108 if ((this.iv == null) || (this.iv.length != 8)) 109 { 110 throw new IllegalArgumentException("IV is not 8 octets"); 111 } 112 } 113 else 114 { 115 throw new IllegalArgumentException( 116 "You should not supply an IV for unwrapping"); 117 } 118 } 119 } 120 121 /** 122 * Method getAlgorithmName 123 * 124 * @return the algorithm name "DESede". 125 */ 126 public String getAlgorithmName() 127 { 128 return "DESede"; 129 } 130 131 /** 132 * Method wrap 133 * 134 * @param in byte array containing the encoded key. 135 * @param inOff off set into in that the data starts at. 136 * @param inLen length of the data. 137 * @return the wrapped bytes. 138 */ 139 public byte[] wrap(byte[] in, int inOff, int inLen) 140 { 141 if (!forWrapping) 142 { 143 throw new IllegalStateException("Not initialized for wrapping"); 144 } 145 146 byte keyToBeWrapped[] = new byte[inLen]; 147 148 System.arraycopy(in, inOff, keyToBeWrapped, 0, inLen); 149 150 // Compute the CMS Key Checksum, (section 5.6.1), call this CKS. 151 byte[] CKS = calculateCMSKeyChecksum(keyToBeWrapped); 152 153 // Let WKCKS = WK || CKS where || is concatenation. 154 byte[] WKCKS = new byte[keyToBeWrapped.length + CKS.length]; 155 156 System.arraycopy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.length); 157 System.arraycopy(CKS, 0, WKCKS, keyToBeWrapped.length, CKS.length); 158 159 // Encrypt WKCKS in CBC mode using KEK as the key and IV as the 160 // initialization vector. Call the results TEMP1. 161 162 int blockSize = engine.getBlockSize(); 163 164 if (WKCKS.length % blockSize != 0) 165 { 166 throw new IllegalStateException("Not multiple of block length"); 167 } 168 169 engine.init(true, paramPlusIV); 170 171 byte TEMP1[] = new byte[WKCKS.length]; 172 173 for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize) 174 { 175 engine.processBlock(WKCKS, currentBytePos, TEMP1, currentBytePos); 176 } 177 178 // Let TEMP2 = IV || TEMP1. 179 byte[] TEMP2 = new byte[this.iv.length + TEMP1.length]; 180 181 System.arraycopy(this.iv, 0, TEMP2, 0, this.iv.length); 182 System.arraycopy(TEMP1, 0, TEMP2, this.iv.length, TEMP1.length); 183 184 // Reverse the order of the octets in TEMP2 and call the result TEMP3. 185 byte[] TEMP3 = reverse(TEMP2); 186 187 // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector 188 // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the desired 189 // result. It is 40 octets long if a 168 bit key is being wrapped. 190 ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); 191 192 this.engine.init(true, param2); 193 194 for (int currentBytePos = 0; currentBytePos != TEMP3.length; currentBytePos += blockSize) 195 { 196 engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos); 197 } 198 199 return TEMP3; 200 } 201 202 /** 203 * Method unwrap 204 * 205 * @param in byte array containing the wrapped key. 206 * @param inOff off set into in that the data starts at. 207 * @param inLen length of the data. 208 * @return the unwrapped bytes. 209 * @throws InvalidCipherTextException 210 */ 211 public byte[] unwrap(byte[] in, int inOff, int inLen) 212 throws InvalidCipherTextException 213 { 214 if (forWrapping) 215 { 216 throw new IllegalStateException("Not set for unwrapping"); 217 } 218 219 if (in == null) 220 { 221 throw new InvalidCipherTextException("Null pointer as ciphertext"); 222 } 223 224 final int blockSize = engine.getBlockSize(); 225 if (inLen % blockSize != 0) 226 { 227 throw new InvalidCipherTextException("Ciphertext not multiple of " + blockSize); 228 } 229 230 /* 231 // Check if the length of the cipher text is reasonable given the key 232 // type. It must be 40 bytes for a 168 bit key and either 32, 40, or 233 // 48 bytes for a 128, 192, or 256 bit key. If the length is not supported 234 // or inconsistent with the algorithm for which the key is intended, 235 // return error. 236 // 237 // we do not accept 168 bit keys. it has to be 192 bit. 238 int lengthA = (estimatedKeyLengthInBit / 8) + 16; 239 int lengthB = estimatedKeyLengthInBit % 8; 240 241 if ((lengthA != keyToBeUnwrapped.length) || (lengthB != 0)) { 242 throw new XMLSecurityException("empty"); 243 } 244 */ 245 246 // Decrypt the cipher text with TRIPLedeS in CBC mode using the KEK 247 // and an initialization vector (IV) of 0x4adda22c79e82105. Call the output TEMP3. 248 ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); 249 250 this.engine.init(false, param2); 251 252 byte TEMP3[] = new byte[inLen]; 253 254 for (int currentBytePos = 0; currentBytePos != inLen; currentBytePos += blockSize) 255 { 256 engine.processBlock(in, inOff + currentBytePos, TEMP3, currentBytePos); 257 } 258 259 // Reverse the order of the octets in TEMP3 and call the result TEMP2. 260 byte[] TEMP2 = reverse(TEMP3); 261 262 // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining octets. 263 this.iv = new byte[8]; 264 265 byte[] TEMP1 = new byte[TEMP2.length - 8]; 266 267 System.arraycopy(TEMP2, 0, this.iv, 0, 8); 268 System.arraycopy(TEMP2, 8, TEMP1, 0, TEMP2.length - 8); 269 270 // Decrypt TEMP1 using TRIPLedeS in CBC mode using the KEK and the IV 271 // found in the previous step. Call the result WKCKS. 272 this.paramPlusIV = new ParametersWithIV(this.param, this.iv); 273 274 this.engine.init(false, this.paramPlusIV); 275 276 byte[] WKCKS = new byte[TEMP1.length]; 277 278 for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize) 279 { 280 engine.processBlock(TEMP1, currentBytePos, WKCKS, currentBytePos); 281 } 282 283 // Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are 284 // those octets before the CKS. 285 byte[] result = new byte[WKCKS.length - 8]; 286 byte[] CKStoBeVerified = new byte[8]; 287 288 System.arraycopy(WKCKS, 0, result, 0, WKCKS.length - 8); 289 System.arraycopy(WKCKS, WKCKS.length - 8, CKStoBeVerified, 0, 8); 290 291 // Calculate a CMS Key Checksum, (section 5.6.1), over the WK and compare 292 // with the CKS extracted in the above step. If they are not equal, return error. 293 if (!checkCMSKeyChecksum(result, CKStoBeVerified)) 294 { 295 throw new InvalidCipherTextException( 296 "Checksum inside ciphertext is corrupted"); 297 } 298 299 // WK is the wrapped key, now extracted for use in data decryption. 300 return result; 301 } 302 303 /** 304 * Some key wrap algorithms make use of the Key Checksum defined 305 * in CMS [CMS-Algorithms]. This is used to provide an integrity 306 * check value for the key being wrapped. The algorithm is 307 * 308 * - Compute the 20 octet SHA-1 hash on the key being wrapped. 309 * - Use the first 8 octets of this hash as the checksum value. 310 * 311 * For details see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum. 312 * 313 * @param key the key to check, 314 * @return the CMS checksum. 315 * @throws RuntimeException 316 */ 317 private byte[] calculateCMSKeyChecksum( 318 byte[] key) 319 { 320 byte[] result = new byte[8]; 321 322 sha1.update(key, 0, key.length); 323 sha1.doFinal(digest, 0); 324 325 System.arraycopy(digest, 0, result, 0, 8); 326 327 return result; 328 } 329 330 /** 331 * For details see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum 332 * 333 * @param key key to be validated. 334 * @param checksum the checksum. 335 * @return true if okay, false otherwise. 336 */ 337 private boolean checkCMSKeyChecksum( 338 byte[] key, 339 byte[] checksum) 340 { 341 return Arrays.constantTimeAreEqual(calculateCMSKeyChecksum(key), checksum); 342 } 343 344 private static byte[] reverse(byte[] bs) 345 { 346 byte[] result = new byte[bs.length]; 347 for (int i = 0; i < bs.length; i++) 348 { 349 result[i] = bs[bs.length - (i + 1)]; 350 } 351 return result; 352 } 353 } 354