From f34117d830f8726b29d96f5de01ef719c60e9e90 Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Fri, 31 May 2024 14:58:23 +1200 Subject: [PATCH 01/15] delegate private key signing responsibilities for kms wip --- pom.xml | 6 ++ .../biscuit/crypto/Ed25519KeyPair.java | 18 +++- .../biscuitsec/biscuit/crypto/KeyPair.java | 12 ++- .../biscuit/crypto/PrivateKeySigner.java | 44 +++++++++ .../biscuit/crypto/RemoteKeyPair.java | 47 ++++++++++ .../biscuit/crypto/SECP256R1KeyPair.java | 24 ++--- .../org/biscuitsec/biscuit/crypto/Signer.java | 21 +++++ .../org/biscuitsec/biscuit/crypto/Token.java | 28 +----- .../org/biscuitsec/biscuit/token/Biscuit.java | 1 + .../biscuit/token/builder/Biscuit.java | 5 ++ .../token/format/SerializedBiscuit.java | 41 +-------- .../biscuit/token/KmsSignerExampleTest.java | 89 +++++++++++++++++++ 12 files changed, 259 insertions(+), 77 deletions(-) create mode 100644 src/main/java/org/biscuitsec/biscuit/crypto/PrivateKeySigner.java create mode 100644 src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java create mode 100644 src/main/java/org/biscuitsec/biscuit/crypto/Signer.java create mode 100644 src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java diff --git a/pom.xml b/pom.xml index 1ac40a5..922582b 100644 --- a/pom.xml +++ b/pom.xml @@ -266,6 +266,12 @@ junit-jupiter test + + software.amazon.awssdk + kms + 2.25.60 + test + diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java index 22857f7..ec14f66 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java @@ -1,6 +1,6 @@ package org.biscuitsec.biscuit.crypto; -import biscuit.format.schema.Schema; +import biscuit.format.schema.Schema.PublicKey.Algorithm; import net.i2p.crypto.eddsa.EdDSAEngine; import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; @@ -10,16 +10,19 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; import org.biscuitsec.biscuit.token.builder.Utils; +import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.Signature; +import java.security.SignatureException; class Ed25519KeyPair extends KeyPair { private final EdDSAPrivateKey privateKey; private final EdDSAPublicKey publicKey; + private final Signer signer; private static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); @@ -32,6 +35,7 @@ public Ed25519KeyPair(byte[] bytes) { this.privateKey = privKey; this.publicKey = pubKey; + this.signer = new PrivateKeySigner(privKey); } public Ed25519KeyPair(SecureRandom rng) { @@ -46,6 +50,7 @@ public Ed25519KeyPair(SecureRandom rng) { this.privateKey = privKey; this.publicKey = pubKey; + this.signer = new PrivateKeySigner(privKey); } public Ed25519KeyPair(String hex) { @@ -76,12 +81,17 @@ public java.security.PublicKey publicKey() { } @Override - public PrivateKey private_key() { - return privateKey; + public byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.sign(block, Algorithm.Ed25519, publicKey); + } + + @Override + public byte[] sign(byte[] block, byte[] publicKey, byte[] signature) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.sign(block, Algorithm.Ed25519, publicKey, signature); } @Override public PublicKey public_key() { - return new PublicKey(Schema.PublicKey.Algorithm.Ed25519, this.publicKey); + return new PublicKey(Algorithm.Ed25519, this.publicKey); } } diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java index 9014271..a457346 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java @@ -4,9 +4,11 @@ import biscuit.format.schema.Schema.PublicKey.Algorithm; import net.i2p.crypto.eddsa.Utils; +import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.Signature; +import java.security.SignatureException; /** * Private and public key. @@ -41,6 +43,10 @@ public static KeyPair generate(Algorithm algorithm, SecureRandom rng) { } } + public static KeyPair generate(PublicKey publicKey, Signer signer) { + return new RemoteKeyPair(publicKey, signer); + } + public static Signature generateSignature(Algorithm algorithm) throws NoSuchAlgorithmException { if (algorithm == Algorithm.Ed25519) { return Ed25519KeyPair.getSignature(); @@ -57,7 +63,9 @@ public static Signature generateSignature(Algorithm algorithm) throws NoSuchAlgo public abstract java.security.PublicKey publicKey(); - public abstract java.security.PrivateKey private_key(); - public abstract PublicKey public_key(); + + public abstract byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException; + + public abstract byte[] sign(byte[] block, byte[] publicKey, byte[] signature) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException; } diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/PrivateKeySigner.java b/src/main/java/org/biscuitsec/biscuit/crypto/PrivateKeySigner.java new file mode 100644 index 0000000..2b20f6a --- /dev/null +++ b/src/main/java/org/biscuitsec/biscuit/crypto/PrivateKeySigner.java @@ -0,0 +1,44 @@ +package org.biscuitsec.biscuit.crypto; + +import biscuit.format.schema.Schema.PublicKey.Algorithm; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.SignatureException; + +public class PrivateKeySigner implements Signer { + + private final PrivateKey privateKey; + + public PrivateKeySigner(PrivateKey privateKey) { + this.privateKey = privateKey; + } + + @Override + public byte[] sign(byte[] block, Algorithm algorithm, byte[] publicKey) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + + var algorithmBuffer = Signer.getAlgorithmBuffer(algorithm); + var signature = KeyPair.generateSignature(algorithm); + signature.initSign(privateKey); + signature.update(block); + signature.update(algorithmBuffer); + signature.update(publicKey); + return signature.sign(); + } + + @Override + public byte[] sign(byte[] block, Algorithm algorithm, byte[] publicKey, byte[] seal) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + + var algorithmBuffer = Signer.getAlgorithmBuffer(algorithm); + var signature = KeyPair.generateSignature(algorithm); + signature.initSign(privateKey); + signature.update(block); + signature.update(algorithmBuffer); + signature.update(publicKey); + signature.update(seal); + return signature.sign(); + } +} diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java new file mode 100644 index 0000000..e77ad71 --- /dev/null +++ b/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java @@ -0,0 +1,47 @@ +package org.biscuitsec.biscuit.crypto; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.SignatureException; + +public class RemoteKeyPair extends KeyPair { + + private final org.biscuitsec.biscuit.crypto.PublicKey publicKey; + private final Signer signer; + + public RemoteKeyPair(org.biscuitsec.biscuit.crypto.PublicKey publicKey, Signer signer) { + this.publicKey = publicKey; + this.signer = signer; + } + + @Override + public byte[] toBytes() { + throw new RuntimeException("Illegal operation; remote private private key cannot be retrieved."); + } + + @Override + public String toHex() { + throw new RuntimeException("Illegal operation; remote private private key cannot be retrieved."); + } + + @Override + public PublicKey publicKey() { + return publicKey.key; + } + + @Override + public byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.sign(block, this.publicKey.algorithm, publicKey); + } + + @Override + public byte[] sign(byte[] block, byte[] publicKey, byte[] signature) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.sign(block, this.publicKey.algorithm, publicKey, signature); + } + + @Override + public org.biscuitsec.biscuit.crypto.PublicKey public_key() { + return publicKey; + } +} diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java index 8bbeb1c..e28a235 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java @@ -1,6 +1,6 @@ package org.biscuitsec.biscuit.crypto; -import biscuit.format.schema.Schema; +import biscuit.format.schema.Schema.PublicKey.Algorithm; import org.biscuitsec.biscuit.token.builder.Utils; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; @@ -11,25 +11,22 @@ import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.util.BigIntegers; +import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; import java.security.SecureRandom; -import java.security.Security; import java.security.Signature; +import java.security.SignatureException; class SECP256R1KeyPair extends KeyPair { private final BCECPrivateKey privateKey; private final BCECPublicKey publicKey; + private final Signer signer; private static final String ALGORITHM = "ECDSA"; private static final String CURVE = "secp256r1"; private static final ECNamedCurveParameterSpec SECP256R1 = ECNamedCurveTable.getParameterSpec(CURVE); - static { - Security.addProvider(new BouncyCastleProvider()); - } - public SECP256R1KeyPair(byte[] bytes) { var privateKeySpec = new ECPrivateKeySpec(BigIntegers.fromUnsignedByteArray(bytes), SECP256R1); var privateKey = new BCECPrivateKey(ALGORITHM, privateKeySpec, BouncyCastleProvider.CONFIGURATION); @@ -39,6 +36,7 @@ public SECP256R1KeyPair(byte[] bytes) { this.privateKey = privateKey; this.publicKey = publicKey; + this.signer = new PrivateKeySigner(privateKey); } public SECP256R1KeyPair(SecureRandom rng) { @@ -53,6 +51,7 @@ public SECP256R1KeyPair(SecureRandom rng) { this.privateKey = privateKey; this.publicKey = publicKey; + this.signer = new PrivateKeySigner(privateKey); } public SECP256R1KeyPair(String hex) { @@ -85,12 +84,17 @@ public java.security.PublicKey publicKey() { } @Override - public PrivateKey private_key() { - return privateKey; + public byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.sign(block, Algorithm.SECP256R1, publicKey); + } + + @Override + public byte[] sign(byte[] block, byte[] publicKey, byte[] signature) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.sign(block, Algorithm.SECP256R1, publicKey, signature); } @Override public PublicKey public_key() { - return new PublicKey(Schema.PublicKey.Algorithm.SECP256R1, publicKey); + return new PublicKey(Algorithm.SECP256R1, publicKey); } } diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java b/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java new file mode 100644 index 0000000..7e63e74 --- /dev/null +++ b/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java @@ -0,0 +1,21 @@ +package org.biscuitsec.biscuit.crypto; + +import biscuit.format.schema.Schema.PublicKey.Algorithm; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +public interface Signer { + byte[] sign(byte[] block, Algorithm algorithm, byte[] publicKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException; + byte[] sign(byte[] block, Algorithm algorithm, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException; + + static byte[] getAlgorithmBuffer(Algorithm algorithm) { + var algorithmBuffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + algorithmBuffer.putInt(algorithm.getNumber()); + algorithmBuffer.flip(); + return algorithmBuffer.array(); + } +} diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/Token.java b/src/main/java/org/biscuitsec/biscuit/crypto/Token.java index 18ed8ca..f391631 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/Token.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/Token.java @@ -18,17 +18,7 @@ class Token { public final KeyPair next; public Token(KeyPair rootKeyPair, byte[] message, KeyPair next) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - Signature sgr = KeyPair.generateSignature(next.public_key().algorithm); - ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - algo_buf.putInt(Integer.valueOf(next.public_key().algorithm.getNumber())); - algo_buf.flip(); - sgr.initSign(rootKeyPair.private_key()); - sgr.update(message); - sgr.update(algo_buf); - sgr.update(next.public_key().toBytes()); - - byte[] signature = sgr.sign(); - + byte[] signature = rootKeyPair.sign(message, next.public_key().toBytes()); this.blocks = new ArrayList<>(); this.blocks.add(message); this.keys = new ArrayList<>(); @@ -38,8 +28,7 @@ public Token(KeyPair rootKeyPair, byte[] message, KeyPair next) throws NoSuchAlg this.next = next; } - public Token(final ArrayList blocks, final ArrayList keys, final ArrayList signatures, - final KeyPair next) { + public Token(final ArrayList blocks, final ArrayList keys, final ArrayList signatures, final KeyPair next) { this.signatures = signatures; this.blocks = blocks; this.keys = keys; @@ -47,17 +36,7 @@ public Token(final ArrayList blocks, final ArrayList keys, fi } public Token append(KeyPair keyPair, byte[] message) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - Signature sgr = KeyPair.generateSignature(next.public_key().algorithm); - sgr.initSign(this.next.private_key()); - ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - algo_buf.putInt(Integer.valueOf(next.public_key().algorithm.getNumber())); - algo_buf.flip(); - sgr.update(message); - sgr.update(algo_buf); - sgr.update(keyPair.public_key().toBytes()); - - byte[] signature = sgr.sign(); - + byte[] signature = next.sign(message, keyPair.public_key().toBytes()); Token token = new Token(this.blocks, this.keys, this.signatures, keyPair); token.blocks.add(message); token.signatures.add(signature); @@ -74,6 +53,7 @@ public Either verify(PublicKey root) throws NoSuchAlgorithmExceptio PublicKey next_key = this.keys.get(i); byte[] signature = this.signatures.get(i); + // todo have a think ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); algo_buf.putInt(Integer.valueOf(next.public_key().algorithm.getNumber())); algo_buf.flip(); diff --git a/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java b/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java index f614009..fd432dd 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java @@ -5,6 +5,7 @@ import org.biscuitsec.biscuit.crypto.KeyDelegate; import org.biscuitsec.biscuit.crypto.KeyPair; import org.biscuitsec.biscuit.crypto.PublicKey; +import org.biscuitsec.biscuit.crypto.Signer; import org.biscuitsec.biscuit.datalog.SymbolTable; import org.biscuitsec.biscuit.error.Error; import org.biscuitsec.biscuit.token.format.SerializedBiscuit; diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java b/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java index db21a76..2ac1040 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java @@ -1,9 +1,14 @@ package org.biscuitsec.biscuit.token.builder; import org.biscuitsec.biscuit.crypto.KeyPair; +import org.biscuitsec.biscuit.crypto.PrivateKeySigner; import org.biscuitsec.biscuit.crypto.PublicKey; +import org.biscuitsec.biscuit.crypto.Signer; import org.biscuitsec.biscuit.datalog.SchemaVersion; import org.biscuitsec.biscuit.datalog.SymbolTable; +import org.biscuitsec.biscuit.datalog.Check; +import org.biscuitsec.biscuit.datalog.Fact; +import org.biscuitsec.biscuit.datalog.Rule; import org.biscuitsec.biscuit.error.Error; import org.biscuitsec.biscuit.token.Block; import io.vavr.Tuple2; diff --git a/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java b/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java index d2df062..5a38dbe 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java @@ -237,17 +237,8 @@ static public Either make(final org.biscui b.writeTo(stream); byte[] block = stream.toByteArray(); org.biscuitsec.biscuit.crypto.PublicKey next_key = next.public_key(); - ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber())); - algo_buf.flip(); - - Signature sgr = KeyPair.generateSignature(root.public_key().algorithm); - sgr.initSign(root.private_key()); - sgr.update(block); - sgr.update(algo_buf); - sgr.update(next_key.toBytes()); - byte[] signature = sgr.sign(); + byte[] signature = root.sign(block, next_key.toBytes()); SignedBlock signedBlock = new SignedBlock(block, next_key, signature, Option.none()); Proof proof = new Proof(next); @@ -270,21 +261,9 @@ public Either append(final org.biscuitsec. byte[] block = stream.toByteArray(); org.biscuitsec.biscuit.crypto.PublicKey next_key = next.public_key(); - ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber())); - algo_buf.flip(); - Signature sgr = KeyPair.generateSignature(next_key.algorithm); - sgr.initSign(this.proof.secretKey.get().private_key()); - sgr.update(block); - if(externalSignature.isDefined()) { - sgr.update(externalSignature.get().signature); - } - sgr.update(algo_buf); - sgr.update(next_key.toBytes()); - byte[] signature = sgr.sign(); - - SignedBlock signedBlock = new SignedBlock(block, next_key, signature, externalSignature); + byte[] signature = proof.secretKey.get().sign(block, next_key.toBytes()); + SignedBlock signedBlock = new SignedBlock(block, next_key, signature, Option.none()); ArrayList blocks = new ArrayList<>(); for (SignedBlock bl : this.blocks) { @@ -496,19 +475,7 @@ public Either seal() throws InvalidKeyException, NoSuchAlgorithmExc block = this.blocks.get(this.blocks.size() - 1); } - Signature sgr = KeyPair.generateSignature(block.key.algorithm); - ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - algo_buf.putInt(Integer.valueOf(block.key.algorithm.getNumber())); - algo_buf.flip(); - - - sgr.initSign(this.proof.secretKey.get().private_key()); - sgr.update(block.block); - sgr.update(algo_buf); - sgr.update(block.key.toBytes()); - sgr.update(block.signature); - - byte[] signature = sgr.sign(); + byte[] signature = proof.secretKey.get().sign(block.block, block.key.toBytes(), block.signature); this.proof.secretKey = Option.none(); this.proof.signature = Option.some(signature); diff --git a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java new file mode 100644 index 0000000..076be32 --- /dev/null +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -0,0 +1,89 @@ +package org.biscuitsec.biscuit.token; + +import biscuit.format.schema.Schema; +import net.i2p.crypto.eddsa.Utils; +import org.biscuitsec.biscuit.crypto.KeyPair; +import org.biscuitsec.biscuit.crypto.PublicKey; +import org.biscuitsec.biscuit.crypto.Signer; +import org.biscuitsec.biscuit.error.Error; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.SigningAlgorithmSpec; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +public class KmsSignerExampleTest { + + static class KmsSigner implements Signer { + + private final KmsClient kmsClient = KmsClient.create(); + + @Override + public byte[] sign(byte[] block, Schema.PublicKey.Algorithm algorithm, byte[] publicKey) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { +// var algorithmBuffer = getAlgorithmBuffer(algorithm); +// var size = block.length + algorithmBuffer.capacity() + publicKey.length; +// var payload = new byte[size]; +// System.arraycopy(block, 0, payload, 0, block.length); +// System.arraycopy(algorithmBuffer.array(), 0, payload, block.length, algorithmBuffer.capacity()); +// System.arraycopy(publicKey, 0, payload, block.length + algorithmBuffer.capacity(), publicKey.length); +// var response = kmsClient.sign(builder -> +// builder.keyId("yourKeyId") +// .signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256) +// .message(SdkBytes.fromByteArray(payload))); +// return response.signature().asByteArray(); + return new byte[0]; + } + + @Override + public byte[] sign(byte[] block, Schema.PublicKey.Algorithm algorithm, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + return new byte[0]; + } + } + + @BeforeAll + public static void setup() { + System.setProperty("aws.region", "ap-southeast-2"); + } + + @AfterAll + public static void teardown() { + System.clearProperty("aws.region"); + } + + @Test + public void createAndSerializeToken() throws Error { + var signer = new KmsSigner(); + var publicKey = new PublicKey(Schema.PublicKey.Algorithm.SECP256R1, publicKeyBytes); + var keyPair = KeyPair.generate(publicKey, signer); + var biscuit = Biscuit.builder(keyPair) + .add_authority_fact("user(\"1234\")") + .add_authority_check("check if operation(\"read\")") + .build(); + } + + @Test + public void test() throws Error { + var root = KeyPair.generate(Schema.PublicKey.Algorithm.SECP256R1); + var biscuit = Biscuit.builder(root) + .add_authority_fact("user(\"1234\")") + .add_authority_check("check if operation(\"read\")") + .build(); + + System.out.println("Root private key: " + root.toHex()); + System.out.println("Root public key: " + root.public_key().toHex()); + var serializedBytes = biscuit.serialize(); + try (var file = new FileOutputStream("token.bc")) { + file.write(serializedBytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} From 0a9e3d67c63ad43053af9e7da976c20a75dead0a Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Fri, 31 May 2024 15:52:20 +1200 Subject: [PATCH 02/15] tidy up the test showing the use case --- pom.xml | 14 +- .../biscuit/crypto/Ed25519KeyPair.java | 28 ++-- .../biscuitsec/biscuit/crypto/KeyPair.java | 19 ++- .../biscuit/crypto/PrivateKeySigner.java | 33 +--- .../biscuit/crypto/RemoteKeyPair.java | 13 +- .../biscuit/crypto/SECP256R1KeyPair.java | 23 +-- .../org/biscuitsec/biscuit/crypto/Signer.java | 14 +- .../org/biscuitsec/biscuit/token/Biscuit.java | 1 - .../biscuit/token/KmsSignerExampleTest.java | 147 ++++++++++++------ 9 files changed, 173 insertions(+), 119 deletions(-) diff --git a/pom.xml b/pom.xml index 922582b..3ef42f4 100644 --- a/pom.xml +++ b/pom.xml @@ -269,7 +269,19 @@ software.amazon.awssdk kms - 2.25.60 + 2.25.65 + test + + + software.amazon.awssdk + utils + 2.25.65 + test + + + software.amazon.awssdk + sso + 2.25.65 test diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java index ec14f66..6874428 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java @@ -13,10 +13,10 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; +import java.util.Optional; class Ed25519KeyPair extends KeyPair { @@ -24,33 +24,33 @@ class Ed25519KeyPair extends KeyPair { private final EdDSAPublicKey publicKey; private final Signer signer; - private static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); + private static final EdDSANamedCurveSpec CURVE_SPEC = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); public Ed25519KeyPair(byte[] bytes) { - EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(bytes, ed25519); + EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(bytes, CURVE_SPEC); EdDSAPrivateKey privKey = new EdDSAPrivateKey(privKeySpec); - EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKey.getA(), ed25519); + EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKey.getA(), CURVE_SPEC); EdDSAPublicKey pubKey = new EdDSAPublicKey(pubKeySpec); this.privateKey = privKey; this.publicKey = pubKey; - this.signer = new PrivateKeySigner(privKey); + this.signer = new PrivateKeySigner(Algorithm.Ed25519, privKey); } public Ed25519KeyPair(SecureRandom rng) { byte[] b = new byte[32]; rng.nextBytes(b); - EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(b, ed25519); + EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(b, CURVE_SPEC); EdDSAPrivateKey privKey = new EdDSAPrivateKey(privKeySpec); - EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKey.getA(), ed25519); + EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKey.getA(), CURVE_SPEC); EdDSAPublicKey pubKey = new EdDSAPublicKey(pubKeySpec); this.privateKey = privKey; this.publicKey = pubKey; - this.signer = new PrivateKeySigner(privKey); + this.signer = new PrivateKeySigner(Algorithm.Ed25519, privKey); } public Ed25519KeyPair(String hex) { @@ -58,11 +58,11 @@ public Ed25519KeyPair(String hex) { } public static java.security.PublicKey decode(byte[] data) { - return new EdDSAPublicKey(new EdDSAPublicKeySpec(data, ed25519)); + return new EdDSAPublicKey(new EdDSAPublicKeySpec(data, CURVE_SPEC)); } public static Signature getSignature() throws NoSuchAlgorithmException { - return new EdDSAEngine(MessageDigest.getInstance(ed25519.getHashAlgorithm())); + return new EdDSAEngine(MessageDigest.getInstance(CURVE_SPEC.getHashAlgorithm())); } @Override @@ -82,12 +82,14 @@ public java.security.PublicKey publicKey() { @Override public byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - return signer.sign(block, Algorithm.Ed25519, publicKey); + var bytes = toSigningFormat(block, Algorithm.Ed25519, publicKey, Optional.empty()); + return signer.sign(bytes); } @Override - public byte[] sign(byte[] block, byte[] publicKey, byte[] signature) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - return signer.sign(block, Algorithm.Ed25519, publicKey, signature); + public byte[] sign(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + var bytes = toSigningFormat(block, Algorithm.Ed25519, publicKey, Optional.of(seal)); + return signer.sign(bytes); } @Override diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java index a457346..a240f39 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java @@ -4,11 +4,14 @@ import biscuit.format.schema.Schema.PublicKey.Algorithm; import net.i2p.crypto.eddsa.Utils; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; +import java.util.Optional; /** * Private and public key. @@ -67,5 +70,19 @@ public static Signature generateSignature(Algorithm algorithm) throws NoSuchAlgo public abstract byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException; - public abstract byte[] sign(byte[] block, byte[] publicKey, byte[] signature) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException; + public abstract byte[] sign(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException; + + protected static byte[] toSigningFormat(byte[] block, Algorithm algorithm, byte[] publicKey, Optional seal) { + var algorithmBuffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + algorithmBuffer.putInt(algorithm.getNumber()); + algorithmBuffer.flip(); + var algorithmBytes = algorithmBuffer.array(); + + var payload = new byte[block.length + algorithmBytes.length + publicKey.length + seal.orElse(new byte[0]).length]; + System.arraycopy(block, 0, payload, 0, block.length); + System.arraycopy(algorithmBytes, 0, payload, block.length, algorithmBytes.length); + System.arraycopy(publicKey, 0, payload, block.length + algorithmBytes.length, publicKey.length); + seal.ifPresent(bytes -> System.arraycopy(bytes, 0, payload, block.length + algorithmBytes.length + publicKey.length, bytes.length)); + return payload; + } } diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/PrivateKeySigner.java b/src/main/java/org/biscuitsec/biscuit/crypto/PrivateKeySigner.java index 2b20f6a..3278ab1 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/PrivateKeySigner.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/PrivateKeySigner.java @@ -9,36 +9,19 @@ public class PrivateKeySigner implements Signer { + private final Algorithm algorithm; private final PrivateKey privateKey; - public PrivateKeySigner(PrivateKey privateKey) { + public PrivateKeySigner(Algorithm algorithm, PrivateKey privateKey) { + this.algorithm = algorithm; this.privateKey = privateKey; } @Override - public byte[] sign(byte[] block, Algorithm algorithm, byte[] publicKey) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - - var algorithmBuffer = Signer.getAlgorithmBuffer(algorithm); - var signature = KeyPair.generateSignature(algorithm); - signature.initSign(privateKey); - signature.update(block); - signature.update(algorithmBuffer); - signature.update(publicKey); - return signature.sign(); - } - - @Override - public byte[] sign(byte[] block, Algorithm algorithm, byte[] publicKey, byte[] seal) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - - var algorithmBuffer = Signer.getAlgorithmBuffer(algorithm); - var signature = KeyPair.generateSignature(algorithm); - signature.initSign(privateKey); - signature.update(block); - signature.update(algorithmBuffer); - signature.update(publicKey); - signature.update(seal); - return signature.sign(); + public byte[] sign(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + var sgr = KeyPair.generateSignature(algorithm); + sgr.initSign(privateKey); + sgr.update(bytes); + return sgr.sign(); } } diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java index e77ad71..cffbfea 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java @@ -4,6 +4,7 @@ import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.SignatureException; +import java.util.Optional; public class RemoteKeyPair extends KeyPair { @@ -17,12 +18,12 @@ public RemoteKeyPair(org.biscuitsec.biscuit.crypto.PublicKey publicKey, Signer s @Override public byte[] toBytes() { - throw new RuntimeException("Illegal operation; remote private private key cannot be retrieved."); + throw new RuntimeException("Illegal operation; remote private private key cannot be accessed."); } @Override public String toHex() { - throw new RuntimeException("Illegal operation; remote private private key cannot be retrieved."); + throw new RuntimeException("Illegal operation; remote private private key cannot be accessed."); } @Override @@ -32,12 +33,14 @@ public PublicKey publicKey() { @Override public byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - return signer.sign(block, this.publicKey.algorithm, publicKey); + var bytes = toSigningFormat(block, this.publicKey.algorithm, publicKey, Optional.empty()); + return signer.sign(bytes); } @Override - public byte[] sign(byte[] block, byte[] publicKey, byte[] signature) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - return signer.sign(block, this.publicKey.algorithm, publicKey, signature); + public byte[] sign(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + var bytes = toSigningFormat(block, this.publicKey.algorithm, publicKey, Optional.of(seal)); + return signer.sign(bytes); } @Override diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java index e28a235..39d87de 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java @@ -16,6 +16,7 @@ import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; +import java.util.Optional; class SECP256R1KeyPair extends KeyPair { @@ -25,33 +26,33 @@ class SECP256R1KeyPair extends KeyPair { private static final String ALGORITHM = "ECDSA"; private static final String CURVE = "secp256r1"; - private static final ECNamedCurveParameterSpec SECP256R1 = ECNamedCurveTable.getParameterSpec(CURVE); + private static final ECNamedCurveParameterSpec CURVE_SPEC = ECNamedCurveTable.getParameterSpec(CURVE); public SECP256R1KeyPair(byte[] bytes) { - var privateKeySpec = new ECPrivateKeySpec(BigIntegers.fromUnsignedByteArray(bytes), SECP256R1); + var privateKeySpec = new ECPrivateKeySpec(BigIntegers.fromUnsignedByteArray(bytes), CURVE_SPEC); var privateKey = new BCECPrivateKey(ALGORITHM, privateKeySpec, BouncyCastleProvider.CONFIGURATION); - var publicKeySpec = new ECPublicKeySpec(SECP256R1.getG().multiply(privateKeySpec.getD()), SECP256R1); + var publicKeySpec = new ECPublicKeySpec(CURVE_SPEC.getG().multiply(privateKeySpec.getD()), CURVE_SPEC); var publicKey = new BCECPublicKey(ALGORITHM, publicKeySpec, BouncyCastleProvider.CONFIGURATION); this.privateKey = privateKey; this.publicKey = publicKey; - this.signer = new PrivateKeySigner(privateKey); + this.signer = new PrivateKeySigner(Algorithm.SECP256R1, privateKey); } public SECP256R1KeyPair(SecureRandom rng) { byte[] bytes = new byte[32]; rng.nextBytes(bytes); - var privateKeySpec = new ECPrivateKeySpec(BigIntegers.fromUnsignedByteArray(bytes), SECP256R1); + var privateKeySpec = new ECPrivateKeySpec(BigIntegers.fromUnsignedByteArray(bytes), CURVE_SPEC); var privateKey = new BCECPrivateKey(ALGORITHM, privateKeySpec, BouncyCastleProvider.CONFIGURATION); - var publicKeySpec = new ECPublicKeySpec(SECP256R1.getG().multiply(privateKeySpec.getD()), SECP256R1); + var publicKeySpec = new ECPublicKeySpec(CURVE_SPEC.getG().multiply(privateKeySpec.getD()), CURVE_SPEC); var publicKey = new BCECPublicKey(ALGORITHM, publicKeySpec, BouncyCastleProvider.CONFIGURATION); this.privateKey = privateKey; this.publicKey = publicKey; - this.signer = new PrivateKeySigner(privateKey); + this.signer = new PrivateKeySigner(Algorithm.SECP256R1, privateKey); } public SECP256R1KeyPair(String hex) { @@ -85,12 +86,14 @@ public java.security.PublicKey publicKey() { @Override public byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - return signer.sign(block, Algorithm.SECP256R1, publicKey); + var bytes = toSigningFormat(block, Algorithm.SECP256R1, publicKey, Optional.empty()); + return signer.sign(bytes); } @Override - public byte[] sign(byte[] block, byte[] publicKey, byte[] signature) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - return signer.sign(block, Algorithm.SECP256R1, publicKey, signature); + public byte[] sign(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + var bytes = toSigningFormat(block, Algorithm.SECP256R1, publicKey, Optional.of(seal)); + return signer.sign(bytes); } @Override diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java b/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java index 7e63e74..6f646b0 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java @@ -1,21 +1,9 @@ package org.biscuitsec.biscuit.crypto; -import biscuit.format.schema.Schema.PublicKey.Algorithm; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; public interface Signer { - byte[] sign(byte[] block, Algorithm algorithm, byte[] publicKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException; - byte[] sign(byte[] block, Algorithm algorithm, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException; - - static byte[] getAlgorithmBuffer(Algorithm algorithm) { - var algorithmBuffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - algorithmBuffer.putInt(algorithm.getNumber()); - algorithmBuffer.flip(); - return algorithmBuffer.array(); - } + byte[] sign(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException; } diff --git a/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java b/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java index fd432dd..f614009 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java @@ -5,7 +5,6 @@ import org.biscuitsec.biscuit.crypto.KeyDelegate; import org.biscuitsec.biscuit.crypto.KeyPair; import org.biscuitsec.biscuit.crypto.PublicKey; -import org.biscuitsec.biscuit.crypto.Signer; import org.biscuitsec.biscuit.datalog.SymbolTable; import org.biscuitsec.biscuit.error.Error; import org.biscuitsec.biscuit.token.format.SerializedBiscuit; diff --git a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java index 076be32..93f3c06 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -1,88 +1,135 @@ package org.biscuitsec.biscuit.token; -import biscuit.format.schema.Schema; +import biscuit.format.schema.Schema.PublicKey.Algorithm; import net.i2p.crypto.eddsa.Utils; import org.biscuitsec.biscuit.crypto.KeyPair; import org.biscuitsec.biscuit.crypto.PublicKey; import org.biscuitsec.biscuit.crypto.Signer; -import org.biscuitsec.biscuit.error.Error; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.services.kms.KmsClient; import software.amazon.awssdk.services.kms.model.SigningAlgorithmSpec; -import java.io.FileOutputStream; +import java.io.ByteArrayInputStream; import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; +import java.util.function.Function; -public class KmsSignerExampleTest { +import static java.lang.ProcessBuilder.Redirect; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - static class KmsSigner implements Signer { - - private final KmsClient kmsClient = KmsClient.create(); - - @Override - public byte[] sign(byte[] block, Schema.PublicKey.Algorithm algorithm, byte[] publicKey) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { -// var algorithmBuffer = getAlgorithmBuffer(algorithm); -// var size = block.length + algorithmBuffer.capacity() + publicKey.length; -// var payload = new byte[size]; -// System.arraycopy(block, 0, payload, 0, block.length); -// System.arraycopy(algorithmBuffer.array(), 0, payload, block.length, algorithmBuffer.capacity()); -// System.arraycopy(publicKey, 0, payload, block.length + algorithmBuffer.capacity(), publicKey.length); -// var response = kmsClient.sign(builder -> -// builder.keyId("yourKeyId") -// .signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256) -// .message(SdkBytes.fromByteArray(payload))); -// return response.signature().asByteArray(); - return new byte[0]; - } - @Override - public byte[] sign(byte[] block, Schema.PublicKey.Algorithm algorithm, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - return new byte[0]; - } - } +public class KmsSignerExampleTest { + + private static final String AWS_PROFILE = null; + private static final String AWS_REGION = null; + private static final String KMS_KEY_ARN = null; @BeforeAll public static void setup() { - System.setProperty("aws.region", "ap-southeast-2"); + System.setProperty("aws.region", AWS_REGION); + System.setProperty("aws.profile", AWS_PROFILE); } @AfterAll public static void teardown() { System.clearProperty("aws.region"); + System.clearProperty("aws.profile"); } @Test - public void createAndSerializeToken() throws Error { - var signer = new KmsSigner(); - var publicKey = new PublicKey(Schema.PublicKey.Algorithm.SECP256R1, publicKeyBytes); - var keyPair = KeyPair.generate(publicKey, signer); + public void createWithRemoteSigner() throws Exception { + Function getPublicKeyBytes = (keyId) -> { + try (var kmsClient = KmsClient.create()) { + var publicKeyResponse = kmsClient.getPublicKey(b -> b.keyId(keyId).build()); + return Convert.DEREncodedX509PkToSEC1CompressedEncodedPk(publicKeyResponse.publicKey().asByteArray()); + } + }; + + byte[] publicKeyBytes; + try { + publicKeyBytes = getPublicKeyBytes.apply(KMS_KEY_ARN); + } catch (SdkClientException e) { + sso(); + publicKeyBytes = getPublicKeyBytes.apply(KMS_KEY_ARN); + } + + var publicKey = new PublicKey(Algorithm.SECP256R1, publicKeyBytes); + var keyPair = KeyPair.generate(publicKey, new Signer() { + + @Override + public byte[] sign(byte[] bytes) { + try (var kmsClient = KmsClient.create()) { + var response = kmsClient.sign(builder -> builder + .keyId(KMS_KEY_ARN) + .signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256) + .message(SdkBytes.fromByteArray(bytes)) + ); + var signature = response.signature().asByteArray(); + var hex = Utils.bytesToHex(signature); + System.out.println("Signature: " + hex); + + var sgr = KeyPair.generateSignature(Algorithm.SECP256R1); + sgr.initVerify(publicKey.key); + sgr.update(bytes); + var verified = sgr.verify(signature); + System.out.println("Verified: " + verified); + + return signature; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + var biscuit = Biscuit.builder(keyPair) .add_authority_fact("user(\"1234\")") .add_authority_check("check if operation(\"read\")") .build(); + assertDoesNotThrow(biscuit::serialize); } - @Test - public void test() throws Error { - var root = KeyPair.generate(Schema.PublicKey.Algorithm.SECP256R1); - var biscuit = Biscuit.builder(root) - .add_authority_fact("user(\"1234\")") - .add_authority_check("check if operation(\"read\")") - .build(); + private static class Convert { + + // converts DER-encoded X.509 public key to SEC1 compressed encoded format + static byte[] DEREncodedX509PkToSEC1CompressedEncodedPk(byte[] publicKeyBytes) { + try (ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(publicKeyBytes))) { + // Parse the ASN.1 encoded public key bytes + var asn1Primitive = asn1InputStream.readObject(); + var subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(asn1Primitive); + + // Extract the public key data + var publicKeyDataBitString = subjectPublicKeyInfo.getPublicKeyData(); + byte[] publicKeyData = publicKeyDataBitString.getBytes(); + + // Parse the public key data to get the elliptic curve point + var ecParameters = ECNamedCurveTable.getByName("secp256r1"); + var ecPoint = ecParameters.getCurve().decodePoint(publicKeyData); + return ecPoint.getEncoded(true); + + } catch (IOException e) { + throw new RuntimeException("Error converting DER-encoded X.509 to SEC1 compressed format", e); + } + } + } - System.out.println("Root private key: " + root.toHex()); - System.out.println("Root public key: " + root.public_key().toHex()); - var serializedBytes = biscuit.serialize(); - try (var file = new FileOutputStream("token.bc")) { - file.write(serializedBytes); - } catch (IOException e) { + private void sso() { + try { + var code = new ProcessBuilder() + .command("aws", "sso", "login", "--profile", AWS_PROFILE) + .redirectOutput(Redirect.INHERIT) + .redirectError(Redirect.INHERIT) + .start() + .waitFor(); + if (code != 0) { + throw new RuntimeException("SSO login failed"); + } + } catch (InterruptedException | IOException e) { throw new RuntimeException(e); } } From da9dffb7bb2a23fa8dbbd7b96a003b22868fe611 Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Thu, 6 Jun 2024 15:00:59 +1200 Subject: [PATCH 03/15] remove unused dep, comment out test annotation for example --- pom.xml | 6 ------ src/main/java/org/biscuitsec/biscuit/crypto/Token.java | 1 - .../org/biscuitsec/biscuit/token/KmsSignerExampleTest.java | 2 +- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 3ef42f4..4f7c1e3 100644 --- a/pom.xml +++ b/pom.xml @@ -272,12 +272,6 @@ 2.25.65 test - - software.amazon.awssdk - utils - 2.25.65 - test - software.amazon.awssdk sso diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/Token.java b/src/main/java/org/biscuitsec/biscuit/crypto/Token.java index f391631..0513a3a 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/Token.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/Token.java @@ -53,7 +53,6 @@ public Either verify(PublicKey root) throws NoSuchAlgorithmExceptio PublicKey next_key = this.keys.get(i); byte[] signature = this.signatures.get(i); - // todo have a think ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); algo_buf.putInt(Integer.valueOf(next.public_key().algorithm.getNumber())); algo_buf.flip(); diff --git a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java index 93f3c06..8eff1c5 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -42,7 +42,7 @@ public static void teardown() { System.clearProperty("aws.profile"); } - @Test +// @Test public void createWithRemoteSigner() throws Exception { Function getPublicKeyBytes = (keyId) -> { try (var kmsClient = KmsClient.create()) { From e25b622a98ab9ce8b82c7d8365ffae7cb65894bb Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Fri, 7 Jun 2024 07:35:48 +1200 Subject: [PATCH 04/15] unused imports --- src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java b/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java index 2ac1040..27e9313 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java @@ -1,7 +1,6 @@ package org.biscuitsec.biscuit.token.builder; import org.biscuitsec.biscuit.crypto.KeyPair; -import org.biscuitsec.biscuit.crypto.PrivateKeySigner; import org.biscuitsec.biscuit.crypto.PublicKey; import org.biscuitsec.biscuit.crypto.Signer; import org.biscuitsec.biscuit.datalog.SchemaVersion; From aadd86b2b4b2c92f7c30215085d2514f4424acc1 Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Fri, 7 Jun 2024 10:22:37 +1200 Subject: [PATCH 05/15] catch specific exceptions --- .../org/biscuitsec/biscuit/token/KmsSignerExampleTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java index 8eff1c5..d8c5f1d 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -18,6 +18,9 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; import java.util.function.Function; import static java.lang.ProcessBuilder.Redirect; @@ -81,7 +84,7 @@ public byte[] sign(byte[] bytes) { System.out.println("Verified: " + verified); return signature; - } catch (Exception e) { + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { throw new RuntimeException(e); } } From 02e9b97ab7975c108c8e0fc9b04fdb331f5974f1 Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Fri, 7 Jun 2024 10:24:18 +1200 Subject: [PATCH 06/15] throws specific errors --- .../org/biscuitsec/biscuit/token/KmsSignerExampleTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java index d8c5f1d..d02cb85 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -5,6 +5,7 @@ import org.biscuitsec.biscuit.crypto.KeyPair; import org.biscuitsec.biscuit.crypto.PublicKey; import org.biscuitsec.biscuit.crypto.Signer; +import org.biscuitsec.biscuit.error.Error; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x9.ECNamedCurveTable; @@ -46,7 +47,7 @@ public static void teardown() { } // @Test - public void createWithRemoteSigner() throws Exception { + public void createWithRemoteSigner() throws Error { Function getPublicKeyBytes = (keyId) -> { try (var kmsClient = KmsClient.create()) { var publicKeyResponse = kmsClient.getPublicKey(b -> b.keyId(keyId).build()); From b5bcf76b254b20705d83a237771f28277466d226 Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Fri, 7 Jun 2024 10:45:16 +1200 Subject: [PATCH 07/15] fail if not verified --- .../org/biscuitsec/biscuit/token/KmsSignerExampleTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java index d02cb85..e55a3b8 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -26,6 +26,7 @@ import static java.lang.ProcessBuilder.Redirect; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.fail; public class KmsSignerExampleTest { @@ -84,6 +85,10 @@ public byte[] sign(byte[] bytes) { var verified = sgr.verify(signature); System.out.println("Verified: " + verified); + if (!verified) { + fail("Signature verification failed"); + } + return signature; } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { throw new RuntimeException(e); From e4a3725ca350288b0e8936351dab122a355db318 Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Fri, 7 Jun 2024 11:43:47 +1200 Subject: [PATCH 08/15] unnecessary wrapper class --- .../biscuit/token/KmsSignerExampleTest.java | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java index e55a3b8..8ffe20f 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -52,7 +52,7 @@ public void createWithRemoteSigner() throws Error { Function getPublicKeyBytes = (keyId) -> { try (var kmsClient = KmsClient.create()) { var publicKeyResponse = kmsClient.getPublicKey(b -> b.keyId(keyId).build()); - return Convert.DEREncodedX509PkToSEC1CompressedEncodedPk(publicKeyResponse.publicKey().asByteArray()); + return convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(publicKeyResponse.publicKey().asByteArray()); } }; @@ -103,27 +103,23 @@ public byte[] sign(byte[] bytes) { assertDoesNotThrow(biscuit::serialize); } - private static class Convert { + static byte[] convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(byte[] publicKeyBytes) { + try (ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(publicKeyBytes))) { - // converts DER-encoded X.509 public key to SEC1 compressed encoded format - static byte[] DEREncodedX509PkToSEC1CompressedEncodedPk(byte[] publicKeyBytes) { - try (ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(publicKeyBytes))) { - // Parse the ASN.1 encoded public key bytes - var asn1Primitive = asn1InputStream.readObject(); - var subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(asn1Primitive); + // Parse the ASN.1 encoded public key bytes + var asn1Primitive = asn1InputStream.readObject(); + var subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(asn1Primitive); - // Extract the public key data - var publicKeyDataBitString = subjectPublicKeyInfo.getPublicKeyData(); - byte[] publicKeyData = publicKeyDataBitString.getBytes(); + // Extract the public key data + var publicKeyDataBitString = subjectPublicKeyInfo.getPublicKeyData(); + byte[] publicKeyData = publicKeyDataBitString.getBytes(); - // Parse the public key data to get the elliptic curve point - var ecParameters = ECNamedCurveTable.getByName("secp256r1"); - var ecPoint = ecParameters.getCurve().decodePoint(publicKeyData); - return ecPoint.getEncoded(true); - - } catch (IOException e) { - throw new RuntimeException("Error converting DER-encoded X.509 to SEC1 compressed format", e); - } + // Parse the public key data to get the elliptic curve point + var ecParameters = ECNamedCurveTable.getByName("secp256r1"); + var ecPoint = ecParameters.getCurve().decodePoint(publicKeyData); + return ecPoint.getEncoded(true); + } catch (IOException e) { + throw new RuntimeException("Error converting DER-encoded X.509 to SEC1 compressed format", e); } } From e1fadcafbd511470b8c246cf2d067a685f89c81a Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Fri, 7 Jun 2024 13:04:13 +1200 Subject: [PATCH 09/15] update the example to use localstack --- pom.xml | 12 +- .../biscuit/token/KmsSignerExampleTest.java | 131 +++++++----------- 2 files changed, 59 insertions(+), 84 deletions(-) diff --git a/pom.xml b/pom.xml index 4f7c1e3..d4cfe1b 100644 --- a/pom.xml +++ b/pom.xml @@ -273,9 +273,15 @@ test - software.amazon.awssdk - sso - 2.25.65 + org.testcontainers + junit-jupiter + 1.19.8 + test + + + org.testcontainers + localstack + 1.19.8 test diff --git a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java index 8ffe20f..f88e298 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -1,7 +1,6 @@ package org.biscuitsec.biscuit.token; import biscuit.format.schema.Schema.PublicKey.Algorithm; -import net.i2p.crypto.eddsa.Utils; import org.biscuitsec.biscuit.crypto.KeyPair; import org.biscuitsec.biscuit.crypto.PublicKey; import org.biscuitsec.biscuit.crypto.Signer; @@ -9,12 +8,19 @@ import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x9.ECNamedCurveTable; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.localstack.LocalStackContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.KeySpec; +import software.amazon.awssdk.services.kms.model.KeyUsageType; import software.amazon.awssdk.services.kms.model.SigningAlgorithmSpec; import java.io.ByteArrayInputStream; @@ -22,88 +28,67 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; -import java.util.function.Function; -import static java.lang.ProcessBuilder.Redirect; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.fail; - +@Testcontainers public class KmsSignerExampleTest { - private static final String AWS_PROFILE = null; - private static final String AWS_REGION = null; - private static final String KMS_KEY_ARN = null; + private static final DockerImageName LOCALSTACK_IMAGE = DockerImageName.parse("localstack/localstack:3.0.1"); - @BeforeAll - public static void setup() { - System.setProperty("aws.region", AWS_REGION); - System.setProperty("aws.profile", AWS_PROFILE); - } + @Container + public static LocalStackContainer LOCALSTACK = new LocalStackContainer(LOCALSTACK_IMAGE) + .withServices(LocalStackContainer.Service.KMS); - @AfterAll - public static void teardown() { - System.clearProperty("aws.region"); - System.clearProperty("aws.profile"); - } + private KmsClient kmsClient; + private String kmsKeyId; -// @Test - public void createWithRemoteSigner() throws Error { - Function getPublicKeyBytes = (keyId) -> { - try (var kmsClient = KmsClient.create()) { - var publicKeyResponse = kmsClient.getPublicKey(b -> b.keyId(keyId).build()); - return convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(publicKeyResponse.publicKey().asByteArray()); - } - }; + @BeforeEach + public void setup() { + var credentials = AwsBasicCredentials.create(LOCALSTACK.getAccessKey(), LOCALSTACK.getSecretKey()); + kmsClient = KmsClient.builder() + .endpointOverride(LOCALSTACK.getEndpointOverride(LocalStackContainer.Service.KMS)) + .credentialsProvider(StaticCredentialsProvider.create(credentials)) + .region(Region.of(LOCALSTACK.getRegion())) + .build(); - byte[] publicKeyBytes; - try { - publicKeyBytes = getPublicKeyBytes.apply(KMS_KEY_ARN); - } catch (SdkClientException e) { - sso(); - publicKeyBytes = getPublicKeyBytes.apply(KMS_KEY_ARN); - } + // ECC_NIST_P256 == SECP256R1 + kmsKeyId = kmsClient.createKey(b -> b + .keySpec(KeySpec.ECC_NIST_P256) + .keyUsage(KeyUsageType.SIGN_VERIFY) + .build() + ).keyMetadata().keyId(); + } - var publicKey = new PublicKey(Algorithm.SECP256R1, publicKeyBytes); + @Test + public void testCreateBiscuitWithRemoteSigner() throws Error, NoSuchAlgorithmException, SignatureException, InvalidKeyException { + var getPublicKeyResponse = kmsClient.getPublicKey(b -> b.keyId(kmsKeyId).build()); + var x509EncodedPublicKey = getPublicKeyResponse.publicKey().asByteArray(); + var sec1CompressedEncodedPublicKey = convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(x509EncodedPublicKey); + var publicKey = new PublicKey(Algorithm.SECP256R1, sec1CompressedEncodedPublicKey); var keyPair = KeyPair.generate(publicKey, new Signer() { - @Override public byte[] sign(byte[] bytes) { - try (var kmsClient = KmsClient.create()) { - var response = kmsClient.sign(builder -> builder - .keyId(KMS_KEY_ARN) - .signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256) - .message(SdkBytes.fromByteArray(bytes)) - ); - var signature = response.signature().asByteArray(); - var hex = Utils.bytesToHex(signature); - System.out.println("Signature: " + hex); - - var sgr = KeyPair.generateSignature(Algorithm.SECP256R1); - sgr.initVerify(publicKey.key); - sgr.update(bytes); - var verified = sgr.verify(signature); - System.out.println("Verified: " + verified); - - if (!verified) { - fail("Signature verification failed"); - } - - return signature; - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { - throw new RuntimeException(e); - } + var signResponse = kmsClient.sign(b -> b + .keyId(kmsKeyId) + .signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256) + .message(SdkBytes.fromByteArray(bytes)) + ); + return signResponse.signature().asByteArray(); } }); - var biscuit = Biscuit.builder(keyPair) .add_authority_fact("user(\"1234\")") .add_authority_check("check if operation(\"read\")") .build(); - assertDoesNotThrow(biscuit::serialize); + var serializedBiscuit = assertDoesNotThrow(biscuit::serialize); + var unverifiedBiscuit = Biscuit.from_bytes(serializedBiscuit); + var deserializedBiscuit = unverifiedBiscuit.verify(publicKey); + + System.out.println(deserializedBiscuit.print()); } - static byte[] convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(byte[] publicKeyBytes) { + private static byte[] convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(byte[] publicKeyBytes) { try (ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(publicKeyBytes))) { // Parse the ASN.1 encoded public key bytes @@ -122,20 +107,4 @@ static byte[] convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(byt throw new RuntimeException("Error converting DER-encoded X.509 to SEC1 compressed format", e); } } - - private void sso() { - try { - var code = new ProcessBuilder() - .command("aws", "sso", "login", "--profile", AWS_PROFILE) - .redirectOutput(Redirect.INHERIT) - .redirectError(Redirect.INHERIT) - .start() - .waitFor(); - if (code != 0) { - throw new RuntimeException("SSO login failed"); - } - } catch (InterruptedException | IOException e) { - throw new RuntimeException(e); - } - } } From cee58b512e37f83515020d93b27c0fd31cc9d6ca Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Fri, 7 Jun 2024 13:07:03 +1200 Subject: [PATCH 10/15] remove unnecessary asserts --- .../biscuitsec/biscuit/token/KmsSignerExampleTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java index f88e298..5a167ed 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -81,11 +81,11 @@ public byte[] sign(byte[] bytes) { .add_authority_fact("user(\"1234\")") .add_authority_check("check if operation(\"read\")") .build(); - var serializedBiscuit = assertDoesNotThrow(biscuit::serialize); - var unverifiedBiscuit = Biscuit.from_bytes(serializedBiscuit); - var deserializedBiscuit = unverifiedBiscuit.verify(publicKey); + var serializedBiscuit = biscuit.serialize(); + var deserializedUnverifiedBiscuit = Biscuit.from_bytes(serializedBiscuit); + var verifiedBiscuit = deserializedUnverifiedBiscuit.verify(publicKey); - System.out.println(deserializedBiscuit.print()); + System.out.println(verifiedBiscuit.print()); } private static byte[] convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(byte[] publicKeyBytes) { From c8b48b7d96662ec77379aff5175abc5f941be075 Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Fri, 14 Jun 2024 09:18:02 +1200 Subject: [PATCH 11/15] add assertion --- .../org/biscuitsec/biscuit/token/KmsSignerExampleTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java index 5a167ed..f2d0f5b 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -61,7 +61,7 @@ public void setup() { } @Test - public void testCreateBiscuitWithRemoteSigner() throws Error, NoSuchAlgorithmException, SignatureException, InvalidKeyException { + public void testCreateBiscuitWithRemoteSigner() throws Error { var getPublicKeyResponse = kmsClient.getPublicKey(b -> b.keyId(kmsKeyId).build()); var x509EncodedPublicKey = getPublicKeyResponse.publicKey().asByteArray(); var sec1CompressedEncodedPublicKey = convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(x509EncodedPublicKey); @@ -83,7 +83,7 @@ public byte[] sign(byte[] bytes) { .build(); var serializedBiscuit = biscuit.serialize(); var deserializedUnverifiedBiscuit = Biscuit.from_bytes(serializedBiscuit); - var verifiedBiscuit = deserializedUnverifiedBiscuit.verify(publicKey); + var verifiedBiscuit = assertDoesNotThrow(() -> deserializedUnverifiedBiscuit.verify(publicKey)); System.out.println(verifiedBiscuit.print()); } From 412cff60f59517e0336b09bf1910f8adb2e7efb2 Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Wed, 26 Jun 2024 14:49:26 +1200 Subject: [PATCH 12/15] post rebase fixup still some tests unhappy --- .../biscuit/token/ThirdPartyBlockRequest.java | 11 +---------- .../biscuitsec/biscuit/token/UnverifiedBiscuit.java | 4 ++-- .../org/biscuitsec/biscuit/token/builder/Biscuit.java | 4 ---- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockRequest.java b/src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockRequest.java index 510f62c..00b1831 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockRequest.java +++ b/src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockRequest.java @@ -45,16 +45,7 @@ public Either createBlock(KeyPair ke byte[] serializedBlock = res.get(); - Signature sgr = KeyPair.generateSignature(keyPair.public_key().algorithm); - sgr.initSign(keyPair.private_key()); - sgr.update(serializedBlock); - - ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - algo_buf.putInt(Integer.valueOf(Schema.PublicKey.Algorithm.Ed25519.getNumber())); - algo_buf.flip(); - sgr.update(algo_buf); - sgr.update(previousKey.toBytes()); - byte[] signature = sgr.sign(); + byte[] signature = keyPair.sign(serializedBlock, previousKey.toBytes()); PublicKey publicKey = keyPair.public_key(); diff --git a/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java b/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java index e1b8d3c..9317b9b 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java @@ -256,14 +256,14 @@ public ThirdPartyBlockRequest thirdPartyRequest() { */ public UnverifiedBiscuit appendThirdPartyBlock(PublicKey externalKey, ThirdPartyBlockContents blockResponse) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error { - KeyPair nextKeyPair = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519); + KeyPair nextKeyPair = KeyPair.generate(externalKey.algorithm); Signature sgr = KeyPair.generateSignature(externalKey.algorithm); sgr.initVerify(externalKey.key); sgr.update(blockResponse.payload); ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - algo_buf.putInt(Integer.valueOf(Schema.PublicKey.Algorithm.Ed25519.getNumber())); + algo_buf.putInt(Integer.valueOf(externalKey.algorithm.getNumber())); algo_buf.flip(); sgr.update(algo_buf); diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java b/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java index 27e9313..db21a76 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java @@ -2,12 +2,8 @@ import org.biscuitsec.biscuit.crypto.KeyPair; import org.biscuitsec.biscuit.crypto.PublicKey; -import org.biscuitsec.biscuit.crypto.Signer; import org.biscuitsec.biscuit.datalog.SchemaVersion; import org.biscuitsec.biscuit.datalog.SymbolTable; -import org.biscuitsec.biscuit.datalog.Check; -import org.biscuitsec.biscuit.datalog.Fact; -import org.biscuitsec.biscuit.datalog.Rule; import org.biscuitsec.biscuit.error.Error; import org.biscuitsec.biscuit.token.Block; import io.vavr.Tuple2; From 716ad9b995da37764b166ea4af171e937ecc4673 Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Thu, 27 Jun 2024 07:44:10 +1200 Subject: [PATCH 13/15] tidy up signing formats --- .../biscuit/crypto/Ed25519KeyPair.java | 13 +++--- .../biscuitsec/biscuit/crypto/KeyPair.java | 18 ++------- .../biscuit/crypto/RemoteKeyPair.java | 13 +++--- .../biscuit/crypto/SECP256R1KeyPair.java | 13 +++--- .../org/biscuitsec/biscuit/crypto/Signer.java | 40 +++++++++++++++++++ .../token/format/SerializedBiscuit.java | 12 ++++-- 6 files changed, 76 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java index 6874428..8ef0d98 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/Ed25519KeyPair.java @@ -82,14 +82,17 @@ public java.security.PublicKey publicKey() { @Override public byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - var bytes = toSigningFormat(block, Algorithm.Ed25519, publicKey, Optional.empty()); - return signer.sign(bytes); + return signer.signStandard(block, Algorithm.Ed25519, publicKey); } @Override - public byte[] sign(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - var bytes = toSigningFormat(block, Algorithm.Ed25519, publicKey, Optional.of(seal)); - return signer.sign(bytes); + public byte[] signExternal(byte[] block, byte[] publicKey, byte[] external) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.signExternal(block, Algorithm.Ed25519, publicKey, external); + } + + @Override + public byte[] signSealed(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.signSealed(block, Algorithm.Ed25519, publicKey, seal); } @Override diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java index a240f39..b2f812a 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java @@ -70,19 +70,7 @@ public static Signature generateSignature(Algorithm algorithm) throws NoSuchAlgo public abstract byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException; - public abstract byte[] sign(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException; - - protected static byte[] toSigningFormat(byte[] block, Algorithm algorithm, byte[] publicKey, Optional seal) { - var algorithmBuffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - algorithmBuffer.putInt(algorithm.getNumber()); - algorithmBuffer.flip(); - var algorithmBytes = algorithmBuffer.array(); - - var payload = new byte[block.length + algorithmBytes.length + publicKey.length + seal.orElse(new byte[0]).length]; - System.arraycopy(block, 0, payload, 0, block.length); - System.arraycopy(algorithmBytes, 0, payload, block.length, algorithmBytes.length); - System.arraycopy(publicKey, 0, payload, block.length + algorithmBytes.length, publicKey.length); - seal.ifPresent(bytes -> System.arraycopy(bytes, 0, payload, block.length + algorithmBytes.length + publicKey.length, bytes.length)); - return payload; - } + public abstract byte[] signExternal(byte[] block, byte[] publicKey, byte[] external) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException; + + public abstract byte[] signSealed(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException; } diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java index cffbfea..19438f7 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java @@ -33,14 +33,17 @@ public PublicKey publicKey() { @Override public byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - var bytes = toSigningFormat(block, this.publicKey.algorithm, publicKey, Optional.empty()); - return signer.sign(bytes); + return signer.signStandard(block, this.publicKey.algorithm, publicKey); } @Override - public byte[] sign(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - var bytes = toSigningFormat(block, this.publicKey.algorithm, publicKey, Optional.of(seal)); - return signer.sign(bytes); + public byte[] signExternal(byte[] block, byte[] publicKey, byte[] external) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.signExternal(block, this.publicKey.algorithm, publicKey, external); + } + + @Override + public byte[] signSealed(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.signSealed(block, this.publicKey.algorithm, publicKey, seal); } @Override diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java index 39d87de..e4b3183 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java @@ -86,14 +86,17 @@ public java.security.PublicKey publicKey() { @Override public byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - var bytes = toSigningFormat(block, Algorithm.SECP256R1, publicKey, Optional.empty()); - return signer.sign(bytes); + return signer.signStandard(block, Algorithm.SECP256R1, publicKey); } @Override - public byte[] sign(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - var bytes = toSigningFormat(block, Algorithm.SECP256R1, publicKey, Optional.of(seal)); - return signer.sign(bytes); + public byte[] signExternal(byte[] block, byte[] publicKey, byte[] external) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.signExternal(block, Algorithm.SECP256R1, publicKey, external); + } + + @Override + public byte[] signSealed(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.signExternal(block, Algorithm.SECP256R1, publicKey, seal); } @Override diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java b/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java index 6f646b0..4f4c1d0 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java @@ -1,9 +1,49 @@ package org.biscuitsec.biscuit.crypto; +import biscuit.format.schema.Schema; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; public interface Signer { byte[] sign(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException; + + default byte[] signStandard(byte[] block, Schema.PublicKey.Algorithm algorithm, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + var algorithmBytes = getBufferForAlgorithm(algorithm); + var payload = new byte[block.length + algorithmBytes.length + publicKey.length]; + System.arraycopy(block, 0, payload, 0, block.length); + System.arraycopy(algorithmBytes, 0, payload, block.length, algorithmBytes.length); + System.arraycopy(publicKey, 0, payload, block.length + algorithmBytes.length, publicKey.length); + return sign(payload); + } + + default byte[] signSealed(byte[] block, Schema.PublicKey.Algorithm algorithm, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + var algorithmBytes = getBufferForAlgorithm(algorithm); + var payload = new byte[block.length + algorithmBytes.length + publicKey.length + seal.length]; + System.arraycopy(block, 0, payload, 0, block.length); + System.arraycopy(algorithmBytes, 0, payload, block.length, algorithmBytes.length); + System.arraycopy(publicKey, 0, payload, block.length + algorithmBytes.length, publicKey.length); + System.arraycopy(seal, 0, payload, block.length + algorithmBytes.length + publicKey.length, seal.length); + return sign(payload); + } + + default byte[] signExternal(byte[] block, Schema.PublicKey.Algorithm algorithm, byte[] publicKey, byte[] external) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + var algorithmBytes = getBufferForAlgorithm(algorithm); + var payload = new byte[block.length + external.length + algorithmBytes.length + publicKey.length]; + System.arraycopy(block, 0, payload, 0, block.length); + System.arraycopy(external, 0, payload, block.length, external.length); + System.arraycopy(algorithmBytes, 0, payload, block.length + external.length, algorithmBytes.length); + System.arraycopy(publicKey, 0, payload, block.length + external.length + algorithmBytes.length, publicKey.length); + return sign(payload); + } + + private static byte[] getBufferForAlgorithm(Schema.PublicKey.Algorithm algorithm) { + var algorithmBuffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + algorithmBuffer.putInt(algorithm.getNumber()); + algorithmBuffer.flip(); + return algorithmBuffer.array(); + } } diff --git a/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java b/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java index 5a38dbe..1dc6f7b 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java @@ -262,8 +262,14 @@ public Either append(final org.biscuitsec. byte[] block = stream.toByteArray(); org.biscuitsec.biscuit.crypto.PublicKey next_key = next.public_key(); - byte[] signature = proof.secretKey.get().sign(block, next_key.toBytes()); - SignedBlock signedBlock = new SignedBlock(block, next_key, signature, Option.none()); + byte[] signature; + if (externalSignature.isDefined()) { + signature = proof.secretKey.get().signExternal(block, next_key.toBytes(), externalSignature.get().signature); + } else { + signature = proof.secretKey.get().sign(block, next_key.toBytes()); + } + + SignedBlock signedBlock = new SignedBlock(block, next_key, signature, externalSignature); ArrayList blocks = new ArrayList<>(); for (SignedBlock bl : this.blocks) { @@ -475,7 +481,7 @@ public Either seal() throws InvalidKeyException, NoSuchAlgorithmExc block = this.blocks.get(this.blocks.size() - 1); } - byte[] signature = proof.secretKey.get().sign(block.block, block.key.toBytes(), block.signature); + byte[] signature = proof.secretKey.get().signSealed(block.block, block.key.toBytes(), block.signature); this.proof.secretKey = Option.none(); this.proof.signature = Option.some(signature); From d3bb4da5469a12d02e58d986edb41e2c8d05be1b Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Mon, 1 Jul 2024 08:17:59 +1200 Subject: [PATCH 14/15] self review, correct signer secp --- .../java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java index e4b3183..34591df 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/SECP256R1KeyPair.java @@ -96,7 +96,7 @@ public byte[] signExternal(byte[] block, byte[] publicKey, byte[] external) thro @Override public byte[] signSealed(byte[] block, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - return signer.signExternal(block, Algorithm.SECP256R1, publicKey, seal); + return signer.signSealed(block, Algorithm.SECP256R1, publicKey, seal); } @Override From 10ec5e3a350cbbf5942b00ab73e40d0749366a46 Mon Sep 17 00:00:00 2001 From: Colin O'Brien Date: Tue, 2 Jul 2024 07:24:37 +1200 Subject: [PATCH 15/15] make the array concatenation more readable --- .../org/biscuitsec/biscuit/crypto/Signer.java | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java b/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java index 4f4c1d0..7c6cd55 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java @@ -7,39 +7,43 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; +import java.util.Arrays; public interface Signer { byte[] sign(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException; + // format: block, algo, publicKey default byte[] signStandard(byte[] block, Schema.PublicKey.Algorithm algorithm, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { var algorithmBytes = getBufferForAlgorithm(algorithm); - var payload = new byte[block.length + algorithmBytes.length + publicKey.length]; - System.arraycopy(block, 0, payload, 0, block.length); - System.arraycopy(algorithmBytes, 0, payload, block.length, algorithmBytes.length); - System.arraycopy(publicKey, 0, payload, block.length + algorithmBytes.length, publicKey.length); + var payload = concatenateArrays(block, algorithmBytes, publicKey); return sign(payload); } + // format: block, algo, publicKey, seal default byte[] signSealed(byte[] block, Schema.PublicKey.Algorithm algorithm, byte[] publicKey, byte[] seal) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { var algorithmBytes = getBufferForAlgorithm(algorithm); - var payload = new byte[block.length + algorithmBytes.length + publicKey.length + seal.length]; - System.arraycopy(block, 0, payload, 0, block.length); - System.arraycopy(algorithmBytes, 0, payload, block.length, algorithmBytes.length); - System.arraycopy(publicKey, 0, payload, block.length + algorithmBytes.length, publicKey.length); - System.arraycopy(seal, 0, payload, block.length + algorithmBytes.length + publicKey.length, seal.length); + var payload = concatenateArrays(block, algorithmBytes, publicKey, seal); return sign(payload); } + // format: block, external, algo, publicKey default byte[] signExternal(byte[] block, Schema.PublicKey.Algorithm algorithm, byte[] publicKey, byte[] external) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { var algorithmBytes = getBufferForAlgorithm(algorithm); - var payload = new byte[block.length + external.length + algorithmBytes.length + publicKey.length]; - System.arraycopy(block, 0, payload, 0, block.length); - System.arraycopy(external, 0, payload, block.length, external.length); - System.arraycopy(algorithmBytes, 0, payload, block.length + external.length, algorithmBytes.length); - System.arraycopy(publicKey, 0, payload, block.length + external.length + algorithmBytes.length, publicKey.length); + var payload = concatenateArrays(block, external, algorithmBytes, publicKey); return sign(payload); } + private static byte[] concatenateArrays(byte[]... arrays) { + int totalLength = Arrays.stream(arrays).mapToInt(arr -> arr.length).sum(); + byte[] result = new byte[totalLength]; + int currentPos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, currentPos, array.length); + currentPos += array.length; + } + return result; + } + private static byte[] getBufferForAlgorithm(Schema.PublicKey.Algorithm algorithm) { var algorithmBuffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); algorithmBuffer.putInt(algorithm.getNumber());