Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android's certificate verification JNI layer #2251

Merged
merged 12 commits into from
May 24, 2022
119 changes: 119 additions & 0 deletions library/common/jni/jni_interface.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <ares.h>

#include <string>
#include <vector>

#include "library/common/api/c_types.h"
#include "library/common/extensions/filters/http/platform_bridge/c_types.h"
Expand Down Expand Up @@ -1031,3 +1032,121 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_setPreferredNetwork(JNIEnv* env
return set_preferred_network(static_cast<envoy_engine_t>(engine),
static_cast<envoy_network_t>(network));
}

bool Java_AndroidCertVerifyResult_isIssuedByKnownRoot(JNIEnv* env, jobject result) {
StefanoDuo marked this conversation as resolved.
Show resolved Hide resolved
jclass jcls_AndroidCertVerifyResult = env->FindClass("org/chromium/net/AndroidCertVerifyResult");
StefanoDuo marked this conversation as resolved.
Show resolved Hide resolved
jmethodID jmid_isIssuedByKnownRoot =
env->GetMethodID(jcls_AndroidCertVerifyResult, "isIssuedByKnownRoot", "()Z");
return env->CallBooleanMethod(jcls_AndroidCertVerifyResult, jmid_isIssuedByKnownRoot, result);
}

envoy_cert_verify_status_t Java_AndroidCertVerifyResult_getStatus(JNIEnv* env, jobject result) {
jclass jcls_AndroidCertVerifyResult = env->FindClass("org/chromium/net/AndroidCertVerifyResult");
StefanoDuo marked this conversation as resolved.
Show resolved Hide resolved
jmethodID jmid_getStatus = env->GetMethodID(jcls_AndroidCertVerifyResult, "getStatus", "()I");

return static_cast<envoy_cert_verify_status_t>(
env->CallIntMethod(jcls_AndroidCertVerifyResult, jmid_getStatus, result));
}

jobjectArray Java_AndroidCertVerifyResult_getCertificateChainEncoded(JNIEnv* env, jobject result) {
jclass jcls_AndroidCertVerifyResult = env->FindClass("org/chromium/net/AndroidCertVerifyResult");
StefanoDuo marked this conversation as resolved.
Show resolved Hide resolved
jmethodID jmid_getCertificateChainEncoded =
env->GetMethodID(jcls_AndroidCertVerifyResult, "getCertificateChainEncoded", "()[[B");

return static_cast<jobjectArray>(
env->CallObjectMethod(jcls_AndroidCertVerifyResult, jmid_getCertificateChainEncoded, result));
}

void ExtractCertVerifyResult(JNIEnv* env, jobject result, envoy_cert_verify_status_t* status,
bool* is_issued_by_known_root,
std::vector<std::string>* verified_chain) {
*status = Java_AndroidCertVerifyResult_getStatus(env, result);

*is_issued_by_known_root = Java_AndroidCertVerifyResult_isIssuedByKnownRoot(env, result);

jobjectArray chain_byte_array =
Java_AndroidCertVerifyResult_getCertificateChainEncoded(env, result);
JavaArrayOfByteArrayToStringVector(env, chain_byte_array, verified_chain);
}

static jobject jvm_verify_x509_cert_chain(const std::vector<std::string>& cert_chain,
std::string auth_type, std::string host) {
jni_log("[Envoy]", "jvm_verify_x509_cert_chain");
JNIEnv* env = get_env();
jclass jcls_AndroidNetworkLibrary = env->FindClass("org/chromium/net/AndroidNetworkLibrary");
StefanoDuo marked this conversation as resolved.
Show resolved Hide resolved
jmethodID jmid_verifyServerCertificates = env->GetStaticMethodID(
jcls_AndroidNetworkLibrary, "verifyServerCertificates",
"([[BLjava/lang/String;Ljava/lang/String;)Lorg/chromium/net/AndroidCertVerifyResult;");

jobjectArray chain_byte_array = ToJavaArrayOfByteArray(env, cert_chain);
jstring auth_string = ConvertUTF8ToJavaString(env, auth_type);
jstring host_string = ConvertUTF8ToJavaString(env, host);

jobject result =
env->CallStaticObjectMethod(jcls_AndroidNetworkLibrary, jmid_verifyServerCertificates,
chain_byte_array, auth_string, host_string);

env->DeleteLocalRef(chain_byte_array);
env->DeleteLocalRef(auth_string);
env->DeleteLocalRef(host_string);
return result;
}

static void verify_x509_cert_chain(const std::vector<std::string>& cert_chain,
std::string auth_type, std::string host,
envoy_cert_verify_status_t* status,
bool* is_issued_by_known_root,
std::vector<std::string>* verified_chain) {
jobject result = jvm_verify_x509_cert_chain(cert_chain, auth_type, host);
ExtractCertVerifyResult(get_env(), result, status, is_issued_by_known_root, verified_chain);
}

static void jvm_add_test_root_certificate(const uint8_t* cert, size_t len) {
jni_log("[Envoy]", "jvm_add_test_root_certificate");
JNIEnv* env = get_env();
jclass jcls_AndroidNetworkLibrary = env->FindClass("org/chromium/net/AndroidNetworkLibrary");
jmethodID jmid_addTestRootCertificate =
env->GetStaticMethodID(jcls_AndroidNetworkLibrary, "addTestRootCertificate", "([B)V");

jbyteArray cert_array = ToJavaByteArray(env, cert, len);
env->CallStaticVoidMethod(jcls_AndroidNetworkLibrary, jmid_addTestRootCertificate, cert_array);
env->DeleteLocalRef(cert_array);
}

static void jvm_clear_test_root_certificate() {
jni_log("[Envoy]", "jvm_clear_test_root_certificate");
JNIEnv* env = get_env();
jclass jcls_AndroidNetworkLibrary = env->FindClass("org/chromium/net/AndroidNetworkLibrary");
jmethodID jmid_clearTestRootCertificates =
env->GetStaticMethodID(jcls_AndroidNetworkLibrary, "clearTestRootCertificates", "()V");

env->CallStaticVoidMethod(jcls_AndroidNetworkLibrary, jmid_clearTestRootCertificates);
}

extern "C" JNIEXPORT jobject JNICALL
Java_io_envoyproxy_envoymobile_engine_JniLibrary_callCertificateVerificationFromNative(
JNIEnv* env, jclass, jobjectArray certChain, jstring authType, jstring jhost) {
std::vector<std::string> cert_chain;
std::string auth_type;
std::string host;

JavaArrayOfByteArrayToStringVector(env, certChain, &cert_chain);
ConvertJavaStringToUTF8(env, authType, &auth_type);
ConvertJavaStringToUTF8(env, jhost, &host);

return jvm_verify_x509_cert_chain(cert_chain, auth_type, host);
}

extern "C" JNIEXPORT void JNICALL
Java_io_envoyproxy_envoymobile_engine_JniLibrary_callAddTestRootCertificateFromNative(
JNIEnv* env, jclass, jbyteArray jcert) {
std::vector<uint8_t> cert;
JavaArrayOfByteToBytesVector(env, jcert, &cert);
jvm_add_test_root_certificate(cert.data(), cert.size());
}

extern "C" JNIEXPORT void JNICALL
Java_io_envoyproxy_envoymobile_engine_JniLibrary_callClearTestRootCertificateFromNative(JNIEnv*,
jclass) {
jvm_clear_test_root_certificate();
}
60 changes: 60 additions & 0 deletions library/common/jni/jni_utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <stdlib.h>
#include <string.h>

#include <codecvt>

#include "source/common/common/assert.h"

#include "library/common/jni/jni_support.h"
Expand Down Expand Up @@ -255,3 +257,61 @@ envoy_map to_native_map(JNIEnv* env, jobjectArray entries) {
envoy_map native_map = {length / 2, entry_array};
return native_map;
}

jstring ConvertUTF8ToJavaString(JNIEnv* env, const std::string& str) {
std::u16string utf16 =
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(str);
const jchar* jutf16 = reinterpret_cast<const jchar*>(utf16.data());
return env->NewString(jutf16, utf16.length());
}

jobjectArray ToJavaArrayOfByteArray(JNIEnv* env, const std::vector<std::string>& v) {
jclass jcls_byte_array = env->FindClass("[B");
jobjectArray joa = env->NewObjectArray(v.size(), jcls_byte_array, nullptr);

for (size_t i = 0; i < v.size(); ++i) {
jbyteArray byte_array =
ToJavaByteArray(env, reinterpret_cast<const uint8_t*>(v[i].data()), v[i].length());
env->SetObjectArrayElement(joa, i, byte_array);
}
return joa;
}

jbyteArray ToJavaByteArray(JNIEnv* env, const uint8_t* bytes, size_t len) {
jbyteArray byte_array = env->NewByteArray(len);

env->SetByteArrayRegion(byte_array, 0, len, reinterpret_cast<const jbyte*>(bytes));

return byte_array;
}

void JavaArrayOfByteArrayToStringVector(JNIEnv* env, jobjectArray array,
std::vector<std::string>* out) {
size_t len = env->GetArrayLength(array);
out->resize(len);
for (size_t i = 0; i < len; ++i) {
jbyteArray bytes_array = static_cast<jbyteArray>(env->GetObjectArrayElement(array, i));
jsize bytes_len = env->GetArrayLength(bytes_array);
jbyte* bytes = env->GetByteArrayElements(bytes_array, nullptr);
(*out)[i].assign(reinterpret_cast<const char*>(bytes), bytes_len);
env->ReleaseByteArrayElements(bytes_array, bytes, JNI_ABORT);
}
}

void JavaArrayOfByteToBytesVector(JNIEnv* env, jbyteArray array, std::vector<uint8_t>* out) {
const size_t len = env->GetArrayLength(array);
out->resize(len);
jbyte* jbytes = env->GetByteArrayElements(array, nullptr);
uint8_t* bytes = reinterpret_cast<uint8_t*>(jbytes);
std::copy(bytes, bytes + len, out->begin());
env->ReleaseByteArrayElements(array, jbytes, JNI_ABORT);
}

void ConvertJavaStringToUTF8(JNIEnv* env, jstring str, std::string* result) {
const jsize length = env->GetStringLength(str);
const jchar* chars = env->GetStringChars(str, NULL);
std::string utf8 = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.to_bytes(
reinterpret_cast<const char16_t*>(chars));
*result = utf8;
env->ReleaseStringChars(str, chars);
}
StefanoDuo marked this conversation as resolved.
Show resolved Hide resolved
19 changes: 19 additions & 0 deletions library/common/jni/jni_utility.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#pragma once

#include <string>
#include <vector>

#include "library/common/jni/import/jni_import.h"
#include "library/common/types/c_types.h"

Expand Down Expand Up @@ -63,3 +66,19 @@ envoy_headers* to_native_headers_ptr(JNIEnv* env, jobjectArray headers);
envoy_stats_tags to_native_tags(JNIEnv* env, jobjectArray tags);

envoy_map to_native_map(JNIEnv* env, jobjectArray entries);

/**
* Utilities to translate C++ std library constructs to their Java counterpart.
StefanoDuo marked this conversation as resolved.
Show resolved Hide resolved
*/
jstring ConvertUTF8ToJavaString(JNIEnv* env, const std::string& str);

jobjectArray ToJavaArrayOfByteArray(JNIEnv* env, const std::vector<std::string>& v);

jbyteArray ToJavaByteArray(JNIEnv* env, const uint8_t* bytes, size_t len);

void JavaArrayOfByteArrayToStringVector(JNIEnv* env, jobjectArray array,
std::vector<std::string>* out);

void JavaArrayOfByteToBytesVector(JNIEnv* env, jbyteArray array, std::vector<uint8_t>* out);

void ConvertJavaStringToUTF8(JNIEnv* env, jstring str, std::string* result);
24 changes: 24 additions & 0 deletions library/common/types/c_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -488,3 +488,27 @@ typedef struct {
// Context passed through to callbacks to provide dispatch and execution state.
const void* context;
} envoy_event_tracker;

/**
* The list of certificate verification results returned from Java side to the
* C++ side.
* A Java counterpart lives in org.chromium.net.CertVerifyStatusAndroid.java
*/
typedef enum {
// Certificate is trusted.
CERT_VERIFY_STATUS_ANDROID_OK = 0,
// Certificate verification could not be conducted.
CERT_VERIFY_STATUS_ANDROID_FAILED = -1,
// Certificate is not trusted due to non-trusted root of the certificate
// chain.
CERT_VERIFY_STATUS_ANDROID_NO_TRUSTED_ROOT = -2,
// Certificate is not trusted because it has expired.
CERT_VERIFY_STATUS_ANDROID_EXPIRED = -3,
// Certificate is not trusted because it is not valid yet.
CERT_VERIFY_STATUS_ANDROID_NOT_YET_VALID = -4,
// Certificate is not trusted because it could not be parsed.
CERT_VERIFY_STATUS_ANDROID_UNABLE_TO_PARSE = -5,
// Certificate is not trusted because it has an extendedKeyUsage field, but
// its value is not correct for a web server.
CERT_VERIFY_STATUS_ANDROID_INCORRECT_KEY_USAGE = -6,
} envoy_cert_verify_status_t;
StefanoDuo marked this conversation as resolved.
Show resolved Hide resolved
26 changes: 26 additions & 0 deletions library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -331,4 +331,30 @@ protected static native int registerStringAccessor(String accessorName,
* @return The resulting status of the operation.
*/
protected static native int setPreferredNetwork(long engine, int network);

/**
* Mimic a call to AndroidNetworkLibrary#verifyServerCertificates from native code.
* To be used for testing only.
*
* @param certChain The ASN.1 DER encoded bytes for certificates.
* @param authType The key exchange algorithm name (e.g. RSA).
* @param host The hostname of the server.
* @return Android certificate verification result code.
*/
public static native Object callCertificateVerificationFromNative(byte[][] certChain,
String authType, String host);
/**
* Mimic a call to AndroidNetworkLibrary#addTestRootCertificate from native code.
* To be used for testing only.
*
* @param rootCert DER encoded bytes of the certificate.
*/
public static native void callAddTestRootCertificateFromNative(byte[] cert);

/**
* Mimic a call to AndroidNetworkLibrary#clearTestRootCertificate from native code.
* To be used for testing only.
*
*/
public static native void callClearTestRootCertificateFromNative();
}
Loading