From 70290819f83da07f76c2e5d70513a09ef6fd4f52 Mon Sep 17 00:00:00 2001 From: Jon Marius Venstad Date: Wed, 2 Oct 2019 17:46:18 +0200 Subject: Construct PublicKeys from serialised keys in controller --- .../java/ai/vespa/hosted/api/RequestSigner.java | 41 +++++++++++++++++++--- .../java/ai/vespa/hosted/api/RequestVerifier.java | 8 ++++- 2 files changed, 44 insertions(+), 5 deletions(-) (limited to 'hosted-api/src') diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/RequestSigner.java b/hosted-api/src/main/java/ai/vespa/hosted/api/RequestSigner.java index b2fd16b7975..5d314d90356 100644 --- a/hosted-api/src/main/java/ai/vespa/hosted/api/RequestSigner.java +++ b/hosted-api/src/main/java/ai/vespa/hosted/api/RequestSigner.java @@ -4,9 +4,10 @@ package ai.vespa.hosted.api; import com.yahoo.security.KeyUtils; import com.yahoo.security.SignatureUtils; -import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.http.HttpRequest; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.time.Clock; @@ -15,6 +16,7 @@ import java.util.function.Supplier; import static ai.vespa.hosted.api.Signatures.sha256Digest; import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA; +import static java.nio.charset.StandardCharsets.UTF_8; /** * Signs HTTP request headers using a private key, for verification by the indicated public key. @@ -25,6 +27,7 @@ public class RequestSigner { private final Signature signer; private final String keyId; + private final String base64PemPublicKey; private final Clock clock; /** Creates a new request signer from the given PEM encoded ECDSA key, with a public key with the given ID. */ @@ -34,8 +37,15 @@ public class RequestSigner { /** Creates a new request signer with a custom clock. */ public RequestSigner(String pemPrivateKey, String keyId, Clock clock) { - this.signer = SignatureUtils.createSigner(KeyUtils.fromPemEncodedPrivateKey(pemPrivateKey), SHA256_WITH_ECDSA); + this(KeyUtils.fromPemEncodedPrivateKey(pemPrivateKey), keyId, clock); + } + + /** Creates a new request signer with a custom clock. */ + public RequestSigner(PrivateKey privateKey, String keyId, Clock clock) { + this.signer = SignatureUtils.createSigner(privateKey, SHA256_WITH_ECDSA); this.keyId = keyId; + this.base64PemPublicKey = Base64.getEncoder().encodeToString(KeyUtils.toPem(KeyUtils.extractPublicKey(privateKey)).getBytes(UTF_8)); + PublicKey key = KeyUtils.extractPublicKey(privateKey); this.clock = clock; } @@ -44,12 +54,35 @@ public class RequestSigner { *
* The request builder's method and data are set to the given arguments, and a hash of the * content is computed and added to a header, together with other meta data, like the URI - * of the request, the current UTC time, and the id of the public key which shall be used to - * verify this signature. + * of the request, the current UTC time, and the id and value of the public key which shall + * be used to * verify this signature. * Finally, a signature is computed from these fields, based on the private key of this, and * added to the request as another header. */ public HttpRequest signed(HttpRequest.Builder request, Method method, Supplier data) { + try { + String timestamp = clock.instant().toString(); + String contentHash = Base64.getEncoder().encodeToString(sha256Digest(data::get)); + byte[] canonicalMessage = Signatures.canonicalMessageOf(method.name(), request.copy().build().uri(), timestamp, contentHash); + signer.update(canonicalMessage); + String signature = Base64.getEncoder().encodeToString(signer.sign()); + + request.setHeader("X-Timestamp", timestamp); + request.setHeader("X-Content-Hash", contentHash); + request.setHeader("X-Key-Id", keyId); + request.setHeader("X-Key", base64PemPublicKey); + request.setHeader("X-Authorization", signature); + + request.method(method.name(), HttpRequest.BodyPublishers.ofInputStream(data)); + return request.build(); + } + catch (SignatureException e) { + throw new IllegalArgumentException(e); + } + } + + // TODO jonmv: Simulates old clients — remove shortly (2 Oct 2019). + public HttpRequest legacySigned(HttpRequest.Builder request, Method method, Supplier data) { try { String timestamp = clock.instant().toString(); String contentHash = Base64.getEncoder().encodeToString(sha256Digest(data::get)); diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/RequestVerifier.java b/hosted-api/src/main/java/ai/vespa/hosted/api/RequestVerifier.java index 9d85ec9bf6b..5a6bea54bce 100644 --- a/hosted-api/src/main/java/ai/vespa/hosted/api/RequestVerifier.java +++ b/hosted-api/src/main/java/ai/vespa/hosted/api/RequestVerifier.java @@ -5,6 +5,7 @@ import com.yahoo.security.KeyUtils; import com.yahoo.security.SignatureUtils; import java.net.URI; +import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.time.Clock; @@ -31,7 +32,12 @@ public class RequestVerifier { /** Creates a new request verifier from the given PEM encoded ECDSA public key, with the given clock. */ public RequestVerifier(String pemPublicKey, Clock clock) { - this.verifier = SignatureUtils.createVerifier(KeyUtils.fromPemEncodedPublicKey(pemPublicKey), SHA256_WITH_ECDSA); + this(KeyUtils.fromPemEncodedPublicKey(pemPublicKey), clock); + } + + /** Creates a new request verifier from the given PEM encoded ECDSA public key, with the given clock. */ + public RequestVerifier(PublicKey publicKey, Clock clock) { + this.verifier = SignatureUtils.createVerifier(publicKey, SHA256_WITH_ECDSA); this.clock = clock; } -- cgit v1.2.3