Skip to content

Commit

Permalink
ARC-1234: Cleanup all around & proper docs
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasrichner-oviva committed Feb 6, 2024
1 parent 0b5ce13 commit 46397b4
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 45 deletions.
60 changes: 58 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,52 @@
- Models for the EntityStatments, IDP list endpoints etc.
- Narrow support for the 'Fachdienst' use-case.

## Generate Keys & Register for Federation
# Quickstart

```shell
# build everything
mvn clean verify

# generate keys for the application, keep those safe
./gen_keys.sh \
--issuer-uri=https://mydiga.example.com \
--member-id="$MEMBER_ID" \
--organisation-name="My DiGA" \
--generate-keys

# configure the application
export EHEALTHID_RP_APP_NAME=Awesome DiGA
export EHEALTHID_RP_BASE_URI=https://mydiga.example.com
export EHEALTHID_RP_FEDERATION_ENC_JWKS_PATH=enc_jwks.json
export EHEALTHID_RP_FEDERATION_MASTER=https://app-test.federationmaster.de
export EHEALTHID_RP_FEDERATION_SIG_JWKS_PATH=sig_jwks.json
export EHEALTHID_RP_REDIRECT_URIS=https://sso-mydiga.example.com/auth/callback
export EHEALTHID_RP_ES_TTL=PT5M

# boots the relying party server
./start.sh
```

# Configuration

Use environment variables to configure the relying party server.

| Name | Description | Example |
|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------|
| `EHEALTHID_RP_FEDERATION_ENC_JWKS_PATH` | Path to a JWKS with at least one keypair for encryption of ID tokens. | `./enc_jwks.json` |
| `EHEALTHID_RP_FEDERATION_SIG_JWKS_PATH` | Path to a JWKS with at least one keypair for signature withing the federation. All these keys __MUST__ be registered with the federation master. | `./sig_jwks.json` |
| `EHEALTHID_RP_REDIRECT_URIS` | Valid redirection URIs for OpenID connect. | `https://sso-mydiga.example.com/auth/callback` |
| `EHEALTHID_RP_BASE_URI` | The external base URI of the relying party. This is also the `issuer` towards the OpenID federation. Additional paths are unsupported for now. | `https://mydiga-rp.example.com` |
| `EHEALTHID_RP_HOST` | Host to bind to. | `0.0.0.0` |
| `EHEALTHID_RP_PORT` | Port to bind to. | `1234` |
| `EHEALTHID_RP_FEDERATION_MASTER` | The URI of the federation master. | `https://app-test.federationmaster.de` |
| `EHEALTHID_RP_APP_NAME` | The application name within the federation. | `Awesome DiGA` |
| `EHEALTHID_RP_ES_TTL` | The time to live for the entity statement. In ISO8601 format. | `PT12H` |
| `EHEALTHID_RP_SCOPES` | The comma separated list of scopes requested in the federation. This __MUST__ match what was registered with the federation master. | `openid,urn:telematik:email,urn:telematik:display_name` |



# Generate Keys & Register for Federation

In order to participate in the GesundheitsID one needs to register the entity statement of the IDP
or in this case the relying party here.
Expand All @@ -25,6 +70,10 @@ See [Gematik documentation](https://wiki.gematik.de/pages/viewpage.action?pageId
details
on the registration process.

```shell
./gen_keys.sh --help
```

### Generate Fresh Keys and Prepare Registration

```shell
Expand All @@ -44,15 +93,22 @@ export MEMBER_ID=FDmyDiGa0112TU
# a string received from Gematik as part of the registration process
export MEMBER_ID=FDmyDiGa0112TU

# specify the environment, either
# TU -> test environment
# RU -> reference environment
# PU -> productive environment
export ENVIRONMENT=RU

./gen_keys.sh \
--issuer-uri=https://mydiga.example.com \
--member-id="$MEMBER_ID" \
--organisation-name="My DiGA" \
--environment=$ENVIRONMENT \
--signing-jwks=./sig_jwks.json \
--encryption-jwks=./enc_jwks.json
```

## End-to-End Test flow with Gematik Reference IDP
## Library IntegrationTest flow with Gematik Reference IDP

**Prerequisites**:

Expand Down
1 change: 1 addition & 0 deletions ehealthid-rp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
</dependencies>

<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ehealthid.relyingparty;
package com.oviva.ehealthid.relyingparty;

import com.nimbusds.jose.jwk.JWKSet;
import com.oviva.ehealthid.auth.AuthenticationFlow;
Expand Down Expand Up @@ -27,14 +27,14 @@
import java.time.Clock;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {

private static final Logger logger = LoggerFactory.getLogger(Main.class);

private static final String BANNER =
"""
____ _
Expand All @@ -43,6 +43,7 @@ public class Main {
\\____/|___/_/|___/\\_,_/
GesundheitsID OpenID Connect Relying-Party
""";
private static final String CONFIG_PREFIX = "EHEALTHID_RP";
private final ConfigProvider configProvider;

public Main(ConfigProvider configProvider) {
Expand All @@ -51,7 +52,7 @@ public Main(ConfigProvider configProvider) {

public static void main(String[] args) throws ExecutionException, InterruptedException {

var main = new Main(new EnvConfigProvider("OIDC_SERVER", System::getenv));
var main = new Main(new EnvConfigProvider(CONFIG_PREFIX, System::getenv));
main.run();
}

Expand All @@ -64,13 +65,17 @@ public void run() throws ExecutionException, InterruptedException {

var validRedirectUris = loadAllowedRedirectUrls();

// TODO load from config
var baseUri = URI.create("https://t.oviva.io");

var supportedResponseTypes = List.of("code");
var baseUri =
configProvider
.get("base_uri")
.map(URI::create)
.orElseThrow(() -> new IllegalArgumentException("no 'base_uri' configured"));

var host = configProvider.get("host").orElse("0.0.0.0");
var port = getPortConfig();

var supportedResponseTypes = List.of("code");

var config = new RelyingPartyConfig(port, baseUri, supportedResponseTypes, validRedirectUris);

var keyStore = new KeyStore();
Expand All @@ -81,38 +86,56 @@ public void run() throws ExecutionException, InterruptedException {
// setup your environment, your own issuer MUST serve a _valid_ and _trusted_ entity
// configuration
// see: https://wiki.gematik.de/pages/viewpage.action?pageId=544316583
var fedmaster = URI.create("https://app-test.federationmaster.de");
var fedmaster =
configProvider
.get("federation_master")
.map(URI::create)
.orElse(URI.create("https://app-test.federationmaster.de"));

// TODO replace with `baseUri`
var federationIssuer = URI.create("https://idp-test.oviva.io/auth/realms/master/ehealthid");
var appName =
configProvider
.get("app_name")
.orElseThrow(() -> new IllegalArgumentException("missing 'app_name' configuration"));

var entityStatementTtl =
configProvider.get("es_ttl").map(Duration::parse).orElse(Duration.ofHours(1));

var authFlow = buildAuthFlow(baseUri, fedmaster, federationEncJwksPath);

var scopes =
configProvider
.get("scopes")
.or(
() ->
Optional.of(
"openid,urn:telematik:email,urn:telematik:versicherter,urn:telematik:display_name"))
.stream()
.flatMap(Strings::mustParseCommaList)
.toList();

var federationConfig =
FederationConfig.create()
.sub(baseUri)
.iss(baseUri)
.appName("Oviva Direkt")
.appName(appName)
.federationMaster(fedmaster)
.entitySigningKey(federationSigJwksPath.getKeys().get(0).toECKey())
.entitySigningKeys(federationSigJwksPath.toPublicJWKSet())
.relyingPartyEncKeys(federationEncJwksPath.toPublicJWKSet())

// TODO: bump up to hours, once we're confident it's correct ;)
// the spec says ~1 day
.ttl(Duration.ofMinutes(5))
.ttl(entityStatementTtl)
.scopes(scopes)
.redirectUris(List.of(baseUri.resolve("/auth/callback").toString()))
.build();

var instance =
SeBootstrap.start(
new App(config, federationConfig, sessionRepo, keyStore, tokenIssuer, authFlow),
Configuration.builder().host("0.0.0.0").port(config.port()).build())
Configuration.builder().host(host).port(config.port()).build())
.toCompletableFuture()
.get();

var localUri = instance.configuration().baseUri();
logger.atInfo().addKeyValue("local_addr", localUri).log("Magic at {}", config.baseUri());
logger.atInfo().log("Magic at {} ({})", config.baseUri(), localUri);

// wait forever
Thread.currentThread().join();
Expand Down Expand Up @@ -162,18 +185,9 @@ private AuthenticationFlow buildAuthFlow(URI selfIssuer, URI fedmaster, JWKSet e
}

private List<URI> loadAllowedRedirectUrls() {

var redirectUris =
configProvider.get("redirect_uris").stream()
.flatMap(Strings::mustParseCommaList)
.map(URI::create)
.toList();

if (!redirectUris.isEmpty()) {
return redirectUris;
}

// TODO: hardcoded
return List.of(URI.create("https://idp-test.oviva.io/auth/realms/master/broker/oidc/endpoint"));
return configProvider.get("redirect_uris").stream()
.flatMap(Strings::mustParseCommaList)
.map(URI::create)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public record FederationConfig(
JWKSet relyingPartyEncKeys,
Duration ttl,
List<String> redirectUris,
List<String> scopes,
String appName) {

public static Builder create() {
Expand All @@ -35,6 +36,7 @@ public static final class Builder {
private JWKSet relyingPartyEncKeys;
private Duration ttl;
private List<String> redirectUris;
private List<String> scopes;
private String appName;

public Builder() {}
Expand Down Expand Up @@ -84,6 +86,11 @@ public Builder appName(String appName) {
return this;
}

public Builder scopes(List<String> scopes) {
this.scopes = scopes;
return this;
}

public FederationConfig build() {
return new FederationConfig(
iss,
Expand All @@ -94,6 +101,7 @@ public FederationConfig build() {
relyingPartyEncKeys,
ttl,
redirectUris,
scopes,
appName);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,7 @@ public Response get() {
.idTokenSignedResponseAlg("ES256")
.idTokenEncryptedResponseAlg("ECDH-ES")
.idTokenEncryptedResponseEnc("A256GCM")
.scope(
String.join(
" ",
"openid",
"urn:telematik:email",
"urn:telematik:versicherter")) // add urn:telematik:display_name
.scope(String.join(" ", federationConfig.scopes()))
.redirectUris(federationConfig.redirectUris())
.build())
.federationEntity(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

class EnvFederationConfigProviderTest {

private static final String PREFIX = "OIDC_SERVER";
private static final String PREFIX = "EHEALTHID_RP";

static Stream<TC> mangleTestCases() {
return Stream.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ static void setUp() throws ExecutionException, InterruptedException, JOSEExcepti
.sub(ISSUER)
.redirectUris(List.of(ISSUER + "/callback"))
.appName("My App")
.scopes(List.of("openid", "email"))
.federationMaster(FEDMASTER)
.relyingPartyEncKeys(new JWKSet(encryptionKey))
.entitySigningKeys(new JWKSet(signatureKey))
Expand Down
2 changes: 1 addition & 1 deletion esgen/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
</dependencies>

<build>
<finalName>esgen</finalName>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
3 changes: 2 additions & 1 deletion esgen/src/main/java/com/oviva/ehealthid/esgen/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public class Main implements Callable<Integer> {

@Option(
names = {"-e", "--environment"},
description = "the environment to register for",
description =
"the environment to register for, either TU (Testumgebung), RU (Referenzumgebung) or PU (Produktivumgebung)",
defaultValue = "TU",
required = true)
private Environment environment;
Expand Down
5 changes: 1 addition & 4 deletions start.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#!/bin/bash

export OIDC_SERVER_FEDERATION_ENC_JWKS_PATH=enc_jwks.json
export OIDC_SERVER_FEDERATION_SIG_JWKS_PATH=sig_jwks.json

java -jar oidc-server/target/oidc-server-*-jar-with-dependencies.jar
java -jar ehealthid-rp/target/ehealthid-rp-jar-with-dependencies.jar

0 comments on commit 46397b4

Please sign in to comment.