001package com.nimbusds.jose.crypto; 002 003 004import java.io.UnsupportedEncodingException; 005import java.security.interfaces.RSAPrivateKey; 006 007import javax.crypto.SecretKey; 008 009import org.bouncycastle.util.Arrays; 010 011import com.nimbusds.jose.CompressionAlgorithm; 012import com.nimbusds.jose.DefaultJWEHeaderFilter; 013import com.nimbusds.jose.EncryptionMethod; 014import com.nimbusds.jose.JOSEException; 015import com.nimbusds.jose.JWEAlgorithm; 016import com.nimbusds.jose.JWEDecrypter; 017import com.nimbusds.jose.JWEHeaderFilter; 018import com.nimbusds.jose.ReadOnlyJWEHeader; 019import com.nimbusds.jose.util.Base64URL; 020import com.nimbusds.jose.util.DeflateUtils; 021 022 023/** 024 * RSA decrypter of {@link com.nimbusds.jose.JWEObject JWE objects}. This class 025 * is thread-safe. 026 * 027 * <p>Supports the following JWE algorithms: 028 * 029 * <ul> 030 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA1_5} 031 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP} 032 * </ul> 033 * 034 * <p>Supports the following encryption methods: 035 * 036 * <ul> 037 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} 038 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} 039 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM} 040 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM} 041 * </ul> 042 * 043 * <p>Accepts all {@link com.nimbusds.jose.JWEHeader#getReservedParameterNames 044 * reserved JWE header parameters}. Modify the {@link #getJWEHeaderFilter 045 * header filter} properties to restrict the acceptable JWE algorithms, 046 * encryption methods and header parameters, or to allow custom JWE header 047 * parameters. 048 * 049 * @author David Ortiz 050 * @author Vladimir Dzhuvinov 051 * @version $version$ (2013-03-27) 052 * 053 */ 054public class RSADecrypter extends RSACryptoProvider implements JWEDecrypter { 055 056 057 /** 058 * The JWE header filter. 059 */ 060 private final DefaultJWEHeaderFilter headerFilter; 061 062 063 /** 064 * The private RSA key. 065 */ 066 private RSAPrivateKey privateKey; 067 068 069 /** 070 * Creates a new RSA decrypter. 071 * 072 * @param privateKey The private RSA key. Must not be {@code null}. 073 */ 074 public RSADecrypter(final RSAPrivateKey privateKey) { 075 076 if (privateKey == null) { 077 078 throw new IllegalArgumentException("The private RSA key must not be null"); 079 } 080 081 this.privateKey = privateKey; 082 083 headerFilter = new DefaultJWEHeaderFilter(supportedAlgorithms(), supportedEncryptionMethods()); 084 } 085 086 087 /** 088 * Gets the private RSA key. 089 * 090 * @return The private RSA key. 091 */ 092 public RSAPrivateKey getPrivateKey() { 093 094 return privateKey; 095 } 096 097 098 @Override 099 public JWEHeaderFilter getJWEHeaderFilter() { 100 101 return headerFilter; 102 } 103 104 105 /** 106 * Applies decompression to the specified plain text if requested. 107 * 108 * @param readOnlyJWEHeader The JWE header. Must not be {@code null}. 109 * @param bytes The plain text bytes. Must not be 110 * {@code null}. 111 * 112 * @return The output bytes, decompressed if requested. 113 * 114 * @throws JOSEException If decompression failed or the requested 115 * compression algorithm is not supported. 116 */ 117 private static final byte[] applyDecompression(final ReadOnlyJWEHeader readOnlyJWEHeader, final byte[] bytes) 118 throws JOSEException { 119 120 CompressionAlgorithm compressionAlg = readOnlyJWEHeader.getCompressionAlgorithm(); 121 122 if (compressionAlg == null) { 123 124 return bytes; 125 126 } else if (compressionAlg.equals(CompressionAlgorithm.DEF)) { 127 128 try { 129 return DeflateUtils.decompress(bytes); 130 131 } catch (Exception e) { 132 133 throw new JOSEException("Couldn't decompress plain text: " + e.getMessage(), e); 134 } 135 136 } else { 137 138 throw new JOSEException("Unsupported compression algorithm: " + compressionAlg); 139 } 140 } 141 142 143 @Override 144 public byte[] decrypt(final ReadOnlyJWEHeader readOnlyJWEHeader, 145 final Base64URL encryptedKey, 146 final Base64URL iv, 147 final Base64URL cipherText, 148 final Base64URL integrityValue) 149 throws JOSEException { 150 151 // Validate required JWE parts 152 if (encryptedKey == null) { 153 154 throw new JOSEException("The encrypted key must not be null"); 155 } 156 157 if (iv == null) { 158 159 throw new JOSEException("The initialization vector (IV) must not be null"); 160 } 161 162 if (integrityValue == null) { 163 164 throw new JOSEException("The integrity value must not be null"); 165 } 166 167 168 // Derive the encryption AES key 169 JWEAlgorithm alg = readOnlyJWEHeader.getAlgorithm(); 170 171 SecretKey cmk = null; 172 173 if (alg.equals(JWEAlgorithm.RSA1_5)) { 174 175 int keyLength = cmkBitLength(readOnlyJWEHeader.getEncryptionMethod()); 176 177 SecretKey randomCMK = AES.generateAESCMK(keyLength); 178 179 try { 180 cmk = RSA1_5.decryptCMK(privateKey, encryptedKey.decode(), keyLength); 181 182 } catch (Exception e) { 183 184 // Protect against MMA attack by generating random CMK on failure, 185 // see http://www.ietf.org/mail-archive/web/jose/current/msg01832.html 186 cmk = randomCMK; 187 } 188 189 } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) { 190 191 cmk = RSA_OAEP.decryptCMK(privateKey, encryptedKey.decode()); 192 193 } else { 194 195 throw new JOSEException("Unsupported algorithm, must be RSA1_5 or RSA_OAEP"); 196 } 197 198 EncryptionMethod enc = readOnlyJWEHeader.getEncryptionMethod(); 199 200 byte[] plainText; 201 202 if (enc.equals(EncryptionMethod.A128CBC_HS256) || enc.equals(EncryptionMethod.A256CBC_HS512) ) { 203 204 byte[] epu = null; 205 206 if (readOnlyJWEHeader.getEncryptionPartyUInfo() != null) { 207 208 epu = readOnlyJWEHeader.getEncryptionPartyUInfo().decode(); 209 } 210 211 byte[] epv = null; 212 213 if (readOnlyJWEHeader.getEncryptionPartyVInfo() != null) { 214 215 epv = readOnlyJWEHeader.getEncryptionPartyVInfo().decode(); 216 } 217 218 SecretKey cek = ConcatKDF.generateCEK(cmk, enc, epu, epv); 219 220 plainText = AESCBC.decrypt(cek, iv.decode(), cipherText.decode()); 221 222 SecretKey cik = ConcatKDF.generateCIK(cmk, enc, epu, epv); 223 224 String macInput = readOnlyJWEHeader.toBase64URL().toString() + "." + 225 encryptedKey.toString() + "." + 226 iv.toString() + "." + 227 cipherText.toString(); 228 229 byte[] mac = HMAC.compute(cik, macInput.getBytes()); 230 231 if (! Arrays.constantTimeAreEqual(integrityValue.decode(), mac)) { 232 233 throw new JOSEException("HMAC integrity check failed"); 234 } 235 236 } else if (enc.equals(EncryptionMethod.A128GCM) || enc.equals(EncryptionMethod.A256GCM) ) { 237 238 // Compose the additional authenticated data 239 String authDataString = readOnlyJWEHeader.toBase64URL().toString() + "." + 240 encryptedKey.toString() + "." + 241 iv.toString(); 242 243 byte[] authData = null; 244 245 try { 246 authData = authDataString.getBytes("UTF-8"); 247 248 } catch (UnsupportedEncodingException e) { 249 250 throw new JOSEException(e.getMessage(), e); 251 } 252 253 plainText = AESGCM.decrypt(cmk, iv.decode(), cipherText.decode(), authData, integrityValue.decode()); 254 255 } else { 256 257 throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A256CBC_HS512, A128GCM or A128GCM"); 258 } 259 260 261 // Apply decompression if requested 262 return applyDecompression(readOnlyJWEHeader, plainText); 263 } 264} 265