diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md index 6f3c9ae56..c577411ac 100644 --- a/.github/ISSUE_TEMPLATE/release.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -11,21 +11,32 @@ labels: ### Dev Tag - [supertokens-core:X.Y](https://github.com/supertokens/supertokens-core/tree/X.Y) - [ ] core + - [ ] check CDI, plugin interface list - [ ] plugin-interface + - [ ] check plugin interface list - [ ] mysql-plugin + - [ ] check plugin interface list - [ ] postgresql-plugin + - [ ] check plugin interface list - [ ] mongodb-plugin + - [ ] check plugin interface list - [ ] [supertokens-node:X.Y](https://github.com/supertokens/supertokens-node/tree/X.Y) + - [ ] check CDI, FDI list - [ ] [supertokens-golang:X.Y](https://github.com/supertokens/supertokens-golang/tree/X.Y) + - [ ] check CDI, FDI list - [ ] [supertokens-python:X.Y](https://github.com/supertokens/supertokens-python/tree/X.Y) + - [ ] check CDI, FDI list - [ ] [supertokens-website:X.Y](https://github.com/supertokens/supertokens-website/X.Y) + - [ ] check FDI list - [ ] [supertokens-auth-react:X.Y](https://github.com/supertokens/supertokens-auth-react/tree/X.Y) + - [ ] check FDI list - [ ] Updated dependencies to use supertokens-website from npm registry - [ ] Various browsers - Safari, Firefox, Chrome, Edge - [ ] Mobile responsiveness - [ ] Make sure using with-typescript example that types are correct for every new configs exposed to users - [ ] Make sure frontend login UI shows even if backend is not working. - [ ] [supertokens-react-native:X.Y](https://github.com/supertokens/supertokens-react-native/X.Y) + - [ ] check FDI list ### Others @@ -35,6 +46,7 @@ labels: - [ ] Examples apps in supertokens-python - [ ] Examples apps in supertokens-node - [ ] [next.js:canary](https://github.com/supertokens/next.js/tree/canary/examples/with-supertokens) + - [ ] RedwoodJS and playground-auth - [ ] Run on netlify (and hence AWS lambda) to check if it works fine there ### 📚 Documentation (test site) @@ -47,6 +59,7 @@ labels: - [ ] homepage - [ ] pricing page feature list - [ ] comparison chart in the pricing page + - [ ] product roadmap page ## 🔥 Production @@ -63,6 +76,11 @@ labels: - [ ] Postgres - [ ] MongoDB - [ ] try.supertokens.io + ``` + docker rm try-supertokens -f + docker rmi supertokens/supertokens-postgresql: + ~/try-supertokens/start_container.sh + ``` - [ ] Update SaaS config - [ ] Update to tables checked for user count / or to know if a deployment is being used or not - [ ] Update logic for exporting csv file for registered users diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fab524d4..69b268202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixes - Issue with JWT expiry always being lower than expected +- Modulus and exponent for JsonWebKeys are now sent as unsigned when fetching public keys from the /jwt/jwks.json + endpoint. Both values are url encoded without any padding. ### Changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f71a513a4..eb0e6eaa4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,6 +77,11 @@ Please ask as many questions as you need, either directly in the issue or on [Di - plugin tests: ![plugin tests passing](https://github.com/supertokens/supertokens-logo/blob/master/images/plugin-tests-passing.png) +## Running the core manually +1. Run `startTestingEnv --wait` in a terminal, and keep it running +2. Then open `supertokens-root` in another terminal and run `cp ./temp/config.yaml .` +3. Then run `java -classpath "./core/*:./plugin-interface/*" io.supertokens.Main ./ DEV`. This will start the core to listen on `http://localhost:3567` + ## Pull Request 1. Before submitting a pull request make sure all tests have passed 2. Reference the relevant issue or pull request and give a clear description of changes/features added when submitting a pull request diff --git a/README.md b/README.md index a44dff9f3..bd8766ece 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,9 @@ Mihály Lengyel
Jacob Marshall
+
miketromba
+
Oleg Vdovenko
+
Siddharth
diff --git a/cli/jar/cli.jar b/cli/jar/cli.jar index f139ad696..b843ad4b6 100644 Binary files a/cli/jar/cli.jar and b/cli/jar/cli.jar differ diff --git a/downloader/jar/downloader.jar b/downloader/jar/downloader.jar index f65229e86..024641cc6 100644 Binary files a/downloader/jar/downloader.jar and b/downloader/jar/downloader.jar differ diff --git a/jar/core-3.6.0.jar b/jar/core-3.6.0.jar deleted file mode 100644 index 93bb02248..000000000 Binary files a/jar/core-3.6.0.jar and /dev/null differ diff --git a/jar/core-3.6.1.jar b/jar/core-3.6.1.jar index 6650ae798..014de9599 100644 Binary files a/jar/core-3.6.1.jar and b/jar/core-3.6.1.jar differ diff --git a/src/main/java/io/supertokens/jwt/JWTSigningFunctions.java b/src/main/java/io/supertokens/jwt/JWTSigningFunctions.java index 7c7602abc..8740e0f50 100644 --- a/src/main/java/io/supertokens/jwt/JWTSigningFunctions.java +++ b/src/main/java/io/supertokens/jwt/JWTSigningFunctions.java @@ -27,6 +27,7 @@ import io.supertokens.pluginInterface.jwt.JWTAsymmetricSigningKeyInfo; import io.supertokens.pluginInterface.jwt.JWTSigningKeyInfo; +import java.math.BigInteger; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -96,6 +97,49 @@ public static String createJWTToken(Main main, String algorithm, JsonObject payl return com.auth0.jwt.JWT.create().withPayload(jwtPayload).withHeader(headerClaims).sign(signingAlgorithm); } + /** + * Returns a byte array representation of the specified big integer + * without the sign bit. + * + * @param bigInt The big integer to be converted. Must not be + * {@code null}. + * + * @return A byte array representation of the big integer, without the + * sign bit. + */ + private static byte[] toBytesUnsigned(final BigInteger bigInt) { + + // Copied from Apache Commons Codec 1.8 + + int bitlen = bigInt.bitLength(); + + // round bitlen + bitlen = ((bitlen + 7) >> 3) << 3; + final byte[] bigBytes = bigInt.toByteArray(); + + if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { + + return bigBytes; + + } + + // set up params for copying everything but sign bit + int startSrc = 0; + int len = bigBytes.length; + + // if bigInt is exactly byte-aligned, just skip signbit in copy + if ((bigInt.bitLength() % 8) == 0) { + + startSrc = 1; + len--; + } + + final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec + final byte[] resizedBytes = new byte[bitlen / 8]; + System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); + return resizedBytes; + } + /** * Used to return public keys that a JWT verifier will use. Note returns an empty array if there are no keys in * storage. @@ -131,10 +175,10 @@ public static List getJWKS(Main main) throws StorageQueryException, // Most verifiers seem to expect kty and alg to be in upper case so forcing that here jwk.addProperty("kty", algorithm.getAlgorithmType().toUpperCase()); jwk.addProperty("kid", currentKeyInfo.keyId); - jwk.addProperty("n", Base64.getUrlEncoder() - .encodeToString(((RSAPublicKey) publicKey).getModulus().toByteArray())); - jwk.addProperty("e", Base64.getUrlEncoder() - .encodeToString(((RSAPublicKey) publicKey).getPublicExponent().toByteArray())); + jwk.addProperty("n", Base64.getUrlEncoder().withoutPadding() + .encodeToString(toBytesUnsigned(((RSAPublicKey) publicKey).getModulus()))); + jwk.addProperty("e", Base64.getUrlEncoder().withoutPadding() + .encodeToString(toBytesUnsigned(((RSAPublicKey) publicKey).getPublicExponent()))); jwk.addProperty("alg", currentKeyInfo.algorithm.toUpperCase()); jwk.addProperty("use", "sig"); // We generate JWKs that are meant to be used for signature // verification diff --git a/src/test/java/io/supertokens/test/jwt/JWKSTest.java b/src/test/java/io/supertokens/test/jwt/JWKSTest.java index 55b4f7662..4a7ae1b95 100644 --- a/src/test/java/io/supertokens/test/jwt/JWKSTest.java +++ b/src/test/java/io/supertokens/test/jwt/JWKSTest.java @@ -238,4 +238,32 @@ public String getPrivateKeyId() { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + /** + * Test that the modulus of the JWK is unsigned + */ + @Test + public void testThatJWKModulusIsUnsigned() throws Exception { + String[] args = { "../" }; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + List keysFromStorage = JWTSigningFunctions.getJWKS(process.getProcess()); + + for (int i = 0; i < keysFromStorage.size(); i++) { + JsonObject key = keysFromStorage.get(i); + byte[] modulusBytes = Base64.getUrlDecoder().decode(key.get("n").getAsString()); + + // The modulus is always positive and should not contain the sign byte (0) + assert modulusBytes[0] != 0; + + byte[] exponentBytes = Base64.getUrlDecoder().decode(key.get("e").getAsString()); + + // The exponent is always positive and should not contain the sign byte (0) + assert exponentBytes[0] != 0; + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } }