wrote default signer; added schema, definition parsers, handlers, and mapping files to configure; modified the signers, and added a keystore class to support

pull/59/head
nemonik 2012-02-13 11:23:10 -05:00
parent b0934eb51e
commit bb61292ec6
24 changed files with 1348 additions and 380 deletions

View File

@ -1,7 +1,5 @@
package org.mitre.jdbc.datasource.util;
import org.springframework.core.io.Resource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
@ -12,6 +10,10 @@ import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
/**
* @author Matt Franklin
*
@ -21,6 +23,9 @@ import java.util.regex.Pattern;
*
*/
public class SqlFileParser {
private static Log logger = LogFactory.getLog(SqlFileParser.class);
private static final Pattern WORD_PATTERN = Pattern
.compile("^([a-zA-Z]*)[ ;]");
private static final String CHILD_SCRIPT_INDICATOR = "@@";
@ -77,9 +82,8 @@ public class SqlFileParser {
processFile(resourceFile, sql);
stateStack.pop();
//System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");
//System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> SQL:: " + sql);
//System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");
logger.debug(" SQL:: " + sql);
return sql.toString();
}

View File

@ -1,46 +0,0 @@
package org.mitre.jwt.service.impl;
import java.security.PublicKey;
import java.util.List;
import org.mitre.jwt.model.Jwt;
import org.mitre.jwt.service.JwtSigningAndValidationService;
import org.springframework.stereotype.Service;
/**
* THIS IS A STUB
*
* TODO: Implement
*
* @author AANGANES
*
*/
@Service
public class DefaultJwtSigningAndValidationService implements
JwtSigningAndValidationService {
@Override
public List<PublicKey> getAllPublicKeys() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean validateSignature(String jwtString) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean validateIssuedJwt(Jwt jwt) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isJwtExpired(Jwt jwt) {
// TODO Auto-generated method stub
return false;
}
}

View File

@ -10,12 +10,7 @@ import com.google.common.base.Strings;
import com.google.common.collect.Lists;
public abstract class AbstractJwtSigner implements JwtSigner {
public static final String PLAINTEXT = "none";
public static final String HS256 = "HS256";
public static final String HS384 = "HS384";
public static final String HS512 = "HS512";
private String algorithm;
public AbstractJwtSigner(String algorithm) {
@ -77,6 +72,4 @@ public abstract class AbstractJwtSigner implements JwtSigner {
protected abstract String generateSignature(String signatureBase);
}

View File

@ -0,0 +1,164 @@
package org.mitre.jwt.signer.impl;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import org.mitre.jwt.signer.AbstractJwtSigner;
import org.mitre.jwt.signer.service.impl.KeyStore;
import org.springframework.beans.factory.InitializingBean;
public class EcdsaSigner extends AbstractJwtSigner implements InitializingBean {
/**
* an enum for mapping a JWS name to standard algorithm name
*
* @author nemonik
*
*/
public enum Algorithm {
//Algorithm constants
ES256("SHA256withECDSA"),
ES384("SHA384withECDSA"),
ES512("SHA512withECDSA");
private static final String DEFAULT = Algorithm.ES256.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");
}
private final String standardName;
/**
* Constructor of Algorithm
*
* @param standardName
*/
Algorithm(String standardName) {
this.standardName = standardName;
}
/**
* Return the Java standard algorithm name
* @return
*/
public String getStandardName() {
return standardName;
}
};
private KeyStore keystore;
private String alias;
private String password;
private PrivateKey privateKey;
private PublicKey publicKey;
private Signature signer;
public EcdsaSigner() {
this(Algorithm.DEFAULT, null, null, null);
}
public EcdsaSigner(String algorithmName, KeyStore keystore, String alias, String password) {
super(algorithmName);
setKeystore(keystore);
setAlias(alias);
setPassword(password);
try {
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();
}
@Override
protected String generateSignature(String signatureBase) {
/*
1) Generate a digital signature of the UTF-8 representation of the JWS Signing Input
using ECDSA P-256 SHA-256 with the desired private key. The output will be the
EC point (R, S), where R and S are unsigned integers.
2) Turn R and S into byte arrays in big endian order. Each array will be 32 bytes long.
3) Concatenate the two byte arrays in the order R and then S.
4) Base64url encode the resulting 64 byte array.
*/
return null;
}
public String getAlias() {
return alias;
}
public KeyStore getKeystore() {
return keystore;
}
public String getPassword() {
return password;
}
public PublicKey getPublicKey() {
return publicKey;
}
public void setAlias(String alias) {
this.alias = alias;
}
public void setKeystore(KeyStore keyStore) {
this.keystore = keyStore;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean verify(String jwtString) {
/*
1) Take the Encoded JWS Signature and base64url decode it into a byte array.
If decoding fails, the signed content MUST be rejected.
2) The output of the base64url decoding MUST be a 64 byte array.
3) Split the 64 byte array into two 32 byte arrays. The first array will be R and
the second S. Remember that the byte arrays are in big endian byte order;
please check the ECDSA validator in use to see what byte order it requires.
4) Submit the UTF-8 representation of the JWS Signing Input, R, S and the public
key (x, y) to the ECDSA P-256 SHA-256 validator.
5) If the validation fails, the signed content MUST be rejected.
*/
return false;
}
}

View File

@ -1,48 +0,0 @@
package org.mitre.jwt.signer.impl;
import org.mitre.jwt.signer.AbstractJwtSigner;
public class Es256Signer extends AbstractJwtSigner {
public Es256Signer() {
this(null);
}
public Es256Signer(String algorithm) {
super("ES256");
}
@Override
protected String generateSignature(String signatureBase) {
/*
1) Generate a digital signature of the UTF-8 representation of the JWS Signing Input
using ECDSA P-256 SHA-256 with the desired private key. The output will be the
EC point (R, S), where R and S are unsigned integers.
2) Turn R and S into byte arrays in big endian order. Each array will be 32 bytes long.
3) Concatenate the two byte arrays in the order R and then S.
4) Base64url encode the resulting 64 byte array.
*/
return null;
}
@Override
public boolean verify(String jwtString) {
/*
1) Take the Encoded JWS Signature and base64url decode it into a byte array.
If decoding fails, the signed content MUST be rejected.
2) The output of the base64url decoding MUST be a 64 byte array.
3) Split the 64 byte array into two 32 byte arrays. The first array will be R and
the second S. Remember that the byte arrays are in big endian byte order;
please check the ECDSA validator in use to see what byte order it requires.
4) Submit the UTF-8 representation of the JWS Signing Input, R, S and the public
key (x, y) to the ECDSA P-256 SHA-256 validator.
5) If the validation fails, the signed content MUST be rejected.
*/
return false;
}
}

View File

@ -1,97 +0,0 @@
package org.mitre.jwt.signer.impl;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.mitre.jwt.signer.AbstractJwtSigner;
public class Hmac256Signer extends AbstractJwtSigner {
private Mac mac;
private byte[] passphrase;
/**
* Create a signer with no passphrase
*/
public Hmac256Signer() {
this(null);
}
/**
* Create a signer with the given passphrase
* @param passphrase
*/
public Hmac256Signer(byte[] passphrase) {
super(HS256);
//TODO: set up a factory for other signature methods
setPassphrase(passphrase);
try {
mac = Mac.getInstance("HMACSHA256");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
protected String generateSignature(String signatureBase) {
if (passphrase == null) {
return null; // TODO: probably throw some kind of exception
}
try {
mac.init(new SecretKeySpec(getPassphrase(), mac.getAlgorithm()));
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
mac.update(signatureBase.getBytes("UTF-8"));
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] sigBytes = mac.doFinal();
String sig = new String(Base64.encodeBase64URLSafe(sigBytes));
// strip off any padding
sig = sig.replace("=", "");
return sig;
}
/**
* @return the passphrase
*/
public byte[] getPassphrase() {
return passphrase;
}
/**
* @param passphrase the passphrase to set
*/
public void setPassphrase(byte[] passphrase) {
this.passphrase = passphrase;
}
}

View File

@ -0,0 +1,208 @@
package org.mitre.jwt.signer.impl;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mitre.jwt.signer.AbstractJwtSigner;
/**
* JWT Signer using either the HMAC SHA-256, SHA-384, SHA-512 hash algorithm
*
* @author AANGANES, nemonik
*
*/
public class HmacSigner extends AbstractJwtSigner {
/**
* an enum for mapping a JWS name to standard algorithm name
*
* @author nemonik
*
*/
public enum Algorithm {
// Algorithm constants
HS256("HMACSHA256"), HS384("HMACSHA384"), HS512("HMACSHA512");
public static final String DEFAULT = Algorithm.HS256.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");
}
private final String standardName;
/**
* Constructor of Algorithm
*
* @param standardName
*/
Algorithm(String standardName) {
this.standardName = standardName;
}
/**
* Return the Java standard algorithm name
*
* @return
*/
public String getStandardName() {
return standardName;
}
};
private static Log logger = LogFactory.getLog(HmacSigner.class);
private Mac mac;
private String passphrase;
/**
* Create a signer with no passphrase
*/
public HmacSigner() {
super(Algorithm.DEFAULT);
}
/**
* Create HMAC singer with default algorithm and passphrase as raw bytes
*
* @param passphraseAsRawBytes
*/
public HmacSigner(byte[] passphraseAsRawBytes) {
this(Algorithm.DEFAULT, new String(passphraseAsRawBytes,
Charset.forName("UTF-8")));
}
/**
* Create HMAC singer with default algorithm and passphrase
*
* @param passwordAsRawBytes
*/
public HmacSigner(String passphrase) {
this(Algorithm.DEFAULT, passphrase);
}
/**
* Create HMAC singer with given algorithm and password as raw bytes
*
* @param algorithmName
* the JWS name for the standard name of the requested MAC
* algorithm
* @param passphraseAsRawBytes
*/
public HmacSigner(String algorithmName, byte[] passphraseAsRawBytes) {
this(algorithmName, new String(passphraseAsRawBytes,
Charset.forName("UTF-8")));
}
/**
* Create HMAC singer with given algorithm and passwords
*
* @param algorithmName
* the JWS name for the standard name of the requested MAC
* algorithm
* @param passphrase
* the passphrase
*/
public HmacSigner(String algorithmName, String passphrase) {
super(algorithmName);
setPassphrase(passphrase);
try {
mac = Mac.getInstance(Algorithm.getByName(algorithmName)
.getStandardName());
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
* (non-Javadoc)
*
* @see
* org.mitre.jwt.signer.AbstractJwtSigner#generateSignature(java.lang.String
* )
*/
@Override
protected String generateSignature(String signatureBase) {
if (passphrase == null) {
return null; // TODO: probably throw some kind of exception
}
try {
mac.init(new SecretKeySpec(getPassphrase().getBytes(), mac
.getAlgorithm()));
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
mac.update(signatureBase.getBytes("UTF-8"));
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] sigBytes = mac.doFinal();
String sig = new String(Base64.encodeBase64URLSafe(sigBytes));
// strip off any padding
sig = sig.replace("=", "");
return sig;
}
public String getPassphrase() {
return passphrase;
}
public void setPassphrase(byte[] rawbytes) {
this.setPassphrase(new String(rawbytes, Charset.forName("UTF-8")));
}
public void setPassphrase(String passphrase) {
if (passphrase.isEmpty())
throw new IllegalArgumentException("passphrase must be set");
this.passphrase = passphrase;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "HmacSigner [mac=" + mac + ", passphrase=" + passphrase + "]";
}
}

View File

@ -4,6 +4,8 @@ import org.mitre.jwt.signer.AbstractJwtSigner;
public class PlaintextSigner extends AbstractJwtSigner {
public static final String PLAINTEXT = "none";
public PlaintextSigner() {
super(PLAINTEXT);
}

View File

@ -1,149 +0,0 @@
package org.mitre.jwt.signer.impl;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.mitre.jwt.signer.AbstractJwtSigner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
/**
* JWT Signer using RSA SHA-256 algorithm
* @author AANGANES
*
*/
public class Rs256Signer extends AbstractJwtSigner {
private PrivateKey privateKey;
private PublicKey publicKey;
private Signature signer;
public Rs256Signer() {
this(null, null);
}
public Rs256Signer(PublicKey publicKey, PrivateKey privateKey) {
super("RS256");
setPublicKey(publicKey);
setPrivateKey(privateKey);
try {
signer = Signature.getInstance("SHA256withRSA");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
protected String generateSignature(String signatureBase) {
try {
signer.initSign(privateKey);
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
signer.update(signatureBase.getBytes("UTF-8"));
} catch (SignatureException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] sigBytes;
String sig = "";
try {
sigBytes = signer.sign();
sig = new String(Base64.encodeBase64URLSafe(sigBytes));
// strip off any padding
sig = sig.replace("=", "");
} catch (SignatureException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sig;
}
@Override
public boolean verify(String jwtString) {
// split on the dots
List<String> parts = Lists.newArrayList(Splitter.on(".").split(jwtString));
if (parts.size() != 3) {
throw new IllegalArgumentException("Invalid JWT format.");
}
String h64 = parts.get(0);
String c64 = parts.get(1);
String s64 = parts.get(2);
String signingInput = h64 + "." + c64;
try {
signer.initVerify(publicKey);
} catch (InvalidKeyException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return false;
}
try {
signer.update(signingInput.getBytes("UTF-8"));
} catch (SignatureException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
signer.verify(s64.getBytes("UTF-8"));
} catch (SignatureException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
return true;
}
public PrivateKey getPrivateKey() {
return privateKey;
}
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
}
public PublicKey getPublicKey() {
return publicKey;
}
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}
}

View File

@ -0,0 +1,265 @@
package org.mitre.jwt.signer.impl;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mitre.jwt.signer.AbstractJwtSigner;
import org.mitre.jwt.signer.service.impl.KeyStore;
import org.springframework.beans.factory.InitializingBean;
import com.google.common.base.Splitter;
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 {
/**
* an enum for mapping a JWS name to standard algorithm name
*
* @author nemonik
*
*/
public enum Algorithm {
//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");
}
private final String standardName;
/**
* Constructor of Algorithm
*
* @param standardName
*/
Algorithm(String standardName) {
this.standardName = standardName;
}
/**
* Return the Java standard algorithm name
* @return
*/
public String getStandardName() {
return standardName;
}
};
private static Log logger = LogFactory.getLog(RsaSigner.class);
public static final String DEFAULT_PASSWORD = "changeit";
private KeyStore keystore;
private String alias;
private String password;
private PrivateKey privateKey;
private PublicKey publicKey;
private Signature signer;
/**
* Default constructor
*/
public RsaSigner() {
this(Algorithm.DEFAULT, null, null, DEFAULT_PASSWORD);
}
/**
* @param algorithmName
* @param keystore
* @param alias
*/
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) {
super(algorithmName);
setKeystore(keystore);
setAlias(alias);
setPassword(password);
try {
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();
logger.debug("RSA Signer ready for business");
}
@Override
protected String generateSignature(String signatureBase) {
try {
signer.initSign(privateKey);
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
signer.update(signatureBase.getBytes("UTF-8"));
} catch (SignatureException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] sigBytes;
String sig = "";
try {
sigBytes = signer.sign();
sig = new String(Base64.encodeBase64URLSafe(sigBytes));
// strip off any padding
sig = sig.replace("=", "");
} catch (SignatureException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sig;
}
public String getAlias() {
return alias;
}
public KeyStore getKeystore() {
return keystore;
}
public String getPassword() {
return password;
}
public PublicKey getPublicKey() {
return publicKey;
}
public void setAlias(String alias) {
this.alias = alias;
}
public void setKeystore(KeyStore keyStore) {
this.keystore = keyStore;
}
public void setPassword(String password) {
this.password = password;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "RsaSigner [keystore=" + keystore + ", alias=" + alias
+ ", password=" + password + ", privateKey=" + privateKey
+ ", publicKey=" + publicKey + ", signer=" + signer + "]";
}
@Override
public boolean verify(String jwtString) {
// split on the dots
List<String> parts = Lists.newArrayList(Splitter.on(".").split(
jwtString));
if (parts.size() != 3) {
throw new IllegalArgumentException("Invalid JWT format.");
}
String h64 = parts.get(0);
String c64 = parts.get(1);
String s64 = parts.get(2);
String signingInput = h64 + "." + c64;
try {
signer.initVerify(publicKey);
} catch (InvalidKeyException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return false;
}
try {
signer.update(signingInput.getBytes("UTF-8"));
} catch (SignatureException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
signer.verify(s64.getBytes("UTF-8"));
} catch (SignatureException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
return true;
}
}

View File

@ -1,4 +1,4 @@
package org.mitre.jwt.service;
package org.mitre.jwt.signer.service;
import java.security.PublicKey;
import java.util.List;
@ -9,29 +9,38 @@ public interface JwtSigningAndValidationService {
/**
* Returns all public keys this service is configured with.
*
* @return
*/
public List<PublicKey> getAllPublicKeys();
/**
* Checks the signature of the given JWT against all configured signers, returns true if at least one of the signers validates it.
* @param jwtString the string representation of the JWT as sent on the wire
* @return true if the signature is valid, false if not
* Check to see if this JWT has expired or not
*
* @param jwt
* the JWT to check
* @return true if this JWT has an expiration and it has passed, false if
* the JWT has no expiration or it has an expiration and the
* expiration has not passed
*/
public boolean validateSignature(String jwtString);
public boolean isJwtExpired(Jwt jwt);
/**
* Checks to see if this JWT has been issued by us
* @param jwt the JWT to check the issuer of
*
* @param jwt
* the JWT to check the issuer of
* @return true if the JWT was issued by this AS, false if not
*/
public boolean validateIssuedJwt(Jwt jwt);
/**
* Check to see if this JWT has expired or not
* @param jwt the JWT to check
* @return true if this JWT has an expiration and it has passed, false if the JWT has no expiration or it has an expiration and the expiration has not passed
* Checks the signature of the given JWT against all configured signers,
* returns true if at least one of the signers validates it.
*
* @param jwtString
* the string representation of the JWT as sent on the wire
* @return true if the signature is valid, false if not
*/
public boolean isJwtExpired(Jwt jwt);
public boolean validateSignature(String jwtString);
}

View File

@ -0,0 +1,22 @@
package org.mitre.jwt.signer.service.impl;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* Support class for implementing custom jwt-signer namespace
*
* @author nemonik
*
*/
public class JwtSignerNamespaceHandler extends NamespaceHandlerSupport {
/* (non-Javadoc)
* @see org.springframework.beans.factory.xml.NamespaceHandler#init()
*/
@Override
public void init() {
registerBeanDefinitionParser("keystore", new KeystoreDefinitionParser());
registerBeanDefinitionParser("service", new ServiceDefinitionParser());
}
}

View File

@ -0,0 +1,161 @@
package org.mitre.jwt.signer.service.impl;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mitre.jwt.model.Jwt;
import org.mitre.jwt.signer.JwtSigner;
import org.mitre.jwt.signer.impl.EcdsaSigner;
import org.mitre.jwt.signer.impl.RsaSigner;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.springframework.beans.factory.InitializingBean;
public class JwtSigningAndValidationServiceDefault implements
JwtSigningAndValidationService, InitializingBean {
private List<? extends JwtSigner> signers = new ArrayList<JwtSigner>();
private static Log logger = LogFactory
.getLog(JwtSigningAndValidationServiceDefault.class);
/**
* default constructor
*/
public JwtSigningAndValidationServiceDefault() {
}
/**
* Create JwtSigningAndValidationServiceDefault
*
* @param signer List of JwtSigners to associate with this service
*/
public JwtSigningAndValidationServiceDefault(List<? extends JwtSigner> signer) {
setSigners(signer);
}
/* (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());
}
}
/*
* (non-Javadoc)
*
* @see
* org.mitre.jwt.signer.service.JwtSigningAndValidationService#getAllPublicKeys
* ()
*/
@Override
public List<PublicKey> getAllPublicKeys() {
// TODO Iterate through the signers, gather up, and return all the PublicKeys
List<PublicKey> publicKeys = new ArrayList<PublicKey>();
PublicKey publicKey;
for (JwtSigner signer: signers) {
if (signer instanceof RsaSigner) {
publicKey = ((RsaSigner) signer).getPublicKey();
if (publicKey != null)
publicKeys.add(((RsaSigner) signer).getPublicKey());
} else if (signer instanceof EcdsaSigner) {
publicKey = ((EcdsaSigner) signer).getPublicKey();
if (publicKey != null)
publicKeys.add(publicKey);
}
}
return publicKeys;
}
/**
* Return the JwtSigners associated with this service
*
* @return
*/
public List<? extends JwtSigner> getSigners() {
return signers;
}
/*
* (non-Javadoc)
*
* @see
* org.mitre.jwt.signer.service.JwtSigningAndValidationService#isJwtExpired
* (org.mitre.jwt.model.Jwt)
*/
@Override
public boolean isJwtExpired(Jwt jwt) {
// TODO Auto-generated method stub
return false;
}
/**
* Set the JwtSigners associated with this service
*
* @param signers
* List of JwtSigners to associate with this service
*/
public void setSigners(List<? extends JwtSigner> signers) {
this.signers = signers;
}
@Override
public String toString() {
return "JwtSigningAndValidationServiceDefault [signers=" + signers
+ "]";
}
/*
* (non-Javadoc)
*
* @see
* org.mitre.jwt.signer.service.JwtSigningAndValidationService#validateIssuedJwt
* (org.mitre.jwt.model.Jwt)
*/
@Override
public boolean validateIssuedJwt(Jwt jwt) {
// TODO Verify this is correct...
for (JwtSigner signer: signers) {
if (signer.verify(jwt.toString()))
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* org.mitre.jwt.signer.service.JwtSigningAndValidationService#validateSignature
* (java.lang.String)
*/
@Override
public boolean validateSignature(String jwtString) {
for (JwtSigner signer: signers) {
if (signer.verify(jwtString))
return true;
}
return false;
}
}

View File

@ -0,0 +1,169 @@
package org.mitre.jwt.signer.service.impl;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
/**
* Creates and manages a JCE KeyStore
*
* @author nemonik
*
*/
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";
private String password;
private Resource location;
private String type;
private java.security.KeyStore keystore;
/**
* default constructor
*/
public KeyStore() {
this(PASSWORD, null, TYPE);
}
/**
* @param password
* @param location
*/
public KeyStore(String password, Resource location) {
this(password, location, TYPE);
}
/**
* KeyStore constructor
*
* @param password
* 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) {
setPassword(password);
setLocation(location);
setType(type);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws IOException,
GeneralSecurityException {
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();
}
}
}
/**
* 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 Resource getLocation() {
return location;
}
public String getPassword() {
return password;
}
public String getType() {
return type;
}
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;
}
public void setType(String type) {
if (StringUtils.hasLength(type)) {
this.type = type;
} else {
throw new IllegalArgumentException("type must not be empty");
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "KeyStore [password=" + password + ", location=" + location
+ ", type=" + type + ", keystore=" + keystore + "]";
}
}

View File

@ -0,0 +1,73 @@
package org.mitre.jwt.signer.service.impl;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Needed to parse and define just a single BeanDefinition for the KeyStore
*
* @author nemonik
*
*/
public class KeystoreDefinitionParser extends
AbstractSingleBeanDefinitionParser {
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
* #doParse(org.w3c.dom.Element,
* org.springframework.beans.factory.xml.ParserContext,
* org.springframework.beans.factory.support.BeanDefinitionBuilder)
*/
@Override
protected void doParse(Element element, ParserContext parserContext,
BeanDefinitionBuilder builder) {
String password = element.getAttribute("password");
if (StringUtils.hasText(password)) {
builder.addConstructorArgValue(password);
}
String location = element.getAttribute("location");
if (!StringUtils.hasText(location)) {
parserContext.getReaderContext().error(
"A location must be supplied on a keystore element.",
element);
} else {
Resource resource = parserContext.getReaderContext().getResourceLoader().getResource(location);
if (!resource.exists()) {
parserContext.getReaderContext().error(
"The location supplied on the keystore element must exist.",
element);
} else {
builder.addConstructorArgValue(resource);
}
}
String type = element.getAttribute("type");
if (StringUtils.hasText(type)) {
builder.addConstructorArgValue(type);
}
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
* #getBeanClass(org.w3c.dom.Element)
*/
@Override
protected Class<?> getBeanClass(Element element) {
return KeyStore.class;
}
}

View File

@ -0,0 +1,134 @@
package org.mitre.jwt.signer.service.impl;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mitre.jwt.signer.impl.HmacSigner;
import org.mitre.jwt.signer.impl.RsaSigner;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* Needed to parse and define just a single BeanDefinition for the
* JwtSigningAndValidationServiceDefault
*
* @author nemonik
*
*/
public class ServiceDefinitionParser extends AbstractSingleBeanDefinitionParser {
private static Log logger = LogFactory.getLog(ServiceDefinitionParser.class);
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
* #doParse(org.w3c.dom.Element,
* org.springframework.beans.factory.xml.ParserContext,
* org.springframework.beans.factory.support.BeanDefinitionBuilder)
*/
@Override
protected void doParse(Element element, ParserContext parserContext,
BeanDefinitionBuilder builder) {
ManagedList<BeanMetadataElement> signers = new ManagedList<BeanMetadataElement>();
List<Element> signerElements = DomUtils.getChildElementsByTagName(
element, new String[] { "rsa", "hmac" });
for (Element signerElement : signerElements) {
if (signerElement.getTagName().contains("rsa")) {
logger.debug("parsing rsa element");
BeanDefinitionBuilder signer = BeanDefinitionBuilder
.rootBeanDefinition(RsaSigner.class);
String bits = signerElement.getAttribute("bits");
if (StringUtils.hasText(bits)) {
signer.addConstructorArgValue("RS".concat(bits));
} else {
signer.addConstructorArgValue(RsaSigner.Algorithm.DEFAULT);
}
String keystoreRef = signerElement.getAttribute("keystore-ref");
if (!StringUtils.hasText(keystoreRef)) {
parserContext
.getReaderContext()
.error("A keystore-ref must be supplied with the definition of a rsa.",
signerElement);
} else {
signer.addConstructorArgReference(keystoreRef);
}
String alias = signerElement.getAttribute("key-alias");
if (!StringUtils.hasText(alias)) {
parserContext
.getReaderContext()
.error("An key-alias must be supplied with the definition of a rsa.",
signerElement);
} else {
signer.addConstructorArgValue(alias);
}
String password = signerElement.getAttribute("password");
if (StringUtils.hasText(password)) {
signer.addConstructorArgValue(password);
} else {
signer.addConstructorArgValue(RsaSigner.DEFAULT_PASSWORD);
}
signers.add(signer.getBeanDefinition());
} else if (signerElement.getTagName().contains("hmac")) {
logger.debug("parsing hmac element");
BeanDefinitionBuilder signer = BeanDefinitionBuilder
.rootBeanDefinition(HmacSigner.class);
String bits = signerElement.getAttribute("bits");
if (StringUtils.hasText(bits)) {
signer.addConstructorArgValue("HS".concat(bits));
} else {
signer.addConstructorArgValue(HmacSigner.Algorithm.DEFAULT);
}
String passphrase = signerElement.getAttribute("passphrase");
if (!StringUtils.hasText(passphrase)) {
parserContext
.getReaderContext()
.error("A passphrase must be supplied with the definition of a hmac.",
signerElement);
} else {
signer.addConstructorArgValue(passphrase);
}
signers.add(signer.getBeanDefinition());
}
}
builder.addPropertyValue("signers", signers);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
* #getBeanClass(org.w3c.dom.Element)
*/
@Override
protected Class<?> getBeanClass(Element element) {
return JwtSigningAndValidationServiceDefault.class;
}
}

View File

@ -1,6 +1,6 @@
package org.mitre.openid.connect.web;
import org.mitre.jwt.service.JwtSigningAndValidationService;
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;

View File

@ -5,7 +5,7 @@ import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.List;
import org.mitre.jwt.service.JwtSigningAndValidationService;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

View File

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

View File

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

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xs:schema xmlns="http://www.mitre.org/schema/openid-connect/jwt-signer"
xmlns:xs="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">
<xs:import namespace="http://www.springframework.org/schema/beans" />
<xs:element name="keystore">
<xs:annotation>
<xs:documentation>
Describes the JCE KeyStore necessary for certain
signers.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<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>
</xs:element>
<xs:element name="service">
<xs:annotation>
<xs:documentation>
Configures the signer service with these signers.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="beans:identifiedType">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="rsa">
<xs:annotation>
<xs:documentation>
Configures an RSA signer.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="bits" type="xs:string" />
<xs:attribute name="keystore-ref" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
The reference to the bean that defines the
KeyStore.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="key-alias" type="xs:string"
use="required">
<xs:annotation>
<xs:documentation>
The alias to the KeyPair to use for
signing/verifying.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="password" type="xs:string">
<xs:annotation>
<xs:documentation>
The password to the KeyPair to use for
signing/verifying.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="hmac">
<xs:annotation>
<xs:documentation>
Configures an HMAC signer.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="bits" type="xs:integer" />
<xs:attribute name="passphrase" type="xs:string">
<xs:annotation>
<xs:documentation>
The passphrase used for signing/verifying.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@ -5,6 +5,7 @@
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:jwt-signer="http://www.mitre.org/schema/openid-connect/jwt-signer"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation=
"http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd
@ -12,9 +13,11 @@
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.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.mitre.org/schema/openid-connect/jwt-signer http://www.mitre.org/schema/openid-connect/jwt-signer/jwt-signer.xsd">
<import resource="data-context.xml" />
<import resource="security-context.xml" />
<tx:annotation-driven transaction-manager="transactionManager" />
@ -70,7 +73,15 @@
</map>
</property>
</bean>
<jwt-signer:keystore id="ping" location="file:src/main/webapp/WEB-INF/spring/keystore.jks" password="changeit" type="JKS" />
<jwt-signer:service id="defaultSignerService">
<jwt-signer:rsa bits="256" keystore-ref="ping" key-alias="test" password="changeit" />
<jwt-signer:hmac bits="256" passphrase="changeit" />
</jwt-signer:service>
<!-- scheduled tasks -->
<task:scheduler id="taskScheduler" pool-size="10" />
<task:executor id="taskExecutor" pool-size="5" />

Binary file not shown.

View File

@ -1,19 +1,16 @@
package org.mitre.jwt;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mitre.jwt.model.Jwt;
import org.mitre.jwt.signer.AbstractJwtSigner;
import org.mitre.jwt.signer.JwtSigner;
import org.mitre.jwt.signer.impl.Hmac256Signer;
import org.mitre.jwt.signer.impl.HmacSigner;
import org.mitre.jwt.signer.impl.PlaintextSigner;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
public class JwtTest {
@ -60,8 +57,8 @@ public class JwtTest {
// TODO Auto-generated catch block
e.printStackTrace();
}
JwtSigner signer = new Hmac256Signer(key);
JwtSigner signer = new HmacSigner(key);
signer.sign(jwt);
@ -95,7 +92,7 @@ public class JwtTest {
e.printStackTrace();
}
JwtSigner signer = new Hmac256Signer(key);
JwtSigner signer = new HmacSigner(key);
/*
* Token string based on the following strucutres, serialized exactly as follows and base64 encoded:
@ -121,7 +118,7 @@ public class JwtTest {
Jwt jwt = Jwt.parse(source);
assertThat(jwt.getHeader().getAlgorithm(), equalTo(AbstractJwtSigner.PLAINTEXT));
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));