diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a05d1122..0dd8924a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -177,7 +177,7 @@ jobs: java-version: '17' - name: Run Android tests - uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # 2.33.0 + uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # 2.38.0 with: api-level: 28 # Android 9, Pie. arch: x86_64 diff --git a/.gitignore b/.gitignore index b7671fd5..1263ab66 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,10 @@ /android/verification/ # Ignore all generated Maven local repository files and folders -/android-release-support/maven/pom.xml -/android-release-support/maven/rustls/rustls-platform-verifier/**/ -/android-release-support/maven/rustls/rustls-platform-verifier/maven-metadata-local.xml +/android-release-support/maven/org/rustls/rustls-platform-verifier/* +# These two must be kept since the state must be shared between normal branches and the Maven archive one. +!/android-release-support/maven/org/rustls/rustls-platform-verifier/maven-metadata.xml +!/android-release-support/maven/org/rustls/rustls-platform-verifier/maven-metadata-local.xml # Nix /result diff --git a/README.md b/README.md index 614ff992..f77c9ba0 100644 --- a/README.md +++ b/README.md @@ -113,83 +113,70 @@ let config = ClientConfig::builder_with_provider(arc_crypto_provider) ### Android Some manual setup is required, outside of `cargo`, to use this crate on Android. In order to use Android's certificate verifier, the crate needs to call into the JVM. A small Kotlin -component must be included in your app's build to support `rustls-platform-verifier`. If distributing a library, that component will need to be bundled into your release jar -[as it is not yet available on Maven](https://github.com/rustls/rustls-platform-verifier/issues/115). +component must be included in your app's build to support `rustls-platform-verifier`. #### Gradle Setup -`rustls-platform-verifier` bundles the required native components in the crate, but the project must be setup to locate them +`rustls-platform-verifier` distributes the required native components in a Maven-compatible format via GitHub, but the project must be setup to locate them automatically and correctly. These steps assume you are using `.gradle` Groovy files because they're the most common, but if you are using Kotlin scripts (`.gradle.kts`) for configuration instead, an example snippet is included towards the end of this section. -Inside of your project's `build.gradle` file, add the following code and Maven repository definition. If applicable, this should only be the one "app" sub-project that -will actually be using this crate at runtime. With multiple projects running this, your Gradle configuration performance may degrade. +Each snippet includes a [`ValueSource`](https://docs.gradle.org/current/javadoc/org/gradle/api/provider/ValueSource.html) implementation that obtains a +Cargo-synchronized dependency version performantly, and is also friendly to Gradle's configuration cache. The version can be be selected manually instead, +but runtime crashes may occur if a SemVer incompatible version is used. + +Inside of your project's `build.gradle` file, add the following code and Maven repository definition: -
- -App Snippets - -`$PATH_TO_DEPENDENT_CRATE` is the relative path to the Cargo manifest (`Cargo.toml`) of any crate in your workspace that depends on `rustls-platform-verifier` from -the location of your `build.gradle` file: +`$PATH_TO_LOCK_FILE` is the relative path to the Cargo lockfile of your crate or workspace (`Cargo.lock`). ```groovy -import groovy.json.JsonSlurper - -// ...Your own script code could be here... repositories { - // ... Your other repositories could be here... maven { - url = findRustlsPlatformVerifierProject() - metadataSources.artifact() + url = "https://github.com/rustls/rustls-platform-verifier/raw/maven-archive/android-release-support/maven/" } } -String findRustlsPlatformVerifierProject() { - def dependencyText = providers.exec { - it.workingDir = new File("../") - commandLine("cargo", "metadata", "--format-version", "1", "--filter-platform", "aarch64-linux-android", "--manifest-path", "$PATH_TO_DEPENDENT_CRATE/Cargo.toml") - }.standardOutput.asText.get() +abstract class RustlsVersion implements ValueSource { + interface Params extends ValueSourceParameters { + RegularFileProperty getLockFile() + } - def dependencyJson = new JsonSlurper().parseText(dependencyText) - def manifestPath = file(dependencyJson.packages.find { it.name == "rustls-platform-verifier-android" }.manifest_path) - return new File(manifestPath.parentFile, "maven").path + static final String CRATE_NAME = "rustls-platform-verifier-android" + + @Override + String obtain() { + def lockFile = parameters.lockFile.get().asFile + def lines = lockFile.readLines() + def idx = lines.findIndexOf { it.trim() == "name = \"$CRATE_NAME\"" } + def version = idx < 0 ? null : lines.drop(idx + 1) + .find { it.stripLeading().startsWith("version = ") } + ?.find(/"([^"]*)"/) { match, v -> v } + if (!version) throw new GradleException("$CRATE_NAME not found in $lockFile") + return version + } } -``` - -Then, wherever you declare your dependencies, add the following: -```groovy -implementation "rustls:rustls-platform-verifier:latest.release" -``` - -
-
-Library Snippets - -```groovy -import groovy.json.JsonSlurper - -// ...Your own script code could be here... - -File findRustlsPlatformVerifierClasses() { - def dependencyText = providers.exec { - it.workingDir = new File("../") - commandLine("cargo", "metadata", "--format-version", "1") - }.standardOutput.asText.get() +def rustlsPlatformVerifierVersion = providers.of(RustlsVersion) { spec -> + spec.parameters.lockFile.set(layout.projectDirectory.file($PATH_TO_LOCK_FILE)) +} - def dependencyJson = new JsonSlurper().parseText(dependencyText) - def manifestFile = file(dependencyJson.packages.find { it.name == "rustls-platform-verifier-android" }.manifest_path) - return new File(manifestFile.parentFile, "classes.jar") +configurations.configureEach { configuration -> + configuration.resolutionStrategy.eachDependency { details -> + if (details.requested.group == "org.rustls" && details.requested.name == "rustls-platform-verifier") { + details.useVersion(rustlsPlatformVerifierVersion.get()) + details.because("native component version must be identical to version of ${RustlsVersion.CRATE_NAME}") + } + } } ``` Then, wherever you declare your dependencies, add the following: ```groovy -implementation files(findRustlsPlatformVerifierClasses()) +implementation "rustls:rustls-platform-verifier" ``` -
+The dependency intentionally has no static version, it is only resolved dynamically at configuration time by the build script. Cargo automatically handles finding the downloaded crate in the correct location for your project. It also handles updating the version when new releases of `rustls-platform-verifier` are published. If you only use published releases, no extra maintenance should be required. @@ -199,49 +186,51 @@ implementation part can be located on-disk. ##### Kotlin and Gradle -
-Kotlin script App example - `build.gradle.kts`: ```kotlin -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive - -buildscript { - dependencies { - classpath(libs.kotlinx.serialization.json) - } -} repositories { - rustlsPlatformVerifier() + maven { + url = uri("https://github.com/rustls/rustls-platform-verifier/raw/maven-archive/android-release-support/maven/") + } } -fun RepositoryHandler.rustlsPlatformVerifier(): MavenArtifactRepository { - @Suppress("UnstableApiUsage") - val manifestPath = let { - val dependencyJson = providers.exec { - workingDir = File(project.rootDir, "../") - commandLine("cargo", "metadata", "--format-version", "1", "--filter-platform", "aarch64-linux-android", "--manifest-path", "$PATH_TO_DEPENDENT_CRATE/Cargo.toml") - }.standardOutput.asText - - val path = Json.decodeFromString(dependencyJson.get()) - .getValue("packages") - .jsonArray - .first { element -> - element.jsonObject.getValue("name").jsonPrimitive.content == "rustls-platform-verifier-android" - }.jsonObject.getValue("manifest_path").jsonPrimitive.content - - File(path) +abstract class RustlsVersion : ValueSource { + interface Params : ValueSourceParameters { + val lockFile: RegularFileProperty + } + + companion object { + const val CRATE_NAME = "rustls-platform-verifier-android" + } + + override fun obtain(): String { + val version = parameters.lockFile.get().asFile.readLines().let { lines -> + val nameIdx = lines.indexOfFirst { it.trim() == "name = \"$CRATE_NAME\"" } + if (nameIdx < 0) { + null + } else { + lines.drop(nameIdx + 1) + .firstOrNull { it.trimStart().startsWith("version = ") } + ?.substringAfter('"', "") + ?.substringBefore('"', "") + ?.takeIf { it.isNotEmpty() } + } + } + return version?: error("$CRATE_NAME not found in Cargo.lock") } +} + +val rustlsPlatformVerifierVersion = providers.of(RustlsVersion::class.java) { + parameters.lockFile.set(layout.projectDirectory.file($PATH_TO_LOCK_FILE)) +} - return maven { - url = uri(File(manifestPath.parentFile, "maven").path) - metadataSources.artifact() +configurations.configureEach { + resolutionStrategy.eachDependency { + if (requested.group == "org.rustls" && requested.name == "rustls-platform-verifier") { + useVersion(rustlsPlatformVerifierVersion.get()) + because("native component version must be identical to version of ${RustlsVersion.CRATE_NAME}") + } } } @@ -253,51 +242,9 @@ dependencies { `libs.version.toml`: ```toml -# We always use the latest release because `cargo` keeps it in sync with the associated Rust crate's version. -rustls-platform-verifier = { group = "rustls", name = "rustls-platform-verifier", version = "latest.release" } -``` -
- -
-Kotlin script Library example - -`build.gradle.kts`: -```kotlin -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive - -buildscript { - dependencies { - classpath(libs.kotlinx.serialization.json) - } -} - -fun findRustlsPlatformVerifierClasses(): File { - val dependencyJson = providers.exec { - workingDir = File(project.rootDir, "../") - commandLine("cargo", "metadata", "--format-version", "1") - }.standardOutput.asText - - val path = Json.decodeFromString(dependencyJson.get()) - .getValue("packages") - .jsonArray - .first { element -> - element.jsonObject.getValue("name").jsonPrimitive.content == "rustls-platform-verifier-android" - }.jsonObject.getValue("manifest_path").jsonPrimitive.content - - val manifestFile = File(path) - return File(manifestFile.parentFile, "classes.jar") -} - -dependencies { - implementation(files(findRustlsPlatformVerifierClasses())) -} +# We keep the dependency unversioned because its version is selected dynamically during configuration. +rustls-platform-verifier = { group = "rustls", name = "rustls-platform-verifier" } ``` -
#### Proguard diff --git a/admin/RELEASING.md b/admin/RELEASING.md index 024cab8c..9c5c5bf2 100644 --- a/admin/RELEASING.md +++ b/admin/RELEASING.md @@ -20,31 +20,25 @@ In the release preparation PR, the releaser may include the following checklist 1. Update main crate's version in `rustls-platform-verifier/Cargo.toml`. 2. If any non-test changes have been made to the `android` directory since the last release: - 1. Update Android artifact version in `android-release-support/Cargo.toml` - 2. Bump dependency version of the Android support crate in `rustls-platform-verifier/Cargo.toml` to match the new one - 3. Commit version increase changes on the release branch + 1. Update Android artifact version in `android-release-support/Cargo.toml`, and in the main crate if creating an incompatible SemVer release. + 2. Commit version increase changes on the release branch * We typically name these branches `rel-xxx` where `xxx` is the major version. * We typically leave these branches around for future maintenance releases. - 4. Run `ci/package_android_release.sh` in a UNIX compatible shell - 5. (Optional) `cargo publish -p rustls-platform-verifier-android --dry-run --allow-dirty` - * `--allow-dirty` is required because we don't check-in the generated Maven local repository. - 6. (Optional) Inspect extracted archive to ensure the local Maven repository artifacts are present - 1. Un-tar the `rustls-platform-verifier-android-*.crate` file inside of `target/package`. - 2. Verify `maven/rustls/rustls-platform-verifier` contains a single `*.RELEASE` directory and that contains a `.aar` file. - 3. (Optional) If the releaser has an external Gradle project that uses the configuration from the README, paste the path to the - unzipped package's `Cargo.toml` as a replacement for the `manifestPath` variable. Run a Gradle Sync and observe everything works. - 7. **Ensure that all version changes are committed to the correct branch before proceeding**. All version increases should be checked in prior + 3. Run `ci/package_android_release.sh` in a UNIX compatible shell + 4. Commit the Maven metadata updates on their own: `git commit -am "Bump Maven release to x.x.x"`. Copy the new commit's short ID. + 5. **Ensure that all version changes are committed to the correct branch before proceeding**. All version increases should be checked in prior to publishing on crates.io. - 8. Publish the Android artifacts' new version: `cargo publish -p rustls-platform-verifier-android --allow-dirty` + 6. Checkout the Maven storage branch: `git checkout maven-archive`. The newly built artifacts are now ready to check in. + 7. Add the new artifacts to storage: `git add . && git commit -m "Prepare Maven release x.x.x"` + 8. Sync the Maven metadata to make the new artifacts visible: `git cherry-pick $MAVEN_BUMP_COMMIT_ID` + 9. Publish the new changes: + * `git push && git checkout rel-xxx` + * Publish the new Android marker version: `cargo publish -p rustls-platform-verifier-android` 3. Commit main crate's version increase on the release branch 4. **Ensure that all version changes are committed to the correct branch before proceeding**. All version increases should be checked in prior to publishing on crates.io. 5. Publish the main crate's new version: `cargo publish -p rustls-platform-verifier` - * Do **not** use `--allow-dirty` for the main crate. Only the Android component requires it and a dirty workspace elsewhere is an error. 6. Follow the remaining steps in [RELEASING] to create the appropiate version tag. -7. If a new Android component release was made: Before publishing the GitHub release, run `./ci/archive_android_release.sh` to create a reproducible archive - containing the Android Maven components that were just published to crates.io. After creating the archive, upload it as an additional release artifact on GitHub. - Then, finish the release creation like normal. [RELEASING]: https://github.com/rustls/rustls/blob/main/RELEASING.md diff --git a/android-release-support/Cargo.toml b/android-release-support/Cargo.toml index ff31c7a7..0ea6279c 100644 --- a/android-release-support/Cargo.toml +++ b/android-release-support/Cargo.toml @@ -6,13 +6,8 @@ repository = "https://github.com/rustls/rustls-platform-verifier" license = "MIT OR Apache-2.0" edition = "2021" -# Explicitly include the Maven local repository for the Android component. -# While not checked into the repository, it is generated for releases and other contexts. include = [ "src/*", - "maven/pom.xml", - "maven/rustls/rustls-platform-verifier/**/", - "maven/rustls/rustls-platform-verifier/maven-metadata-local.xml", ] [dependencies] diff --git a/android-release-support/maven/org/rustls/rustls-platform-verifier/maven-metadata-local.xml b/android-release-support/maven/org/rustls/rustls-platform-verifier/maven-metadata-local.xml new file mode 120000 index 00000000..ee6912e9 --- /dev/null +++ b/android-release-support/maven/org/rustls/rustls-platform-verifier/maven-metadata-local.xml @@ -0,0 +1 @@ +maven-metadata.xml \ No newline at end of file diff --git a/android-release-support/maven/org/rustls/rustls-platform-verifier/maven-metadata.xml b/android-release-support/maven/org/rustls/rustls-platform-verifier/maven-metadata.xml new file mode 100644 index 00000000..576b1b77 --- /dev/null +++ b/android-release-support/maven/org/rustls/rustls-platform-verifier/maven-metadata.xml @@ -0,0 +1,13 @@ + + + org.rustls + rustls-platform-verifier + + 0.1.1 + + 0.1.0 + 0.1.1 + + 20240729132246 + + diff --git a/android-release-support/maven/rustls/rustls-platform-verifier/.gitkeep b/android-release-support/maven/rustls/rustls-platform-verifier/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/android-release-support/pom-template.xml b/android-release-support/pom-template.xml index cbc86eb9..04e43c69 100644 --- a/android-release-support/pom-template.xml +++ b/android-release-support/pom-template.xml @@ -2,7 +2,7 @@ 4.0.0 - rustls + org.rustls rustls-platform-verifier $VERSION aar diff --git a/android-release-support/src/lib.rs b/android-release-support/src/lib.rs index 97fb79ad..6bb66dbc 100644 --- a/android-release-support/src/lib.rs +++ b/android-release-support/src/lib.rs @@ -2,8 +2,8 @@ //! //! This crate is an implementation detail of the actual [rustls-platform-verifier](https://github.com/rustls/rustls-platform-verifier) crate. //! -//! It contains no Rust code and is solely intended as a convenient delivery mechanism for the supporting Kotlin code that the main crate -//! requires to perform TLS certificate validation using Android's APIs. +//! It contains no Rust code and is solely intended as a convenient mechanism to synchronize a SemVer version managed by `cargo` to Gradle in +//! Android build systems in such a way that a SemVer incompatible version of the native component is never used. //! //! Other crates should not directly depend on this crate in any way, as nothing about it is considered stable and it is probably useless elsewhere. //! @@ -13,17 +13,20 @@ //! //! ### Why? //! -//! It was the best middle ground between several tradeoffs. The important ones, in priority order, are: +//! It is the best known middle ground between several tradeoffs. The important ones, in priority order, are: //! - Automatically keeping component versions in sync //! - Allowing well-tested and well-known `cargo` dependency management patterns to apply everywhere //! - Providing a smooth developer experience as an Android consumer of `rustls-platform-verifier` //! -//! Firstly, what alternatives are available for distributing the component? The other two known are source distribution in some form (here, it will be through crates.io) -//! and Maven Central. Starting with the first, its become infeasible due to toolchain syncing requirements. If the Android component is -//! built as part of the host app's Gradle build, then it becomes subject to any Gradle or Android Gradle Plugin incompatibilities/requirements. In practice this means +//! Firstly, what alternatives are available for distributing the component? The other known ones are: +//! - Source distribution in some form (here, it will be through crates.io) +//! - Maven Central (or another hosted package registry) +//! - Bundling Android release artifacts inside crates.io releases +//! +//! Starting with the first, its infeasible due to toolchain syncing requirements. If the Android component is built as part of the host +//! app's Gradle build, then it becomes subject to any Gradle or Android Gradle Plugin incompatibilities/requirements. In practice this means //! the AGP version between this project and the main application have to match all the time. Sometimes this works, but it becomes challenging/unfeasible -//! during yearly toolchain/SDK upgrades and is not maintainable long term. Note that this is the _only_ option in this section which retains compatibility -//! with Cargo's Git dependency patching. +//! during yearly toolchain/SDK upgrades and is not maintainable long term. //! //! Next, Maven Central. This is considered the standard way of distributing public Android dependencies. There are two downsides to this //! approach: version synchronization and publishing overhead. Version syncing is the hardest part: There's not a good way to know what version @@ -38,38 +41,27 @@ //! //! ### The solution //! -//! The final design was built to avoid the pitfalls the previous two options mentioned. To build it, we rely on CI and packaging scripts to build -//! the Android component into a prebuilt AAR file before creating a release. Next, a [on-disk Maven repository](https://maven.apache.org/repositories/local.html) -//! is hosted inside of this repository. Only the unchanging file structure of it is kept checked-in, to avoid churn. The remaining parts are filled in -//! during the packaging/release process, before being included in `cargo package` via an `include` Cargo.toml directive. Finally, once the repository has had -//! its artifacts added the crate containing the Maven repository is published to crates.io. Then, the main crate ensures it's downloaded when an Android target -//! is compiled via a platform-specific dependency. -//! -//! On [the Gradle side](https://github.com/rustls/rustls-platform-verifier/tree/main#gradle-setup), we include a very small snippet of code for users to include in their `settings.gradle` file -//! to dynamically locate the local maven repository on disk automatically based off Cargo's current version of it. The script is configuration cache friendly and -//! doesn't impact performance either. When the script is run, it finds the cargo-cached download of the crate and tells Gradle it can find the Android component there -//! when it gets sourced into the hosting application's build tree. -//! -//! Assuming a properly configured Gradle project, the slow (~500ms) script should only run once per Gradle sync while the `android-release-support` crate -//! remains untouched. This is due to the configuration cache previously mentioned and is what ensures performance on-par with a "normal" Maven repository. -//! Upon any version updates (semver, Git refs, etc), the change will be detected as-intended by Gradle, break the cache, and the project will update the dependency reference to the new AAR file. +//! The current design was built after running into several painpoints with the previous attempted distribution implementations and the need to start including +//! more than just Android code in releases. To produce the release, we rely on packaging scripts to build the Android component into a prebuilt AAR file. +//! Next, a [on-disk Maven repository](https://maven.apache.org/repositories/local.html) is hosted inside of this repository with a special branch on GitHub. +//! Using GitHub's ability to serve raw files, this local repository creates an emulated Maven package repository that can be queried and downloaded from like a hosted registry. //! -//! ### Precompiled artifacts? +//! The remaining parts are filled in during the packaging/release process, with artifacts being pushed from release branches into the special archive branch. +//! The main crate ensures it always uses a compatible version from this local repository by declaring a standard platform-specific dependency on this shim crate. +//! Cargo lockfile resolution takes care of the rest. //! -//! For some, the notion of shipping something pre-compiled with an existing source distribution might seem incorrect, or insecure. However in this specific case, -//! putting aside the fact shipping Kotlin code doesn't work (see above), there are many reasons this isn't the case: -//! - Shipping pre-compiled artifacts is normal in the Java ecosystem. Maven Central and other package repositories do the same thing and serve `.jar` downloads. -//! - Those not using Android will never download the pre-compiled AAR file. -//! - The artifacts are incredibly easy to reproduce given an identical compilation toolchain. -//! - The artifacts are not native executables, or raw `.jar` files, so they can't be accidentally executed on a host system. +//! On [the Gradle side](https://github.com/rustls/rustls-platform-verifier/tree/main#gradle-setup), we instruct users to include a small code snippet in their `settings.gradle` file +//! to dynamically resolve a correct Android library's version to download like any other. When the snippet is run, it finds the version inside the workspace's `Cargo.lock` +//! and provides that to Gradle's version resolution. When the lockfile is changed, the configuration cache is invalidated and the version is calculated again. +//! This happens after any version updates (semver, Git refs, etc). //! //! ## Summary //! //! In summary, the selected distribution method avoids most of the previous pitfalls while still balancing a good experience for `cargo` and Gradle users. Some of its //! positive properties include: //! - Full compatibility with Cargo's dependency management, including Git patching[^1] -//! - No version checking or synchronization required +//! - No version checking or manual synchronization required //! - Painless and harmless to integrate into an Android app's build system //! - Low maintenance for the main crate maintainers' //! -//! [^1]: The Git reference being used must have the local maven repository built and checked-in first. +//! [^1]: The Git reference being used must have an equivalent Maven repository branch inside of it and the Maven repository URL must be switched too. diff --git a/android/gradle/libraries.versions.toml b/android/gradle/libraries.versions.toml index 2bbb3309..6cf1e1d1 100644 --- a/android/gradle/libraries.versions.toml +++ b/android/gradle/libraries.versions.toml @@ -1,6 +1,6 @@ [versions] -kotlin = "1.6.10" +kotlin = "2.2.10" [libraries] -android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version = "7.3.0" } +android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version = "8.3.2" } kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar index e708b1c0..afba1092 100644 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index b2233f38..7a04a2dc 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue May 31 12:07:11 CDT 2022 -distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip -distributionPath=wrapper/dists -zipStorePath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew index 4f906e0c..65dcd68d 100755 --- a/android/gradlew +++ b/android/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat index ac1b06f9..6689b85b 100644 --- a/android/gradlew.bat +++ b/android/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/android/rustls-platform-verifier/build.gradle b/android/rustls-platform-verifier/build.gradle index 59a931c8..8a89d097 100644 --- a/android/rustls-platform-verifier/build.gradle +++ b/android/rustls-platform-verifier/build.gradle @@ -101,9 +101,9 @@ task ktlintFormat(type: JavaExec, group: "formatting") { dependencies { testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${libs.versions.kotlin.get()}" - ktlint 'com.pinterest:ktlint:0.46.1' + ktlint 'com.pinterest:ktlint:0.50.0' } diff --git a/android/rustls-platform-verifier/src/main/AndroidManifest.xml b/android/rustls-platform-verifier/src/main/AndroidManifest.xml index 4fb03756..acb84c07 100644 --- a/android/rustls-platform-verifier/src/main/AndroidManifest.xml +++ b/android/rustls-platform-verifier/src/main/AndroidManifest.xml @@ -1,2 +1,7 @@ - + + + + diff --git a/android/rustls-platform-verifier/src/main/java/org/rustls/platformverifier/CertificateVerifier.kt b/android/rustls-platform-verifier/src/main/java/org/rustls/platformverifier/CertificateVerifier.kt index b9358aa4..618f5eef 100644 --- a/android/rustls-platform-verifier/src/main/java/org/rustls/platformverifier/CertificateVerifier.kt +++ b/android/rustls-platform-verifier/src/main/java/org/rustls/platformverifier/CertificateVerifier.kt @@ -42,7 +42,7 @@ private enum class StatusCode(val value: Int) { // Marked private as this is not meant to be used in Android code. private class VerificationResult( status: StatusCode, - @Suppress("unused") val message: String? = null + @Suppress("unused") val message: String? = null, ) { @Suppress("unused") private val code: Int = status.value @@ -186,7 +186,7 @@ internal object CertificateVerifier { allowedEkus: Array, ocspResponse: ByteArray?, time: Long, - certChain: Array + certChain: Array, ): VerificationResult { // Convert the array of (supposedly) DER bytes into certificates. val certificateChain = mutableListOf() @@ -268,7 +268,7 @@ internal object CertificateVerifier { return when (rootCause) { is CertificateExpiredException, is CertificateNotYetValidException -> VerificationResult( StatusCode.Expired, - rootCause.toString() + rootCause.toString(), ) else -> VerificationResult(StatusCode.UnknownCert, rootCause.toString()) @@ -330,7 +330,7 @@ internal object CertificateVerifier { revocationChecker.options = EnumSet.of( PKIXRevocationChecker.Option.SOFT_FAIL, - PKIXRevocationChecker.Option.ONLY_END_ENTITY + PKIXRevocationChecker.Option.ONLY_END_ENTITY, ) // Use the OCSP data `rustls` provided, if present. @@ -357,14 +357,6 @@ internal object CertificateVerifier { try { validator.validate(certFactory.generateCertPath(validChain), parameters) } catch (e: CertPathValidatorException) { - // LetsEncrypt no longer include OCSP information (as OCSP is being deprecated) which Android is not - // happy with since it *only* tries OCSP by default. We aren't 100% decided on how to fix this yet for real - // (see https://github.com/rustls/rustls-platform-verifier/pull/179) so for now we implement an out for - // tests to allow regular maintenance to proceed. - if (BuildConfig.TEST && e.reason == CertPathValidatorException.BasicReason.UNSPECIFIED) { - return VerificationResult(StatusCode.Ok) - } - return VerificationResult(StatusCode.Revoked, e.toString()) } } else { diff --git a/ci/archive_android_release.sh b/ci/archive_android_release.sh deleted file mode 100755 index 5186e3a5..00000000 --- a/ci/archive_android_release.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash - -# This script's purpose is to package up the Android-specific artifacts from a previous run of `cargo publish -p rustls-platform-verifier-android` for -# later attachment to GitHub releases. It is also intended to be fully reproducible. - -set -euo pipefail - -TAR_NAME="tar" -OUTPUT_NAME="android-artifacts.tar" -version=$(grep -m 1 "version = " android-release-support/Cargo.toml | tr -d "version= " | tr -d '"') -source_date_epoch=$(git log -1 --pretty=%ct) - -# bsdtar (which is the default on macOS) doesn't support the flags we want, so attempt to find a version of GNU -# tar on the system is possible. -if $TAR_NAME --version | grep -q "bsdtar"; then - echo "Detected bsdtar, which is not compatible with this script. Attempting 'gnutar'" - TAR_NAME="gnutar" - - if $TAR_NAME --version | grep -q "bsdtar"; then - echo "GNU tar not found, exiting" - exit 1 - fi -fi - -artifacts_dir="target/package/rustls-platform-verifier-android-$version" - -# This differs based on host target, etc. -rm -rf "$artifacts_dir/target" -# This shows up a lot on macOS, so make sure it doesn't get in the way. -rm -f "$artifacts_dir/.DS_Store" - -# Ref: https://reproducible-builds.org/docs/archives/ -$TAR_NAME --sort=name \ - --mtime="@${source_date_epoch}" \ - --owner=0 --group=0 --numeric-owner \ - --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime \ - -cf "$artifacts_dir/../$OUTPUT_NAME" -C "$artifacts_dir" . - -echo "Successfully created tarball at $artifacts_dir/$OUTPUT_NAME" diff --git a/ci/package_android_release.sh b/ci/package_android_release.sh index 91b06eac..bbda1d25 100755 --- a/ci/package_android_release.sh +++ b/ci/package_android_release.sh @@ -3,7 +3,10 @@ # This script's purpose is to automate the build + packaging steps for the pre-compiled Android verifier component. # It works with template files and directories inside the `android-release-support/` part of the repository to setup # a Maven local repository and then add the pre-compiled AAR file into it for distribution. The results of this packaging -# are then included by `cargo` when publishing `rustls-platform-verifier-android`. +# are then published to dedicated artifacts Git branch on GitHub, emulating an actual online Mavan package repository. +# +# Gradle and other clients download the artifacts from thier native build systems later on with the requested files lining up +# with the structure of the Git repo's contents. This idea was originally inspired by https://github.com/RiV-chain/github-publish-maven-action. set -euo pipefail @@ -23,14 +26,13 @@ pushd ./android popd -artifact_name="rustls-platform-verifier-release.aar" +package_name="rustls-platform-verifier" -pushd ./android-release-support +artifact_name="$package_name-release.aar" -artifact_path="../android/rustls-platform-verifier/build/outputs/aar/$artifact_name" +pushd ./android-release-support -# Ensure no prior artifacts are present -git clean -dfX "./maven/" +artifact_path="../android/$package_name/build/outputs/aar/$artifact_name" cp ./pom-template.xml ./maven/pom.xml @@ -41,3 +43,15 @@ sed -i.bak "s/\$VERSION/$version/" ./maven/pom.xml rm ./maven/pom.xml.bak mvn install:install-file -Dfile="$artifact_path" -Dpackaging="aar" -DpomFile="./maven/pom.xml" -DlocalRepositoryPath="./maven/" + +rm ./maven/pom.xml + +pushd ./maven/ + +artifacts_folder="org/rustls/$package_name/$version" + +rm "$artifacts_folder/_remote.repositories" + +sha1sum "$artifacts_folder/$package_name-$version.aar" > "$artifacts_folder/$package_name-$version.aar.sha1" +sha1sum "$artifacts_folder/$package_name-$version.pom" > "$artifacts_folder/$package_name-$version.pom.sha1" +