Skip to content

Instantly share code, notes, and snippets.

@thomasdarimont
Last active May 3, 2023 19:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomasdarimont/888015a56689fcaf88f6d54c070a03bc to your computer and use it in GitHub Desktop.
Save thomasdarimont/888015a56689fcaf88f6d54c070a03bc to your computer and use it in GitHub Desktop.
PoC for encoding ReferenceTokens with expirationDate and signature
Reference Token: bXl0b2tlbjEyMzoxNjgzMTMyODczNDgyOk1FUUNJR1hUTEwwS2xBS0kvNUh1eDdMWXFYMExuV1NFOVN4dTNtUnFyU2lsVU1wVEFpQjZEb2NYMXZZUTZ5WXVYR0VHM2xwQW42dFFqN1ZPMDgrOHRBajYzUldScEE9PQ
Reference token ID: mytoken123
Timestamp: 1683132873482
Verified: true
Expired: false
package demo;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.Clock;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
public class ReferenceTokenEncoder {
private final Clock clock;
private final PrivateKey privateKey;
private final PublicKey publicKey;
private final String signatureAlgorithm;
public ReferenceTokenEncoder(Clock clock, PrivateKey privateKey, PublicKey publicKey, String signatureAlgorithm) {
this.clock = clock;
this.privateKey = privateKey;
this.publicKey = publicKey;
this.signatureAlgorithm = signatureAlgorithm;
}
public String encodeToken(String referenceTokenId, long expirationTimestamp) throws Exception {
String referenceTokenString = referenceTokenId + ":" + expirationTimestamp;
byte[] signature = signData(referenceTokenString);
String signatureString = Base64.getEncoder().encodeToString(signature);
return Base64.getEncoder().withoutPadding().encodeToString((referenceTokenString + ":" + signatureString).getBytes(StandardCharsets.UTF_8));
}
public ReferenceToken decodeToken(String input) throws Exception {
String referenceTokenString = new String(Base64.getDecoder().decode(input), StandardCharsets.UTF_8);
String[] parts = referenceTokenString.split(":");
if (parts.length != 3) {
throw new IllegalArgumentException("Invalid reference token string");
}
String referenceTokenId = parts[0];
long timestamp = Long.parseLong(parts[1]);
String signatureString = parts[2];
byte[] signature = Base64.getDecoder().decode(signatureString);
String referenceTokenData = referenceTokenId + ":" + timestamp;
boolean verified = verifyData(referenceTokenData, signature);
boolean expired = timestamp < clock.millis();
return new ReferenceToken(referenceTokenId, timestamp, verified && !expired);
}
private byte[] signData(String data) throws Exception {
Signature sig = Signature.getInstance(signatureAlgorithm);
sig.initSign(privateKey);
sig.update(data.getBytes(StandardCharsets.UTF_8));
return sig.sign();
}
private boolean verifyData(String data, byte[] signature) throws Exception {
Signature sig = Signature.getInstance(signatureAlgorithm);
sig.initVerify(publicKey);
sig.update(data.getBytes(StandardCharsets.UTF_8));
return sig.verify(signature);
}
public record ReferenceToken(String referenceTokenId, long timestamp, boolean verified) {
public boolean isExpired(Clock clock) {
return timestamp < clock.millis();
}
}
public static void main(String[] args) throws Exception {
String signatureAlgorithm = "SHA256withECDSA";
KeyPair keyPair = Keys.generateKeyPair();
String privateKeyString = Keys.privateKeyToString(keyPair.getPrivate());
String publicKeyString = Keys.publicKeyToString(keyPair.getPublic());
Clock clock = Clock.systemDefaultZone();
ReferenceTokenEncoder tokenEncoder = new ReferenceTokenEncoder( //
clock, //
Keys.getPrivateKey(privateKeyString), //
Keys.getPublicKey(publicKeyString), //
signatureAlgorithm //
);
String referenceTokenId = "mytoken123";
long expiresAtTimestamp = Instant.now().plus(5, ChronoUnit.MINUTES).toEpochMilli();
String referenceTokenString = tokenEncoder.encodeToken(referenceTokenId, expiresAtTimestamp);
System.out.println("Reference Token: " + referenceTokenString);
ReferenceToken referenceToken = tokenEncoder.decodeToken(referenceTokenString);
System.out.println("Reference token ID: " + referenceToken.referenceTokenId());
System.out.println("Timestamp: " + referenceToken.timestamp());
System.out.println("Verified: " + referenceToken.verified());
System.out.println("Expired: " + referenceToken.isExpired(clock));
}
static class Keys {
public static final String KEY_ALGORITHM = "EC";
private static final String CURVE_NAME = "secp256r1";
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
ECGenParameterSpec ecSpec = new ECGenParameterSpec(CURVE_NAME);
keyGen.initialize(ecSpec);
return keyGen.generateKeyPair();
}
public static String publicKeyToString(PublicKey publicKey) {
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
public static String privateKeyToString(PrivateKey privateKey) {
return Base64.getEncoder().encodeToString(privateKey.getEncoded());
}
public static PrivateKey getPrivateKey(String privateKey) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(Keys.KEY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
public static PublicKey getPublicKey(String publicKey) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(Keys.KEY_ALGORITHM);
return keyFactory.generatePublic(keySpec);
}
}
}
JWK: {"kty":"EC","d":"doCDsWOra9c8OmOnyP8ASWWkiziLId6W5Qc5U8I32jE","crv":"P-256","kid":"123","x":"WBhLezpT8YmLPvMxR8-HexJmD26ZDIaiLEGmTIEtrI8","y":"cvYF_nnKY-AIH4FHgTArNwYPS-dx2YGiDTgC3jOMo2w"}
Token: eyJraWQiOiIxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJodHRwczovL2lkLmFjbWUudGVzdC9hdXRoL3JlYWxtcy9kZW1vIiwiZXhwIjoxNjgzMTQzNDI3MzM0LCJqdGkiOiJjNjI2MDEzYi0yMTkyLTQ0ZGUtYTg1Ny02MGJjM2IwOTdkYTEifQ.y_GbepRNEKRRQ2Rq60hPCELBHXhvEj3xDKYu4DxjbiQcsmrAWM6waHLdRCZCs33hYjLJScjOwB-TLC5M5udynA
Valid: true
Payload: {"iss":"https://id.acme.test/auth/realms/demo","exp":1683143427334,"jti":"c626013b-2192-44de-a857-60bc3b097da1"}
Expired: false
package demo;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.crypto.ECDSAVerifier;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jwt.JWTClaimNames;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.UUID;
public class NimbusReferenceTokenExample {
public static void main(String[] args) throws Exception {
ECKey ecJWK = new ECKeyGenerator(Curve.P_256).keyID("123").generate();
System.out.println("JWK: " + ecJWK);
JWSSigner signer = new ECDSASigner(ecJWK);
String referenceTokenId = UUID.randomUUID().toString();
long expiresAtTimestamp = Instant.now().plus(5, ChronoUnit.MINUTES).toEpochMilli();
String issuer = "https://id.acme.test/auth/realms/demo";
Map<String, Object> data = Map.of( //
JWTClaimNames.JWT_ID, referenceTokenId, //
JWTClaimNames.EXPIRATION_TIME, expiresAtTimestamp, //
JWTClaimNames.ISSUER, issuer
);
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(ecJWK.getKeyID()).type(JOSEObjectType.JWT).build();
Payload payload = new Payload(data);
JWSObject jwsObject = new JWSObject(header, payload);
jwsObject.sign(signer);
String token = jwsObject.serialize();
System.out.println("Token: " + token);
JWSVerifier verifier = new ECDSAVerifier(ecJWK.toPublicJWK());
boolean valid = jwsObject.verify(verifier);
System.out.println("Valid: " + valid);
Payload verifiedPayload = jwsObject.getPayload();
System.out.println("Payload: " + verifiedPayload);
long expiresAt = Long.parseLong(String.valueOf(verifiedPayload.toJSONObject().get(JWTClaimNames.EXPIRATION_TIME)));
System.out.println("Expired: " + (expiresAt < System.currentTimeMillis()));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment