Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 141 additions & 56 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions rustls-platform-verifier/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rustls-platform-verifier"
version = "0.4.0"
version = "0.5.0"
authors = ["ComplexSpaces <complexspacescode@gmail.com>", "1Password"]
description = "rustls-platform-verifier supports verifying TLS certificates in rustls with the operating system verifier"
keywords = ["tls", "certificate", "verification", "os", "native"]
Expand Down Expand Up @@ -32,7 +32,7 @@ docsrs = ["jni"]
rustls = { version = "0.23.16", default-features = false, features = ["std"] }
log = { version = "0.4" }
base64 = { version = "0.22", optional = true } # Only used when the `cert-logging` feature is enabled.
jni = { version = "0.19", default-features = false, optional = true } # Only used during doc generation
jni = { version = "0.21", default-features = false, optional = true } # Only used during doc generation
once_cell = "1.9"
paste = { version = "1.0", default-features = false, optional = true } # Only used when `ffi-testing` feature is enabled

Expand All @@ -42,7 +42,7 @@ webpki = { package = "rustls-webpki", version = "0.102", default-features = fals

[target.'cfg(target_os = "android")'.dependencies]
rustls-platform-verifier-android = { path = "../android-release-support", version = "0.1.0" }
jni = { version = "0.19", default-features = false }
jni = { version = "0.21", default-features = false }
webpki = { package = "rustls-webpki", version = "0.102", default-features = false }
android_logger = { version = "0.13", optional = true } # Only used during testing.

Expand Down
46 changes: 20 additions & 26 deletions rustls-platform-verifier/src/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ impl Global {
};

Ok(Context {
context: env.new_global_ref(context)?,
loader: env.new_global_ref(loader)?,
env,
context: JObject::from(context),
loader: JObject::from(loader),
})
}
}
Expand All @@ -74,10 +74,10 @@ fn global() -> &'static Global {
/// nothing else in your application needs access the Android runtime.
///
/// Initialization must be done before any verification is attempted.
pub fn init_hosted(env: &JNIEnv, context: JObject) -> Result<(), JNIError> {
pub fn init_hosted(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> {
Comment thread
cpu marked this conversation as resolved.
GLOBAL.get_or_try_init(|| -> Result<_, JNIError> {
let loader =
env.call_method(context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?;
env.call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?;
let global = Global::Internal {
java_vm: env.get_java_vm()?,
context: env.new_global_ref(context)?,
Expand Down Expand Up @@ -126,14 +126,14 @@ impl From<JNIError> for Error {

pub(super) struct Context<'a> {
env: JNIEnv<'a>,
context: JObject<'a>,
loader: JObject<'a>,
context: GlobalRef,
loader: GlobalRef,
}

impl<'a> Context<'a> {
/// Borrow a reference to the JNI Environment executing the Android application
pub(super) fn env(&self) -> &JNIEnv<'a> {
&self.env
pub(super) fn env(&mut self) -> &mut JNIEnv<'a> {
&mut self.env
}

/// Borrow the `applicationContext` from the Android application
Expand All @@ -146,14 +146,13 @@ impl<'a> Context<'a> {
///
/// This should be used instead of `JNIEnv::find_class` to ensure all classes
/// in the application can be found.
pub(super) fn load_class(&self, name: &str) -> Result<JClass<'a>, Error> {
let env = self.env();
let name = env.new_string(name)?;
let class = env.call_method(
self.loader,
pub(super) fn load_class(&mut self, name: &str) -> Result<JClass<'a>, Error> {
let name = self.env.new_string(name)?;
let class = self.env.call_method(
&self.loader,
"loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[JValue::from(name)],
&[JValue::from(&name)],
)?;

Ok(JObject::try_from(class)?.into())
Expand All @@ -165,19 +164,14 @@ impl<'a> Context<'a> {
/// are cleared.
pub(super) fn with_context<F, T>(f: F) -> Result<T, Error>
where
F: FnOnce(&Context) -> Result<T, Error>,
F: FnOnce(&mut Context, &mut JNIEnv) -> Result<T, Error>,
{
let context = global().context()?;
let env = context.env();
let mut context = global().context()?;
let mut binding = global().context()?;
let env = binding.env();

// 16 is the default capacity in the JVM, we can make this configurable if necessary
env.push_local_frame(16)?;

let res = f(&context);

env.pop_local_frame(JObject::null())?;

res
env.with_local_frame(16, |env| f(&mut context, env))
}

/// Loads and caches a class on first use
Expand All @@ -196,13 +190,13 @@ impl CachedClass {
}

/// Gets the cached class reference, loaded on first use
pub(super) fn get<'a: 'b, 'b>(&'a self, cx: &Context<'b>) -> Result<JClass<'b>, Error> {
pub(super) fn get<'a: 'b, 'b>(&'a self, cx: &mut Context<'b>) -> Result<&JClass<'b>, Error> {
let class = self.class.get_or_try_init(|| -> Result<_, Error> {
let class = cx.load_class(self.name)?;

Ok(cx.env().new_global_ref(class)?)
})?;

Ok(JClass::from(class.as_obj()))
Ok(class.as_obj().into())
}
}
20 changes: 10 additions & 10 deletions rustls-platform-verifier/src/tests/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mod android {
const SUCCESS_MARKER: &str = "success";

fn run_android_test<'a>(
env: &'a JNIEnv,
env: &'a mut JNIEnv,
cx: JObject,
suite_name: &'static str,
test_cases: &'static [fn()],
Expand Down Expand Up @@ -86,53 +86,53 @@ mod android {

#[export_name = "Java_org_rustls_platformverifier_CertificateVerifierTests_mockTests"]
pub extern "C" fn rustls_platform_verifier_mock_test_suite(
env: JNIEnv,
mut env: JNIEnv,
_class: JClass,
cx: JObject,
) -> jstring {
log::info!("running mock test suite...");

run_android_test(
&env,
&mut env,
cx,
"mock tests",
tests::verification_mock::ALL_TEST_CASES,
)
.into_inner()
.into_raw()
}

#[export_name = "Java_org_rustls_platformverifier_CertificateVerifierTests_verifyMockRootUsage"]
pub extern "C" fn rustls_platform_verifier_verify_mock_root_usage(
env: JNIEnv,
mut env: JNIEnv,
_class: JClass,
cx: JObject,
) -> jstring {
log::info!("verifying mock roots are not used by default...");

run_android_test(
&env,
&mut env,
cx,
"mock root verification",
&[tests::verification_mock::verification_without_mock_root],
)
.into_inner()
.into_raw()
}

#[export_name = "Java_org_rustls_platformverifier_CertificateVerifierTests_realWorldTests"]
pub extern "C" fn rustls_platform_verifier_real_world_test_suite(
env: JNIEnv,
mut env: JNIEnv,
_class: JClass,
cx: JObject,
) -> jstring {
log::info!("running real world suite...");

run_android_test(
&env,
&mut env,
cx,
"real world",
tests::verification_real_world::ALL_TEST_CASES,
)
.into_inner()
.into_raw()
}
}

Expand Down
4 changes: 2 additions & 2 deletions rustls-platform-verifier/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ pub fn assert_cert_error_eq<E: StdError + PartialEq + 'static>(
/// we know the test certificates are valid. This must be updated if the mock certificates
/// are regenerated.
pub(crate) fn verification_time() -> pki_types::UnixTime {
// Monday, September 16th, 2024 19:21:42 UTC
pki_types::UnixTime::since_unix_epoch(Duration::from_secs(1_726_514_502))
// Saturday, November 9th, 2024 19:52:49 UTC
pki_types::UnixTime::since_unix_epoch(Duration::from_secs(1_731_181_969))
}

fn ensure_global_state() {
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ const LETSENCRYPT_ORG: &str = "letsencrypt.org";
const VALID_LETSENCRYPT_ORG_CHAIN: &[&[u8]] = &[
include_bytes!("letsencrypt_org_valid_1.crt"),
include_bytes!("letsencrypt_org_valid_2.crt"),
include_bytes!("letsencrypt_org_valid_3.crt"),
];

macro_rules! real_world_test_cases {
Expand Down
52 changes: 28 additions & 24 deletions rustls-platform-verifier/src/verification/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ impl Default for Verifier {
#[cfg(any(test, feature = "ffi-testing"))]
impl Drop for Verifier {
fn drop(&mut self) {
with_context::<_, ()>(|cx| {
let env = cx.env();
with_context::<_, ()>(|cx, env| {
env.call_static_method(CERT_VERIFIER_CLASS.get(cx)?, "clearMockRoots", "()V", &[])?
.v()?;
Ok(())
Expand Down Expand Up @@ -113,8 +112,7 @@ impl Verifier {
.try_into()
.map_err(|_| TlsError::FailedToGetCurrentTime)?;

let verification_result = with_context(|cx| {
let env = cx.env();
let verification_result = with_context(|cx, env| {
// We don't provide an initial element so that the array filling can be cleaner.
// It's valid to provide a `null` value. Ref: https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html -> NewObjectArray
let cert_list = {
Expand All @@ -127,7 +125,7 @@ impl Verifier {
for (idx, cert) in certificate_chain {
let idx = idx.try_into().unwrap();
let cert_buffer = env.byte_array_from_slice(cert)?;
env.set_object_array_element(array, idx, cert_buffer)?
env.set_object_array_element(&array, idx, cert_buffer)?
}

array
Expand All @@ -143,17 +141,16 @@ impl Verifier {
for (idx, eku) in ALLOWED_EKUS.iter().enumerate() {
let idx = idx.try_into().unwrap();
let eku = env.new_string(eku)?;
env.set_object_array_element(array, idx, eku)?;
env.set_object_array_element(&array, idx, eku)?;
}

array
};

let ocsp_response = ocsp_response
.map(|b| env.byte_array_from_slice(b))
.transpose()?
.map(JObject::from)
.unwrap_or_else(JObject::null);
let ocsp_response = match ocsp_response {
Some(b) => env.byte_array_from_slice(b)?,
None => JObject::null().into(),
};

#[cfg(any(test, feature = "ffi-testing"))]
{
Expand All @@ -163,7 +160,7 @@ impl Verifier {
CERT_VERIFIER_CLASS.get(cx)?,
"addMockRoot",
"([B)V",
&[JValue::from(mock_root)],
&[JValue::from(&mock_root)],
)?
.v()
.expect("failed to add test root")
Expand All @@ -189,13 +186,13 @@ impl Verifier {
"verifyCertificateChain",
VERIFIER_CALL,
&[
JValue::from(*cx.application_context()),
JValue::from(env.new_string(server_name.to_str())?),
JValue::from(env.new_string(AUTH_TYPE)?),
JValue::from(JObject::from(allowed_ekus)),
JValue::from(ocsp_response),
JValue::from(cx.application_context()),
JValue::from(&env.new_string(server_name.to_str())?),
JValue::from(&env.new_string(AUTH_TYPE)?),
JValue::from(&JObject::from(allowed_ekus)),
JValue::from(&ocsp_response),
JValue::Long(now),
JValue::from(JObject::from(cert_list)),
JValue::from(&JObject::from(cert_list)),
],
)?
.l()?;
Expand Down Expand Up @@ -245,9 +242,12 @@ impl Verifier {
}
}

fn extract_result_info(env: &JNIEnv<'_>, result: JObject<'_>) -> (VerifierStatus, Option<String>) {
fn extract_result_info(
env: &mut JNIEnv<'_>,
result: JObject<'_>,
) -> (VerifierStatus, Option<String>) {
let status_code = env
.get_field(result, "code", "I")
.get_field(&result, "code", "I")
.and_then(|code| code.i())
.unwrap();

Expand All @@ -266,11 +266,15 @@ fn extract_result_info(env: &JNIEnv<'_>, result: JObject<'_>) -> (VerifierStatus
let msg = env
.get_field(result, "message", "Ljava/lang/String;")
.and_then(|m| m.l())
.map(|o| (!o.is_null()).then_some(o))
.and_then(|s| s.map(|s| JavaStr::from_env(env, s.into())).transpose())
.map(|s| {
Comment thread
complexspaces marked this conversation as resolved.
if s.is_null() {
None
} else {
JavaStr::from_env(env, &s.into()).ok().map(String::from)
}
})
.unwrap();

(status, msg.map(String::from))
(status, msg)
}

impl ServerCertVerifier for Verifier {
Expand Down