Skip to content

Commit

Permalink
no export, params from record to class
Browse files Browse the repository at this point in the history
  • Loading branch information
wangweij committed May 17, 2024
1 parent 0cbd952 commit 652d63f
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 108 deletions.
24 changes: 13 additions & 11 deletions src/java.base/share/classes/com/sun/crypto/provider/HPKE.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[]
}

//@Override
protected SecretKey engineExport(byte[] context, String algorithm, int length) {
protected SecretKey engineExportKey(byte[] context, String algorithm, int length) {
if (state == BEGIN) {
throw new IllegalStateException("State: " + state);
} else {
Expand Down Expand Up @@ -269,7 +269,6 @@ private static class Impl {
HPKEParameterSpec params;
Context context;
AEAD aead;
PrivateKey sk;

byte[] suite_id;
HKDF hkdf;
Expand Down Expand Up @@ -344,11 +343,11 @@ public void init(Key key, HPKEParameterSpec p, SecureRandom rand)
}
checkMatch(pk, params.kem_id());
KEM.Encapsulator e;
if (p.kS() == null) {
if (p.authKey() == null) {
e = kem().newEncapsulator(pk, rand);
} else {
if (p.kS() instanceof PrivateKey skS) {
e = kem().newAuthEncapsulator(pk, skS, rand);
if (p.authKey() instanceof PrivateKey) {
throw new UnsupportedOperationException("auth mode not supported");
} else {
throw new InvalidAlgorithmParameterException("Cannot auth with public key");
}
Expand All @@ -363,11 +362,11 @@ public void init(Key key, HPKEParameterSpec p, SecureRandom rand)
checkMatch(sk, params.kem_id());
try {
KEM.Decapsulator d;
if (p.kS() == null) {
if (p.authKey() == null) {
d = kem().newDecapsulator(sk);
} else {
if (p.kS() instanceof PublicKey pkS) {
d = kem().newAuthDecapsulator(sk, pkS);
if (p.authKey() instanceof PublicKey) {
throw new UnsupportedOperationException("auth mode not supported");
} else {
throw new InvalidAlgorithmParameterException("Cannot auth with private key");
}
Expand All @@ -383,7 +382,7 @@ public void init(Key key, HPKEParameterSpec p, SecureRandom rand)
}

var usePSK = usePSK(params.psk(), params.psk_id());
int mode = params.kS() == null ? (usePSK ? 1 : 0) : (usePSK ? 3 : 2);
int mode = params.authKey() == null ? (usePSK ? 1 : 0) : (usePSK ? 3 : 2);
context = KeySchedule(mode, shared_secret,
params.info(),
params.psk(),
Expand Down Expand Up @@ -454,8 +453,11 @@ private void setParams(Key key, HPKEParameterSpec p)
default -> throw new InvalidAlgorithmParameterException();
};
int aead_id = 0x2;
params = new HPKEParameterSpec(kem_id, kdf_id, aead_id,
p.info(), p.psk(), p.psk_id(), p.kS(), p.encapsulation());
params = HPKEParameterSpec.of(kem_id, kdf_id, aead_id)
.info(p.info())
.psk(p.psk(), p.psk_id())
.authKey(p.authKey())
.encapsulation(p.encapsulation());
} else {
params = p;
}
Expand Down
211 changes: 153 additions & 58 deletions src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,60 +32,85 @@
import java.util.Objects;

/**
* Specifies the set of parameters used by a {@code Cipher} using the
* Specifies a set of parameters used by a {@code Cipher} for the
* Hybrid Public Key Encryption (HPKE) algorithm. HPKE is defined
* in https://datatracker.ietf.org/doc/rfc9180/.
* in https://datatracker.ietf.org/doc/rfc9180/. The <a href=
* "{@docRoot}/../specs/security/standard-names.html#cipher-algorithms">
* standard algorithm name</a> of the cipher is "HPKE".
* <p>
* In HPKE, The sender is always initialized with the recipient's public key
* in encrypt mode, and the recipient is always initialized with its own
* private key in decrypt mode.
* <p>
* An {@code HPKEParameterSpec} object can be provided at initialization.
* When the object is generated by the {@link #of()} method, the identifiers
* for the KEM, KDF, and AEAD algorithms used by HPKE will be determined by
* the type of key provided to the {@code init()} method. When the object
* is generated by {@link #of(int, int, int)}, the KEM, KDF, and AEAD
* algorithms are determined by the numeric identifiers provided. The
* identifiers must not be zero.
* The HPKE cipher initialization can include an optional
* {@code HPKEParameterSpec} object.
* <p>
* Any application-supplied information can be provided using the
* {@link #info(byte[])} method by both parties.
* An {@code HPKEParameterSpec} object can be created in two ways. The
* {@link #of()} method returns one whose identifiers for the KEM, KDF, and
* AEAD algorithms used by HPKE will be determined by the type of key provided
* to the {@code init()} method. The {@link #of(int, int, int)} method returns
* one whose KEM, KDF, and AEAD algorithms are determined by the numeric
* identifiers provided. The identifiers provided to this method must not be zero.
* <p>
* After an {@code HPKEParameterSpec} object is created, it can call several
* other methods to generate a new {@code HPKEParameterSpec} object with
* different features:
* <ul>
* <li>
* An application-supplied information can be provided using the
* {@link #info(byte[])} method by both parties.
* <li>
* If HPKE modes {@code mode_auth} or {@code mode_auth_psk} are used,
* the KEM keys for authentication must be provided using the
* {@link #auth(Key)} method. Precisely, the sender must call this method
* the keys for authentication must be provided using the
* {@link #authKey(Key)} method. Precisely, the sender must call this method
* with its own private key and the recipient must call it with the sender's
* public key.
* <p>
* <li>
* If HPKE modes {@code mode_psk} or {@code mode_auth_psk} are used,
* the pre-shared key for authentication and its identifier must be provided
* using the {@link #psk(SecretKey, byte[])} method by both parties.The key
* and the identifier must not be empty.
* <p>
* For successful interoperability, both parties need to supply identical
* {@code info}, {@code psk}, and {@code psk_id} if provided.
* <p>
* In an HPKE cipher, a shared secret is negotiated by the KEM step and a key
* <li>
* In HPKE, a shared secret is negotiated during the KEM step and a key
* encapsulation message must be transmitted from the sender to the recipient
* to recover this shared secret. On the sender side, the key encapsulation
* message can be retrieved using the {@link Cipher#getIV()} method after the
* cipher is initialized. There are two ways to provide the key encapsulation
* message to the recipient at initialization time. The recipient can either
* create an {@code HPKEParameterSpec} object and call its
* {@link #encapsulation(byte[])} method to embed the key encapsulation message
* inside the {@code HPKEParameterSpec} object. Or, it can use an
* {@link IvParameterSpec} object with the key encapsulation message as its
* initialization vector. Providing {@code new IvParameterSpec(encap)} is
* equivalent to providing {@code HPKEParameterSpec.of().encapsulation(encap)}
* when the recipient cipher is initialized.
* so that the recipient can recover this shared secret. On the sender side,
* the key encapsulation message can be retrieved using the {@link Cipher#getIV()}
* method after the cipher is initialized. On the recipient side, the key
* encapsulation message is provided using the {@link #encapsulation(byte[])}
* method.
* </ul>
* For successful interoperability, both parties need to supply identical
* {@code info}, {@code psk}, and {@code psk_id} or matching authentication
* keys if provided.
* <p>
* If the sender cipher is initialized without parameters, it assumes a
* default parameters object is used, which is equivalent to
* {@code HPKEParameterSpec.of()}. In this case, the cipher object always
* work in {@code mode_base} mode with an empty {@code info}. The recipient
* cipher must be initialized with either an {@code HPKEParameterSpec} object
* or an {@code IvParameterSpec} object.
* {@code HPKEParameterSpec.of()}. The recipient cipher can also be initialized
* with a {@code new IvParameterSpec(encap)} object, which is equivalent to
* {@code HPKEParameterSpec.of().encapsulation(encap)}. In either case, the
* cipher always work in {@code mode_base} mode with an empty {@code info}.
*
* <p>
* Example:
* {@snippet lang = java:
* var g = KeyPairGenerator.getInstance("X25519");
* var kp = g.generateKeyPair();
* var sender = Cipher.getInstance("HPKE");
* var ps = HPKEParameterSpec.of()
* .info("this_info".getBytes(StandardCharsets.UTF_8));
* sender.init(Cipher.ENCRYPT_MODE, kp.getPublic(), ps);
* var receiver = Cipher.getInstance("HPKE");
* var pr = HPKEParameterSpec.of()
* .info("this_info".getBytes(StandardCharsets.UTF_8))
* .encapsulation(sender.getIV());
* receiver.init(Cipher.DECRYPT_MODE, kp.getPrivate(), pr);
* var msg = "Hello World".getBytes(StandardCharsets.UTF_8);
* var ct = sender.doFinal(msg);
* var pt = receiver.doFinal(ct);
*
* assert Arrays.equals(msg, pt);
* }
*
* @implNote
* In the HPKE implementation in the SunJCE provider included in this JDK
* implementation, {@code HPKEParameterSpec.of()} chooses the following
Expand All @@ -99,23 +124,31 @@
* </ul>
* The aead_id is always 0x2. Other keys are not supported.
* <p>
* The {@code mode_auth} and {@code mode_auth_psk} modes are not supported yet
* since authenticated KEM is not supported yet. The {@code export} function
* of HPKE is not supported yet since {@code Cipher} does not have an export
* method.
*
* @param kem_id identifier for KEM, 0 if determined by key type
* @param kdf_id identifier for KDF, 0 if determined by key type
* @param aead_id identifier for AEAD, 0 if determined by key type
* @param info application-specific info, empty if none
* @param psk pre-shared key, null if none
* @param psk_id identifier for PSK, empty if none
* @param kS key for authentication, null if none
* @param encapsulation key encapsulation message, null if none
* The {@code mode_auth} and {@code mode_auth_psk} modes are not supported.
*/
public record HPKEParameterSpec(int kem_id, int kdf_id, int aead_id, byte[] info,
SecretKey psk, byte[] psk_id, Key kS, byte[] encapsulation)
implements AlgorithmParameterSpec {
public final class HPKEParameterSpec implements AlgorithmParameterSpec {

private final int kem_id; // 0 is determined by key later
private final int kdf_id; // 0 is determined by key later
private final int aead_id; // 0 is determined by key later
private final byte[] info; // never null, can be empty
private final SecretKey psk; // null if not used
private final byte[] psk_id; // never null, can be empty
private final Key kS; // null if not used
private final byte[] encapsulation; // null if none

// Note: this constructor does not clone array arguments.
private HPKEParameterSpec(int kem_id, int kdf_id, int aead_id, byte[] info,
SecretKey psk, byte[] psk_id, Key kS, byte[] encapsulation) {
this.kem_id = kem_id;
this.kdf_id = kdf_id;
this.aead_id = aead_id;
this.info = info;
this.psk = psk;
this.psk_id = psk_id;
this.kS = kS;
this.encapsulation = encapsulation;
}

/**
* A factory method to create an empty {@code HPKEParameterSpec} in
Expand Down Expand Up @@ -156,12 +189,14 @@ public static HPKEParameterSpec of(int kem_id, int kdf_id, int aead_id)
* {@code info} value.
*
* @param info application-specific info. Must not be {@code null}.
* The contents of the array are copied to protect
* against subsequent modification.
* @return a new {@code HPKEParameterSpec} object
* @throws NullPointerException if {@code info} is {@code null}
*/
public HPKEParameterSpec info(byte[] info) {
return new HPKEParameterSpec(kem_id, kdf_id, aead_id,
Objects.requireNonNull(info), psk, psk_id, kS, encapsulation);
Objects.requireNonNull(info).clone(), psk, psk_id, kS, encapsulation);
}

/**
Expand All @@ -170,7 +205,8 @@ public HPKEParameterSpec info(byte[] info) {
*
* @param psk pre-shared key. Set to {@code null} if no pre-shared key is used.
* @param psk_id identifier for PSK. Set to empty if no pre-shared key is used.
* Must not be {@code null}
* Must not be {@code null}. The contents of the array are copied
* to protect against subsequent modification.
* @return a new {@code HPKEParameterSpec} object
* @throws NullPointerException if {@code psk_id} is {@code null}
* @throws InvalidAlgorithmParameterException if {@code psk} and {@code psk_id} are
Expand All @@ -186,21 +222,24 @@ public HPKEParameterSpec psk(SecretKey psk, byte[] psk_id)
throw new InvalidAlgorithmParameterException("psk and psk_id do not match");
}
return new HPKEParameterSpec(kem_id, kdf_id, aead_id,
info, psk, psk_id, kS, encapsulation);
info, psk, psk_id.clone(), kS, encapsulation);
}

/**
* Creates a new {@code HPKEParameterSpec} object with a different
* key encapsulation message value that will be used by the recipient.
*
* @param encapsulation the key encapsulation message. If set to
* {@code null}, the previous key encapsulation
* message is cleared.
* {@code null}, the previous key encapsulation message is cleared.
* The contents of the array are copied to protect against
* subsequent modification.
*
* @return a new {@code HPKEParameterSpec} object
*/
public HPKEParameterSpec encapsulation(byte[] encapsulation) {
return new HPKEParameterSpec(kem_id, kdf_id, aead_id,
info, psk, psk_id, kS, encapsulation);
info, psk, psk_id, kS,
encapsulation == null ? null : encapsulation.clone());
}

/**
Expand All @@ -211,8 +250,64 @@ public HPKEParameterSpec encapsulation(byte[] encapsulation) {
* authentication key is cleared.
* @return a new {@code HPKEParameterSpec} object
*/
public HPKEParameterSpec auth(Key kS) {
public HPKEParameterSpec authKey(Key kS) {
return new HPKEParameterSpec(kem_id, kdf_id, aead_id,
info, psk, psk_id, kS, encapsulation);
}

/**
* {@return the identifier for KEM, 0 if determined by key type}
*/
public int kem_id() {
return kem_id;
}

/**
* {@return the identifier for KDF, 0 if determined by key type}
*/
public int kdf_id() {
return kdf_id;
}

/**
* {@return the identifier for AEAD, 0 if determined by key type}
*/
public int aead_id() {
return aead_id;
}

/**
* {@return a copy of the application-specific info, empty if none}
*/
public byte[] info() {
return info.clone();
}

/**
* {@return pre-shared key, {@code null} if none}
*/
public SecretKey psk() {
return psk;
}

/**
* {@return a copy of the identifier for PSK, empty if none}
*/
public byte[] psk_id() {
return psk_id.clone();
}

/**
* {@return the key for authentication, {@code null} if none}
*/
public Key authKey() {
return kS;
}

/**
* {@return a copy of the key encapsulation message, {@code null} if none}
*/
public byte[] encapsulation() {
return encapsulation == null ? null : encapsulation.clone();
}
}
Loading

0 comments on commit 652d63f

Please sign in to comment.