diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..83a6502 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + scalaversion: ["2.11.12", "2.12.15", "2.13.8"] + jsEnvKind: [NodeJS, JSDOMNodeJS, Firefox, Chrome] + + steps: + - uses: actions/checkout@v2 + - uses: olafurpg/setup-scala@v10 + with: + java-version: "adopt@1.8" + - uses: coursier/cache-action@v5 + + - name: Setup Node.js v14 LTS + if: matrix.jsEnvKind == 'NodeJS' || matrix.jsEnvKind == 'JSDOMNodeJS' + uses: actions/setup-node@v2.1.2 + with: + node-version: 14 + + - name: Install jsdom + if: matrix.jsEnvKind == 'JSDOMNodeJS' + run: npm install + + - name: Test JVM + run: sbt "++${{ matrix.scalaversion }}" testSuiteJVM/test + - name: Test JS + run: sbt "++${{ matrix.scalaversion }}" "set ThisBuild/jsEnvKind := build.JSEnvKind.${{ matrix.jsEnvKind }}" testSuiteJS/test + - name: Doc generation + run: sbt "++${{ matrix.scalaversion }}" root/doc + - name: Header check + run: sbt "++${{ matrix.scalaversion }}" root/headerCheck testSuiteJVM/test:headerCheck testSuiteJS/test:headerCheck diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46c8def --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +/node_modules/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a5ceed --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2013-2020 EPFL + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index f6dceda..59e94aa 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,27 @@ # scalajs-java-securerandom -`scalajs-java-securerandom` provides `java.security.SecureRandom` for Scala.js, in browsers and in Node.js. +`scalajs-java-securerandom` provides `java.security.SecureRandom` for Scala.js, in Node.js, in browsers, and in other environments that provide the Web Crypto API. + +## Usage + +Use the following dependency: + +```scala +libraryDependencies += ("org.scala-js" %%% "scalajs-java-securerandom" % "1.0.0-SNAPSHOT").cross(CrossVersion.for3Use2_13) +``` + +When using a `crossProject`, add the above in `.jsSettings(...)`. + +You can then use `java.security.SecureRandom` from your code, and by extension, the `java.util.UUID.randomUUID()` method. + +When running in an unsupported environment, a `java.lang.UnsupportedOperationException` will be thrown when trying to instantiate `java.security.SecureRandom`. + +Supported environments are: + +* Node.js, +* Browsers, +* Other environments that provide `crypto.getRandomValues(typedArray)`, from the Web Crypto API. + +## License + +`scalajs-java-securerandom` is distributed under the [Apache 2.0 license](./LICENSE.txt), like Scala.js itself. diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..6502546 --- /dev/null +++ b/build.sbt @@ -0,0 +1,146 @@ +import org.openqa.selenium.WebDriver +import org.openqa.selenium.chrome.{ChromeDriver, ChromeOptions} +import org.openqa.selenium.firefox.{FirefoxOptions, FirefoxProfile} +import org.openqa.selenium.remote.server.{DriverFactory, DriverProvider} + +import org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv +import org.scalajs.jsenv.nodejs.NodeJSEnv +import org.scalajs.jsenv.selenium.SeleniumJSEnv + +import java.util.concurrent.TimeUnit + +import build.JSEnvKind + +lazy val jsEnvKind = + settingKey[JSEnvKind]("Use Node.js or a headless browser for running Scala.js tests") + +inThisBuild(Def.settings( + crossScalaVersions := Seq("2.12.15", "2.11.12", "2.13.8"), + scalaVersion := crossScalaVersions.value.head, + + version := "1.0.0-SNAPSHOT", + organization := "org.scala-js", + scalacOptions ++= Seq( + "-encoding", "utf-8", + "-deprecation", + "-feature", + "-Xfatal-warnings", + ), + + // Licensing + homepage := Some(url("https://github.com/scala-js/scala-js-java-securerandom")), + startYear := Some(2022), + licenses += (("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0"))), + scmInfo := Some(ScmInfo( + url("https://github.com/scala-js/scala-js-java-securerandom"), + "scm:git:git@github.com:scala-js/scala-js-java-securerandom.git", + Some("scm:git:git@github.com:scala-js/scala-js-java-securerandom.git"))), + + // Publishing + publishMavenStyle := true, + publishTo := { + val nexus = "https://oss.sonatype.org/" + if (version.value.endsWith("-SNAPSHOT")) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") + }, + pomExtra := ( + + + sjrd + Sébastien Doeraene + https://github.com/sjrd/ + + + gzm0 + Tobias Schlatter + https://github.com/gzm0/ + + + ), + pomIncludeRepository := { _ => false }, + + // Environments + + jsEnvKind := JSEnvKind.NodeJS, + + Test / jsEnv := { + import JSEnvKind._ + + jsEnvKind.value match { + case NodeJS => + new NodeJSEnv() + case JSDOMNodeJS => + new JSDOMNodeJSEnv() + case Firefox => + val profile = new FirefoxProfile() + profile.setPreference("privacy.file_unique_origin", false) + val options = new FirefoxOptions() + options.setProfile(profile) + options.setHeadless(true) + new SeleniumJSEnv(options) + case Chrome => + val options = new ChromeOptions() + options.setHeadless(true) + options.addArguments("--allow-file-access-from-files") + val factory = new DriverFactory { + val defaultFactory = SeleniumJSEnv.Config().driverFactory + def newInstance(capabilities: org.openqa.selenium.Capabilities): WebDriver = { + val driver = defaultFactory.newInstance(capabilities).asInstanceOf[ChromeDriver] + driver.manage().timeouts().pageLoadTimeout(1, TimeUnit.HOURS) + driver.manage().timeouts().setScriptTimeout(1, TimeUnit.HOURS) + driver + } + def registerDriverProvider(provider: DriverProvider): Unit = + defaultFactory.registerDriverProvider(provider) + } + new SeleniumJSEnv(options, SeleniumJSEnv.Config().withDriverFactory(factory)) + } + }, +)) + +val commonSettings = Def.settings( + // sbt-header configuration + headerLicense := Some(HeaderLicense.Custom( + s"""scalajs-java-securerandom (${homepage.value.get}) + | + |Copyright EPFL. + | + |Licensed under Apache License 2.0 + |(https://www.apache.org/licenses/LICENSE-2.0). + | + |See the NOTICE file distributed with this work for + |additional information regarding copyright ownership. + |""".stripMargin + )), +) + +lazy val root: Project = project.in(file(".")) + .enablePlugins(ScalaJSPlugin) + .settings(commonSettings: _*) + .settings( + name := "scalajs-java-securerandom", + Compile / packageBin / mappings ~= { + _.filter(!_._2.endsWith(".class")) + }, + exportJars := true, + ) + +lazy val testSuite = crossProject(JSPlatform, JVMPlatform) + .in(file("test-suite")) + .jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin)) + .enablePlugins(BuildInfoPlugin) + .settings(commonSettings: _*) + .settings( + testOptions += Tests.Argument(TestFramework("com.novocode.junit.JUnitFramework"), "-v", "-a"), + buildInfoPackage := "org.scalajs.testsuite.utils", + ) + .jsConfigure(_.dependsOn(root)) + .jsSettings( + buildInfoKeys := Seq(BuildInfoKey("jsEnvKind" -> (jsEnvKind.value.toString().toLowerCase(java.util.Locale.ROOT)))), + ) + .jvmSettings( + buildInfoKeys := Seq(BuildInfoKey("jsEnvKind" -> "jvm")), + libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test", + ) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..942ef0a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,502 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dev": true, + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "ws": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", + "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b375582 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "private": true, + "devDependencies": { + "jsdom": "^16.7.0" + } +} diff --git a/project/JSEnvKind.scala b/project/JSEnvKind.scala new file mode 100644 index 0000000..5e7ee8d --- /dev/null +++ b/project/JSEnvKind.scala @@ -0,0 +1,10 @@ +package build + +sealed abstract class JSEnvKind + +object JSEnvKind { + case object NodeJS extends JSEnvKind + case object JSDOMNodeJS extends JSEnvKind + case object Firefox extends JSEnvKind + case object Chrome extends JSEnvKind +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..c8fcab5 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.2 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..9935049 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,8 @@ +libraryDependencies += "org.scala-js" %% "scalajs-env-selenium" % "1.1.1" +libraryDependencies += "org.scala-js" %% "scalajs-env-jsdom-nodejs" % "1.1.0" + +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.0") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0") + +addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.6.0") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") diff --git a/src/main/scala/java/security/SecureRandom.scala b/src/main/scala/java/security/SecureRandom.scala new file mode 100644 index 0000000..a6682f7 --- /dev/null +++ b/src/main/scala/java/security/SecureRandom.scala @@ -0,0 +1,94 @@ +/* + * scalajs-java-securerandom (https://github.com/scala-js/scala-js-java-securerandom) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.security + +import scala.scalajs.js +import scala.scalajs.js.typedarray._ + +// The seed in java.util.Random will be unused, so set to 0L instead of having to generate one +class SecureRandom() extends java.util.Random(0L) { + // Make sure to resolve the appropriate function no later than the first instantiation + private val getRandomValuesFun = SecureRandom.getRandomValuesFun + + /* setSeed has no effect. For cryptographically secure PRNGs, giving a seed + * can only ever increase the entropy. It is never allowed to decrease it. + * Given that we don't have access to an API to strengthen the entropy of the + * underlying PRNG, it's fine to ignore it instead. + * + * Note that the doc of `SecureRandom` says that it will seed itself upon + * first call to `nextBytes` or `next`, if it has not been seeded yet. This + * suggests that an *initial* call to `setSeed` would make a `SecureRandom` + * instance deterministic. Experimentally, this does not seem to be the case, + * however, so we don't spend extra effort to make that happen. + */ + override def setSeed(x: Long): Unit = () + + override def nextBytes(bytes: Array[Byte]): Unit = { + val len = bytes.length + val buffer = new Int8Array(len) + getRandomValuesFun(buffer) + var i = 0 + while (i != len) { + bytes(i) = buffer(i) + i += 1 + } + } + + override protected final def next(numBits: Int): Int = { + if (numBits <= 0) { + 0 // special case because the formula on the last line is incorrect for numBits == 0 + } else { + val buffer = new Int32Array(1) + getRandomValuesFun(buffer) + val rand32 = buffer(0) + rand32 & (-1 >>> (32 - numBits)) // Clear the (32 - numBits) higher order bits + } + } +} + +object SecureRandom { + private lazy val getRandomValuesFun: js.Function1[ArrayBufferView, Unit] = { + if (js.typeOf(js.Dynamic.global.crypto) != "undefined" && + js.typeOf(js.Dynamic.global.crypto.getRandomValues) == "function") { + { (buffer: ArrayBufferView) => + js.Dynamic.global.crypto.getRandomValues(buffer) + () + } + } else if (js.typeOf(js.Dynamic.global.require) == "function") { + try { + val crypto = js.Dynamic.global.require("crypto") + if (js.typeOf(crypto.randomFillSync) == "function") { + { (buffer: ArrayBufferView) => + crypto.randomFillSync(buffer) + () + } + } else { + notSupported + } + } catch { + case _: Throwable => + notSupported + } + } else { + notSupported + } + } + + private def notSupported: Nothing = { + throw new UnsupportedOperationException( + "java.security.SecureRandom is not supported on this platform " + + "because it provides neither `crypto.getRandomValues` nor " + + "Node.js' \"crypto\" module." + ) + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/security/SecureRandomTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/security/SecureRandomTest.scala new file mode 100644 index 0000000..2df3201 --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/security/SecureRandomTest.scala @@ -0,0 +1,79 @@ +/* + * scalajs-java-securerandom (https://github.com/scala-js/scala-js-java-securerandom) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.javalib.security + +import org.junit.Test +import org.junit.Assert._ +import org.junit.Assume._ + +import java.security.SecureRandom + +import org.scalajs.testsuite.utils.Platform + +class SecureRandomTest { + @Test def nextBytes(): Unit = { + assumeTrue("requires a supported CSPRNG", Platform.hasSupportedCSPRNG) + + val random = new SecureRandom() + + val buffer = new Array[Byte](24) + assertTrue("confidence check", buffer.count(_ == 0) == 24) + + random.nextBytes(buffer) + + // the likelihood of this failing should be one in 4 billion + assertTrue("nextBytes() must fill the provided buffer", buffer.count(_ == 0) < 4) + + val buffer2 = new Array[Byte](24) + random.nextBytes(buffer2) + assertFalse("nextBytes() must return different results", buffer2.sameElements(buffer)) + } + + @Test def nextInt(): Unit = { + assumeTrue("requires a supported CSPRNG", Platform.hasSupportedCSPRNG) + + val random = new SecureRandom() + + val i1 = random.nextInt() + val i2 = random.nextInt() + assertNotEquals(i2, i1) + } + + @Test def nextFloat(): Unit = { + assumeTrue("requires a supported CSPRNG", Platform.hasSupportedCSPRNG) + + /* `nextFloat()` actually relies on `next(n)` clearing the higher order + * bits for the resulting float not to exceed 1.0f, so this test is an + * indirect way of testing that specification. + */ + + val random = new SecureRandom() + for (_ <- 0 until 100) { + val f = random.nextFloat() + assertTrue(f >= 0.0f && f < 1.0f) + } + } + + @Test def verifyFailure(): Unit = { + assumeFalse("requires that there is no supported CSPRNG", Platform.hasSupportedCSPRNG) + + val gotFailure = try { + new SecureRandom() + false + } catch { + case _: UnsupportedOperationException => true + } + + assertTrue("expected a failure", gotFailure) + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/UUIDTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/UUIDTest.scala new file mode 100644 index 0000000..92f92ef --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/UUIDTest.scala @@ -0,0 +1,50 @@ +/* + * scalajs-java-securerandom (https://github.com/scala-js/scala-js-java-securerandom) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.javalib.util + +import org.junit.Test +import org.junit.Assert._ +import org.junit.Assume._ + +import java.util.UUID + +import org.scalajs.testsuite.utils.Platform + +class UUIDTest { + @Test def randomUUID(): Unit = { + assumeTrue("requires a supported CSPRNG", Platform.hasSupportedCSPRNG) + + val uuid1 = UUID.randomUUID() + assertEquals(2, uuid1.variant()) + assertEquals(4, uuid1.version()) + + val uuid2 = UUID.randomUUID() + assertEquals(2, uuid2.variant()) + assertEquals(4, uuid2.version()) + + assertNotEquals(uuid1, uuid2) + } + + @Test def verifyFailure(): Unit = { + assumeFalse("requires that there is no supported CSPRNG", Platform.hasSupportedCSPRNG) + + val gotFailure = try { + UUID.randomUUID() + false + } catch { + case _: UnsupportedOperationException => true + } + + assertTrue("expected a failure", gotFailure) + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/Platform.scala new file mode 100644 index 0000000..26d217d --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/Platform.scala @@ -0,0 +1,18 @@ +/* + * scalajs-java-securerandom (https://github.com/scala-js/scala-js-java-securerandom) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.utils + +object Platform { + val hasSupportedCSPRNG: Boolean = + BuildInfo.jsEnvKind != "jsdomnodejs" +}