diff --git a/pom.xml b/pom.xml index 1ac40a5..d4cfe1b 100644 --- a/pom.xml +++ b/pom.xml @@ -266,6 +266,24 @@ junit-jupiter test + + software.amazon.awssdk + kms + 2.25.65 + test + + + org.testcontainers + junit-jupiter + 1.19.8 + test + + + org.testcontainers + localstack + 1.19.8 + 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..8ef0d98 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,42 +10,47 @@ 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; +import java.util.Optional; 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); + 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(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(Algorithm.Ed25519, privKey); } public Ed25519KeyPair(String hex) { @@ -53,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 @@ -76,12 +81,22 @@ 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.signStandard(block, Algorithm.Ed25519, publicKey); + } + + @Override + 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 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..b2f812a 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java @@ -4,9 +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. @@ -41,6 +46,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 +66,11 @@ 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[] 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/PrivateKeySigner.java b/src/main/java/org/biscuitsec/biscuit/crypto/PrivateKeySigner.java new file mode 100644 index 0000000..3278ab1 --- /dev/null +++ b/src/main/java/org/biscuitsec/biscuit/crypto/PrivateKeySigner.java @@ -0,0 +1,27 @@ +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 Algorithm algorithm; + private final PrivateKey privateKey; + + public PrivateKeySigner(Algorithm algorithm, PrivateKey privateKey) { + this.algorithm = algorithm; + this.privateKey = privateKey; + } + + @Override + 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 new file mode 100644 index 0000000..19438f7 --- /dev/null +++ b/src/main/java/org/biscuitsec/biscuit/crypto/RemoteKeyPair.java @@ -0,0 +1,53 @@ +package org.biscuitsec.biscuit.crypto; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.SignatureException; +import java.util.Optional; + +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 accessed."); + } + + @Override + public String toHex() { + throw new RuntimeException("Illegal operation; remote private private key cannot be accessed."); + } + + @Override + public PublicKey publicKey() { + return publicKey.key; + } + + @Override + public byte[] sign(byte[] block, byte[] publicKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + return signer.signStandard(block, this.publicKey.algorithm, publicKey); + } + + @Override + 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 + 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..34591df 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,48 +11,48 @@ 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; +import java.util.Optional; 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()); - } + 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(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(Algorithm.SECP256R1, privateKey); } public SECP256R1KeyPair(String hex) { @@ -85,12 +85,22 @@ 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.signStandard(block, Algorithm.SECP256R1, publicKey); + } + + @Override + 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.signSealed(block, Algorithm.SECP256R1, publicKey, seal); } @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..7c6cd55 --- /dev/null +++ b/src/main/java/org/biscuitsec/biscuit/crypto/Signer.java @@ -0,0 +1,53 @@ +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; +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 = 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 = 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 = 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()); + 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..0513a3a 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); 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/format/SerializedBiscuit.java b/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java index d2df062..1dc6f7b 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,20 +261,14 @@ 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); + 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()); } - sgr.update(algo_buf); - sgr.update(next_key.toBytes()); - byte[] signature = sgr.sign(); - + SignedBlock signedBlock = new SignedBlock(block, next_key, signature, externalSignature); ArrayList blocks = new ArrayList<>(); @@ -496,19 +481,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().signSealed(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..f2d0f5b --- /dev/null +++ b/src/test/java/org/biscuitsec/biscuit/token/KmsSignerExampleTest.java @@ -0,0 +1,110 @@ +package org.biscuitsec.biscuit.token; + +import biscuit.format.schema.Schema.PublicKey.Algorithm; +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.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.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; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +@Testcontainers +public class KmsSignerExampleTest { + + private static final DockerImageName LOCALSTACK_IMAGE = DockerImageName.parse("localstack/localstack:3.0.1"); + + @Container + public static LocalStackContainer LOCALSTACK = new LocalStackContainer(LOCALSTACK_IMAGE) + .withServices(LocalStackContainer.Service.KMS); + + private KmsClient kmsClient; + private String kmsKeyId; + + @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(); + + // ECC_NIST_P256 == SECP256R1 + kmsKeyId = kmsClient.createKey(b -> b + .keySpec(KeySpec.ECC_NIST_P256) + .keyUsage(KeyUsageType.SIGN_VERIFY) + .build() + ).keyMetadata().keyId(); + } + + @Test + public void testCreateBiscuitWithRemoteSigner() throws Error { + 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) { + 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(); + var serializedBiscuit = biscuit.serialize(); + var deserializedUnverifiedBiscuit = Biscuit.from_bytes(serializedBiscuit); + var verifiedBiscuit = assertDoesNotThrow(() -> deserializedUnverifiedBiscuit.verify(publicKey)); + + System.out.println(verifiedBiscuit.print()); + } + + private static byte[] convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(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); + } + } +}