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
1 change: 0 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
<protobuf.version>3.25.5</protobuf.version>
<protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version>
<os-maven-plugin.version>1.7.1</os-maven-plugin.version>
<net.i2p.crypto.eddsa.version>0.3.0</net.i2p.crypto.eddsa.version>
<vavr.version>0.10.3</vavr.version>
<re2j.version>1.6</re2j.version>
<gson.version>2.8.9</gson.version>
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/org/eclipse/biscuit/crypto/Ed25519KeyPair.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.math.ec.rfc8032.Ed25519;
import org.eclipse.biscuit.error.Error;
import org.eclipse.biscuit.token.builder.Utils;

final class Ed25519KeyPair extends KeyPair {
Expand All @@ -20,7 +22,11 @@ final class Ed25519KeyPair extends KeyPair {
private final Ed25519PrivateKeyParameters privateKey;
private final Ed25519PublicKeyParameters publicKey;

Ed25519KeyPair(byte[] bytes) {
Ed25519KeyPair(byte[] bytes) throws Error.FormatError.InvalidKeySize {
if (bytes.length != Ed25519.SECRET_KEY_SIZE) {
throw new Error.FormatError.InvalidKeySize(bytes.length);
}

Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(bytes);
Ed25519PublicKeyParameters publicKey = privateKey.generatePublicKey();

Expand Down
11 changes: 9 additions & 2 deletions src/main/java/org/eclipse/biscuit/crypto/Ed25519PublicKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.Arrays;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.eclipse.biscuit.error.Error;

class Ed25519PublicKey extends PublicKey {
private final Ed25519PublicKeyParameters publicKey;
Expand All @@ -18,8 +19,14 @@ class Ed25519PublicKey extends PublicKey {
this.publicKey = publicKey;
}

static Ed25519PublicKey loadEd25519(byte[] data) {
return new Ed25519PublicKey(new Ed25519PublicKeyParameters(data));
static Ed25519PublicKey loadEd25519(byte[] data) throws Error.FormatError.InvalidKey {
Ed25519PublicKeyParameters params;
try {
params = new Ed25519PublicKeyParameters(data);
} catch (IllegalArgumentException e) {
throw new Error.FormatError.InvalidKey(e.getMessage());
}
return new Ed25519PublicKey(params);
}

@Override
Expand Down
57 changes: 51 additions & 6 deletions src/main/java/org/eclipse/biscuit/crypto/KeyPair.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,84 @@

import biscuit.format.schema.Schema.PublicKey.Algorithm;
import java.security.SecureRandom;
import org.eclipse.biscuit.error.Error;
import org.eclipse.biscuit.token.builder.Utils;

/** Private and public key. */
public abstract class KeyPair implements Signer {
public interface Factory {
KeyPair generate(byte[] bytes) throws Error.FormatError.InvalidKeySize;

KeyPair generate(SecureRandom rng);
}

public static final Factory DEFAULT_ED25519_FACTORY =
new Factory() {
@Override
public KeyPair generate(byte[] bytes) throws Error.FormatError.InvalidKeySize {
return new Ed25519KeyPair(bytes);
}

@Override
public KeyPair generate(SecureRandom rng) {
return new Ed25519KeyPair(rng);
}
};

public static final Factory DEFAULT_SECP256R1_FACTORY =
new Factory() {
@Override
public KeyPair generate(byte[] bytes) throws Error.FormatError.InvalidKeySize {
return new SECP256R1KeyPair(bytes);
}

@Override
public KeyPair generate(SecureRandom rng) {
return new SECP256R1KeyPair(rng);
}
};

private static volatile Factory ed25519Factory = DEFAULT_ED25519_FACTORY;
private static volatile Factory secp256r1Factory = DEFAULT_SECP256R1_FACTORY;

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

public static KeyPair generate(Algorithm algorithm, String hex) {
public static KeyPair generate(Algorithm algorithm, String hex)
throws Error.FormatError.InvalidKeySize {
return generate(algorithm, Utils.hexStringToByteArray(hex));
}

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

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

public static void setEd25519Factory(Factory factory) {
ed25519Factory = factory;
}

public static void setSECP256R1Factory(Factory factory) {
secp256r1Factory = factory;
}

public abstract byte[] toBytes();

public abstract String toHex();
Expand Down
30 changes: 24 additions & 6 deletions src/main/java/org/eclipse/biscuit/crypto/PublicKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,32 @@
import org.eclipse.biscuit.token.builder.Utils;

public abstract class PublicKey {
public interface Factory {
PublicKey load(byte[] bytes) throws Error.FormatError.InvalidKey;
}

public static final Factory DEFAULT_ED25519_FACTORY =
bytes -> Ed25519PublicKey.loadEd25519(bytes);
public static final Factory DEFAULT_SECP256R1_FACTORY =
bytes -> SECP256R1PublicKey.loadSECP256R1(bytes);

private static volatile Factory ed25519Factory = DEFAULT_ED25519_FACTORY;
private static volatile Factory secp256r1Factory = DEFAULT_SECP256R1_FACTORY;

private static final Set<Algorithm> SUPPORTED_ALGORITHMS =
Set.of(Algorithm.Ed25519, Algorithm.SECP256R1);

public static PublicKey load(Algorithm algorithm, byte[] data) {
public static PublicKey load(Algorithm algorithm, byte[] data) throws Error.FormatError {
if (algorithm == Algorithm.Ed25519) {
return Ed25519PublicKey.loadEd25519(data);
return ed25519Factory.load(data);
} else if (algorithm == Algorithm.SECP256R1) {
return SECP256R1PublicKey.loadSECP256R1(data);
return secp256r1Factory.load(data);
} else {
throw new IllegalArgumentException("Unsupported algorithm");
}
}

public static PublicKey load(Algorithm algorithm, String hex) {
public static PublicKey load(Algorithm algorithm, String hex) throws Error.FormatError {
return load(algorithm, Utils.hexStringToByteArray(hex));
}

Expand All @@ -48,8 +59,7 @@ public Schema.PublicKey serialize() {
return publicKey.build();
}

public static PublicKey deserialize(Schema.PublicKey pk)
throws Error.FormatError.DeserializationError {
public static PublicKey deserialize(Schema.PublicKey pk) throws Error.FormatError {
if (!pk.hasAlgorithm() || !pk.hasKey() || !SUPPORTED_ALGORITHMS.contains(pk.getAlgorithm())) {
throw new Error.FormatError.DeserializationError("Invalid public key");
}
Expand All @@ -74,6 +84,14 @@ public static Optional<Error> validateSignatureLength(Algorithm algorithm, int l
return error;
}

public static void setEd25519Factory(Factory factory) {
ed25519Factory = factory;
}

public static void setSECP256R1Factory(Factory factory) {
secp256r1Factory = factory;
}

public abstract Algorithm getAlgorithm();

public abstract boolean verify(byte[] data, byte[] signature)
Expand Down
45 changes: 24 additions & 21 deletions src/main/java/org/eclipse/biscuit/crypto/SECP256R1KeyPair.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@

package org.eclipse.biscuit.crypto;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.io.IOException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.StandardDSAEncoding;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.ECNamedCurveTable;
Expand All @@ -19,6 +18,7 @@
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.BigIntegers;
import org.eclipse.biscuit.error.Error;
import org.eclipse.biscuit.token.builder.Utils;

@SuppressWarnings("checkstyle:AbbreviationAsWordInName")
Expand All @@ -31,15 +31,14 @@ final class SECP256R1KeyPair extends KeyPair {
private final BCECPrivateKey privateKey;
private final BCECPublicKey publicKey;

static {
Security.addProvider(new BouncyCastleProvider());
}

static final String ALGORITHM = "ECDSA";
static final String CURVE = "secp256r1";
static final ECNamedCurveParameterSpec SECP256R1 = ECNamedCurveTable.getParameterSpec(CURVE);

SECP256R1KeyPair(byte[] bytes) {
SECP256R1KeyPair(byte[] bytes) throws Error.FormatError.InvalidKeySize {
if (bytes.length != BUFFER_SIZE) {
throw new Error.FormatError.InvalidKeySize(bytes.length);
}
var privateKeySpec = new ECPrivateKeySpec(BigIntegers.fromUnsignedByteArray(bytes), SECP256R1);
var privateKey =
new BCECPrivateKey(ALGORITHM, privateKeySpec, BouncyCastleProvider.CONFIGURATION);
Expand Down Expand Up @@ -68,18 +67,22 @@ final class SECP256R1KeyPair extends KeyPair {
this.publicKey = publicKey;
}

static Signature getSignature() throws NoSuchAlgorithmException {
return Signature.getInstance(
"SHA256withECDSA", Security.getProvider(BouncyCastleProvider.PROVIDER_NAME));
}

@Override
public byte[] sign(byte[] data)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature sgr = getSignature();
sgr.initSign(privateKey);
sgr.update(data);
return sgr.sign();
public byte[] sign(byte[] data) {
var digest = new SHA256Digest();
digest.update(data, 0, data.length);
var hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);

var signer = new ECDSASigner();
signer.init(true, privateKey.engineGetKeyParameters());
var sig = signer.generateSignature(hash);

try {
return StandardDSAEncoding.INSTANCE.encode(signer.getOrder(), sig[0], sig[1]);
} catch (IOException e) {
throw new IllegalStateException(e.toString());
}
}

@Override
Expand Down
57 changes: 45 additions & 12 deletions src/main/java/org/eclipse/biscuit/crypto/SECP256R1PublicKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,53 @@
package org.eclipse.biscuit.crypto;

import static org.eclipse.biscuit.crypto.SECP256R1KeyPair.CURVE;
import static org.eclipse.biscuit.crypto.SECP256R1KeyPair.getSignature;

import biscuit.format.schema.Schema.PublicKey.Algorithm;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.bouncycastle.crypto.signers.StandardDSAEncoding;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.eclipse.biscuit.error.Error;

@SuppressWarnings("checkstyle:AbbreviationAsWordInName")
class SECP256R1PublicKey extends PublicKey {

private static final X9ECParameters x9ECParameters = SECNamedCurves.getByName("secp256r1");
private static final ECDomainParameters domainParameters =
new ECDomainParameters(
x9ECParameters.getCurve(),
x9ECParameters.getG(),
x9ECParameters.getN(),
x9ECParameters.getH());

private final BCECPublicKey publicKey;

SECP256R1PublicKey(BCECPublicKey publicKey) {
super();
this.publicKey = publicKey;
}

static SECP256R1PublicKey loadSECP256R1(byte[] data) {
static SECP256R1PublicKey loadSECP256R1(byte[] data) throws Error.FormatError.InvalidKey {
var params = ECNamedCurveTable.getParameterSpec(CURVE);
var spec = new ECPublicKeySpec(params.getCurve().decodePoint(data), params);
ECPoint ecPoint;
try {
ecPoint = params.getCurve().decodePoint(data);
} catch (IllegalArgumentException e) {
throw new Error.FormatError.InvalidKey(e.getMessage());
}
var spec = new ECPublicKeySpec(ecPoint, params);
return new SECP256R1PublicKey(
new BCECPublicKey(SECP256R1KeyPair.ALGORITHM, spec, BouncyCastleProvider.CONFIGURATION));
}
Expand Down Expand Up @@ -69,11 +91,22 @@ public Algorithm getAlgorithm() {
}

@Override
public boolean verify(byte[] data, byte[] signature)
throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
var sgr = getSignature();
sgr.initVerify(this.publicKey);
sgr.update(data);
return sgr.verify(signature);
public boolean verify(byte[] data, byte[] signature) {
var digest = new SHA256Digest();
digest.update(data, 0, data.length);
var hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);

var signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
signer.init(false, new ECPublicKeyParameters(publicKey.getQ(), domainParameters));

BigInteger[] sig;
try {
sig = StandardDSAEncoding.INSTANCE.decode(signer.getOrder(), signature);
} catch (IOException e) {
throw new IllegalStateException(e.toString());
}

return signer.verifySignature(hash, sig[0], sig[1]);
}
}
Loading