moved to JPSK based key store

pull/306/merge
Justin Richer 2013-03-28 15:03:44 -04:00
parent e2ad4d2e8f
commit 5a04198eac
7 changed files with 186 additions and 360 deletions

View File

@ -0,0 +1,92 @@
/**
*
*/
package org.mitre.jose.keystore;
import java.io.InputStreamReader;
import java.util.List;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import com.google.common.base.Charsets;
import com.google.common.io.CharStreams;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
/**
* @author jricher
*
*/
public class JWKSetKeyStore implements InitializingBean {
private JWKSet jwkSet;
private Resource location;
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
if (jwkSet == null) {
if (location != null) {
if (location.exists() && location.isReadable()) {
// read in the file from disk
String s = CharStreams.toString(new InputStreamReader(location.getInputStream(), Charsets.UTF_8));
// parse it into a jwkSet object
jwkSet = JWKSet.parse(s);
} else {
throw new IllegalArgumentException("Key Set resource could not be read: " + location);
}
} else {
throw new IllegalArgumentException("Key store must be initialized with at least one of a jwkSet or a location.");
}
}
}
/**
* @return the jwkSet
*/
public JWKSet getJwkSet() {
return jwkSet;
}
/**
* @param jwkSet the jwkSet to set
*/
public void setJwkSet(JWKSet jwkSet) {
this.jwkSet = jwkSet;
}
/**
* @return the location
*/
public Resource getLocation() {
return location;
}
/**
* @param location the location to set
*/
public void setLocation(Resource location) {
this.location = location;
}
/**
* Pass through to underlying JwK set and return its keys.
* @return
*/
public List<JWK> getKeys() {
return jwkSet.getKeys();
}
}

View File

@ -1,171 +0,0 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.mitre.jwt.encryption.impl;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
/**
* Creates and manages a JCE KeyStore
*
* @author nemonik
*
*/
public class KeyStore implements InitializingBean {
private static Logger logger = LoggerFactory.getLogger(KeyStore.class);
public static final String TYPE = java.security.KeyStore.getDefaultType();
public static final String PASSWORD = "changeit";
private String password;
private Resource location;
private java.security.KeyStore keystore;
/**
* Default Constructor
*/
public KeyStore() {
}
/**
* KeyStore constructor
*
* @param password
* the password used to unlock the keystore
* @param location
* the location of the keystore
*/
public KeyStore(String password, Resource location) {
setPassword(password);
setLocation(location);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
InputStream inputStream = null;
try {
keystore = java.security.KeyStore.getInstance(TYPE);
inputStream = location.getInputStream();
keystore.load(inputStream, this.password.toCharArray());
logger.info("Loaded keystore from " + location);
} finally {
if (inputStream != null) {
inputStream.close();
}
}
// TODO: a more specific exception perhaps? is an empty keystore even an exception?
if (keystore.size() == 0) {
throw new Exception("Keystore is empty; it has no entries");
}
}
/**
* Returns a KeyPair for the alias given the password
*
* @param alias
* the alias name
* @param password
* the password for recovering the key pair
* @return the key pair
* @throws GeneralSecurityException
*/
public KeyPair getKeyPairForAlias(String alias, String password)
throws GeneralSecurityException {
Key key = keystore.getKey(alias, password.toCharArray());
if (key instanceof PrivateKey) {
// Get certificate of public key
java.security.cert.Certificate cert = keystore
.getCertificate(alias);
// Get public key
PublicKey publicKey = cert.getPublicKey();
return new KeyPair(publicKey, (PrivateKey) key);
}
return null;
}
public java.security.KeyStore getKeystore() {
return keystore;
}
public Resource getLocation() {
return location;
}
public String getPassword() {
return password;
}
public Provider getProvider() {
return keystore.getProvider();
}
public void setKeystore(java.security.KeyStore keystore) {
this.keystore = keystore;
}
public void setLocation(Resource location) {
if (location != null && location.exists()) {
this.location = location;
} else {
throw new IllegalArgumentException("location must exist");
}
}
public void setPassword(String password) {
this.password = password;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "KeyStore [password=" + password + ", location=" + location
+ ", keystore=" + keystore + "]";
}
}

View File

@ -17,23 +17,30 @@ package org.mitre.jwt.signer.service.impl;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.mitre.jose.keystore.JWKSetKeyStore;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.collect.ImmutableMap;
import com.google.common.base.Strings;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.SignedJWT;
public class DefaultJwtSigningAndValidationService implements JwtSigningAndValidationService, InitializingBean {
@ -45,9 +52,11 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
private static Logger logger = LoggerFactory.getLogger(DefaultJwtSigningAndValidationService.class);
private String defaultSignerId;
private String defaultSignerKeyId;
private JWSAlgorithm defaultAlgorithm;
private JWKSetKeyStore keyStore;
/**
* default constructor
@ -64,38 +73,17 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
}
/**
* Load this signing and validation service from the given builders (which load keys from keystores)
* @param builders
* @return the defaultSignerKeyId
*/
public void setBuilders(Map<String, RSASSASignerVerifierBuilder> builders) {
for (Entry<String, RSASSASignerVerifierBuilder> e : builders.entrySet()) {
JWSSigner signer = e.getValue().buildSigner();
signers.put(e.getKey(), signer);
if (e.getValue().isDefault()) {
defaultSignerId = e.getKey();
}
JWSVerifier verifier = e.getValue().buildVerifier();
verifiers.put(e.getKey(), verifier);
}
public String getDefaultSignerKeyId() {
return defaultSignerKeyId;
}
/**
* @return the defaultSignerId
* @param defaultSignerKeyId the defaultSignerKeyId to set
*/
public String getDefaultSignerId() {
return defaultSignerId;
}
/**
* @param defaultSignerId the defaultSignerId to set
*/
public void setDefaultSignerId(String defaultSignerId) {
this.defaultSignerId = defaultSignerId;
public void setDefaultSignerKeyId(String defaultSignerId) {
this.defaultSignerKeyId = defaultSignerId;
}
/**
@ -117,6 +105,20 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
return null;
}
}
/**
* @return the keyStore
*/
public JWKSetKeyStore getKeyStore() {
return keyStore;
}
/**
* @param keyStore the keyStore to set
*/
public void setKeyStore(JWKSetKeyStore keyStore) {
this.keyStore = keyStore;
}
/*
* (non-Javadoc)
*
@ -124,7 +126,47 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet(){
public void afterPropertiesSet() throws NoSuchAlgorithmException, InvalidKeySpecException{
if (keyStore != null) {
// if we have a keystore, load it up into our signers
List<JWK> keys = keyStore.getKeys();
for (JWK jwk : keys) {
if (!Strings.isNullOrEmpty(jwk.getKeyID())) {
if (jwk instanceof RSAKey) {
// build RSA signers & verifiers
RSASSASigner signer = new RSASSASigner(((RSAKey) jwk).toRSAPrivateKey());
RSASSAVerifier verifier = new RSASSAVerifier(((RSAKey) jwk).toRSAPublicKey());
signers.put(jwk.getKeyID(), signer);
verifiers.put(jwk.getKeyID(), verifier);
} else if (jwk instanceof ECKey) {
// build EC signers & verifiers
// TODO: add support for EC keys
logger.warn("EC Keys are not yet supported.");
} else if (jwk instanceof OctetSequenceKey) {
// build HMAC signers & verifiers
MACSigner signer = new MACSigner(((OctetSequenceKey) jwk).toByteArray());
MACVerifier verifier = new MACVerifier(((OctetSequenceKey) jwk).toByteArray());
signers.put(jwk.getKeyID(), signer);
verifiers.put(jwk.getKeyID(), verifier);
} else {
logger.warn("Unknown key type: " + jwk);
}
} else {
logger.warn("Found a key with no KeyId: " + jwk);
}
}
}
logger.info("DefaultJwtSigningAndValidationService is ready: " + this.toString());
}
@ -133,11 +175,11 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
*/
@Override
public void signJwt(SignedJWT jwt) {
if (getDefaultSignerId() == null) {
if (getDefaultSignerKeyId() == null) {
throw new IllegalStateException("Tried to call default signing with no default signer ID set");
}
JWSSigner signer = signers.get(getDefaultSignerId());
JWSSigner signer = signers.get(getDefaultSignerKeyId());
try {
jwt.sign(signer);

View File

@ -1,139 +0,0 @@
/**
*
*/
package org.mitre.jwt.signer.service.impl;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import org.mitre.jwt.encryption.impl.KeyStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
/**
* @author jricher
*
*/
public class RSASSASignerVerifierBuilder {
private static Logger log = LoggerFactory.getLogger(RSASSASignerVerifierBuilder.class);
private String alias;
private String password;
private KeyStore keystore;
private boolean defaultSigner = false;
/**
* @return the alias
*/
public String getAlias() {
return alias;
}
/**
* @param alias the alias to set
*/
public void setAlias(String alias) {
this.alias = alias;
}
/**
* @return the password
*/
public String getPassword() {
return password;
}
/**
* @param password the password to set
*/
public void setPassword(String password) {
this.password = password;
}
/**
* @return the keystore
*/
public KeyStore getKeystore() {
return keystore;
}
/**
* @param keystore the keystore to set
*/
public void setKeystore(KeyStore keystore) {
this.keystore = keystore;
}
/**
* @return the defaultSigner
*/
public boolean isDefault() {
return defaultSigner;
}
/**
* @param defaultSigner the defaultSigner to set
*/
public void setDefault(boolean defaultSigner) {
this.defaultSigner = defaultSigner;
}
/**
* Build the signer as configured from the given keystore, null if it can't be built for some reason
* @return
*/
public RSASSASigner buildSigner() {
try {
KeyPair keyPair = keystore.getKeyPairForAlias(alias, password);
PrivateKey privateKey = keyPair.getPrivate();
if (privateKey instanceof RSAPrivateKey) {
RSASSASigner signer = new RSASSASigner((RSAPrivateKey) privateKey);
return signer;
} else {
log.warn("Couldn't build signer, referenced key is not RSA");
return null;
}
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
log.warn("Couldn't buld signer:", e);
}
log.warn("Couldn't build signer");
return null;
}
/**
* Build the signer as configured from the given keystore, null if it can't be built for some reason
* @return
*/
public RSASSAVerifier buildVerifier() {
try {
KeyPair keyPair = keystore.getKeyPairForAlias(alias, password);
PublicKey publicKey = keyPair.getPublic();
if (publicKey instanceof RSAPublicKey) {
RSASSAVerifier signer = new RSASSAVerifier((RSAPublicKey) publicKey);
return signer;
} else {
log.warn("Couldn't build verifier, referenced key is not RSA");
return null;
}
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
log.warn("Couldn't buld verifier:", e);
}
log.warn("Couldn't build verifier");
return null;
}
}

View File

@ -0,0 +1,13 @@
{
"keys":
[
{
"alg": "RS256",
"d": "RkRPP2wJQQWOTTjgRnt4WLEl2rpo8Vpg586NhrE8QbJfu0XfrrTL6_pz0kCyqcFcy0Aq0QO5-Rn56M7aZFE5cqo8xq4pPM32IfvlKTuxQxW_0I9fod-dy9GMnhVCO9WKhXQ23H6vKYdq0gsI9ov_S1jIN7JPwdyzzLpAGXf12I0",
"e": "AQAB",
"n": "23zs5r8PQKpsKeoUd2Bjz3TJkUljWqMD8X98SaIb1LE7dCQzi9jwO58FGL0ieY1Dfnr9-g1iiY8sNzV-byawK98W9yFiopaghfoKtxXgUD8pi0fLPeWmAkntjn28Z_WZvvA265ELbBhphPXEJcFhdzUfgESHVuqFMEqp1pB-CP0",
"kty": "RSA",
"kid": "rsa1"
}
]
}

View File

@ -3,25 +3,14 @@
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.xsd">
<bean id="defaultKeystore" class="org.mitre.jwt.encryption.impl.KeyStore">
<constructor-arg name="location" value="classpath:keystore.jks" />
<constructor-arg name="password" value="changeit" />
<bean id="defaultKeyStore" class="org.mitre.jose.keystore.JWKSetKeyStore">
<property name="location" value="classpath:keystore.jwks" />
</bean>
<bean id="defaultsignerService"
class="org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService">
<property name="builders">
<map>
<entry key="rsa1">
<bean id="rsaSignerBuilder" class="org.mitre.jwt.signer.service.impl.RSASSASignerVerifierBuilder">
<property name="keystore" ref="defaultKeystore" />
<property name="alias" value="rsa" />
<property name="password" value="changeit" />
<property name="default" value="true" />
</bean>
</entry>
</map>
</property>
<property name="keyStore" ref="defaultKeyStore" />
<property name="defaultSignerKeyId" value="rsa1" />
<property name="defaultSigningAlgorithmName" value="RS256" />
</bean>