Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ dependencies {
compile 'com.fasterxml.jackson.core:jackson-databind:2.9.2'
compile 'commons-codec:commons-codec:1.11'
compile 'com.google.code.gson:gson:2.8.2'
compile 'com.auth0:jwks-rsa:0.3.0'
compile 'com.nimbusds:nimbus-jose-jwt:2.19.1'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels wrong that a jwt library is using another jwt library. Where is this used for and can it be coded in this same library rather than depending on them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its for the PemReader object used in these 2 lines:

            PemReader reader = new PemReader(file);
            X509EncodedKeySpec caKeySpec = new X509EncodedKeySpec(reader.readPemObject().getContent());

compile 'org.apache.httpcomponents:httpcore:4.4.1'
compile 'org.apache.httpcomponents:httpclient:4.5'
compile group: 'org.slf4j', name:'slf4j-api', version: '1.7.2'
compile group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1.1'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yet another JSON library?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably was an unused import. removed.

testCompile 'org.bouncycastle:bcprov-jdk15on:1.58'
testCompile 'junit:junit:4.12'
testCompile 'net.jodah:concurrentunit:0.4.3'
Expand All @@ -61,4 +67,4 @@ test {
task clean(type: Delete) {
delete rootProject.buildDir
delete 'CHANGELOG.md.release'
}
}
1 change: 1 addition & 0 deletions lib/jwksRSA.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"keys":[{"alg":"RS256","kty":"RSA","use":"sig","x5c":["MIIDDTCCAfWgAwIBAgIJAJVkuSv2H8mDMA0GCSqGSIb3DQEBBQUAMB0xGzAZBgNVBAMMEnNhbmRyaW5vLmF1dGgwLmNvbTAeFw0xNDA1MTQyMTIyMjZaFw0yODAxMjEyMTIyMjZaMB0xGzAZBgNVBAMMEnNhbmRyaW5vLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL6jWASkHhXz5Ug6t5BsYBrXDIgrWu05f3oq2fE+5J5REKJiY0Ddc+Kda34ZwOptnUoef3JwKPDAckTJQDugweNNZPwOmFMRKj4xqEpxEkIX8C+zHs41Q6x54ZZy0xU+WvTGcdjzyZTZ/h0iOYisswFQT/s6750tZG0BOBtZ5qS/80tmWH7xFitgewdWteJaASE/eO1qMtdNsp9fxOtN5U/pZDUyFm3YRfOcODzVqp3wOz+dcKb7cdZN11EYGZOkjEekpcedzHCo9H4aOmdKCpytqL/9FXoihcBMg39s1OW3cfwfgf5/kvOJdcqR4PoATQTfsDVoeMWVB4XLGR6SC5kCAwEAAaNQME4wHQYDVR0OBBYEFHDYn9BQdup1CoeoFi0Rmf5xn/W9MB8GA1UdIwQYMBaAFHDYn9BQdup1CoeoFi0Rmf5xn/W9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGLpQZdd2ICVnGjc6CYfT3VNoujKYWk7E0shGaCXFXptrZ8yaryfo6WAizTfgOpQNJH+Jz+QsCjvkRt6PBSYX/hb5OUDU2zNJN48/VOw57nzWdjI70H2Ar4oJLck36xkIRs/+QX+mSNCjZboRwh0LxanXeALHSbCgJkbzWbjVnfJEQUP9P/7NGf0MkO5I95C/Pz9g91y8gU+R3imGppLy9Zx+OwADFwKAEJak4JrNgcjHBQenakAXnXP6HG4hHH4MzO8LnLiKv8ZkKVL67da/80PcpO0miMNPaqBBMd2Cy6GzQYE0ag6k0nk+DMIFn7K+o21gjUuOEJqIbAvhbf2KcM="],"n":"vqNYBKQeFfPlSDq3kGxgGtcMiCta7Tl_eirZ8T7knlEQomJjQN1z4p1rfhnA6m2dSh5_cnAo8MByRMlAO6DB401k_A6YUxEqPjGoSnESQhfwL7MezjVDrHnhlnLTFT5a9MZx2PPJlNn-HSI5iKyzAVBP-zrvnS1kbQE4G1nmpL_zS2ZYfvEWK2B7B1a14loBIT947Woy102yn1_E603lT-lkNTIWbdhF85w4PNWqnfA7P51wpvtx1k3XURgZk6SMR6Slx53McKj0fho6Z0oKnK2ov_0VeiKFwEyDf2zU5bdx_B-B_n-S84l1ypHg-gBNBN-wNWh4xZUHhcsZHpILmQ","e":"AQAB","kid":"8RGoVdVjD8fItyR3FFo0hVNaZYtPGwoP6xKi9e_V7bI","x5t":"RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg"}]}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this for and why is it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its for the tests to test x509 functionality

64 changes: 52 additions & 12 deletions lib/src/main/java/com/auth0/jwt/JWTDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,26 @@
package com.auth0.jwt;

import com.auth0.jwt.creators.EncodeType;
import com.auth0.jwt.creators.JWTCreator;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.creators.FbJwtCreator;
import com.auth0.jwt.creators.GoogleJwtCreator;
import com.auth0.jwt.creators.GoogleOrFbJwtCreator;
import com.auth0.jwt.impl.Claims;
import com.auth0.jwt.impl.JWTParser;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Header;
import com.auth0.jwt.interfaces.Payload;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.binary.StringUtils;

import com.auth0.jwt.utils.TokenUtils;
import com.google.common.base.Strings;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.binary.StringUtils;

/**
* The JWTDecoder class holds the decode method to parse a given JWT token into it's JWT representation.
Expand All @@ -47,6 +50,8 @@ public final class JWTDecoder implements DecodedJWT {
private final String[] parts;
private final Header header;
private final Payload payload;
private static final String ISSUER_FACEBOOK = "facebook";
private static final String ISSUER_GOOGLE = "google";

public JWTDecoder(String jwt, EncodeType encodeType) throws Exception {
parts = TokenUtils.splitToken(jwt);
Expand All @@ -55,13 +60,13 @@ public JWTDecoder(String jwt, EncodeType encodeType) throws Exception {
String payloadJson = null;
switch (encodeType) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the encode type doesn't match any value defined in the switch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i provide the values myself, so theres no way it cant be one of the 3

case Base16:
headerJson = URLDecoder.decode(new String(Hex.decodeHex(parts[0])), "UTF-8");
payloadJson = URLDecoder.decode(new String(Hex.decodeHex(parts[1])), "UTF-8");
headerJson = URLDecoder.decode(new String(Hex.decodeHex(parts[0])), StandardCharsets.UTF_8.name());
payloadJson = URLDecoder.decode(new String(Hex.decodeHex(parts[1])), StandardCharsets.UTF_8.name());
break;
case Base32:
Base32 base32 = new Base32();
headerJson = URLDecoder.decode(new String(base32.decode(parts[0]), "UTF-8"));
payloadJson = URLDecoder.decode(new String(base32.decode(parts[1]), "UTF-8"));
headerJson = URLDecoder.decode(new String(base32.decode(parts[0]), StandardCharsets.UTF_8.name()));
payloadJson = URLDecoder.decode(new String(base32.decode(parts[1]), StandardCharsets.UTF_8.name()));
break;
case Base64:
headerJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[0]));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why StringUtils.newStringUtf8() here and new String("", UTF8) above?. Keep 1. Normally, stick to classes provided in the JDK.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. changed.

Expand Down Expand Up @@ -161,4 +166,39 @@ public String getSignature() {
public String getToken() {
return String.format("%s.%s.%s", parts[0], parts[1], parts[2]);
}

public GoogleOrFbJwtCreator decodeJWT(DecodedJWT jwt) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class itself is a JWT decoder. It receives the token upon construction, and decodes the values storing the result on final fields. It's intended to use and throw away. What's this method for?

By the way: How is that a JWTDecoder class has a decodeJWT method that receives a DecodedJWT instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it goes back to our discussion during one of our scrum meetings. @lccodes might have more context.

Map<String, Claim> claims = jwt.getClaims();
Claim issuerClaim = claims.get(Claims.ISSUER);
if(issuerClaim == null) {
throw new IllegalArgumentException("null issuer claim");
}
String issuer = issuerClaim.asString();
GoogleOrFbJwtCreator googleOrFbJwtCreator = null;
if(Strings.isNullOrEmpty(issuer)) {
throw new IllegalArgumentException("null or empty issuer");
}
if(ISSUER_FACEBOOK.contains(issuer)) {
googleOrFbJwtCreator = FbJwtCreator.build()
.withExp(claims.get(Claims.EXPIRES_AT).asDate())
.withIat(claims.get(Claims.ISSUED_AT).asDate())
.withAppId(claims.get(Claims.APP_ID).asString())
.withUserId(claims.get(Claims.USER_ID).asString());
} else if(ISSUER_GOOGLE.contains(issuer)) {
googleOrFbJwtCreator = GoogleJwtCreator.build()
.withPicture(claims.get(Claims.PICTURE).asString())
.withEmail(claims.get(Claims.EMAIL).asString())
.withIssuer(claims.get(Claims.ISSUER).asString())
.withSubject(claims.get(Claims.SUBJECT).asString())
.withAudience(claims.get(Claims.AUDIENCE).asString())
.withExp(claims.get(Claims.EXPIRES_AT).asDate())
.withIat(claims.get(Claims.ISSUED_AT).asDate())
.withName(claims.get(Claims.NAME).asString());
} else {
throw new IllegalArgumentException("Not from a Facebook or Google issuer");
}

return googleOrFbJwtCreator;
}

}
20 changes: 17 additions & 3 deletions lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import com.auth0.jwt.interfaces.RSAKeyProvider;

import java.io.UnsupportedEncodingException;
import java.security.interfaces.*;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;


/**
* The Algorithm class represents an algorithm to be used in the Signing or Verification process of a Token.
Expand Down Expand Up @@ -385,6 +390,15 @@ public String toString() {
*/
public abstract void verify(DecodedJWT jwt, EncodeType encodeType) throws Exception;

/**
* Verify the given token including x509 functionality
* @param jwt the already decoded JWT that it's going to be verified.
* @param jwksFile
* @param pemFile
* @throws Exception
*/
public abstract void verifyWithX509(DecodedJWT jwt, String jwksFile, String pemFile) throws Exception;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the Algorithm needs to know how to construct/parse a PublicKey from any of this parameters. Extract that to a separate class and just receive the Key/Secret upon instantiation like the original implementation was doing.

Also:

  • a jwksfile will lead to the construction of the key, right?
  • and the pemfile is the key itself, right?
    In what scenario am I going to call this passing the two parameters?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think i know what you're getting at, but the issue also comes with JWT.decodeWithX509() which is used primarily for the RSA algorithm and just uses the algorithm attribute. should we have a separate rsaAlgorithm but then how would we know we are trying to sign with rsaAlgorithm? i would appreciate it if we could have a google hangouts meeting to discuss this design.


/**
* Sign the given content using this Algorithm instance.
*
Expand All @@ -406,4 +420,4 @@ public boolean equals(Object algorithmParam) {
Algorithm algorithm = (Algorithm) algorithmParam;
return this.description.equals(algorithm.description) && this.name.equals(algorithm.name);
}
}
}
8 changes: 7 additions & 1 deletion lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@

package com.auth0.jwt.algorithms;

import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;

class CryptoHelper {

Expand Down
20 changes: 12 additions & 8 deletions lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,16 @@
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.binary.StringUtils;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

class ECDSAAlgorithm extends Algorithm {

Expand Down Expand Up @@ -66,12 +64,12 @@ public void verify(DecodedJWT jwt, EncodeType encodeType) throws Exception {
String urlDecoded = null;
switch (encodeType) {
case Base16:
urlDecoded = URLDecoder.decode(signature, "UTF-8");
urlDecoded = URLDecoder.decode(signature, StandardCharsets.UTF_8.name());
signatureBytes = Hex.decodeHex(urlDecoded);
break;
case Base32:
Base32 base32 = new Base32();
urlDecoded = URLDecoder.decode(signature, "UTF-8");
urlDecoded = URLDecoder.decode(signature, StandardCharsets.UTF_8.name());
signatureBytes = base32.decode(urlDecoded);
break;
case Base64:
Expand All @@ -94,6 +92,11 @@ public void verify(DecodedJWT jwt, EncodeType encodeType) throws Exception {
}
}

@Override
public void verifyWithX509(DecodedJWT jwt, String jwksFile, String pemFile) throws Exception {
throw new UnsupportedOperationException("X509 is not supported for ECDSA algorithm");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things like this point you that this class shouldn't have this knowledge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comes back to the fact that they all extend Algorithm and the design issue up above. lets discuss.

}

@Override
public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
try {
Expand Down Expand Up @@ -241,6 +244,7 @@ static ECDSAKeyProvider providerForKeys(final ECPublicKey publicKey, final ECPri
if (publicKey == null && privateKey == null) {
throw new IllegalArgumentException("Both provided Keys cannot be null.");
}

return new ECDSAKeyProvider() {
@Override
public ECPublicKey getPublicKeyById(String keyId) {
Expand All @@ -258,4 +262,4 @@ public String getPrivateKeyId() {
}
};
}
}
}
23 changes: 12 additions & 11 deletions lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,18 @@
package com.auth0.jwt.algorithms;

import com.auth0.jwt.creators.EncodeType;
import com.auth0.jwt.creators.JWTCreator;
import com.auth0.jwt.exceptions.SignatureGenerationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.binary.StringUtils;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

class HMACAlgorithm extends Algorithm {

Expand Down Expand Up @@ -76,12 +72,12 @@ public void verify(DecodedJWT jwt, EncodeType encodeType) throws Exception {
String urlDecoded = null;
switch (encodeType) {
case Base16:
urlDecoded = URLDecoder.decode(signature, "UTF-8");
urlDecoded = URLDecoder.decode(signature, StandardCharsets.UTF_8.name());
signatureBytes = Hex.decodeHex(urlDecoded);
break;
case Base32:
Base32 base32 = new Base32();
urlDecoded = URLDecoder.decode(signature, "UTF-8");
urlDecoded = URLDecoder.decode(signature, StandardCharsets.UTF_8.name());
signatureBytes = base32.decode(urlDecoded);
break;
case Base64:
Expand All @@ -99,6 +95,11 @@ public void verify(DecodedJWT jwt, EncodeType encodeType) throws Exception {
}
}

@Override
public void verifyWithX509(DecodedJWT jwt, String jwksFile, String pemFile) throws Exception {
throw new UnsupportedOperationException("X509 is not supported for HMAC algorithm");
}

@Override
public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
try {
Expand All @@ -108,4 +109,4 @@ public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
}
}

}
}
15 changes: 10 additions & 5 deletions lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
import com.auth0.jwt.exceptions.SignatureGenerationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import java.net.URLDecoder;

class NoneAlgorithm extends Algorithm {

NoneAlgorithm() {
Expand All @@ -42,12 +42,12 @@ public void verify(DecodedJWT jwt, EncodeType encodeType) throws Exception {
String urlDecoded = null;
switch (encodeType) {
case Base16:
urlDecoded = URLDecoder.decode(signature, "UTF-8");
urlDecoded = URLDecoder.decode(signature, StandardCharsets.UTF_8.name());
signatureBytes = Hex.decodeHex(urlDecoded);
break;
case Base32:
Base32 base32 = new Base32();
urlDecoded = URLDecoder.decode(signature, "UTF-8");
urlDecoded = URLDecoder.decode(signature, StandardCharsets.UTF_8.name());
signatureBytes = base32.decode(urlDecoded);
break;
case Base64:
Expand All @@ -59,8 +59,13 @@ public void verify(DecodedJWT jwt, EncodeType encodeType) throws Exception {
}
}

@Override
public void verifyWithX509(DecodedJWT jwt, String jwksFile, String pemFile) throws Exception {
throw new UnsupportedOperationException("X509 is not supported for None algorithm");
}

@Override
public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
return new byte[0];
}
}
}
Loading