/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.picketlink.json.jose.crypto;
import static org.picketlink.json.JsonConstants.JWE.ALG_RSA1_5;
import static org.picketlink.json.JsonConstants.JWE.ALG_RSA_OAEP;
import static org.picketlink.json.JsonConstants.JWE.ALG_RSA_OAEP_256;
import static org.picketlink.json.JsonConstants.JWE.ENC_A128CBC_HS256;
import static org.picketlink.json.JsonConstants.JWE.ENC_A128GCM;
import static org.picketlink.json.JsonConstants.JWE.ENC_A192CBC_HS384;
import static org.picketlink.json.JsonConstants.JWE.ENC_A192GCM;
import static org.picketlink.json.JsonConstants.JWE.ENC_A256CBC_HS512;
import static org.picketlink.json.JsonConstants.JWE.ENC_A256GCM;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import javax.crypto.SecretKey;
import org.picketlink.json.jose.JWE;
import org.picketlink.json.util.Base64Util;
/**
* JWE Decrypter for JSON Web Decryption.
*
* <p>
* Supports the following JWE algorithms:
*
* <ul>
* <li>{@link org.picketlink.json.JsonConstants.JWE.RSA1_5}
* <li>{@link org.picketlink.json.JsonConstants.JWE.RSA_OAEP}
* <li>{@link org.picketlink.json.JsonConstants.JWE.RSA_OAEP_256}
* </ul>
*
* <p>
* Supports the following encryption algorithms:
*
* <ul>
* <li>{@link org.picketlink.json.JsonConstants.JWE.A128CBC_HS256}
* <li>{@link org.picketlink.json.JsonConstants.JWE.A192CBC_HS384}
* <li>{@link org.picketlink.json.JsonConstants.JWE.A256CBC_HS512}
* <li>{@link org.picketlink.json.JsonConstants.JWE.A128GCM}
* <li>{@link org.picketlink.json.JsonConstants.JWE.A192GCM}
* <li>{@link org.picketlink.json.JsonConstants.JWE.A256GCM}
* </ul>
*
* @author Giriraj Sharma
*/
public class JWEDecrypter {
/**
* The private RSA key.
*/
private final RSAPrivateKey privateKey;
/**
* Creates a new RSA decrypter.
*
* @param privateKey The private RSA key. Must not be {@code null}.
*/
public JWEDecrypter(final RSAPrivateKey privateKey) {
if (privateKey == null) {
throw new IllegalArgumentException("The private RSA key must not be null");
}
this.privateKey = privateKey;
}
/**
* Gets the private RSA key.
*
* @return The private RSA key.
*/
public RSAPrivateKey getPrivateKey() {
return privateKey;
}
public byte[] decrypt(final JWE jweHeader,
final String encryptedKey,
final String iv,
final String cipherText,
final String authTag) {
// Validate required JWE parts
if (encryptedKey == null) {
throw new RuntimeException("The encrypted key must not be null");
}
if (iv == null) {
throw new RuntimeException("The initialization vector (IV) must not be null");
}
if (authTag == null) {
throw new RuntimeException("The authentication tag must not be null");
}
// Derive the content encryption key
String alg = jweHeader.getAlgorithm();
SecretKey cek;
if (alg.equals(ALG_RSA1_5)) {
int keyLength = Integer.parseInt(jweHeader.getCEKBitLength());
// Protect against MMA attack by generating random CEK on failure,
// see http://www.ietf.org/mail-archive/web/jose/current/msg01832.html
SecureRandom randomGen = new SecureRandom();
SecretKey randomCEK = AES.generateKey(keyLength, randomGen);
try {
cek = RSA1_5.decryptCEK(privateKey, Base64Util.b64Decode(encryptedKey), keyLength);
if (cek == null) {
// CEK length mismatch, signalled by null instead of
// exception to prevent MMA attack
cek = randomCEK;
}
} catch (Exception e) {
// continue
cek = randomCEK;
}
} else if (alg.equals(ALG_RSA_OAEP)) {
cek = RSA_OAEP.decryptCEK(privateKey, Base64Util.b64Decode(encryptedKey));
} else if (alg.equals(ALG_RSA_OAEP_256)) {
cek = RSA_OAEP_256.decryptCEK(privateKey, Base64Util.b64Decode(encryptedKey));
} else {
throw new RuntimeException("Unsupported JWE algorithm, must be RSA1_5 or RSA_OAEP");
}
// Compose the AAD
byte[] aad = Base64Util.b64Encode(jweHeader.toString()).getBytes(Charset.forName("UTF-8"));
// Decrypt the cipher text according to the JWE enc
String enc = jweHeader.getEncryptionAlgorithm();
byte[] plainText;
if (enc.equals(ENC_A128CBC_HS256) ||
enc.equals(ENC_A192CBC_HS384) ||
enc.equals(ENC_A256CBC_HS512)) {
plainText = AESCBC.decryptAuthenticated(
cek,
Base64Util.b64Decode(iv),
Base64Util.b64Decode(cipherText),
aad,
Base64Util.b64Decode(authTag));
} else if (enc.equals(ENC_A128GCM) ||
enc.equals(ENC_A192GCM) ||
enc.equals(ENC_A256GCM)) {
plainText = AESGCM.decrypt(
cek,
Base64Util.b64Decode(iv),
Base64Util.b64Decode(cipherText),
aad,
Base64Util.b64Decode(authTag));
} else {
throw new RuntimeException("Unsupported encryption method, must be A128CBC_HS256, A192CBC_HS384, A256CBC_HS512, A128GCM, A192GCM or A256GCM");
}
// Apply decompression if requested
try {
return DeflateUtils.decompress(plainText);
} catch (IOException e) {
throw new RuntimeException("Failed to decompress plainText");
}
}
}