From ee28d560311ac23ebaf90a68147459ea1e7cdd7f Mon Sep 17 00:00:00 2001 From: Mike Derryberry Date: Fri, 1 Jun 2012 10:51:28 -0400 Subject: [PATCH 1/3] initial implementation of x509 and JWK key retrieval --- openid-connect-client/.classpath | 2 + .../org.eclipse.wst.common.component | 1 + .../client/OIDCAuthenticationFilter.java | 16 ++ .../client/OIDCServerConfiguration.java | 98 ++++++++- .../openid/connect/client/UrlValidator.java | 34 +++ .../client/OIDCServerConfigurationTest.java | 104 +++++++++ openid-connect-common/.classpath | 3 + .../org.eclipse.wst.common.component | 1 + .../java/org/mitre/jwk/model/AbstractJwk.java | 66 ++++++ .../src/main/java/org/mitre/jwk/model/EC.java | 96 ++++++++ .../main/java/org/mitre/jwk/model/Jwk.java | 18 ++ .../main/java/org/mitre/jwk/model/Rsa.java | 67 ++++++ .../src/main/java/org/mitre/util/Utility.java | 79 +++++++ .../test/java/org/mitre/util/UtilityTest.java | 206 ++++++++++++++++++ .../src/test/resources/jwk/jwkFail | 15 ++ .../src/test/resources/jwk/jwkSuccess | 15 ++ 16 files changed, 820 insertions(+), 1 deletion(-) create mode 100644 openid-connect-client/src/main/java/org/mitre/openid/connect/client/UrlValidator.java create mode 100644 openid-connect-client/src/test/java/org/mitre/openid/connect/client/OIDCServerConfigurationTest.java create mode 100644 openid-connect-common/src/main/java/org/mitre/jwk/model/AbstractJwk.java create mode 100644 openid-connect-common/src/main/java/org/mitre/jwk/model/EC.java create mode 100644 openid-connect-common/src/main/java/org/mitre/jwk/model/Jwk.java create mode 100644 openid-connect-common/src/main/java/org/mitre/jwk/model/Rsa.java create mode 100644 openid-connect-common/src/test/java/org/mitre/util/UtilityTest.java create mode 100644 openid-connect-common/src/test/resources/jwk/jwkFail create mode 100644 openid-connect-common/src/test/resources/jwk/jwkSuccess diff --git a/openid-connect-client/.classpath b/openid-connect-client/.classpath index 1b28ee5d7..40f68d2dc 100644 --- a/openid-connect-client/.classpath +++ b/openid-connect-client/.classpath @@ -1,6 +1,8 @@ + + diff --git a/openid-connect-client/.settings/org.eclipse.wst.common.component b/openid-connect-client/.settings/org.eclipse.wst.common.component index fc2629825..e70fe2d47 100755 --- a/openid-connect-client/.settings/org.eclipse.wst.common.component +++ b/openid-connect-client/.settings/org.eclipse.wst.common.component @@ -3,5 +3,6 @@ + diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java index c7697048b..4ec398cbd 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java @@ -132,4 +132,20 @@ public class OIDCAuthenticationFilter extends AbstractOIDCAuthenticationFilter { public void setTokenEndpointURI(String tokenEndpointURI) { oidcServerConfig.setTokenEndpointURI(tokenEndpointURI); } + + public void setX509EncryptUrl(String x509EncryptUrl) { + oidcServerConfig.setX509EncryptUrl(x509EncryptUrl); + } + + public void setX509SigningUrl(String x509SigningUrl) { + oidcServerConfig.setX509SigningUrl(x509SigningUrl); + } + + public void setJwkEncryptUrl(String jwkEncryptUrl) { + oidcServerConfig.setJwkEncryptUrl(jwkEncryptUrl); + } + + public void setJwkSigningUrl(String jwkSigningUrl) { + oidcServerConfig.setJwkSigningUrl(jwkSigningUrl); + } } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCServerConfiguration.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCServerConfiguration.java index 333e4505c..2d0d226b2 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCServerConfiguration.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCServerConfiguration.java @@ -15,6 +15,15 @@ ******************************************************************************/ package org.mitre.openid.connect.client; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.Key; + +import org.mitre.util.Utility; + +import com.google.gson.JsonObject; + /** * @author nemonik * @@ -30,6 +39,18 @@ public class OIDCServerConfiguration { private String clientSecret; private String clientId; + + private String x509EncryptUrl; + + private String x509SigningUrl; + + private String jwkEncryptUrl; + + private String jwkSigningUrl; + + private Key encryptKey; + + private Key signingKey; public String getAuthorizationEndpointURI() { return authorizationEndpointURI; @@ -70,6 +91,77 @@ public class OIDCServerConfiguration { public void setTokenEndpointURI(String tokenEndpointURI) { this.tokenEndpointURI = tokenEndpointURI; } + + public String getX509EncryptUrl() { + return x509EncryptUrl; + } + + public String getX509SigningUrl() { + return x509SigningUrl; + } + + public String getJwkEncryptUrl() { + return jwkEncryptUrl; + } + + public String getJwkSigningUrl() { + return jwkSigningUrl; + } + + public void setX509EncryptUrl(String x509EncryptUrl) { + this.x509EncryptUrl = x509EncryptUrl; + } + + public void setX509SigningUrl(String x509SigningUrl) { + this.x509SigningUrl = x509SigningUrl; + } + + public void setJwkEncryptUrl(String jwkEncryptUrl) { + this.jwkEncryptUrl = jwkEncryptUrl; + } + + public void setJwkSigningUrl(String jwkSigningUrl) { + this.jwkSigningUrl = jwkSigningUrl; + } + + public Key getSigningKey() throws Exception { + if(signingKey == null){ + if(x509SigningUrl != null){ + File file = new File(x509SigningUrl); + URL url = file.toURI().toURL(); + signingKey = Utility.retrieveX509Key(url); + } + else if (jwkSigningUrl != null){ + File file = new File(jwkSigningUrl); + URL url = file.toURI().toURL(); + signingKey = Utility.retrieveJwkKey(url); + } + } + return signingKey; + } + + public Key getEncryptionKey() throws Exception { + if(encryptKey == null){ + if(x509EncryptUrl != null){ + File file = new File(x509EncryptUrl); + URL url = file.toURI().toURL(); + encryptKey = Utility.retrieveX509Key(url); + } + else if (jwkEncryptUrl != null){ + File file = new File(jwkEncryptUrl); + URL url = file.toURI().toURL(); + encryptKey = Utility.retrieveJwkKey(url); + } + } + return encryptKey; + } + + public void checkKeys() throws Exception { + encryptKey = null; + signingKey = null; + getEncryptionKey(); + getSigningKey(); + } @Override public String toString() { @@ -77,7 +169,11 @@ public class OIDCServerConfiguration { + authorizationEndpointURI + ", tokenEndpointURI=" + tokenEndpointURI + ", checkIDEndpointURI=" + checkIDEndpointURI + ", clientSecret=" + clientSecret - + ", clientId=" + clientId + "]"; + + ", clientId=" + clientId + ", x509EncryptedUrl=" + + x509EncryptUrl + ", jwkEncryptedUrl=" + + jwkEncryptUrl + ", x509SigningUrl=" + + x509SigningUrl + ", jwkSigningUrl=" + + jwkSigningUrl + "]"; } } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/UrlValidator.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/UrlValidator.java new file mode 100644 index 000000000..66c723f0a --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/UrlValidator.java @@ -0,0 +1,34 @@ +package org.mitre.openid.connect.client; + +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; + +public class UrlValidator implements Validator{ + + + + @Override + public boolean supports(Class clzz) { + return OIDCServerConfiguration.class.equals(clzz); + } + + @Override + public void validate(Object obj, Errors e) { + ValidationUtils.rejectIfEmpty(e, "x509EncryptUrl", "x509EncryptUrl.empty"); + + } + + public void validate1(Object obj, Errors e) { + ValidationUtils.rejectIfEmpty(e, "x509SigningUrl", "x509SigningUrl.empty"); + } + + public void validate2(Object obj, Errors e) { + ValidationUtils.rejectIfEmpty(e, "jwkEncryptUrl", "jwkEncryptUrl.empty"); + } + + public void validate3(Object obj, Errors e) { + ValidationUtils.rejectIfEmpty(e, "jwkSigningUrl", "jwkSigningUrl.empty"); + } + +} \ No newline at end of file diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/OIDCServerConfigurationTest.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/OIDCServerConfigurationTest.java new file mode 100644 index 000000000..67456b7a1 --- /dev/null +++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/OIDCServerConfigurationTest.java @@ -0,0 +1,104 @@ +package org.mitre.openid.connect.client; + +import java.net.URL; +import java.security.Key; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mitre.util.Utility; + +import junit.framework.TestCase; + +public class OIDCServerConfigurationTest extends TestCase { + + URL jwkUrl = this.getClass().getResource("/jwk/jwk"); + URL x509Url = this.getClass().getResource("/x509/x509"); + URL jwkEncryptedUrl = this.getClass().getResource("/jwk/jwkEncrypted"); + URL x509EncryptedUrl = this.getClass().getResource("/x509/x509Encrypted"); + OIDCServerConfiguration oidc; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + oidc = new OIDCServerConfiguration(); + super.setUp(); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link org.mitre.util.Utility#retrieveJwk(java.lang.String)}. + * @throws Exception + */ + @Test + public void testGetSigningKeyBoth() throws Exception { + oidc.setX509SigningUrl(x509Url.getPath()); + oidc.setJwkSigningUrl(jwkUrl.getPath()); + Key key = oidc.getSigningKey(); + assertEquals(key, Utility.retrieveX509Key(x509Url)); + } + + @Test + public void testGetSigningKeyJwk() throws Exception { + oidc.setX509SigningUrl(null); + oidc.setJwkSigningUrl(jwkUrl.getPath()); + Key key1 = oidc.getSigningKey(); + assertEquals(key1, Utility.retrieveJwkKey(jwkUrl)); + } + + @Test + public void testGetSigningKeyX509() throws Exception { + oidc.setX509SigningUrl(x509Url.getPath()); + oidc.setJwkSigningUrl(null); + Key key2 = oidc.getSigningKey(); + assertEquals(key2, Utility.retrieveX509Key(x509Url)); + } + + @Test + public void testGetSigningKeyNone() throws Exception { + oidc.setX509SigningUrl(null); + oidc.setJwkSigningUrl(null); + Key key3 = oidc.getSigningKey(); + assertEquals(key3, null); + } + + @Test + public void testGetEncryptionKeyBoth() throws Exception { + oidc.setX509EncryptUrl(x509EncryptedUrl.getPath()); + oidc.setJwkEncryptUrl(jwkEncryptedUrl.getPath()); + Key key = oidc.getEncryptionKey(); + assertEquals(key, Utility.retrieveX509Key(x509EncryptedUrl)); + } + + @Test + public void testGetEncryptionKeyJwk() throws Exception { + oidc.setX509EncryptUrl(null); + oidc.setJwkEncryptUrl(jwkEncryptedUrl.getPath()); + Key key1 = oidc.getEncryptionKey(); + assertEquals(key1, Utility.retrieveJwkKey(jwkEncryptedUrl)); + } + + @Test + public void testGetEncryptionKeyX509() throws Exception { + oidc.setX509EncryptUrl(x509EncryptedUrl.getPath()); + oidc.setJwkEncryptUrl(null); + Key key2 = oidc.getEncryptionKey(); + assertEquals(key2, Utility.retrieveX509Key(x509EncryptedUrl)); + } + + @Test + public void testGetEncryptionKeyNone() throws Exception { + oidc.setX509EncryptUrl(null); + oidc.setJwkEncryptUrl(null); + Key key3 = oidc.getEncryptionKey(); + assertEquals(key3, null); + } +} diff --git a/openid-connect-common/.classpath b/openid-connect-common/.classpath index 1b28ee5d7..5d8133f83 100644 --- a/openid-connect-common/.classpath +++ b/openid-connect-common/.classpath @@ -1,6 +1,8 @@ + + @@ -9,3 +11,4 @@ + diff --git a/openid-connect-common/.settings/org.eclipse.wst.common.component b/openid-connect-common/.settings/org.eclipse.wst.common.component index 1e24c2b7c..c681a1288 100644 --- a/openid-connect-common/.settings/org.eclipse.wst.common.component +++ b/openid-connect-common/.settings/org.eclipse.wst.common.component @@ -2,5 +2,6 @@ + diff --git a/openid-connect-common/src/main/java/org/mitre/jwk/model/AbstractJwk.java b/openid-connect-common/src/main/java/org/mitre/jwk/model/AbstractJwk.java new file mode 100644 index 000000000..483dbe170 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwk/model/AbstractJwk.java @@ -0,0 +1,66 @@ +package org.mitre.jwk.model; + +import com.google.gson.JsonObject; + +public abstract class AbstractJwk implements Jwk{ + + public static final String ALGORITHM = "alg"; + public static final String USE = "use"; + public static final String KEY_ID = "kid"; + + private String kid; + private String alg; + private String use; + + public AbstractJwk(JsonObject object){ + init(object); + } + + /* (non-Javadoc) + * @see org.mitre.jwk.model.Jwk2#getAlg() + */ + @Override + public String getAlg() { + return alg; + } + + public void setAlg(String alg) { + this.alg = alg; + } + + /* (non-Javadoc) + * @see org.mitre.jwk.model.Jwk2#getKid() + */ + @Override + public String getKid() { + return kid; + } + + public void setKid(String kid) { + this.kid = kid; + } + + /* (non-Javadoc) + * @see org.mitre.jwk.model.Jwk2#getUse() + */ + @Override + public String getUse() { + return use; + } + + public void setUse(String use) { + this.use = use; + } + + protected void init(JsonObject object){ + if(object.get(ALGORITHM) != null){ + setAlg(object.get(ALGORITHM).getAsString()); + } + if(object.get(KEY_ID) != null){ + setKid(object.get(KEY_ID).getAsString()); + } + if(object.get(USE) != null){ + setUse(object.get(USE).getAsString()); + } + } +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/jwk/model/EC.java b/openid-connect-common/src/main/java/org/mitre/jwk/model/EC.java new file mode 100644 index 000000000..c24bdd037 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwk/model/EC.java @@ -0,0 +1,96 @@ +package org.mitre.jwk.model; + +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.spec.ECFieldF2m; +import java.security.spec.EllipticCurve; +import java.security.spec.InvalidKeySpecException; + +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.provider.JCEECPublicKey; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; + +import com.google.gson.JsonObject; + +public class EC extends AbstractJwk{ + + public static final String CURVE = "crv"; + public static final String X = "x"; + public static final String Y = "y"; + + private String crv; + private String x; + private String y; + + JsonObject object = new JsonObject(); + + public String getCrv() { + return crv; + } + + public void setCrv(String crv) { + this.crv = crv; + } + + public String getX() { + return x; + } + + public void setX(String x) { + this.x = x; + } + + public String getY() { + return y; + } + + public void setY(String y) { + this.y = y; + } + + public EC(JsonObject object) { + super(object); + } + + public void init(JsonObject object){ + super.init(object); + setCrv(object.get(CURVE).getAsString()); + setX(object.get(X).getAsString()); + setY(object.get(Y).getAsString()); + } + + @Override + public PublicKey getKey() throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { + // TODO Auto-generated method stub + + byte[] x_byte = Base64.decodeBase64(x); + BigInteger x_int = new BigInteger(x_byte); + byte[] y_byte = Base64.decodeBase64(y); + BigInteger y_int = new BigInteger(y_byte); + + ECNamedCurveParameterSpec curveSpec = ECNamedCurveTable.getParameterSpec(crv); + BigInteger orderOfGen = curveSpec.getH(); + int cofactor = Math.abs(curveSpec.getN().intValue()); + ECCurve crv = curveSpec.getCurve(); + BigInteger a = crv.getA().toBigInteger(); + BigInteger b = crv.getB().toBigInteger(); + int fieldSize = crv.getFieldSize(); + ECFieldF2m field = new ECFieldF2m(fieldSize); + EllipticCurve curve = new EllipticCurve(field, a, b); + //ECPoint.Fp point = new ECPoint.Fp(curve, arg1, arg2); + return null; + + //ECParameterSpec paramSpec = new ECParameterSpec(curve, point, orderOfGen, cofactor); + //ECPublicKeySpec spec = new ECPublicKeySpec(point, paramSpec); + //PublicKey key = new JCEECPublicKey("ECDCA", spec); + + //return key; + } +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/jwk/model/Jwk.java b/openid-connect-common/src/main/java/org/mitre/jwk/model/Jwk.java new file mode 100644 index 000000000..67edc2b15 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwk/model/Jwk.java @@ -0,0 +1,18 @@ +package org.mitre.jwk.model; + +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.spec.InvalidKeySpecException; + +public interface Jwk { + + public abstract String getAlg(); + + public abstract String getKid(); + + public abstract String getUse(); + + public abstract Key getKey() throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException; + +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/jwk/model/Rsa.java b/openid-connect-common/src/main/java/org/mitre/jwk/model/Rsa.java new file mode 100644 index 000000000..dc1c795f7 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwk/model/Rsa.java @@ -0,0 +1,67 @@ +package org.mitre.jwk.model; + +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; + +import org.apache.commons.codec.binary.Base64; + +import com.google.gson.JsonObject; + +public class Rsa extends AbstractJwk{ + + public static final String MODULUS = "mod"; + public static final String EXPONENT = "exp"; + + private String mod; + private String exp; + + JsonObject object = new JsonObject(); + + public String getMod() { + return mod; + } + + public void setMod(String mod) { + this.mod = mod; + } + + public String getExp() { + return exp; + } + + public void setExp(String exp) { + this.exp = exp; + } + + public Rsa(JsonObject object){ + super(object); + } + + public void init(JsonObject object){ + super.init(object); + setMod(object.get(MODULUS).getAsString()); + setExp(object.get(EXPONENT).getAsString()); + } + + @Override + public PublicKey getKey() throws NoSuchAlgorithmException, InvalidKeySpecException { + // TODO Auto-generated method stub + byte[] modulusByte = Base64.decodeBase64(mod); + BigInteger modulus = new BigInteger(modulusByte); + byte[] exponentByte = Base64.decodeBase64(exp); + BigInteger exponent = new BigInteger(exponentByte); + + RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent); + KeyFactory factory = KeyFactory.getInstance("RSA"); + PublicKey pub = factory.generatePublic(spec); + + return pub; + } +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/util/Utility.java b/openid-connect-common/src/main/java/org/mitre/util/Utility.java index d6655286a..5bc809bd5 100644 --- a/openid-connect-common/src/main/java/org/mitre/util/Utility.java +++ b/openid-connect-common/src/main/java/org/mitre/util/Utility.java @@ -15,8 +15,34 @@ ******************************************************************************/ package org.mitre.util; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.net.URL; +import java.security.Key; +import java.security.KeyFactory; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.RSAPublicKeySpec; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.List; + import javax.servlet.http.HttpServletRequest; +import org.apache.commons.codec.binary.Base64; +import org.mitre.jwk.model.AbstractJwk; +import org.mitre.jwk.model.EC; +import org.mitre.jwk.model.Jwk; +import org.mitre.jwk.model.Rsa; + +import com.google.gson.JsonArray; +import com.google.gson.JsonIOException; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; + /** * A collection of utility methods. * @@ -43,4 +69,57 @@ public class Utility { } return issuer; } + + public static List retrieveJwk(URL path) throws Exception { + List keys = new ArrayList(); + + JsonParser parser = new JsonParser(); + JsonObject json = parser.parse(new BufferedReader(new InputStreamReader(path.openStream()))).getAsJsonObject(); + JsonArray getArray = json.getAsJsonArray("jwk"); + + for(int i = 0; i < getArray.size(); i++){ + + JsonObject object = getArray.get(i).getAsJsonObject(); + String algorithm = object.get("alg").getAsString(); + + if(algorithm.equals("RSA")){ + Rsa rsa = new Rsa(object); + keys.add(rsa); + } + + else{ + EC ec = new EC(object); + keys.add(ec); + } + } + return keys; + } + + public static Key retrieveX509Key(URL url) throws Exception { + + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) factory.generateCertificate(url.openStream()); + Key key = cert.getPublicKey(); + + return key; + } + + public static Key retrieveJwkKey(URL url) throws Exception { + + JsonParser parser = new JsonParser(); + JsonObject json = parser.parse(new BufferedReader(new InputStreamReader(url.openStream()))).getAsJsonObject(); + JsonArray getArray = json.getAsJsonArray("jwk"); + JsonObject object = getArray.get(0).getAsJsonObject(); + + byte[] modulusByte = Base64.decodeBase64(object.get("mod").getAsString()); + BigInteger modulus = new BigInteger(modulusByte); + byte[] exponentByte = Base64.decodeBase64(object.get("exp").getAsString()); + BigInteger exponent = new BigInteger(exponentByte); + + RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent); + KeyFactory factory = KeyFactory.getInstance("RSA"); + PublicKey pub = factory.generatePublic(spec); + + return pub; + } } diff --git a/openid-connect-common/src/test/java/org/mitre/util/UtilityTest.java b/openid-connect-common/src/test/java/org/mitre/util/UtilityTest.java new file mode 100644 index 000000000..6404eda77 --- /dev/null +++ b/openid-connect-common/src/test/java/org/mitre/util/UtilityTest.java @@ -0,0 +1,206 @@ +/** + * + */ +package org.mitre.util; + +import java.security.Key; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAPublicKeySpec; +import java.util.List; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.net.URL; + +import junit.framework.TestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mitre.jwk.model.Jwk; +import org.mitre.jwk.model.Rsa; +import org.mitre.jwk.model.EC; +import org.mitre.util.Utility; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import org.apache.commons.codec.binary.*; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.jce.provider.JCEECPublicKey; + +/** + * @author DERRYBERRY + * + */ +public class UtilityTest extends TestCase{ + + URL url = this.getClass().getResource("/jwk/jwkSuccess"); + URL certUrl = this.getClass().getResource("/x509/certTest"); + URL rsaUrl = this.getClass().getResource("/jwk/rsaOnly"); + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + super.setUp(); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link org.mitre.util.Utility#retrieveJwk(java.lang.String)}. + * @throws Exception + */ + @Test + public void testRetrieveJwk() throws Exception { + + JsonParser parser = new JsonParser(); + JsonObject json = parser.parse(new BufferedReader(new InputStreamReader(url.openStream()))).getAsJsonObject(); + JsonArray getArray = json.getAsJsonArray("jwk"); + + List list = Utility.retrieveJwk(url); + + for(int i = 0; i < list.size(); i++){ + + Jwk jwk = list.get(i); + JsonObject object = getArray.get(i).getAsJsonObject(); + + assertEquals(object.get("alg").getAsString(), jwk.getAlg()); + if(object.get("kid") != null){ + assertEquals(object.get("kid").getAsString(), jwk.getKid()); + } + if(object.get("use") != null){ + assertEquals(object.get("use").getAsString(), jwk.getUse()); + } + + if(jwk instanceof Rsa){ + assertEquals(object.get("mod").getAsString(), ((Rsa) jwk).getMod()); + assertEquals(object.get("exp").getAsString(), ((Rsa) jwk).getExp()); + } + else { + assertEquals(object.get("crv").getAsString(), ((EC) jwk).getCrv()); + assertEquals(object.get("x").getAsString(), ((EC) jwk).getX()); + assertEquals(object.get("y").getAsString(), ((EC) jwk).getY()); + } + } + } + + @Test + public void testMakeRsa() throws Exception{ + + JsonParser parser = new JsonParser(); + JsonObject json = parser.parse(new BufferedReader(new InputStreamReader(url.openStream()))).getAsJsonObject(); + JsonArray getArray = json.getAsJsonArray("jwk"); + + List list = Utility.retrieveJwk(url); + + for(int i = 0; i < list.size(); i++){ + Jwk jwk = list.get(i); + JsonObject object = getArray.get(i).getAsJsonObject(); + + if(jwk instanceof Rsa){ + + RSAPublicKey key = ((RSAPublicKey) ((Rsa) jwk).getKey()); + + byte[] mod = Base64.decodeBase64(object.get("mod").getAsString()); + BigInteger modInt = new BigInteger(mod); + assertEquals(modInt, key.getModulus()); + + byte[] exp = Base64.decodeBase64(object.get("exp").getAsString()); + BigInteger expInt = new BigInteger(exp); + assertEquals(expInt, key.getPublicExponent()); + } + } + } + + @Test + public void testRetriveX509Key() throws Exception { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate x509 = (X509Certificate) factory.generateCertificate(certUrl.openStream()); + Key key = Utility.retrieveX509Key(certUrl); + assertEquals(x509.getPublicKey(), key); + assertEquals("RSA", key.getAlgorithm()); + assertEquals("X.509", key.getFormat()); + } + + public void testRetriveJwkKey() throws Exception { + Key key = Utility.retrieveJwkKey(rsaUrl); + + JsonParser parser = new JsonParser(); + JsonObject json = parser.parse(new BufferedReader(new InputStreamReader(rsaUrl.openStream()))).getAsJsonObject(); + JsonArray getArray = json.getAsJsonArray("jwk"); + JsonObject object = getArray.get(0).getAsJsonObject(); + + byte[] modulusByte = Base64.decodeBase64(object.get("mod").getAsString()); + BigInteger modulus = new BigInteger(modulusByte); + byte[] exponentByte = Base64.decodeBase64(object.get("exp").getAsString()); + BigInteger exponent = new BigInteger(exponentByte); + + RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent); + KeyFactory factory = KeyFactory.getInstance("RSA"); + PublicKey pub = factory.generatePublic(spec); + + assertEquals(pub, key); + } + + //@Test + //public void testMakeEC() throws Exception{ + + /*JsonParser parser = new JsonParser(); + JsonObject json = parser.parse(new BufferedReader(new InputStreamReader(url.openStream()))).getAsJsonObject(); + JsonArray getArray = json.getAsJsonArray("jwk"); + + List list = Utility.retrieveJwk(url); + + for(int i = 0; i < list.size(); i++){ + Jwk jwk = list.get(i); + JsonObject object = getArray.get(i).getAsJsonObject(); + + if(jwk instanceof EC){ + + ECPublicKey key = ((ECPublicKey) ((EC) jwk).getKey()); + + byte[] xArray = Base64.decodeBase64(object.get("x").getAsString()); + BigInteger xInt = new BigInteger(xArray); + byte[] yArray = Base64.decodeBase64(object.get("y").getAsString()); + BigInteger yInt = new BigInteger(yArray); + + String curveName = object.get("crv").getAsString(); + ECNamedCurveParameterSpec curveSpec = ECNamedCurveTable.getParameterSpec(curveName); + ECCurve crv = curveSpec.getCurve(); + BigInteger a = crv.getA().toBigInteger(); + BigInteger b = crv.getB().toBigInteger(); + int fieldSize = crv.getFieldSize(); + BigInteger orderOfGen = curveSpec.getH(); + int cofactor = Math.abs(curveSpec.getN().intValue()); + + assertEquals(a, key.getParams().getCurve().getA()); + assertEquals(b, key.getParams().getCurve().getB()); + assertEquals(fieldSize, key.getParams().getCurve().getField()); + assertEquals(orderOfGen, key.getParams().getOrder()); + assertEquals(cofactor, key.getParams().getCofactor()); + assertEquals(xInt, key.getW().getAffineX()); + assertEquals(yInt, key.getW().getAffineY()); + } + }*/ + //fail("method not implemented"); + //} + + +} \ No newline at end of file diff --git a/openid-connect-common/src/test/resources/jwk/jwkFail b/openid-connect-common/src/test/resources/jwk/jwkFail new file mode 100644 index 000000000..4a208df47 --- /dev/null +++ b/openid-connect-common/src/test/resources/jwk/jwkFail @@ -0,0 +1,15 @@ + {"jwk": + [ + {"alg":"never", + "crv":"gonna", + "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "use":"give", + "kid":"1"}, + + {"alg":"you", + "mod": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "exp":"up", + "kid":"rick astley"} + ] + } \ No newline at end of file diff --git a/openid-connect-common/src/test/resources/jwk/jwkSuccess b/openid-connect-common/src/test/resources/jwk/jwkSuccess new file mode 100644 index 000000000..1ad6f5d5f --- /dev/null +++ b/openid-connect-common/src/test/resources/jwk/jwkSuccess @@ -0,0 +1,15 @@ + {"jwk": + [ + {"alg":"EC", + "crv":"P-256", + "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "use":"enc", + "kid":"1"}, + + {"alg":"RSA", + "mod": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "exp":"AQAB", + "kid":"2011-04-29"} + ] + } \ No newline at end of file From fad6caa968783d768814cc9f93f5530f8aa3ef91 Mon Sep 17 00:00:00 2001 From: Mike Derryberry Date: Thu, 7 Jun 2012 14:28:09 -0400 Subject: [PATCH 2/3] Added testing for signers for Hmac, Rsa, and Plaintext --- .../main/java/org/mitre/jwt/model/Jwt.java | 2 +- .../java/org/mitre/jwt/model/JwtClaims.java | 1 - .../mitre/jwt/signer/AbstractJwtSigner.java | 8 +- .../java/org/mitre/jwt/signer/JwtSigner.java | 6 +- .../org/mitre/jwt/signer/impl/HmacSigner.java | 17 +- .../org/mitre/jwt/signer/impl/RsaSigner.java | 17 +- .../JwtSigningAndValidationService.java | 7 +- ...bstractJwtSigningAndValidationService.java | 54 +++++++ ...DynamicJwtSigningAndValidationService.java | 146 ++++++++++++++++++ ...JwtSigningAndValidationServiceDefault.java | 109 ++++--------- .../mitre/jwt/signer/impl/HmacSignerTest.java | 73 +++++++++ .../jwt/signer/impl/PlaintextSignerTest.java | 55 +++++++ .../mitre/jwt/signer/impl/RsaSignerTest.java | 99 ++++++++++++ ...micJwtSigningAndValidationServiceTest.java | 64 ++++++++ .../test/java/org/mitre/util/UtilityTest.java | 2 +- .../src/test/resources/jwk/rsaOnly | 8 + .../src/test/resources/jwt/claims | 6 + .../src/test/resources/jwt/hs256 | 2 + .../src/test/resources/jwt/hs384 | 2 + .../src/test/resources/jwt/hs512 | 2 + .../src/test/resources/jwt/plaintext | 2 + .../src/test/resources/jwt/rs256 | 2 + .../src/test/resources/jwt/rs384 | 2 + .../src/test/resources/jwt/rs512 | 2 + .../src/test/resources/x509/HmacJwt | 6 + .../src/test/resources/x509/x509Cert | 15 ++ 26 files changed, 609 insertions(+), 100 deletions(-) create mode 100644 openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/AbstractJwtSigningAndValidationService.java create mode 100644 openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationService.java create mode 100644 openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/HmacSignerTest.java create mode 100644 openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/PlaintextSignerTest.java create mode 100644 openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/RsaSignerTest.java create mode 100644 openid-connect-common/src/test/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationServiceTest.java create mode 100644 openid-connect-common/src/test/resources/jwk/rsaOnly create mode 100644 openid-connect-common/src/test/resources/jwt/claims create mode 100644 openid-connect-common/src/test/resources/jwt/hs256 create mode 100644 openid-connect-common/src/test/resources/jwt/hs384 create mode 100644 openid-connect-common/src/test/resources/jwt/hs512 create mode 100644 openid-connect-common/src/test/resources/jwt/plaintext create mode 100644 openid-connect-common/src/test/resources/jwt/rs256 create mode 100644 openid-connect-common/src/test/resources/jwt/rs384 create mode 100644 openid-connect-common/src/test/resources/jwt/rs512 create mode 100644 openid-connect-common/src/test/resources/x509/HmacJwt create mode 100644 openid-connect-common/src/test/resources/x509/x509Cert diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/model/Jwt.java b/openid-connect-common/src/main/java/org/mitre/jwt/model/Jwt.java index 39d609bf3..ca0474b88 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/model/Jwt.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/model/Jwt.java @@ -133,7 +133,7 @@ public class Jwt { return h64 + "." + c64; } - + /** * Parse a wire-encoded JWT */ diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/model/JwtClaims.java b/openid-connect-common/src/main/java/org/mitre/jwt/model/JwtClaims.java index 0c4807baf..1831edf10 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/model/JwtClaims.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/model/JwtClaims.java @@ -25,7 +25,6 @@ import java.util.Map.Entry; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; public class JwtClaims extends ClaimSet { diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/AbstractJwtSigner.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/AbstractJwtSigner.java index 7aeeb4cb3..5b5e427a2 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/AbstractJwtSigner.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/AbstractJwtSigner.java @@ -15,6 +15,7 @@ ******************************************************************************/ package org.mitre.jwt.signer; +import java.security.NoSuchAlgorithmException; import java.util.List; import org.mitre.jwt.model.Jwt; @@ -52,9 +53,10 @@ public abstract class AbstractJwtSigner implements JwtSigner { * * @param jwt the jwt to sign * @return the signed jwt + * @throws NoSuchAlgorithmException */ @Override - public Jwt sign(Jwt jwt) { + public Jwt sign(Jwt jwt) throws NoSuchAlgorithmException { if (!Objects.equal(algorithm, jwt.getHeader().getAlgorithm())) { // algorithm type doesn't match // TODO: should this be an error or should we just fix it in the incoming jwt? @@ -73,7 +75,7 @@ public abstract class AbstractJwtSigner implements JwtSigner { * @see org.mitre.jwt.JwtSigner#verify(java.lang.String) */ @Override - public boolean verify(String jwtString) { + public boolean verify(String jwtString) throws NoSuchAlgorithmException { // split on the dots List parts = Lists.newArrayList(Splitter.on(".").split(jwtString)); @@ -92,5 +94,5 @@ public abstract class AbstractJwtSigner implements JwtSigner { } - protected abstract String generateSignature(String signatureBase); + protected abstract String generateSignature(String signatureBase) throws NoSuchAlgorithmException; } diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/JwtSigner.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/JwtSigner.java index a43314659..e2ab061b3 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/JwtSigner.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/JwtSigner.java @@ -15,12 +15,14 @@ ******************************************************************************/ package org.mitre.jwt.signer; +import java.security.NoSuchAlgorithmException; + import org.mitre.jwt.model.Jwt; public interface JwtSigner { - public Jwt sign(Jwt jwt); + public Jwt sign(Jwt jwt) throws NoSuchAlgorithmException; - public boolean verify(String jwtString); + public boolean verify(String jwtString) throws NoSuchAlgorithmException; } diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/impl/HmacSigner.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/impl/HmacSigner.java index 974ed43f1..5f93ff13a 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/impl/HmacSigner.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/impl/HmacSigner.java @@ -134,23 +134,24 @@ public class HmacSigner extends AbstractJwtSigner implements InitializingBean { * ) */ @Override - public String generateSignature(String signatureBase) { + public String generateSignature(String signatureBase) throws NoSuchAlgorithmException { + Mac _mac = getMac(); if (passphrase == null) { throw new IllegalArgumentException("Passphrase cannot be null"); } try { - mac.init(new SecretKeySpec(getPassphrase().getBytes(), mac + _mac.init(new SecretKeySpec(getPassphrase().getBytes(), mac .getAlgorithm())); - mac.update(signatureBase.getBytes("UTF-8")); + _mac.update(signatureBase.getBytes("UTF-8")); } catch (GeneralSecurityException e) { logger.error(e); } catch (UnsupportedEncodingException e) { logger.error(e); } - byte[] sigBytes = mac.doFinal(); + byte[] sigBytes = _mac.doFinal(); String sig = new String(Base64.encodeBase64URLSafe(sigBytes)); @@ -171,6 +172,14 @@ public class HmacSigner extends AbstractJwtSigner implements InitializingBean { public void setPassphrase(String passphrase) { this.passphrase = passphrase; } + + private Mac getMac() throws NoSuchAlgorithmException { + if(mac == null){ + mac = Mac.getInstance(JwsAlgorithm.getByName(super.getAlgorithm()) + .getStandardName()); + } + return mac; + } /* * (non-Javadoc) diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/impl/RsaSigner.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/impl/RsaSigner.java index d12869259..4b261a15e 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/impl/RsaSigner.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/impl/RsaSigner.java @@ -18,6 +18,7 @@ package org.mitre.jwt.signer.impl; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; @@ -173,15 +174,16 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean { * ) */ @Override - public String generateSignature(String signatureBase) { + public String generateSignature(String signatureBase) throws NoSuchAlgorithmException { String sig = null; + Signature _signer = getSigner(); try { - signer.initSign(privateKey); - signer.update(signatureBase.getBytes("UTF-8")); + _signer.initSign(privateKey); + _signer.update(signatureBase.getBytes("UTF-8")); - byte[] sigBytes = signer.sign(); + byte[] sigBytes = _signer.sign(); sig = (new String(Base64.encodeBase64URLSafe(sigBytes))).replace("=", ""); } catch (GeneralSecurityException e) { @@ -228,6 +230,13 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean { public void setPrivateKey(RSAPrivateKey privateKey) { this.privateKey = privateKey; } + + private Signature getSigner() throws NoSuchAlgorithmException{ + if(signer == null){ + signer = Signature.getInstance(JwsAlgorithm.getByName(super.getAlgorithm()).getStandardName()); + } + return signer; + } /* * (non-Javadoc) diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java index 44c956302..4f5ffbde7 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java @@ -15,6 +15,7 @@ ******************************************************************************/ package org.mitre.jwt.signer.service; +import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.util.List; import java.util.Map; @@ -59,8 +60,9 @@ public interface JwtSigningAndValidationService { * @param jwtString * the string representation of the JWT as sent on the wire * @return true if the signature is valid, false if not + * @throws NoSuchAlgorithmException */ - public boolean validateSignature(String jwtString); + public boolean validateSignature(String jwtString) throws NoSuchAlgorithmException; /** * Called to sign a jwt in place for a client that hasn't registered a preferred signing algorithm. @@ -68,8 +70,9 @@ public interface JwtSigningAndValidationService { * * @param jwt the jwt to sign * @return the signed jwt + * @throws NoSuchAlgorithmException */ - public void signJwt(Jwt jwt); + public void signJwt(Jwt jwt) throws NoSuchAlgorithmException; /** * Sign a jwt using the selected algorithm. The algorithm is selected using the String parameter values specified diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/AbstractJwtSigningAndValidationService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/AbstractJwtSigningAndValidationService.java new file mode 100644 index 000000000..ed4d99216 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/AbstractJwtSigningAndValidationService.java @@ -0,0 +1,54 @@ +package org.mitre.jwt.signer.service.impl; + +import java.security.NoSuchAlgorithmException; +import java.util.Date; +import java.util.Map; + +import org.mitre.jwt.model.Jwt; +import org.mitre.jwt.signer.JwtSigner; +import org.mitre.jwt.signer.service.JwtSigningAndValidationService; + +public abstract class AbstractJwtSigningAndValidationService implements JwtSigningAndValidationService{ + + /** + * Return the JwtSigners associated with this service + * + * @return + */ + public abstract Map getSigners(); + + @Override + public boolean isJwtExpired(Jwt jwt) { + + Date expiration = jwt.getClaims().getExpiration(); + + if (expiration != null) + return new Date().after(expiration); + else + return false; + + } + + @Override + public boolean validateIssuedJwt(Jwt jwt, String expectedIssuer) { + + String iss = jwt.getClaims().getIssuer(); + + if (iss.equals(expectedIssuer)) + return true; + + return false; + } + + @Override + public boolean validateSignature(String jwtString) throws NoSuchAlgorithmException { + + for (JwtSigner signer : getSigners().values()) { + if (signer.verify(jwtString)) + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationService.java new file mode 100644 index 000000000..efcd571ac --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationService.java @@ -0,0 +1,146 @@ +package org.mitre.jwt.signer.service.impl; + +import java.io.File; +import java.net.URL; +import java.security.Key; +import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.HashMap; +import java.util.Map; + +import org.mitre.jwt.model.Jwt; +import org.mitre.jwt.model.JwtHeader; +import org.mitre.jwt.signer.JwtSigner; +import org.mitre.jwt.signer.impl.HmacSigner; +import org.mitre.jwt.signer.impl.PlaintextSigner; +import org.mitre.jwt.signer.impl.RsaSigner; +import org.mitre.util.Utility; + + +public class DynamicJwtSigningAndValidationService extends AbstractJwtSigningAndValidationService{ + + private String x509SigningUrl; + + private String jwkSigningUrl; + + private String clientSecret; + + private Key signingKey; + + private Map map; + + private PublicKey publicKey; + + private Map signers; + + private String signingAlgorithm; + + + public DynamicJwtSigningAndValidationService(String x509SigningUrl, String jwkSigningUrl, String clientSecret) throws Exception { + setX509SigningUrl(x509SigningUrl); + setJwkSigningUrl(jwkSigningUrl); + setClientSecret(clientSecret); + } + + public Key getSigningKey() throws Exception { + if(signingKey == null){ + if(x509SigningUrl != null){ + File file = new File(x509SigningUrl); + URL url = file.toURI().toURL(); + signingKey = Utility.retrieveX509Key(url); + } + else if (jwkSigningUrl != null){ + File file = new File(jwkSigningUrl); + URL url = file.toURI().toURL(); + signingKey = Utility.retrieveJwkKey(url); + } + } + return signingKey; + } + + public String getSigningX509Url() { + return x509SigningUrl; + } + + public void setX509SigningUrl(String x509SigningUrl) { + this.x509SigningUrl = x509SigningUrl; + } + + public String getSigningJwkUrl() { + return jwkSigningUrl; + } + + public void setJwkSigningUrl(String jwkSigningUrl) { + this.jwkSigningUrl = jwkSigningUrl; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + @Override + public Map getAllPublicKeys() { + if(publicKey != null){ + //check to make sure key isn't null, return map + map.put(((RSAPublicKey) publicKey).getModulus() + .toString(16).toUpperCase() + + ((RSAPublicKey) publicKey).getPublicExponent() + .toString(16).toUpperCase(), publicKey); + } + return map; + } + + @Override + public void signJwt(Jwt jwt) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public Map getSigners() { + // TODO Auto-generated method stub + signers = new HashMap(); + return signers; + } + + @Override + public boolean validateSignature(String jwtString) { + + try { + JwtSigner signer = getSigner(jwtString); + return signer.verify(jwtString); + } + catch(Exception e) { + return false; + } + + } + + public JwtSigner getSigner(String str) throws Exception { + JwtHeader header = Jwt.parse(str).getHeader(); + String alg = header.getAlgorithm(); + JwtSigner signer = null; + + if(alg.equals("HS256") || alg.equals("HS384") || alg.equals("HS512")){ + signer = new HmacSigner(alg, clientSecret); + } + + else if (alg.equals("RS256") || alg.equals("RS384") || alg.equals("RS512")){ + signer = new RsaSigner(alg, (PublicKey) getSigningKey(), null); + } + + else if (alg.equals("none")){ + signer = new PlaintextSigner(); + } + + else{ + throw new IllegalArgumentException("Not an existing algorithm type"); + } + + return signer; + } +} diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java index 2237e19ac..595b46347 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java @@ -15,9 +15,9 @@ ******************************************************************************/ package org.mitre.jwt.signer.service.impl; +import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.interfaces.RSAPublicKey; -import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -31,14 +31,13 @@ import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; -public class JwtSigningAndValidationServiceDefault implements +public class JwtSigningAndValidationServiceDefault extends AbstractJwtSigningAndValidationService implements JwtSigningAndValidationService, InitializingBean { - @Autowired - private ConfigurationPropertiesBean configBean; + @Autowired ConfigurationPropertiesBean configBean; // map of identifier to signer - private Map signers = new HashMap(); + Map signers = new HashMap(); private static Log logger = LogFactory .getLog(JwtSigningAndValidationServiceDefault.class); @@ -111,34 +110,6 @@ public class JwtSigningAndValidationServiceDefault implements return map; } - /** - * Return the JwtSigners associated with this service - * - * @return - */ - public Map getSigners() { - return signers; - } - - /* - * (non-Javadoc) - * - * @see - * org.mitre.jwt.signer.service.JwtSigningAndValidationService#isJwtExpired - * (org.mitre.jwt.model.Jwt) - */ - @Override - public boolean isJwtExpired(Jwt jwt) { - - Date expiration = jwt.getClaims().getExpiration(); - - if (expiration != null) - return new Date().after(expiration); - else - return false; - - } - /** * Set the JwtSigners associated with this service * @@ -158,55 +129,6 @@ public class JwtSigningAndValidationServiceDefault implements + "]"; } - /* - * (non-Javadoc) - * - * @see - * org.mitre.jwt.signer.service.JwtSigningAndValidationService#validateIssuedJwt - * (org.mitre.jwt.model.Jwt) - */ - @Override - public boolean validateIssuedJwt(Jwt jwt, String expectedIssuer) { - - String iss = jwt.getClaims().getIssuer(); - - if (iss.equals(expectedIssuer)) - return true; - - return false; - } - - /* - * (non-Javadoc) - * - * @see - * org.mitre.jwt.signer.service.JwtSigningAndValidationService#validateSignature - * (java.lang.String) - */ - @Override - public boolean validateSignature(String jwtString) { - - for (JwtSigner signer : signers.values()) { - if (signer.verify(jwtString)) - return true; - } - - return false; - } - - /** - * Sign a jwt in place using the configured default signer. - */ - @Override - public void signJwt(Jwt jwt) { - String signerId = configBean.getDefaultJwtSigner(); - - JwtSigner signer = signers.get(signerId); - - signer.sign(jwt); - - } - /** * @return the configBean */ @@ -220,4 +142,27 @@ public class JwtSigningAndValidationServiceDefault implements public void setConfigBean(ConfigurationPropertiesBean configBean) { this.configBean = configBean; } + + /** + * Sign a jwt in place using the configured default signer. + * @throws NoSuchAlgorithmException + */ + @Override + public void signJwt(Jwt jwt) throws NoSuchAlgorithmException { + String signerId = configBean.getDefaultJwtSigner(); + + JwtSigner signer = getSigners().get(signerId); + + signer.sign(jwt); + + } + + /** + * Return the JwtSigners associated with this service + * + * @return + */ + public Map getSigners() { + return signers; + } } diff --git a/openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/HmacSignerTest.java b/openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/HmacSignerTest.java new file mode 100644 index 000000000..ddac6e860 --- /dev/null +++ b/openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/HmacSignerTest.java @@ -0,0 +1,73 @@ +package org.mitre.jwt.signer.impl; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; + +import junit.framework.TestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mitre.jwt.model.Jwt; +import org.mitre.jwt.model.JwtClaims; +import org.mitre.jwt.model.JwtHeader; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class HmacSignerTest extends TestCase { + + URL claimsUrl = this.getClass().getResource("/jwt/claims"); + URL hs256Url = this.getClass().getResource("/jwt/hs256"); + URL hs384Url = this.getClass().getResource("/jwt/hs384"); + URL hs512Url = this.getClass().getResource("/jwt/hs512"); + Jwt jwt = null; + JwtClaims claims = null; + JwtHeader header = null; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp(URL url) throws Exception { + JsonParser parser = new JsonParser(); + JsonObject claimsObject = parser.parse(new BufferedReader(new InputStreamReader(claimsUrl.openStream()))).getAsJsonObject(); + JsonObject headerObject = parser.parse(new BufferedReader(new InputStreamReader(url.openStream()))).getAsJsonObject(); + claims = new JwtClaims(claimsObject); + header = new JwtHeader(headerObject); + jwt = new Jwt(header, claims, null); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + @Test + public void testHmacSigner256() throws Exception { + setUp(hs256Url); + HmacSigner hmac = new HmacSigner(header.getAlgorithm(), "secret"); + jwt = hmac.sign(jwt); + assertEquals(hmac.verify(jwt.toString()), true); + } + + @Test + public void testHmacSigner384() throws Exception { + setUp(hs384Url); + HmacSigner hmac = new HmacSigner(header.getAlgorithm(), "secret"); + jwt = hmac.sign(jwt); + assertEquals(hmac.verify(jwt.toString()), true); + } + + @Test + public void testHmacSigner512() throws Exception { + setUp(hs512Url); + HmacSigner hmac = new HmacSigner(header.getAlgorithm(), "secret"); + jwt = hmac.sign(jwt); + assertEquals(hmac.verify(jwt.toString()), true); + } + +} diff --git a/openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/PlaintextSignerTest.java b/openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/PlaintextSignerTest.java new file mode 100644 index 000000000..446212872 --- /dev/null +++ b/openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/PlaintextSignerTest.java @@ -0,0 +1,55 @@ +package org.mitre.jwt.signer.impl; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mitre.jwt.model.Jwt; +import org.mitre.jwt.model.JwtClaims; +import org.mitre.jwt.model.JwtHeader; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import junit.framework.TestCase; + +public class PlaintextSignerTest extends TestCase { + + URL claimsUrl = this.getClass().getResource("/jwt/claims"); + URL plaintextUrl = this.getClass().getResource("/jwt/plaintext"); + Jwt jwt = null; + JwtClaims claims = null; + JwtHeader header = null; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp(URL url) throws Exception { + JsonParser parser = new JsonParser(); + JsonObject claimsObject = parser.parse(new BufferedReader(new InputStreamReader(claimsUrl.openStream()))).getAsJsonObject(); + JsonObject headerObject = parser.parse(new BufferedReader(new InputStreamReader(url.openStream()))).getAsJsonObject(); + claims = new JwtClaims(claimsObject); + header = new JwtHeader(headerObject); + jwt = new Jwt(header, claims, null); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + @Test + public void testPlaintextSigner() throws Exception { + setUp(plaintextUrl); + PlaintextSigner plaintext = new PlaintextSigner(); + jwt = plaintext.sign(jwt); + assertEquals(plaintext.verify(jwt.toString()), true); + } + +} diff --git a/openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/RsaSignerTest.java b/openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/RsaSignerTest.java new file mode 100644 index 000000000..cd74c577a --- /dev/null +++ b/openid-connect-common/src/test/java/org/mitre/jwt/signer/impl/RsaSignerTest.java @@ -0,0 +1,99 @@ +package org.mitre.jwt.signer.impl; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; + +import junit.framework.TestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mitre.jwt.model.Jwt; +import org.mitre.jwt.model.JwtClaims; +import org.mitre.jwt.model.JwtHeader; +import org.mitre.jwt.signer.JwsAlgorithm; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class RsaSignerTest extends TestCase { + + + URL claimsUrl = this.getClass().getResource("/jwt/claims"); + URL rs256Url = this.getClass().getResource("/jwt/rs256"); + URL rs384Url = this.getClass().getResource("/jwt/rs384"); + URL rs512Url = this.getClass().getResource("/jwt/rs512"); + Jwt jwt = null; + JwtClaims claims = null; + JwtHeader header = null; + KeyPairGenerator keyGen; + KeyPair keyPair; + PublicKey publicKey; + PrivateKey privateKey; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp(URL url) throws Exception { + JsonParser parser = new JsonParser(); + JsonObject claimsObject = parser.parse(new BufferedReader(new InputStreamReader(claimsUrl.openStream()))).getAsJsonObject(); + JsonObject headerObject = parser.parse(new BufferedReader(new InputStreamReader(url.openStream()))).getAsJsonObject(); + claims = new JwtClaims(claimsObject); + header = new JwtHeader(headerObject); + jwt = new Jwt(header, claims, null); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + @Test + public void testRsaSigner256() throws Exception { + setUp(rs256Url); + keyGen = KeyPairGenerator.getInstance("RSA"); + keyPair = keyGen.generateKeyPair(); + publicKey = keyPair.getPublic(); + privateKey = keyPair.getPrivate(); + RsaSigner rsa = new RsaSigner(JwsAlgorithm.RS256.toString(), publicKey, privateKey); + jwt = rsa.sign(jwt); + assertEquals(rsa.verify(jwt.toString()), true); + + } + + @Test + public void testRsaSigner384() throws Exception{ + setUp(rs384Url); + keyGen = KeyPairGenerator.getInstance("RSA"); + keyPair = keyGen.generateKeyPair(); + publicKey = keyPair.getPublic(); + privateKey = keyPair.getPrivate(); + RsaSigner rsa = new RsaSigner(JwsAlgorithm.RS384.toString(), publicKey, privateKey); + jwt = rsa.sign(jwt); + assertEquals(rsa.verify(jwt.toString()), true); + + } + + @Test + public void testRsaSigner512() throws Exception{ + setUp(rs512Url); + keyGen = KeyPairGenerator.getInstance("RSA"); + keyPair = keyGen.generateKeyPair(); + publicKey = keyPair.getPublic(); + privateKey = keyPair.getPrivate(); + RsaSigner rsa = new RsaSigner(JwsAlgorithm.RS512.toString(), publicKey, privateKey); + jwt = rsa.sign(jwt); + assertEquals(rsa.verify(jwt.toString()), true); + + } + + +} diff --git a/openid-connect-common/src/test/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationServiceTest.java b/openid-connect-common/src/test/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationServiceTest.java new file mode 100644 index 000000000..95391cea0 --- /dev/null +++ b/openid-connect-common/src/test/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationServiceTest.java @@ -0,0 +1,64 @@ +package org.mitre.jwt.signer.service.impl; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.security.Key; + +import junit.framework.TestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mitre.jwt.signer.JwtSigner; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class DynamicJwtSigningAndValidationServiceTest extends TestCase { + + URL x509Url = this.getClass().getResource("/x509/x509Cert"); + URL jwkUrl = this.getClass().getResource("/jwk/rsaOnly"); + Key jwkKey = null; + Key x509Key = null; + + DynamicJwtSigningAndValidationService jsvs; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + super.setUp(); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link org.mitre.util.Utility#retrieveJwk(java.lang.String)}. + * @throws Exception + */ + @Test + public void testGetSigner() throws Exception { + //create key, sign it, for both x509 and jwk. + /* jsvs.setX509SigningUrl(x509Url.getPath()); + x509Key = jsvs.getSigningKey(); + jsvs.setJwkSigningUrl(jwkUrl.getPath()); + jwkKey = jsvs.getSigningKey(); + + JsonParser parser = new JsonParser(); + + String rsaStr = parser.parse(new BufferedReader(new InputStreamReader(jwkUrl.openStream()))).getAsString(); + JwtSigner rsaSigner = jsvs.getSigner(rsaStr); + + String x509Str = parser.parse(new BufferedReader(new InputStreamReader(x509Url.openStream()))).getAsString(); + JwtSigner x509Signer = jsvs.getSigner(x509Str);*/ + assertEquals("yo", "yo"); + } + +} diff --git a/openid-connect-common/src/test/java/org/mitre/util/UtilityTest.java b/openid-connect-common/src/test/java/org/mitre/util/UtilityTest.java index 6404eda77..674e2306a 100644 --- a/openid-connect-common/src/test/java/org/mitre/util/UtilityTest.java +++ b/openid-connect-common/src/test/java/org/mitre/util/UtilityTest.java @@ -45,7 +45,7 @@ import org.bouncycastle.jce.provider.JCEECPublicKey; public class UtilityTest extends TestCase{ URL url = this.getClass().getResource("/jwk/jwkSuccess"); - URL certUrl = this.getClass().getResource("/x509/certTest"); + URL certUrl = this.getClass().getResource("/x509/x509Cert"); URL rsaUrl = this.getClass().getResource("/jwk/rsaOnly"); /** diff --git a/openid-connect-common/src/test/resources/jwk/rsaOnly b/openid-connect-common/src/test/resources/jwk/rsaOnly new file mode 100644 index 000000000..a5f42177d --- /dev/null +++ b/openid-connect-common/src/test/resources/jwk/rsaOnly @@ -0,0 +1,8 @@ + {"jwk": + [ + {"alg":"RSA", + "mod": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "exp":"AQAB", + "kid":"2011-04-29"} + ] + } \ No newline at end of file diff --git a/openid-connect-common/src/test/resources/jwt/claims b/openid-connect-common/src/test/resources/jwt/claims new file mode 100644 index 000000000..11f69c7e2 --- /dev/null +++ b/openid-connect-common/src/test/resources/jwt/claims @@ -0,0 +1,6 @@ +{"iss":"joe", + "user_id":34252452623, + "aud":"yolo", + "exp":35324583457247, + "iat":43215325235, + "nonce":"howdy"} \ No newline at end of file diff --git a/openid-connect-common/src/test/resources/jwt/hs256 b/openid-connect-common/src/test/resources/jwt/hs256 new file mode 100644 index 000000000..a4cdd0b17 --- /dev/null +++ b/openid-connect-common/src/test/resources/jwt/hs256 @@ -0,0 +1,2 @@ +{"typ":"JWT", + "alg":"HS256"} diff --git a/openid-connect-common/src/test/resources/jwt/hs384 b/openid-connect-common/src/test/resources/jwt/hs384 new file mode 100644 index 000000000..68f175c1a --- /dev/null +++ b/openid-connect-common/src/test/resources/jwt/hs384 @@ -0,0 +1,2 @@ +{"typ":"JWT", + "alg":"HS384"} diff --git a/openid-connect-common/src/test/resources/jwt/hs512 b/openid-connect-common/src/test/resources/jwt/hs512 new file mode 100644 index 000000000..42c727e09 --- /dev/null +++ b/openid-connect-common/src/test/resources/jwt/hs512 @@ -0,0 +1,2 @@ +{"typ":"JWT", + "alg":"HS512"} diff --git a/openid-connect-common/src/test/resources/jwt/plaintext b/openid-connect-common/src/test/resources/jwt/plaintext new file mode 100644 index 000000000..e16befde3 --- /dev/null +++ b/openid-connect-common/src/test/resources/jwt/plaintext @@ -0,0 +1,2 @@ +{"typ":"JWT", + "alg":"none"} \ No newline at end of file diff --git a/openid-connect-common/src/test/resources/jwt/rs256 b/openid-connect-common/src/test/resources/jwt/rs256 new file mode 100644 index 000000000..1d069af3f --- /dev/null +++ b/openid-connect-common/src/test/resources/jwt/rs256 @@ -0,0 +1,2 @@ +{"typ":"JWT", + "alg":"RS256"} \ No newline at end of file diff --git a/openid-connect-common/src/test/resources/jwt/rs384 b/openid-connect-common/src/test/resources/jwt/rs384 new file mode 100644 index 000000000..21ceb32e6 --- /dev/null +++ b/openid-connect-common/src/test/resources/jwt/rs384 @@ -0,0 +1,2 @@ +{"typ":"JWT", + "alg":"RS384"} diff --git a/openid-connect-common/src/test/resources/jwt/rs512 b/openid-connect-common/src/test/resources/jwt/rs512 new file mode 100644 index 000000000..37d614532 --- /dev/null +++ b/openid-connect-common/src/test/resources/jwt/rs512 @@ -0,0 +1,2 @@ +{"typ":"JWT", + "alg":"RS512"} diff --git a/openid-connect-common/src/test/resources/x509/HmacJwt b/openid-connect-common/src/test/resources/x509/HmacJwt new file mode 100644 index 000000000..f076c9dab --- /dev/null +++ b/openid-connect-common/src/test/resources/x509/HmacJwt @@ -0,0 +1,6 @@ +eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9 +. +eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt +cGxlLmNvbS9pc19yb290Ijp0cnVlfQ +. +dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk \ No newline at end of file diff --git a/openid-connect-common/src/test/resources/x509/x509Cert b/openid-connect-common/src/test/resources/x509/x509Cert new file mode 100644 index 000000000..2d60d2c3e --- /dev/null +++ b/openid-connect-common/src/test/resources/x509/x509Cert @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICxDCCAi0CBECcV/wwDQYJKoZIhvcNAQEEBQAwgagxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVU +ZXhhczEPMA0GA1UEBxMGQXVzdGluMSowKAYDVQQKEyFUaGUgVW5pdmVyc2l0eSBvZiBUZXhhcyBh +dCBBdXN0aW4xKDAmBgNVBAsTH0luZm9ybWF0aW9uIFRlY2hub2xvZ3kgU2VydmljZXMxIjAgBgNV +BAMTGXhtbGdhdGV3YXkuaXRzLnV0ZXhhcy5lZHUwHhcNMDQwNTA4MDM0NjA0WhcNMDQwODA2MDM0 +NjA0WjCBqDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0aW4xKjAo +BgNVBAoTIVRoZSBVbml2ZXJzaXR5IG9mIFRleGFzIGF0IEF1c3RpbjEoMCYGA1UECxMfSW5mb3Jt +YXRpb24gVGVjaG5vbG9neSBTZXJ2aWNlczEiMCAGA1UEAxMZeG1sZ2F0ZXdheS5pdHMudXRleGFz +LmVkdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsmc+6+NjLmanvh+FvBziYdBwTiz+d/DZ +Uy2jyvij6f8Xly6zkhHLSsuBzw08wPzr2K+F359bf9T3uiZMuao//FBGtDrTYpvQwkn4PFZwSeY2 +Ynw4edxp1JEWT2zfOY+QJDfNgpsYQ9hrHDwqnpbMVVqjdBq5RgTKGhFBj9kxEq0CAwEAATANBgkq +hkiG9w0BAQQFAAOBgQCPYGXF6oRbnjti3CPtjfwORoO7ab1QzNS9Z2rLMuPnt6POlm1A3UPEwCS8 +6flTlAqg19Sh47H7+Iq/LuzotKvUE5ugK52QRNMa4c0OSaO5UEM5EfVox1pT9tZV1Z3whYYMhThg +oC4y/On0NUVMN5xfF/GpSACga/bVjoNvd8HWEg== +-----END CERTIFICATE----- \ No newline at end of file From 65dc3daaf8b3b9efcd384182ccfa82983e34c7a2 Mon Sep 17 00:00:00 2001 From: Mike Derryberry Date: Tue, 12 Jun 2012 16:09:01 -0400 Subject: [PATCH 3/3] smart client --- openid-connect-client/.classpath | 4 +- .../org.eclipse.wst.common.component | 4 +- openid-connect-client/.springBeans | 14 ++ .../AbstractOIDCAuthenticationFilter.java | 159 +++++++----------- .../client/OIDCAuthenticationFilter.java | 11 +- .../OIDCAuthenticationUsingChooserFilter.java | 13 +- .../client/OIDCServerConfiguration.java | 27 ++- .../connect/client/OIDCUserDetailService.java | 39 +++++ .../client/OIDCServerConfigurationTest.java | 10 ++ .../src/test/resources/jwk/jwk | 8 + .../src/test/resources/jwk/jwkEncrypted | 8 + .../src/test/resources/x509/x509 | 15 ++ .../src/test/resources/x509/x509Encrypted | 15 ++ openid-connect-common/.classpath | 5 +- .../org.eclipse.wst.common.component | 1 - .../java/org/mitre/jwt/model/JwtClaims.java | 5 - .../JwtSigningAndValidationService.java | 34 +++- ...bstractJwtSigningAndValidationService.java | 31 ++++ ...DynamicJwtSigningAndValidationService.java | 37 +++- ...JwtSigningAndValidationServiceDefault.java | 33 +++- .../token/ConnectAuthCodeTokenGranter.java | 7 +- .../openid/connect/web/CheckIDEndpoint.java | 4 +- .../src/test/java/org/mitre/jwt/JwtTest.java | 3 +- 23 files changed, 359 insertions(+), 128 deletions(-) create mode 100644 openid-connect-client/.springBeans create mode 100644 openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCUserDetailService.java create mode 100644 openid-connect-client/src/test/resources/jwk/jwk create mode 100644 openid-connect-client/src/test/resources/jwk/jwkEncrypted create mode 100644 openid-connect-client/src/test/resources/x509/x509 create mode 100644 openid-connect-client/src/test/resources/x509/x509Encrypted diff --git a/openid-connect-client/.classpath b/openid-connect-client/.classpath index 40f68d2dc..57073c1a5 100644 --- a/openid-connect-client/.classpath +++ b/openid-connect-client/.classpath @@ -1,8 +1,8 @@ - - + + diff --git a/openid-connect-client/.settings/org.eclipse.wst.common.component b/openid-connect-client/.settings/org.eclipse.wst.common.component index e70fe2d47..6227d3014 100755 --- a/openid-connect-client/.settings/org.eclipse.wst.common.component +++ b/openid-connect-client/.settings/org.eclipse.wst.common.component @@ -1,8 +1,8 @@ - - + + diff --git a/openid-connect-client/.springBeans b/openid-connect-client/.springBeans new file mode 100644 index 000000000..707e4bb7c --- /dev/null +++ b/openid-connect-client/.springBeans @@ -0,0 +1,14 @@ + + + 1 + + + + + + + spring-servlet.xml + + + + diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AbstractOIDCAuthenticationFilter.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AbstractOIDCAuthenticationFilter.java index 49c775859..dca5b1724 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AbstractOIDCAuthenticationFilter.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AbstractOIDCAuthenticationFilter.java @@ -28,6 +28,7 @@ import java.security.SecureRandom; import java.security.Signature; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; @@ -42,9 +43,16 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.DefaultHttpClient; import org.mitre.openid.connect.model.IdToken; +import org.mitre.jwt.model.Jwt; +import org.mitre.jwt.model.JwtHeader; +import org.mitre.jwt.model.JwtClaims; +import org.mitre.jwt.signer.AbstractJwtSigner; +import org.mitre.jwt.signer.impl.HmacSigner; +import org.mitre.jwt.signer.service.impl.DynamicJwtSigningAndValidationService; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; @@ -60,6 +68,7 @@ import org.springframework.web.util.WebUtils; import com.google.common.base.Splitter; import com.google.common.collect.Lists; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; /** @@ -376,11 +385,12 @@ public class AbstractOIDCAuthenticationFilter extends * authentication * @return The authenticated user token, or null if authentication is * incomplete. + * @throws Exception * @throws UnsupportedEncodingException */ protected Authentication handleAuthorizationGrantResponse( String authorizationGrant, HttpServletRequest request, - OIDCServerConfiguration serverConfig) { + OIDCServerConfiguration serverConfig) throws Exception { final boolean debug = logger.isDebugEnabled(); @@ -457,42 +467,45 @@ public class AbstractOIDCAuthenticationFilter extends // Extract the id_token to insert into the // OpenIdConnectAuthenticationToken - + + Cookie nonceSignatureCookie = WebUtils.getCookie(request, + NONCE_SIGNATURE_COOKIE_NAME); + IdToken idToken = null; - + DynamicJwtSigningAndValidationService dynamic = new DynamicJwtSigningAndValidationService(null, null, null); + OIDCServerConfiguration oidc = new OIDCServerConfiguration(); + if (jsonRoot.getAsJsonObject().get("id_token") != null) { - try { - idToken = IdToken.parse(jsonRoot.getAsJsonObject() - .get("id_token").getAsString()); + if(dynamic.validateSignature(jsonRoot.getAsJsonObject().get("id_token").getAsString()) + && + dynamic.validateIssuedJwt(idToken, oidc.getIssuer()) + && + dynamic.validateAudience(idToken, oidc.getClientId()) + && + dynamic.isJwtExpired(idToken) + && + dynamic.validateIssuedAt(idToken) + && + dynamic.validateNonce(idToken, nonceSignatureCookie.getValue())){ + + try { + idToken = IdToken.parse(jsonRoot.getAsJsonObject().get("id_token").getAsString()); + + } catch (Exception e) { - List parts = Lists.newArrayList(Splitter.on(".") - .split(jsonRoot.getAsJsonObject().get("id_token") - .getAsString())); + // I suspect this could happen - if (parts.size() != 3) { - throw new IllegalArgumentException( - "Invalid JWT format."); + logger.error("Problem parsing id_token: " + e); + // e.printStackTrace(); + + throw new AuthenticationServiceException( + "Problem parsing id_token return from Token endpoint: " + + e); } - - String h64 = parts.get(0); - String c64 = parts.get(1); - String s64 = parts.get(2); - - logger.debug("h64 = " + h64); - logger.debug("c64 = " + c64); - logger.debug("s64 = " + s64); - - } catch (Exception e) { - - // I suspect this could happen - - logger.error("Problem parsing id_token: " + e); - // e.printStackTrace(); - - throw new AuthenticationServiceException( - "Problem parsing id_token return from Token endpoint: " - + e); + } + else{ + throw new AuthenticationServiceException("Problem verifying id_token"); } } else { @@ -505,100 +518,58 @@ public class AbstractOIDCAuthenticationFilter extends "Token Endpoint did not return a token_id"); } - // Handle Check ID Endpoint interaction + - httpClient = new DefaultHttpClient(); - httpClient.getParams().setParameter("http.socket.timeout", - new Integer(httpSocketTimeout)); - - factory = new HttpComponentsClientHttpRequestFactory(httpClient); - restTemplate = new RestTemplate(factory); - - form = new LinkedMultiValueMap(); - - form.add("access_token", jsonRoot.getAsJsonObject().get("id_token") - .getAsString()); - - jsonString = null; - - try { - jsonString = restTemplate.postForObject( - serverConfig.getCheckIDEndpointURI(), form, - String.class); - } catch (HttpClientErrorException httpClientErrorException) { - - // Handle error - - logger.error("Check ID Endpoint error response: " - + httpClientErrorException.getStatusText() + " : " - + httpClientErrorException.getMessage()); - - throw new AuthenticationServiceException("Unable check token."); - } - - jsonRoot = new JsonParser().parse(jsonString); // String iss = jsonRoot.getAsJsonObject().get("iss") // .getAsString(); - String userId = jsonRoot.getAsJsonObject().get("user_id") - .getAsString(); + //String userId = jsonRoot.getAsJsonObject().get("user_id") + // .getAsString(); // String aud = jsonRoot.getAsJsonObject().get("aud") // .getAsString(); - String nonce = jsonRoot.getAsJsonObject().get("nonce") - .getAsString(); + //String nonce = jsonRoot.getAsJsonObject().get("nonce") + // .getAsString(); // String exp = jsonRoot.getAsJsonObject().get("exp") // .getAsString(); // Compare returned ID Token to signed session cookie // to detect ID Token replay by third parties. - Cookie nonceSignatureCookie = WebUtils.getCookie(request, - NONCE_SIGNATURE_COOKIE_NAME); - if (nonceSignatureCookie != null) { - String sigText = nonceSignatureCookie.getValue(); + String sigText = nonceSignatureCookie.getValue(); - if (sigText != null && !sigText.isEmpty()) { + if (sigText != null && !sigText.isEmpty()) { - if (!verify(signer, publicKey, nonce, sigText)) { - logger.error("Possible replay attack detected! " - + "The comparison of the nonce in the returned " - + "ID Token to the signed session " - + NONCE_SIGNATURE_COOKIE_NAME + " failed."); - - throw new AuthenticationServiceException( - "Possible replay attack detected! " - + "The comparison of the nonce in the returned " - + "ID Token to the signed session " - + NONCE_SIGNATURE_COOKIE_NAME - + " failed."); - } - - } else { - logger.error(NONCE_SIGNATURE_COOKIE_NAME - + " was found, but was null or empty."); + if (!verify(signer, publicKey, idToken.getClaims().getNonce(), sigText)) { + logger.error("Possible replay attack detected! " + + "The comparison of the nonce in the returned " + + "ID Token to the signed session " + + NONCE_SIGNATURE_COOKIE_NAME + " failed."); throw new AuthenticationServiceException( - NONCE_SIGNATURE_COOKIE_NAME - + " was found, but was null or empty."); + "Possible replay attack detected! " + + "The comparison of the nonce in the returned " + + "ID Token to the signed session " + + NONCE_SIGNATURE_COOKIE_NAME + + " failed."); } } else { - logger.error(NONCE_SIGNATURE_COOKIE_NAME - + " cookie was not found."); + + " was found, but was null or empty."); throw new AuthenticationServiceException( - NONCE_SIGNATURE_COOKIE_NAME + " cookie was not found."); + NONCE_SIGNATURE_COOKIE_NAME + + " was found, but was null or empty."); } // Create an Authentication object for the token, and // return. OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken( - userId, idToken); + idToken.getTokenClaims().getUserId(), idToken); Authentication authentication = this.getAuthenticationManager() .authenticate(token); diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java index 4ec398cbd..2797c7ff1 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java @@ -97,9 +97,14 @@ public class OIDCAuthenticationFilter extends AbstractOIDCAuthenticationFilter { } else if (StringUtils.isNotBlank(request.getParameter("code"))) { - return handleAuthorizationGrantResponse( - request.getParameter("code"), new SanatizedRequest(request, - new String[] { "code" }), oidcServerConfig); + try { + return handleAuthorizationGrantResponse( + request.getParameter("code"), new SanatizedRequest(request, + new String[] { "code" }), oidcServerConfig); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } else { diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationUsingChooserFilter.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationUsingChooserFilter.java index 9a420dc65..ccfc6dd93 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationUsingChooserFilter.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationUsingChooserFilter.java @@ -108,10 +108,15 @@ public class OIDCAuthenticationUsingChooserFilter extends Cookie issuerCookie = WebUtils.getCookie(request, ISSUER_COOKIE_NAME); - return handleAuthorizationGrantResponse( - request.getParameter("code"), new SanatizedRequest(request, - new String[] { "code" }), - oidcServerConfigs.get(issuerCookie.getValue())); + try { + return handleAuthorizationGrantResponse( + request.getParameter("code"), new SanatizedRequest(request, + new String[] { "code" }), + oidcServerConfigs.get(issuerCookie.getValue())); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } else { diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCServerConfiguration.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCServerConfiguration.java index 2d0d226b2..1aeb6a23c 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCServerConfiguration.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCServerConfiguration.java @@ -16,19 +16,19 @@ package org.mitre.openid.connect.client; import java.io.File; -import java.net.MalformedURLException; import java.net.URL; import java.security.Key; +import org.mitre.jwt.signer.service.impl.DynamicJwtSigningAndValidationService; import org.mitre.util.Utility; -import com.google.gson.JsonObject; - /** * @author nemonik * */ public class OIDCServerConfiguration { + + DynamicJwtSigningAndValidationService dynamic; private String authorizationEndpointURI; @@ -40,6 +40,8 @@ public class OIDCServerConfiguration { private String clientId; + private String issuer; + private String x509EncryptUrl; private String x509SigningUrl; @@ -63,6 +65,10 @@ public class OIDCServerConfiguration { public String getClientId() { return clientId; } + + public String getIssuer() { + return issuer; + } public String getClientSecret() { return clientSecret; @@ -84,6 +90,10 @@ public class OIDCServerConfiguration { this.clientId = clientId; } + public void setIssuer(String issuer) { + this.issuer = issuer; + } + public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; } @@ -169,11 +179,18 @@ public class OIDCServerConfiguration { + authorizationEndpointURI + ", tokenEndpointURI=" + tokenEndpointURI + ", checkIDEndpointURI=" + checkIDEndpointURI + ", clientSecret=" + clientSecret - + ", clientId=" + clientId + ", x509EncryptedUrl=" + + ", clientId=" + clientId + ", issuer=" + issuer + +", x509EncryptedUrl=" + x509EncryptUrl + ", jwkEncryptedUrl=" + jwkEncryptUrl + ", x509SigningUrl=" + x509SigningUrl + ", jwkSigningUrl=" + jwkSigningUrl + "]"; } + + public DynamicJwtSigningAndValidationService getDynamic() throws Exception{ + dynamic = new DynamicJwtSigningAndValidationService(getX509SigningUrl(), getJwkSigningUrl(), getClientSecret()); + return dynamic; + } -} + +} \ No newline at end of file diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCUserDetailService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCUserDetailService.java new file mode 100644 index 000000000..64a0980b1 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCUserDetailService.java @@ -0,0 +1,39 @@ +package org.mitre.openid.connect.client; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; + +import org.mitre.openid.connect.model.IdToken; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +public class OIDCUserDetailService implements UserDetailsService, + AuthenticationUserDetailsService { + + public IdToken retrieveToken(URL url) throws IOException{ + String str = new BufferedReader(new InputStreamReader(url.openStream())).toString(); + IdToken idToken = IdToken.parse(str); + return idToken; + } + + + @Override + public UserDetails loadUserDetails(OpenIdConnectAuthenticationToken token) + throws UsernameNotFoundException { + // TODO Auto-generated method stub + + return null; + } + + @Override + public UserDetails loadUserByUsername(String username) + throws UsernameNotFoundException { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/OIDCServerConfigurationTest.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/OIDCServerConfigurationTest.java index 67456b7a1..e3ce69fbe 100644 --- a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/OIDCServerConfigurationTest.java +++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/OIDCServerConfigurationTest.java @@ -101,4 +101,14 @@ public class OIDCServerConfigurationTest extends TestCase { Key key3 = oidc.getEncryptionKey(); assertEquals(key3, null); } + + @Test + public void testGetDynamic() throws Exception { + oidc.setX509SigningUrl(x509Url.getPath()); + oidc.setJwkSigningUrl(jwkUrl.getPath()); + oidc.setClientSecret("foo"); + assertEquals(oidc.getDynamic().getSigningX509Url(), x509Url.getPath()); + assertEquals(oidc.getDynamic().getSigningJwkUrl(), jwkUrl.getPath()); + assertEquals(oidc.getDynamic().getClientSecret(), "foo"); + } } diff --git a/openid-connect-client/src/test/resources/jwk/jwk b/openid-connect-client/src/test/resources/jwk/jwk new file mode 100644 index 000000000..a5f42177d --- /dev/null +++ b/openid-connect-client/src/test/resources/jwk/jwk @@ -0,0 +1,8 @@ + {"jwk": + [ + {"alg":"RSA", + "mod": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "exp":"AQAB", + "kid":"2011-04-29"} + ] + } \ No newline at end of file diff --git a/openid-connect-client/src/test/resources/jwk/jwkEncrypted b/openid-connect-client/src/test/resources/jwk/jwkEncrypted new file mode 100644 index 000000000..a5f42177d --- /dev/null +++ b/openid-connect-client/src/test/resources/jwk/jwkEncrypted @@ -0,0 +1,8 @@ + {"jwk": + [ + {"alg":"RSA", + "mod": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "exp":"AQAB", + "kid":"2011-04-29"} + ] + } \ No newline at end of file diff --git a/openid-connect-client/src/test/resources/x509/x509 b/openid-connect-client/src/test/resources/x509/x509 new file mode 100644 index 000000000..2d60d2c3e --- /dev/null +++ b/openid-connect-client/src/test/resources/x509/x509 @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICxDCCAi0CBECcV/wwDQYJKoZIhvcNAQEEBQAwgagxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVU +ZXhhczEPMA0GA1UEBxMGQXVzdGluMSowKAYDVQQKEyFUaGUgVW5pdmVyc2l0eSBvZiBUZXhhcyBh +dCBBdXN0aW4xKDAmBgNVBAsTH0luZm9ybWF0aW9uIFRlY2hub2xvZ3kgU2VydmljZXMxIjAgBgNV +BAMTGXhtbGdhdGV3YXkuaXRzLnV0ZXhhcy5lZHUwHhcNMDQwNTA4MDM0NjA0WhcNMDQwODA2MDM0 +NjA0WjCBqDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0aW4xKjAo +BgNVBAoTIVRoZSBVbml2ZXJzaXR5IG9mIFRleGFzIGF0IEF1c3RpbjEoMCYGA1UECxMfSW5mb3Jt +YXRpb24gVGVjaG5vbG9neSBTZXJ2aWNlczEiMCAGA1UEAxMZeG1sZ2F0ZXdheS5pdHMudXRleGFz +LmVkdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsmc+6+NjLmanvh+FvBziYdBwTiz+d/DZ +Uy2jyvij6f8Xly6zkhHLSsuBzw08wPzr2K+F359bf9T3uiZMuao//FBGtDrTYpvQwkn4PFZwSeY2 +Ynw4edxp1JEWT2zfOY+QJDfNgpsYQ9hrHDwqnpbMVVqjdBq5RgTKGhFBj9kxEq0CAwEAATANBgkq +hkiG9w0BAQQFAAOBgQCPYGXF6oRbnjti3CPtjfwORoO7ab1QzNS9Z2rLMuPnt6POlm1A3UPEwCS8 +6flTlAqg19Sh47H7+Iq/LuzotKvUE5ugK52QRNMa4c0OSaO5UEM5EfVox1pT9tZV1Z3whYYMhThg +oC4y/On0NUVMN5xfF/GpSACga/bVjoNvd8HWEg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/openid-connect-client/src/test/resources/x509/x509Encrypted b/openid-connect-client/src/test/resources/x509/x509Encrypted new file mode 100644 index 000000000..2d60d2c3e --- /dev/null +++ b/openid-connect-client/src/test/resources/x509/x509Encrypted @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICxDCCAi0CBECcV/wwDQYJKoZIhvcNAQEEBQAwgagxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVU +ZXhhczEPMA0GA1UEBxMGQXVzdGluMSowKAYDVQQKEyFUaGUgVW5pdmVyc2l0eSBvZiBUZXhhcyBh +dCBBdXN0aW4xKDAmBgNVBAsTH0luZm9ybWF0aW9uIFRlY2hub2xvZ3kgU2VydmljZXMxIjAgBgNV +BAMTGXhtbGdhdGV3YXkuaXRzLnV0ZXhhcy5lZHUwHhcNMDQwNTA4MDM0NjA0WhcNMDQwODA2MDM0 +NjA0WjCBqDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0aW4xKjAo +BgNVBAoTIVRoZSBVbml2ZXJzaXR5IG9mIFRleGFzIGF0IEF1c3RpbjEoMCYGA1UECxMfSW5mb3Jt +YXRpb24gVGVjaG5vbG9neSBTZXJ2aWNlczEiMCAGA1UEAxMZeG1sZ2F0ZXdheS5pdHMudXRleGFz +LmVkdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsmc+6+NjLmanvh+FvBziYdBwTiz+d/DZ +Uy2jyvij6f8Xly6zkhHLSsuBzw08wPzr2K+F359bf9T3uiZMuao//FBGtDrTYpvQwkn4PFZwSeY2 +Ynw4edxp1JEWT2zfOY+QJDfNgpsYQ9hrHDwqnpbMVVqjdBq5RgTKGhFBj9kxEq0CAwEAATANBgkq +hkiG9w0BAQQFAAOBgQCPYGXF6oRbnjti3CPtjfwORoO7ab1QzNS9Z2rLMuPnt6POlm1A3UPEwCS8 +6flTlAqg19Sh47H7+Iq/LuzotKvUE5ugK52QRNMa4c0OSaO5UEM5EfVox1pT9tZV1Z3whYYMhThg +oC4y/On0NUVMN5xfF/GpSACga/bVjoNvd8HWEg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/openid-connect-common/.classpath b/openid-connect-common/.classpath index 5d8133f83..191f61b4a 100644 --- a/openid-connect-common/.classpath +++ b/openid-connect-common/.classpath @@ -1,8 +1,8 @@ - - + + @@ -11,4 +11,3 @@ - diff --git a/openid-connect-common/.settings/org.eclipse.wst.common.component b/openid-connect-common/.settings/org.eclipse.wst.common.component index c681a1288..1e24c2b7c 100644 --- a/openid-connect-common/.settings/org.eclipse.wst.common.component +++ b/openid-connect-common/.settings/org.eclipse.wst.common.component @@ -2,6 +2,5 @@ - diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/model/JwtClaims.java b/openid-connect-common/src/main/java/org/mitre/jwt/model/JwtClaims.java index 1831edf10..bc6bc9aca 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/model/JwtClaims.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/model/JwtClaims.java @@ -15,12 +15,7 @@ ******************************************************************************/ package org.mitre.jwt.model; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Date; -import java.util.HashMap; -import java.util.Map; import java.util.Map.Entry; import com.google.gson.JsonElement; diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java index 4f5ffbde7..0cb60026f 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java @@ -17,7 +17,6 @@ package org.mitre.jwt.signer.service; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; -import java.util.List; import java.util.Map; import org.mitre.jwt.model.Jwt; @@ -72,6 +71,39 @@ public interface JwtSigningAndValidationService { * @return the signed jwt * @throws NoSuchAlgorithmException */ + public boolean validateIssuedAt(Jwt jwt); + + /** + * Checks to see when this JWT was issued + * + * @param jwt + * the JWT to check + * @return true if the issued at is valid, false if not + * @throws NoSuchAlgorithmException + */ + public boolean validateAudience(Jwt jwt, String clientId); + + /** + * Checks the audience that the given JWT against the client_id of the Client + * + * @param jwt + * @param clientId + * the string representation of the client_id + * @return true if the audience matches the clinet_id, false if otherwise + * @throws NoSuchAlgorithmException + */ + public boolean validateNonce(Jwt jwt, String nonce); + + /** + * Checks to see if the nonce parameter sent in the Authorization Request + * is equal to the nonce parameter in the id token + * + * @param jwt + * @param nonce + * the string representation of the Nonce + * @return true if both nonce parameters are equal, false if otherwise + * @throws NoSuchAlgorithmException + */ public void signJwt(Jwt jwt) throws NoSuchAlgorithmException; /** diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/AbstractJwtSigningAndValidationService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/AbstractJwtSigningAndValidationService.java index ed4d99216..8d3a8d437 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/AbstractJwtSigningAndValidationService.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/AbstractJwtSigningAndValidationService.java @@ -50,5 +50,36 @@ public abstract class AbstractJwtSigningAndValidationService implements JwtSigni return false; } + + @Override + public boolean validateIssuedAt(Jwt jwt) { + Date issuedAt = jwt.getClaims().getIssuedAt(); + + if (issuedAt != null) + return new Date().before(issuedAt); + else + return false; + } + + @Override + public boolean validateAudience(Jwt jwt, String clientId) { + + if(jwt.getClaims().getAudience().equals(clientId)){ + return true; + } + else{ + return false; + } + } + + @Override + public boolean validateNonce(Jwt jwt, String nonce) { + if(jwt.getClaims().getNonce().equals(nonce)){ + return true; + } + else{ + return false; + } + } } \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationService.java index efcd571ac..cf337051e 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationService.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DynamicJwtSigningAndValidationService.java @@ -5,6 +5,7 @@ import java.net.URL; import java.security.Key; import java.security.PublicKey; import java.security.interfaces.RSAPublicKey; +import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -33,9 +34,6 @@ public class DynamicJwtSigningAndValidationService extends AbstractJwtSigningAnd private Map signers; - private String signingAlgorithm; - - public DynamicJwtSigningAndValidationService(String x509SigningUrl, String jwkSigningUrl, String clientSecret) throws Exception { setX509SigningUrl(x509SigningUrl); setJwkSigningUrl(jwkSigningUrl); @@ -143,4 +141,35 @@ public class DynamicJwtSigningAndValidationService extends AbstractJwtSigningAnd return signer; } -} + + @Override + public boolean validateIssuedAt(Jwt jwt) { + Date issuedAt = jwt.getClaims().getIssuedAt(); + + if (issuedAt != null) + return new Date().before(issuedAt); + else + return false; + } + + @Override + public boolean validateAudience(Jwt jwt, String clientId) { + + if(jwt.getClaims().getAudience().equals(clientId)){ + return true; + } + else{ + return false; + } + } + + @Override + public boolean validateNonce(Jwt jwt, String nonce) { + if(jwt.getClaims().getNonce().equals(nonce)){ + return true; + } + else{ + return false; + } + } +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java index 8d4ddd162..77eb8cd83 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java @@ -17,7 +17,7 @@ package org.mitre.jwt.signer.service.impl; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; -import java.security.interfaces.RSAPublicKey; +import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -163,4 +163,35 @@ public class JwtSigningAndValidationServiceDefault extends AbstractJwtSigningAnd public Map getSigners() { return signers; } + + @Override + public boolean validateIssuedAt(Jwt jwt) { + Date issuedAt = jwt.getClaims().getIssuedAt(); + + if (issuedAt != null) + return new Date().before(issuedAt); + else + return false; + } + + @Override + public boolean validateAudience(Jwt jwt, String clientId) { + + if(clientId.equals(jwt.getClaims().getAudience())){ + return true; + } + else{ + return false; + } + } + + @Override + public boolean validateNonce(Jwt jwt, String nonce) { + if(nonce.equals(jwt.getClaims().getNonce())){ + return true; + } + else{ + return false; + } + } } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectAuthCodeTokenGranter.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectAuthCodeTokenGranter.java index f3b56d6bb..3615953ed 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectAuthCodeTokenGranter.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectAuthCodeTokenGranter.java @@ -18,6 +18,7 @@ */ package org.mitre.openid.connect.token; +import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.Map; import java.util.Set; @@ -30,6 +31,7 @@ import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.mitre.openid.connect.model.IdToken; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; @@ -112,10 +114,13 @@ public class ConnectAuthCodeTokenGranter implements TokenGranter { * @param parameters * @param clientId * @param scope + * @throws NoSuchAlgorithmException + * @throws AuthenticationException + * @throws InvalidGrantException */ @Override public OAuth2AccessToken grant(String grantType, - Map parameters, String clientId, Set scope) { + Map parameters, String clientId, Set scope) throws NoSuchAlgorithmException, InvalidGrantException, AuthenticationException { if (!GRANT_TYPE.equals(grantType)) { return null; diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java index aa859b3cc..a7ef053db 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java @@ -15,6 +15,8 @@ ******************************************************************************/ package org.mitre.openid.connect.web; +import java.security.NoSuchAlgorithmException; + import javax.servlet.http.HttpServletRequest; import org.mitre.jwt.signer.service.JwtSigningAndValidationService; @@ -44,7 +46,7 @@ public class CheckIDEndpoint { @PreAuthorize("hasRole('ROLE_USER')") @RequestMapping("/checkid") - public ModelAndView checkID(@RequestParam("access_token") String tokenString, ModelAndView mav, HttpServletRequest request) { + public ModelAndView checkID(@RequestParam("access_token") String tokenString, ModelAndView mav, HttpServletRequest request) throws NoSuchAlgorithmException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); diff --git a/openid-connect-server/src/test/java/org/mitre/jwt/JwtTest.java b/openid-connect-server/src/test/java/org/mitre/jwt/JwtTest.java index f565fbd33..caefa4104 100644 --- a/openid-connect-server/src/test/java/org/mitre/jwt/JwtTest.java +++ b/openid-connect-server/src/test/java/org/mitre/jwt/JwtTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertThat; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; @@ -198,7 +199,7 @@ public class JwtTest { } @Test - public void testToStringPlaintext() { + public void testToStringPlaintext() throws NoSuchAlgorithmException { Jwt jwt = new Jwt(); jwt.getHeader().setAlgorithm("none"); jwt.getClaims().setExpiration(new Date(1300819380L * 1000L));