Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions common/src/main/java/org/conscrypt/HpkeImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -220,27 +220,29 @@ public X25519_CHACHA20() {
}
}

private static final OpenSslXwingKeyFactory xwingKeyFactory = new OpenSslXwingKeyFactory();

private static class HpkeXwingImpl extends HpkeImpl {
HpkeXwingImpl(HpkeSuite hpkeSuite) {
super(hpkeSuite);
}

@Override
byte[] getRecipientPublicKeyBytes(PublicKey publicKey) throws InvalidKeyException {
if (!(publicKey instanceof OpenSslXwingPublicKey)) {
throw new InvalidKeyException(
"Unsupported recipient key class: " + publicKey.getClass());
Key translatedKey = xwingKeyFactory.engineTranslateKey(publicKey);
if (!(translatedKey instanceof OpenSslXwingPublicKey)) {
throw new IllegalStateException("Unexpected public key class");
}
return ((OpenSslXwingPublicKey) publicKey).getRaw();
return ((OpenSslXwingPublicKey) translatedKey).getRaw();
}

@Override
byte[] getPrivateRecipientKeyBytes(PrivateKey recipientKey) throws InvalidKeyException {
if (!(recipientKey instanceof OpenSslXwingPrivateKey)) {
throw new InvalidKeyException(
"Unsupported recipient private key class: " + recipientKey.getClass());
Key translatedKey = xwingKeyFactory.engineTranslateKey(recipientKey);
if (!(translatedKey instanceof OpenSslXwingPrivateKey)) {
throw new IllegalStateException("Unexpected private key class");
}
return ((OpenSslXwingPrivateKey) recipientKey).getRaw();
return ((OpenSslXwingPrivateKey) translatedKey).getRaw();
}
}

Expand Down
40 changes: 34 additions & 6 deletions common/src/main/java/org/conscrypt/OpenSslXwingKeyFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,26 @@ protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
if (keySpec == null) {
throw new InvalidKeySpecException("keySpec == null");
}
try {
key = engineTranslateKey(key);
} catch (InvalidKeyException e) {
throw new InvalidKeySpecException("Unsupported key class: " + key.getClass(), e);
}
if (key instanceof OpenSslXwingPublicKey) {
OpenSslXwingPublicKey conscryptKey = (OpenSslXwingPublicKey) key;
if (X509EncodedKeySpec.class.isAssignableFrom(keySpec)) {
throw new UnsupportedOperationException(
"X509EncodedKeySpec is currently not supported");
@SuppressWarnings("unchecked") // safe because of isAssignableFrom check above
T result = (T) new X509EncodedKeySpec(key.getEncoded());
return result;
} else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) {
return KeySpecUtil.makeRawKeySpec(conscryptKey.getRaw(), keySpec);
}
} else if (key instanceof OpenSslXwingPrivateKey) {
OpenSslXwingPrivateKey conscryptKey = (OpenSslXwingPrivateKey) key;
if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) {
throw new UnsupportedOperationException(
"PKCS8EncodedKeySpec is currently not supported");
@SuppressWarnings("unchecked") // safe because of isAssignableFrom check above
T result = (T) new PKCS8EncodedKeySpec(key.getEncoded());
return result;
} else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) {
return KeySpecUtil.makeRawKeySpec(conscryptKey.getRaw(), keySpec);
}
Expand All @@ -94,7 +101,28 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException {
if ((key instanceof OpenSslXwingPublicKey) || (key instanceof OpenSslXwingPrivateKey)) {
return key;
}
throw new InvalidKeyException(
"Key must be OpenSslXwingPublicKey or OpenSslXwingPrivateKey");
if ((key instanceof PrivateKey) && key.getFormat().equals("PKCS#8")) {
byte[] encoded = key.getEncoded();
if (encoded == null) {
throw new InvalidKeyException("Key does not support encoding");
}
try {
return engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
} else if ((key instanceof PublicKey) && key.getFormat().equals("X.509")) {
byte[] encoded = key.getEncoded();
if (encoded == null) {
throw new InvalidKeyException("Key does not support encoding");
}
try {
return engineGeneratePublic(new X509EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
} else {
throw new InvalidKeyException("Key is not a XWING key");
}
}
}
45 changes: 42 additions & 3 deletions common/src/main/java/org/conscrypt/OpenSslXwingPrivateKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,47 @@ public class OpenSslXwingPrivateKey implements PrivateKey {

static final int PRIVATE_KEY_SIZE_BYTES = 32;

// The PKCS#8 encoding of a X-Wing private key is always the concatenation of a fixed
// prefix and the raw key.
private static final byte[] pkcs8Preamble = new byte[] {
0x30,
0x34,
0x02,
0x01,
0x00,
0x30,
0x0d,
0x06,
0x0b,
0x2b,
0x06,
0x01,
0x04,
0x01,
(byte) 0x83,
(byte) 0xe6,
0x2d,
(byte) 0x81,
(byte) 0xc8,
(byte) 0x7a,
0x04,
0x20,
};

private byte[] raw;

public OpenSslXwingPrivateKey(EncodedKeySpec keySpec) throws InvalidKeySpecException {
byte[] encoded = keySpec.getEncoded();
if (keySpec.getFormat().equalsIgnoreCase("raw")) {
if (keySpec.getFormat().equals("PKCS#8")) {
byte[] preamble = Arrays.copyOf(encoded, pkcs8Preamble.length);
if (!Arrays.equals(preamble, pkcs8Preamble)) {
throw new InvalidKeySpecException("Invalid X-Wing PKCS8 key preamble");
}
raw = Arrays.copyOfRange(encoded, pkcs8Preamble.length, encoded.length);
if (raw.length != PRIVATE_KEY_SIZE_BYTES) {
throw new InvalidKeySpecException("Invalid key size");
}
} else if (keySpec.getFormat().equalsIgnoreCase("raw")) {
if (encoded.length != PRIVATE_KEY_SIZE_BYTES) {
throw new InvalidKeySpecException("Invalid key size");
}
Expand All @@ -58,12 +94,15 @@ public String getAlgorithm() {

@Override
public String getFormat() {
throw new UnsupportedOperationException("getFormat() not yet supported");
return "PKCS#8";
}

@Override
public byte[] getEncoded() {
throw new UnsupportedOperationException("getEncoded() not yet supported");
if (raw == null) {
throw new IllegalStateException("key is destroyed");
}
return ArrayUtils.concat(pkcs8Preamble, raw);
}

byte[] getRaw() {
Expand Down
46 changes: 43 additions & 3 deletions common/src/main/java/org/conscrypt/OpenSslXwingPublicKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,48 @@ public class OpenSslXwingPublicKey implements PublicKey {

static final int PUBLIC_KEY_SIZE_BYTES = 1216;

// The X.509 encoding of a X-Wing public key is always the concatenation of a fixed
// prefix and the raw key.
private static final byte[] x509Preamble = new byte[] {
0x30,
(byte) 0x82,
0x04,
(byte) 0xd4,
0x30,
0x0d,
0x06,
0x0b,
0x2b,
0x06,
0x01,
0x04,
0x01,
(byte) 0x83,
(byte) 0xe6,
0x2d,
(byte) 0x81,
(byte) 0xc8,
(byte) 0x7a,
0x03,
(byte) 0x82,
0x04,
(byte) 0xc1,
0x00,
};

private final byte[] raw;

public OpenSslXwingPublicKey(EncodedKeySpec keySpec) throws InvalidKeySpecException {
byte[] encoded = keySpec.getEncoded();
if (keySpec.getFormat().equalsIgnoreCase("raw")) {
if (keySpec.getFormat().equals("X.509")) {
if (!ArrayUtils.startsWith(encoded, x509Preamble)) {
throw new InvalidKeySpecException("Invalid X-Wing X.509 key preamble");
}
raw = Arrays.copyOfRange(encoded, x509Preamble.length, encoded.length);
if (raw.length != PUBLIC_KEY_SIZE_BYTES) {
throw new InvalidKeySpecException("Invalid key size");
}
} else if (keySpec.getFormat().equalsIgnoreCase("raw")) {
if (encoded.length != PUBLIC_KEY_SIZE_BYTES) {
throw new InvalidKeySpecException("Invalid key size");
}
Expand All @@ -57,12 +94,15 @@ public String getAlgorithm() {

@Override
public String getFormat() {
throw new UnsupportedOperationException("getFormat() not yet supported");
return "X.509";
}

@Override
public byte[] getEncoded() {
throw new UnsupportedOperationException("getEncoded() not yet supported");
if (raw == null) {
throw new IllegalStateException("key is destroyed");
}
return ArrayUtils.concat(x509Preamble, raw);
}

byte[] getRaw() {
Expand Down
147 changes: 140 additions & 7 deletions common/src/test/java/org/conscrypt/XwingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,119 @@ public void generatePublic_fromRawPublicKey_validatesSize() throws Exception {
() -> keyFactory.generatePublic(new RawKeySpec(new byte[rawPublicKey.length + 1])));
}

/** Helper class to test KeyFactory.translateKey. */
static class TestPublicKey implements PublicKey {
public TestPublicKey(byte[] x509encoded) {
this.x509encoded = x509encoded;
}

private final byte[] x509encoded;

@Override
public String getAlgorithm() {
return "XWING";
}

@Override
public String getFormat() {
return "X.509";
}

@Override
public byte[] getEncoded() {
return x509encoded;
}
}

/** Helper class to test KeyFactory.translateKey. */
static class TestPrivateKey implements PrivateKey {
public TestPrivateKey(byte[] pkcs8encoded) {
this.pkcs8encoded = pkcs8encoded;
}

private final byte[] pkcs8encoded;

@Override
public String getAlgorithm() {
return "XWING";
}

@Override
public String getFormat() {
return "PKCS#8";
}

@Override
public byte[] getEncoded() {
return pkcs8encoded;
}
}

@Test
public void x509AndPkcs8_areNotSupported() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("XWING", conscryptProvider);
KeyPair keyPair = keyGen.generateKeyPair();
public void toAndFromX509AndPkcs8_works() throws Exception {
// from https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem/, Appendix D
byte[] rawPrivateKey = TestUtils.decodeHex(
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
byte[] rawPublicKey = NativeCrypto.XWING_public_key_from_seed(rawPrivateKey);

byte[] encodedPrivateKey = TestUtils.decodeBase64(
"MDQCAQAwDQYLKwYBBAGD5i2ByHoEIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4f");
byte[] encodedPublicKey = TestUtils.decodeBase64(
"MIIE1DANBgsrBgEEAYPmLYHIegOCBMEAb1QJigoOZBFGYUtpYLpg2GA9YvRH+atJ"
+ "m0e9aQbMQLBh2GNKPoiQbyhJWOdEHKbHJcu5cJW3ZxpGK2aByeZYC7yNYLFJ+mAm"
+ "EEOvu6UvIFpgKDhIUVlq3zcavqmNM0c4PSu2c0OPZ4NhK/hwFPe5Gol0AmU0XfZ5"
+ "NARz0cTBdohuXim48Fi7fHNTFmhs/1w764wmHLAJcKacGvzFS5TLhuHOY7pjbjlc"
+ "pFEB4hx70EwxPqGa8kFB79KtREFqJbpPZZEO99iAnDCT8EqvAOPNluNcSqPIAsGK"
+ "1vOdpLS42YyL15Atg6B7pFOWZ0pgJDyrk+gP2bHId3N2qcwNb6EV4mOTgLnGvnhI"
+ "vRNYjGRwOgU10ZoPgWM6l2oKEFtm7ihdD9JV6CwDMZJfQ4O278dh72CZI1oLmHJj"
+ "WKqdAbi4llGfkhR0u3wUuyIlK1wvENQSRsmyPnZEhJNn9UGhX2O8koo5u3vHPwe2"
+ "ZcSWu2VYyPRUiacuxLrNNOnFlMM4cbcj8DSV6ItDkasm5DBD3rYRezkZ5FxMGxar"
+ "KOR93XI2Y4VHZhkvwYBspwq7eGy9swky5oyKNwvPsHmDoBLDJmuT76YmV/S4ODdM"
+ "sLuV4OwGVBsHZdmc8VO8a5YTXKeApVs2R3ieMZFeRig8+ce7boRT+2aCEFFB8dwN"
+ "ANhe7XA7bGyWH3nIRSdrQkiUnAZ4LlE+spkbldlgQuOMvto1JEmytQhOvaUiamIG"
+ "QAeJEwowlkSYSLYp/upKLCp0PEoN3Jyz89Z2/FY3MbJsShpm3IRZFwBW1XaX8UQ7"
+ "gamjRBK7e/BfMydXWlkR3TAdYFOGfzwwgHEfG/EVh7C7KYQnayaF53ViEOSz+JVT"
+ "hCMeVYxvUQyR4PxWtdGIX/KUnpWka8G+4fpx9QJ+EMRDsOkdD9dED0Z6JyISEuiP"
+ "XGumQpbK4NIHv8YPiMfPtcRaoYOdGMs3xFhD5UJqSpDIArZCj5U8NZxKwGA0UvrA"
+ "tzYeL9NdzIhakhRdT8oBWPG31wtLzRGOSipBVEON8xDESpobmepBWQcmeoiwYkJB"
+ "V5wXIvRu1hwuPspUXJlwUXF1OZuADbJdo5WT0GSQ1xQsAOiNLbBH6YmL23rLftkH"
+ "9uMEFswN5UokLAohJjAvXVTIW8Zqwvg8eXlFtQZ8qkK9LgwZypdQblB6sKXJ9WM3"
+ "CEmcGfJK7FE705A6XXO27EmR98cuuZHBw3iJgFyx6jigzAIXayfFjWOM5aMmaEV8"
+ "+bm+AnygIUBXlxcl1UEC6JlnFusq2CNFO2BbhVNwsbIbOTLN7UFgqplzx+uuWsR2"
+ "TZTPfMlQbwd7rXMBLbtKyBQKOHRkEuszyVFFliBfcHY1hiIX2bYJGMYmjZNEkVuE"
+ "eiR2waJw8VSlyEI0FlrPyGk5hwLOqemgfnsOmeqb3LeEH+nA+iXIM4CSVho+3dxw"
+ "AfR4rWV4GmAkqtFl2baXmtrESKRGL1ZGhVJ/diQ0/ppCWoRDe0VzkuyoDJE1BhUe"
+ "OhMjnzQvynZVtuquhFoiHOs+Z/VjnGGT9v3u9X45m4CLfzqitXQKre2QFj3F13XJ"
+ "+vfx+9B12rNE6dfRRmRygfu6ezxWyv1YM7epMOxCBufDptd2T+gdeg==");

KeyFactory keyFactory = KeyFactory.getInstance("XWING", conscryptProvider);

assertThrows(UnsupportedOperationException.class,
() -> keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class));
assertThrows(UnsupportedOperationException.class,
() -> keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class));
// Check generatePrivate from PKCS8EncodedKeySpec works.
PrivateKey privateKey =
keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedPrivateKey));
assertArrayEquals(encodedPrivateKey, privateKey.getEncoded());
assertArrayEquals(rawPrivateKey, ((OpenSslXwingPrivateKey) privateKey).getRaw());

// Check generatePublic from X509EncodedKeySpec works.
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedPublicKey));
assertArrayEquals(encodedPublicKey, publicKey.getEncoded());
assertArrayEquals(rawPublicKey, ((OpenSslXwingPublicKey) publicKey).getRaw());

// Check getKeySpec with works for both private and public keys.
EncodedKeySpec privateKeySpec =
keyFactory.getKeySpec(privateKey, PKCS8EncodedKeySpec.class);
assertEquals("PKCS#8", privateKeySpec.getFormat());
assertArrayEquals(encodedPrivateKey, privateKeySpec.getEncoded());

EncodedKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey, X509EncodedKeySpec.class);
assertEquals("X.509", publicKeySpec.getFormat());
assertArrayEquals(encodedPublicKey, publicKeySpec.getEncoded());

assertEquals(privateKey, keyFactory.translateKey(privateKey));
assertEquals(
privateKey, keyFactory.translateKey(new TestPrivateKey(privateKey.getEncoded())));
assertEquals(publicKey, keyFactory.translateKey(publicKey));
assertEquals(publicKey, keyFactory.translateKey(new TestPublicKey(publicKey.getEncoded())));
}

@Test
Expand Down Expand Up @@ -168,6 +270,37 @@ public void sealAndOpen_works() throws Exception {
}
}

@Test
public void sealAndOpenWithForeignKeys_works() throws Exception {
byte[] info = TestUtils.decodeHex("aa");
byte[] plaintext = TestUtils.decodeHex("bb");
byte[] aad = TestUtils.decodeHex("cc");
for (int aead : new int[] {AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_CHACHA20POLY1305}) {
HpkeSuite suite = new HpkeSuite(KEM_XWING, KDF_HKDF_SHA256, aead);
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("XWING", conscryptProvider);
KeyPair keyPairRecipient = keyGen.generateKeyPair();
PublicKey foreignPublicKey =
new TestPublicKey(keyPairRecipient.getPublic().getEncoded());
PrivateKey foreignPrivateKey =
new TestPrivateKey(keyPairRecipient.getPrivate().getEncoded());

HpkeContextSender ctxSender =
HpkeContextSender.getInstance(suite.name(), conscryptProvider);
ctxSender.init(foreignPublicKey, info);

byte[] encapsulated = ctxSender.getEncapsulated();
byte[] ciphertext = ctxSender.seal(plaintext, aad);

HpkeContextRecipient foreignContextRecipient =
HpkeContextRecipient.getInstance(suite.name(), conscryptProvider);
foreignContextRecipient.init(encapsulated, foreignPrivateKey, info);

byte[] foreignOutput = foreignContextRecipient.open(ciphertext, aad);

assertArrayEquals(plaintext, foreignOutput);
}
}

@Test
public void kemTestVectors_encapsulatedIsCorrect() throws Exception {
HpkeSuite suite = new HpkeSuite(KEM_XWING, KDF_HKDF_SHA256, AEAD_AES_128_GCM);
Expand Down
Loading