001 package com.nimbusds.jose.crypto; 002 003 004 import java.io.UnsupportedEncodingException; 005 006 import javax.crypto.SecretKey; 007 008 import org.bouncycastle.util.Arrays; 009 010 import com.nimbusds.jose.DefaultJWEHeaderFilter; 011 import com.nimbusds.jose.EncryptionMethod; 012 import com.nimbusds.jose.JOSEException; 013 import com.nimbusds.jose.JWEAlgorithm; 014 import com.nimbusds.jose.JWEDecrypter; 015 import com.nimbusds.jose.JWEHeaderFilter; 016 import com.nimbusds.jose.ReadOnlyJWEHeader; 017 import com.nimbusds.jose.util.Base64URL; 018 019 020 /** 021 * Direct decrypter of {@link com.nimbusds.jose.JWEObject JWE objects} with a 022 * shared symmetric key. This class is thread-safe. 023 * 024 * <p>Supports the following JWE algorithms: 025 * 026 * <ul> 027 * <li>{@link com.nimbusds.jose.JWEAlgorithm#DIR} 028 * </ul> 029 * 030 * <p>Supports the following encryption methods: 031 * 032 * <ul> 033 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} 034 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} 035 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM} 036 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM} 037 * </ul> 038 * 039 * <p>Accepts all {@link com.nimbusds.jose.JWEHeader#getReservedParameterNames 040 * reserved JWE header parameters}. Modify the {@link #getJWEHeaderFilter 041 * header filter} properties to restrict the acceptable JWE algorithms, 042 * encryption methods and header parameters, or to allow custom JWE header 043 * parameters. 044 * 045 * @author Vladimir Dzhuvinov 046 * @version $version$ (2013-04-16) 047 * 048 */ 049 public class DirectDecrypter extends DirectCryptoProvider implements JWEDecrypter { 050 051 052 /** 053 * The JWE header filter. 054 */ 055 private final DefaultJWEHeaderFilter headerFilter; 056 057 058 /** 059 * Creates a new direct decrypter. 060 * 061 * @param key The shared symmetric key. Must be 128 bits (16 bytes), 062 * 256 bits (32 bytes) or 512 bits (64 bytes) long. Must not 063 * be {@code null}. 064 * 065 * @throws JOSEException If the key length is unexpected. 066 */ 067 public DirectDecrypter(final byte[] key) 068 throws JOSEException { 069 070 super(key); 071 072 headerFilter = new DefaultJWEHeaderFilter(supportedAlgorithms(), supportedEncryptionMethods()); 073 } 074 075 076 @Override 077 public JWEHeaderFilter getJWEHeaderFilter() { 078 079 return headerFilter; 080 } 081 082 083 @Override 084 public byte[] decrypt(final ReadOnlyJWEHeader readOnlyJWEHeader, 085 final Base64URL encryptedKey, 086 final Base64URL iv, 087 final Base64URL cipherText, 088 final Base64URL integrityValue) 089 throws JOSEException { 090 091 // Validate required JWE parts 092 if (encryptedKey != null) { 093 094 throw new JOSEException("Unexpected encrypted key, must be omitted"); 095 } 096 097 if (iv == null) { 098 099 throw new JOSEException("The initialization vector (IV) must not be null"); 100 } 101 102 if (integrityValue == null) { 103 104 throw new JOSEException("The integrity value must not be null"); 105 } 106 107 108 JWEAlgorithm alg = readOnlyJWEHeader.getAlgorithm(); 109 110 if (! alg.equals(JWEAlgorithm.DIR)) { 111 112 throw new JOSEException("Unsupported algorithm, must be \"dir\""); 113 } 114 115 EncryptionMethod enc = readOnlyJWEHeader.getEncryptionMethod(); 116 117 byte[] plainText; 118 119 if (enc.equals(EncryptionMethod.A128CBC_HS256) || enc.equals(EncryptionMethod.A256CBC_HS512) ) { 120 121 byte[] epu = null; 122 123 if (readOnlyJWEHeader.getEncryptionPartyUInfo() != null) { 124 125 epu = readOnlyJWEHeader.getEncryptionPartyUInfo().decode(); 126 } 127 128 byte[] epv = null; 129 130 if (readOnlyJWEHeader.getEncryptionPartyVInfo() != null) { 131 132 epv = readOnlyJWEHeader.getEncryptionPartyVInfo().decode(); 133 } 134 135 SecretKey cek = ConcatKDF.generateCEK(cmk, enc, epu, epv); 136 137 plainText = AESCBC.decrypt(cek, iv.decode(), cipherText.decode()); 138 139 SecretKey cik = ConcatKDF.generateCIK(cmk, enc, epu, epv); 140 141 String macInput = readOnlyJWEHeader.toBase64URL().toString() + "." + 142 /* encryptedKey omitted */ "." + 143 iv.toString() + "." + 144 cipherText.toString(); 145 146 byte[] mac = HMAC.compute(cik, macInput.getBytes()); 147 148 if (! Arrays.constantTimeAreEqual(integrityValue.decode(), mac)) { 149 150 throw new JOSEException("HMAC integrity check failed"); 151 } 152 153 } else if (enc.equals(EncryptionMethod.A128GCM) || enc.equals(EncryptionMethod.A256GCM) ) { 154 155 // Compose the additional authenticated data 156 String authDataString = readOnlyJWEHeader.toBase64URL().toString() + "." + 157 /* encryptedKey omitted */ "." + 158 iv.toString(); 159 160 byte[] authData = null; 161 162 try { 163 authData = authDataString.getBytes("UTF-8"); 164 165 } catch (UnsupportedEncodingException e) { 166 167 throw new JOSEException(e.getMessage(), e); 168 } 169 170 plainText = AESGCM.decrypt(cmk, iv.decode(), cipherText.decode(), authData, integrityValue.decode()); 171 172 } else { 173 174 throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A256CBC_HS512, A128GCM or A128GCM"); 175 } 176 177 178 // Apply decompression if requested 179 return DeflateHelper.applyDecompression(readOnlyJWEHeader, plainText); 180 } 181 } 182