Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ECDSA key support (SECP256R1) again #108

Merged
merged 28 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1c5789b
p256 in progress, commit before updating tests
itstheceo May 20, 2024
15a93d7
p256 update existing tests
itstheceo May 20, 2024
31bdef7
p256 keypair impl
itstheceo May 20, 2024
46eda04
p256 struggles
itstheceo May 22, 2024
fac0939
return seed bytes like Ed.. feels weird though
itstheceo May 23, 2024
dab7d6a
fix for padding
itstheceo May 23, 2024
6455586
fix it
itstheceo May 23, 2024
658d120
exception cleanup since adding BC
itstheceo May 23, 2024
adc8f83
cleanup more of the mess i made early on
itstheceo May 23, 2024
13b8a68
add provided secp256r1 sample
itstheceo May 23, 2024
a42407a
get the new test passing
itstheceo May 23, 2024
5ade42e
update samples readme as per rust pr
itstheceo May 24, 2024
57da835
cleaning up exceptions, imports
itstheceo May 24, 2024
bfada32
squash condition
itstheceo May 24, 2024
b500faf
resolve todos by making attenuation specify the algorithm to use
itstheceo Jun 12, 2024
6b1c00b
accomodate for variable length ecdsa signature, and throw errors duri…
itstheceo Jun 13, 2024
b5ca28c
amend secp256r1 signature length check
itstheceo Jun 18, 2024
984132f
Merge pull request #3 from biscuit-auth/master
itstheceo Jun 18, 2024
c4df990
Implement 3rd party block creation (#104) (#4)
itstheceo Jun 25, 2024
6921c04
Merge branch 'master' into ec_p256
itstheceo Jun 25, 2024
72d0ed1
Merge pull request #5 from biscuit-auth/master
itstheceo Jun 25, 2024
eb470ca
Merge branch 'master' into ec_p256
itstheceo Jun 25, 2024
e138303
Merge branch 'master' into ec_p256
itstheceo Nov 26, 2024
0e84b05
post merge compile fix
itstheceo Nov 26, 2024
7b049c6
Test with SPECIFICATION’s samples
Korbik Jan 15, 2025
48c2bac
review feedback, less the failing test
itstheceo Jan 16, 2025
fbeb4c1
copy test036_secp256r1 from equivalent rust ecdsa pr
itstheceo Jan 17, 2025
a27f89b
final class for Ed25519Keypair
Korbik Jan 17, 2025
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
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.79</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
89 changes: 89 additions & 0 deletions src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.biscuitsec.biscuit.crypto;

import biscuit.format.schema.Schema;
import net.i2p.crypto.eddsa.EdDSAEngine;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import org.biscuitsec.biscuit.token.builder.Utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Signature;

final class Ed25519KeyPair extends KeyPair {

static final int SIGNATURE_LENGTH = 64;

private final EdDSAPrivateKey privateKey;
private final EdDSAPublicKey publicKey;

private static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);

public Ed25519KeyPair(byte[] bytes) {
EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(bytes, ed25519);
EdDSAPrivateKey privKey = new EdDSAPrivateKey(privKeySpec);

EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKey.getA(), ed25519);
EdDSAPublicKey pubKey = new EdDSAPublicKey(pubKeySpec);

this.privateKey = privKey;
this.publicKey = pubKey;
}

public Ed25519KeyPair(SecureRandom rng) {
byte[] b = new byte[32];
rng.nextBytes(b);

EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(b, ed25519);
EdDSAPrivateKey privKey = new EdDSAPrivateKey(privKeySpec);

EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKey.getA(), ed25519);
EdDSAPublicKey pubKey = new EdDSAPublicKey(pubKeySpec);

this.privateKey = privKey;
this.publicKey = pubKey;
}

public Ed25519KeyPair(String hex) {
this(Utils.hexStringToByteArray(hex));
}

public static java.security.PublicKey decode(byte[] data) {
return new EdDSAPublicKey(new EdDSAPublicKeySpec(data, ed25519));
}

public static Signature getSignature() throws NoSuchAlgorithmException {
return new EdDSAEngine(MessageDigest.getInstance(ed25519.getHashAlgorithm()));
}

@Override
public byte[] toBytes() {
return privateKey.getSeed();
}

@Override
public String toHex() {
return Utils.byteArrayToHexString(toBytes());
}

@Override
public java.security.PublicKey publicKey() {
return publicKey;
}

@Override
public PrivateKey private_key() {
return privateKey;
}

@Override
public PublicKey public_key() {
return new PublicKey(Schema.PublicKey.Algorithm.Ed25519, this.publicKey);
}
}
95 changes: 26 additions & 69 deletions src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java
Original file line number Diff line number Diff line change
@@ -1,106 +1,63 @@
package org.biscuitsec.biscuit.crypto;


import biscuit.format.schema.Schema;
import biscuit.format.schema.Schema.PublicKey.Algorithm;
import net.i2p.crypto.eddsa.EdDSAEngine;
import org.biscuitsec.biscuit.token.builder.Utils;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.*;
import net.i2p.crypto.eddsa.Utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Signature;

/**
* Private and public key
* Private and public key.
*/
public final class KeyPair {
public final EdDSAPrivateKey private_key;
public final EdDSAPublicKey public_key;
public abstract class KeyPair {

private static final int ED25519_PUBLIC_KEYSIZE = 32;
private static final int ED25519_PRIVATE_KEYSIZE = 64;
private static final int ED25519_SEED_SIZE = 32;
public static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);

public KeyPair() {
this(new SecureRandom());
public static KeyPair generate(Algorithm algorithm) {
return generate(algorithm, new SecureRandom());
}

public KeyPair(final SecureRandom rng) {
byte[] b = new byte[32];
rng.nextBytes(b);

EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(b, ed25519);
EdDSAPrivateKey privKey = new EdDSAPrivateKey(privKeySpec);

EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKey.getA(), ed25519);
EdDSAPublicKey pubKey = new EdDSAPublicKey(pubKeySpec);

this.private_key = privKey;
this.public_key = pubKey;
public static KeyPair generate(Algorithm algorithm, String hex) {
return generate(algorithm, Utils.hexToBytes(hex));
}

public static KeyPair generate(Algorithm algorithm) {
return generate(algorithm, new SecureRandom());
public static KeyPair generate(Algorithm algorithm, byte[] bytes) {
if (algorithm == Algorithm.Ed25519) {
return new Ed25519KeyPair(bytes);
} else if (algorithm == Algorithm.SECP256R1) {
return new SECP256R1KeyPair(bytes);
} else {
throw new IllegalArgumentException("Unsupported algorithm");
}
}

public static KeyPair generate(Algorithm algorithm, SecureRandom rng) {
if (algorithm == Algorithm.Ed25519) {
return new KeyPair(rng);
return new Ed25519KeyPair(rng);
} else if (algorithm == Algorithm.SECP256R1) {
return new SECP256R1KeyPair(rng);
} else {
throw new IllegalArgumentException("Unsupported algorithm");
}
}

public static Signature generateSignature(Algorithm algorithm) throws NoSuchAlgorithmException {
if (algorithm == Algorithm.Ed25519) {
return KeyPair.getSignature();
return Ed25519KeyPair.getSignature();
} else if (algorithm == Algorithm.SECP256R1) {
return SECP256R1KeyPair.getSignature();
} else {
throw new NoSuchAlgorithmException("Unsupported algorithm");
}
}

public byte[] toBytes() {
return this.private_key.getSeed();
}

public KeyPair(byte[] b) {
EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(b, ed25519);
EdDSAPrivateKey privKey = new EdDSAPrivateKey(privKeySpec);

EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKey.getA(), ed25519);
EdDSAPublicKey pubKey = new EdDSAPublicKey(pubKeySpec);

this.private_key = privKey;
this.public_key = pubKey;
}

public String toHex() {
return Utils.byteArrayToHexString(this.toBytes());
}

public KeyPair(String hex) {
byte[] b = Utils.hexStringToByteArray(hex);

EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(b, ed25519);
EdDSAPrivateKey privKey = new EdDSAPrivateKey(privKeySpec);
public abstract byte[] toBytes();

EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKey.getA(), ed25519);
EdDSAPublicKey pubKey = new EdDSAPublicKey(pubKeySpec);
public abstract String toHex();

this.private_key = privKey;
this.public_key = pubKey;
}
public abstract java.security.PublicKey publicKey();

public static Signature getSignature() throws NoSuchAlgorithmException {
return new EdDSAEngine(MessageDigest.getInstance(ed25519.getHashAlgorithm()));
}
public abstract java.security.PrivateKey private_key();

public PublicKey public_key() {
return new PublicKey(Schema.PublicKey.Algorithm.Ed25519, this.public_key);
}
public abstract PublicKey public_key();
}
66 changes: 53 additions & 13 deletions src/main/java/org/biscuitsec/biscuit/crypto/PublicKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,46 @@

import biscuit.format.schema.Schema;
import biscuit.format.schema.Schema.PublicKey.Algorithm;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import org.biscuitsec.biscuit.error.Error;
import org.biscuitsec.biscuit.token.builder.Utils;
import com.google.protobuf.ByteString;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;

import static org.biscuitsec.biscuit.crypto.KeyPair.ed25519;
import java.util.Optional;
import java.util.Set;

public class PublicKey {

public final EdDSAPublicKey key;
public final java.security.PublicKey key;
public final Algorithm algorithm;

public PublicKey(Algorithm algorithm, EdDSAPublicKey public_key) {
private static final Set<Algorithm> SUPPORTED_ALGORITHMS = Set.of(Algorithm.Ed25519, Algorithm.SECP256R1);

public PublicKey(Algorithm algorithm, java.security.PublicKey public_key) {
this.key = public_key;
this.algorithm = algorithm;
}

public PublicKey(Algorithm algorithm, byte[] data) {
EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(data, ed25519);
this.key = new EdDSAPublicKey(pubKeySpec);
if (algorithm == Algorithm.Ed25519) {
this.key = Ed25519KeyPair.decode(data);
} else if (algorithm == Algorithm.SECP256R1) {
this.key = SECP256R1KeyPair.decode(data);
} else {
throw new IllegalArgumentException("Invalid algorithm");
}
this.algorithm = algorithm;
}

public byte[] toBytes() {
return this.key.getAbyte();
if (algorithm == Algorithm.Ed25519) {
return ((EdDSAPublicKey) key).getAbyte();
} else if (algorithm == Algorithm.SECP256R1) {
return ((BCECPublicKey) key).getQ().getEncoded(true); // true = compressed
} else {
throw new IllegalArgumentException("Invalid algorithm");
}
}

public String toHex() {
Expand All @@ -36,8 +50,13 @@ public String toHex() {

public PublicKey(Algorithm algorithm, String hex) {
byte[] data = Utils.hexStringToByteArray(hex);
EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(data, ed25519);
this.key = new EdDSAPublicKey(pubKeySpec);
if (algorithm == Algorithm.Ed25519) {
this.key = Ed25519KeyPair.decode(data);
} else if (algorithm == Algorithm.SECP256R1) {
this.key = SECP256R1KeyPair.decode(data);
} else {
throw new IllegalArgumentException("Invalid algorithm");
}
this.algorithm = algorithm;
}

Expand All @@ -49,13 +68,28 @@ public Schema.PublicKey serialize() {
}

static public PublicKey deserialize(Schema.PublicKey pk) throws Error.FormatError.DeserializationError {
if(!pk.hasAlgorithm() || !pk.hasKey() || pk.getAlgorithm() != Algorithm.Ed25519) {
if(!pk.hasAlgorithm() || !pk.hasKey() || !SUPPORTED_ALGORITHMS.contains(pk.getAlgorithm())) {
throw new Error.FormatError.DeserializationError("Invalid public key");
}

return new PublicKey(pk.getAlgorithm(), pk.getKey().toByteArray());
}

public static Optional<Error> validateSignatureLength(Algorithm algorithm, int length) {
Optional<Error> error = Optional.empty();
if (algorithm == Algorithm.Ed25519) {
if (length != Ed25519KeyPair.SIGNATURE_LENGTH) {
error = Optional.of(new Error.FormatError.Signature.InvalidSignatureSize(length));
}
} else if (algorithm == Algorithm.SECP256R1) {
if (length < SECP256R1KeyPair.MINIMUM_SIGNATURE_LENGTH || length > SECP256R1KeyPair.MAXIMUM_SIGNATURE_LENGTH) {
error = Optional.of(new Error.FormatError.Signature.InvalidSignatureSize(length));
}
} else {
error = Optional.of(new Error.FormatError.Signature.InvalidSignature("unsupported algorithm"));
}
return error;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -73,6 +107,12 @@ public int hashCode() {

@Override
public String toString() {
return "ed25519/" + toHex().toLowerCase();
if (algorithm == Algorithm.Ed25519) {
return "ed25519/" + toHex().toLowerCase();
} else if (algorithm == Algorithm.SECP256R1) {
return "secp256r1/" + toHex().toLowerCase();
} else {
return null;
}
}
}
Loading
Loading