All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.authlete.hms.fapi.FapiResourceResponseSigner Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2024 Authlete, Inc.
 *
 * 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
 *
 *     https://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 com.authlete.hms.fapi;


import java.net.URI;
import java.security.SignatureException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import com.authlete.hms.ComponentIdentifier;
import com.authlete.hms.ComponentIdentifierParameters;
import com.authlete.hms.SignatureBase;
import com.authlete.hms.SignatureBaseLine;
import com.authlete.hms.SignatureInfo;
import com.authlete.hms.SignatureMetadata;
import com.authlete.hms.SignatureMetadataParameters;
import com.authlete.hms.SignatureParamsLine;
import com.authlete.hms.impl.JoseHttpSigner;
import com.nimbusds.jose.jwk.JWK;


/**
 * A utility for signing a resource response in accordance with
 * the FAPI 2.0 Message Signing requirements.
 *
 * 

Sample Code

* *
 * // Create a signer.
 * FapiResourceResponseSigner signer = new FapiResourceResponseSigner()
 *         .setMethod("GET")
 *         .setTargetUri(URI.create("https://example.com/path?key=value"))
 *         .setStatus(200)
 *         .setResponseContentDigest(
 *             "sha-256=:RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=:"
 *         )
 *         .setCreated(Instant.now())
 *         .setSigningKey(JWK.parse(SIGNING_KEY))
 *         ;
 *
 * // Sign the HTTP response.
 * SignatureInfo info = signer.sign();
 *
 * // Signature HTTP field.
 * String signatureFieldValue = String.format("sig=%s", info.getSerializedSignature());
 *     // e.g. sig=:OXJQdFoyuYsbMfJHl/+bT8WwKv49Pt6fiYz/0bTQSAynaJH+HELTqZVzzm3/pyk/MPrjQ9iPmPxz8rgkkRe5kQ==:
 * responseBuilder.header("Signature", signatureFieldValue);
 *
 * // Signature-Input HTTP field.
 * String signatureInputFieldValue = String.format("sig=%s", info.getSerializedSignatureMetadata());
 *     // e.g. sig=("@method";req "@target-uri";req "@status" "content-digest");created=1729584639;keyid="snIZq-_NvzkKV-IdiM348BCz_RKdwmufnrPubsKKyio";tag="fapi-2-response"
 * responseBuilder.header("Signature-Input", signatureInputFieldValue);
 * 
* * @since 1.3 * * @see FAPI 2.0 Message Signing * * @see RFC 9421: HTTP Message Signatures */ public class FapiResourceResponseSigner { /** * The component identifier, {@code "@method";req}. */ private static final ComponentIdentifier COMP_ID_METHOD_REQ = new ComponentIdentifier("@method", new ComponentIdentifierParameters().setReq(true)); /** * The component identifier, {@code "@target-uri";req}. */ private static final ComponentIdentifier COMP_ID_TARGET_URI_REQ = new ComponentIdentifier("@target-uri", new ComponentIdentifierParameters().setReq(true)); /** * The component identifier, {@code "content-digest";req}. */ private static final ComponentIdentifier COMP_ID_CONTENT_DIGEST_REQ = new ComponentIdentifier("content-digest", new ComponentIdentifierParameters().setReq(true)); /** * The component identifier, {@code "@status"}. */ private static final ComponentIdentifier COMP_ID_STATUS = new ComponentIdentifier("@status"); /** * The component identifier, {@code "content-digest"}. */ private static final ComponentIdentifier COMP_ID_CONTENT_DIGEST = new ComponentIdentifier("content-digest"); /** * The value of the {@code tag} parameter of the signature metadata. */ private static final String TAG_VALUE_FAPI_2_RESPONSE = "fapi-2-response"; /** * The HTTP method of the request. */ private String method; /** * The target URI (the original request URL) of the HTTP request. */ private URI targetUri; /** * The value of the {@code Content-Digest} HTTP field of the request. */ private String requestContentDigest; /** * The value of the three-digit numeric HTTP status code of the response. */ private int status; /** * The value of the {@code Content-Digest} HTTP field of the response. */ private String responseContentDigest; /** * The value of the {@code created} parameter of the signature metadata. */ private Long created; /** * The private key for signing the signature base. */ private JWK signingKey; /** * Get the HTTP method of the request. This is used as the value of the * {@code "@method";req} derived component. * * @return * The HTTP method of the request. * * @see RFC 9421: HTTP Message Signatures, Section 2.2.1. Method */ public String getMethod() { return method; } /** * Set the HTTP method of the request. This is used as the value of the * {@code "@method";req} derived component. * *

* This must be set before calling the {@link #sign()} method. *

* * @param method * The HTTP method of the request. * * @return * {@code this} object. * * @see RFC 9421: HTTP Message Signatures, Section 2.2.1. Method */ public FapiResourceResponseSigner setMethod(String method) { this.method = method; return this; } /** * Get the target URI (the original request URL) of the HTTP request. * This is used as the value of the {@code "@target-uri";req} derived * component. * * @return * The target URI (the original request URL) of the HTTP request. * * @see RFC 9421: HTTP Message Signatures, Section 2.2.2. Target URI */ public URI getTargetUri() { return targetUri; } /** * Set the target URI (the original request URL) of the HTTP request. * This is used as the value of the {@code "@target-uri";req} derived * component. * *

* This must be set before calling the {@link #sign()} method. *

* * @param targetUri * The target URI (the original request URL) of the HTTP request. * * @return * {@code this} object. * * @see RFC 9421: HTTP Message Signatures, Section 2.2.2. Target URI */ public FapiResourceResponseSigner setTargetUri(URI targetUri) { this.targetUri = targetUri; return this; } /** * Get the value of the {@code Content-Digest} HTTP field of the request. * This is used as the value of the {@code "content-digest";req} component. * * @return * The value of the {@code Content-Digest} HTTP field of the request. * * @see RFC 9530: Digest Fields */ public String getRequestContentDigest() { return requestContentDigest; } /** * Set the value of the {@code Content-Digest} HTTP field of the request. * This is used as the value of the {@code "content-digest";req} component. * *

* If the HTTP request contains a request body, this must be set before * calling the {@link #sign()} method. *

* * @param contentDigest * The value of the {@code Content-Digest} HTTP field of the request. * * @return * {@code this} object. * * @see RFC 9530: Digest Fields */ public FapiResourceResponseSigner setRequestContentDigest(String contentDigest) { this.requestContentDigest = contentDigest; return this; } /** * Get the status code of the HTTP response. This is used as the value of * the {@code "@status"} derived component. * * @return * The status code of the HTTP response. * * @see RFC 9421: HTTP Message Signatures, Section 2.2.9. Status Code */ public int getStatus() { return status; } /** * Set the status code of the HTTP response. This is used as the value of * the {@code "@status"} derived component. * *

* This must be set before calling the {@link #sign()} method. *

* * @param status * The status code of the HTTP response. * * @return * {@code this} object. * * @see RFC 9421: HTTP Message Signatures, Section 2.2.9. Status Code */ public FapiResourceResponseSigner setStatus(int status) { this.status = status; return this; } /** * Get the value of the {@code Content-Digest} HTTP field of the response. * This is used as the value of the {@code "content-digest"} component. * * @return * The value of the {@code Content-Digest} HTTP field of the response. * * @see RFC 9530: Digest Fields */ public String getResponseContentDigest() { return responseContentDigest; } /** * Set the value of the {@code Content-Digest} HTTP field of the response. * This is used as the value of the {@code "content-digest"} component. * *

* If the HTTP response contains a request body, this must be set before * calling the {@link #sign()} method. *

* * @param contentDigest * The value of the {@code Content-Digest} HTTP field of the response. * * @return * {@code this} object. * * @see RFC 9530: Digest Fields */ public FapiResourceResponseSigner setResponseContentDigest(String contentDigest) { this.responseContentDigest = contentDigest; return this; } /** * Get the creation time of the signature, represented as seconds since * the Unix epoch. This is used as the value of the {@code created} * parameter of the signature metadata. * * @return * The creation time of the signature. * * @see RFC 9421: HTTP Message Signatures, Section 2.3. Signature Parameters */ public Long getCreated() { return created; } /** * Set the creation time of the signature, represented as seconds since * the Unix epoch. This is used as the value of the {@code created} * parameter of the signature metadata. * *

* If this is not set before calling the {@link #sign()} method, the * current time at which the method will be executed is used as the * signature creation time. *

* * @param created * The creation time of the signature. * * @return * {@code this} object. * * @see RFC 9421: HTTP Message Signatures, Section 2.3. Signature Parameters */ public FapiResourceResponseSigner setCreated(Long created) { this.created = created; return this; } /** * Set the creation time of the signature. This is used as the value of * the {@code created} parameter of the signature metadata. * *

* If this is not set before calling the {@link #sign()} method, the * current time at which the method will be executed is used as the * signature creation time. *

* * @param created * The creation time of the signature. * * @return * {@code this} object. * * @see RFC 9421: HTTP Message Signatures, Section 2.3. Signature Parameters */ public FapiResourceResponseSigner setCreated(Instant created) { Long timestamp = null; if (created != null) { // Seconds since the Unix epoch. timestamp = created.getEpochSecond(); } return setCreated(timestamp); } private Long getCreatedOrNow() { // The value of the 'created' parameter held by this instance. Long timestamp = getCreated(); // If the 'created' parameter has not been set. if (timestamp == null) { // The current time (= seconds since the Unix epoch). timestamp = Instant.now().getEpochSecond(); } return timestamp; } /** * Get the private key for signing the HTTP response. * * @return * The private key for signing the HTTP response. */ public JWK getSigningKey() { return signingKey; } /** * Set the private key for signing the HTTP response. * * @param signingKey * The private key for signing the HTTP response. * * @return * {@code this} object. */ public FapiResourceResponseSigner setSigningKey(JWK signingKey) { this.signingKey = signingKey; return this; } /** * Execute HTTP message signing. * * @return * Information about the signing operation, including the * computed signature base and the generated signature. * * @throws IllegalStateException * Mandatory input parameters, such as {@code method}, * {@code targetUri}, {@code status}, and {@code signingKey}, * are not set. * * @throws SignatureException * Signing failed. */ public SignatureInfo sign() throws IllegalStateException, SignatureException { // Check if input parameters have been properly set. checkParameters(); // Create the signature base. SignatureBase base = createSignatureBase(); // Sign the signature base with the specified signing key. byte[] signature = sign(base); // Collect information about the signing operation. SignatureInfo info = new SignatureInfo() .setSigningKey(getSigningKey()) .setSignatureBase(base) .setSignature(signature) ; return info; } private void checkParameters() { // method checkParameter("The HTTP method of the request", getMethod(), "setMethod(String)"); // targetUri checkParameter("The target URI (the original request URL) of the HTTP request", getTargetUri(), "setTargetUri(URI)"); // status checkParameterStatus(getStatus()); // signingKey checkParameter("A private key for signing the HTTP response", getSigningKey(), "setSigningKey(JWK)"); } private static void checkParameter( String parameterName, Object parameterValue, String setterMethod) { // If the parameter value is not null. if (parameterValue != null) { // OK. return; } throw new IllegalStateException(String.format( "%s must be set using the %s method before calling the sign() method.", parameterName, setterMethod)); } private static void checkParameterStatus(int status) { // If the status is a three-digit, positive integer. if (100 <= status && status < 1000) { // OK. return; } // The status code value is outside the valid range. // If it appears that neither of the setStatus methods has been called. if (status == 0) { throw new IllegalStateException( "The status code of the response must be set using the " + "setStatus(int) or setStatus(Instant) method before calling "+ "the sign() method."); } throw new IllegalStateException( "The status code of the response must be a three-digit, positive integer."); } private SignatureBase createSignatureBase() { // RFC 9421 HTTP Message Signatures // 2.5 Creating the Signature Base // // signature-base = *( signature-base-line LF ) signature-params-line // // signature-base-line's List baseLines = createSignatureBaseLines(); // signature-params-line SignatureParamsLine paramsLine = createSignatureParamsLine(); // signature-base return new SignatureBase(baseLines, paramsLine); } private List createSignatureBaseLines() { List baseLines = new ArrayList<>(); // FAPI 2.0 Message Signing // // shall cryptographically link the response to the request // by including the request method, request target-uri and // (if applicable) the request content-digest in the response // signature input by means of the req boolean flag defined // in Section 2.4 of [RFC9421]; // // "@method";req addBaseLine(baseLines, COMP_ID_METHOD_REQ, getMethod()); // "@target-uri";req addBaseLine(baseLines, COMP_ID_TARGET_URI_REQ, getTargetUri().toASCIIString()); if (getRequestContentDigest() != null) { // "content-digest";req addBaseLine(baseLines, COMP_ID_CONTENT_DIGEST_REQ, getRequestContentDigest()); } // FAPI 2.0 Message Signing // // shall include @status (the status code of the response) in // the signature; // // "@status" addBaseLine(baseLines, COMP_ID_STATUS, String.valueOf(getStatus())); // FAPI 2.0 Message Signing // // when the response contains a response body, shall include the // content-digest header as defined in [RFC9530] in the response, // and shall include that header in the signature, and should use // content-encoding agnostic digest methods (such as sha-256). // if (getResponseContentDigest() != null) { // "content-digest" addBaseLine(baseLines, COMP_ID_CONTENT_DIGEST, getResponseContentDigest()); } // The signature baselines. return baseLines; } private static void addBaseLine( List baseLines, ComponentIdentifier identifier, String value) { baseLines.add(new SignatureBaseLine(identifier, value)); } private SignatureParamsLine createSignatureParamsLine() { //----------------------------------------------------------------- // Component identifiers included in the signature params line. //----------------------------------------------------------------- List identifiers = new ArrayList<>(); // "@method";req identifiers.add(COMP_ID_METHOD_REQ); // "@target-uri";req identifiers.add(COMP_ID_TARGET_URI_REQ); if (getRequestContentDigest() != null) { // "content-digest";req identifiers.add(COMP_ID_CONTENT_DIGEST_REQ); } // "@status" identifiers.add(COMP_ID_STATUS); if (getResponseContentDigest() != null) { // "content-digest" identifiers.add(COMP_ID_CONTENT_DIGEST); } //----------------------------------------------------------------- // Parameters included in the signature params line. //----------------------------------------------------------------- SignatureMetadataParameters parameters = new SignatureMetadataParameters(); // FAPI 2.0 Message Signing // // shall include the created parameter (the signature creation time) // in the signature; // // created parameters.setCreated(getCreatedOrNow()); // keyid parameters.setKeyid(getSigningKey().getKeyID()); // FAPI 2.0 Message Signing // // shall include the tag parameter with a value of fapi-2-response // in the signature; // // tag parameters.setTag(TAG_VALUE_FAPI_2_RESPONSE); //----------------------------------------------------------------- // Signature metadata //----------------------------------------------------------------- SignatureMetadata metadata = new SignatureMetadata(identifiers, parameters); // The signature-params-line consists of the signature metadata. return new SignatureParamsLine(metadata); } private byte[] sign(SignatureBase base) throws SignatureException { // Sign the signature base using the specified signing key. return base.sign(new JoseHttpSigner(getSigningKey())); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy