WebEncryption.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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.stream.Stream;

import edu.iu.IuText;
import edu.iu.crypt.WebCryptoHeader.Param;
import edu.iu.crypt.WebKey.Algorithm;

/**
 * Unifies algorithm support and maps from JCE encryption to JSON Web Encryption
 * (JWE).
 * 
 * @see <a href="https://datatracker.ietf.org/doc/html/rfc7516">RFC 7516</a>
 */
public interface WebEncryption {

	/**
	 * Enumerates content encryption algorithms.
	 */
	enum Encryption {

		/**
		 * AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm.
		 * 
		 * @see <a href=
		 *      "https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.3">RFC-7518
		 *      Section 5.2.3</a>
		 */
		AES_128_CBC_HMAC_SHA_256("A128CBC-HS256", 256, "AES/CBC/PKCS5Padding", "HmacSHA256"),

		/**
		 * AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm.
		 * 
		 * @see <a href=
		 *      "https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.3">RFC-7518
		 *      Section 5.2.3</a>
		 */
		AES_192_CBC_HMAC_SHA_384("A192CBC-HS384", 384, "AES/CBC/PKCS5Padding", "HmacSHA384"),

		/**
		 * AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm.
		 * 
		 * @see <a href=
		 *      "https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.3">RFC-7518
		 *      Section 5.2.3</a>
		 */
		AES_256_CBC_HMAC_SHA_512("A256CBC-HS512", 512, "AES/CBC/PKCS5Padding", "HmacSHA512"),

		/**
		 * AES-128 GCM.
		 */
		A128GCM("A128GCM", 128, "AES/GCM/NoPadding", null),

		/**
		 * AES-192 GCM.
		 */
		A192GCM("A192GCM", 192, "AES/GCM/NoPadding", null),

		/**
		 * AES-256 GCM.
		 */
		A256GCM("A256GCM", 256, "AES/GCM/NoPadding", null);

		/**
		 * Selects encryption by JOSE enc parameter value.
		 * 
		 * @param enc JOSE parameter value
		 * @return encryption
		 */
		public static Encryption from(String enc) {
			return Stream.of(Encryption.values()).filter(a -> a.enc.equals(enc)).findFirst().get();
		}

		/**
		 * JOSE enc attribute value.
		 */
		public final String enc;

		/**
		 * CEK size, in bits.
		 */
		public final int size;

		/**
		 * JCE Cipher algorithm.
		 */
		public final String algorithm;

		/**
		 * JCE MAC algorithm.
		 */
		public final String mac;

		private Encryption(String enc, int size, String algorithm, String mac) {
			this.enc = enc;
			this.size = size;
			this.algorithm = algorithm;
			this.mac = mac;
		}
	}

	/**
	 * Prepares a new encrypted message.
	 */
	interface Builder {
		/**
		 * Protects all header parameters except jwk and verifies inputs are valid for
		 * JWE compact serialization.
		 * 
		 * @return this
		 */
		Builder compact();

		/**
		 * Defines standard protected header parameters.
		 * 
		 * @param params protected header parameters
		 * @return this
		 */
		Builder protect(Param... params);

		/**
		 * Defines extended protected header parameters.
		 * 
		 * @param params protected header parameter names
		 * @return this
		 */
		Builder protect(String... params);

		/**
		 * Provides additional authentication data for protecting the encrypted content.
		 * 
		 * @param additionalData additional authentication data
		 * @return this
		 */
		Builder aad(byte[] additionalData);

		/**
		 * Adds a new recipient.
		 * 
		 * @param algorithm key encryption algorithm
		 * @return {@link WebEncryptionRecipient.Builder}
		 */
		WebEncryptionRecipient.Builder<?> addRecipient(Algorithm algorithm);

		/**
		 * Encrypts data for sending to all recipients.
		 * 
		 * @param text data to encrypt
		 * @return encrypted message
		 */
		default WebEncryption encrypt(String text) {
			return encrypt(IuText.utf8(text));
		}

		/**
		 * Encrypts data for sending to all recipients.
		 * 
		 * @param data data to encrypt
		 * @return encrypted message
		 */
		default WebEncryption encrypt(byte[] data) {
			return encrypt(new ByteArrayInputStream(data));
		}

		/**
		 * Encrypts data for sending to all recipients.
		 * 
		 * @param in stream of data to encrypt
		 * @return encrypted message
		 */
		WebEncryption encrypt(InputStream in);
	}

	/**
	 * Starts a new encrypted message for a single recipient with
	 * {@link Builder#compact() compact semantics} and compression enabled.
	 * 
	 * @param encryption {@link Encryption content encryption algorithm}
	 * @param algorithm  {@link Algorithm key encryption algorithm}
	 * 
	 * @return {@link WebEncryptionRecipient.Builder}
	 */
	static WebEncryptionRecipient.Builder<?> to(Encryption encryption, Algorithm algorithm) {
		return builder(encryption, true).compact().addRecipient(algorithm);
	}

	/**
	 * Starts a new encrypted message with compression enabled.
	 * 
	 * @param encryption {@link Encryption encryption algorithm}
	 * 
	 * @return {@link Builder}
	 */
	static Builder builder(Encryption encryption) {
		return builder(encryption, true);
	}

	/**
	 * Starts a new encrypted message.
	 * 
	 * @param encryption {@link Encryption encryption algorithm}
	 * @param deflate    true to compress content; false to encrypt without
	 *                   compression
	 * 
	 * @return {@link Builder}
	 */
	static Builder builder(Encryption encryption, boolean deflate) {
		return Init.SPI.getJweBuilder(encryption, deflate);
	}

	/**
	 * Parses a compact or serialized JWE.
	 * 
	 * @param jwe compact or serialized JWE
	 * @return {@link WebEncryption}
	 */
	static WebEncryption parse(String jwe) {
		return Init.SPI.parseJwe(jwe);
	}

	/**
	 * Gets the encryption algorithm.
	 * 
	 * @return encryption algorithm
	 */
	Encryption getEncryption();

	/**
	 * Determines whether or not to compress content before encryption.
	 * 
	 * @return true to compress content before encrypting; false to encrypt as-is
	 */
	boolean isDeflate();

	/**
	 * Gets the recipients.
	 * 
	 * @return recipients
	 */
	Iterable<? extends WebEncryptionRecipient> getRecipients();

	/**
	 * Gets the iv JWE attribute
	 * 
	 * @return iv JWE attribute
	 */
	byte[] getInitializationVector();

	/**
	 * Gets the ciphertext JWE attribute
	 * 
	 * @return ciphertext JWE attribute
	 */
	byte[] getCipherText();

	/**
	 * Gets the tag JWE attribute
	 * 
	 * @return tag JWE attribute
	 */
	byte[] getAuthenticationTag();

	/**
	 * Gets the aad JWE attribute
	 * 
	 * @return aad JWE attribute
	 */
	byte[] getAdditionalData();

	/**
	 * Decrypts UTF-8 encoded encrypted content.
	 * 
	 * @param key private or secret key; <em>should</em> be verified by the
	 *            application as correct for the recipient before calling.
	 * @return decrypted content
	 */
	default String decryptText(WebKey key) {
		return IuText.utf8(decrypt(key));
	}

	/**
	 * Decrypts the encrypted content.
	 * 
	 * @param key private or secret key; <em>should</em> be verified by the
	 *            application as correct for the recipient before calling.
	 * @return decrypted content
	 */
	default byte[] decrypt(WebKey key) {
		final var out = new ByteArrayOutputStream();
		decrypt(key, out);
		return out.toByteArray();
	}

	/**
	 * Decrypts the encrypted content.
	 * 
	 * @param key private or secret key; <em>should</em> be verified by the
	 *            application as correct for the recipient before calling.
	 * @param out {@link OutputStream} to write the decrypted content to
	 */
	void decrypt(WebKey key, OutputStream out);

	/**
	 * Gets the message encrypted for only this recipient in compact JWE format.
	 * 
	 * @return compact JWE
	 */
	String compact();

	/**
	 * Gets the encrypted message in serialized JWE format.
	 * 
	 * @return serialized JWE
	 */
	@Override
	String toString();

}