Skip to content

Commit

Permalink
feat(jans-auth-server): archived jwks (#6503)
Browse files Browse the repository at this point in the history
* feat(jans-auth-server): archive rotated keys and make them available via endpoint #6437

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>

* feat(jans-auth-server): added /jwks/archived to swagger #6437

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>

* feat(jans-auth-server): added jwk archive on key rotation #6437

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>

* feat(jans-auth-server): added tests for archived jwk and clean up support #6437

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>

---------

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>
Signed-off-by: Mustafa Baser <mbaser@mail.com>
  • Loading branch information
yuriyz authored and devrimyatar committed Dec 30, 2023
1 parent 4436d90 commit 6590691
Show file tree
Hide file tree
Showing 27 changed files with 699 additions and 24 deletions.
6 changes: 6 additions & 0 deletions docs/admin/auth-server/crypto/keys.md
Expand Up @@ -29,6 +29,12 @@ Gets list of JSON Web Key (JWK) used by server. JWK is a JSON data structure tha

***Let's see some example of JWT public keys used in janssen.***

When jwk is expired, it is archived and can be access by following url:

```https://<your_server>/jans-auth/restv1/jwks/archived/{kid}```

More info about archived jwks can be found [here](../endpoints/archived-jwks-uri.md)

### Example-1

The "kty" (Key Type) of this key is RSA. This key is "use" (Public Key Use) for "sig" (Signature) on data. The "alg" (Algorithm) intended with this key is "RS256".
Expand Down
45 changes: 45 additions & 0 deletions docs/admin/auth-server/endpoints/archived-jwks-uri.md
@@ -0,0 +1,45 @@
---
tags:
- administration
- auth-server
- jwks
- json-web-key-set
- endpoint
---

# Overview

Janssen Server supports `/jwks/archived/{kid}` metadata endpoint and publishes its Archived JSON Web Keys (JWKs) at this endpoint. This
endpoint publishes expired signing keys as well as expired encryption keys used by Janssen Server. RP can use these keys to validate
signatures from Janssen Server, and also to perform encryption and decryption if keys are no longer present in `/jwks` endpoint.
Like other metadata endpoints, this is not a secure endpoint.

URL to access archived jwks endpoint on Janssen Server is listed in the response of Janssen Server's well-known
[configuration endpoint](./configuration.md) given below.

```text
https://janssen.server.host/jans-auth/.well-known/openid-configuration
```

`archived_jwks_uri` claim in the response specifies the URL for archived jwks endpoint. By default, the archived jwks endpoint looks like below:

```
https://janssen.server.host/jans-auth/restv1/jwks/archived/{kid}
```

This endpoint is always enabled and can not be disabled using feature flags.

## Configuration Properties

Archived JWKs endpoint can be further configured using Janssen Server configuration properties listed below. When using
[Janssen Text-based UI(TUI)](../../config-guide/config-tools/jans-tui/README.md) to configure the properties,
navigate via `Auth Server`->`Properties`.

- [archivedJwksUri](../../reference/json/properties/janssenauthserver-properties.md#archivedjwksuri)
- [archivedJwkLifetimeInSeconds](../../reference/json/properties/janssenauthserver-properties.md#archivedjwklifetimeinseconds)

If `archivedJwkLifetimeInSeconds` is not set then AS falls back to one year expiration. After archived jwk lifetime is passed, jwk is removed from archive.

## Want to contribute?

If you have content you'd like to contribute to this page in the meantime, you can get started with our [Contribution guide](https://docs.jans.io/head/CONTRIBUTING/).
Expand Up @@ -113,6 +113,7 @@ public static void parse(String json, OpenIdConfigurationResponse response) {
response.setCheckSessionIFrame(jsonObj.optString(CHECK_SESSION_IFRAME, null));
response.setEndSessionEndpoint(jsonObj.optString(END_SESSION_ENDPOINT, null));
response.setJwksUri(jsonObj.optString(JWKS_URI, null));
response.setArchivedJwksUri(jsonObj.optString(ARCHIVED_JWKS_URI, null));
response.setRegistrationEndpoint(jsonObj.optString(REGISTRATION_ENDPOINT, null));
response.setIntrospectionEndpoint(jsonObj.optString(INTROSPECTION_ENDPOINT, null));
response.setParEndpoint(jsonObj.optString(PAR_ENDPOINT, null));
Expand Down
Expand Up @@ -36,6 +36,7 @@ public class OpenIdConfigurationResponse extends BaseResponse implements Seriali
private String checkSessionIFrame;
private String endSessionEndpoint;
private String jwksUri;
private String archivedJwksUri;
private String registrationEndpoint;
private String introspectionEndpoint;
private String parEndpoint;
Expand Down Expand Up @@ -377,6 +378,24 @@ public void setJwksUri(String jwksUri) {
this.jwksUri = jwksUri;
}

/**
* Gets the URL of the OP's Archived JSON Web Key Set (JWK) document.
*
* @return The URL of the OP's Archived JSON Web Key Set (JWK) document.
*/
public String getArchivedJwksUri() {
return archivedJwksUri;
}

/**
* Sets the URL of the OP's Archived JSON Web Key Set (JWK) document.
*
* @param archivedJwksUri The URL of the OP's Archived JSON Web Key Set (JWK) document.
*/
public void setArchivedJwksUri(String archivedJwksUri) {
this.archivedJwksUri = archivedJwksUri;
}

/**
* Returns the URL of the Dynamic Client Registration endpoint.
*
Expand Down Expand Up @@ -1229,6 +1248,7 @@ public String toString() {
", checkSessionIFrame='" + checkSessionIFrame + '\'' +
", endSessionEndpoint='" + endSessionEndpoint + '\'' +
", jwksUri='" + jwksUri + '\'' +
", archivedJwksUri='" + archivedJwksUri + '\'' +
", registrationEndpoint='" + registrationEndpoint + '\'' +
", introspectionEndpoint='" + introspectionEndpoint + '\'' +
", deviceAuthzEndpoint='" + deviceAuthzEndpoint + '\'' +
Expand Down
Expand Up @@ -99,6 +99,7 @@ public abstract class BaseTest {
protected String checkSessionIFrame;
protected String endSessionEndpoint;
protected String jwksUri;
protected String archivedJwksUri;
protected String registrationEndpoint;
protected String configurationEndpoint;
protected String introspectionEndpoint;
Expand Down Expand Up @@ -358,6 +359,14 @@ public void setJwksUri(String jwksUri) {
this.jwksUri = jwksUri;
}

public String getArchivedJwksUri() {
return archivedJwksUri;
}

public void setArchivedJwksUri(String archivedJwksUri) {
this.archivedJwksUri = archivedJwksUri;
}

public String getRegistrationEndpoint() {
return registrationEndpoint;
}
Expand Down Expand Up @@ -988,6 +997,7 @@ public void discovery(ITestContext context) throws Exception {
checkSessionIFrame = response.getCheckSessionIFrame();
endSessionEndpoint = response.getEndSessionEndpoint();
jwksUri = response.getJwksUri();
archivedJwksUri = response.getArchivedJwksUri();
registrationEndpoint = response.getRegistrationEndpoint();
introspectionEndpoint = response.getIntrospectionEndpoint();
parEndpoint = response.getParEndpoint();
Expand All @@ -1010,6 +1020,7 @@ public void discovery(ITestContext context) throws Exception {
checkSessionIFrame = context.getCurrentXmlTest().getParameter("checkSessionIFrame");
endSessionEndpoint = context.getCurrentXmlTest().getParameter("endSessionEndpoint");
jwksUri = context.getCurrentXmlTest().getParameter("jwksUri");
archivedJwksUri = context.getCurrentXmlTest().getParameter("archivedJwksUri");
registrationEndpoint = context.getCurrentXmlTest().getParameter("registrationEndpoint");
configurationEndpoint = context.getCurrentXmlTest().getParameter("configurationEndpoint");
introspectionEndpoint = context.getCurrentXmlTest().getParameter("introspectionEndpoint");
Expand Down
@@ -0,0 +1,99 @@
package io.jans.as.common.model.common;

import io.jans.orm.annotation.*;
import io.jans.orm.model.base.DeletableEntity;
import org.json.JSONObject;

import java.io.Serializable;
import java.util.Date;

/**
* @author Yuriy Z
*/
@DataEntry
@ObjectClass(value = "jansArchJwk")
public class ArchivedJwk extends DeletableEntity implements Serializable {

@DN
private String dn;

@AttributeName(name = "jansId")
private String id;

@AttributeName(name = "creationDate")
private Date creationDate = new Date();

@JsonObject
@AttributeName(name = "jansData")
private JSONObject data;

@AttributeName(name = "attr")
@JsonObject
private ArchivedKeyAttributes attributes;

@Expiration
private int ttl;

@Override
public String getDn() {
return dn;
}

@Override
public void setDn(String dn) {
this.dn = dn;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public Date getCreationDate() {
return creationDate;
}

public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}

public JSONObject getData() {
return data;
}

public void setData(JSONObject data) {
this.data = data;
}

public ArchivedKeyAttributes getAttributes() {
if (attributes == null) attributes = new ArchivedKeyAttributes();
return attributes;
}

public void setAttributes(ArchivedKeyAttributes attributes) {
this.attributes = attributes;
}

public int getTtl() {
return ttl;
}

public void setTtl(int ttl) {
this.ttl = ttl;
}

@Override
public String toString() {
return "ArchivedKey{" +
"dn='" + dn + '\'' +
", id='" + id + '\'' +
", creationDate=" + creationDate +
", data=" + data +
", attributes=" + attributes +
", ttl=" + ttl +
"} " + super.toString();
}
}
@@ -0,0 +1,36 @@
package io.jans.as.common.model.common;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
* @author Yuriy Z
*/
@JsonIgnoreProperties(
ignoreUnknown = true
)
public class ArchivedKeyAttributes implements Serializable {

@JsonProperty("attributes")
private Map<String, String> attributes;

public Map<String, String> getAttributes() {
if (attributes == null) attributes = new HashMap<>();
return attributes;
}

public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}

@Override
public String toString() {
return "ArchivedKeyAttributes{" +
"attributes=" + attributes +
'}';
}
}
45 changes: 45 additions & 0 deletions jans-auth-server/docs/swagger.yaml
Expand Up @@ -1227,6 +1227,51 @@ paths:
$ref: '#/components/schemas/JsonWebKey'
500:
$ref: '#/components/responses/InternalServerError'
/jwks/archived:
get:
tags:
- Archived JWK - Archived JSON Web Key (JWK)
summary: An archived JSON Web Key (JWK) used by server. JWK is a JSON data structure that represents a set of public keys as a JSON object [RFC4627].
description: Provides archived JWK used by server.
operationId: archived-jwk
parameters:
- name: kid
in: query
description: Key ID.
schema:
type: string
responses:
200:
description: OK
content:
application/json:
schema:
title: JsonWebKey
type: object
description: JSON Web Key (JWK) - A JSON object that represents a JWK.
$ref: '#/components/schemas/JsonWebKey'
400:
description: Error codes for archived jwk endpoint.
content:
application/json:
schema:
title: Error
type: object
required:
- error
- error_description
properties:
error:
type: string
format: enum
example:
- invalid_request
error_description:
type: string
details:
type: string
500:
$ref: '#/components/responses/InternalServerError'
/register:
post:
tags:
Expand Down
Expand Up @@ -65,6 +65,16 @@ public class BaseDnConfiguration {
private String fido2Attestation;
@XmlElement(name = "fido2Assertion")
private String fido2Assertion;
@XmlElement(name = "archivedJwks")
private String archivedJwks;

public String getArchivedJwks() {
return archivedJwks;
}

public void setArchivedJwks(String archivedJwks) {
this.archivedJwks = archivedJwks;
}

public String getStat() {
return stat;
Expand Down

0 comments on commit 6590691

Please sign in to comment.