Skip to content
This repository was archived by the owner on Jul 15, 2025. It is now read-only.

Commit e1fadca

Browse files
committed
update the example to use localstack
1 parent e4a3725 commit e1fadca

File tree

2 files changed

+59
-84
lines changed

2 files changed

+59
-84
lines changed

pom.xml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,15 @@
273273
<scope>test</scope>
274274
</dependency>
275275
<dependency>
276-
<groupId>software.amazon.awssdk</groupId>
277-
<artifactId>sso</artifactId>
278-
<version>2.25.65</version>
276+
<groupId>org.testcontainers</groupId>
277+
<artifactId>junit-jupiter</artifactId>
278+
<version>1.19.8</version>
279+
<scope>test</scope>
280+
</dependency>
281+
<dependency>
282+
<groupId>org.testcontainers</groupId>
283+
<artifactId>localstack</artifactId>
284+
<version>1.19.8</version>
279285
<scope>test</scope>
280286
</dependency>
281287
</dependencies>
Lines changed: 50 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,94 @@
11
package org.biscuitsec.biscuit.token;
22

33
import biscuit.format.schema.Schema.PublicKey.Algorithm;
4-
import net.i2p.crypto.eddsa.Utils;
54
import org.biscuitsec.biscuit.crypto.KeyPair;
65
import org.biscuitsec.biscuit.crypto.PublicKey;
76
import org.biscuitsec.biscuit.crypto.Signer;
87
import org.biscuitsec.biscuit.error.Error;
98
import org.bouncycastle.asn1.ASN1InputStream;
109
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
1110
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
12-
import org.junit.jupiter.api.AfterAll;
13-
import org.junit.jupiter.api.BeforeAll;
11+
import org.junit.jupiter.api.BeforeEach;
1412
import org.junit.jupiter.api.Test;
13+
import org.testcontainers.containers.localstack.LocalStackContainer;
14+
import org.testcontainers.junit.jupiter.Container;
15+
import org.testcontainers.junit.jupiter.Testcontainers;
16+
import org.testcontainers.utility.DockerImageName;
17+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
18+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
1519
import software.amazon.awssdk.core.SdkBytes;
16-
import software.amazon.awssdk.core.exception.SdkClientException;
20+
import software.amazon.awssdk.regions.Region;
1721
import software.amazon.awssdk.services.kms.KmsClient;
22+
import software.amazon.awssdk.services.kms.model.KeySpec;
23+
import software.amazon.awssdk.services.kms.model.KeyUsageType;
1824
import software.amazon.awssdk.services.kms.model.SigningAlgorithmSpec;
1925

2026
import java.io.ByteArrayInputStream;
2127
import java.io.IOException;
2228
import java.security.InvalidKeyException;
2329
import java.security.NoSuchAlgorithmException;
2430
import java.security.SignatureException;
25-
import java.util.function.Function;
2631

27-
import static java.lang.ProcessBuilder.Redirect;
2832
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
29-
import static org.junit.jupiter.api.Assertions.fail;
30-
3133

34+
@Testcontainers
3235
public class KmsSignerExampleTest {
3336

34-
private static final String AWS_PROFILE = null;
35-
private static final String AWS_REGION = null;
36-
private static final String KMS_KEY_ARN = null;
37+
private static final DockerImageName LOCALSTACK_IMAGE = DockerImageName.parse("localstack/localstack:3.0.1");
3738

38-
@BeforeAll
39-
public static void setup() {
40-
System.setProperty("aws.region", AWS_REGION);
41-
System.setProperty("aws.profile", AWS_PROFILE);
42-
}
39+
@Container
40+
public static LocalStackContainer LOCALSTACK = new LocalStackContainer(LOCALSTACK_IMAGE)
41+
.withServices(LocalStackContainer.Service.KMS);
4342

44-
@AfterAll
45-
public static void teardown() {
46-
System.clearProperty("aws.region");
47-
System.clearProperty("aws.profile");
48-
}
43+
private KmsClient kmsClient;
44+
private String kmsKeyId;
4945

50-
// @Test
51-
public void createWithRemoteSigner() throws Error {
52-
Function<String, byte[]> getPublicKeyBytes = (keyId) -> {
53-
try (var kmsClient = KmsClient.create()) {
54-
var publicKeyResponse = kmsClient.getPublicKey(b -> b.keyId(keyId).build());
55-
return convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(publicKeyResponse.publicKey().asByteArray());
56-
}
57-
};
46+
@BeforeEach
47+
public void setup() {
48+
var credentials = AwsBasicCredentials.create(LOCALSTACK.getAccessKey(), LOCALSTACK.getSecretKey());
49+
kmsClient = KmsClient.builder()
50+
.endpointOverride(LOCALSTACK.getEndpointOverride(LocalStackContainer.Service.KMS))
51+
.credentialsProvider(StaticCredentialsProvider.create(credentials))
52+
.region(Region.of(LOCALSTACK.getRegion()))
53+
.build();
5854

59-
byte[] publicKeyBytes;
60-
try {
61-
publicKeyBytes = getPublicKeyBytes.apply(KMS_KEY_ARN);
62-
} catch (SdkClientException e) {
63-
sso();
64-
publicKeyBytes = getPublicKeyBytes.apply(KMS_KEY_ARN);
65-
}
55+
// ECC_NIST_P256 == SECP256R1
56+
kmsKeyId = kmsClient.createKey(b -> b
57+
.keySpec(KeySpec.ECC_NIST_P256)
58+
.keyUsage(KeyUsageType.SIGN_VERIFY)
59+
.build()
60+
).keyMetadata().keyId();
61+
}
6662

67-
var publicKey = new PublicKey(Algorithm.SECP256R1, publicKeyBytes);
63+
@Test
64+
public void testCreateBiscuitWithRemoteSigner() throws Error, NoSuchAlgorithmException, SignatureException, InvalidKeyException {
65+
var getPublicKeyResponse = kmsClient.getPublicKey(b -> b.keyId(kmsKeyId).build());
66+
var x509EncodedPublicKey = getPublicKeyResponse.publicKey().asByteArray();
67+
var sec1CompressedEncodedPublicKey = convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(x509EncodedPublicKey);
68+
var publicKey = new PublicKey(Algorithm.SECP256R1, sec1CompressedEncodedPublicKey);
6869
var keyPair = KeyPair.generate(publicKey, new Signer() {
69-
7070
@Override
7171
public byte[] sign(byte[] bytes) {
72-
try (var kmsClient = KmsClient.create()) {
73-
var response = kmsClient.sign(builder -> builder
74-
.keyId(KMS_KEY_ARN)
75-
.signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256)
76-
.message(SdkBytes.fromByteArray(bytes))
77-
);
78-
var signature = response.signature().asByteArray();
79-
var hex = Utils.bytesToHex(signature);
80-
System.out.println("Signature: " + hex);
81-
82-
var sgr = KeyPair.generateSignature(Algorithm.SECP256R1);
83-
sgr.initVerify(publicKey.key);
84-
sgr.update(bytes);
85-
var verified = sgr.verify(signature);
86-
System.out.println("Verified: " + verified);
87-
88-
if (!verified) {
89-
fail("Signature verification failed");
90-
}
91-
92-
return signature;
93-
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
94-
throw new RuntimeException(e);
95-
}
72+
var signResponse = kmsClient.sign(b -> b
73+
.keyId(kmsKeyId)
74+
.signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256)
75+
.message(SdkBytes.fromByteArray(bytes))
76+
);
77+
return signResponse.signature().asByteArray();
9678
}
9779
});
98-
9980
var biscuit = Biscuit.builder(keyPair)
10081
.add_authority_fact("user(\"1234\")")
10182
.add_authority_check("check if operation(\"read\")")
10283
.build();
103-
assertDoesNotThrow(biscuit::serialize);
84+
var serializedBiscuit = assertDoesNotThrow(biscuit::serialize);
85+
var unverifiedBiscuit = Biscuit.from_bytes(serializedBiscuit);
86+
var deserializedBiscuit = unverifiedBiscuit.verify(publicKey);
87+
88+
System.out.println(deserializedBiscuit.print());
10489
}
10590

106-
static byte[] convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(byte[] publicKeyBytes) {
91+
private static byte[] convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(byte[] publicKeyBytes) {
10792
try (ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(publicKeyBytes))) {
10893

10994
// Parse the ASN.1 encoded public key bytes
@@ -122,20 +107,4 @@ static byte[] convertDEREncodedX509PublicKeyToSEC1CompressedEncodedPublicKey(byt
122107
throw new RuntimeException("Error converting DER-encoded X.509 to SEC1 compressed format", e);
123108
}
124109
}
125-
126-
private void sso() {
127-
try {
128-
var code = new ProcessBuilder()
129-
.command("aws", "sso", "login", "--profile", AWS_PROFILE)
130-
.redirectOutput(Redirect.INHERIT)
131-
.redirectError(Redirect.INHERIT)
132-
.start()
133-
.waitFor();
134-
if (code != 0) {
135-
throw new RuntimeException("SSO login failed");
136-
}
137-
} catch (InterruptedException | IOException e) {
138-
throw new RuntimeException(e);
139-
}
140-
}
141110
}

0 commit comments

Comments
 (0)