Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JWT builder code and tests #158

Merged
merged 1 commit into from
Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions implementation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@
<artifactId>javax.json</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.smallrye.config</groupId>
<artifactId>smallrye-config</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
240 changes: 238 additions & 2 deletions implementation/src/main/java/io/smallrye/jwt/KeyUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,21 @@
*/
package io.smallrye.jwt;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.math.BigInteger;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
Expand All @@ -34,19 +41,39 @@
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;

import org.jboss.logging.Logger;
import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.jwk.OctetSequenceJsonWebKey;
import org.jose4j.jwk.PublicJsonWebKey;

import io.smallrye.jwt.auth.principal.KeyLocationResolver;

/**
* Utility methods for dealing with decoding public and private keys resources
*/
public class KeyUtils {
public final class KeyUtils {

private static final Logger LOGGER = Logger.getLogger(KeyLocationResolver.class);
private static final String HTTP_BASED_SCHEME = "http";
private static final String CLASSPATH_SCHEME = "classpath:";
private static final String FILE_SCHEME = "file:";
private static final String RSA = "RSA";

private KeyUtils() {
}

public static PrivateKey readPrivateKey(String pemResName) throws IOException, GeneralSecurityException {
InputStream contentIS = KeyUtils.class.getResourceAsStream(pemResName);
byte[] tmp = new byte[4096];
Expand Down Expand Up @@ -190,6 +217,215 @@ private static String removeCertBeginEnd(String pem) {
return pem.trim();
}

private KeyUtils() {
static String readKeyContent(String keyLocation) throws IOException {

InputStream is = null;

if (keyLocation.startsWith(HTTP_BASED_SCHEME)) {
// It can be PEM key at HTTP or HTTPS URL, JWK set at HTTP URL or single JWK at either HTTP or HTTPS URL
is = getUrlResolver().resolve(keyLocation);
} else if (keyLocation.startsWith(FILE_SCHEME)) {
is = getAsFileSystemResource(keyLocation.substring(FILE_SCHEME.length()));
} else if (keyLocation.startsWith(CLASSPATH_SCHEME)) {
is = getAsClasspathResource(keyLocation.substring(CLASSPATH_SCHEME.length()));
} else {
is = getAsFileSystemResource(keyLocation);
if (is == null) {
is = getAsClasspathResource(keyLocation);
}
if (is == null) {
is = getUrlResolver().resolve(keyLocation);
}
}

if (is == null) {
throw new IOException("No resource with the named " + keyLocation + " location exists");
}

StringWriter contents = new StringWriter();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
String line = null;
while ((line = reader.readLine()) != null) {
contents.write(line);
}
}
return contents.toString();
}

static UrlStreamResolver getUrlResolver() {
return new UrlStreamResolver();
}

static PrivateKey tryAsPEMPrivateKey(String content) {
LOGGER.debugf("Trying to create a key from the encoded PEM key...");
try {
return decodePrivateKey(content);
} catch (Exception e) {
LOGGER.debug("Failed to create a key from the encoded PEM key", e);
}
return null;
}

static PublicKey tryAsPEMPublicKey(String content) {
LOGGER.debugf("Trying to create a key from the encoded PEM key...");
try {
return KeyUtils.decodePublicKey(content);
} catch (Exception e) {
LOGGER.debug("Failed to create a key from the encoded PEM key", e);
}
return null;
}

static PublicKey tryAsPEMCertificate(String content) {
LOGGER.debugf("Trying to create a key from the encoded PEM certificate...");
try {
return KeyUtils.decodeCertificate(content);
} catch (Exception e) {
LOGGER.debug("Failed to to create a key from the encoded PEM certificate", e);
}
return null;
}

static List<JsonWebKey> loadJsonWebKeys(String content) {
LOGGER.debugf("Trying to load the local JWK(S)...");

JsonObject jwks = null;
try (JsonReader reader = Json.createReader(new StringReader(content))) {
jwks = reader.readObject();
} catch (Exception ex) {
LOGGER.debug("Failed to load the JWK(S)");
return null;
}

List<JsonWebKey> localKeys = null;
JsonArray keys = jwks.getJsonArray(JsonWebKeySet.JWK_SET_MEMBER_NAME);

try {
if (keys != null) {
// JWK set
localKeys = new ArrayList<>(keys.size());
for (int i = 0; i < keys.size(); i++) {
localKeys.add(createJsonWebKey(keys.getJsonObject(i)));
}
} else {
// single JWK
localKeys = Collections.singletonList(createJsonWebKey(jwks));
}
} catch (Exception ex) {
LOGGER.debug("Failed to parse the JWK JSON representation");
return null;
}
return localKeys;
}

static JsonWebKey createJsonWebKey(JsonObject jsonObject) throws Exception {
return JsonWebKey.Factory.newJwk(JsonUtil.parseJson(jsonObject.toString()));
}

static InputStream getAsFileSystemResource(String publicKeyLocation) throws IOException {
try {
return new FileInputStream(publicKeyLocation);
} catch (FileNotFoundException e) {
return null;
}
}

static InputStream getAsClasspathResource(String location) {
InputStream is = KeyUtils.class.getResourceAsStream(location);
if (is == null) {
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(location);
}
return is;
}

static class UrlStreamResolver {
public InputStream resolve(String keyLocation) throws IOException {
return new URL(keyLocation).openStream();
}
}

public static Key readEncryptionKey(String location, String kid) throws IOException {
String content = readKeyContent(location);

Key key = tryAsPEMPublicKey(content);
if (key == null) {
key = tryAsPEMCertificate(content);
}
if (key == null) {
List<JsonWebKey> jwks = loadJsonWebKeys(content);
if (jwks != null) {
key = getEncryptionKeyFromJwkSet(kid, jwks);
}
}
return key;
}

static Key getEncryptionKeyFromJwkSet(String kid, List<JsonWebKey> keys) {
if (kid != null) {
for (JsonWebKey currentJwk : keys) {
if (kid.equals(currentJwk.getKeyId())) {
return getPublicOrSecretEncryptingKey(currentJwk);
}
}
}
// if JWK set contains a single JWK only then try to use it
// but only if 'kid' is not set in both the token and this JWK
if (keys.size() == 1 && (kid == null || keys.get(0).getKeyId() == null)) {
return getPublicOrSecretEncryptingKey(keys.get(0));
}
return null;
}

static Key getPublicOrSecretEncryptingKey(JsonWebKey currentJwk) {
List<String> keyOps = currentJwk.getKeyOps();
if (keyOps == null || keyOps.contains("encryption")) {
if ("oct".equals(currentJwk.getKeyType())) {
return OctetSequenceJsonWebKey.class.cast(currentJwk).getKey();
} else {
return PublicJsonWebKey.class.cast(currentJwk).getPublicKey();
}
}
return null;
}

public static Key readSigningKey(String location, String kid) throws IOException {
String content = readKeyContent(location);

Key key = tryAsPEMPrivateKey(content);
if (key == null) {
List<JsonWebKey> jwks = loadJsonWebKeys(content);
if (jwks != null) {
key = getSigningKeyFromJwkSet(kid, jwks);
}
}
return key;
}

static Key getSigningKeyFromJwkSet(String kid, List<JsonWebKey> keys) {
if (kid != null) {
for (JsonWebKey currentJwk : keys) {
if (kid.equals(currentJwk.getKeyId())) {
return getPrivateOrSecretSigningKey(currentJwk);
}
}
}
// if JWK set contains a single JWK only then try to use it
// but only if 'kid' is not set in both the token and this JWK
if (keys.size() == 1 && (kid == null || keys.get(0).getKeyId() == null)) {
return getPrivateOrSecretSigningKey(keys.get(0));
}
return null;
}

static Key getPrivateOrSecretSigningKey(JsonWebKey currentJwk) {
List<String> keyOps = currentJwk.getKeyOps();
if (keyOps == null || keyOps.contains("sign")) {
if ("oct".equals(currentJwk.getKeyType())) {
return OctetSequenceJsonWebKey.class.cast(currentJwk).getKey();
} else {
return PublicJsonWebKey.class.cast(currentJwk).getPrivateKey();
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.smallrye.jwt.algorithm;

/**
* * JWT JSON Web Content Encryption Algorithms.
*
* @see <a href="https://tools.ietf.org/html/rfc7518#section-5">https://tools.ietf.org/html/rfc7518#section-5</a>
*/
public enum ContentEncryptionAlgorithm {
A256GCM("A256GCM"),
A128CBC_HS256("A128CBC-HS256");

private String algorithmName;

private ContentEncryptionAlgorithm(String algorithmName) {
this.algorithmName = algorithmName;
}

public String getAlgorithm() {
return algorithmName;
}

public static ContentEncryptionAlgorithm fromAlgorithm(String algorithmName) {
return ContentEncryptionAlgorithm.valueOf(algorithmName.replaceAll("-", "_").replaceAll("\\+", "_"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.smallrye.jwt.algorithm;

/**
* JWT JSON Web Key Encryption (Management) Algorithms.
*
* @see <a href="https://tools.ietf.org/html/rfc7518#section-4">https://tools.ietf.org/html/rfc7518#section-4</a>
*/
public enum KeyEncryptionAlgorithm {
RSA_OAEP_256("RSA-OAEP-256"),
ECDH_ES_A256KW("ECDH-ES+A256KW"),
A256KW("A256KW");

private String algorithmName;

private KeyEncryptionAlgorithm(String algorithmName) {
this.algorithmName = algorithmName;
}

public String getAlgorithm() {
return algorithmName;
}

public static KeyEncryptionAlgorithm fromAlgorithm(String algorithmName) {
return KeyEncryptionAlgorithm.valueOf(algorithmName.replaceAll("-", "_").replaceAll("\\+", "_"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.smallrye.jwt.algorithm;

/**
* JWT JSON Web Signature Algorithms.
*
* @see <a href="https://tools.ietf.org/html/rfc7518#section-3">https://tools.ietf.org/html/rfc7518#section-3</a>
*/
public enum SignatureAlgorithm {
RS256,
ES256,
HS256;

public String getAlgorithm() {
return this.name();
}

public static SignatureAlgorithm fromAlgorithm(String algorithmName) {
return SignatureAlgorithm.valueOf(algorithmName);
}
}
Loading