diff options
author | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-10-19 12:40:34 +0200 |
---|---|---|
committer | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-10-19 12:40:34 +0200 |
commit | 82c8d614762c3e4bb0abc14148a1fba1ca3182e5 (patch) | |
tree | bcbd539039e4e0b3ed4c35f41959eecb54994fbd /security-utils | |
parent | 9bd0a86bba6280aded2ff575ba095a446d6aa4e7 (diff) |
Add X25519 private to public key extraction and use for HPKE opening
Avoids the need to pass the full key pair when opening a sealed piece
of ciphertext, since we can just extract the public key on-demand.
Uses BouncyCastle X25519 utils under the hood.
Diffstat (limited to 'security-utils')
6 files changed, 49 insertions, 25 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/KeyUtils.java b/security-utils/src/main/java/com/yahoo/security/KeyUtils.java index 9fe64baa80a..cef0dd9a62e 100644 --- a/security-utils/src/main/java/com/yahoo/security/KeyUtils.java +++ b/security-utils/src/main/java/com/yahoo/security/KeyUtils.java @@ -13,6 +13,7 @@ import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.FixedPointCombMultiplier; +import org.bouncycastle.math.ec.rfc7748.X25519; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; @@ -317,6 +318,14 @@ public class KeyUtils { } } + // TODO unify with extractPublicKey() + public static XECPublicKey extractX25519PublicKey(XECPrivateKey privateKey) { + byte[] privScalar = toRawX25519PrivateKeyBytes(privateKey); + byte[] pubPoint = new byte[X25519.POINT_SIZE]; + X25519.generatePublicKey(privScalar, 0, pubPoint, 0); // scalarMultBase => public key point + return fromRawX25519PublicKey(pubPoint); + } + /** * Computes a shared secret using the Elliptic Curve Diffie-Hellman (ECDH) protocol for X25519 curves. * <p> diff --git a/security-utils/src/main/java/com/yahoo/security/hpke/DHKemX25519HkdfSha256.java b/security-utils/src/main/java/com/yahoo/security/hpke/DHKemX25519HkdfSha256.java index 430a9d57097..8f6dffcb9c2 100644 --- a/security-utils/src/main/java/com/yahoo/security/hpke/DHKemX25519HkdfSha256.java +++ b/security-utils/src/main/java/com/yahoo/security/hpke/DHKemX25519HkdfSha256.java @@ -118,18 +118,13 @@ final class DHKemX25519HkdfSha256 implements Kem { * shared_secret = ExtractAndExpand(dh, kem_context) * return shared_secret * </pre> - * - * Implementation note: we take in the key pair to avoid needing to compute the public key (TODO!) */ @Override - public byte[] decap(byte[] enc, KeyPair kpR) { + public byte[] decap(byte[] enc, XECPrivateKey skR) { var pkE = deserializePublicKey(enc); - - var skR = (XECPrivateKey)kpR.getPrivate(); - var pkR = (XECPublicKey)kpR.getPublic(); byte[] dh = KeyUtils.ecdh(skR, pkE); - byte[] pkRm = serializePublicKey(pkR); + byte[] pkRm = serializePublicKey(KeyUtils.extractX25519PublicKey(skR)); byte[] kemContext = concat(enc, pkRm); return extractAndExpand(dh, kemContext); diff --git a/security-utils/src/main/java/com/yahoo/security/hpke/Hpke.java b/security-utils/src/main/java/com/yahoo/security/hpke/Hpke.java index e3f233285a8..133798faa99 100644 --- a/security-utils/src/main/java/com/yahoo/security/hpke/Hpke.java +++ b/security-utils/src/main/java/com/yahoo/security/hpke/Hpke.java @@ -2,6 +2,7 @@ package com.yahoo.security.hpke; import java.security.KeyPair; +import java.security.interfaces.XECPrivateKey; import java.security.interfaces.XECPublicKey; import java.util.Arrays; @@ -246,11 +247,9 @@ public final class Hpke { * return KeyScheduleR(mode_base, shared_secret, info, * default_psk, default_psk_id) * </pre> - * - * TODO only take private key, not key pair. Need functionality for X25519 priv -> pub extraction first. */ - ContextR setupBaseR(byte[] enc, KeyPair kpR, byte[] info) { - byte[] sharedSecret = kem.decap(enc, kpR); + ContextR setupBaseR(byte[] enc, XECPrivateKey skR, byte[] info) { + byte[] sharedSecret = kem.decap(enc, skR); return new ContextR(keySchedule(MODE_BASE, sharedSecret, info, DEFAULT_PSK, DEFAULT_PSK_ID)); } @@ -310,8 +309,8 @@ public final class Hpke { * return pt * </pre> */ - public byte[] openBase(byte[] enc, KeyPair kpR, byte[] info, byte[] aad, byte[] ct) { - var ctx = setupBaseR(enc, kpR, info); + public byte[] openBase(byte[] enc, XECPrivateKey skR, byte[] info, byte[] aad, byte[] ct) { + var ctx = setupBaseR(enc, skR, info); var base = ctx.base; // TODO wrap any exceptions in OpenError et al? return aead.open(base.key(), base.nonce(), aad, ct); diff --git a/security-utils/src/main/java/com/yahoo/security/hpke/Kem.java b/security-utils/src/main/java/com/yahoo/security/hpke/Kem.java index 7bbb2df0960..99c019a9d0b 100644 --- a/security-utils/src/main/java/com/yahoo/security/hpke/Kem.java +++ b/security-utils/src/main/java/com/yahoo/security/hpke/Kem.java @@ -4,6 +4,7 @@ package com.yahoo.security.hpke; import com.yahoo.security.KeyUtils; import java.security.KeyPair; +import java.security.interfaces.XECPrivateKey; import java.security.interfaces.XECPublicKey; /** @@ -30,10 +31,8 @@ public interface Kem { * "Deterministic algorithm using the private key <code>skR</code> to recover the * ephemeral symmetric key (the KEM shared secret) from its encapsulated * representation <code>enc</code>." - * - * TODO just take skR instead of entire key pair */ - byte[] decap(byte[] enc, KeyPair kpR); + byte[] decap(byte[] enc, XECPrivateKey skR); /** The length in bytes of a KEM shared secret produced by this KEM. */ short nSecret(); diff --git a/security-utils/src/test/java/com/yahoo/security/HpkeTest.java b/security-utils/src/test/java/com/yahoo/security/HpkeTest.java index 24944759c5c..4455eade7cd 100644 --- a/security-utils/src/test/java/com/yahoo/security/HpkeTest.java +++ b/security-utils/src/test/java/com/yahoo/security/HpkeTest.java @@ -9,6 +9,7 @@ import com.yahoo.security.hpke.Kem; import org.junit.jupiter.api.Test; import java.security.KeyPair; +import java.security.interfaces.XECPrivateKey; import java.security.interfaces.XECPublicKey; import static com.yahoo.security.ArrayUtils.hex; @@ -35,6 +36,14 @@ public class HpkeTest { return new KeyPair(pub, priv); } + private static XECPublicKey pk(KeyPair kp) { + return (XECPublicKey) kp.getPublic(); + } + + private static XECPrivateKey sk(KeyPair kp) { + return (XECPrivateKey) kp.getPrivate(); + } + /** * https://www.rfc-editor.org/rfc/rfc9180.html test vector * @@ -55,7 +64,7 @@ public class HpkeTest { var ciphersuite = Ciphersuite.of(kem, Kdf.hkdfSha256(), Aead.aesGcm128()); var hpke = Hpke.of(ciphersuite); - var s = hpke.sealBase((XECPublicKey) kpR.getPublic(), info, aad, pt); + var s = hpke.sealBase(pk(kpR), info, aad, pt); // The "enc" output is the ephemeral public key var expectedEnc = "37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431"; @@ -65,7 +74,7 @@ public class HpkeTest { "6d8770ac83d07bea87e13c512a"; assertEquals(expectedCiphertext, hex(s.ciphertext())); - byte[] openedPt = hpke.openBase(s.enc(), kpR, info, aad, s.ciphertext()); + byte[] openedPt = hpke.openBase(s.enc(), sk(kpR), info, aad, s.ciphertext()); assertEquals(hex(pt), hex(openedPt)); } @@ -78,12 +87,12 @@ public class HpkeTest { var hpke = Hpke.of(Ciphersuite.defaultSuite()); - var s1 = hpke.sealBase((XECPublicKey) kpR.getPublic(), info, aad, pt); - byte[] openedPt = hpke.openBase(s1.enc(), kpR, info, aad, s1.ciphertext()); + var s1 = hpke.sealBase(pk(kpR), info, aad, pt); + byte[] openedPt = hpke.openBase(s1.enc(), sk(kpR), info, aad, s1.ciphertext()); assertEquals(hex(pt), hex(openedPt)); - var s2 = hpke.sealBase((XECPublicKey) kpR.getPublic(), info, aad, pt); - openedPt = hpke.openBase(s2.enc(), kpR, info, aad, s2.ciphertext()); + var s2 = hpke.sealBase(pk(kpR), info, aad, pt); + openedPt = hpke.openBase(s2.enc(), sk(kpR), info, aad, s2.ciphertext()); assertEquals(hex(pt), hex(openedPt)); assertNotEquals(hex(s1.enc()), hex(s2.enc())); // This is the ephemeral public key @@ -97,13 +106,13 @@ public class HpkeTest { var kpR = receiverRrfc9180TestVectorKeyPair(); var hpke = Hpke.of(Ciphersuite.defaultSuite()); - var s = hpke.sealBase((XECPublicKey) kpR.getPublic(), info, aad, pt); + var s = hpke.sealBase(pk(kpR), info, aad, pt); byte[] badInfo = toUtf8Bytes("lesser info"); // TODO better exception classes! Triggers AEAD auth tag mismatch behind the scenes - assertThrows(RuntimeException.class, () -> hpke.openBase(s.enc(), kpR, badInfo, aad, s.ciphertext())); + assertThrows(RuntimeException.class, () -> hpke.openBase(s.enc(), sk(kpR), badInfo, aad, s.ciphertext())); byte[] badAad = toUtf8Bytes("non-groovy AAD"); - assertThrows(RuntimeException.class, () -> hpke.openBase(s.enc(), kpR, info, badAad, s.ciphertext())); + assertThrows(RuntimeException.class, () -> hpke.openBase(s.enc(), sk(kpR), info, badAad, s.ciphertext())); } } diff --git a/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java b/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java index fbf27b67f4b..97fd8ce9e41 100644 --- a/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java +++ b/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java @@ -170,4 +170,17 @@ public class KeyUtilsTest { assertEquals(pubHex, xecHexFromPub(pub2)); } + @Test + void can_extract_public_key_from_x25519_private_key() { + var priv = xecPrivFromHex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"); + var expectedPubHex = "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"; + var pub = KeyUtils.extractX25519PublicKey(priv); + assertEquals(expectedPubHex, xecHexFromPub(pub)); + + priv = xecPrivFromHex("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"); + expectedPubHex = "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"; + pub = KeyUtils.extractX25519PublicKey(priv); + assertEquals(expectedPubHex, xecHexFromPub(pub)); + } + } |