-
Notifications
You must be signed in to change notification settings - Fork 283
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Ed25519 signatures to Conscrypt. (#1297)
* Add Ed25519 Signatures to Conscrypt. * Remove test keygenWithConscrypt_useWithDefaultProvider. This is not a good test, its success depends on what the default provider is.
- Loading branch information
Showing
11 changed files
with
966 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
156 changes: 156 additions & 0 deletions
156
common/src/main/java/org/conscrypt/OpenSslEdDsaKeyFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/* | ||
* Copyright 2025 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.conscrypt; | ||
|
||
import java.lang.reflect.Constructor; | ||
import java.lang.reflect.InvocationTargetException; | ||
import java.security.InvalidKeyException; | ||
import java.security.Key; | ||
import java.security.KeyFactorySpi; | ||
import java.security.PrivateKey; | ||
import java.security.PublicKey; | ||
import java.security.spec.EncodedKeySpec; | ||
import java.security.spec.InvalidKeySpecException; | ||
import java.security.spec.KeySpec; | ||
import java.security.spec.PKCS8EncodedKeySpec; | ||
import java.security.spec.X509EncodedKeySpec; | ||
|
||
/** An implementation of a {@link KeyFactorySpi} for EdDSA keys based on BoringSSL. */ | ||
@Internal | ||
public final class OpenSslEdDsaKeyFactory extends KeyFactorySpi { | ||
public OpenSslEdDsaKeyFactory() {} | ||
|
||
@Override | ||
protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { | ||
if (keySpec == null) { | ||
throw new InvalidKeySpecException("keySpec == null"); | ||
} | ||
if (keySpec instanceof EncodedKeySpec) { | ||
return new OpenSslEdDsaPublicKey((EncodedKeySpec) keySpec); | ||
} | ||
throw new InvalidKeySpecException("Must use X509EncodedKeySpec or Raw EncodedKeySpec; was " | ||
+ keySpec.getClass().getName()); | ||
} | ||
|
||
@Override | ||
protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException { | ||
if (keySpec == null) { | ||
throw new InvalidKeySpecException("keySpec == null"); | ||
} | ||
if (keySpec instanceof EncodedKeySpec) { | ||
return new OpenSslEdDsaPrivateKey((EncodedKeySpec) keySpec); | ||
} | ||
throw new InvalidKeySpecException("Must use PKCS8EncodedKeySpec or Raw EncodedKeySpec; was " | ||
+ keySpec.getClass().getName()); | ||
} | ||
|
||
@Override | ||
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec) | ||
throws InvalidKeySpecException { | ||
if (key == null) { | ||
throw new InvalidKeySpecException("key == null"); | ||
} | ||
if (keySpec == null) { | ||
throw new InvalidKeySpecException("keySpec == null"); | ||
} | ||
if (!key.getAlgorithm().equals("EdDSA") && !key.getAlgorithm().equals("Ed25519")) { | ||
throw new InvalidKeySpecException("Key must be an EdDSA or Ed25519 key"); | ||
} | ||
if (key.getEncoded() == null) { | ||
throw new InvalidKeySpecException("Key is destroyed"); | ||
} | ||
// Convert any "foreign" keys to our own type, this has the same requirements as | ||
// converting to a KeySpec below, and is a no-op for our own keys. | ||
try { | ||
key = engineTranslateKey(key); | ||
} catch (InvalidKeyException e) { | ||
throw new InvalidKeySpecException("Unsupported key class: " + key.getClass(), e); | ||
} | ||
|
||
if (key instanceof OpenSslEdDsaPublicKey) { | ||
OpenSslEdDsaPublicKey conscryptKey = (OpenSslEdDsaPublicKey) key; | ||
if (X509EncodedKeySpec.class.isAssignableFrom(keySpec)) { | ||
@SuppressWarnings("unchecked") | ||
T result = (T) new X509EncodedKeySpec(key.getEncoded()); | ||
return result; | ||
} else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { | ||
return makeRawKeySpec(conscryptKey.getRaw(), keySpec); | ||
} | ||
} else if (key instanceof OpenSslEdDsaPrivateKey) { | ||
OpenSslEdDsaPrivateKey conscryptKey = (OpenSslEdDsaPrivateKey) key; | ||
if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) { | ||
@SuppressWarnings("unchecked") | ||
T result = (T) new PKCS8EncodedKeySpec(key.getEncoded()); | ||
return result; | ||
} else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { | ||
return makeRawKeySpec(conscryptKey.getRaw(), keySpec); | ||
} | ||
} | ||
throw new InvalidKeySpecException("Unsupported key type and key spec combination; key=" | ||
+ key.getClass().getName() + ", keySpec=" + keySpec.getName()); | ||
} | ||
|
||
private <T extends KeySpec> T makeRawKeySpec(byte[] bytes, Class<T> keySpecClass) | ||
throws InvalidKeySpecException { | ||
try { | ||
Constructor<T> constructor = keySpecClass.getConstructor(byte[].class); | ||
T instance = constructor.newInstance((Object) bytes); | ||
EncodedKeySpec spec = (EncodedKeySpec) instance; | ||
if (!spec.getFormat().equalsIgnoreCase("raw")) { | ||
throw new InvalidKeySpecException("EncodedKeySpec class must be raw format"); | ||
} | ||
return instance; | ||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | ||
| IllegalAccessException e) { | ||
throw new InvalidKeySpecException( | ||
"Can't process KeySpec class " + keySpecClass.getName(), e); | ||
} | ||
} | ||
|
||
@Override | ||
protected Key engineTranslateKey(Key key) throws InvalidKeyException { | ||
if (key == null) { | ||
throw new InvalidKeyException("key == null"); | ||
} | ||
if ((key instanceof OpenSslEdDsaPublicKey) || (key instanceof OpenSslEdDsaPrivateKey)) { | ||
return key; | ||
} else 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 must be XEC public or private key; was " + key.getClass().getName()); | ||
} | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
common/src/main/java/org/conscrypt/OpenSslEdDsaKeyPairGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/* | ||
* Copyright 2025 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.conscrypt; | ||
|
||
import java.security.InvalidAlgorithmParameterException; | ||
import java.security.KeyPair; | ||
import java.security.KeyPairGenerator; | ||
import java.security.SecureRandom; | ||
import java.security.spec.AlgorithmParameterSpec; | ||
import java.util.Arrays; | ||
|
||
/** | ||
* An implementation of {@link KeyPairGenerator} for XDH keys which uses BoringSSL to perform all | ||
* the operations. This only supports EdDSA keys. | ||
*/ | ||
@Internal | ||
public final class OpenSslEdDsaKeyPairGenerator extends KeyPairGenerator { | ||
private static final String ALGORITHM = "EdDSA"; | ||
|
||
public OpenSslEdDsaKeyPairGenerator() { | ||
super(ALGORITHM); | ||
} | ||
|
||
@Override | ||
public KeyPair generateKeyPair() { | ||
byte[] publicKeyBytes = new byte[32]; | ||
byte[] privateKeyBytes = new byte[64]; | ||
|
||
NativeCrypto.ED25519_keypair(publicKeyBytes, privateKeyBytes); | ||
|
||
// BoringSSL uses a 64-byte private key. We only need the seed, which is the first 32 bytes. | ||
byte[] privateKeySeed = Arrays.copyOf(privateKeyBytes, 32); | ||
|
||
return new KeyPair(new OpenSslEdDsaPublicKey(publicKeyBytes), | ||
new OpenSslEdDsaPrivateKey(privateKeySeed)); | ||
} | ||
|
||
@Override | ||
public void initialize(int keysize, SecureRandom random) { | ||
if (keysize != 255) { | ||
throw new IllegalArgumentException("EdDSA only supports key size 255"); | ||
} | ||
} | ||
|
||
@Override | ||
public void initialize(AlgorithmParameterSpec param, SecureRandom random) | ||
throws InvalidAlgorithmParameterException { | ||
throw new InvalidAlgorithmParameterException( | ||
"No AlgorithmParameterSpec classes are supported"); | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
common/src/main/java/org/conscrypt/OpenSslEdDsaPrivateKey.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
* Copyright 2025 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.conscrypt; | ||
|
||
import java.security.PrivateKey; | ||
import java.security.spec.EncodedKeySpec; | ||
import java.security.spec.InvalidKeySpecException; | ||
import java.util.Arrays; | ||
|
||
/** An OpenSSL EdDSA private key. */ | ||
public class OpenSslEdDsaPrivateKey implements PrivateKey { | ||
private static final long serialVersionUID = -3136201500221850916L; | ||
private static final byte[] pkcs8Preamble = new byte[] { | ||
0x30, 0x2e, // Sequence: 46 bytes | ||
0x02, 0x01, 0x00, // Integer: 0 (version) | ||
0x30, 0x05, // Sequence: 5 bytes | ||
0x06, 0x03, 0x2b, 0x65, 0x70, // OID: 1.3.101.112 (EdDSA) | ||
0x04, 0x22, 0x04, 0x20, // Octet string: 32 bytes | ||
// Key bytes follow directly | ||
}; | ||
|
||
// BoringSSL uses a 64-byte private key. But this key here is only | ||
// the 32-byte seed, as defined in RFC 8032. | ||
static final int ED25519_PRIVATE_KEY_SIZE_BYTES = 32; | ||
|
||
private byte[] privateKeyBytes; | ||
|
||
public OpenSslEdDsaPrivateKey(EncodedKeySpec keySpec) throws InvalidKeySpecException { | ||
byte[] encoded = keySpec.getEncoded(); | ||
if (keySpec.getFormat().equals("PKCS#8")) { | ||
byte[] preamble = Arrays.copyOf(encoded, pkcs8Preamble.length); | ||
if (!Arrays.equals(preamble, pkcs8Preamble)) { | ||
throw new InvalidKeySpecException("Invalid EdDSA PKCS8 key preamble"); | ||
} | ||
privateKeyBytes = Arrays.copyOfRange(encoded, pkcs8Preamble.length, encoded.length); | ||
} else if (keySpec.getFormat().equalsIgnoreCase("raw")) { | ||
privateKeyBytes = encoded; | ||
} else { | ||
throw new InvalidKeySpecException("Encoding must be in PKCS#8 or raw format"); | ||
} | ||
if (privateKeyBytes.length != ED25519_PRIVATE_KEY_SIZE_BYTES) { | ||
throw new InvalidKeySpecException("Invalid key size"); | ||
} | ||
} | ||
|
||
public OpenSslEdDsaPrivateKey(byte[] privateKeyBytes) { | ||
if (privateKeyBytes.length != ED25519_PRIVATE_KEY_SIZE_BYTES) { | ||
throw new IllegalArgumentException("Invalid key size"); | ||
} | ||
this.privateKeyBytes = privateKeyBytes.clone(); | ||
} | ||
|
||
@Override | ||
public String getAlgorithm() { | ||
return "EdDSA"; | ||
} | ||
|
||
@Override | ||
public String getFormat() { | ||
return "PKCS#8"; | ||
} | ||
|
||
@Override | ||
public byte[] getEncoded() { | ||
if (privateKeyBytes == null) { | ||
throw new IllegalStateException("key is destroyed"); | ||
} | ||
return ArrayUtils.concat(pkcs8Preamble, privateKeyBytes); | ||
} | ||
|
||
byte[] getRaw() { | ||
if (privateKeyBytes == null) { | ||
throw new IllegalStateException("key is destroyed"); | ||
} | ||
return privateKeyBytes.clone(); | ||
} | ||
|
||
@Override | ||
public void destroy() { | ||
if (privateKeyBytes != null) { | ||
Arrays.fill(privateKeyBytes, (byte) 0); | ||
privateKeyBytes = null; | ||
} | ||
} | ||
|
||
@Override | ||
public boolean isDestroyed() { | ||
return privateKeyBytes == null; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) | ||
return true; | ||
if (!(o instanceof OpenSslEdDsaPrivateKey)) { | ||
return false; | ||
} | ||
OpenSslEdDsaPrivateKey that = (OpenSslEdDsaPrivateKey) o; | ||
return Arrays.equals(privateKeyBytes, that.privateKeyBytes); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Arrays.hashCode(privateKeyBytes); | ||
} | ||
} |
Oops, something went wrong.