io.helidon.security.provider.httpsign.OutboundTargetDefinition Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of helidon-security-provider-http-signature Show documentation
Show all versions of helidon-security-provider-http-signature Show documentation
HTTP Signatures authentication and outbound security provider
/*
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.security.provider.httpsign;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import io.helidon.common.pki.KeyConfig;
import io.helidon.config.Config;
/**
* Configuration of outbound target to sign outgoing requests.
*/
public class OutboundTargetDefinition {
private final String keyId;
private final String algorithm;
private final KeyConfig keyConfig;
private final HttpSignHeader header;
private final byte[] hmacSharedSecret;
private final SignedHeadersConfig signedHeadersConfig;
private OutboundTargetDefinition(Builder builder) {
this.keyId = builder.keyId;
this.algorithm = builder.algorithm;
this.keyConfig = builder.keyConfig;
this.header = builder.header;
this.hmacSharedSecret = builder.hmacSharedSecret;
this.signedHeadersConfig = builder.signedHeadersConfig;
Objects.requireNonNull(algorithm, "Signature algorithm must not be null");
Objects.requireNonNull(keyId, "Key id must not be null");
Objects.requireNonNull(header, "Header to use must not be null");
Objects.requireNonNull(signedHeadersConfig, "Configuration of how to sign headers must not be null");
if (HttpSignProvider.ALGORITHM_HMAC.equals(algorithm)) {
Objects.requireNonNull(hmacSharedSecret, "HMAC shared secret must not be null");
} else if (HttpSignProvider.ALGORITHM_RSA.equals(algorithm)) {
Objects.requireNonNull(keyConfig, "RSA Keys configuration must not be null");
}
}
/**
* Get a new builder .
*
* @param keyId keyId to send with signature
* @return builder instance
*/
public static Builder builder(String keyId) {
return new Builder().keyId(keyId);
}
/**
* Create a builder from configuration.
*
* @param config configuration located at this target, expects "key-id" to be a child
* @return builder instance
*/
public static Builder builder(Config config) {
return new Builder().fromConfig(config);
}
/**
* Create an instance from configuration.
*
* @param config configuration located at this outbound key, expects "key-id" to be a child
* @return new instance configured from config
*/
public static OutboundTargetDefinition fromConfig(Config config) {
return builder(config).build();
}
/**
* Key id of this service (will be mapped by target service to validate signature).
*
* @return key id string (may be an API key, key fingerprint, service name etc.)
*/
public String getKeyId() {
return keyId;
}
/**
* Algorithm used by this signature.
*
* @return algorithm
*/
public String getAlgorithm() {
return algorithm;
}
/**
* Private key configuration for RSA based algorithms.
*
* @return private key location and configuration or empty optional if not configured
*/
public Optional getKeyConfig() {
return Optional.ofNullable(keyConfig);
}
/**
* Shared secret for HMAC based algorithms.
*
* @return shared secret or empty optional if not configured
*/
public Optional getHmacSharedSecret() {
return Optional.ofNullable(hmacSharedSecret);
}
/**
* Header to store signature in.
*
* @return header type
*/
public HttpSignHeader getHeader() {
return header;
}
/**
* Configuration of method to headers to define headers to be signed.
*
* The following headers have special handling:
*
* - date - if not present and required, will be added to request
* - host - if not present and required, will be added to request from target URI
* - (request-target) - as per spec, calculated from method and path
* - authorization - if {@link #getHeader()} returns {@link HttpSignHeader#AUTHORIZATION} it is ignored
*
*
* @return configuration of headers to be signed
*/
public SignedHeadersConfig getSignedHeadersConfig() {
return signedHeadersConfig;
}
/**
* Fluent API builder to build {@link OutboundTargetDefinition} instances.
* Call {@link #build()} to create a new instance.
*/
public static class Builder implements io.helidon.common.Builder {
private String keyId;
private String algorithm;
private KeyConfig keyConfig;
private HttpSignHeader header = HttpSignHeader.SIGNATURE;
private byte[] hmacSharedSecret;
private SignedHeadersConfig signedHeadersConfig = HttpSignProvider.DEFAULT_REQUIRED_HEADERS;
private Builder() {
}
/**
* Key id of this service (will be mapped by target service to validate signature).
*
* @param keyId key id mapped by target service
* @return updated builder instance
*/
public Builder keyId(String keyId) {
this.keyId = keyId;
return this;
}
/**
* Header to store signature in.
*
* @param header header type
* @return updated builder instance
*/
public Builder header(HttpSignHeader header) {
this.header = header;
return this;
}
/**
* Algorithm used by this signature.
* Set automatically on call to methods {@link #privateKeyConfig(KeyConfig)} and {@link #hmacSecret(byte[])}.
*
* @param algorithm algorithm to use for outbound signatures
* @return updated builder instance
*/
public Builder algorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
/**
* Private key configuration for RSA based algorithms.
* If called sets the algorithm to "rsa-sha256". Expects either explicit private key, or keystore and private key
* alias.
*
* @param keyConfig private key configuration for outbound signatures
* @return updated builder instance
*/
public Builder privateKeyConfig(KeyConfig keyConfig) {
if (null == algorithm) {
algorithm = HttpSignProvider.ALGORITHM_RSA;
}
// make sure this is a private key (signature of outbound requests)
keyConfig.getPrivateKey()
.orElseThrow(() -> new HttpSignatureException("Configuration must contain a private key"));
this.keyConfig = keyConfig;
return this;
}
/**
* Configuration of required and "if-present" headers to be signed for this target.
* Defaults to the same as {@link HttpSignProvider.Builder#inboundRequiredHeaders(SignedHeadersConfig)}.
*
* @param config configuration of outbound headers to be signed for each method.
* @return updated builder instance
*/
public Builder signedHeaders(SignedHeadersConfig config) {
this.signedHeadersConfig = config;
return this;
}
/**
* Shared secret for HMAC based algorithms.
* Also sets the algorithm to "hmac-sha256"
*
* @param secret secret to sign outgoing requests (symmetric)
* @return updated builder instance
*/
public Builder hmacSecret(byte[] secret) {
if (null == algorithm) {
algorithm = HttpSignProvider.ALGORITHM_HMAC;
}
this.hmacSharedSecret = Arrays.copyOf(secret, secret.length);
return this;
}
/**
* Shared secret for HMAC based algorithms.
* Calls {@link #hmacSecret(byte[])} getting bytes of the secret string with UTF-8.
*
* @param secret shared secret to sign outgoing requests
* @return updated builder instance
*/
public Builder hmacSecret(String secret) {
return hmacSecret(secret.getBytes(StandardCharsets.UTF_8));
}
@Override
public OutboundTargetDefinition build() {
return new OutboundTargetDefinition(this);
}
/**
* Update this builder instance from configuration.
*
* @param config config instance
* @return updated builder instance
*/
public Builder fromConfig(Config config) {
Builder builder = new Builder();
// mandatory
builder.keyId(config.get("key-id").asString());
config.get("header").asOptional(HttpSignHeader.class).ifPresent(builder::header);
config.get("sign-headers").asOptional(SignedHeadersConfig.class).ifPresent(builder::signedHeaders);
config.get("private-key").asOptional(KeyConfig.class).ifPresent(builder::privateKeyConfig);
config.get("hmac.secret").value().ifPresent(builder::hmacSecret);
// last, as we configure defaults based on configuration
config.get("algorithm").value().ifPresent(builder::algorithm);
return builder;
}
}
}