diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java index 92bb9fc38..ffdb8a43a 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java @@ -11,12 +11,14 @@ import org.junit.Test; import org.whispersystems.curve25519.Curve25519; import java.security.GeneralSecurityException; +import java.util.Arrays; import java.util.Random; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.util.StringUtils.fromHexString; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; public class KeyAgreementTest extends BrambleTestCase { @@ -66,11 +68,9 @@ public class KeyAgreementTest extends BrambleTestCase { @Test public void testRfc7748TestVector() { - // Private keys need to be clamped because curve25519-java does the - // clamping at key generation time, not multiplication time - byte[] aPriv = AgreementKeyParser.clamp(fromHexString(ALICE_PRIVATE)); + byte[] aPriv = parsePrivateKey(ALICE_PRIVATE); byte[] aPub = fromHexString(ALICE_PUBLIC); - byte[] bPriv = AgreementKeyParser.clamp(fromHexString(BOB_PRIVATE)); + byte[] bPriv = parsePrivateKey(BOB_PRIVATE); byte[] bPub = fromHexString(BOB_PUBLIC); byte[] sharedSecret = fromHexString(SHARED_SECRET); Curve25519 curve25519 = Curve25519.getInstance("java"); @@ -79,4 +79,29 @@ public class KeyAgreementTest extends BrambleTestCase { assertArrayEquals(sharedSecret, curve25519.calculateAgreement(bPub, aPriv)); } + + @Test + public void testDerivesSameSharedSecretFromEquivalentPublicKey() { + byte[] aPub = fromHexString(ALICE_PUBLIC); + byte[] bPriv = parsePrivateKey(BOB_PRIVATE); + byte[] sharedSecret = fromHexString(SHARED_SECRET); + Curve25519 curve25519 = Curve25519.getInstance("java"); + + // Flip the unused most significant bit of the little-endian public key + byte[] aPubEquivalent = aPub.clone(); + aPubEquivalent[31] ^= (byte) 128; + + // The public keys should be different but give the same shared secret + assertFalse(Arrays.equals(aPub, aPubEquivalent)); + assertArrayEquals(sharedSecret, + curve25519.calculateAgreement(aPub, bPriv)); + assertArrayEquals(sharedSecret, + curve25519.calculateAgreement(aPubEquivalent, bPriv)); + } + + private byte[] parsePrivateKey(String hex) { + // Private keys need to be clamped because curve25519-java does the + // clamping at key generation time, not multiplication time + return AgreementKeyParser.clamp(fromHexString(hex)); + } }