@Test
    void testSigning() {
        Clock clock = Clock.fixed(Instant.EPOCH, ZoneOffset.UTC);
        RequestSigner signer = new RequestSigner(ecPemPrivateKey, "myKey", clock);

        URI requestUri = URI.create("https://host:123/path//./../more%2fpath/?yes=no");
        HttpRequest.Builder builder = HttpRequest.newBuilder(requestUri);
        HttpRequest request = signer.signed(builder, Method.GET, InputStream::nullInputStream);

        // GET request with correct signature and URI as-is.
        RequestVerifier verifier = new RequestVerifier(ecPemPublicKey, clock);
        assertTrue(verifier.verify(Method.valueOf(request.method()),
                                   request.uri(),
                                   request.headers().firstValue("X-Timestamp").get(),
                                   request.headers().firstValue("X-Content-Hash").get(),
                                   request.headers().firstValue("X-Authorization").get()));

        // POST request with correct signature and URI normalized.
        MultiPartStreamer streamer = new MultiPartStreamer().addText("message", new String(message, UTF_8))
                                                            .addBytes("copy", message);
        request = signer.signed(builder.setHeader("Content-Type", streamer.contentType()), Method.POST, streamer::data);
        assertTrue(verifier.verify(Method.valueOf(request.method()),
                                   request.uri().normalize(),
                                   request.headers().firstValue("X-Timestamp").get(),
                                   request.headers().firstValue("X-Content-Hash").get(),
                                   request.headers().firstValue("X-Authorization").get()));

        // Wrong method.
        assertFalse(verifier.verify(Method.PATCH,
                                    request.uri().normalize(),
                                    request.headers().firstValue("X-Timestamp").get(),
                                    request.headers().firstValue("X-Content-Hash").get(),
                                    request.headers().firstValue("X-Authorization").get()));

        // Wrong path.
        assertFalse(verifier.verify(Method.valueOf(request.method()),
                                    request.uri().resolve("asdf"),
                                    request.headers().firstValue("X-Timestamp").get(),
                                    request.headers().firstValue("X-Content-Hash").get(),
                                    request.headers().firstValue("X-Authorization").get()));

        // Wrong timestamp.
        assertFalse(verifier.verify(Method.valueOf(request.method()),
                                    request.uri().normalize(),
                                    Instant.EPOCH.plusMillis(1).toString(),
                                    request.headers().firstValue("X-Content-Hash").get(),
                                    request.headers().firstValue("X-Authorization").get()));

        // Wrong content hash.
        assertFalse(verifier.verify(Method.valueOf(request.method()),
                                    request.uri().normalize(),
                                    request.headers().firstValue("X-Timestamp").get(),
                                    "Wrong/hash",
                                    request.headers().firstValue("X-Authorization").get()));

        // Wrong signature.
        assertFalse(verifier.verify(Method.valueOf(request.method()),
                                    request.uri().normalize(),
                                    request.headers().firstValue("X-Timestamp").get(),
                                    request.headers().firstValue("X-Content-Hash").get(),
                                    "Wrong/signature"));

        // Key pair mismatch.
        verifier = new RequestVerifier(otherEcPemPublicKey, clock);
        assertFalse(verifier.verify(Method.valueOf(request.method()),
                                    request.uri().normalize(),
                                    request.headers().firstValue("X-Timestamp").get(),
                                    request.headers().firstValue("X-Content-Hash").get(),
                                    request.headers().firstValue("X-Authorization").get()));

        // Too old request.
        verifier = new RequestVerifier(ecPemPublicKey, Clock.fixed(Instant.EPOCH.plusSeconds(301), ZoneOffset.UTC));
        assertFalse(verifier.verify(Method.valueOf(request.method()),
                                    request.uri().normalize(),
                                    request.headers().firstValue("X-Timestamp").get(),
                                    request.headers().firstValue("X-Content-Hash").get(),
                                    request.headers().firstValue("X-Authorization").get()));

        // Too new request.
        verifier = new RequestVerifier(ecPemPublicKey, Clock.fixed(Instant.EPOCH.minusSeconds(301), ZoneOffset.UTC));
        assertFalse(verifier.verify(Method.valueOf(request.method()),
                                    request.uri().normalize(),
                                    request.headers().firstValue("X-Timestamp").get(),
                                    request.headers().firstValue("X-Content-Hash").get(),
                                    request.headers().firstValue("X-Authorization").get()));

    }
Tests that messages can be signed and verified, and that the keys used for this can be parsed.

To generate appropriate keys, run the following commands:
<ol>
<li>{@code openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem}</li>
<li>{@code openssl ec -pubout -in private_key.pem -out public_key.pem}</li>
</ol>
Step 1 generates a private key and Step 2 extracts and writes the public key to a separate file.

@author jonmv
hosted-api/src/test/java/ai/vespa/hosted/api/SignaturesTest.java