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
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.util.Base64URL;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.eclipse.edc.spi.EdcException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.KeyFactory;
Expand All @@ -55,7 +58,6 @@
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EdECPoint;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.text.ParseException;
Expand Down Expand Up @@ -382,7 +384,7 @@ private static Ed25519Verifier createEdDsaVerifier(PublicKey publicKey) throws J
var curve = getCurveAllowing(edKey, ALGORITHM_ED25519);


var urlX = encodeX(edKey.getPoint());
var urlX = encodeX(publicKey);
var okp = new OctetKeyPair.Builder(curve, urlX)
.build();
return new Ed25519Verifier(okp);
Expand Down Expand Up @@ -434,7 +436,7 @@ private static OctetKeyPair convertEdDsaKey(KeyPair keypair, @Nullable String ki

// if the public key is not present, an empty byte array is set, because as with all elliptic curves the public
// key can be recovered from the private key. OctetKeyPairs do this for us behind the scenes.
var urlX = ofNullable(pub).map(pubkey -> encodeX(pubkey.getPoint())).orElseGet(() -> Base64URL.encode(new byte[0]));
var urlX = ofNullable(pub).map(CryptoConverter::encodeX).orElseGet(() -> Base64URL.encode(new byte[0]));
var urlD = ofNullable(priv).map(CryptoConverter::encodeD).orElse(null);

var curveName = ofNullable((EdECKey) priv).orElse(pub).getParams().getName();
Expand All @@ -458,16 +460,20 @@ private static Base64URL encodeD(EdECPrivateKey edKey) {
* Encodes the public key part of an EdDSA key as {@link Base64URL}
*/
@NotNull
private static Base64URL encodeX(EdECPoint point) {
var bytes = reverseArray(point.getY().toByteArray());
Comment thread
ndr-brt marked this conversation as resolved.
private static Base64URL encodeX(PublicKey publicKey) {
var encoded = publicKey.getEncoded();

// when the X-coordinate of the curve is odd, we flip the highest-order bit of the first (or last, since we reversed) byte
if (point.isXOdd()) {
var mask = (byte) 128; // is 1000 0000 binary
bytes[bytes.length - 1] ^= mask; // XOR means toggle the left-most bit
if (encoded == null || encoded.length == 0) {
throw new IllegalArgumentException("Encoded bytes are null or empty.");
}

return Base64URL.encode(bytes);
try {
var spki = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(encoded));
var bytes = spki.getPublicKeyData().getBytes();
return Base64URL.encode(bytes);
} catch (IOException e) {
throw new IllegalArgumentException("Failed to parse the public key ASN.1 structure. The key bytes may be corrupted.", e);
}
}

private static String notSupportedError(String algorithm) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -49,6 +51,7 @@
import java.security.Provider;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.EdECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECGenParameterSpec;
Expand All @@ -62,6 +65,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.eclipse.edc.junit.testfixtures.TestUtils.getResourceFileContentAsString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class CryptoConverterTest {

Expand Down Expand Up @@ -459,4 +464,62 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext context) th
}
}

@Nested
@DisplayName("JWK creation")
class JwkCreationTest {

@Test
@DisplayName("Ed25519 key pair is valid")
void createJwk_whenKeyPairIsValid_expectException() throws NoSuchAlgorithmException {
var kp = createEd25519(null);
var jwk = CryptoConverter.createJwk(new KeyPair(kp.getPublic(), kp.getPrivate()));

assertThat(jwk).isInstanceOf(OctetKeyPair.class);
var okp = (OctetKeyPair) jwk;
assertThat(okp.getX()).isNotNull();
assertThat(okp.getD()).isNotNull();
}

@Test
@DisplayName("Key pair does not contain a public key")
void createJwk_whenKeyPairHasNoPublicKey_expectException() throws NoSuchAlgorithmException {
var kp = createEd25519(null);
var jwk = CryptoConverter.createJwk(new KeyPair(null, kp.getPrivate()));

assertThat(jwk).isInstanceOf(OctetKeyPair.class);
var okp = (OctetKeyPair) jwk;
assertThat(okp.getX()).isNotNull();
assertThat(okp.getD()).isNotNull();
}

@Test
@DisplayName("Public key contains garbage bytes")
void createJwk_whenPublicKeyContainsGarbageBytes_expectException() {
var mockPublicKey = mock(EdECPublicKey.class);
when(mockPublicKey.getAlgorithm()).thenReturn("Ed25519");
when(mockPublicKey.getFormat()).thenReturn("X.509");
when(mockPublicKey.getEncoded()).thenReturn(new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF});

var keypair = new KeyPair(mockPublicKey, null);
assertThatThrownBy(() -> CryptoConverter.createJwk(keypair))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Failed to parse the public key ASN.1 structure. The key bytes may be corrupted.");
}

@Test
@DisplayName("Public key contains empty or null byte array")
void createJwk_whenPublicKeyHasEmptyEncoding_expectException() {
var mockPublicKey = mock(EdECPublicKey.class);
when(mockPublicKey.getAlgorithm()).thenReturn("Ed25519");
when(mockPublicKey.getFormat()).thenReturn("X.509");
when(mockPublicKey.getEncoded()).thenReturn(null);

var keypair = new KeyPair(mockPublicKey, null);
assertThatThrownBy(() -> CryptoConverter.createJwk(keypair))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Encoded bytes are null or empty.");
}

}

}
Loading