WebCertificateReference.java

/*
 * Copyright © 2025 Indiana University
 * All rights reserved.
 *
 * BSD 3-Clause License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * - Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * 
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * 
 * - Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package edu.iu.crypt;

import java.io.InputStream;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.Arrays;

import javax.net.ssl.X509TrustManager;

import edu.iu.IuDigest;
import edu.iu.IuException;

/**
 * Common super-interface for components that hold a reference to a web
 * certificate and/or chain.
 */
public interface WebCertificateReference {

	/**
	 * Defines basic verification rules for objects that define a certificate
	 * reference.
	 * 
	 * <ul>
	 * <li>Hard reference to cert chain is used if provided; URI is ignored</li>
	 * <li>URI is referenced and parsed if provided, and hard reference is not</li>
	 * <li>SHA-1 and SHA-256 are verified against the first cert found either by
	 * hard reference or URI</li>
	 * </ul>
	 * 
	 * <p>
	 * Further verification, i.e., via {@link X509TrustManager}, is not handled by
	 * this library and <em>should</em> be handled according to the application's
	 * trust configuration.
	 * </p>
	 * 
	 * @param reference certificate reference
	 * @return resolved and verified {@link X509Certificate} chain, null if not
	 *         populated
	 */
	@SuppressWarnings("deprecation")
	static X509Certificate[] verify(WebCertificateReference reference) {
		var certificateChain = reference.getCertificateChain();
		if (certificateChain == null) {
			final var certificateUri = reference.getCertificateUri();
			if (certificateUri != null)
				certificateChain = PemEncoded.getCertificateChain(certificateUri);
		}

		if (certificateChain != null) {
			if (certificateChain.length < 1)
				throw new IllegalArgumentException("At least one certificate is required");
			final var cert = certificateChain[0];

			final var certificateThumbprint = reference.getCertificateThumbprint();
			if (certificateThumbprint != null //
					&& !Arrays.equals(certificateThumbprint, IuDigest.sha1(IuException.unchecked(cert::getEncoded))))
				throw new IllegalArgumentException("Certificate SHA-1 thumbprint mismatch");

			final var certificateSha256Thumbprint = reference.getCertificateSha256Thumbprint();
			if (certificateSha256Thumbprint != null //
					&& !Arrays.equals(certificateSha256Thumbprint,
							IuDigest.sha256(IuException.unchecked(cert::getEncoded))))
				throw new IllegalArgumentException("Certificate SHA-256 thumbprint mismatch");
		}

		return certificateChain;
	}

	/**
	 * Builder interface for creating {@link WebCertificateReference} instances.
	 * 
	 * @param <B> builder type
	 */
	interface Builder<B extends Builder<B>> {
		/**
		 * Sets the URI where X.509 certificate associated with this key can be
		 * retrieved.
		 * 
		 * <p>
		 * The URI will be validated and resolved when this method is invoked. To ensure
		 * dependency on a remote URI won't impact application startup, always store
		 * certificates locally and use {@link #cert(X509Certificate...)} instead of
		 * this method for critical initialization in production environments.
		 * </p>
		 * 
		 * @param uri {@link URI}
		 * @return this
		 */
		B cert(URI uri);

		/**
		 * Sets the URI where X.509 certificate associated with this key can be
		 * retrieved.
		 * 
		 * @param chain one or more {@link X509Certificate}s
		 * @return this
		 */
		B cert(X509Certificate... chain);

		/**
		 * Sets the certificate thumbprint.
		 * 
		 * @param certificateThumbprint JSON x5t attribute value
		 * @return this
		 */
		B x5t(byte[] certificateThumbprint);

		/**
		 * Sets the certificate SHA-256 thumbprint.
		 * 
		 * @param certificateSha256Thumbprint JSON x5t attribute value
		 * @return this
		 */
		B x5t256(byte[] certificateSha256Thumbprint);

		/**
		 * Sets key data from potentially concatenated PEM-encoded input.
		 * 
		 * @param pemEncoded {@link InputStream} of PEM encoded key data, potentially
		 *                   concatenated
		 * @return this
		 */
		B pem(InputStream pemEncoded);

		/**
		 * Sets key data from potentially concatenated PEM-encoded input.
		 * 
		 * @param pemEncoded potentially concatenated PEM encoded key data
		 * @return this
		 */
		B pem(String pemEncoded);
	}

	/**
	 * Gets the URI where X.509 certificate associated with this key can be
	 * retrieved.
	 * 
	 * <p>
	 * The protocol used to acquire the resource MUST provide integrity protection;
	 * an HTTP GET request to retrieve the certificate MUST use TLS [RFC2818]
	 * [RFC5246]; the identity of the server MUST be validated, as per Section 6 of
	 * RFC 6125 [RFC6125].
	 * </p>
	 * 
	 * @return {@link URI}
	 */
	default URI getCertificateUri() {
		return null;
	}

	/**
	 * Gets the certificate chain.
	 * 
	 * @return parsed JSON x5c attribute value
	 */
	default X509Certificate[] getCertificateChain() {
		return null;
	}

	/**
	 * Gets the certificate thumbprint.
	 * 
	 * @return JSON x5t attribute value
	 */
	default byte[] getCertificateThumbprint() {
		return null;
	}

	/**
	 * Gets the certificate SHA-256 thumbprint.
	 * 
	 * @return JSON x5t#S256 attribute value
	 */
	default byte[] getCertificateSha256Thumbprint() {
		return null;
	}

}