Skip to content

Commit

Permalink
Configure primary signing key via YML. Verify signature using key ID
Browse files Browse the repository at this point in the history
from token.

[#107773584] https://www.pivotaltracker.com/story/show/107773584

Signed-off-by: Jonathan Lo <jlo@us.ibm.com>
  • Loading branch information
Jeremy Coffield authored and pivotal committed Mar 14, 2016
1 parent 84f9b68 commit 2b636d6
Show file tree
Hide file tree
Showing 18 changed files with 357 additions and 116 deletions.
62 changes: 38 additions & 24 deletions docs/UAA-APIs.rst
Expand Up @@ -850,8 +850,16 @@ Request body *example* ::
}, },
"tokenPolicy": { "tokenPolicy": {
"accessTokenValidity": 4800, "accessTokenValidity": 4800,
"keys": {}, "refreshTokenValidity": 9600,
"refreshTokenValidity": 9600 "primaryKeyId": "key-id-1",
"keys": {
"key-id-1": {
"signingKey": "pr1V4T3_keY_t3xT"
},
"key-id-2": {
"signingKey": "an07h3r_PRivA73_K3y"
}
}
} }
} }
"name": "The Twiglet Zone", "name": "The Twiglet Zone",
Expand Down Expand Up @@ -899,34 +907,36 @@ Response *Codes* ::
Fields *Available Fields* :: Fields *Available Fields* ::


Identity Zone Fields Identity Zone Fields
============================== ==================== ======== ======================================================================================================================================================================== ============================== ==================== ========= ========================================================================================================================================================================
id String(36) Required Unique identifier for this zone, often set to same as subdomain id String(36) Required Unique identifier for this zone, often set to same as subdomain
subdomain String(255) Required Unique subdomain for the running instance. May only contain legal characters for a sub domain name subdomain String(255) Required Unique subdomain for the running instance. May only contain legal characters for a sub domain name
name String(255) Required Human readable zone name name String(255) Required Human readable zone name
version int Optional Reserved for future use of E-Tag versioning version int Optional Reserved for future use of E-Tag versioning
description String Optional Description of the zone description String Optional Description of the zone
created epoch timestamp Auto UAA sets the creation date created epoch timestamp Auto UAA sets the creation date
last_modified epoch timestamp Auto UAA sets the modification date last_modified epoch timestamp Auto UAA sets the modification date


Identity Zone Configuration (provided in JSON format as part of the ``config`` field on the Identity Zone - See class org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration) Identity Zone Configuration (provided in JSON format as part of the ``config`` field on the Identity Zone - See class org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration)
============================== ==================== ======== ======================================================================================================================================================================== ============================== ==================== ========= ========================================================================================================================================================================
tokenPolicy TokenPolicy Optional Various fields pertaining to the JWT access and refresh tokens. See `Token Policy` section below for details. tokenPolicy TokenPolicy Optional Various fields pertaining to the JWT access and refresh tokens. See `Token Policy` section below for details.
samlConfig SamlConfig Optional Various fields pertaining to SAML identity provider configuration. See ``SamlConfig`` section below for details. samlConfig SamlConfig Optional Various fields pertaining to SAML identity provider configuration. See ``SamlConfig`` section below for details.


Token Policy ``TokenPolicy`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.TokenPolicy) Token Policy ``TokenPolicy`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.TokenPolicy)
============================== ==================== ======== ======================================================================================================================================================================== ============================== ==================== ========= ========================================================================================================================================================================
accessTokenValidity int Optional How long the access token is valid for in seconds. accessTokenValidity int Optional How long the access token is valid for in seconds.
refreshTokenValidity int Optional How long the refresh token is valid for seconds. refreshTokenValidity int Optional How long the refresh token is valid for seconds.
keys Map Optional A map specifying current and historical JWT signing keys, with unique IDs for referring to them. For an explanation of key rotation, see ``/token_key``.
primaryKeyId String Dependent The ID of the signing key that should be used for signing tokens. Optional if only one signing key is specified. For an explanation of key rotation, see ``/token_key``.


SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig) SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig)
============================== ==================== ======== ======================================================================================================================================================================== ============================== ==================== ========= ========================================================================================================================================================================
requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``true``. requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``true``.
wantAssertionSigned Boolean Optional Exposed SAML metadata property. If ``true``, all assertions received by the SAML provider must be signed. Defaults to ``true``. wantAssertionSigned Boolean Optional Exposed SAML metadata property. If ``true``, all assertions received by the SAML provider must be signed. Defaults to ``true``.
certificate String Optional Exposed SAML metadata property. The certificate used to sign all communications. Reserved for future use. certificate String Optional Exposed SAML metadata property. The certificate used to sign all communications. Reserved for future use.
privateKey String Optional Exposed SAML metadata property. The SAML provider's private key. Reserved for future use. privateKey String Optional Exposed SAML metadata property. The SAML provider's private key. Reserved for future use.
privateKeyPassword String Optional Exposed SAML metadata property. The SAML provider's private key password. Reserved for future use. privateKeyPassword String Optional Exposed SAML metadata property. The SAML provider's private key password. Reserved for future use.


============================= ==================== ======== ======================================================================================================================================================================== ============================= ==================== ========= ========================================================================================================================================================================


Curl Example POST (Token contains ``zones.write`` scope) :: Curl Example POST (Token contains ``zones.write`` scope) ::


Expand Down Expand Up @@ -2753,6 +2763,10 @@ Get the Token Signing Key: ``GET /token_key``
An endpoint which returns the JSON Web Token (JWT) key, used by the UAA to sign JWT access tokens, and to be used by authorized clients to verify that a token came from the UAA. The key is in JSON Web Key format. For complete information about JSON Web Keys, see RFC 7517 (https://tools.ietf.org/html/rfc7517). An endpoint which returns the JSON Web Token (JWT) key, used by the UAA to sign JWT access tokens, and to be used by authorized clients to verify that a token came from the UAA. The key is in JSON Web Key format. For complete information about JSON Web Keys, see RFC 7517 (https://tools.ietf.org/html/rfc7517).
In the case when the token key is symmetric, signer key and verifier key are the same, then this call is authenticated with client credentials using the HTTP Basic method. In the case when the token key is symmetric, signer key and verifier key are the same, then this call is authenticated with client credentials using the HTTP Basic method.


JWT signing keys are specified via the identity zone configuration (see ``/identity-zones``). An identity zone token policy can be configured with multiple keys for purposes of key rotation. When adding a new key, set its ID as the ``primaryKeyId`` to use it to sign all new tokens. ``/check_token`` will continue to verify tokens signed with the previous signing key for as long as it is present in the ``keys`` of the identity zone's token policy. Remove it to invalidate all those tokens.

JWT tokens issued by the UAA contain a ``kid`` field, indicating which key should be used for verification of the token. In the case that this is not the primary key, use ``GET /token_keys`` to retrieve all currently valid keys, and select the key that matches the token's ``kid``.

================ ======================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================= ================ =======================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================
Request ``GET /token_key`` Request ``GET /token_key``
Request body *empty* Request body *empty*
Expand Down Expand Up @@ -2790,7 +2804,7 @@ might also see an asymmetric RSA public key with algorithm
Get Token Signing Keys: ``GET /token_keys`` Get Token Signing Keys: ``GET /token_keys``
--------------------------------------------- ---------------------------------------------


An endpoint which returns the list of JWT keys. An endpoint which returns the list of JWT keys. To support key rotation, this list specifies the IDs of all currently valid keys. JWT tokens issued by the UAA contain a ``kid`` field, indicating which key should be used for verification of the token.


================ ======================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================= ================ =======================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================
Request ``GET /token_keys`` Request ``GET /token_keys``
Expand Down
Expand Up @@ -51,4 +51,5 @@ public class ClaimConstants {
public static final String ROLES = "roles"; public static final String ROLES = "roles";
public static final String PROFILE = "profile"; public static final String PROFILE = "profile";
public static final String USER_ATTRIBUTES = "user_attributes"; public static final String USER_ATTRIBUTES = "user_attributes";
public static final String KID = "kid";
} }
Expand Up @@ -78,6 +78,8 @@ public class Claims {
private String profile; private String profile;
@JsonProperty(ClaimConstants.USER_ATTRIBUTES) @JsonProperty(ClaimConstants.USER_ATTRIBUTES)
private String userAttributes; private String userAttributes;
@JsonProperty(ClaimConstants.KID)
private String kid;


public String getUserId() { public String getUserId() {
return userId; return userId;
Expand Down Expand Up @@ -300,4 +302,12 @@ public String getUserAttributes() {
public void setUserAttributes(String userAttributes) { public void setUserAttributes(String userAttributes) {
this.userAttributes = userAttributes; this.userAttributes = userAttributes;
} }

public String getKid() {
return kid;
}

public void setKid(String kid) {
this.kid = kid;
}
} }
Expand Up @@ -39,13 +39,13 @@ public class TokenPolicy {
private int refreshTokenValidity; private int refreshTokenValidity;


@JsonGetter("keys") @JsonGetter("keys")
public Map<String, KeyInformation> getKeysLegacy() { private Map<String, KeyInformation> getKeysLegacy() {
Map<String, String> keys = getKeys(); Map<String, String> keys = getKeys();
return keys == null ? null : keys.entrySet().stream().collect(outputCollector); return keys == null ? null : keys.entrySet().stream().collect(outputCollector);
} }


@JsonSetter("keys") @JsonSetter("keys")
public void setKeysLegacy(Map<String, KeyInformation> keys) { private void setKeysLegacy(Map<String, KeyInformation> keys) {
setKeys(keys == null ? null : keys.entrySet().stream().collect(inputCollector)); setKeys(keys == null ? null : keys.entrySet().stream().collect(inputCollector));
} }


Expand All @@ -61,10 +61,10 @@ public TokenPolicy(int accessTokenValidity, int refreshTokenValidity) {
this.refreshTokenValidity = refreshTokenValidity; this.refreshTokenValidity = refreshTokenValidity;
} }


public TokenPolicy(int accessTokenValidity, int refreshTokenValidity, SigningKeysMap keyPairsMap) { public TokenPolicy(int accessTokenValidity, int refreshTokenValidity, SigningKeysMap signingKeysMap) {
this(accessTokenValidity, refreshTokenValidity); this(accessTokenValidity, refreshTokenValidity);


setKeys(keyPairsMap.getKeys()); setKeys(signingKeysMap.getKeys());
} }


public int getAccessTokenValidity() { public int getAccessTokenValidity() {
Expand Down Expand Up @@ -96,19 +96,12 @@ public void setKeys(Map<String, String> keys) {
throw new IllegalArgumentException("KeyId and Signing key should not be null or empty"); throw new IllegalArgumentException("KeyId and Signing key should not be null or empty");
} }
}); });
Set<String> keyIds = keys.keySet();
if (primaryKeyId == null || !keyIds.contains(primaryKeyId)) {
Optional<String> firstKeyId = keyIds.stream().findFirst();
if (firstKeyId.isPresent()) {
primaryKeyId = firstKeyId.get();
}
}
} }
this.keys = keys == null ? null : new HashMap<>(keys); this.keys = keys == null ? null : new HashMap<>(keys);
} }


@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public static class KeyInformation { private static class KeyInformation {
private String signingKey; private String signingKey;


public String getSigningKey() { public String getSigningKey() {
Expand All @@ -121,11 +114,19 @@ public void setSigningKey(String signingKey) {
} }


public String getPrimaryKeyId() { public String getPrimaryKeyId() {
return primaryKeyId; if(primaryKeyId != null) {
return primaryKeyId;
}

if(keys != null && keys.size() == 1) {
return keys.keySet().stream().findAny().get();
}

return null;
} }


public void setPrimaryKeyId(String primaryKeyId) { public void setPrimaryKeyId(String primaryKeyId) {
this.primaryKeyId = primaryKeyId; this.primaryKeyId = primaryKeyId;
} }


} }
Expand Up @@ -82,7 +82,7 @@ public Claims checkToken(@RequestParam("token") String value, @RequestParam(name
} }


Claims response = getClaimsForToken(value); Claims response = getClaimsForToken(value);
List<String> claimScopes = response.getScope().stream().map(s -> s.toLowerCase()).collect(Collectors.toList()); List<String> claimScopes = response.getScope().stream().map(String::toLowerCase).collect(Collectors.toList());


List<String> missingScopes = new ArrayList<>(); List<String> missingScopes = new ArrayList<>();
for(String expectedScope : scopes) { for(String expectedScope : scopes) {
Expand Down

0 comments on commit 2b636d6

Please sign in to comment.