From 98d1a11d3c8bfe96b0edbdcfa96dda636385a1b0 Mon Sep 17 00:00:00 2001 From: Adam Preuss Date: Mon, 12 May 2025 11:08:10 -0600 Subject: [PATCH 1/5] Remove unused net.i2p dependency. --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5dec3d76..745a0722 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,6 @@ 3.25.5 0.6.1 1.7.1 - 0.3.0 0.10.3 1.6 2.8.9 From d979993b5b7c19535830d28ca215cf2010c509dc Mon Sep 17 00:00:00 2001 From: Adam Preuss Date: Fri, 23 May 2025 16:56:26 -0600 Subject: [PATCH 2/5] Remove all dependencies on JCE in Secp256r1. --- .../biscuit/crypto/SECP256R1KeyPair.java | 39 ++++++++-------- .../biscuit/crypto/SECP256R1PublicKey.java | 45 ++++++++++++++----- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/eclipse/biscuit/crypto/SECP256R1KeyPair.java b/src/main/java/org/eclipse/biscuit/crypto/SECP256R1KeyPair.java index 8c032c32..46a2cc9e 100644 --- a/src/main/java/org/eclipse/biscuit/crypto/SECP256R1KeyPair.java +++ b/src/main/java/org/eclipse/biscuit/crypto/SECP256R1KeyPair.java @@ -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; @@ -31,10 +30,6 @@ 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); @@ -68,18 +63,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 diff --git a/src/main/java/org/eclipse/biscuit/crypto/SECP256R1PublicKey.java b/src/main/java/org/eclipse/biscuit/crypto/SECP256R1PublicKey.java index 370a3c93..14c0a1ac 100644 --- a/src/main/java/org/eclipse/biscuit/crypto/SECP256R1PublicKey.java +++ b/src/main/java/org/eclipse/biscuit/crypto/SECP256R1PublicKey.java @@ -6,13 +6,19 @@ 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; @@ -21,6 +27,14 @@ @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) { @@ -69,11 +83,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]); } } From 29ce25d68375d9dada7e5a765606b931774dd68a Mon Sep 17 00:00:00 2001 From: Adam Preuss Date: Fri, 23 May 2025 17:07:00 -0600 Subject: [PATCH 3/5] Introduce factory classes so that custom cryptography implementations can be injected. The default key factories are stored as as public static variables, allowing external code to easily revert to the default; e.g. in test code. --- .../org/eclipse/biscuit/crypto/KeyPair.java | 50 +++++++++++++++++-- .../org/eclipse/biscuit/crypto/PublicKey.java | 23 ++++++++- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/eclipse/biscuit/crypto/KeyPair.java b/src/main/java/org/eclipse/biscuit/crypto/KeyPair.java index f60b2512..f887cf7b 100644 --- a/src/main/java/org/eclipse/biscuit/crypto/KeyPair.java +++ b/src/main/java/org/eclipse/biscuit/crypto/KeyPair.java @@ -11,6 +11,40 @@ /** Private and public key. */ public abstract class KeyPair implements Signer { + public interface Factory { + KeyPair generate(byte[] bytes); + + KeyPair generate(SecureRandom rng); + } + + public static final Factory DEFAULT_ED25519_FACTORY = + new Factory() { + @Override + public KeyPair generate(byte[] bytes) { + 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) { + 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()); @@ -22,9 +56,9 @@ public static KeyPair generate(Algorithm algorithm, String hex) { public static KeyPair generate(Algorithm algorithm, byte[] bytes) { 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"); } @@ -32,14 +66,22 @@ public static KeyPair generate(Algorithm algorithm, byte[] bytes) { 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(); diff --git a/src/main/java/org/eclipse/biscuit/crypto/PublicKey.java b/src/main/java/org/eclipse/biscuit/crypto/PublicKey.java index e55e25e5..3d2cc99c 100644 --- a/src/main/java/org/eclipse/biscuit/crypto/PublicKey.java +++ b/src/main/java/org/eclipse/biscuit/crypto/PublicKey.java @@ -17,15 +17,26 @@ import org.eclipse.biscuit.token.builder.Utils; public abstract class PublicKey { + public interface Factory { + PublicKey load(byte[] bytes); + } + + 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 SUPPORTED_ALGORITHMS = Set.of(Algorithm.Ed25519, Algorithm.SECP256R1); public static PublicKey load(Algorithm algorithm, byte[] data) { 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"); } @@ -74,6 +85,14 @@ public static Optional 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) From 037b2714057a1c5f652f80d0b183aa460a60493f Mon Sep 17 00:00:00 2001 From: Adam Preuss Date: Fri, 23 May 2025 13:49:39 -0600 Subject: [PATCH 4/5] Introduce InvalidKeySize validation for key pairs. --- .../biscuit/crypto/Ed25519KeyPair.java | 8 +++- .../org/eclipse/biscuit/crypto/KeyPair.java | 13 ++++--- .../biscuit/crypto/SECP256R1KeyPair.java | 6 ++- .../java/org/eclipse/biscuit/error/Error.java | 39 +++++++++++++++++++ .../token/format/SerializedBiscuit.java | 10 ++--- .../eclipse/biscuit/crypto/SignatureTest.java | 25 ++++++++++-- .../eclipse/biscuit/token/SamplesTest.java | 2 +- 7 files changed, 85 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/eclipse/biscuit/crypto/Ed25519KeyPair.java b/src/main/java/org/eclipse/biscuit/crypto/Ed25519KeyPair.java index 781694f4..0cc0bf7e 100644 --- a/src/main/java/org/eclipse/biscuit/crypto/Ed25519KeyPair.java +++ b/src/main/java/org/eclipse/biscuit/crypto/Ed25519KeyPair.java @@ -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 { @@ -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(); diff --git a/src/main/java/org/eclipse/biscuit/crypto/KeyPair.java b/src/main/java/org/eclipse/biscuit/crypto/KeyPair.java index f887cf7b..cfb23c80 100644 --- a/src/main/java/org/eclipse/biscuit/crypto/KeyPair.java +++ b/src/main/java/org/eclipse/biscuit/crypto/KeyPair.java @@ -7,12 +7,13 @@ 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); + KeyPair generate(byte[] bytes) throws Error.FormatError.InvalidKeySize; KeyPair generate(SecureRandom rng); } @@ -20,7 +21,7 @@ public interface Factory { public static final Factory DEFAULT_ED25519_FACTORY = new Factory() { @Override - public KeyPair generate(byte[] bytes) { + public KeyPair generate(byte[] bytes) throws Error.FormatError.InvalidKeySize { return new Ed25519KeyPair(bytes); } @@ -33,7 +34,7 @@ public KeyPair generate(SecureRandom rng) { public static final Factory DEFAULT_SECP256R1_FACTORY = new Factory() { @Override - public KeyPair generate(byte[] bytes) { + public KeyPair generate(byte[] bytes) throws Error.FormatError.InvalidKeySize { return new SECP256R1KeyPair(bytes); } @@ -50,11 +51,13 @@ 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 ed25519Factory.generate(bytes); } else if (algorithm == Algorithm.SECP256R1) { diff --git a/src/main/java/org/eclipse/biscuit/crypto/SECP256R1KeyPair.java b/src/main/java/org/eclipse/biscuit/crypto/SECP256R1KeyPair.java index 46a2cc9e..f8821320 100644 --- a/src/main/java/org/eclipse/biscuit/crypto/SECP256R1KeyPair.java +++ b/src/main/java/org/eclipse/biscuit/crypto/SECP256R1KeyPair.java @@ -18,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") @@ -34,7 +35,10 @@ final class SECP256R1KeyPair extends KeyPair { 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); diff --git a/src/main/java/org/eclipse/biscuit/error/Error.java b/src/main/java/org/eclipse/biscuit/error/Error.java index 8830b9b8..d5493564 100644 --- a/src/main/java/org/eclipse/biscuit/error/Error.java +++ b/src/main/java/org/eclipse/biscuit/error/Error.java @@ -401,6 +401,45 @@ public JsonElement toJson() { return FormatError.jsonWrapper(jo); } } + + public static final class InvalidKeySize extends FormatError { + private final int size; + + public InvalidKeySize(int size) { + this.size = size; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InvalidKeySize iss = (InvalidKeySize) o; + + return size == iss.size; + } + + @Override + public int hashCode() { + return Objects.hash(size); + } + + @Override + public String toString() { + return "InvalidKeySize{" + "size=" + size + '}'; + } + + @Override + public JsonElement toJson() { + JsonObject jo = new JsonObject(); + jo.add("InvalidKeySize", new JsonPrimitive(size)); + return FormatError.jsonWrapper(jo); + } + } } public static final class InvalidAuthorityIndex extends Error { diff --git a/src/main/java/org/eclipse/biscuit/token/format/SerializedBiscuit.java b/src/main/java/org/eclipse/biscuit/token/format/SerializedBiscuit.java index 25dcc19e..aa40985a 100644 --- a/src/main/java/org/eclipse/biscuit/token/format/SerializedBiscuit.java +++ b/src/main/java/org/eclipse/biscuit/token/format/SerializedBiscuit.java @@ -115,10 +115,9 @@ static SerializedBiscuit fromBytesInner(Schema.Biscuit data, PublicKey root) * * @param slice * @return SerializedBiscuit - * @throws Error.FormatError.DeserializationError + * @throws Error.FormatError */ - public static SerializedBiscuit deserializeUnsafe(byte[] slice) - throws Error.FormatError.DeserializationError { + public static SerializedBiscuit deserializeUnsafe(byte[] slice) throws Error.FormatError { try { Schema.Biscuit data = Schema.Biscuit.parseFrom(slice); return SerializedBiscuit.deserialize(data); @@ -132,10 +131,9 @@ public static SerializedBiscuit deserializeUnsafe(byte[] slice) * * @param data * @return SerializedBiscuit - * @throws Error.FormatError.DeserializationError + * @throws Error.FormatError */ - private static SerializedBiscuit deserialize(Schema.Biscuit data) - throws Error.FormatError.DeserializationError { + private static SerializedBiscuit deserialize(Schema.Biscuit data) throws Error.FormatError { if (data.getAuthority().hasExternalSignature()) { throw new Error.FormatError.DeserializationError( "the authority block must not contain an external signature"); diff --git a/src/test/java/org/eclipse/biscuit/crypto/SignatureTest.java b/src/test/java/org/eclipse/biscuit/crypto/SignatureTest.java index dd094f2c..0aba0076 100644 --- a/src/test/java/org/eclipse/biscuit/crypto/SignatureTest.java +++ b/src/test/java/org/eclipse/biscuit/crypto/SignatureTest.java @@ -9,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import biscuit.format.schema.Schema; import java.security.InvalidKeyException; @@ -24,7 +25,7 @@ */ public class SignatureTest { @Test - public void testSerialize() { + public void testSerialize() throws Error.FormatError.InvalidKeySize { prTestSerialize(Schema.PublicKey.Algorithm.Ed25519, 32); prTestSerialize( // compressed - 0x02 or 0x03 prefix byte, 32 bytes for X coordinate @@ -32,7 +33,7 @@ public void testSerialize() { } @Test - public void testHex() { + public void testHex() throws Error.FormatError.InvalidKeySize { prGenSigKeys(Schema.PublicKey.Algorithm.SECP256R1); prGenSigKeys(Schema.PublicKey.Algorithm.Ed25519); } @@ -57,8 +58,23 @@ public void testSerializeBiscuit() throws Error { assertDoesNotThrow(() -> unverified.verify(root.getPublicKey())); } + @Test + void testInvalidSepc256r1Key() { + assertThrows( + Error.FormatError.InvalidKeySize.class, + () -> KeyPair.generate(Schema.PublicKey.Algorithm.SECP256R1, "badkey".getBytes())); + } + + @Test + void testInvalidEd25519Key() { + assertThrows( + Error.FormatError.InvalidKeySize.class, + () -> KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, "badkey".getBytes())); + } + private static void prTestSerialize( - Schema.PublicKey.Algorithm algorithm, int expectedPublicKeyLength) { + Schema.PublicKey.Algorithm algorithm, int expectedPublicKeyLength) + throws Error.FormatError.InvalidKeySize { byte[] seed = {1, 2, 3, 4}; SecureRandom rng = new SecureRandom(seed); @@ -110,7 +126,8 @@ private static void prTestThreeMessages(Schema.PublicKey.Algorithm algorithm) assertEquals(Right(null), token3.verify(root.getPublicKey())); } - private static void prGenSigKeys(Schema.PublicKey.Algorithm algorithm) { + private static void prGenSigKeys(Schema.PublicKey.Algorithm algorithm) + throws Error.FormatError.InvalidKeySize { var keypair = KeyPair.generate(algorithm); var pubKey = keypair.getPublicKey(); var privHexString = keypair.toHex(); diff --git a/src/test/java/org/eclipse/biscuit/token/SamplesTest.java b/src/test/java/org/eclipse/biscuit/token/SamplesTest.java index b788b253..768e7fc2 100644 --- a/src/test/java/org/eclipse/biscuit/token/SamplesTest.java +++ b/src/test/java/org/eclipse/biscuit/token/SamplesTest.java @@ -48,7 +48,7 @@ class SamplesTest { final RunLimits runLimits = new RunLimits(500, 100, Duration.ofMillis(500)); @TestFactory - Stream jsonTest() { + Stream jsonTest() throws Error.FormatError.InvalidKeySize { InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("samples/samples.json"); Gson gson = new Gson(); From 821d2c3287aad4fc7164083d194fed15c6cfa025 Mon Sep 17 00:00:00 2001 From: Adam Preuss Date: Fri, 23 May 2025 13:49:39 -0600 Subject: [PATCH 5/5] Add InvalidKey error type for deserializing public keys. --- .../biscuit/crypto/Ed25519PublicKey.java | 11 +++++- .../org/eclipse/biscuit/crypto/PublicKey.java | 9 ++--- .../biscuit/crypto/SECP256R1PublicKey.java | 12 +++++- .../java/org/eclipse/biscuit/error/Error.java | 37 +++++++++++++++++++ .../token/ThirdPartyBlockContents.java | 4 +- .../biscuit/token/builder/parser/Parser.java | 31 +++++++++------- .../biscuit/builder/parser/ParserTest.java | 2 +- .../eclipse/biscuit/crypto/SignatureTest.java | 24 +++++++++--- .../eclipse/biscuit/token/SamplesTest.java | 2 +- 9 files changed, 99 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/eclipse/biscuit/crypto/Ed25519PublicKey.java b/src/main/java/org/eclipse/biscuit/crypto/Ed25519PublicKey.java index 39e77176..5408b8b0 100644 --- a/src/main/java/org/eclipse/biscuit/crypto/Ed25519PublicKey.java +++ b/src/main/java/org/eclipse/biscuit/crypto/Ed25519PublicKey.java @@ -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; @@ -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 diff --git a/src/main/java/org/eclipse/biscuit/crypto/PublicKey.java b/src/main/java/org/eclipse/biscuit/crypto/PublicKey.java index 3d2cc99c..b10aac41 100644 --- a/src/main/java/org/eclipse/biscuit/crypto/PublicKey.java +++ b/src/main/java/org/eclipse/biscuit/crypto/PublicKey.java @@ -18,7 +18,7 @@ public abstract class PublicKey { public interface Factory { - PublicKey load(byte[] bytes); + PublicKey load(byte[] bytes) throws Error.FormatError.InvalidKey; } public static final Factory DEFAULT_ED25519_FACTORY = @@ -32,7 +32,7 @@ public interface Factory { private static final Set 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 ed25519Factory.load(data); } else if (algorithm == Algorithm.SECP256R1) { @@ -42,7 +42,7 @@ public static PublicKey load(Algorithm algorithm, byte[] data) { } } - 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)); } @@ -59,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"); } diff --git a/src/main/java/org/eclipse/biscuit/crypto/SECP256R1PublicKey.java b/src/main/java/org/eclipse/biscuit/crypto/SECP256R1PublicKey.java index 14c0a1ac..5199eab7 100644 --- a/src/main/java/org/eclipse/biscuit/crypto/SECP256R1PublicKey.java +++ b/src/main/java/org/eclipse/biscuit/crypto/SECP256R1PublicKey.java @@ -23,6 +23,8 @@ 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 { @@ -42,9 +44,15 @@ class SECP256R1PublicKey extends PublicKey { 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)); } diff --git a/src/main/java/org/eclipse/biscuit/error/Error.java b/src/main/java/org/eclipse/biscuit/error/Error.java index d5493564..76bad6e0 100644 --- a/src/main/java/org/eclipse/biscuit/error/Error.java +++ b/src/main/java/org/eclipse/biscuit/error/Error.java @@ -440,6 +440,43 @@ public JsonElement toJson() { return FormatError.jsonWrapper(jo); } } + + public static final class InvalidKey extends FormatError { + private final String err; + + public InvalidKey(String e) { + this.err = e; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + InvalidKey other = (InvalidKey) o; + return err.equals(other.err); + } + + @Override + public int hashCode() { + return Objects.hash(err); + } + + @Override + public String toString() { + return "Err(Format(InvalidKey(\"" + this.err + "\"))"; + } + + @Override + public JsonElement toJson() { + JsonObject jo = new JsonObject(); + jo.addProperty("InvalidKey", this.err); + return FormatError.jsonWrapper(jo); + } + } } public static final class InvalidAuthorityIndex extends Error { diff --git a/src/main/java/org/eclipse/biscuit/token/ThirdPartyBlockContents.java b/src/main/java/org/eclipse/biscuit/token/ThirdPartyBlockContents.java index af34dec6..cc76e3c4 100644 --- a/src/main/java/org/eclipse/biscuit/token/ThirdPartyBlockContents.java +++ b/src/main/java/org/eclipse/biscuit/token/ThirdPartyBlockContents.java @@ -39,7 +39,7 @@ public Schema.ThirdPartyBlockContents serialize() throws Error.FormatError.Seria } public static ThirdPartyBlockContents deserialize(Schema.ThirdPartyBlockContents b) - throws Error.FormatError.DeserializationError { + throws Error.FormatError { byte[] payload = b.getPayload().toByteArray(); byte[] signature = b.getExternalSignature().getSignature().toByteArray(); PublicKey publicKey = PublicKey.deserialize(b.getExternalSignature().getPublicKey()); @@ -48,7 +48,7 @@ public static ThirdPartyBlockContents deserialize(Schema.ThirdPartyBlockContents } public static ThirdPartyBlockContents fromBytes(byte[] slice) - throws InvalidProtocolBufferException, Error.FormatError.DeserializationError { + throws InvalidProtocolBufferException, Error.FormatError { return ThirdPartyBlockContents.deserialize(Schema.ThirdPartyBlockContents.parseFrom(slice)); } diff --git a/src/main/java/org/eclipse/biscuit/token/builder/parser/Parser.java b/src/main/java/org/eclipse/biscuit/token/builder/parser/Parser.java index 43febdde..9a2eaa83 100644 --- a/src/main/java/org/eclipse/biscuit/token/builder/parser/Parser.java +++ b/src/main/java/org/eclipse/biscuit/token/builder/parser/Parser.java @@ -41,8 +41,8 @@ private Parser() {} *

If one succeeds it returns Right(Block) else it returns a Map[lineNumber, List[Error]] * * @param s datalog string to parse - * @return Either>, Tuple5, List, - * List, List, List>> + * @return Either>, Tuple5, List, List, + * List, List>> */ public static Either< Map>, @@ -157,16 +157,13 @@ private Parser() {} } /** - * Takes a datalog string with \n as datalog line separator. It - * tries to parse each + * Takes a datalog string with \n as datalog line separator. It tries to parse each * line using fact, rule, check and scope sequentially. * - *

- * If one succeeds it returns Right(Block) else it returns a Map[lineNumber, - * List[Error]] + *

If one succeeds it returns Right(Block) else it returns a Map[lineNumber, List[Error]] * * @param index block index - * @param s datalog string to parse + * @param s datalog string to parse * @return Either>, Block> */ public static Either>, Block> datalog(long index, String s) { @@ -510,19 +507,25 @@ public static Either> scope(String s) { } public static Either> publicKey(String s) { + Schema.PublicKey.Algorithm algorithm; if (s.startsWith("ed25519/")) { s = s.substring("ed25519/".length()); - Tuple2 t = hex(s); - return Either.right( - new Tuple2(t._1, PublicKey.load(Schema.PublicKey.Algorithm.Ed25519, t._2))); + algorithm = Schema.PublicKey.Algorithm.Ed25519; } else if (s.startsWith("secp256r1/")) { s = s.substring("secp256r1/".length()); - Tuple2 t = hex(s); - return Either.right( - new Tuple2(t._1, PublicKey.load(Schema.PublicKey.Algorithm.SECP256R1, t._2))); + algorithm = Schema.PublicKey.Algorithm.SECP256R1; } else { return Either.left(new Error(s, "unrecognized public key prefix")); } + + var t = hex(s); + PublicKey publicKey; + try { + publicKey = PublicKey.load(algorithm, t._2); + } catch (org.eclipse.biscuit.error.Error.FormatError e) { + return Either.left(new Error(s, e.getMessage())); + } + return Either.right(new Tuple2<>(t._1, publicKey)); } public static Either> factPredicate(String s) { diff --git a/src/test/java/org/eclipse/biscuit/builder/parser/ParserTest.java b/src/test/java/org/eclipse/biscuit/builder/parser/ParserTest.java index cbb4f99c..5bd4ef09 100644 --- a/src/test/java/org/eclipse/biscuit/builder/parser/ParserTest.java +++ b/src/test/java/org/eclipse/biscuit/builder/parser/ParserTest.java @@ -251,7 +251,7 @@ void ruleWithFreeExpressionVariables() { } @Test - void testRuleWithScope() { + void testRuleWithScope() throws org.eclipse.biscuit.error.Error.FormatError { Either> res = Parser.rule( "valid_date(\"file1\") <- resource(\"file1\") trusting" diff --git a/src/test/java/org/eclipse/biscuit/crypto/SignatureTest.java b/src/test/java/org/eclipse/biscuit/crypto/SignatureTest.java index 0aba0076..f10ea097 100644 --- a/src/test/java/org/eclipse/biscuit/crypto/SignatureTest.java +++ b/src/test/java/org/eclipse/biscuit/crypto/SignatureTest.java @@ -25,7 +25,7 @@ */ public class SignatureTest { @Test - public void testSerialize() throws Error.FormatError.InvalidKeySize { + public void testSerialize() throws Error.FormatError { prTestSerialize(Schema.PublicKey.Algorithm.Ed25519, 32); prTestSerialize( // compressed - 0x02 or 0x03 prefix byte, 32 bytes for X coordinate @@ -33,7 +33,7 @@ public void testSerialize() throws Error.FormatError.InvalidKeySize { } @Test - public void testHex() throws Error.FormatError.InvalidKeySize { + public void testHex() throws Error.FormatError { prGenSigKeys(Schema.PublicKey.Algorithm.SECP256R1); prGenSigKeys(Schema.PublicKey.Algorithm.Ed25519); } @@ -72,9 +72,22 @@ void testInvalidEd25519Key() { () -> KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, "badkey".getBytes())); } + @Test + void testInvalidSepc256r1PublicKey() { + assertThrows( + Error.FormatError.InvalidKey.class, + () -> PublicKey.load(Schema.PublicKey.Algorithm.SECP256R1, "badkey".getBytes())); + } + + @Test + void testInvalidEd25519PublicKey() { + assertThrows( + Error.FormatError.InvalidKey.class, + () -> PublicKey.load(Schema.PublicKey.Algorithm.Ed25519, "badkey".getBytes())); + } + private static void prTestSerialize( - Schema.PublicKey.Algorithm algorithm, int expectedPublicKeyLength) - throws Error.FormatError.InvalidKeySize { + Schema.PublicKey.Algorithm algorithm, int expectedPublicKeyLength) throws Error.FormatError { byte[] seed = {1, 2, 3, 4}; SecureRandom rng = new SecureRandom(seed); @@ -126,8 +139,7 @@ private static void prTestThreeMessages(Schema.PublicKey.Algorithm algorithm) assertEquals(Right(null), token3.verify(root.getPublicKey())); } - private static void prGenSigKeys(Schema.PublicKey.Algorithm algorithm) - throws Error.FormatError.InvalidKeySize { + private static void prGenSigKeys(Schema.PublicKey.Algorithm algorithm) throws Error.FormatError { var keypair = KeyPair.generate(algorithm); var pubKey = keypair.getPublicKey(); var privHexString = keypair.toHex(); diff --git a/src/test/java/org/eclipse/biscuit/token/SamplesTest.java b/src/test/java/org/eclipse/biscuit/token/SamplesTest.java index 768e7fc2..ff1a5b29 100644 --- a/src/test/java/org/eclipse/biscuit/token/SamplesTest.java +++ b/src/test/java/org/eclipse/biscuit/token/SamplesTest.java @@ -48,7 +48,7 @@ class SamplesTest { final RunLimits runLimits = new RunLimits(500, 100, Duration.ofMillis(500)); @TestFactory - Stream jsonTest() throws Error.FormatError.InvalidKeySize { + Stream jsonTest() throws Error.FormatError { InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("samples/samples.json"); Gson gson = new Gson();