Merge branch 'master' of github.com:jricher/OpenID-Connect-Java-Spring-Server
commit
be132dc5bd
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,19 @@
|
|||
Changelog
|
||||
|
||||
Updated on 2/7/2012
|
||||
|
||||
OAuth2:
|
||||
* Removed refresh_token from the Access Token response on the Client Credentials flow.
|
||||
Ref: http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-4.4.3
|
||||
"A refresh token SHOULD NOT be included."
|
||||
|
||||
* Changed "Consumer" to "Client".
|
||||
|
||||
Connect:
|
||||
* Changed "Consumer" to "Client".
|
||||
|
||||
* Clarified required/optional wording. Parameters are REQUIRED unless otherwise stated.
|
||||
|
||||
* Implicit Flow: changed wording on redirect_uri requirement in the Authorization Request. Now reads "required IFF the client has pre-configured more than one value with the service provider".
|
||||
|
||||
* Diagram 3 was renamed to "Optional Steps" (from "Additional Steps"), as these steps may or may not be taken and may be done in any order. Added "openid" to the schema parameter in the UserInfo Request.
|
|
@ -269,6 +269,16 @@
|
|||
<artifactId>maven-replacer-plugin</artifactId>
|
||||
<version>1.3.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ws</groupId>
|
||||
<artifactId>spring-ws-security</artifactId>
|
||||
<version>2.0.3.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-oxm</artifactId>
|
||||
<version>${org.springframework-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<!-- For testing against latest Spring snapshots -->
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
package org.mitre.jwt.signer.impl;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
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.List;
|
||||
|
||||
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;
|
||||
|
||||
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";
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
private KeyStore keystore;
|
||||
private String alias;
|
||||
private String password;
|
||||
|
||||
private RSAPrivateKey privateKey;
|
||||
private RSAPublicKey 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(), "BC");
|
||||
} catch (GeneralSecurityException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
KeyPair keyPair = keystore.getKeyPairForAlias(alias, password);
|
||||
|
||||
publicKey = ((RSAPublicKey) keyPair.getPublic());
|
||||
privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||
|
||||
logger.debug("RSA Signer ready for business");
|
||||
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.mitre.jwt.signer.AbstractJwtSigner#generateSignature(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
protected String generateSignature(String signatureBase) {
|
||||
|
||||
try {
|
||||
signer.initSign(privateKey);
|
||||
signer.update(signatureBase.getBytes("UTF-8"));
|
||||
} catch (GeneralSecurityException 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);
|
||||
signer.update(signingInput.getBytes("UTF-8"));
|
||||
signer.verify(s64.getBytes("UTF-8"));
|
||||
} catch (GeneralSecurityException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public RSAPrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public void setPrivateKey(RSAPrivateKey privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.mitre.jwt.signer.service;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
|
||||
import org.mitre.jwt.model.Jwt;
|
||||
|
||||
public interface JwtSigningAndValidationService {
|
||||
|
||||
/**
|
||||
* Returns all public keys this service is configured with.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<PublicKey> getAllPublicKeys();
|
||||
|
||||
/**
|
||||
* 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 isJwtExpired(Jwt jwt);
|
||||
|
||||
/**
|
||||
* Checks to see if this JWT has been issued by us
|
||||
*
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* 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 validateSignature(String jwtString);
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,324 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
* Creates and manages a JCE KeyStore
|
||||
*
|
||||
* @author nemonik
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class KeyStore implements InitializingBean {
|
||||
|
||||
private static Log logger = LogFactory.getLog(KeyStore.class);
|
||||
|
||||
public static final String TYPE = "BKS";
|
||||
public static final String PASSWORD = "changeit";
|
||||
|
||||
static {
|
||||
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 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=" + 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(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", "BC");
|
||||
rsaKeyPairGenerator.initialize(2048);
|
||||
KeyPair rsaKeyPair = rsaKeyPairGenerator.generateKeyPair();
|
||||
|
||||
X509V3CertificateGenerator v3CertGen = createCertificate(domainName,
|
||||
daysNotValidBefore, daysNotValidAfter);
|
||||
|
||||
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) rsaKeyPair.getPrivate();
|
||||
|
||||
v3CertGen.setPublicKey(rsaKeyPair.getPublic());
|
||||
v3CertGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); // "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 java.security.KeyStore keystore;
|
||||
|
||||
/**
|
||||
* default constructor
|
||||
*/
|
||||
public KeyStore() {
|
||||
this(PASSWORD, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
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, (RSAPrivateKey) 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 + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
|
||||
* #getBeanClass(org.w3c.dom.Element)
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> getBeanClass(Element element) {
|
||||
return KeyStore.class;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.mitre.oauth2.model.serializer;
|
||||
package org.mitre.oauth2.view;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
|
@ -1,4 +1,4 @@
|
|||
package org.mitre.oauth2.model.serializer;
|
||||
package org.mitre.oauth2.view;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
|
@ -0,0 +1,5 @@
|
|||
package org.mitre.openid.connect.exception;
|
||||
|
||||
public class ExpiredTokenException extends RuntimeException {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.mitre.openid.connect.exception;
|
||||
|
||||
public class InvalidJwtIssuerException extends RuntimeException {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.mitre.openid.connect.exception;
|
||||
|
||||
public class InvalidJwtSignatureException extends RuntimeException {
|
||||
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.mitre.openid.connect.token;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.mitre.oauth2.service.impl.DefaultOAuth2ProviderTokenService;
|
||||
import org.mitre.openid.connect.model.IdToken;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
|
||||
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
|
||||
import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException;
|
||||
import org.springframework.security.oauth2.provider.AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.provider.ClientCredentialsChecker;
|
||||
import org.springframework.security.oauth2.provider.ClientDetailsService;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.TokenGranter;
|
||||
import org.springframework.security.oauth2.provider.code.AuthorizationRequestHolder;
|
||||
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* AccessToken granter for Authorization Code flow.
|
||||
*
|
||||
* Note: does this need to be able to grant straight OAuth2.0 Access Tokens as
|
||||
* well as Connect Access Tokens?
|
||||
*
|
||||
*
|
||||
* @author AANGANES
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class ConnectAuthCodeTokenGranter implements TokenGranter {
|
||||
|
||||
private static final String GRANT_TYPE = "authorization_code";
|
||||
|
||||
@Autowired
|
||||
private JdbcAuthorizationCodeServices authorizationCodeServices;
|
||||
|
||||
@Autowired
|
||||
private ClientCredentialsChecker clientCredentialsChecker;
|
||||
|
||||
//TODO: Do we need to modify/update this?
|
||||
@Autowired
|
||||
private DefaultOAuth2ProviderTokenService tokenServices;
|
||||
|
||||
|
||||
/**
|
||||
* Default empty constructor
|
||||
*/
|
||||
public ConnectAuthCodeTokenGranter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for unit tests
|
||||
*
|
||||
* @param tokenServices
|
||||
* @param authorizationCodeServices
|
||||
* @param clientDetailsService
|
||||
*/
|
||||
public ConnectAuthCodeTokenGranter(
|
||||
DefaultOAuth2ProviderTokenService tokenServices,
|
||||
JdbcAuthorizationCodeServices authorizationCodeServices,
|
||||
ClientDetailsService clientDetailsService) {
|
||||
|
||||
setTokenServices(tokenServices);
|
||||
setAuthorizationCodeServices(authorizationCodeServices);
|
||||
setClientCredentialsChecker(new ClientCredentialsChecker(clientDetailsService));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Grant an OpenID Connect Access Token
|
||||
*
|
||||
* @param grantType
|
||||
* @param parameters
|
||||
* @param clientId
|
||||
* @param scope
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AccessToken grant(String grantType,
|
||||
Map<String, String> parameters, String clientId, Set<String> scope) {
|
||||
|
||||
if (!GRANT_TYPE.equals(grantType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String authorizationCode = parameters.get("code");
|
||||
String redirectUri = parameters.get("redirect_uri");
|
||||
|
||||
if (authorizationCode == null) {
|
||||
throw new OAuth2Exception("An authorization code must be supplied.");
|
||||
}
|
||||
|
||||
AuthorizationRequestHolder storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
|
||||
if (storedAuth == null) {
|
||||
throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);
|
||||
}
|
||||
|
||||
AuthorizationRequest unconfirmedAuthorizationRequest = storedAuth.getAuthenticationRequest();
|
||||
if (unconfirmedAuthorizationRequest.getRequestedRedirect() != null
|
||||
&& !unconfirmedAuthorizationRequest.getRequestedRedirect().equals(redirectUri)) {
|
||||
throw new RedirectMismatchException("Redirect URI mismatch.");
|
||||
}
|
||||
|
||||
if (clientId != null && !clientId.equals(unconfirmedAuthorizationRequest.getClientId())) {
|
||||
// just a sanity check.
|
||||
throw new InvalidClientException("Client ID mismatch");
|
||||
}
|
||||
|
||||
// From SECOAUTH: Secret is not required in the authorization request, so it won't be available
|
||||
// in the unconfirmedAuthorizationCodeAuth. We do want to check that a secret is provided
|
||||
// in the new request, but that happens elsewhere.
|
||||
|
||||
//Validate credentials
|
||||
AuthorizationRequest authorizationRequest = clientCredentialsChecker.validateCredentials(grantType, clientId,
|
||||
unconfirmedAuthorizationRequest.getScope());
|
||||
if (authorizationRequest == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Authentication userAuth = storedAuth.getUserAuthentication();
|
||||
|
||||
OAuth2AccessTokenEntity token = tokenServices.createAccessToken(new OAuth2Authentication(authorizationRequest, userAuth));
|
||||
|
||||
/**
|
||||
* Authorization request scope MUST include "openid", but access token request
|
||||
* may or may not include the scope parameter. As long as the AuthorizationRequest
|
||||
* has the proper scope, we can consider this a valid OpenID Connect request.
|
||||
*/
|
||||
if (authorizationRequest.getScope().contains("openid")) {
|
||||
IdToken idToken = new IdToken();
|
||||
|
||||
//TODO: build IdToken
|
||||
|
||||
//Where does the data for the IdToken come from?
|
||||
|
||||
//TODO: insert IdToken into OAuth2AccessTokenEntity
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the authorizationCodeServices
|
||||
*/
|
||||
public JdbcAuthorizationCodeServices getAuthorizationCodeServices() {
|
||||
return authorizationCodeServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authorizationCodeServices the authorizationCodeServices to set
|
||||
*/
|
||||
public void setAuthorizationCodeServices(JdbcAuthorizationCodeServices authorizationCodeServices) {
|
||||
this.authorizationCodeServices = authorizationCodeServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the clientCredentialsChecker
|
||||
*/
|
||||
public ClientCredentialsChecker getClientCredentialsChecker() {
|
||||
return clientCredentialsChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientCredentialsChecker the clientCredentialsChecker to set
|
||||
*/
|
||||
public void setClientCredentialsChecker(ClientCredentialsChecker clientCredentialsChecker) {
|
||||
this.clientCredentialsChecker = clientCredentialsChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tokenServices
|
||||
*/
|
||||
public DefaultOAuth2ProviderTokenService getTokenServices() {
|
||||
return tokenServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tokenServices the tokenServices to set
|
||||
*/
|
||||
public void setTokenServices(DefaultOAuth2ProviderTokenService tokenServices) {
|
||||
this.tokenServices = tokenServices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.mitre.openid.connect.model.serializer;
|
||||
package org.mitre.openid.connect.view;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
|
@ -1,4 +1,4 @@
|
|||
package org.mitre.openid.connect.model.serializer;
|
||||
package org.mitre.openid.connect.view;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.mitre.openid.connect.view;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class JwkKeyListView extends AbstractView {
|
||||
|
||||
@Override
|
||||
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
Gson gson = new GsonBuilder()
|
||||
.setExclusionStrategies(new ExclusionStrategy() {
|
||||
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean shouldSkipClass(Class<?> clazz) {
|
||||
// skip the JPA binding wrapper
|
||||
if (clazz.equals(BeanPropertyBindingResult.class)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
})
|
||||
.registerTypeAdapter(RSAPublicKey.class, new JsonSerializer<RSAPublicKey>() {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(RSAPublicKey src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
|
||||
|
||||
|
||||
JsonObject o = new JsonObject();
|
||||
o.addProperty("mod", src.getModulus().toString());
|
||||
|
||||
return o;
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
.create();
|
||||
|
||||
|
||||
response.setContentType("application/json");
|
||||
|
||||
Writer out = response.getWriter();
|
||||
|
||||
Object obj = model.get("entity");
|
||||
if (obj == null) {
|
||||
obj = model;
|
||||
}
|
||||
|
||||
gson.toJson(obj, out);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
package org.mitre.openid.connect.web;
|
||||
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
@ -10,17 +15,33 @@ import org.springframework.web.servlet.ModelAndView;
|
|||
@Controller
|
||||
public class CheckIDEndpoint {
|
||||
|
||||
@Autowired
|
||||
JwtSigningAndValidationService jwtSignerService;
|
||||
|
||||
|
||||
@RequestMapping("/checkid")
|
||||
public ModelAndView checkID(@RequestParam("id_token") String tokenString, ModelAndView mav) {
|
||||
|
||||
if (!jwtSignerService.validateSignature(tokenString)) {
|
||||
// can't validate
|
||||
throw new InvalidJwtSignatureException(); // TODO: attach a view to this exception
|
||||
}
|
||||
|
||||
// it's a valid signature, parse the token
|
||||
IdToken token = IdToken.parse(tokenString);
|
||||
|
||||
// check the expiration
|
||||
if (jwtSignerService.isJwtExpired(token)) {
|
||||
// token has expired
|
||||
throw new ExpiredTokenException(); // TODO create a view for this exception
|
||||
}
|
||||
|
||||
// check the issuer (sanity check)
|
||||
if (!jwtSignerService.validateIssuedJwt(token)) {
|
||||
throw new InvalidJwtIssuerException(); // TODO: create a view for this exception
|
||||
}
|
||||
|
||||
|
||||
return new ModelAndView("jsonIdTokenView", "checkId", token);
|
||||
return new ModelAndView("jsonIdTokenView", "checkId", token); // TODO: create a view for this
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package org.mitre.openid.connect.web;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
@Controller
|
||||
public class JsonWebKeyEndpoint {
|
||||
|
||||
@Autowired
|
||||
JwtSigningAndValidationService jwtService;
|
||||
|
||||
@RequestMapping("/jwk")
|
||||
public ModelAndView getJwk() {
|
||||
|
||||
List<PublicKey> keys = jwtService.getAllPublicKeys();
|
||||
|
||||
// TODO: check if keys are empty, return a 404 here?
|
||||
|
||||
return new ModelAndView("jwkKeyList", "keys", keys); // TODO: make a view
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
http\://www.mitre.org/schema/openid-connect/jwt-signer=org.mitre.jwt.signer.service.impl.JwtSignerNamespaceHandler
|
|
@ -0,0 +1 @@
|
|||
http\://www.mitre.org/schema/openid-connect/jwt-signer/jwt-signer.xsd=org/mitre/jwt/signer/service/impl/jwt-signer.xsd
|
|
@ -0,0 +1,93 @@
|
|||
<?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" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"/>
|
||||
|
||||
<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: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>
|
|
@ -3,7 +3,9 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:security="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.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.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
|
||||
|
||||
|
@ -12,6 +14,8 @@
|
|||
<!-- Enables the Spring MVC @Controller programming model -->
|
||||
<annotation-driven />
|
||||
|
||||
<security:global-method-security pre-post-annotations="enabled" proxy-target-class="true"/>
|
||||
|
||||
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
|
||||
<resources mapping="/resources/**" location="/resources/" />
|
||||
|
||||
|
@ -30,8 +34,8 @@
|
|||
<!-- JSON views for each type of model object -->
|
||||
<beans:bean id="jsonOpenIdConfigurationView" class="org.mitre.swd.view.JsonOpenIdConfigurationView" />
|
||||
<beans:bean id="jsonSwdResponseView" class="org.mitre.swd.view.SwdResponse" />
|
||||
<!-- <beans:bean id="jsonUserInfoView" class="org.mitre.openid.connect.model.serializer.JSONUserInfoView"/> -->
|
||||
<!-- <beans:bean id="jsonIdTokenView" class="org.mitre.openid.connect.model.serializer.JSONIdTokenView"/> -->
|
||||
<!-- <beans:bean id="jsonUserInfoView" class="org.mitre.openid.connect.view.JSONUserInfoView"/> -->
|
||||
<!-- <beans:bean id="jsonIdTokenView" class="org.mitre.openid.connect.view.JSONIdTokenView"/> -->
|
||||
|
||||
<beans:import resource="controllers.xml" />
|
||||
|
||||
|
|
|
@ -5,13 +5,18 @@
|
|||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:security="http://www.springframework.org/schema/security"
|
||||
xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
|
||||
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
|
||||
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.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/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.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">
|
||||
|
||||
<import resource="data-context.xml" />
|
||||
|
||||
<import resource="data-context.xml" />
|
||||
<import resource="security-context.xml" />
|
||||
|
||||
<tx:annotation-driven transaction-manager="transactionManager" />
|
||||
|
@ -22,6 +27,26 @@
|
|||
<property name="showSql" value="true" />
|
||||
</bean>
|
||||
|
||||
<!-- Authorization Code Service, used by TokenGranter -->
|
||||
<bean id="jdbcAuthCodeServices" class="org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices">
|
||||
<constructor-arg>
|
||||
<bean class="org.apache.commons.dbcp.BasicDataSource"/>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean id="clientCredentialsChecker" class="org.springframework.security.oauth2.provider.ClientCredentialsChecker">
|
||||
<constructor-arg>
|
||||
<bean class="org.mitre.oauth2.service.impl.DefaultOAuth2ClientDetailsEntityService"/>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<!-- SECOAUTH Authorization Server, with our custom token granter plugged in -->
|
||||
<oauth:authorization-server client-details-service-ref="defaultOAuth2ClientDetailsEntityService"
|
||||
token-services-ref="defaultOAuth2ProviderTokenService" token-granter-ref="connectAuthCodeTokenGranter"
|
||||
authorization-endpoint-url="/openidconnect/auth*">
|
||||
<!-- <oauth:authorization-code disabled="true"/> -->
|
||||
</oauth:authorization-server>
|
||||
|
||||
<!-- Map our custom exception classes to named views -->
|
||||
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
|
||||
<property name="exceptionMappings">
|
||||
|
@ -47,12 +72,19 @@
|
|||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<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:hmac bits="256" passphrase="changeit" />
|
||||
</jwt-signer:service>
|
||||
|
||||
<!-- scheduled tasks -->
|
||||
<task:scheduler id="taskScheduler" pool-size="10" />
|
||||
<task:executor id="taskExecutor" pool-size="5" />
|
||||
<task:annotation-driven scheduler="taskScheduler" executor="taskExecutor" />
|
||||
|
||||
<context:component-scan annotation-config="true" base-package="org.mitre.openid" />
|
||||
<context:component-scan annotation-config="true" base-package="org.mitre" />
|
||||
|
||||
</beans>
|
||||
|
|
Binary file not shown.
|
@ -14,6 +14,17 @@
|
|||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<!-- filter through Spring Security -->
|
||||
<filter>
|
||||
<filter-name>springSecurityFilterChain</filter-name>
|
||||
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
|
||||
</filter>
|
||||
|
||||
<filter-mapping>
|
||||
<filter-name>springSecurityFilterChain</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
<!-- Processes application requests -->
|
||||
<servlet>
|
||||
<servlet-name>appServlet</servlet-name>
|
||||
|
@ -30,4 +41,11 @@
|
|||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<jsp-config>
|
||||
<jsp-property-group>
|
||||
<url-pattern>*.jsp</url-pattern>
|
||||
<trim-directive-whitespaces>true</trim-directive-whitespaces>
|
||||
</jsp-property-group>
|
||||
</jsp-config>
|
||||
|
||||
</web-app>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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;
|
||||
|
@ -9,14 +9,26 @@ 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.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;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations = {
|
||||
"file:src/main/webapp/WEB-INF/spring/application-context.xml",
|
||||
"classpath:test-context.xml" })
|
||||
public class JwtTest {
|
||||
|
||||
|
||||
@Autowired
|
||||
@Qualifier("testKeystore")
|
||||
KeyStore keystore;
|
||||
|
||||
@Test
|
||||
public void testToStringPlaintext() {
|
||||
Jwt jwt = new Jwt();
|
||||
|
@ -60,13 +72,13 @@ public class JwtTest {
|
|||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
JwtSigner signer = new Hmac256Signer(key);
|
||||
|
||||
JwtSigner signer = new HmacSigner(key);
|
||||
|
||||
signer.sign(jwt);
|
||||
|
||||
/*
|
||||
* Expected string based on the following strucutres, serialized exactly as follows and base64 encoded:
|
||||
* Expected string based on the following structures, serialized exactly as follows and base64 encoded:
|
||||
*
|
||||
* header: {"typ":"JWT","alg":"HS256"}
|
||||
* claims: {"exp":1300819380,"iss":"joe","http://example.com/is_root":true}
|
||||
|
@ -84,6 +96,40 @@ public class JwtTest {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* @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));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateHmacSignature() {
|
||||
// sign it
|
||||
|
@ -95,7 +141,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 +167,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));
|
||||
|
|
|
@ -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.
|
@ -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>
|
Loading…
Reference in New Issue