summaryrefslogtreecommitdiffstats
path: root/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java
blob: 81cf86a535e30e6932aae07651e51f621b825141 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.security;

import java.nio.ByteBuffer;
import java.util.Base64;

/**
 * A SealedSharedKey represents the public part of a secure one-way ephemeral key exchange.
 *
 * It is "sealed" in the sense that it is expected to be computationally infeasible
 * for anyone to derive the correct shared key from the sealed key without holding
 * the correct private key.
 *
 * A SealedSharedKey can be converted to--and from--an opaque string token representation.
 * This token representation is expected to be used as a convenient serialization
 * form when communicating shared keys.
 */
public record SealedSharedKey(int keyId, byte[] eciesPayload) {

    /** Current encoding version of opaque sealed key tokens. Must be less than 256. */
    public static final int CURRENT_TOKEN_VERSION = 1;

    /**
     * Creates an opaque URL-safe string token that contains enough information to losslessly
     * reconstruct the SealedSharedKey instance when passed verbatim to fromTokenString().
     */
    public String toTokenString() {
        if (keyId >= (1 << 24)) {
            throw new IllegalArgumentException("Key id is too large to be encoded");
        }

        ByteBuffer encoded = ByteBuffer.allocate(4 + eciesPayload.length);
        encoded.putInt((CURRENT_TOKEN_VERSION << 24) | keyId);
        encoded.put(eciesPayload);
        encoded.flip();

        byte[] encBytes = new byte[encoded.remaining()];
        encoded.get(encBytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(encBytes);
    }

    /**
     * Attempts to unwrap a SealedSharedKey opaque token representation that was previously
     * created by a call to toTokenString().
     */
    public static SealedSharedKey fromTokenString(String tokenString) {
        byte[] rawTokenBytes = Base64.getUrlDecoder().decode(tokenString);
        if (rawTokenBytes.length < 4) {
            throw new IllegalArgumentException("Decoded token too small to contain a header");
        }
        ByteBuffer decoded = ByteBuffer.wrap(rawTokenBytes);
        int versionAndKeyId = decoded.getInt();
        int version = versionAndKeyId >>> 24;
        if (version != CURRENT_TOKEN_VERSION) {
            throw new IllegalArgumentException("Token had unexpected version. Expected %d, was %d"
                                               .formatted(CURRENT_TOKEN_VERSION, version));
        }
        byte[] eciesPayload = new byte[decoded.remaining()];
        decoded.get(eciesPayload);

        int keyId = versionAndKeyId & 0xffffff;
        return new SealedSharedKey(keyId, eciesPayload);
    }

}