diff --git a/mitre-local b/mitre-local index f1f301046..b81dd3091 160000 --- a/mitre-local +++ b/mitre-local @@ -1 +1 @@ -Subproject commit f1f301046b6c6f397bc4d40a25dd407ca6f2b757 +Subproject commit b81dd3091e53be285bbfd9f0886b5a88b2cab19c diff --git a/pom.xml b/pom.xml index 1a0d6dc47..0de433283 100644 --- a/pom.xml +++ b/pom.xml @@ -194,7 +194,7 @@ <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> - <version>1.7.1</version> + <version>2.0</version> </dependency> <dependency> <groupId>org.eclipse.persistence</groupId> @@ -214,7 +214,7 @@ <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> - <version>r09</version> + <version>10.0.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> @@ -229,7 +229,12 @@ <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> - <version>1.3</version> + <version>1.6</version> + </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk16</artifactId> + <version>1.46</version> </dependency> </dependencies> <repositories> diff --git a/src/main/java/org/mitre/jwt/Jwt.java b/src/main/java/org/mitre/jwt/Jwt.java new file mode 100644 index 000000000..fa47a2605 --- /dev/null +++ b/src/main/java/org/mitre/jwt/Jwt.java @@ -0,0 +1,118 @@ +package org.mitre.jwt; + +import java.util.List; + +import org.apache.commons.codec.binary.Base64; + +import com.google.common.base.Objects; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class Jwt { + + private JwtHeader header = new JwtHeader(); + + private JwtClaims claims = new JwtClaims(); + + private String signature; + + + + + /** + * @return the header + */ + public JwtHeader getHeader() { + return header; + } + + + + /** + * @param header the header to set + */ + public void setHeader(JwtHeader header) { + this.header = header; + } + + + + /** + * @return the claims + */ + public JwtClaims getClaims() { + return claims; + } + + + + /** + * @param claims the claims to set + */ + public void setClaims(JwtClaims claims) { + this.claims = claims; + } + + + + /** + * @return the signature + */ + public String getSignature() { + return signature; + } + + + + /** + * @param signature the signature to set + */ + public void setSignature(String signature) { + this.signature = signature; + } + + /** + * Return the canonical encoded string of this JWT + */ + public String toString() { + JsonObject h = header.getAsJsonObject(); + JsonObject o = claims.getAsJsonObject(); + + String h64 = new String(Base64.encodeBase64URLSafe(h.toString().getBytes())); + String o64 = new String(Base64.encodeBase64(o.toString().getBytes())); + + return h64 + "." + o64 + "." + Strings.nullToEmpty(this.signature); + } + + + /** + * Parse a wire-encoded JWT + */ + public static Jwt parse(String s) { + + // split on the dots + List<String> parts = Lists.newArrayList(Splitter.on(".").split(s)); + + if (parts.size() != 3) { + throw new IllegalArgumentException("Invalid JWT format."); + } + + String h64 = parts.get(0); + String o64 = parts.get(1); + String s64 = parts.get(2); + + JsonParser parser = new JsonParser(); + + + + // shuttle for return value + Jwt jwt = new Jwt(); + + return jwt; + + } + +} diff --git a/src/main/java/org/mitre/jwt/JwtClaims.java b/src/main/java/org/mitre/jwt/JwtClaims.java new file mode 100644 index 000000000..dd4124db4 --- /dev/null +++ b/src/main/java/org/mitre/jwt/JwtClaims.java @@ -0,0 +1,236 @@ +package org.mitre.jwt; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.JsonObject; + +public class JwtClaims { + + /** + * ISO8601 / RFC3339 Date Format + */ + public static DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-DD'T'HH:mm:ssz"); + + private Date expiration; + + private Date notBefore; + + private Date issuedAt; + + private String issuer; + + private String audience; + + private String principal; + + private String jwtId; + + private String type; + + private Map<String, Object> claims = new HashMap<String, Object>(); + + public JwtClaims() { + + } + + /** + * @return the expiration + */ + public Date getExpiration() { + return expiration; + } + + /** + * @param expiration the expiration to set + */ + public void setExpiration(Date expiration) { + this.expiration = expiration; + } + + /** + * @return the notBefore + */ + public Date getNotBefore() { + return notBefore; + } + + /** + * @param notBefore the notBefore to set + */ + public void setNotBefore(Date notBefore) { + this.notBefore = notBefore; + } + + /** + * @return the issuedAt + */ + public Date getIssuedAt() { + return issuedAt; + } + + /** + * @param issuedAt the issuedAt to set + */ + public void setIssuedAt(Date issuedAt) { + this.issuedAt = issuedAt; + } + + /** + * @return the issuer + */ + public String getIssuer() { + return issuer; + } + + /** + * @param issuer the issuer to set + */ + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + /** + * @return the audience + */ + public String getAudience() { + return audience; + } + + /** + * @param audience the audience to set + */ + public void setAudience(String audience) { + this.audience = audience; + } + + /** + * @return the principal + */ + public String getPrincipal() { + return principal; + } + + /** + * @param principal the principal to set + */ + public void setPrincipal(String principal) { + this.principal = principal; + } + + /** + * @return the jwtId + */ + public String getJwtId() { + return jwtId; + } + + /** + * @param jwtId the jwtId to set + */ + public void setJwtId(String jwtId) { + this.jwtId = jwtId; + } + + /** + * @return the type + */ + public String getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(String type) { + this.type = type; + } + + /** + * Get an extension claim + */ + public Object getClaim(String key) { + return claims.get(key); + } + + /** + * Set an extension claim + */ + public void setClaim(String key, Object value) { + claims.put(key, value); + } + + /** + * Remove an extension claim + */ + public Object removeClaim(String key) { + return claims.remove(key); + } + + /** + * Get a copy of this header as a JsonObject. The JsonObject is not + * backed by a live copy of this JwtHeader. + * @return a copy of the data in this header in a JsonObject + */ + public JsonObject getAsJsonObject() { + JsonObject o = new JsonObject(); + + if (this.expiration != null) { + o.addProperty("exp", dateFormat.format(this.expiration)); + } + + if (this.notBefore != null) { + o.addProperty("nbf", dateFormat.format(this.notBefore)); + } + + if (this.issuedAt != null) { + o.addProperty("iat", dateFormat.format(this.issuedAt)); + } + + if (this.issuer != null) { + o.addProperty("iss", this.issuer); + } + + if (this.audience != null) { + o.addProperty("aud", this.audience); + } + + if (this.principal != null) { + o.addProperty("prn", this.principal); + } + + if (this.jwtId != null) { + o.addProperty("jti", this.jwtId); + } + + if (this.type != null) { + o.addProperty("typ", this.type); + } + + if (this.claims != null) { + for (Map.Entry<String, Object> claim : this.claims.entrySet()) { + if (claim.getValue() instanceof String) { + o.addProperty(claim.getKey(), (String)claim.getValue()); + } else if (claim.getValue() instanceof Number) { + o.addProperty(claim.getKey(), (Number)claim.getValue()); + } else if (claim.getValue() instanceof Boolean) { + o.addProperty(claim.getKey(), (Boolean)claim.getValue()); + } else if (claim.getValue() instanceof Character) { + o.addProperty(claim.getKey(), (Character)claim.getValue()); + } else if (claim.getValue() != null) { + // try to put it in as a string + o.addProperty(claim.getKey(), claim.getValue().toString()); + } else { + // otherwise add in as a null + o.add(claim.getKey(), null); + } + } + } + + return o; + } + +} diff --git a/src/main/java/org/mitre/jwt/JwtHeader.java b/src/main/java/org/mitre/jwt/JwtHeader.java new file mode 100644 index 000000000..fd7560769 --- /dev/null +++ b/src/main/java/org/mitre/jwt/JwtHeader.java @@ -0,0 +1,133 @@ +package org.mitre.jwt; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.JsonObject; + +public class JwtHeader { + + private String type; + + private String algorithm; + + private String encryptionMethod; + + private Map<String, Object> claims = new HashMap<String, Object>(); + + + public JwtHeader() { + + } + + + /** + * @return the type + */ + public String getType() { + return type; + } + + + /** + * @param type the type to set + */ + public void setType(String type) { + this.type = type; + } + + + /** + * @return the algorithm + */ + public String getAlgorithm() { + return algorithm; + } + + + /** + * @param algorithm the algorithm to set + */ + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + + /** + * @return the encryptionMethod + */ + public String getEncryptionMethod() { + return encryptionMethod; + } + + + /** + * @param encryptionMethod the encryptionMethod to set + */ + public void setEncryptionMethod(String encryptionMethod) { + this.encryptionMethod = encryptionMethod; + } + + /** + * Get an extension claim + */ + public Object getClaim(String key) { + return claims.get(key); + } + + /** + * Set an extension claim + */ + public void setClaim(String key, Object value) { + claims.put(key, value); + } + + /** + * Remove an extension claim + */ + public Object removeClaim(String key) { + return claims.remove(key); + } + + /** + * Get a copy of this header as a JsonObject. The JsonObject is not + * backed by a live copy of this JwtHeader. + * @return a copy of the data in this header in a JsonObject + */ + public JsonObject getAsJsonObject() { + JsonObject o = new JsonObject(); + + o.addProperty("typ", this.type); + if (this.algorithm != null) { + o.addProperty("alg", this.algorithm); + } + + if (this.encryptionMethod != null) { + o.addProperty("enc", this.encryptionMethod); + } + + if (this.claims != null) { + for (Map.Entry<String, Object> claim : this.claims.entrySet()) { + if (claim.getValue() instanceof String) { + o.addProperty(claim.getKey(), (String)claim.getValue()); + } else if (claim.getValue() instanceof Number) { + o.addProperty(claim.getKey(), (Number)claim.getValue()); + } else if (claim.getValue() instanceof Boolean) { + o.addProperty(claim.getKey(), (Boolean)claim.getValue()); + } else if (claim.getValue() instanceof Character) { + o.addProperty(claim.getKey(), (Character)claim.getValue()); + } else if (claim.getValue() != null) { + // try to put it in as a string + o.addProperty(claim.getKey(), claim.getValue().toString()); + } else { + // otherwise add in as a null + o.add(claim.getKey(), null); + } + } + } + + return o; + } + + +} diff --git a/src/main/java/org/mitre/jwt/JwtSigner.java b/src/main/java/org/mitre/jwt/JwtSigner.java new file mode 100644 index 000000000..3d2f3c797 --- /dev/null +++ b/src/main/java/org/mitre/jwt/JwtSigner.java @@ -0,0 +1,7 @@ +package org.mitre.jwt; + +public interface JwtSigner { + + public void sign(Jwt jwt); + +} diff --git a/src/main/java/org/mitre/openid/connect/model/IdToken.java b/src/main/java/org/mitre/openid/connect/model/IdToken.java index 734f6bd9e..7e55800b9 100644 --- a/src/main/java/org/mitre/openid/connect/model/IdToken.java +++ b/src/main/java/org/mitre/openid/connect/model/IdToken.java @@ -2,11 +2,13 @@ package org.mitre.openid.connect.model; import javax.persistence.Entity; +import org.mitre.jwt.Jwt; + /* * TODO: This class needs to be encoded as a JWT */ @Entity -public class IdToken { +public class IdToken extends Jwt { private String iss; private String user_id; diff --git a/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml b/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml index ca62af15a..0ee38fc01 100644 --- a/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml +++ b/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml @@ -30,7 +30,7 @@ </beans:bean> <!-- JSON views for each type of model object --> - <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.model.serializer.JSONUserInfoView"/> --> +<!-- <beans:bean id="jsonIdTokenView" class="org.mitre.openid.connect.model.serializer.JSONIdTokenView"/> --> </beans:beans>