Learn OIDC - Part 1 - JWS
Tags: java, jwt, oidc, diy
Published: 2024-02-23 10:43:21

---
title: Learn OIDC - Part 1 - JWS
published: true
date: 2024-02-20 23:00:00 UTC
tags: java,jwt,oidc,diy
series: Learn OIDC by implementing a basic OpenID Provider
canonical_url: http://coduinix.com/2024/02/21/oidc-jws
---

## Introduction

For a project I was working on, I was working a lot with OIDC-related technology.

Somewhere in the project there was the need for an easy to use and easy to configure OIDC server mock. We used Keycloak<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" rel="footnote">1</a></sup>, which is highly configurable, to act as an OIDC server for our end-to-end test needs. This worked quite well.

But at some point we needed something which would be easy to _mock_ for our (integration) tests. Googling around didn’t reveal any lightweight, easy to configure mock OIDC server, so I decided to try to build one myself.

**WARNING:** _The code shown in this series is for educational purposes only and is not meant to be used in production._

## JWS

One of the building blocks of OIDC is JWS (JSON Web Signature)<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" rel="footnote">2</a></sup>. JWS is the underlying structure for JWTs (JSON Web Tokens). JWT is the structure being used to transmit authorization in OAuth and user identity info in OIDC. We’ll dive into JWT in a later post.

But I thought that it would be a good idea to take a quick look at JWS first.

### Structure

JSON Web Signature is basically a digitally signed payload and a header describing the type of contents and the type of signature. The most common way to represent a JWS is _Compact Serialization_. The specification describes this as:

> BASE64URL(UTF8(JWS Protected Header)) || ‘.’ || BASE64URL(JWS Payload) || ‘.’ || BASE64URL(JWS Signature)

Where JWS Payload can be any kind of payload. This could be JSON but could also be some arbitrary byte-array.

The most common signature algorithms being used on examples on the web are `HS256` and `RS256` which stands _HMAC using SHA-256_ and _RSASSA-PKCS1-v1\_5 using SHA-256_ respectively. Implementing a JWS generation method could be done using standard Java APIs.

#### HS256

The following function generates a valid JWT compact representation for any `byte[]` payload using the provided secret key. This secret key is supposed to be shared between sender and recipient of the JWS.

```java
static final Base64.Encoder ENCODER = Base64.getUrlEncoder().withoutPadding();

public static String compactHS256(byte[] payload, SecretKey secretKey) throws GeneralSecurityException {
    var header = """
            {"alg": "HS256"}
            """;
    var encodedHeader = ENCODER.encodeToString(header.getBytes(StandardCharsets.UTF_8));
    var encodedPayload = ENCODER.encodeToString(payload);
    var signingInput = encodedHeader + "." + encodedPayload;

    var hmac = Mac.getInstance("HmacSHA256");
    hmac.init(secretKey);
    var signature = ENCODER.encodeToString(hmac.doFinal(signingInput.getBytes()));
    return signingInput + "." + signature;
}
```

#### RS256

Since the previous signature method (rs256) requires a shared secret to be shared between both parties, it might not be the best fit for implementing OIDC servers. A better approach would be to rely on (RSA) public/private key pairs. This could be achieved with the `rs256` algorithm. This implementation looks quite similar to the `hs256` one, but uses a `Signature` object instead of a `Mac` object. And of course the input parameter is an RSA privateKey instead of some shared secret.

```java
static final Base64.Encoder ENCODER = Base64.getUrlEncoder().withoutPadding();

public static String compactRS256(byte[] payload, RSAPrivateKey privateKey) throws GeneralSecurityException {
    var header = """
            {"alg": "RS256"}
            """;
    var encodedHeader = ENCODER.encodeToString(header.getBytes(StandardCharsets.UTF_8));
    var encodedPayload = ENCODER.encodeToString(payload);
    var signingInput = encodedHeader + "." + encodedPayload;

    var signer = Signature.getInstance("SHA256withRSA");
    signer.initSign(privateKey);
    signer.update(signingInput.getBytes());
    var signature = ENCODER.encodeToString(signer.sign());
    return signingInput + "." + signature;
}
```

### Example usage

The method above is quite easy to use. It requires a payload, let’s use the string `Hello World!` for this example. The most complex part would be to load the private key to use for the signing. For now let’s assume that the file is present in the file `signing.der`<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" rel="footnote">3</a></sup>.

```java
// Load private key
var keyFactory = KeyFactory.getInstance("RSA");
var keyFileName = "signing.der";
byte[] privateKeyBytes = Files.readAllBytes(new File(keyFileName).toPath());
var privateKey = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));

// Example payload
byte[] payload = "Hello World!".getBytes(StandardCharsets.UTF_8);

var compactJws = Jws.compactRS256(payload, privateKey);
System.out.println(compactJws);
```

This would print something like:

```java
eyJhbGciOiAiUlMyNTYifQo.SGVsbG8gV29ybGQh.<signature-bytes>
```

This `compactJws` could be sent to a receiving party which could verify the integrity of the message by checking the signature using the public key of the sender.

### Multiple keys

In a lot of systems keys tend to be rotated every now and then. This is also the case for signing keys for JWS.

To give recipients an indication of which key was used to sign the message, a special `kid` (Key ID) claim in the JWS header could be utilized. The `kid` is an identifier of the key that was used to sign the specific message.

_NOTE:_ The specification doesn’t say anything about how key ids map to a specific key. This has to be agreed upon by the sending and receiving side (typically chosen by the sender side).

One elegant way to communicate the mapping of `kid` to the public key is the use of `JWK Set`<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" rel="footnote">4</a></sup>. This JWK Set can list multiple ‘kid’ to public key mappings.

#### Add kid claim

Adding support for the `kid` claim to our JWS implementation is quite straightforward. The only thing that we need to change is the `header` variable:

```java
public static String compactRS256(byte[] payload, RSAPrivateKey privateKey, String kid) throws GeneralSecurityException {
    var header = """
            {"alg": "RS256","kid": "%s"}
            """.formatted(kid);
    var encodedHeader = ENCODER.encodeToString(header.getBytes(StandardCharsets.UTF_8));
    var encodedPayload = ENCODER.encodeToString(payload);
    var signingInput = encodedHeader + "." + encodedPayload;

    var signer = Signature.getInstance("SHA256withRSA");
    signer.initSign(privateKey);
    signer.update(signingInput.getBytes());
    var signature = ENCODER.encodeToString(signer.sign());
    return signingInput + "." + signature;
}
```

### Summary

We implemented a very bare-bones method to create valid JWS objects which basically contains a digitally signed payload. We looked at two separate mechanisms to sign the JWS: shared secret (`hs256`) en public/private key (`rs256`).

This should serve as a good starting point for implementing JWT on top of it. … Which will be a topic of an upcoming blog post.

1. Keycloak is an Open Source Identity and Access Management, see https://keycloak.org [↩](#fnref:1)

2. JWS Specification: [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515) [↩](#fnref:2)

3. The file `signing.der` could be generated using the openssl command: `openssl genrsa -outform der -out signing.der 2048 && openssl rsa -in signing.pem -outform DER -out signing.der` [↩](#fnref:3)

4. JSON Web Key Set: [RFC 7517 - Section 5](https://datatracker.ietf.org/doc/html/rfc7517#section-5) [↩](#fnref:4)