001package com.nimbusds.jose.crypto; 002 003 004import java.security.SecureRandom; 005import java.security.interfaces.RSAPrivateKey; 006import java.util.HashSet; 007import java.util.Set; 008 009import javax.crypto.SecretKey; 010 011import com.nimbusds.jose.EncryptionMethod; 012import com.nimbusds.jose.JOSEException; 013import com.nimbusds.jose.JWEAlgorithm; 014import com.nimbusds.jose.JWEDecrypter; 015import com.nimbusds.jose.ReadOnlyJWEHeader; 016import com.nimbusds.jose.util.Base64URL; 017import com.nimbusds.jose.util.StringUtils; 018 019 020/** 021 * RSA decrypter of {@link com.nimbusds.jose.JWEObject JWE objects}. This class 022 * is thread-safe. 023 * 024 * <p>Supports the following JWE algorithms: 025 * 026 * <ul> 027 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA1_5} 028 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP} 029 * </ul> 030 * 031 * <p>Supports the following encryption methods: 032 * 033 * <ul> 034 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} 035 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384} 036 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} 037 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM} 038 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM} 039 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM} 040 * </ul> 041 * 042 * <p>Accepts all {@link com.nimbusds.jose.JWEHeader#getRegisteredParameterNames 043 * registered JWE header parameters}. Use {@link #setAcceptedAlgorithms} and 044 * {@link #setAcceptedEncryptionMethods} to restrict the acceptable JWE 045 * algorithms and encryption methods. 046 * 047 * @author David Ortiz 048 * @author Vladimir Dzhuvinov 049 * @version $version$ (2014-04-22) 050 * 051 */ 052public class RSADecrypter extends RSACryptoProvider implements JWEDecrypter { 053 054 055 /** 056 * The accepted JWE algorithms. 057 */ 058 private Set<JWEAlgorithm> acceptedAlgs = 059 new HashSet<JWEAlgorithm>(supportedAlgorithms()); 060 061 062 /** 063 * The accepted encryption methods. 064 */ 065 private Set<EncryptionMethod> acceptedEncs = 066 new HashSet<EncryptionMethod>(supportedEncryptionMethods()); 067 068 069 /** 070 * The critical header parameter checker. 071 */ 072 private final CriticalHeaderParameterChecker critParamChecker = 073 new CriticalHeaderParameterChecker(); 074 075 076 /** 077 * The private RSA key. 078 */ 079 private RSAPrivateKey privateKey; 080 081 082 /** 083 * Creates a new RSA decrypter. 084 * 085 * @param privateKey The private RSA key. Must not be {@code null}. 086 */ 087 public RSADecrypter(final RSAPrivateKey privateKey) { 088 089 if (privateKey == null) { 090 091 throw new IllegalArgumentException("The private RSA key must not be null"); 092 } 093 094 this.privateKey = privateKey; 095 } 096 097 098 /** 099 * Gets the private RSA key. 100 * 101 * @return The private RSA key. 102 */ 103 public RSAPrivateKey getPrivateKey() { 104 105 return privateKey; 106 } 107 108 109 @Override 110 public Set<JWEAlgorithm> getAcceptedAlgorithms() { 111 112 return acceptedAlgs; 113 } 114 115 116 @Override 117 public void setAcceptedAlgorithms(final Set<JWEAlgorithm> acceptedAlgs) { 118 119 if (acceptedAlgs == null) { 120 throw new IllegalArgumentException("The accepted JWE algorithms must not be null"); 121 } 122 123 if (! supportedAlgorithms().containsAll(acceptedAlgs)) { 124 throw new IllegalArgumentException("Unsupported JWE algorithm(s)"); 125 } 126 127 this.acceptedAlgs = acceptedAlgs; 128 } 129 130 131 @Override 132 public Set<EncryptionMethod> getAcceptedEncryptionMethods() { 133 134 return acceptedEncs; 135 } 136 137 138 @Override 139 public void setAcceptedEncryptionMethods(final Set<EncryptionMethod> acceptedEncs) { 140 141 if (acceptedEncs == null) 142 throw new IllegalArgumentException("The accepted encryption methods must not be null"); 143 144 if (!supportedEncryptionMethods().containsAll(acceptedEncs)) { 145 throw new IllegalArgumentException("Unsupported encryption method(s)"); 146 } 147 148 this.acceptedEncs = acceptedEncs; 149 } 150 151 152 @Override 153 public Set<String> getIgnoredCriticalHeaderParameters() { 154 155 return critParamChecker.getIgnoredCriticalHeaders(); 156 } 157 158 159 @Override 160 public void setIgnoredCriticalHeaderParameters(final Set<String> headers) { 161 162 critParamChecker.setIgnoredCriticalHeaders(headers); 163 } 164 165 166 @Override 167 public byte[] decrypt(final ReadOnlyJWEHeader header, 168 final Base64URL encryptedKey, 169 final Base64URL iv, 170 final Base64URL cipherText, 171 final Base64URL authTag) 172 throws JOSEException { 173 174 // Validate required JWE parts 175 if (encryptedKey == null) { 176 177 throw new JOSEException("The encrypted key must not be null"); 178 } 179 180 if (iv == null) { 181 182 throw new JOSEException("The initialization vector (IV) must not be null"); 183 } 184 185 if (authTag == null) { 186 187 throw new JOSEException("The authentication tag must not be null"); 188 } 189 190 if (! critParamChecker.headerPasses(header)) { 191 192 throw new JOSEException("Unsupported critical header parameter"); 193 } 194 195 196 // Derive the content encryption key 197 JWEAlgorithm alg = header.getAlgorithm(); 198 199 SecretKey cek; 200 201 if (alg.equals(JWEAlgorithm.RSA1_5)) { 202 203 int keyLength = header.getEncryptionMethod().cekBitLength(); 204 205 SecureRandom randomGen = getSecureRandom(); 206 SecretKey randomCEK = AES.generateKey(keyLength, randomGen); 207 208 try { 209 cek = RSA1_5.decryptCEK(privateKey, encryptedKey.decode(), keyLength, keyEncryptionProvider); 210 211 } catch (Exception e) { 212 213 // Protect against MMA attack by generating random CEK on failure, 214 // see http://www.ietf.org/mail-archive/web/jose/current/msg01832.html 215 cek = randomCEK; 216 } 217 218 } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) { 219 220 cek = RSA_OAEP.decryptCEK(privateKey, encryptedKey.decode(), keyEncryptionProvider); 221 222 } else { 223 224 throw new JOSEException("Unsupported JWE algorithm, must be RSA1_5 or RSA_OAEP"); 225 } 226 227 // Compose the AAD 228 byte[] aad = StringUtils.toByteArray(header.toBase64URL().toString()); 229 230 // Decrypt the cipher text according to the JWE enc 231 EncryptionMethod enc = header.getEncryptionMethod(); 232 233 byte[] plainText; 234 235 if (enc.equals(EncryptionMethod.A128CBC_HS256) || enc.equals(EncryptionMethod.A192CBC_HS384) || enc.equals(EncryptionMethod.A256CBC_HS512)) { 236 237 plainText = AESCBC.decryptAuthenticated(cek, iv.decode(), cipherText.decode(), aad, authTag.decode(), contentEncryptionProvider, macProvider); 238 239 } else if (enc.equals(EncryptionMethod.A128GCM) || enc.equals(EncryptionMethod.A192GCM) || enc.equals(EncryptionMethod.A256GCM)) { 240 241 plainText = AESGCM.decrypt(cek, iv.decode(), cipherText.decode(), aad, authTag.decode(), contentEncryptionProvider); 242 243 } else { 244 245 throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A192CBC_HS384, A256CBC_HS512, A128GCM, A192GCM or A256GCM"); 246 } 247 248 249 // Apply decompression if requested 250 return DeflateHelper.applyDecompression(header, plainText); 251 } 252} 253