blob: c4ca9e1e183f28120f83824f5b682eb37130735b [file]
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.crypto.spec;
import javax.crypto.SecretKey;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Parameters for the combined Extract, Expand, or Extract-then-Expand
* operations of the HMAC-based Key Derivation Function (HKDF). The HKDF
* function is defined in <a href="http://tools.ietf.org/html/rfc5869">RFC
* 5869</a>.
* <p>
* In the Extract and Extract-then-Expand cases, users may call the {@code
* addIKM} and/or {@code addSalt} methods repeatedly (and chain these calls).
* This provides for use-cases where a portion of the input keying material
* (IKM) resides in a non-extractable {@code SecretKey} and the whole IKM
* cannot be provided as a single object. The same feature is available for
* salts.
* <p>
* The above feature is particularly useful for "labeled" HKDF Extract used in
* TLS 1.3 and HPKE, where the IKM consists of concatenated components, which
* may include both byte arrays and (possibly non-extractable) secret keys.
* <p>
* Examples:
* {@snippet lang = java:
* // this usage depicts the initialization of an HKDF-Extract AlgorithmParameterSpec
* AlgorithmParameterSpec derivationSpec =
* HKDFParameterSpec.ofExtract()
* .addIKM(label)
* .addIKM(ikm)
* .addSalt(salt).extractOnly();
*}
* {@snippet lang = java:
* // this usage depicts the initialization of an HKDF-Expand AlgorithmParameterSpec
* AlgorithmParameterSpec derivationSpec =
* HKDFParameterSpec.expandOnly(prk, info, 32);
*}
* {@snippet lang = java:
* // this usage depicts the initialization of an HKDF-ExtractExpand AlgorithmParameterSpec
* AlgorithmParameterSpec derivationSpec =
* HKDFParameterSpec.ofExtract()
* .addIKM(ikm)
* .addSalt(salt).thenExpand(info, 32);
*}
*
* @spec https://www.rfc-editor.org/info/rfc5869
* RFC 5869: HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
* @see javax.crypto.KDF
* @since 25
*/
public interface HKDFParameterSpec extends AlgorithmParameterSpec {
/**
* This {@code Builder} builds {@code Extract} and {@code ExtractThenExpand}
* objects.
* <p>
* The {@code Builder} is initialized via the {@code ofExtract} method of
* {@code HKDFParameterSpec}. As stated in the class description,
* {@code addIKM} and/or {@code addSalt} may be called as needed. Finally,
* an object is "built" by calling either {@code extractOnly} or
* {@code thenExpand} for {@code Extract} and {@code ExtractThenExpand}
* use-cases respectively. Note that the {@code Builder} is not
* thread-safe.
*/
final class Builder {
private List<SecretKey> ikms = new ArrayList<>();
private List<SecretKey> salts = new ArrayList<>();
private Builder() {}
/**
* Builds an {@code Extract} object from the current state of the
* {@code Builder}.
*
* @return an immutable {@code Extract} object
*/
public Extract extractOnly() {
return new Extract(ikms, salts);
}
/**
* Builds an {@code ExtractThenExpand} object from the current state of
* the {@code Builder}.
*
* @implNote HKDF implementations will enforce that the length
* is not greater than 255 * HMAC length. HKDF implementations
* will also enforce that a {code null} info value is treated as
* zero-length byte array.
*
* @param info
* the optional context and application specific information
* (may be {@code null}); the byte array is cloned to prevent
* subsequent modification
* @param length
* the length of the output keying material (must be greater
* than 0)
*
* @return an immutable {@code ExtractThenExpand} object
*
* @throws IllegalArgumentException
* if {@code length} is not greater than 0
*/
public ExtractThenExpand thenExpand(byte[] info, int length) {
return new ExtractThenExpand(
extractOnly(), info,
length);
}
/**
* Adds input keying material (IKM) to the builder.
* <p>
* Users may call {@code addIKM} multiple times when the input keying
* material value is to be assembled piece-meal or if part of the IKM is
* to be supplied by a hardware crypto device. The {@code ikms()}
* method of the {@code Extract} or {@code ExtractThenExpand} object
* that is subsequently built returns the assembled input keying
* material as a list of {@code SecretKey} objects.
*
* @param ikm
* the input keying material (IKM) value
*
* @return this builder
*
* @throws NullPointerException
* if the {@code ikm} argument is null
*/
public Builder addIKM(SecretKey ikm) {
Objects.requireNonNull(ikm, "ikm must not be null");
ikms.add(ikm);
return this;
}
/**
* Adds input keying material (IKM) to the builder. Note that an
* {@code ikm} byte array of length zero will be discarded.
* <p>
* Users may call {@code addIKM} multiple times when the input keying
* material value is to be assembled piece-meal or if part of the IKM is
* to be supplied by a hardware crypto device. The {@code ikms()}
* method of the {@code Extract} or {@code ExtractThenExpand} object
* that is subsequently built returns the assembled input keying
* material as a list of {@code SecretKey} objects.
*
* @param ikm
* the input keying material (IKM) value; the {@code ikm}
* byte array will be converted to a {@code SecretKeySpec},
* which means that the byte array will be cloned inside the
* {@code SecretKeySpec} constructor
*
* @return this builder
*
* @throws NullPointerException
* if the {@code ikm} argument is null
*/
public Builder addIKM(byte[] ikm) {
Objects.requireNonNull(ikm, "ikm must not be null");
if (ikm.length != 0) {
return addIKM(new SecretKeySpec(ikm, "Generic"));
} else {
return this;
}
}
/**
* Adds a salt to the builder.
* <p>
* Users may call {@code addSalt} multiple times when the salt value is
* to be assembled piece-meal or if part of the salt is to be supplied
* by a hardware crypto device. The {@code salts()} method of the
* {@code Extract} or {@code ExtractThenExpand} object that is
* subsequently built returns the assembled salt as a list of
* {@code SecretKey} objects.
*
* @param salt
* the salt value
*
* @return this builder
*
* @throws NullPointerException
* if the {@code salt} is null
*/
public Builder addSalt(SecretKey salt) {
Objects.requireNonNull(salt, "salt must not be null");
salts.add(salt);
return this;
}
/**
* Adds a salt to the builder. Note that a {@code salt} byte array of
* length zero will be discarded.
* <p>
* Users may call {@code addSalt} multiple times when the salt value is
* to be assembled piece-meal or if part of the salt is to be supplied
* by a hardware crypto device. The {@code salts()} method of the
* {@code Extract} or {@code ExtractThenExpand} object that is
* subsequently built returns the assembled salt as a list of
* {@code SecretKey} objects.
*
* @param salt
* the salt value; the {@code salt} byte array will be
* converted to a {@code SecretKeySpec}, which means that the
* byte array will be cloned inside the {@code SecretKeySpec}
* constructor
*
* @return this builder
*
* @throws NullPointerException
* if the {@code salt} is null
*/
public Builder addSalt(byte[] salt) {
Objects.requireNonNull(salt, "salt must not be null");
if (salt.length != 0) {
return addSalt(new SecretKeySpec(salt, "Generic"));
} else {
return this;
}
}
}
/**
* Returns a {@code Builder} for building {@code Extract} and
* {@code ExtractThenExpand} objects.
*
* @return a new {@code Builder}
*/
static Builder ofExtract() {
return new Builder();
}
/**
* Creates an {@code Expand} object.
*
* @implNote HKDF implementations will enforce that the length is
* not greater than 255 * HMAC length. Implementations will also
* enforce that the prk argument is at least as many bytes as the
* HMAC length. Implementations will also enforce that a {code null}
* info value is treated as zero-length byte array.
*
* @param prk
* the pseudorandom key (PRK); must not be {@code null}
* @param info
* the optional context and application specific information (may be
* {@code null}); the byte array is cloned to prevent subsequent
* modification
* @param length
* the length of the output keying material (must be greater than
* 0)
*
* @return an {@code Expand} object
*
* @throws NullPointerException
* if the {@code prk} argument is {@code null}
* @throws IllegalArgumentException
* if {@code length} is not greater than 0
*/
static Expand expandOnly(SecretKey prk, byte[] info, int length) {
if (prk == null) {
throw new NullPointerException("prk must not be null");
}
return new Expand(prk, info, length);
}
/**
* Defines the input parameters of an Extract operation as defined in <a
* href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>.
*/
final class Extract implements HKDFParameterSpec {
// HKDF-Extract(salt, IKM) -> PRK
private final List<SecretKey> ikms;
private final List<SecretKey> salts;
private Extract(List<SecretKey> ikms, List<SecretKey> salts) {
this.ikms = List.copyOf(ikms);
this.salts = List.copyOf(salts);
}
/**
* Returns an unmodifiable {@code List} of input keying material values
* in the order they were added. Returns an empty list if there are no
* input keying material values.
* <p>
* Input keying material values added by {@link Builder#addIKM(byte[])}
* are converted to a {@code SecretKeySpec} object. Empty arrays are
* discarded.
*
* @implNote An HKDF implementation should concatenate the input
* keying materials into a single value to be used in
* HKDF-Extract.
*
* @return the unmodifiable {@code List} of input keying material
* values
*/
public List<SecretKey> ikms() {
return ikms;
}
/**
* Returns an unmodifiable {@code List} of salt values in the order they
* were added. Returns an empty list if there are no salt values.
* <p>
* Salt values added by {@link Builder#addSalt(byte[])} are converted to
* a {@code SecretKeySpec} object. Empty arrays are discarded.
*
* @implNote An HKDF implementation should concatenate the salts
* into a single value to be used in HKDF-Extract.
*
* @return the unmodifiable {@code List} of salt values
*/
public List<SecretKey> salts() {
return salts;
}
}
/**
* Defines the input parameters of an Expand operation as defined in <a
* href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>.
*/
final class Expand implements HKDFParameterSpec {
// HKDF-Expand(PRK, info, L) -> OKM
private final SecretKey prk;
private final byte[] info;
private final int length;
/**
* Constructor that may be used to initialize an {@code Expand} object
*
* @param prk
* the pseudorandom key (PRK); in the case of
* {@code ExtractThenExpand}, the {@code prk} argument may be
* {@null} since the output of extract phase is used
* @param info
* the optional context and application specific information
* (may be {@code null}); the byte array is cloned to prevent
* subsequent modification
* @param length
* the length of the output keying material
*
* @throws IllegalArgumentException
* if {@code length} not greater than 0
*/
private Expand(SecretKey prk, byte[] info, int length) {
// a null prk argument could be indicative of ExtractThenExpand
this.prk = prk;
this.info = (info == null) ? null : info.clone();
if (!(length > 0)) {
throw new IllegalArgumentException("length must be > 0");
}
this.length = length;
}
/**
* Returns the pseudorandom key (PRK).
*
* @return the pseudorandom key
*/
public SecretKey prk() {
return prk;
}
/**
* Returns the optional context and application specific information.
*
* @return a clone of the optional context and application specific
* information, or {@code null} if not specified
*/
public byte[] info() {
return (info == null) ? null : info.clone();
}
/**
* Returns the length of the output keying material.
*
* @return the length of the output keying material
*/
public int length() {
return length;
}
}
/**
* Defines the input parameters of an Extract-then-Expand operation as
* defined in <a href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>.
*/
final class ExtractThenExpand implements HKDFParameterSpec {
private final Extract ext;
private final Expand exp;
/**
* Constructor that may be used to initialize an
* {@code ExtractThenExpand} object
*
* @param ext
* a pre-generated {@code Extract}
* @param info
* the optional context and application specific information
* (may be {@code null}); the byte array is cloned to prevent
* subsequent modification
* @param length
* the length of the output keying material
*
* @throws IllegalArgumentException
* if {@code length} is not greater than 0
*/
private ExtractThenExpand(Extract ext, byte[] info, int length) {
Objects.requireNonNull(ext, "Extract object must not be null");
this.ext = ext;
// - null prk argument is ok here (it's a signal)
// - {@code Expand} constructor can deal with a null info
// - length is checked in {@code Expand} constructor
this.exp = new Expand(null, info, length);
}
/**
* Returns an unmodifiable {@code List} of input keying material values
* in the order they were added. Returns an empty list if there are no
* input keying material values.
* <p>
* Input keying material values added by {@link Builder#addIKM(byte[])}
* are converted to a {@code SecretKeySpec} object. Empty arrays are
* discarded.
*
* @implNote An HKDF implementation should concatenate the input
* keying materials into a single value to be used in the
* HKDF-Extract phase.
*
* @return the unmodifiable {@code List} of input keying material
* values
*/
public List<SecretKey> ikms() {
return ext.ikms();
}
/**
* Returns an unmodifiable {@code List} of salt values in the order they
* were added. Returns an empty list if there are no salt values.
* <p>
* Salt values added by {@link Builder#addSalt(byte[])} are converted to
* a {@code SecretKeySpec} object. Empty arrays are discarded.
*
* @implNote An HKDF implementation should concatenate the salts
* into a single value to be used in the HKDF-Extract phase.
*
* @return the unmodifiable {@code List} of salt values
*
*/
public List<SecretKey> salts() {
return ext.salts();
}
/**
* Returns the optional context and application specific information.
*
* @return a clone of the optional context and application specific
* information, or {@code null} if not specified
*/
public byte[] info() {
return exp.info();
}
/**
* Returns the length of the output keying material.
*
* @return the length of the output keying material
*/
public int length() {
return exp.length();
}
}
}