diff --git a/server/src/main/java/org/mitre/jwt/signer/impl/RsaSigner.java b/server/src/main/java/org/mitre/jwt/signer/impl/RsaSigner.java index c697db8ab..96f4e2e88 100644 --- a/server/src/main/java/org/mitre/jwt/signer/impl/RsaSigner.java +++ b/server/src/main/java/org/mitre/jwt/signer/impl/RsaSigner.java @@ -4,6 +4,7 @@ import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; @@ -83,19 +84,14 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean { private static Log logger = LogFactory.getLog(RsaSigner.class); - public static final String PROVIDER = "BC"; public static final String DEFAULT_PASSWORD = "changeit"; -// static { -// Security.addProvider(new BouncyCastleProvider()); -// } - private KeyStore keystore; private String alias; private String password; - private RSAPrivateKey privateKey; - private RSAPublicKey publicKey; + private PrivateKey privateKey; + private PublicKey publicKey; private Signature signer; /** @@ -113,7 +109,7 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean { public RsaSigner(String algorithmName, KeyStore keystore, String alias) { this(algorithmName, keystore, alias, DEFAULT_PASSWORD); } - + /** * @param algorithmName * @param keystore @@ -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 public void afterPropertiesSet() throws Exception { KeyPair keyPair = keystore.getKeyPairForAlias(alias, password); - publicKey = ((RSAPublicKey) keyPair.getPublic()); - privateKey = (RSAPrivateKey) keyPair.getPrivate(); + publicKey = keyPair.getPublic(); + privateKey = keyPair.getPrivate(); - logger.debug("RSA Signer ready for business"); + logger.debug( Algorithm.getByName(getAlgorithm()).getStandardName() + " RSA Signer ready for business"); } @@ -152,15 +157,17 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean { */ @Override protected String generateSignature(String signatureBase) { - + try { signer.initSign(privateKey); signer.update(signatureBase.getBytes("UTF-8")); } catch (GeneralSecurityException e) { + System.out.println("boooom 1"); // TODO Auto-generated catch block e.printStackTrace(); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block + System.out.println("boooom 2"); e.printStackTrace(); } @@ -176,7 +183,7 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean { // TODO Auto-generated catch block e.printStackTrace(); } - + return sig; } @@ -192,6 +199,10 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean { return password; } + public PrivateKey getPrivateKey() { + return privateKey; + } + public PublicKey getPublicKey() { return publicKey; } @@ -208,6 +219,10 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean { this.password = password; } + public void setPrivateKey(RSAPrivateKey privateKey) { + this.privateKey = privateKey; + } + /* * (non-Javadoc) * @@ -220,6 +235,9 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean { + ", publicKey=" + publicKey + ", signer=" + signer + "]"; } + /* (non-Javadoc) + * @see org.mitre.jwt.signer.AbstractJwtSigner#verify(java.lang.String) + */ @Override public boolean verify(String jwtString) { @@ -251,12 +269,4 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean { return true; } - - public RSAPrivateKey getPrivateKey() { - return privateKey; - } - - public void setPrivateKey(RSAPrivateKey privateKey) { - this.privateKey = privateKey; - } } diff --git a/server/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java b/server/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java index 6060bac53..cb0bb8f1e 100644 --- a/server/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java +++ b/server/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java @@ -30,9 +30,11 @@ public interface JwtSigningAndValidationService { * * @param jwt * 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, diff --git a/server/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java b/server/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java index 50723a51a..4168a8f00 100644 --- a/server/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java +++ b/server/src/main/java/org/mitre/jwt/signer/service/impl/JwtSigningAndValidationServiceDefault.java @@ -1,8 +1,12 @@ package org.mitre.jwt.signer.service.impl; import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -16,6 +20,7 @@ import org.springframework.beans.factory.InitializingBean; public class JwtSigningAndValidationServiceDefault implements JwtSigningAndValidationService, InitializingBean { + private List signers = new ArrayList(); private static Log logger = LogFactory @@ -23,31 +28,36 @@ public class JwtSigningAndValidationServiceDefault implements /** * default constructor - */ - public JwtSigningAndValidationServiceDefault() { + */ + public 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 signer) { + public JwtSigningAndValidationServiceDefault( + List signer) { setSigners(signer); } - - /* (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + /* + * (non-Javadoc) + * + * @see + * org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ @Override public void afterPropertiesSet() throws Exception { // used for debugging... if (!signers.isEmpty()) { - logger.info(this.toString()); + logger.info(this.toString()); } - - logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> JwtSigningAndValidationServiceDefault is open for business"); + + logger.info("JwtSigningAndValidationServiceDefault is open for business"); + } /* @@ -59,30 +69,31 @@ public class JwtSigningAndValidationServiceDefault implements */ @Override public List getAllPublicKeys() { - // TODO Iterate through the signers, gather up, and return all the PublicKeys - - List publicKeys = new ArrayList(); - PublicKey publicKey; - - for (JwtSigner signer: signers) { - + + Map map = new HashMap(); + + PublicKey publicKey; + + for (JwtSigner signer : signers) { + if (signer instanceof RsaSigner) { - + publicKey = ((RsaSigner) signer).getPublicKey(); - + 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) { - - publicKey = ((EcdsaSigner) signer).getPublicKey(); - - if (publicKey != null) - publicKeys.add(publicKey); + + // TODO } } - - return publicKeys; + + return new ArrayList(map.values()); + } /** @@ -103,10 +114,16 @@ public class JwtSigningAndValidationServiceDefault implements */ @Override public boolean isJwtExpired(Jwt jwt) { - // TODO Auto-generated method stub - return false; + + Date expiration = jwt.getClaims().getExpiration(); + + if (expiration != null) + return new Date().after(expiration); + else + return false; + } - + /** * Set the JwtSigners associated with this service * @@ -117,6 +134,9 @@ public class JwtSigningAndValidationServiceDefault implements this.signers = signers; } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ @Override public String toString() { return "JwtSigningAndValidationServiceDefault [signers=" + signers @@ -131,14 +151,10 @@ public class JwtSigningAndValidationServiceDefault implements * (org.mitre.jwt.model.Jwt) */ @Override - public boolean validateIssuedJwt(Jwt jwt) { - - // TODO Verify this is correct... + public boolean validateIssuedJwt(Jwt jwt, String expectedIssuer) { - for (JwtSigner signer: signers) { - if (signer.verify(jwt.toString())) - return true; - } + if (jwt.getClaims().getIssuer() == expectedIssuer) + return true; return false; } @@ -153,11 +169,11 @@ public class JwtSigningAndValidationServiceDefault implements @Override public boolean validateSignature(String jwtString) { - for (JwtSigner signer: signers) { + for (JwtSigner signer : signers) { if (signer.verify(jwtString)) return true; } - + return false; } } diff --git a/server/src/main/java/org/mitre/jwt/signer/service/impl/KeyStore.java b/server/src/main/java/org/mitre/jwt/signer/service/impl/KeyStore.java index 8130e0a07..2f5ddcdaf 100644 --- a/server/src/main/java/org/mitre/jwt/signer/service/impl/KeyStore.java +++ b/server/src/main/java/org/mitre/jwt/signer/service/impl/KeyStore.java @@ -1,30 +1,15 @@ 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.math.BigInteger; import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; 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.LogFactory; -import org.bouncycastle.jce.X509Principal; -import org.bouncycastle.x509.X509V3CertificateGenerator; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; @@ -34,13 +19,11 @@ import org.springframework.core.io.Resource; * @author nemonik * */ -@SuppressWarnings("deprecation") public class KeyStore implements InitializingBean { private static Log logger = LogFactory.getLog(KeyStore.class); - public static final String TYPE = java.security.KeyStore.getDefaultType(); // "BKS"; - public static final String PROVIDER = "BC"; + public static final String TYPE = java.security.KeyStore.getDefaultType(); public static final String PASSWORD = "changeit"; private String password; @@ -81,7 +64,7 @@ public class KeyStore implements InitializingBean { InputStream inputStream = null; try { - keystore = java.security.KeyStore.getInstance(TYPE); //, PROVIDER); + keystore = java.security.KeyStore.getInstance(TYPE); inputStream = location.getInputStream(); keystore.load(inputStream, this.password.toCharArray()); @@ -120,8 +103,8 @@ public class KeyStore implements InitializingBean { // Get public key PublicKey publicKey = cert.getPublicKey(); - - return new KeyPair(publicKey, (RSAPrivateKey) key); + + return new KeyPair(publicKey, (PrivateKey) key); } return null; @@ -169,4 +152,5 @@ public class KeyStore implements InitializingBean { return "KeyStore [password=" + password + ", location=" + location + ", keystore=" + keystore + "]"; } + } diff --git a/server/src/main/java/org/mitre/openid/connect/view/JwkKeyListView.java b/server/src/main/java/org/mitre/openid/connect/view/JwkKeyListView.java index ecbf2e57d..af7794a0d 100644 --- a/server/src/main/java/org/mitre/openid/connect/view/JwkKeyListView.java +++ b/server/src/main/java/org/mitre/openid/connect/view/JwkKeyListView.java @@ -72,7 +72,9 @@ public class JwkKeyListView extends AbstractView { String e64 = Base64.encodeBase64URLSafeString(exp.toByteArray()); JsonObject o = new JsonObject(); - + + o.addProperty("use", "sig"); + o.addProperty("alg", "RSA"); o.addProperty("mod", m64); o.addProperty("exp", e64); diff --git a/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java b/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java index 182a62b22..15859f91f 100644 --- a/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java +++ b/server/src/main/java/org/mitre/openid/connect/web/CheckIDEndpoint.java @@ -1,11 +1,13 @@ package org.mitre.openid.connect.web; +import javax.servlet.http.HttpServletRequest; + import org.mitre.jwt.signer.service.JwtSigningAndValidationService; import org.mitre.openid.connect.exception.ExpiredTokenException; import org.mitre.openid.connect.exception.InvalidJwtIssuerException; import org.mitre.openid.connect.exception.InvalidJwtSignatureException; 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.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,9 +20,8 @@ public class CheckIDEndpoint { @Autowired JwtSigningAndValidationService jwtSignerService; - @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)) { // can't validate @@ -37,7 +38,7 @@ public class CheckIDEndpoint { } // 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 } diff --git a/server/src/main/java/org/mitre/openid/connect/web/JsonWebKeyEndpoint.java b/server/src/main/java/org/mitre/openid/connect/web/JsonWebKeyEndpoint.java index e1a9abb0a..b5abb363a 100644 --- a/server/src/main/java/org/mitre/openid/connect/web/JsonWebKeyEndpoint.java +++ b/server/src/main/java/org/mitre/openid/connect/web/JsonWebKeyEndpoint.java @@ -3,7 +3,9 @@ package org.mitre.openid.connect.web; import java.security.PublicKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPublicKey; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.mitre.jwt.signer.service.JwtSigningAndValidationService; 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? - return new ModelAndView("jwkKeyList", "entity", keys); + Map jwk = new HashMap(); + jwk.put("jwk", keys); + + return new ModelAndView("jwkKeyList", "entity", jwk); } } diff --git a/server/src/main/java/org/mitre/swd/web/SimpleWebDiscoveryEndpoint.java b/server/src/main/java/org/mitre/swd/web/SimpleWebDiscoveryEndpoint.java index d70be0e9d..273c64762 100644 --- a/server/src/main/java/org/mitre/swd/web/SimpleWebDiscoveryEndpoint.java +++ b/server/src/main/java/org/mitre/swd/web/SimpleWebDiscoveryEndpoint.java @@ -3,17 +3,15 @@ package org.mitre.swd.web; import java.util.HashMap; import java.util.Map; -import javax.servlet.ServletRequest; 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.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; @Controller public class SimpleWebDiscoveryEndpoint { @@ -22,7 +20,7 @@ public class SimpleWebDiscoveryEndpoint { params={"principal", "service=http://openid.net/specs/connect/1.0/issuer"}) 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 // if so, return this server @@ -42,7 +40,7 @@ public class SimpleWebDiscoveryEndpoint { @RequestMapping("/.well-known/openid-configuration") 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. @@ -90,17 +88,5 @@ public class SimpleWebDiscoveryEndpoint { 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; - } } diff --git a/server/src/main/java/org/mitre/util/Utility.java b/server/src/main/java/org/mitre/util/Utility.java new file mode 100644 index 000000000..8799c3cb9 --- /dev/null +++ b/server/src/main/java/org/mitre/util/Utility.java @@ -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; + } +} diff --git a/server/src/main/resources/META-INF/spring.schemas b/server/src/main/resources/META-INF/spring.schemas index 144fa3f73..4c1b8a1de 100644 --- a/server/src/main/resources/META-INF/spring.schemas +++ b/server/src/main/resources/META-INF/spring.schemas @@ -1 +1,2 @@ -http\://www.mitre.org/schema/openid-connect/jwt-signer/jwt-signer.xsd=org/mitre/jwt/signer/service/impl/jwt-signer.xsd \ No newline at end of file +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 \ No newline at end of file diff --git a/server/src/main/resources/keystore.jks b/server/src/main/resources/keystore.jks index bb424e264..b3f5de2df 100644 Binary files a/server/src/main/resources/keystore.jks and b/server/src/main/resources/keystore.jks differ diff --git a/server/src/main/resources/org/mitre/jwt/signer/service/impl/jwt-signer-1.0.xsd b/server/src/main/resources/org/mitre/jwt/signer/service/impl/jwt-signer-1.0.xsd new file mode 100644 index 000000000..81b06f12c --- /dev/null +++ b/server/src/main/resources/org/mitre/jwt/signer/service/impl/jwt-signer-1.0.xsd @@ -0,0 +1,96 @@ + + + + + + + + + + Describes the JCE KeyStore necessary for certain + signers. + + + + + + + + + + + + + + + + + + Configures the signer service with these signers. + + + + + + + + + + + Configures an RSA signer. + + + + + + + + The reference to the bean that defines the + KeyStore. + + + + + + + The alias to the KeyPair to use for + signing/verifying. + + + + + + + The password to the KeyPair to use for + signing/verifying. + + + + + + + + + Configures an HMAC signer. + + + + + + + + The passphrase used for signing/verifying. + + + + + + + + + + + \ No newline at end of file diff --git a/server/src/main/webapp/WEB-INF/resources/static_resources_go_here b/server/src/main/webapp/WEB-INF/resources/static_resources_go_here new file mode 100644 index 000000000..e69de29bb diff --git a/server/src/main/webapp/WEB-INF/spring/application-context.xml b/server/src/main/webapp/WEB-INF/spring/application-context.xml index ad536ebda..1684f0212 100644 --- a/server/src/main/webapp/WEB-INF/spring/application-context.xml +++ b/server/src/main/webapp/WEB-INF/spring/application-context.xml @@ -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/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.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/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> @@ -55,6 +55,8 @@ + + diff --git a/server/src/test/java/org/mitre/jwt/JwtTest.java b/server/src/test/java/org/mitre/jwt/JwtTest.java index bc8a6481a..214c7b9f5 100644 --- a/server/src/test/java/org/mitre/jwt/JwtTest.java +++ b/server/src/test/java/org/mitre/jwt/JwtTest.java @@ -1,6 +1,8 @@ package org.mitre.jwt; 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 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.service.impl.KeyStore; 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.junit4.SpringJUnit4ClassRunner; @@ -25,35 +26,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; public class JwtTest { @Autowired - @Qualifier("testKeystore") 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 public void testGenerateHmacSignature() { Jwt jwt = new Jwt(); @@ -94,40 +68,70 @@ public class JwtTest { assertThat(jwt.getSignature(), equalTo(signature)); } - + /** * @throws Exception */ -// @Test -// public void testGenerateRsaSignature() throws Exception { -// -//// java.security.KeyStore ks = KeyStore.generateRsaKeyPair(keystore -//// .getLocation().getFile().getPath(), "OpenID Connect Server", -//// "twentyYears", KeyStore.PASSWORD, KeyStore.PASSWORD, 30, 365*20); -//// -//// keystore.setKeystore(ks); -// -// Jwt jwt = new Jwt(); -// jwt.getHeader().setType("JWT"); -// jwt.getHeader().setAlgorithm("RS256"); -// jwt.getClaims().setExpiration(new Date(1300819380L * 1000L)); -// jwt.getClaims().setIssuer("joe"); -// jwt.getClaims().setClaim("http://example.com/is_root", Boolean.TRUE); -// -// JwtSigner signer = new RsaSigner(RsaSigner.Algorithm.DEFAULT, keystore, "twentyYears"); -// ((RsaSigner) signer).afterPropertiesSet(); -// -// signer.sign(jwt); -// -// String signature = "TW0nOd_vr1rnV7yIS-lIV2-00V_zJMWxzOc3Z7k3gvMO2aIjIGjZ9nByZMI0iL5komMxYXPl_RCkbd9OKiPkk4iK5CDj7Mawbzu95LgEOOqdXO1f7-IqX9dIvJhVXXInLD3RsGvavyheIqNeFEVidLrJo30tBchB_niljEW7VeX8nSZfiCOdbOTW3hu0ycnon7wFpejb-cRP_S0iqGxCgbYXJzqPT192EHmRy_wmFxxIy9Lc84uqNkAZSIn1jVIeAemm22RoWbq0xLVLTRyiZoxJTUzac_VteiSPRNFlUQuOdxqNf0Hxqh_wVfX1mfXUzv0D8vHJVy6aIqTISmn-qg"; -// String expected = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOjEzMDA4MTkzODAsImlzcyI6ImpvZSIsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.TW0nOd_vr1rnV7yIS-lIV2-00V_zJMWxzOc3Z7k3gvMO2aIjIGjZ9nByZMI0iL5komMxYXPl_RCkbd9OKiPkk4iK5CDj7Mawbzu95LgEOOqdXO1f7-IqX9dIvJhVXXInLD3RsGvavyheIqNeFEVidLrJo30tBchB_niljEW7VeX8nSZfiCOdbOTW3hu0ycnon7wFpejb-cRP_S0iqGxCgbYXJzqPT192EHmRy_wmFxxIy9Lc84uqNkAZSIn1jVIeAemm22RoWbq0xLVLTRyiZoxJTUzac_VteiSPRNFlUQuOdxqNf0Hxqh_wVfX1mfXUzv0D8vHJVy6aIqTISmn-qg"; -// -// String actual = jwt.toString(); -// -// assertThat(actual, equalTo(expected)); -// assertThat(jwt.getSignature(), equalTo(signature)); -// -// } + /** + * @throws Exception + */ + @Test + public void testGenerateRsaSignature() throws Exception { + + Jwt jwt = new Jwt(); + jwt.getHeader().setType("JWT"); + jwt.getHeader().setAlgorithm("RS256"); + jwt.getClaims().setExpiration(new Date(1300819380L * 1000L)); + jwt.getClaims().setIssuer("joe"); + jwt.getClaims().setClaim("http://example.com/is_root", Boolean.TRUE); + + JwtSigner signer = new RsaSigner(RsaSigner.Algorithm.RS256.toString(), keystore, "test", "changeit"); + ((RsaSigner)signer).afterPropertiesSet(); + + signer.sign(jwt); + + assertThat(jwt.getSignature(), not(nullValue())); + } + + @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)); + + } + + @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 public void testValidateHmacSignature() { @@ -154,23 +158,8 @@ public class JwtTest { String jwtString = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEzMDA4MTkzODAsImlzcyI6ImpvZSIsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.iGBPJj47S5q_HAhSoQqAdcS6A_1CFj3zrLaImqNbt9E"; boolean valid = signer.verify(jwtString); - + 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)); - - } - } diff --git a/server/src/test/java/org/mitre/jwt/signer/service/impl/KeyStoreTest.java b/server/src/test/java/org/mitre/jwt/signer/service/impl/KeyStoreTest.java index 62fdf400c..1aa39a18d 100644 --- a/server/src/test/java/org/mitre/jwt/signer/service/impl/KeyStoreTest.java +++ b/server/src/test/java/org/mitre/jwt/signer/service/impl/KeyStoreTest.java @@ -5,9 +5,19 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; import java.io.IOException; +import java.math.BigInteger; import java.security.GeneralSecurityException; 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.runner.RunWith; 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.junit4.SpringJUnit4ClassRunner; -@SuppressWarnings("restriction") // I know... +@SuppressWarnings("deprecation") @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:test-context.xml" }) @@ -25,31 +35,114 @@ public class KeyStoreTest { @Qualifier("testKeystore") 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 public void storeKeyPair() throws GeneralSecurityException, IOException { -// -// java.security.KeyStore ks = KeyStore.generateRsaKeyPair(keystore -// .getLocation().getFile().getPath(), "OpenID Connect Server", -// "test", KeyStore.PASSWORD, KeyStore.PASSWORD, 30, 30); -// -// keystore.setKeystore(ks); -// -// assertThat(ks, not(nullValue())); - assertThat(true, not(false)); - } + java.security.KeyStore ks = null; + + try { + ks = KeyStoreTest.generateRsaKeyPair(keystore, "OpenID Connect Server", "storeKeyPair", "changeit", 30, 365); + + } catch (GeneralSecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + assertThat(ks, not(nullValue())); + } + @Test public void readKey() throws GeneralSecurityException { - -// Key key = keystore.getKeystore().getKey("test", -// KeyStore.PASSWORD.toCharArray()); -// -// System.out.println("-----BEGIN PRIVATE KEY-----"); -// System.out -// .println(new sun.misc.BASE64Encoder().encode(key.getEncoded())); -// System.out.println("-----END PRIVATE KEY-----"); -// -// assertThat(key, not(nullValue())); - assertThat(true, not(false)); + + Key key = keystore.getKeystore().getKey("storeKeyPair", + KeyStore.PASSWORD.toCharArray()); + + assertThat(key, not(nullValue())); } } diff --git a/server/src/test/resources/keystore.jks b/server/src/test/resources/keystore.jks index bb424e264..b3f5de2df 100644 Binary files a/server/src/test/resources/keystore.jks and b/server/src/test/resources/keystore.jks differ diff --git a/server/src/test/resources/test-context.xml b/server/src/test/resources/test-context.xml index fcd72a9b5..2f32e8191 100644 --- a/server/src/test/resources/test-context.xml +++ b/server/src/test/resources/test-context.xml @@ -4,7 +4,7 @@ xmlns:jwt-signer="http://www.mitre.org/schema/openid-connect/jwt-signer" xsi:schemaLocation= "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" >