Skip to content
This repository has been archived by the owner on May 31, 2022. It is now read-only.

Commit

Permalink
Add TokenStore supporting Jwk verification
Browse files Browse the repository at this point in the history
Fixes gh-977
  • Loading branch information
jgrandja committed Feb 13, 2017
1 parent 4f65bf4 commit d15f6f6
Show file tree
Hide file tree
Showing 9 changed files with 817 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2012-2016 the original author or authors.
*

This comment has been minimized.

Copy link
@nealeu

nealeu Mar 6, 2017

Contributor

These should all be "Copyright 2017" as these are new files

This comment has been minimized.

Copy link
@jgrandja

jgrandja Mar 6, 2017

Author Contributor

@nealeu You are looking at an older commit. The Copyright has been updated in this commit

* 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.springframework.security.oauth2.provider.token.store.jwk;

/**
* @author Joe Grandja
*/
final class JwkAttributes {
static final String KEY_ID = "kid";

static final String KEY_TYPE = "kty";

static final String ALGORITHM = "alg";

static final String PUBLIC_KEY_USE = "use";

static final String RSA_PUBLIC_KEY_MODULUS = "n";

static final String RSA_PUBLIC_KEY_EXPONENT = "e";

static final String KEYS = "keys";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* 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.springframework.security.oauth2.provider.token.store.jwk;

/**
* @author Joe Grandja
*/
abstract class JwkDefinition {
private final String keyId;
private final KeyType keyType;
private final PublicKeyUse publicKeyUse;
private final CryptoAlgorithm algorithm;

protected JwkDefinition(String keyId,
KeyType keyType,
PublicKeyUse publicKeyUse,
CryptoAlgorithm algorithm) {
this.keyId = keyId;
this.keyType = keyType;
this.publicKeyUse = publicKeyUse;
this.algorithm = algorithm;
}

String getKeyId() {
return this.keyId;
}

KeyType getKeyType() {
return this.keyType;
}

PublicKeyUse getPublicKeyUse() {
return this.publicKeyUse;
}

CryptoAlgorithm getAlgorithm() {
return this.algorithm;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}

JwkDefinition that = (JwkDefinition) obj;
if (!this.getKeyId().equals(that.getKeyId())) {
return false;
}

return this.getKeyType().equals(that.getKeyType());
}

@Override
public int hashCode() {
int result = this.getKeyId().hashCode();
result = 31 * result + this.getKeyType().hashCode();
return result;
}

enum KeyType {
RSA("RSA"),
EC("EC"),
OCT("oct");

private final String value;

KeyType(String value) {
this.value = value;
}

String value() {
return this.value;
}

static KeyType fromValue(String value) {
KeyType result = null;
for (KeyType keyType : values()) {
if (keyType.value().equals(value)) {
result = keyType;
break;
}
}
return result;
}
}

enum PublicKeyUse {
SIG("sig"),
ENC("enc");

private final String value;

PublicKeyUse(String value) {
this.value = value;
}

String value() {
return this.value;
}

static PublicKeyUse fromValue(String value) {
PublicKeyUse result = null;
for (PublicKeyUse publicKeyUse : values()) {
if (publicKeyUse.value().equals(value)) {
result = publicKeyUse;
break;
}
}
return result;
}
}

enum CryptoAlgorithm {
RS256("SHA256withRSA", "RS256", "RSASSA-PKCS1-v1_5 using SHA-256"),
RS384("SHA384withRSA", "RS384", "RSASSA-PKCS1-v1_5 using SHA-384"),
RS512("SHA512withRSA", "RS512", "RSASSA-PKCS1-v1_5 using SHA-512");

private final String standardName; // JCA Standard Name
private final String headerParamValue;
private final String description;

CryptoAlgorithm(String standardName, String headerParamValue, String description) {
this.standardName = standardName;
this.headerParamValue = headerParamValue;
this.description = description;
}

String standardName() {
return this.standardName;
}

String headerParamValue() {
return this.headerParamValue;
}

String description() {
return this.description;
}

static CryptoAlgorithm fromStandardName(String standardName) {
CryptoAlgorithm result = null;
for (CryptoAlgorithm algorithm : values()) {
if (algorithm.standardName().equals(standardName)) {
result = algorithm;
break;
}
}
return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* 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.springframework.security.oauth2.provider.token.store.jwk;

import org.springframework.security.jwt.codec.Codecs;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;

import java.io.IOException;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

/**
* @author Joe Grandja
*/
class JwkDefinitionSource {
private final URL jwkSetUrl;
private final JwkSetConverter jwkSetConverter = new JwkSetConverter();
private final AtomicReference<Map<JwkDefinition, SignatureVerifier>> jwkDefinitions =
new AtomicReference<Map<JwkDefinition, SignatureVerifier>>(new HashMap<JwkDefinition, SignatureVerifier>());

JwkDefinitionSource(String jwkSetUrl) {
try {
this.jwkSetUrl = new URL(jwkSetUrl);
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Invalid JWK Set URL: " + ex.getMessage(), ex);
}
}

JwkDefinition getDefinition(String keyId) {
JwkDefinition result = null;
for (JwkDefinition jwkDefinition : this.jwkDefinitions.get().keySet()) {
if (jwkDefinition.getKeyId().equals(keyId)) {
result = jwkDefinition;
break;
}
}
return result;
}

JwkDefinition getDefinitionRefreshIfNecessary(String keyId) {
JwkDefinition result = this.getDefinition(keyId);
if (result != null) {
return result;
}
this.refreshJwkDefinitions();
return this.getDefinition(keyId);
}

SignatureVerifier getVerifier(String keyId) {
SignatureVerifier result = null;
JwkDefinition jwkDefinition = this.getDefinitionRefreshIfNecessary(keyId);
if (jwkDefinition != null) {
result = this.jwkDefinitions.get().get(jwkDefinition);
}
return result;
}

private void refreshJwkDefinitions() {
Set<JwkDefinition> jwkDefinitionSet;
try {
jwkDefinitionSet = this.jwkSetConverter.convert(this.jwkSetUrl.openStream());
} catch (IOException ex) {
throw new JwkException("An I/O error occurred while refreshing the JWK Set: " + ex.getMessage(), ex);
}

Map<JwkDefinition, SignatureVerifier> refreshedJwkDefinitions = new LinkedHashMap<JwkDefinition, SignatureVerifier>();

for (JwkDefinition jwkDefinition : jwkDefinitionSet) {
if (JwkDefinition.KeyType.RSA.equals(jwkDefinition.getKeyType())) {
refreshedJwkDefinitions.put(jwkDefinition, this.createRSAVerifier((RSAJwkDefinition)jwkDefinition));
}
}

this.jwkDefinitions.set(refreshedJwkDefinitions);
}

private RsaVerifier createRSAVerifier(RSAJwkDefinition rsaDefinition) {
RsaVerifier result;
try {
BigInteger modulus = new BigInteger(Codecs.b64UrlDecode(rsaDefinition.getModulus()));
BigInteger exponent = new BigInteger(Codecs.b64UrlDecode(rsaDefinition.getExponent()));

RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
.generatePublic(new RSAPublicKeySpec(modulus, exponent));

result = new RsaVerifier(rsaPublicKey, rsaDefinition.getAlgorithm().standardName());

} catch (Exception ex) {
throw new JwkException("An error occurred while creating a RSA Public Key Verifier for \"" +
rsaDefinition.getKeyId() + "\" : " + ex.getMessage(), ex);
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* 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.springframework.security.oauth2.provider.token.store.jwk;

import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;

/**
* @author Joe Grandja
*/
public class JwkException extends OAuth2Exception {

public JwkException(String message) {
super(message);
}

public JwkException(String message, Throwable cause) {
super(message, cause);
}

@Override
public String getOAuth2ErrorCode() {
return "server_error";
}

@Override
public int getHttpErrorCode() {
return 500;
}
}
Loading

0 comments on commit d15f6f6

Please sign in to comment.