Merge pull request #128 from mtderryberry/jwe-and-jwt-fixes

Jwe and jwt fixes
pull/165/merge
Justin Richer 2012-07-31 10:52:04 -07:00
commit 863693cf59
12 changed files with 221 additions and 67 deletions

View File

@ -19,8 +19,6 @@ public class Rsa extends AbstractJwk{
private String mod;
private String exp;
JsonObject object = new JsonObject();
public String getMod() {
return mod;
}

View File

@ -0,0 +1,39 @@
package org.mitre.jwt.encryption;
import org.apache.commons.lang.StringUtils;
public enum JweAlgorithms {
//Key Derivation Function Values
CS256("256"),
CS384("384"),
CS512("512"),
//Encryption Method Values
A128GCM("GCM"),
A256GCM("GCM"),
A128CBC("CBC"),
A256CBC("CBC");
private final String value;
JweAlgorithms(String value) {
this.value = value;
}
public static String getByName(String name) {
for (JweAlgorithms correspondingType : JweAlgorithms.values()) {
if (correspondingType.toString().equals(name)) {
return correspondingType.value;
}
}
throw new IllegalArgumentException(
"JweAlgorithm name " + name + " does not have a corresponding JweAlgorithm: expected one of [" + StringUtils.join(JweAlgorithms.values(), ", ") + "]");
}
public String getValue() {
return value;
}
}

View File

@ -2,7 +2,6 @@ package org.mitre.jwt.encryption;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
@ -13,11 +12,11 @@ import org.mitre.jwe.model.Jwe;
public interface JweDecrypter {
public Jwe decrypt(String encryptedJwe, Key privateKey) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException;
public Jwe decrypt(String encryptedJwe) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException;
public byte[] decryptCipherText(Jwe jwe, byte[] cek) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException;
public byte[] decryptEncryptionKey(Jwe jwe, Key privateKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException;
public byte[] decryptEncryptionKey(Jwe jwe) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException;
}

View File

@ -3,7 +3,6 @@ package org.mitre.jwt.encryption;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
@ -19,11 +18,11 @@ import com.google.gson.JsonSyntaxException;
public interface JweEncrypter {
public byte[] encryptKey(Jwe jwe, byte[] cmk, Key publicKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException;
public byte[] encryptKey(Jwe jwe, byte[] cmk) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException;
public byte[] encryptClaims(Jwe jwe, byte[] cik) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException;
public Jwe encryptAndSign(Jwe jwe, Key publicKey) throws NoSuchAlgorithmException, JsonIOException, JsonSyntaxException, IOException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException;
public Jwe encryptAndSign(Jwe jwe) throws NoSuchAlgorithmException, JsonIOException, JsonSyntaxException, IOException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException;
public byte[] generateContentKey(byte[] cmk, int keyDataLen, byte[] type) throws NoSuchAlgorithmException;

View File

@ -2,8 +2,9 @@ package org.mitre.jwt.encryption.impl;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@ -15,13 +16,16 @@ import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.mitre.jwe.model.Jwe;
import org.mitre.jwt.encryption.AbstractJweDecrypter;
import org.mitre.jwt.signer.impl.HmacSigner;
import org.mitre.jwt.encryption.JweAlgorithms;
public class RsaDecrypter extends AbstractJweDecrypter {
private PublicKey publicKey;
private PrivateKey privateKey;
@Override
public Jwe decrypt(String encryptedJwe, Key privateKey) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
public Jwe decrypt(String encryptedJwe) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
Jwe jwe = Jwe.parse(encryptedJwe);
@ -29,39 +33,20 @@ public class RsaDecrypter extends AbstractJweDecrypter {
if(alg.equals("RSA1_5") || alg.equals("RSA-OAEP") || alg.equals("ECDH-ES") || alg.equals("A128KW") || alg.equals("A256KW")) {
//decrypt to get cmk to be used for cek and cik
jwe.setEncryptedKey(decryptEncryptionKey(jwe, privateKey));
jwe.setEncryptedKey(decryptEncryptionKey(jwe));
//generation of cek and cik
byte[] contentEncryptionKey = null;
byte[] contentIntegrityKey = null;
//check what the key length is
String encMethod = jwe.getHeader().getKeyDerivationFunction();
char[] array = encMethod.toCharArray();
String keyBitLengthString = new String("" + array[2] + array[3] + array[4]);
int keyBitLength = Integer.parseInt(keyBitLengthString);
String kdf = jwe.getHeader().getKeyDerivationFunction();
String keyLength = JweAlgorithms.getByName(kdf);
int keyBitLength = Integer.parseInt(keyLength);
//generate cek and cik
contentEncryptionKey = generateContentKey(jwe.getEncryptedKey(), keyBitLength, "Encryption".getBytes());
contentIntegrityKey = generateContentKey(jwe.getEncryptedKey(), keyBitLength, "Integrity".getBytes());
//decrypt ciphertext to get claims
jwe.setCiphertext(decryptCipherText(jwe, contentEncryptionKey));
//generate signature for decrypted signature base in order to verify that decryption worked
/*String signature = null;
try {
HmacSigner hmacSigner = new HmacSigner(contentIntegrityKey);
signature = hmacSigner.generateSignature(jwe.getSignatureBase());
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//verifys that the signature base was decrypted correctly
if(signature != jwe.getSignature()){
throw new IllegalArgumentException("Didn't decrypt correctly. Decoded Sig and generated Sig do not match. " +
"Generated Signature is: " + signature + " while decoded sig is: " + jwe.getSignature());
}*/
} else {
throw new IllegalArgumentException(jwe.getHeader().getEncryptionMethod() + " is not a valid decrypting algorithm");
}
@ -78,10 +63,9 @@ public class RsaDecrypter extends AbstractJweDecrypter {
//TODO: should also check for A128GCM and A256GCM, but Cipher.getInstance() does not support the GCM mode. For now, don't use them
if(encMethod.equals("A128CBC") || encMethod.equals("A256CBC")) {
String delims = "[8,6]+";
String[] mode = encMethod.split(delims);
String mode = JweAlgorithms.getByName(encMethod);
Cipher cipher = Cipher.getInstance("AES/" + mode[1] + "/PKCS5Padding");
Cipher cipher = Cipher.getInstance("AES/" + mode + "/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(contentEncryptionKey, "AES"), new IvParameterSpec(iv));
byte[] clearText = cipher.doFinal(jwe.getCiphertext());
@ -95,11 +79,11 @@ public class RsaDecrypter extends AbstractJweDecrypter {
}
@Override
public byte[] decryptEncryptionKey(Jwe jwe, Key privateKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
public byte[] decryptEncryptionKey(Jwe jwe) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
if(jwe.getHeader().getAlgorithm().equals("RSA1_5")){
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey());
byte[] contentMasterKey = cipher.doFinal(jwe.getEncryptedKey());
return contentMasterKey;
@ -109,4 +93,20 @@ public class RsaDecrypter extends AbstractJweDecrypter {
}
public PublicKey getPublicKey() {
return publicKey;
}
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}
public PrivateKey getPrivateKey() {
return privateKey;
}
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
}
}

View File

@ -2,8 +2,9 @@ package org.mitre.jwt.encryption.impl;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Random;
@ -17,11 +18,15 @@ import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.mitre.jwe.model.Jwe;
import org.mitre.jwt.encryption.AbstractJweEncrypter;
import org.mitre.jwt.encryption.JweAlgorithms;
import org.mitre.jwt.signer.impl.HmacSigner;
public class RsaEncrypter extends AbstractJweEncrypter {
private PublicKey publicKey;
private PrivateKey privateKey;
public Jwe encryptAndSign(Jwe jwe, Key publicKey) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException {
public Jwe encryptAndSign(Jwe jwe) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException {
String alg = jwe.getHeader().getAlgorithm();
String integrityAlg = jwe.getHeader().getIntegrity();
@ -31,10 +36,9 @@ public class RsaEncrypter extends AbstractJweEncrypter {
//generate random content master key
//check what the key length is
String encMethod = jwe.getHeader().getKeyDerivationFunction();
char[] array = encMethod.toCharArray();
String keyBitLengthString = new String("" + array[2] + array[3] + array[4]);
int keyBitLength = Integer.parseInt(keyBitLengthString);
String kdf = jwe.getHeader().getKeyDerivationFunction();
String keyLength = JweAlgorithms.getByName(kdf);
int keyBitLength = Integer.parseInt(keyLength);
byte[] contentMasterKey = new byte[keyBitLength];
new Random().nextBytes(contentMasterKey);
@ -48,7 +52,7 @@ public class RsaEncrypter extends AbstractJweEncrypter {
//encrypt claims and cmk to get ciphertext and encrypted key
jwe.setCiphertext(encryptClaims(jwe, contentEncryptionKey));
jwe.setEncryptedKey(encryptKey(jwe, contentMasterKey, publicKey));
jwe.setEncryptedKey(encryptKey(jwe, contentMasterKey));
//Signer must be hmac
if(integrityAlg.equals("HS256") || integrityAlg.equals("HS384") || integrityAlg.equals("HS512")){
@ -67,12 +71,12 @@ public class RsaEncrypter extends AbstractJweEncrypter {
return jwe;
}
public byte[] encryptKey(Jwe jwe, byte[] contentMasterKey, Key publicKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
public byte[] encryptKey(Jwe jwe, byte[] contentMasterKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
if(jwe.getHeader().getAlgorithm().equals("RSA1_5")){
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey());
byte[] encryptedKey = cipher.doFinal(contentMasterKey);
return encryptedKey;
@ -96,11 +100,10 @@ public class RsaEncrypter extends AbstractJweEncrypter {
String encMethod = jwe.getHeader().getEncryptionMethod();
//TODO: should also check for A128GCM and A256GCM, but Cipher.getInstance() does not support the GCM mode. For now, don't use them
if(encMethod.equals("A128CBC") || encMethod.equals("A256CBC")) {
// FIXME: this is fragile
String delims = "[8,6]+";
String[] mode = encMethod.split(delims);
Cipher cipher = Cipher.getInstance("AES/" + mode[1] + "/PKCS5Padding");
String mode = JweAlgorithms.getByName(encMethod);
Cipher cipher = Cipher.getInstance("AES/" + mode + "/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(contentEncryptionKey, "AES"), new IvParameterSpec(iv));
byte[] cipherText = cipher.doFinal(jwe.getCiphertext());
return cipherText;
@ -110,4 +113,20 @@ public class RsaEncrypter extends AbstractJweEncrypter {
}
}
public PublicKey getPublicKey() {
return publicKey;
}
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}
public PrivateKey getPrivateKey() {
return privateKey;
}
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
}
}

View File

@ -37,6 +37,8 @@ import com.google.gson.JsonPrimitive;
*
*/
public class ClaimSet {
private String jsonString;
// the LinkedHashMap preserves insertion order
private Map<String, Object> claims = new LinkedHashMap<String, Object>();
@ -94,6 +96,7 @@ public class ClaimSet {
* Set an extension claim
*/
public void setClaim(String key, Object value) {
jsonString = null;
claims.put(key, value);
}
@ -101,6 +104,7 @@ public class ClaimSet {
* Set a primitive claim
*/
public void setClaim(String key, JsonPrimitive prim) {
jsonString = null;
if (prim == null) {
// in case we get here with a primitive null
claims.put(key, prim);
@ -111,6 +115,7 @@ public class ClaimSet {
} else if (prim.isString()) {
claims.put(key, prim.getAsString());
}
}
/**
@ -203,4 +208,11 @@ public class ClaimSet {
loadFromJsonObject(json);
}
public String toString() {
if(jsonString == null) {
jsonString = this.getAsJsonObject().toString();
}
return jsonString;
}
}

View File

@ -22,7 +22,6 @@ import org.apache.commons.codec.binary.Base64;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gson.JsonObject;
public class Jwt {
@ -124,11 +123,9 @@ public class Jwt {
* The signature base of a JWT is the header in Base64, a period ".", and the claims in Base64.
*/
public String getSignatureBase() {
JsonObject h = header.getAsJsonObject();
JsonObject c = claims.getAsJsonObject();
String h64 = new String(Base64.encodeBase64URLSafe(h.toString().getBytes()));
String c64 = new String(Base64.encodeBase64URLSafe(c.toString().getBytes()));
String h64 = new String(Base64.encodeBase64URLSafe(header.toString().getBytes()));
String c64 = new String(Base64.encodeBase64URLSafe(claims.toString().getBytes()));
return h64 + "." + c64;
}

View File

@ -15,6 +15,7 @@
******************************************************************************/
package org.mitre.jwt.model;
import java.text.ParseException;
import java.util.Map.Entry;
import com.google.gson.JsonElement;
@ -59,7 +60,12 @@ public class JwtHeader extends ClaimSet {
if (element.getValue().isJsonNull()) {
pass.add(element.getKey(), element.getValue());
} else if (element.getKey().equals(TYPE)) {
this.setType(element.getValue().getAsString());
try {
this.setType(element.getValue().getAsString());
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else if (element.getKey().equals(ALGORITHM)) {
this.setAlgorithm(element.getValue().getAsString());
} else if (element.getKey().equals(ENCRYPTION_METHOD)) {
@ -85,9 +91,13 @@ public class JwtHeader extends ClaimSet {
/**
* @param type the type to set
* @throws ParseException
*/
public void setType(String type) {
setClaim(TYPE, type);
public void setType(String type) throws ParseException {
if(type == null) {
throw new NullPointerException("JWT header type value must not be null");
}
setClaim(TYPE, Type.parse(type));
}

View File

@ -0,0 +1,72 @@
package org.mitre.jwt.model;
public enum Type {
/**
* Type ({@code typ}) parameter indicating a JWT.
*
* <p>Corresponds to the follwoing {@code typ} values:
*
* <ul>
* <li>"JWT"
* <li>"urn:ietf:params:oauth:token-type:jwt"
* </ul>
*/
JWT,
/**
* Type ({@code typ}) parameter indicating a nested JWS.
*
* <p>Corresponds to the following {@code typ} value:
*
* <ul>
* <li>"JWS"
* </ul>
*/
JWS,
/**
* Type ({@code typ}) parameter indicating a nested JWE.
*
* <p>Corresponds to the follwoing {@code typ} value:
*
* <ul>
* <li>"JWE"
* </ul>
*/
JWE;
/**
* Parses the specified type string (case sensitive).
*
* <p>Note that both "JWT" and
* "urn:ietf:params:oauth:token-type:jwt" resolve to
* {@link #JWT}.
*
* @param s The string to parse.
*
* @throws java.text.ParseException If the string couldn't be
* parsed to a supported JWT
* header type.
*/
public static Type parse(final String s)
throws java.text.ParseException {
if (s == null)
throw new NullPointerException("The parsed JWT header \"typ\" value must not be null");
if (s.equals("JWT") || s.equals("urn:ietf:params:oauth:token-type:jwt"))
return JWT;
if (s.equals("JWS"))
return JWS;
if (s.equals("JWE"))
return JWE;
throw new java.text.ParseException("Unsupported JWT header \"typ\" value: " + s, 0);
}
}

View File

@ -20,7 +20,6 @@ import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.easymock.internal.matchers.GreaterThan;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
@ -64,22 +63,26 @@ public class RsaEncrypterDecrypterTest {
//read in header and plaintext from files
JsonParser parser = new JsonParser();
JsonObject jweHeaderObject = parser.parse(new BufferedReader(new InputStreamReader(jweHeaderUrl.openStream()))).getAsJsonObject();
//create jwe based on header and plaintext
Jwe jwe = new Jwe(new JweHeader(jweHeaderObject), null, jwePlaintextString.getBytes(), null);
//generate key pair. this will be passed in from the user
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(4096);
KeyPair pair = keyGen.generateKeyPair();
PublicKey publicKey = pair.getPublic();
PrivateKey privateKey = pair.getPrivate();
//create jwe based on header and plaintext
Jwe jwe = new Jwe(new JweHeader(jweHeaderObject), null, jwePlaintextString.getBytes(), null);
//encrypt
RsaEncrypter rsaEncrypter = new RsaEncrypter();
jwe = rsaEncrypter.encryptAndSign(jwe, publicKey);
rsaEncrypter.setPublicKey(publicKey);
rsaEncrypter.setPrivateKey(privateKey);
jwe = rsaEncrypter.encryptAndSign(jwe);
//decrypt
RsaDecrypter rsaDecrypter = new RsaDecrypter();
rsaDecrypter.setPublicKey(publicKey);
rsaDecrypter.setPrivateKey(privateKey);
String encryptedJweString = jwe.toString();
jwe = rsaDecrypter.decrypt(encryptedJweString, privateKey);
jwe = rsaDecrypter.decrypt(encryptedJweString);
String jweDecryptedCleartext = new String(jwe.getCiphertext());
//test ALL THE THINGS

View File

@ -27,6 +27,7 @@ import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.text.ParseException;
import java.util.Date;
import org.bouncycastle.jce.X509Principal;
@ -57,7 +58,12 @@ public class JwtTest {
@Test
public void testGenerateHmacSignature() {
Jwt jwt = new Jwt();
jwt.getHeader().setType("JWT");
try {
jwt.getHeader().setType("JWT");
} catch (ParseException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
jwt.getHeader().setAlgorithm("HS256");
jwt.getClaims().setExpiration(new Date(1300819380L * 1000L));
jwt.getClaims().setIssuer("joe");