From a181aea8b0db7a87e5aabdd9743a35c830ffa4ff Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Wed, 27 Sep 2023 14:27:06 -0600 Subject: [PATCH] README, formatting. --- README.md | 233 ++++++++++++++++++++++++--------------------- fusionauth-jwt.ipr | 4 + 2 files changed, 130 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 181df69..9af8239 100644 --- a/README.md +++ b/README.md @@ -1,218 +1,237 @@ ## FusionAuth JWT ![semver 2.0.0 compliant](https://img.shields.io/badge/semver-2.0.0-brightgreen.svg?style=flat-square) ![Tests](https://github.com/FusionAuth/fusionauth-jwt/workflows/Tests/badge.svg) + FusionAuth JWT is intended to be fast and easy to use. FusionAuth JWT has a single external dependency on Jackson, no Bouncy Castle, Apache Commons or Guava. ## Security disclosures -If you find a vulnerability or other security related bug, please send a note to security@fusionauth.io before opening a GitHub issue. This will allow us to assess the disclosure and prepare a fix prior to a public disclosure. + +If you find a vulnerability or other security related bug, please send a note to security@fusionauth.io before opening a GitHub issue. This will allow us to assess the disclosure and prepare a fix prior to a public disclosure. We are very interested in compensating anyone that can identify a security related bug or vulnerability and properly disclose it to us. ## Features - - JWT signing using HMAC, RSA and Elliptic Curve support - - `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512` - - JWT signing using RSA-PSS signatures - - `PS256`, `PS384`, `PS512` - - Requires Java 8 update 251 or greater, or any version that includes support RSASSA-PSS - - https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146293 - - Available in versions >= 3.5.0 - - Modular crypto provider so you can drop in support for BC FIPS or other JCE security providers. - - PEM decoding / encoding - - Decode PEM files to PrivateKey or PublicKey - - Decode private EC keys un-encapsulated in PKCS#8, returned PEM will be in PKCS#8 form. - - Both public and private keys will be returned when encoded in the private PEM - - Encode PrivateKey or PublicKey to PEM - - JSON Web Key - - Build JWK from Private Key - - Build JWK from Public Key - - Build JWK from PEM - - Parse public keys from a JSON Web Key - - Retrieve JWK from JWKS endpoints - - Helpers - - Generate RSA Key Pairs in `2048`, `3072` or `4096` bit sizes - - Generate EC Key Pairs in `256`, `384` and `521` bit sizes - - Generate `x5t` and `x5t#256` values from X.509 Certificates - - Generate JWK thumbprint using `SHA-1` or `SHA-256` - - Generate ideal HMAC secret lengths for `SHA-256`, `SHA-384` and `SHA-512` - - Generate the `at_hash` and `c_hash` claims for OpenID Connect + +- JWT signing using HMAC, RSA and Elliptic Curve support + - `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512` +- JWT signing using RSA-PSS signatures + - `PS256`, `PS384`, `PS512` + - Requires Java 8 update 251 or greater, or any version that includes support RSASSA-PSS + - https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146293 + - Available in versions >= 3.5.0 +- Modular crypto provider so you can drop in support for BC FIPS or other JCE security providers. +- PEM decoding / encoding + - Decode PEM files to PrivateKey or PublicKey + - Decode private EC keys un-encapsulated in PKCS#8, returned PEM will be in PKCS#8 form. + - Both public and private keys will be returned when encoded in the private PEM + - Encode PrivateKey or PublicKey to PEM +- JSON Web Key + - Build JWK from Private Key + - Build JWK from Public Key + - Build JWK from PEM + - Parse public keys from a JSON Web Key + - Retrieve JWK from JWKS endpoints +- Helpers + - Generate RSA Key Pairs in `2048`, `3072` or `4096` bit sizes + - Generate EC Key Pairs in `256`, `384` and `521` bit sizes + - Generate `x5t` and `x5t#256` values from X.509 Certificates + - Generate JWK thumbprint using `SHA-1` or `SHA-256` + - Generate ideal HMAC secret lengths for `SHA-256`, `SHA-384` and `SHA-512` + - Generate the `at_hash` and `c_hash` claims for OpenID Connect ## Get it ### Maven + ```xml + - io.fusionauth - fusionauth-jwt - 5.2.4 + io.fusionauth + fusionauth-jwt + 5.3.0 ``` ### Gradle + ```groovy -implementation 'io.fusionauth:fusionauth-jwt:5.2.4' +implementation 'io.fusionauth:fusionauth-jwt:5.3.0' ``` ### Gradle Kotlin + ```kotlin -implementation("io.fusionauth:fusionauth-jwt:5.2.4") +implementation("io.fusionauth:fusionauth-jwt:5.3.0") ``` -### Savant +### Savant + ```groovy -dependency(id: "io.fusionauth:fusionauth-jwt:5.2.4") +dependency(id: "io.fusionauth:fusionauth-jwt:5.3.0") ``` For others see [https://search.maven.org](https://search.maven.org/artifact/io.fusionauth/fusionauth-jwt/4.0.1/jar). - + ## Example Code: ### JWT Signing and Verifying #### Sign and encode a JWT using HMAC + ```java // Build an HMAC signer using a SHA-256 hash -Signer signer = HMACSigner.newSHA256Signer("too many secrets"); +Signer signer=HMACSigner.newSHA256Signer("too many secrets"); // Build a new JWT with an issuer(iss), issued at(iat), subject(sub) and expiration(exp) -JWT jwt = new JWT().setIssuer("www.acme.com") - .setIssuedAt(ZonedDateTime.now(ZoneOffset.UTC)) - .setSubject("f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3") - .setExpiration(ZonedDateTime.now(ZoneOffset.UTC).plusMinutes(60)); - + JWT jwt=new JWT().setIssuer("www.acme.com") + .setIssuedAt(ZonedDateTime.now(ZoneOffset.UTC)) + .setSubject("f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3") + .setExpiration(ZonedDateTime.now(ZoneOffset.UTC).plusMinutes(60)); + // Sign and encode the JWT to a JSON string representation -String encodedJWT = JWT.getEncoder().encode(jwt, signer); + String encodedJWT=JWT.getEncoder().encode(jwt,signer); ``` A higher strength hash can be used by changing the signer. The encoding and decoding steps are not affected. + ```java // Build an HMAC signer using a SHA-384 hash -Signer signer384 = HMACSigner.newSHA384Signer("too many secrets"); +Signer signer384=HMACSigner.newSHA384Signer("too many secrets"); // Build an HMAC signer using a SHA-512 hash -Signer signer512 = HMACSigner.newSHA512Signer("too many secrets"); + Signer signer512=HMACSigner.newSHA512Signer("too many secrets"); ``` #### Verify and decode a JWT using HMAC + ```java // Build an HMC verifier using the same secret that was used to sign the JWT -Verifier verifier = HMACVerifier.newVerifier("too many secrets"); +Verifier verifier=HMACVerifier.newVerifier("too many secrets"); // Verify and decode the encoded string JWT to a rich object -JWT jwt = JWT.getDecoder().decode(encodedJWT, verifier); + JWT jwt=JWT.getDecoder().decode(encodedJWT,verifier); // Assert the subject of the JWT is as expected -assertEquals(jwt.subject, "f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"); + assertEquals(jwt.subject,"f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"); ``` #### Sign and encode a JWT using RSA + ```java // Build an RSA signer using a SHA-256 hash. A signer may also be built using the PrivateKey object. -Signer signer = RSASigner.newSHA256Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); +Signer signer=RSASigner.newSHA256Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); // Build a new JWT with an issuer(iss), issued at(iat), subject(sub) and expiration(exp) -JWT jwt = new JWT().setIssuer("www.acme.com") - .setIssuedAt(ZonedDateTime.now(ZoneOffset.UTC)) - .setSubject("f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3") - .setExpiration(ZonedDateTime.now(ZoneOffset.UTC).plusMinutes(60)); - + JWT jwt=new JWT().setIssuer("www.acme.com") + .setIssuedAt(ZonedDateTime.now(ZoneOffset.UTC)) + .setSubject("f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3") + .setExpiration(ZonedDateTime.now(ZoneOffset.UTC).plusMinutes(60)); + // Sign and encode the JWT to a JSON string representation -String encodedJWT = JWT.getEncoder().encode(jwt, signer); + String encodedJWT=JWT.getEncoder().encode(jwt,signer); ``` A higher strength hash can be used by changing the signer. The encoding and decoding steps are not affected. + ```java // Build an RSA signer using a SHA-384 hash -Signer signer = RSASigner.newSHA384Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); +Signer signer=RSASigner.newSHA384Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); // Build an RSA signer using a SHA5124 hash -Signer signer = RSASigner.newSHA512Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); + Signer signer=RSASigner.newSHA512Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); ``` #### Verify and decode a JWT using RSA + ```java // Build an RSA verifier using an RSA Public Key. A verifier may also be built using the PublicKey object. -Verifier verifier = RSAVerifier.newVerifier(Paths.get("public_key.pem")); +Verifier verifier=RSAVerifier.newVerifier(Paths.get("public_key.pem")); // Verify and decode the encoded string JWT to a rich object -JWT jwt = JWT.getDecoder().decode(encodedJWT, verifier); + JWT jwt=JWT.getDecoder().decode(encodedJWT,verifier); // Assert the subject of the JWT is as expected -assertEquals(jwt.subject, "f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"); + assertEquals(jwt.subject,"f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"); ``` #### Sign and encode a JWT using EC + ```java // Build an EC signer using a SHA-256 hash. A signer may also be built using the PrivateKey object. -Signer signer = ECSigner.newSHA256Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); +Signer signer=ECSigner.newSHA256Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); // Build a new JWT with an issuer(iss), issued at(iat), subject(sub) and expiration(exp) -JWT jwt = new JWT().setIssuer("www.acme.com") - .setIssuedAt(ZonedDateTime.now(ZoneOffset.UTC)) - .setSubject("f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3") - .setExpiration(ZonedDateTime.now(ZoneOffset.UTC).plusMinutes(60)); - + JWT jwt=new JWT().setIssuer("www.acme.com") + .setIssuedAt(ZonedDateTime.now(ZoneOffset.UTC)) + .setSubject("f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3") + .setExpiration(ZonedDateTime.now(ZoneOffset.UTC).plusMinutes(60)); + // Sign and encode the JWT to a JSON string representation -String encodedJWT = JWT.getEncoder().encode(jwt, signer); + String encodedJWT=JWT.getEncoder().encode(jwt,signer); ``` A higher strength hash can be used by changing the signer. The encoding and decoding steps are not affected. + ```java // Build an EC signer using a SHA-384 hash -Signer signer = ECSigner.newSHA384Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); +Signer signer=ECSigner.newSHA384Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); // Build an EC signer using a SHA-512 hash -Signer signer = ECSigner.newSHA512Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); + Signer signer=ECSigner.newSHA512Signer(new String(Files.readAllBytes(Paths.get("private_key.pem")))); ``` #### Verify and decode a JWT using EC + ```java // Build an EC verifier using an EC Public Key. A verifier may also be built using the PublicKey object. -Verifier verifier = ECVerifier.newVerifier(Paths.get("public_key.pem")); +Verifier verifier=ECVerifier.newVerifier(Paths.get("public_key.pem")); // Verify and decode the encoded string JWT to a rich object -JWT jwt = JWT.getDecoder().decode(encodedJWT, verifier); + JWT jwt=JWT.getDecoder().decode(encodedJWT,verifier); // Assert the subject of the JWT is as expected -assertEquals(jwt.subject, "f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"); + assertEquals(jwt.subject,"f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"); ``` #### Verify a JWT adjusting for Clock Skew + ```java // Build an EC verifier using an EC Public Key -Verifier verifier = ECVerifier.newVerifier(Paths.get("public_key.pem")); +Verifier verifier=ECVerifier.newVerifier(Paths.get("public_key.pem")); // Verify and decode the encoded string JWT to a rich object and allow up to 60 seconds // of clock skew when asserting the 'exp' and 'nbf' claims if they exist. -JWT jwt = JWT.getDecoder().withClockSkew(60).decode(encodedJWT, verifier); + JWT jwt=JWT.getDecoder().withClockSkew(60).decode(encodedJWT,verifier); // Assert the subject of the JWT is as expected -assertEquals(jwt.subject, "f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"); + assertEquals(jwt.subject,"f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"); ``` #### Verify an expired JWT by going back in time + In a scenario where you may have a hard coded JWT in a test case that you wish to validate, you may use the time machine JWT decoder. Ideally you would not hard code JWTs in your tests and instead generate a new one each time so that the JWT would pass the expiration check. If this is not possible, this option is provided. + ```java // Build an EC verifier using an EC Public Key -Verifier verifier = ECVerifier.newVerifier(Paths.get("public_key.pem")); +Verifier verifier=ECVerifier.newVerifier(Paths.get("public_key.pem")); // Using the time machine decoder, you may adjust 'now' to any point in the past, or future. // Note, this is only provided for testing, and should not be used in production. -ZonedDateTime thePast = ZonedDateTime.of(2019, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) -JWT jwt = JWT.getTimeMachineDecoder(thePast).decode(encodedJWT, verifier); + ZonedDateTime thePast=ZonedDateTime.of(2019,1,1,0,0,0,0,ZoneOffset.UTC) + JWT jwt=JWT.getTimeMachineDecoder(thePast).decode(encodedJWT,verifier); // Assert the subject of the JWT is as expected -assertEquals(jwt.subject, "f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"); + assertEquals(jwt.subject,"f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"); ``` - ### Build a Signer, or a Verifier using a provided CryptoProvider This pattern is available on the HMAC, RSA and EC verifier and signers. - + ```java // Build and EC signer using a BC Fips ready Crypto Provider -Signer signer = ECSigner.newSHA256Signer(new String(Files.readAllBytes(Paths.get("private_key.pem"))), new BCFIPSCryptoProvider()); +Signer signer=ECSigner.newSHA256Signer(new String(Files.readAllBytes(Paths.get("private_key.pem"))),new BCFIPSCryptoProvider()); // Build an EC verifier using a BC Fips ready Crypto Provider -Verifier verifier = ECVerifier.newVerifier(Paths.get("public_key.pem"), new BCFIPSCryptoProvider()); + Verifier verifier=ECVerifier.newVerifier(Paths.get("public_key.pem"),new BCFIPSCryptoProvider()); ``` ## JSON Web Keys @@ -222,21 +241,21 @@ Verifier verifier = ECVerifier.newVerifier(Paths.get("public_key.pem"), new BCFI ```java // Retrieve JSON Web Keys using a known JWKS endpoint // - You may optionally provide a HttpURLConnection to this method instead of a string if you want to build your own connection. -List keys = JSONWebKeySetHelper.retrieveKeysFromJWKS("https://www.googleapis.com/oauth2/v3/certs"); +List keys=JSONWebKeySetHelper.retrieveKeysFromJWKS("https://www.googleapis.com/oauth2/v3/certs"); // Retrieve JSON Web Keys using a well known OpenID Connect configuration endpoint // - You may optionally provide a HttpURLConnection to this method instead of a string if you want to build your own connection. -List keys = JSONWebKeySetHelper.retrieveKeysFromWellKnownConfiguration("https://accounts.google.com/.well-known/openid-configuration"); + List keys=JSONWebKeySetHelper.retrieveKeysFromWellKnownConfiguration("https://accounts.google.com/.well-known/openid-configuration"); // Retrieve JSON Web Keys using an OpenID Connect issuer endpoint -List keys = JSONWebKeySetHelper.retrieveKeysFromIssuer("https://accounts.google.com"); + List keys=JSONWebKeySetHelper.retrieveKeysFromIssuer("https://accounts.google.com"); ``` ### Convert a Public Key to JWK ```java -JSONWebKey jwk = JSONWebKey.build(publicKey); -String json = jwk.toJSON(); +JSONWebKey jwk=JSONWebKey.build(publicKey); + String json=jwk.toJSON(); ``` ```json @@ -260,17 +279,17 @@ String json = jwk.toJSON(); ``` ```java -String json = { ... example above ... } -byte[] bytes = json.getBytes(StandardCharsets.UTF_8); -JSONWebKey jwk = Mapper.deserialize(bytes, JSONWebKey.class); -PublicKey publicKey = JSONWebKey.parse(jwk); +String json={...example above...} + byte[]bytes=json.getBytes(StandardCharsets.UTF_8); + JSONWebKey jwk=Mapper.deserialize(bytes,JSONWebKey.class); + PublicKey publicKey=JSONWebKey.parse(jwk); ``` ### Convert a Private Key to JWK ```java -JSONWebKey jwk = JSONWebKey.build(privateKey); -String json = jwk.toJSON(); +JSONWebKey jwk=JSONWebKey.build(privateKey); + String json=jwk.toJSON(); ``` ```json @@ -291,33 +310,33 @@ String json = jwk.toJSON(); ### Add a custom property to a JWK ```java -JSONWebKey jwk = JSONWebKey.build(privateKey) - .add("boom", "goes the dynamite") - .add("more", "cowbell"); -String json = jwk.toJSON(); +JSONWebKey jwk=JSONWebKey.build(privateKey) + .add("boom","goes the dynamite") + .add("more","cowbell"); + String json=jwk.toJSON(); ``` ```json { - "alg" : "ES256", - "boom" : "goes the dynamite", - "crv" : "P-256", - "kty" : "EC", - "more" : "cowbell", - "use" : "sig", - "x" : "NIWpsIea0qzB22S0utDG8dGFYqEInv9C7ZgZuKtwjno", - "y" : "iVFFtTgiInz_fjh-n1YqbibnUb2vtBZFs3wPpQw3mc0" + "alg": "ES256", + "boom": "goes the dynamite", + "crv": "P-256", + "kty": "EC", + "more": "cowbell", + "use": "sig", + "x": "NIWpsIea0qzB22S0utDG8dGFYqEInv9C7ZgZuKtwjno", + "y": "iVFFtTgiInz_fjh-n1YqbibnUb2vtBZFs3wPpQw3mc0" } ``` ## Building - + ## Building with Maven + ```bash $ mvn install ``` - ## Building with Savant ```bash diff --git a/fusionauth-jwt.ipr b/fusionauth-jwt.ipr index 609481f..0b4e9fe 100644 --- a/fusionauth-jwt.ipr +++ b/fusionauth-jwt.ipr @@ -266,6 +266,10 @@