mods to keystore, rsasigner, and unit tests

pull/59/head
nemonik 2012-02-13 21:20:35 -05:00
parent 35c09743f9
commit 9a75bb7bd0
10 changed files with 371 additions and 97 deletions

View File

@ -5,14 +5,22 @@ import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.mitre.jwt.signer.AbstractJwtSigner;
import org.mitre.jwt.signer.service.impl.KeyStore;
import org.springframework.beans.factory.InitializingBean;
@ -24,7 +32,7 @@ import com.google.common.collect.Lists;
* JWT Signer using either the RSA SHA-256, SHA-384, SHA-512 hash algorithm
*
* @author AANGANES, nemonik
*
*
*/
public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
@ -32,36 +40,36 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
* an enum for mapping a JWS name to standard algorithm name
*
* @author nemonik
*
*
*/
public enum Algorithm {
//Algorithm constants
RS256("SHA256withRSA"),
RS384("SHA384withRSA"),
RS512("SHA512withRSA");
// Algorithm constants
RS256("SHA256withRSA"), RS384("SHA384withRSA"), RS512("SHA512withRSA");
public static final String DEFAULT = Algorithm.RS256.toString();
/**
* Returns the Algorithm for the name
* @param name
* @return
*/
public static Algorithm getByName(String name) {
for (Algorithm correspondingType : Algorithm.values()) {
if (correspondingType.toString().equals(name)) {
return correspondingType;
}
}
// corresponding type not found
throw new IllegalArgumentException("Algorithm name does not have a corresponding Algorithm");
}
* Returns the Algorithm for the name
*
* @param name
* @return
*/
public static Algorithm getByName(String name) {
for (Algorithm correspondingType : Algorithm.values()) {
if (correspondingType.toString().equals(name)) {
return correspondingType;
}
}
// corresponding type not found
throw new IllegalArgumentException(
"Algorithm name does not have a corresponding Algorithm");
}
private final String standardName;
/**
/**
* Constructor of Algorithm
*
* @param standardName
@ -69,26 +77,31 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
Algorithm(String standardName) {
this.standardName = standardName;
}
/**
* Return the Java standard algorithm name
* @return
*/
public String getStandardName() {
return standardName;
}
/**
* Return the Java standard algorithm name
*
* @return
*/
public String getStandardName() {
return standardName;
}
};
private static Log logger = LogFactory.getLog(RsaSigner.class);
private static Log logger = LogFactory.getLog(RsaSigner.class);
public static final String DEFAULT_PASSWORD = "changeit";
static {
Security.addProvider(new BouncyCastleProvider());
}
private KeyStore keystore;
private String alias;
private String password;
private PrivateKey privateKey;
private PublicKey publicKey;
private RSAPrivateKey privateKey;
private RSAPublicKey publicKey;
private Signature signer;
/**
@ -106,14 +119,15 @@ 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
* @param alias
* @param password
*/
public RsaSigner(String algorithmName, KeyStore keystore, String alias, String password) {
public RsaSigner(String algorithmName, KeyStore keystore, String alias,
String password) {
super(algorithmName);
setKeystore(keystore);
@ -121,22 +135,23 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
setPassword(password);
try {
signer = Signature.getInstance(Algorithm.getByName(algorithmName).getStandardName());
signer = Signature.getInstance(Algorithm.getByName(algorithmName)
.getStandardName());
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void afterPropertiesSet() throws Exception {
KeyPair keyPair = keystore.getKeyPairForAlias(alias, password);
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();
publicKey = (RSAPublicKey) keyPair.getPublic();
privateKey = (RSAPrivateKey) keyPair.getPrivate();
logger.debug("RSA Signer ready for business");
}
@Override
@ -178,7 +193,7 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
public String getAlias() {
return alias;
}
public KeyStore getKeystore() {
return keystore;
}
@ -203,7 +218,9 @@ public class RsaSigner extends AbstractJwtSigner implements InitializingBean {
this.password = password;
}
/* (non-Javadoc)
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override

View File

@ -1,18 +1,35 @@
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.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
/**
* Creates and manages a JCE KeyStore
@ -20,18 +37,165 @@ import org.springframework.util.StringUtils;
* @author nemonik
*
*/
@SuppressWarnings("deprecation")
public class KeyStore implements InitializingBean {
// TODO: Doesn't have a provider attribute, getter/setter. Not sure we need.
private static Log logger = LogFactory.getLog(KeyStore.class);
private static final String TYPE = java.security.KeyStore.getDefaultType();
private static final String PASSWORD = "changeit";
public static final String TYPE = "BKS";
public static final String PASSWORD = "changeit";
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* Creates a certificate.
*
* @param domainName
* @param daysNotValidBefore
* @param daysNotValidAfter
* @return
*/
private static X509V3CertificateGenerator createCertificate(
String domainName, int daysNotValidBefore, int daysNotValidAfter) {
// BC docs say to use another, but it seemingly isn't included...
X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
v3CertGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
v3CertGen.setIssuerDN(new X509Principal("CN=" + domainName
+ ", 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=" + domainName
+ ", 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(String location,
String domainName, String alias, String keystorePassword,
String aliasPassword, int daysNotValidBefore, int daysNotValidAfter)
throws GeneralSecurityException, IOException {
java.security.KeyStore ks = loadJceKeyStore(location, keystorePassword);
KeyPairGenerator rsaKeyPairGenerator = KeyPairGenerator
.getInstance("RSA");
rsaKeyPairGenerator.initialize(2048);
KeyPair rsaKeyPair = rsaKeyPairGenerator.generateKeyPair();
X509V3CertificateGenerator v3CertGen = createCertificate(domainName,
daysNotValidBefore, daysNotValidAfter);
RSAPublicKey rsaPublicKey = (RSAPublicKey) rsaKeyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) rsaKeyPair.getPrivate();
v3CertGen.setPublicKey(rsaPublicKey);
v3CertGen.setSignatureAlgorithm("SHA1withRSA"); // "MD5WithRSAEncryption");
// 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 });
storeJceKeyStore(location, keystorePassword, ks);
return ks;
}
/**
* Creates or loads a JCE KeyStore
* @param location
* @param keystorePassword
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
private static java.security.KeyStore loadJceKeyStore(String location, String keystorePassword) throws GeneralSecurityException, IOException {
java.security.KeyStore ks = java.security.KeyStore.getInstance(TYPE);
File keystoreFile = new File(location);
if (!keystoreFile.exists()) {
ks.load(null, null);
} else {
InputStream ios = new FileInputStream(keystoreFile);
try {
ks.load(ios, keystorePassword.toCharArray());
logger.info("Loaded keystore from " + location);
} finally {
ios.close();
}
}
return ks;
}
public static void main(String[] args) {
//TODO create a cmd-line to create the KeyStore?
try {
KeyStore.generateRsaKeyPair("/tmp/keystore.jks",
"OpenID Connect Server", "test", KeyStore.PASSWORD,
KeyStore.PASSWORD, 30, 365);
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Store the JCE KeyStore
*
* @param location
* @param keystorePassword
* @param ks
* @throws FileNotFoundException
* @throws KeyStoreException
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
*/
private static void storeJceKeyStore(String location,
String keystorePassword, java.security.KeyStore ks)
throws FileNotFoundException, KeyStoreException, IOException,
NoSuchAlgorithmException, CertificateException {
File keystoreFile = new File(location);
FileOutputStream fos = new FileOutputStream(keystoreFile);
try {
ks.store(fos, keystorePassword.toCharArray());
} finally {
fos.close();
}
logger.info("Keystore created here: " + keystoreFile.getAbsolutePath());
}
private String password;
private Resource location;
private String type;
private java.security.KeyStore keystore;
@ -39,15 +203,7 @@ public class KeyStore implements InitializingBean {
* default constructor
*/
public KeyStore() {
this(PASSWORD, null, TYPE);
}
/**
* @param password
* @param location
*/
public KeyStore(String password, Resource location) {
this(password, location, TYPE);
this(PASSWORD, null);
}
/**
@ -57,15 +213,11 @@ public class KeyStore implements InitializingBean {
* the password used to unlock the keystore
* @param location
* the location of the keystore
* @param type
* the type of keystore. See Appendix A in the Java Cryptography
* Architecture API Specification & Reference for information
* about standard keystore types.
*/
public KeyStore(String password, Resource location, String type) {
public KeyStore(String password, Resource location) {
setPassword(password);
setLocation(location);
setType(type);
}
/*
@ -75,13 +227,12 @@ public class KeyStore implements InitializingBean {
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws IOException,
GeneralSecurityException {
public void afterPropertiesSet() throws Exception {
InputStream inputStream = null;
try {
keystore = java.security.KeyStore.getInstance(type);
keystore = java.security.KeyStore.getInstance(TYPE);
inputStream = location.getInputStream();
keystore.load(inputStream, this.password.toCharArray());
@ -91,6 +242,10 @@ public class KeyStore implements InitializingBean {
inputStream.close();
}
}
if (keystore.size() == 0) {
throw new Exception("Keystore is empty; it has no entries");
}
}
/**
@ -123,6 +278,10 @@ public class KeyStore implements InitializingBean {
return null;
}
public java.security.KeyStore getKeystore() {
return keystore;
}
public Resource getLocation() {
return location;
}
@ -131,8 +290,14 @@ public class KeyStore implements InitializingBean {
return password;
}
public String getType() {
return type;
public Provider getProvider() {
return keystore.getProvider();
}
public void setKeystore(java.security.KeyStore keystore) {
this.keystore = keystore;
}
public void setLocation(Resource location) {
@ -147,14 +312,6 @@ public class KeyStore implements InitializingBean {
this.password = password;
}
public void setType(String type) {
if (StringUtils.hasLength(type)) {
this.type = type;
} else {
throw new IllegalArgumentException("type must not be empty");
}
}
/*
* (non-Javadoc)
*
@ -163,7 +320,6 @@ public class KeyStore implements InitializingBean {
@Override
public String toString() {
return "KeyStore [password=" + password + ", location=" + location
+ ", type=" + type + ", keystore=" + keystore + "]";
+ ", keystore=" + keystore + "]";
}
}

View File

@ -52,11 +52,6 @@ public class KeystoreDefinitionParser extends
builder.addConstructorArgValue(resource);
}
}
String type = element.getAttribute("type");
if (StringUtils.hasText(type)) {
builder.addConstructorArgValue(type);
}
}
/*

View File

@ -18,7 +18,6 @@
<xs:extension base="beans:identifiedType">
<xs:attribute name="location" type="xs:string" use="required" />
<xs:attribute name="password" type="xs:string" />
<xs:attribute name="type" type="xs:string" />
</xs:extension>
</xs:complexContent>
</xs:complexType>

View File

@ -72,14 +72,13 @@
</map>
</property>
</bean>
<jwt-signer:keystore id="defaultKeystore" location="file:src/main/webapp/WEB-INF/spring/keystore.jks" password="changeit" type="JKS" />
<jwt-signer:keystore id="defaultKeystore" location="file:src/main/webapp/WEB-INF/spring/keystore.jks" password="changeit" />
<jwt-signer:service id="defaultSignerService">
<jwt-signer:rsa bits="256" keystore-ref="defaultKeystore" key-alias="test" password="changeit" />
<jwt-signer:rsa bits="256" keystore-ref="defaultKeystore" key-alias="test" password="changeit" />
<jwt-signer:hmac bits="256" passphrase="changeit" />
</jwt-signer:service>
<!-- scheduled tasks -->
<task:scheduler id="taskScheduler" pool-size="10" />

View File

@ -4,16 +4,30 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import java.io.UnsupportedEncodingException;
import java.security.Security;
import java.util.Date;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mitre.jwt.model.Jwt;
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.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.KeyStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:test-context.xml"})
public class JwtTest {
// @Autowired
// KeyStore keystore;
@Test
public void testToStringPlaintext() {
Jwt jwt = new Jwt();
@ -81,6 +95,40 @@ public class JwtTest {
}
// @Test
// public void testGenerateRsaSignature() {
// 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);
//
// // sign it
// System.out.println("keystore PROVIDER::" + keystore.getProvider());
//
// JwtSigner signer = new RsaSigner(RsaSigner.Algorithm.DEFAULT, keystore, "test");
//
// signer.sign(jwt);
//
// System.out.println("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv");
// System.out.println("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv");
// System.out.println("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv");
// System.out.println(jwt.getSignature());
// System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
// System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
// System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
//
//// String signature = "p-63Jzz7mgi3H4hvW6MFB7lmPRZjhsL666MYkmpX33Y";
//// String expected = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEzMDA4MTkzODAsImlzcyI6ImpvZSIsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ." + signature;
////
//// String actual = jwt.toString();
////
//// assertThat(actual, equalTo(expected));
//// assertThat(jwt.getSignature(), equalTo(signature));
//
// }
@Test
public void testValidateHmacSignature() {
// sign it

View File

@ -0,0 +1,54 @@
package org.mitre.jwt.signer.service.impl;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.Key;
import org.junit.Test;
import org.junit.runner.RunWith;
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;
@SuppressWarnings("restriction") // I know...
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"file:src/main/webapp/WEB-INF/spring/application-context.xml",
"classpath:test-context.xml" })
public class KeyStoreTest {
@Autowired
@Qualifier("testKeystore")
KeyStore keystore;
@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()));
}
@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()));
}
}

Binary file not shown.

View File

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" >
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" >
<!-- Creates an in-memory database populated with test jdbc -->
<bean id="dataSource" class="org.mitre.jdbc.datasource.H2DataSourceFactory">
@ -33,4 +36,7 @@
</map>
</property>
</bean>
<jwt-signer:keystore id="testKeystore" location="file:src/test/resources/keystore.jks" password="changeit" />
</beans>