This repository has been archived by the owner on May 31, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TokenStore supporting Jwk verification
Fixes gh-977
- Loading branch information
Showing
9 changed files
with
817 additions
and
0 deletions.
There are no files selected for viewing
35 changes: 35 additions & 0 deletions
35
...main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong. |
||
* 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"; | ||
} |
168 changes: 168 additions & 0 deletions
168
...main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
117 changes: 117 additions & 0 deletions
117
...ava/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
.../main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.
These should all be "Copyright 2017" as these are new files