Skip to content

Commit

Permalink
first attempt at jwe util
Browse files Browse the repository at this point in the history
Signed-off-by: Bruce Tiffany <[email protected]>
  • Loading branch information
brutif committed Feb 7, 2019
1 parent 9199734 commit bb87590
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 4 deletions.
65 changes: 61 additions & 4 deletions sandbox/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@
specific language governing permissions and limitations
under the License.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>


<parent>
<!-- This is just for now and will not work if the API has a separate release cycle than the rest. -->

<groupId>org.eclipse.microprofile.jwt</groupId>
<artifactId>microprofile-jwt-auth-parent</artifactId>
<version>1.1-SNAPSHOT</version>
<version>1.2-SNAPSHOT</version>
</parent>


<artifactId>microprofile-jwt-auth-sandbox</artifactId>
<version>1.1-SNAPSHOT</version>
Expand Down Expand Up @@ -75,12 +78,12 @@
<dependency>
<groupId>org.eclipse.microprofile.jwt</groupId>
<artifactId>microprofile-jwt-auth-api</artifactId>
<version>1.1-SNAPSHOT</version>
<version>1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.jwt</groupId>
<artifactId>microprofile-jwt-auth-tck</artifactId>
<version>1.1-SNAPSHOT</version>
<version>1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.bitbucket.b_c</groupId>
Expand Down Expand Up @@ -123,6 +126,60 @@
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.apache.rat
</groupId>
<artifactId>
apache-rat-plugin
</artifactId>
<versionRange>
[0.12,)
</versionRange>
<goals>
<goal>check</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-checkstyle-plugin
</artifactId>
<versionRange>
[2.17,)
</versionRange>
<goals>
<goal>check</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/*
* Copyright (c) 2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.eclipse.microprofile.jwt.tck.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.security.PrivateKey;
import java.security.PublicKey;

import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwa.AlgorithmConstraints.ConstraintType;
import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers;
import org.jose4j.jwe.JsonWebEncryption;
import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.lang.JoseException;

// Utility class to produce and consume encrypted Jwt.
// largely derived from https://bitbucket.org/b_c/jose4j/wiki/JWT%20Examples
public class JWETokenUtils {

private JWETokenUtils() {

}

/**
* Encrypt Json to JWT.
* @param inputJson
* @param sendersPrivateKey
* @param sendersKeyId
* @param receiversPublicKey
* @param receiversKeyId
* @return base64url-encoded parts in the form Header.EncryptedKey.IV.Ciphertext.AuthenticationTag
*/
public static String createEncryptedJWT(String inputJson, PrivateKey sendersPrivateKey, String sendersKeyId,
PublicKey receiversPublicKey, String receiversKeyId) {

// A JWT is a JWS and/or a JWE with JSON claims as the payload.
// In this example it is a JWS nested inside a JWE
// So we first create a JsonWebSignature object.
JsonWebSignature jws = new JsonWebSignature();


// The payload of the JWS is JSON content of the JWT Claims
jws.setPayload(inputJson);

// The JWT is signed using the sender's private key
jws.setKey(sendersPrivateKey);

// Set the Key ID (kid) header because it's just the polite thing to do.
// We only have one signing key in this example but a using a Key ID helps
// facilitate a smooth key rollover process
jws.setKeyIdHeaderValue(sendersKeyId);

// Set the signature algorithm on the JWT/JWS that will integrity protect the claims
//jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256);
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);

String jwt = null;
try {
// Sign the JWS and produce the compact serialization, which will be the inner JWT/JWS
// representation, which is a string consisting of three dot ('.') separated
// base64url-encoded parts in the form Header.Payload.Signature
String innerJwt = jws.getCompactSerialization();

// The outer JWT is a JWE
JsonWebEncryption jwe = new JsonWebEncryption();

// The output of the ECDH-ES key agreement will encrypt a randomly generated content encryption key
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);

// A content encryption key will be generated, then encrypted
// with the receiver public key.
// The content encryption key is used to encrypt the payload
// with a composite AES-CBC / HMAC SHA2 encryption algorithm
String encAlg = ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256;
jwe.setEncryptionMethodHeaderParameter(encAlg);

// We encrypt to the receiver using their public key
jwe.setKey(receiversPublicKey);
jwe.setKeyIdHeaderValue(receiversKeyId);

// A nested JWT requires that the cty (Content Type) header be set to "JWT" in the outer JWT
jwe.setContentTypeHeaderValue("JWT");

// The inner JWT is the payload of the outer JWT
jwe.setPayload(innerJwt);

// Produce the JWE compact serialization, which is the complete JWT/JWE representation,
// which is a string consisting of five dot ('.') separated
// base64url-encoded parts in the form Header.EncryptedKey.IV.Ciphertext.AuthenticationTag
jwt = jwe.getCompactSerialization();
}
catch (JoseException e) {
e.printStackTrace(System.out);
}
return jwt;
}

/**
* Decrypt an encrypted JWT.
* Example only. Real implementations would need to further customize their consumer settings.
* @param encryptedJWT
* @param sendersPublicKey
* @param receiversPrivateKey
* @return
*/
public static String decryptJWT(String encryptedJWT, PublicKey sendersPublicKey, PrivateKey receiversPrivateKey) {
String result = null;
// Use JwtConsumerBuilder to construct an appropriate JwtConsumer, which will
// be used to validate and process the JWT.
// The specific validation requirements for a JWT are context dependent, however,
// it typically advisable to require a (reasonable) expiration time, a trusted issuer, and
// and audience that identifies your system as the intended recipient.
// It is also typically good to allow only the expected algorithm(s) in the given context

AlgorithmConstraints jwsAlgConstraints = new AlgorithmConstraints(ConstraintType.WHITELIST,
AlgorithmIdentifiers.RSA_USING_SHA256);

AlgorithmConstraints jweAlgConstraints = new AlgorithmConstraints(ConstraintType.WHITELIST,
KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);

AlgorithmConstraints jweEncConstraints = new AlgorithmConstraints(ConstraintType.WHITELIST,
ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);

JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime() // the JWT must have an expiration time
//.setMaxFutureValidityInMinutes(300) // but the expiration time can't be too crazy
.setRequireSubject() // the JWT must have a subject claim
.setExpectedIssuer("https://server.example.com") // whom the JWT needs to have been issued by
.setExpectedAudience("s6BhdRkqt3") // to whom the JWT is intended for
.setDecryptionKey(receiversPrivateKey) // decrypt with the receiver's private key
.setVerificationKey(sendersPublicKey) // verify the signature with the sender's public key
.setJwsAlgorithmConstraints(jwsAlgConstraints) // limits the acceptable signature algorithm(s)
.setJweAlgorithmConstraints(jweAlgConstraints) // limits acceptable encryption key establishment algorithm(s)
.setJweContentEncryptionAlgorithmConstraints(jweEncConstraints) // limits acceptable content encryption algorithm(s)
.build(); // create the JwtConsumer instance

try {
// Validate the JWT and process it to the Claims
result = jwtConsumer.processToClaims(encryptedJWT).toJson();
}
catch (InvalidJwtException e) {
e.printStackTrace(System.out);
return null;
}

return result;

}

public static void main(String [] args) {
selfTestRSA();
}

public static void selfTestRSA() {
RsaJsonWebKey senderKey = null;
RsaJsonWebKey receiverKey = null;
// Generate an RSA key pair, which will be used for signing and verification of the JWT, wrapped in a JWK
try {
senderKey = RsaJwkGenerator.generateJwk(2048);
receiverKey = RsaJwkGenerator.generateJwk(2048);
// Give the JWK a Key ID (kid), which is just the polite thing to do
senderKey.setKeyId("k1");
receiverKey.setKeyId("k2");

}
catch (JoseException e) {
e.printStackTrace();
}
String json = readJson("src/test/resources/NeverExpires.json");
System.out.println("Json: " + json);

String eJwt = createEncryptedJWT(json, senderKey.getPrivateKey(), senderKey.getKeyId(), receiverKey.getPublicKey(), receiverKey.getKeyId());

System.out.println("Encrypted JWT: " + eJwt);

String result = decryptJWT(eJwt, senderKey.getPublicKey(), receiverKey.getPrivateKey());
System.out.println("Decrypted JWT: " + result + "\n");

System.out.println(result.equals(json) ? "PASS" : "FAIL");
}

public static String readJson(String jsonResName) {
File f = new File(jsonResName);
String buf = null;
String result = "";
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(f));
while(true) {
buf = br.readLine();
if (buf == null) {
break;
}
result += buf;
}
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result.replaceAll(" ", "");
}
}
63 changes: 63 additions & 0 deletions sandbox/src/test/resources/NeverExpires.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"iss": "https://server.example.com",
"jti": "a-123",
"sub": "24400320",
"upn": "[email protected]",
"preferred_username": "jdoe",
"aud": "s6BhdRkqt3",
"exp": 1864830360,
"iat": 1311280970,
"auth_time": 1311280969,
"roles": [
"Echoer"
],
"groups": [
"Echoer",
"Tester",
"group1",
"group2"
],
"customString": "customStringValue",
"customInteger": 123456789,
"customDouble": 3.141592653589793,
"customStringArray": [
"value0",
"value1",
"value2"
],
"customIntegerArray": [
0,
1,
2,
3
],
"customDoubleArray": [
0.1,
1.1,
2.2,
3.3,
4.4
],
"customObject": {
"my-service": {
"groups": [
"group1",
"group2"
],
"roles": [
"role-in-my-service"
]
},
"service-B": {
"roles": [
"role-in-B"
]
},
"service-C": {
"groups": [
"groupC",
"web-tier"
]
}
}
}

0 comments on commit bb87590

Please sign in to comment.