Skip to content

Commit

Permalink
Merge pull request #158 from sberyozkin/generate_token
Browse files Browse the repository at this point in the history
JWT builder code and tests
  • Loading branch information
starksm64 authored Jan 13, 2020
2 parents abe3801 + b4186de commit 37a1687
Show file tree
Hide file tree
Showing 29 changed files with 2,686 additions and 3 deletions.
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

0 comments on commit 37a1687

Please sign in to comment.