001 package com.nimbusds.jose.crypto; 002 003 004 import java.io.UnsupportedEncodingException; 005 import java.security.NoSuchAlgorithmException; 006 import java.security.SecureRandom; 007 import java.security.interfaces.RSAPublicKey; 008 009 import javax.crypto.SecretKey; 010 011 import com.nimbusds.jose.CompressionAlgorithm; 012 import com.nimbusds.jose.EncryptionMethod; 013 import com.nimbusds.jose.JOSEException; 014 import com.nimbusds.jose.JWEAlgorithm; 015 import com.nimbusds.jose.JWECryptoParts; 016 import com.nimbusds.jose.JWEEncrypter; 017 import com.nimbusds.jose.ReadOnlyJWEHeader; 018 import com.nimbusds.jose.util.Base64URL; 019 import com.nimbusds.jose.util.DeflateUtils; 020 021 022 023 /** 024 * RSA encrypter 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 * @author David Ortiz 044 * @author Vladimir Dzhuvinov 045 * @version $version$ (2013-03-26) 046 */ 047 public class RSAEncrypter extends RSACryptoProvider implements JWEEncrypter { 048 049 050 /** 051 * Random byte generator. 052 */ 053 private final SecureRandom randomGen; 054 055 056 /** 057 * The public RSA key. 058 */ 059 private final RSAPublicKey publicKey; 060 061 062 /** 063 * Creates a new RSA encrypter. 064 * 065 * @param publicKey The public RSA key. Must not be {@code null}. 066 * 067 * @throws JOSEException If the underlying secure random generator 068 * couldn't be instantiated. 069 */ 070 public RSAEncrypter(final RSAPublicKey publicKey) 071 throws JOSEException { 072 073 if (publicKey == null) { 074 075 throw new IllegalArgumentException("The public RSA key must not be null"); 076 } 077 078 this.publicKey = publicKey; 079 080 081 try { 082 randomGen = SecureRandom.getInstance("SHA1PRNG"); 083 084 } catch(NoSuchAlgorithmException e) { 085 086 throw new JOSEException(e.getMessage(), e); 087 } 088 } 089 090 091 /** 092 * Gets the public RSA key. 093 * 094 * @return The public RSA key. 095 */ 096 public RSAPublicKey getPublicKey() { 097 098 return publicKey; 099 } 100 101 102 /** 103 * Applies compression to the specified plain text if requested. 104 * 105 * @param readOnlyJWEHeader The JWE header. Must not be {@code null}. 106 * @param bytes The plain text bytes. Must not be 107 * {@code null}. 108 * 109 * @return The bytes to encrypt. 110 * 111 * @throws JOSEException If compression failed or the requested 112 * compression algorithm is not supported. 113 */ 114 private static final byte[] applyCompression(final ReadOnlyJWEHeader readOnlyJWEHeader, final byte[] bytes) 115 throws JOSEException { 116 117 CompressionAlgorithm compressionAlg = readOnlyJWEHeader.getCompressionAlgorithm(); 118 119 if (compressionAlg == null) { 120 121 return bytes; 122 123 } else if (compressionAlg.equals(CompressionAlgorithm.DEF)) { 124 125 try { 126 return DeflateUtils.compress(bytes); 127 128 } catch (Exception e) { 129 130 throw new JOSEException("Couldn't compress plain text: " + e.getMessage(), e); 131 } 132 133 } else { 134 135 throw new JOSEException("Unsupported compression algorithm: " + compressionAlg); 136 } 137 } 138 139 140 @Override 141 public JWECryptoParts encrypt(final ReadOnlyJWEHeader readOnlyJWEHeader, final byte[] bytes) 142 throws JOSEException { 143 144 JWEAlgorithm alg = readOnlyJWEHeader.getAlgorithm(); 145 EncryptionMethod enc = readOnlyJWEHeader.getEncryptionMethod(); 146 147 // Generate and encrypt the CMK according to the JWE alg 148 final int keyLength = RSACryptoProvider.cmkBitLength(enc); 149 150 SecretKey cmk = AES.generateAESCMK(keyLength); 151 152 Base64URL encryptedKey = null; // The second JWE part 153 154 if (alg.equals(JWEAlgorithm.RSA1_5)) { 155 156 encryptedKey = Base64URL.encode(RSA1_5.encryptCMK(publicKey, cmk)); 157 158 } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) { 159 160 encryptedKey = Base64URL.encode(RSA_OAEP.encryptCMK(publicKey, cmk)); 161 162 } else { 163 164 throw new JOSEException("Unsupported algorithm, must be RSA1_5 or RSA_OAEP"); 165 } 166 167 if (encryptedKey == null ) { 168 169 throw new JOSEException("Couldn't generate encrypted key"); 170 } 171 172 173 // Apply compression if instructed 174 byte[] plainText = applyCompression(readOnlyJWEHeader, bytes); 175 176 177 // Encrypt the plain text according to the JWE enc 178 if (enc.equals(EncryptionMethod.A128CBC_HS256) || enc.equals(EncryptionMethod.A256CBC_HS512)) { 179 180 byte[] epu = null; 181 182 if (readOnlyJWEHeader.getEncryptionPartyUInfo() != null) { 183 184 epu = readOnlyJWEHeader.getEncryptionPartyUInfo().decode(); 185 } 186 187 byte[] epv = null; 188 189 if (readOnlyJWEHeader.getEncryptionPartyVInfo() != null) { 190 191 epv = readOnlyJWEHeader.getEncryptionPartyVInfo().decode(); 192 } 193 194 SecretKey cek = ConcatKDF.generateCEK(cmk, enc, epu, epv); 195 196 byte[] iv = AESCBC.generateIV(randomGen); 197 198 byte[] cipherText = AESCBC.encrypt(cek, iv, plainText); 199 200 SecretKey cik = ConcatKDF.generateCIK(cmk, enc, epu, epv); 201 202 String macInput = readOnlyJWEHeader.toBase64URL().toString() + "." + 203 encryptedKey.toString() + "." + 204 Base64URL.encode(iv).toString() + "." + 205 Base64URL.encode(cipherText); 206 207 byte[] mac = HMAC.compute(cik, macInput.getBytes()); 208 209 return new JWECryptoParts(encryptedKey, 210 Base64URL.encode(iv), 211 Base64URL.encode(cipherText), 212 Base64URL.encode(mac)); 213 214 } else if (enc.equals(EncryptionMethod.A128GCM) || enc.equals(EncryptionMethod.A256GCM)) { 215 216 byte[] iv = AESGCM.generateIV(randomGen); 217 218 // Compose the additional authenticated data 219 String authDataString = readOnlyJWEHeader.toBase64URL().toString() + "." + 220 encryptedKey.toString() + "." + 221 Base64URL.encode(iv).toString(); 222 223 byte[] authData; 224 225 try { 226 authData = authDataString.getBytes("UTF-8"); 227 228 } catch (UnsupportedEncodingException e) { 229 230 throw new JOSEException(e.getMessage(), e); 231 } 232 233 234 AESGCM.Result result = AESGCM.encrypt(cmk, iv, plainText, authData); 235 236 return new JWECryptoParts(encryptedKey, 237 Base64URL.encode(iv), 238 Base64URL.encode(result.getCipherText()), 239 Base64URL.encode(result.getAuthenticationTag())); 240 241 } else { 242 243 throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A256CBC_HS512, A128GCM or A128GCM"); 244 } 245 } 246 }