Merge branch 'master' of github.com:jricher/OpenID-Connect-Java-Spring-Server

pull/59/head
Amanda Anganes 2012-02-16 16:23:04 -05:00
commit 6c7527aaec
18 changed files with 425 additions and 207 deletions

View File

@ -4,6 +4,7 @@ import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.Signature; import java.security.Signature;
import java.security.SignatureException; import java.security.SignatureException;
@ -83,19 +84,14 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
private static Log logger = LogFactory.getLog(RsaSigner.class); private static Log logger = LogFactory.getLog(RsaSigner.class);
public static final String PROVIDER = "BC";
public static final String DEFAULT_PASSWORD = "changeit"; public static final String DEFAULT_PASSWORD = "changeit";
// static {
// Security.addProvider(new BouncyCastleProvider());
// }
private KeyStore keystore; private KeyStore keystore;
private String alias; private String alias;
private String password; private String password;
private RSAPrivateKey privateKey; private PrivateKey privateKey;
private RSAPublicKey publicKey; private PublicKey publicKey;
private Signature signer; private Signature signer;
/** /**
@ -136,14 +132,23 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
} }
} }
/**
* Default constructor
*/
public RsaSigner(String algorithmName, RSAPublicKey publicKey, RSAPrivateKey privateKey) {
super(algorithmName);
this.publicKey = publicKey;
this.privateKey = privateKey;
}
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
KeyPair keyPair = keystore.getKeyPairForAlias(alias, password); KeyPair keyPair = keystore.getKeyPairForAlias(alias, password);
publicKey = ((RSAPublicKey) keyPair.getPublic()); publicKey = keyPair.getPublic();
privateKey = (RSAPrivateKey) keyPair.getPrivate(); privateKey = keyPair.getPrivate();
logger.debug("RSA Signer ready for business"); logger.debug( Algorithm.getByName(getAlgorithm()).getStandardName() + " RSA Signer ready for business");
} }
@ -157,10 +162,12 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
signer.initSign(privateKey); signer.initSign(privateKey);
signer.update(signatureBase.getBytes("UTF-8")); signer.update(signatureBase.getBytes("UTF-8"));
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
System.out.println("boooom 1");
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
System.out.println("boooom 2");
e.printStackTrace(); e.printStackTrace();
} }
@ -192,6 +199,10 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
return password; return password;
} }
public PrivateKey getPrivateKey() {
return privateKey;
}
public PublicKey getPublicKey() { public PublicKey getPublicKey() {
return publicKey; return publicKey;
} }
@ -208,6 +219,10 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
this.password = password; this.password = password;
} }
public void setPrivateKey(RSAPrivateKey privateKey) {
this.privateKey = privateKey;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
@ -220,6 +235,9 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
+ ", publicKey=" + publicKey + ", signer=" + signer + "]"; + ", publicKey=" + publicKey + ", signer=" + signer + "]";
} }
/* (non-Javadoc)
* @see org.mitre.jwt.signer.AbstractJwtSigner#verify(java.lang.String)
*/
@Override @Override
public boolean verify(String jwtString) { public boolean verify(String jwtString) {
@ -251,12 +269,4 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
return true; return true;
} }
public RSAPrivateKey getPrivateKey() {
return privateKey;
}
public void setPrivateKey(RSAPrivateKey privateKey) {
this.privateKey = privateKey;
}
} }

View File

@ -30,9 +30,11 @@ public interface JwtSigningAndValidationService {
* *
* @param jwt * @param jwt
* the JWT to check the issuer of * the JWT to check the issuer of
* @return true if the JWT was issued by this AS, false if not * @param expectedIssuer
* the expected issuer
* @return true if the JWT was issued by this expected issuer, false if not
*/ */
public boolean validateIssuedJwt(Jwt jwt); public boolean validateIssuedJwt(Jwt jwt, String expectedIssuer);
/** /**
* Checks the signature of the given JWT against all configured signers, * Checks the signature of the given JWT against all configured signers,

View File

@ -1,8 +1,12 @@
package org.mitre.jwt.signer.service.impl; package org.mitre.jwt.signer.service.impl;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -16,6 +20,7 @@ import org.springframework.beans.factory.InitializingBean;
public class JwtSigningAndValidationServiceDefault implements public class JwtSigningAndValidationServiceDefault implements
JwtSigningAndValidationService, InitializingBean { JwtSigningAndValidationService, InitializingBean {
private List<? extends JwtSigner> signers = new ArrayList<JwtSigner>(); private List<? extends JwtSigner> signers = new ArrayList<JwtSigner>();
private static Log logger = LogFactory private static Log logger = LogFactory
@ -30,15 +35,19 @@ public class JwtSigningAndValidationServiceDefault implements
/** /**
* Create JwtSigningAndValidationServiceDefault * Create JwtSigningAndValidationServiceDefault
* *
* @param signer List of JwtSigners to associate with this service * @param signer
* List of JwtSigners to associate with this service
*/ */
public JwtSigningAndValidationServiceDefault(List<? extends JwtSigner> signer) { public JwtSigningAndValidationServiceDefault(
List<? extends JwtSigner> signer) {
setSigners(signer); setSigners(signer);
} }
/*
/* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() *
* @see
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/ */
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
@ -47,7 +56,8 @@ public class JwtSigningAndValidationServiceDefault implements
logger.info(this.toString()); logger.info(this.toString());
} }
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> JwtSigningAndValidationServiceDefault is open for business"); logger.info("JwtSigningAndValidationServiceDefault is open for business");
} }
/* /*
@ -59,9 +69,9 @@ public class JwtSigningAndValidationServiceDefault implements
*/ */
@Override @Override
public List<PublicKey> getAllPublicKeys() { public List<PublicKey> getAllPublicKeys() {
// TODO Iterate through the signers, gather up, and return all the PublicKeys
List<PublicKey> publicKeys = new ArrayList<PublicKey>(); Map<String, PublicKey> map = new HashMap<String, PublicKey>();
PublicKey publicKey; PublicKey publicKey;
for (JwtSigner signer : signers) { for (JwtSigner signer : signers) {
@ -71,18 +81,19 @@ public class JwtSigningAndValidationServiceDefault implements
publicKey = ((RsaSigner) signer).getPublicKey(); publicKey = ((RsaSigner) signer).getPublicKey();
if (publicKey != null) if (publicKey != null)
publicKeys.add(((RsaSigner) signer).getPublicKey()); map.put(((RSAPublicKey) publicKey).getModulus()
.toString(16).toUpperCase()
+ ((RSAPublicKey) publicKey).getPublicExponent()
.toString(16).toUpperCase(), publicKey);
} else if (signer instanceof EcdsaSigner) { } else if (signer instanceof EcdsaSigner) {
publicKey = ((EcdsaSigner) signer).getPublicKey(); // TODO
if (publicKey != null)
publicKeys.add(publicKey);
} }
} }
return publicKeys; return new ArrayList<PublicKey>(map.values());
} }
/** /**
@ -103,8 +114,14 @@ public class JwtSigningAndValidationServiceDefault implements
*/ */
@Override @Override
public boolean isJwtExpired(Jwt jwt) { public boolean isJwtExpired(Jwt jwt) {
// TODO Auto-generated method stub
Date expiration = jwt.getClaims().getExpiration();
if (expiration != null)
return new Date().after(expiration);
else
return false; return false;
} }
/** /**
@ -117,6 +134,9 @@ public class JwtSigningAndValidationServiceDefault implements
this.signers = signers; this.signers = signers;
} }
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override @Override
public String toString() { public String toString() {
return "JwtSigningAndValidationServiceDefault [signers=" + signers return "JwtSigningAndValidationServiceDefault [signers=" + signers
@ -131,14 +151,10 @@ public class JwtSigningAndValidationServiceDefault implements
* (org.mitre.jwt.model.Jwt) * (org.mitre.jwt.model.Jwt)
*/ */
@Override @Override
public boolean validateIssuedJwt(Jwt jwt) { public boolean validateIssuedJwt(Jwt jwt, String expectedIssuer) {
// TODO Verify this is correct... if (jwt.getClaims().getIssuer() == expectedIssuer)
for (JwtSigner signer: signers) {
if (signer.verify(jwt.toString()))
return true; return true;
}
return false; return false;
} }

View File

@ -1,30 +1,15 @@
package org.mitre.jwt.signer.service.impl; package org.mitre.jwt.signer.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.Key; import java.security.Key;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.Provider; import java.security.Provider;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.Date;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@ -34,13 +19,11 @@ import org.springframework.core.io.Resource;
* @author nemonik * @author nemonik
* *
*/ */
@SuppressWarnings("deprecation")
public class KeyStore implements InitializingBean { public class KeyStore implements InitializingBean {
private static Log logger = LogFactory.getLog(KeyStore.class); private static Log logger = LogFactory.getLog(KeyStore.class);
public static final String TYPE = java.security.KeyStore.getDefaultType(); // "BKS"; public static final String TYPE = java.security.KeyStore.getDefaultType();
public static final String PROVIDER = "BC";
public static final String PASSWORD = "changeit"; public static final String PASSWORD = "changeit";
private String password; private String password;
@ -81,7 +64,7 @@ public class KeyStore implements InitializingBean {
InputStream inputStream = null; InputStream inputStream = null;
try { try {
keystore = java.security.KeyStore.getInstance(TYPE); //, PROVIDER); keystore = java.security.KeyStore.getInstance(TYPE);
inputStream = location.getInputStream(); inputStream = location.getInputStream();
keystore.load(inputStream, this.password.toCharArray()); keystore.load(inputStream, this.password.toCharArray());
@ -121,7 +104,7 @@ public class KeyStore implements InitializingBean {
// Get public key // Get public key
PublicKey publicKey = cert.getPublicKey(); PublicKey publicKey = cert.getPublicKey();
return new KeyPair(publicKey, (RSAPrivateKey) key); return new KeyPair(publicKey, (PrivateKey) key);
} }
return null; return null;
@ -169,4 +152,5 @@ public class KeyStore implements InitializingBean {
return "KeyStore [password=" + password + ", location=" + location return "KeyStore [password=" + password + ", location=" + location
+ ", keystore=" + keystore + "]"; + ", keystore=" + keystore + "]";
} }
} }

View File

@ -73,6 +73,8 @@ public class JwkKeyListView extends AbstractView {
JsonObject o = new JsonObject(); JsonObject o = new JsonObject();
o.addProperty("use", "sig");
o.addProperty("alg", "RSA");
o.addProperty("mod", m64); o.addProperty("mod", m64);
o.addProperty("exp", e64); o.addProperty("exp", e64);

View File

@ -1,11 +1,13 @@
package org.mitre.openid.connect.web; package org.mitre.openid.connect.web;
import javax.servlet.http.HttpServletRequest;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService; import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.openid.connect.exception.ExpiredTokenException; import org.mitre.openid.connect.exception.ExpiredTokenException;
import org.mitre.openid.connect.exception.InvalidJwtIssuerException; import org.mitre.openid.connect.exception.InvalidJwtIssuerException;
import org.mitre.openid.connect.exception.InvalidJwtSignatureException; import org.mitre.openid.connect.exception.InvalidJwtSignatureException;
import org.mitre.openid.connect.model.IdToken; import org.mitre.openid.connect.model.IdToken;
import org.mitre.openid.connect.model.IdTokenClaims; import org.mitre.util.Utility;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -18,9 +20,8 @@ public class CheckIDEndpoint {
@Autowired @Autowired
JwtSigningAndValidationService jwtSignerService; JwtSigningAndValidationService jwtSignerService;
@RequestMapping("/checkid") @RequestMapping("/checkid")
public ModelAndView checkID(@RequestParam("id_token") String tokenString, ModelAndView mav) { public ModelAndView checkID(@RequestParam("id_token") String tokenString, ModelAndView mav, HttpServletRequest request) {
if (!jwtSignerService.validateSignature(tokenString)) { if (!jwtSignerService.validateSignature(tokenString)) {
// can't validate // can't validate
@ -37,7 +38,7 @@ public class CheckIDEndpoint {
} }
// check the issuer (sanity check) // check the issuer (sanity check)
if (!jwtSignerService.validateIssuedJwt(token)) { if (!jwtSignerService.validateIssuedJwt(token, Utility.findBaseUrl(request))) {
throw new InvalidJwtIssuerException(); // TODO: create a view for this exception throw new InvalidJwtIssuerException(); // TODO: create a view for this exception
} }

View File

@ -3,7 +3,9 @@ package org.mitre.openid.connect.web;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.interfaces.ECPublicKey; import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService; import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -24,7 +26,10 @@ public class JsonWebKeyEndpoint {
// TODO: check if keys are empty, return a 404 here or just an empty list? // TODO: check if keys are empty, return a 404 here or just an empty list?
return new ModelAndView("jwkKeyList", "entity", keys); Map<String, Object> jwk = new HashMap<String, Object>();
jwk.put("jwk", keys);
return new ModelAndView("jwkKeyList", "entity", jwk);
} }
} }

View File

@ -3,17 +3,15 @@ package org.mitre.swd.web;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired; import org.mitre.util.Utility;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@Controller @Controller
public class SimpleWebDiscoveryEndpoint { public class SimpleWebDiscoveryEndpoint {
@ -22,7 +20,7 @@ public class SimpleWebDiscoveryEndpoint {
params={"principal", "service=http://openid.net/specs/connect/1.0/issuer"}) params={"principal", "service=http://openid.net/specs/connect/1.0/issuer"})
public ModelAndView openIdConnectIssuerDiscovery(@RequestParam("principal") String principal, ModelAndView modelAndView, HttpServletRequest request) { public ModelAndView openIdConnectIssuerDiscovery(@RequestParam("principal") String principal, ModelAndView modelAndView, HttpServletRequest request) {
String baseUrl = findBaseUrl(request); String baseUrl = Utility.findBaseUrl(request);
// look up user, see if they're local // look up user, see if they're local
// if so, return this server // if so, return this server
@ -42,7 +40,7 @@ public class SimpleWebDiscoveryEndpoint {
@RequestMapping("/.well-known/openid-configuration") @RequestMapping("/.well-known/openid-configuration")
public ModelAndView providerConfiguration(ModelAndView modelAndView, HttpServletRequest request) { public ModelAndView providerConfiguration(ModelAndView modelAndView, HttpServletRequest request) {
String baseUrl = findBaseUrl(request); String baseUrl = Utility.findBaseUrl(request);
/* /*
* version string Version of the provider response. "3.0" is the default. * version string Version of the provider response. "3.0" is the default.
@ -91,16 +89,4 @@ public class SimpleWebDiscoveryEndpoint {
return modelAndView; return modelAndView;
} }
private String findBaseUrl(HttpServletRequest request) {
String baseUrl = String.format("%s://%s%s", request.getScheme(), request.getServerName(), request.getContextPath());
if ((request.getScheme().equals("http") && request.getServerPort() != 80)
|| (request.getScheme().equals("https") && request.getServerPort() != 443)) {
// nonstandard port, need to include it
baseUrl = String.format("%s://%s:%d%s", request.getScheme(), request.getServerName(), request.getServerPort(), request.getContextPath());
}
return baseUrl;
}
} }

View File

@ -0,0 +1,31 @@
package org.mitre.util;
import javax.servlet.http.HttpServletRequest;
/**
* A collection of utility methods.
*
*/
public class Utility {
/**
* Returns the base URL from a HttpServletRequest
*
* @param request
* @return
*/
public static String findBaseUrl(HttpServletRequest request) {
String issuer = String.format("%s://%s%s", request.getScheme(),
request.getServerName(), request.getContextPath());
if ((request.getScheme().equals("http") && request.getServerPort() != 80)
|| (request.getScheme().equals("https") && request
.getServerPort() != 443)) {
// nonstandard port, need to include it
issuer = String.format("%s://%s:%d%s", request.getScheme(),
request.getServerName(), request.getServerPort(),
request.getContextPath());
}
return issuer;
}
}

View File

@ -1 +1,2 @@
http\://www.mitre.org/schema/openid-connect/jwt-signer/jwt-signer.xsd=org/mitre/jwt/signer/service/impl/jwt-signer.xsd http\://www.mitre.org/schema/openid-connect/jwt-signer/jwt-signer-1.0.xsd=org/mitre/jwt/signer/service/impl/jwt-signer-1.0.xsd
http\://www.mitre.org/schema/openid-connect/jwt-signer/jwt-signer.xsd=org/mitre/jwt/signer/service/impl/jwt-signer-1.0.xsd

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mitre.org/schema/openid-connect/jwt-signer"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.mitre.org/schema/openid-connect/jwt-signer"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.1.xsd" />
<xsd:element name="keystore">
<xsd:annotation>
<xsd:documentation>
Describes the JCE KeyStore necessary for certain
signers.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="location" type="xsd:string" use="required" />
<xsd:attribute name="password" type="xsd:string" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="service">
<xsd:annotation>
<xsd:documentation>
Configures the signer service with these signers.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="rsa">
<xsd:annotation>
<xsd:documentation>
Configures an RSA signer.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="bits" type="xsd:string" />
<xsd:attribute name="keystore-ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
The reference to the bean that defines the
KeyStore.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="key-alias" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
The alias to the KeyPair to use for
signing/verifying.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="password" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The password to the KeyPair to use for
signing/verifying.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="hmac">
<xsd:annotation>
<xsd:documentation>
Configures an HMAC signer.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="bits" type="xsd:integer" />
<xsd:attribute name="passphrase" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The passphrase used for signing/verifying.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@ -11,7 +11,7 @@
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.mitre.org/schema/openid-connect/jwt-signer classpath:/org/mitre/jwt/signer/service/impl/jwt-signer.xsd http://www.mitre.org/schema/openid-connect/jwt-signer http://www.mitre.org/schema/openid-connect/jwt-signer/jwt-signer-1.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
@ -55,6 +55,8 @@
</property> </property>
</bean> </bean>
<!-- TODO: working remove red X's see: http://forum.springsource.org/showthread.php?123193-STS-not-respecting-META-INF-spring-schemas-for-validation&p=401926#post401926 -->
<jwt-signer:keystore id="defaultKeystore" location="classpath:keystore.jks" password="changeit" /> <jwt-signer:keystore id="defaultKeystore" location="classpath:keystore.jks" password="changeit" />
<jwt-signer:service id="defaultSignerService"> <jwt-signer:service id="defaultSignerService">

View File

@ -1,6 +1,8 @@
package org.mitre.jwt; package org.mitre.jwt;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@ -15,7 +17,6 @@ import org.mitre.jwt.signer.impl.PlaintextSigner;
import org.mitre.jwt.signer.impl.RsaSigner; import org.mitre.jwt.signer.impl.RsaSigner;
import org.mitre.jwt.signer.service.impl.KeyStore; import org.mitre.jwt.signer.service.impl.KeyStore;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ -25,35 +26,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
public class JwtTest { public class JwtTest {
@Autowired @Autowired
@Qualifier("testKeystore")
KeyStore keystore; KeyStore keystore;
@Test
public void testToStringPlaintext() {
Jwt jwt = new Jwt();
jwt.getHeader().setAlgorithm("none");
jwt.getClaims().setExpiration(new Date(1300819380L * 1000L));
jwt.getClaims().setIssuer("joe");
jwt.getClaims().setClaim("http://example.com/is_root", Boolean.TRUE);
// sign it with a blank signature
JwtSigner signer = new PlaintextSigner();
signer.sign(jwt);
/*
* Expected string based on the following structures, serialized exactly as follows and base64 encoded:
*
* header: {"alg":"none"}
* claims: {"exp":1300819380,"iss":"joe","http://example.com/is_root":true}
*/
String expected = "eyJhbGciOiJub25lIn0.eyJleHAiOjEzMDA4MTkzODAsImlzcyI6ImpvZSIsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.";
String actual = jwt.toString();
assertThat(actual, equalTo(expected));
}
@Test @Test
public void testGenerateHmacSignature() { public void testGenerateHmacSignature() {
Jwt jwt = new Jwt(); Jwt jwt = new Jwt();
@ -98,36 +72,66 @@ public class JwtTest {
/** /**
* @throws Exception * @throws Exception
*/ */
// @Test /**
// public void testGenerateRsaSignature() throws Exception { * @throws Exception
// */
//// java.security.KeyStore ks = KeyStore.generateRsaKeyPair(keystore @Test
//// .getLocation().getFile().getPath(), "OpenID Connect Server", public void testGenerateRsaSignature() throws Exception {
//// "twentyYears", KeyStore.PASSWORD, KeyStore.PASSWORD, 30, 365*20);
//// Jwt jwt = new Jwt();
//// keystore.setKeystore(ks); jwt.getHeader().setType("JWT");
// jwt.getHeader().setAlgorithm("RS256");
// Jwt jwt = new Jwt(); jwt.getClaims().setExpiration(new Date(1300819380L * 1000L));
// jwt.getHeader().setType("JWT"); jwt.getClaims().setIssuer("joe");
// jwt.getHeader().setAlgorithm("RS256"); jwt.getClaims().setClaim("http://example.com/is_root", Boolean.TRUE);
// jwt.getClaims().setExpiration(new Date(1300819380L * 1000L));
// jwt.getClaims().setIssuer("joe"); JwtSigner signer = new RsaSigner(RsaSigner.Algorithm.RS256.toString(), keystore, "test", "changeit");
// jwt.getClaims().setClaim("http://example.com/is_root", Boolean.TRUE); ((RsaSigner)signer).afterPropertiesSet();
//
// JwtSigner signer = new RsaSigner(RsaSigner.Algorithm.DEFAULT, keystore, "twentyYears"); signer.sign(jwt);
// ((RsaSigner) signer).afterPropertiesSet();
// assertThat(jwt.getSignature(), not(nullValue()));
// signer.sign(jwt); }
//
// String signature = "TW0nOd_vr1rnV7yIS-lIV2-00V_zJMWxzOc3Z7k3gvMO2aIjIGjZ9nByZMI0iL5komMxYXPl_RCkbd9OKiPkk4iK5CDj7Mawbzu95LgEOOqdXO1f7-IqX9dIvJhVXXInLD3RsGvavyheIqNeFEVidLrJo30tBchB_niljEW7VeX8nSZfiCOdbOTW3hu0ycnon7wFpejb-cRP_S0iqGxCgbYXJzqPT192EHmRy_wmFxxIy9Lc84uqNkAZSIn1jVIeAemm22RoWbq0xLVLTRyiZoxJTUzac_VteiSPRNFlUQuOdxqNf0Hxqh_wVfX1mfXUzv0D8vHJVy6aIqTISmn-qg"; @Test
// String expected = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOjEzMDA4MTkzODAsImlzcyI6ImpvZSIsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.TW0nOd_vr1rnV7yIS-lIV2-00V_zJMWxzOc3Z7k3gvMO2aIjIGjZ9nByZMI0iL5komMxYXPl_RCkbd9OKiPkk4iK5CDj7Mawbzu95LgEOOqdXO1f7-IqX9dIvJhVXXInLD3RsGvavyheIqNeFEVidLrJo30tBchB_niljEW7VeX8nSZfiCOdbOTW3hu0ycnon7wFpejb-cRP_S0iqGxCgbYXJzqPT192EHmRy_wmFxxIy9Lc84uqNkAZSIn1jVIeAemm22RoWbq0xLVLTRyiZoxJTUzac_VteiSPRNFlUQuOdxqNf0Hxqh_wVfX1mfXUzv0D8vHJVy6aIqTISmn-qg"; public void testParse() {
// String source = "eyJhbGciOiJub25lIn0.eyJleHAiOjEzMDA4MTkzODAsImlzcyI6ImpvZSIsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.";
// String actual = jwt.toString();
//
// assertThat(actual, equalTo(expected)); Jwt jwt = Jwt.parse(source);
// assertThat(jwt.getSignature(), equalTo(signature));
// assertThat(jwt.getHeader().getAlgorithm(), equalTo(PlaintextSigner.PLAINTEXT));
// } assertThat(jwt.getClaims().getIssuer(), equalTo("joe"));
assertThat(jwt.getClaims().getExpiration(), equalTo(new Date(1300819380L * 1000L)));
assertThat((Boolean)jwt.getClaims().getClaim("http://example.com/is_root"), equalTo(Boolean.TRUE));
}
@Test
public void testToStringPlaintext() {
Jwt jwt = new Jwt();
jwt.getHeader().setAlgorithm("none");
jwt.getClaims().setExpiration(new Date(1300819380L * 1000L));
jwt.getClaims().setIssuer("joe");
jwt.getClaims().setClaim("http://example.com/is_root", Boolean.TRUE);
// sign it with a blank signature
JwtSigner signer = new PlaintextSigner();
signer.sign(jwt);
/*
* Expected string based on the following structures, serialized exactly as follows and base64 encoded:
*
* header: {"alg":"none"}
* claims: {"exp":1300819380,"iss":"joe","http://example.com/is_root":true}
*/
String expected = "eyJhbGciOiJub25lIn0.eyJleHAiOjEzMDA4MTkzODAsImlzcyI6ImpvZSIsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.";
String actual = jwt.toString();
assertThat(actual, equalTo(expected));
}
@Test @Test
public void testValidateHmacSignature() { public void testValidateHmacSignature() {
@ -158,19 +162,4 @@ public class JwtTest {
assertThat(valid, equalTo(Boolean.TRUE)); assertThat(valid, equalTo(Boolean.TRUE));
} }
@Test
public void testParse() {
String source = "eyJhbGciOiJub25lIn0.eyJleHAiOjEzMDA4MTkzODAsImlzcyI6ImpvZSIsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.";
Jwt jwt = Jwt.parse(source);
assertThat(jwt.getHeader().getAlgorithm(), equalTo(PlaintextSigner.PLAINTEXT));
assertThat(jwt.getClaims().getIssuer(), equalTo("joe"));
assertThat(jwt.getClaims().getExpiration(), equalTo(new Date(1300819380L * 1000L)));
assertThat((Boolean)jwt.getClaims().getClaim("http://example.com/is_root"), equalTo(Boolean.TRUE));
}
} }

View File

@ -5,9 +5,19 @@ import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.Key; import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.Date;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -15,7 +25,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SuppressWarnings("restriction") // I know... @SuppressWarnings("deprecation")
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { @ContextConfiguration(locations = {
"classpath:test-context.xml" }) "classpath:test-context.xml" })
@ -25,31 +35,114 @@ public class KeyStoreTest {
@Qualifier("testKeystore") @Qualifier("testKeystore")
KeyStore keystore; KeyStore keystore;
static {
// Needed to create the certificate
Security.addProvider(new BouncyCastleProvider());
}
/**
* Creates a certificate.
*
* @param commonName
* @param daysNotValidBefore
* @param daysNotValidAfter
* @return
*/
private static X509V3CertificateGenerator createCertificate(
String commonName, int daysNotValidBefore, int daysNotValidAfter) {
// BC sez X509V3CertificateGenerator is deprecated and the docs say to
// use another, but it seemingly isn't included jar...
X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
v3CertGen
.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
v3CertGen.setIssuerDN(new X509Principal("CN=" + commonName
+ ", OU=None, O=None L=None, C=None"));
v3CertGen.setNotBefore(new Date(System.currentTimeMillis()
- (1000L * 60 * 60 * 24 * daysNotValidBefore)));
v3CertGen.setNotAfter(new Date(System.currentTimeMillis()
+ (1000L * 60 * 60 * 24 * daysNotValidAfter)));
v3CertGen.setSubjectDN(new X509Principal("CN=" + commonName
+ ", OU=None, O=None L=None, C=None"));
return v3CertGen;
}
/**
* Create an RSA KeyPair and insert into specified KeyStore
*
* @param location
* @param domainName
* @param alias
* @param keystorePassword
* @param aliasPassword
* @param daysNotValidBefore
* @param daysNotValidAfter
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static java.security.KeyStore generateRsaKeyPair(KeyStore keystore,
String domainName, String alias, String aliasPassword, int daysNotValidBefore, int daysNotValidAfter)
throws GeneralSecurityException, IOException {
java.security.KeyStore ks = keystore.getKeystore();
KeyPairGenerator rsaKeyPairGenerator = null;
rsaKeyPairGenerator = KeyPairGenerator.getInstance("RSA");
rsaKeyPairGenerator.initialize(2048);
KeyPair rsaKeyPair = rsaKeyPairGenerator.generateKeyPair();
// BC sez X509V3CertificateGenerator is deprecated and the docs say to
// use another, but it seemingly isn't included jar...
X509V3CertificateGenerator v3CertGen = createCertificate(domainName,
daysNotValidBefore, daysNotValidAfter);
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) rsaKeyPair.getPrivate();
v3CertGen.setPublicKey(rsaKeyPair.getPublic());
v3CertGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
// BC docs say to use another, but it seemingly isn't included...
X509Certificate certificate = v3CertGen
.generateX509Certificate(rsaPrivateKey);
// if exist, overwrite
ks.setKeyEntry(alias, rsaPrivateKey, aliasPassword.toCharArray(),
new java.security.cert.Certificate[] { certificate });
keystore.setKeystore(ks);
return ks;
}
@Test @Test
public void storeKeyPair() throws GeneralSecurityException, IOException { public void storeKeyPair() throws GeneralSecurityException, IOException {
//
// java.security.KeyStore ks = KeyStore.generateRsaKeyPair(keystore java.security.KeyStore ks = null;
// .getLocation().getFile().getPath(), "OpenID Connect Server",
// "test", KeyStore.PASSWORD, KeyStore.PASSWORD, 30, 30); try {
// ks = KeyStoreTest.generateRsaKeyPair(keystore, "OpenID Connect Server", "storeKeyPair", "changeit", 30, 365);
// keystore.setKeystore(ks);
// } catch (GeneralSecurityException e) {
// assertThat(ks, not(nullValue())); // TODO Auto-generated catch block
assertThat(true, not(false)); e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
assertThat(ks, not(nullValue()));
} }
@Test @Test
public void readKey() throws GeneralSecurityException { public void readKey() throws GeneralSecurityException {
// Key key = keystore.getKeystore().getKey("test", Key key = keystore.getKeystore().getKey("storeKeyPair",
// KeyStore.PASSWORD.toCharArray()); KeyStore.PASSWORD.toCharArray());
//
// System.out.println("-----BEGIN PRIVATE KEY-----"); assertThat(key, not(nullValue()));
// System.out
// .println(new sun.misc.BASE64Encoder().encode(key.getEncoded()));
// System.out.println("-----END PRIVATE KEY-----");
//
// assertThat(key, not(nullValue()));
assertThat(true, not(false));
} }
} }

View File

@ -4,7 +4,7 @@
xmlns:jwt-signer="http://www.mitre.org/schema/openid-connect/jwt-signer" xmlns:jwt-signer="http://www.mitre.org/schema/openid-connect/jwt-signer"
xsi:schemaLocation= xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.mitre.org/schema/openid-connect/jwt-signer http://www.mitre.org/schema/openid-connect/jwt-signer/jwt-signer.xsd" > http://www.mitre.org/schema/openid-connect/jwt-signer http://www.mitre.org/schema/openid-connect/jwt-signer/jwt-signer-1.0.xsd" >
<!-- Creates an in-memory database populated with test jdbc --> <!-- Creates an in-memory database populated with test jdbc -->
<bean id="dataSource" class="org.mitre.jdbc.datasource.H2DataSourceFactory"> <bean id="dataSource" class="org.mitre.jdbc.datasource.H2DataSourceFactory">