Skip to content

Commit 9bc05fb

Browse files
committed
Implementing server certificate verification on Android.
1 parent 3f3984e commit 9bc05fb

File tree

3 files changed

+340
-8
lines changed

3 files changed

+340
-8
lines changed

Release/src/http/client/http_linux.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ namespace web { namespace http
453453

454454
private:
455455
tcp::resolver m_resolver;
456-
#if defined(__APPLE__)
456+
#if defined(__APPLE__) || defined(ANDROID)
457457
bool m_openssl_failed;
458458
#endif
459459

@@ -490,7 +490,7 @@ namespace web { namespace http
490490
{
491491
ctx->m_ssl_stream->set_verify_mode(boost::asio::ssl::context::verify_peer);
492492
ctx->m_ssl_stream->set_verify_callback(boost::bind(&linux_client::handle_cert_verification, shared_from_this(), _1, _2));
493-
#if defined(__APPLE__)
493+
#if defined(__APPLE__) || defined(ANDROID)
494494
m_openssl_failed = false;
495495
#endif
496496
}
@@ -544,7 +544,7 @@ namespace web { namespace http
544544
{
545545
ctx->m_ssl_stream->set_verify_mode(boost::asio::ssl::context::verify_peer);
546546
ctx->m_ssl_stream->set_verify_callback(boost::bind(&linux_client::handle_cert_verification, shared_from_this(), _1, _2));
547-
#if defined(__APPLE__)
547+
#if defined(__APPLE__) || defined(ANDROID)
548548
m_openssl_failed = false;
549549
#endif
550550
}
@@ -566,7 +566,7 @@ namespace web { namespace http
566566
// certificate chain, the rest are optional intermediate certificates, followed
567567
// finally by the root CA self signed certificate.
568568

569-
#if defined(__APPLE__)
569+
#if defined(__APPLE__) || defined(ANDROID)
570570
if(!preverified)
571571
{
572572
m_openssl_failed = true;

Release/src/http/client/x509_cert_utilities.cpp

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
2626
****/
2727

28+
#include "stdafx.h"
2829
#include "cpprest/x509_cert_utilities.h"
2930

3031
#include <vector>
@@ -38,6 +39,12 @@
3839
#include <Security/SecTrust.h>
3940
#endif
4041

42+
#if defined(ANDROID)
43+
#include <jni.h>
44+
#endif
45+
46+
using namespace crossplat;
47+
4148
namespace web { namespace http { namespace client { namespace details {
4249

4350
#if defined(__APPLE__)
@@ -131,4 +138,329 @@ bool verify_X509_cert_chain(const std::vector<std::string> &certChain, const std
131138
}
132139
#endif
133140

141+
#if defined(ANDROID)
142+
143+
#include <android/log.h>
144+
145+
void android_print(const std::string &s)
146+
{
147+
//__android_log_print(ANDROID_LOG_WARN, "UnitTestpp", "%s", s.c_str());
148+
}
149+
150+
void printerror(JNIEnv *env)
151+
{
152+
jthrowable thr = env->ExceptionOccurred();
153+
if(thr != nullptr)
154+
{
155+
env->ExceptionClear();
156+
157+
jclass throwable_class = env->FindClass("java/lang/Throwable");
158+
jmethodID getMsg = env->GetMethodID(throwable_class,
159+
"getMessage",
160+
"()Ljava/lang/String;");
161+
162+
jstring str = static_cast<jstring>(env->CallObjectMethod(thr, getMsg));
163+
const char *charStr = env->GetStringUTFChars(str, 0);
164+
android_print(charStr);
165+
}
166+
}
167+
168+
/// <summary>
169+
/// Helper function to check return value and see if any exceptions
170+
/// occurred when calling a JNI function.
171+
/// <summary>
172+
/// <returns><c>true</c> if JNI call <c>failed</c>, false othewise.</returns>
173+
bool jni_failed(JNIEnv *env)
174+
{
175+
if(env->ExceptionOccurred())
176+
{
177+
// Clear exception otherwise no other JNI functions can be called.
178+
// In the future if we improve error reporting the exception message
179+
// can be retrieved from here.
180+
env->ExceptionClear();
181+
//printerror(env); // TODO
182+
return true;
183+
}
184+
return false;
185+
}
186+
template <typename T>
187+
bool jni_failed(JNIEnv *env, const T &result)
188+
{
189+
if(jni_failed(env))
190+
{
191+
return true;
192+
}
193+
else if(result == nullptr)
194+
{
195+
return true;
196+
}
197+
return false;
198+
}
199+
200+
bool verify_X509_cert_chain(const std::vector<std::string> &certChain, const std::string &hostName)
201+
{
202+
JNIEnv* env = get_jvm_env();
203+
204+
// Possible performance improvement:
205+
// In the future we could gain performance by turning all the jclass local
206+
// references into global references. Then we could lazy initialize and
207+
// save them globally. If this is done I'm not exactly sure where the release
208+
// should be.
209+
210+
// ByteArrayInputStream
211+
java_local_ref<jclass> byteArrayInputStreamClass(env->FindClass("java/io/ByteArrayInputStream"));
212+
if(jni_failed(env, byteArrayInputStreamClass))
213+
{
214+
return false;
215+
}
216+
jmethodID byteArrayInputStreamConstructorMethod = env->GetMethodID(
217+
byteArrayInputStreamClass.get(),
218+
"<init>",
219+
"([B)V");
220+
if(jni_failed(env, byteArrayInputStreamConstructorMethod))
221+
{
222+
return false;
223+
}
224+
225+
// CertificateFactory
226+
java_local_ref<jclass> certificateFactoryClass(env->FindClass("java/security/cert/CertificateFactory"));
227+
if(jni_failed(env, certificateFactoryClass))
228+
{
229+
return false;
230+
}
231+
jmethodID certificateFactoryGetInstanceMethod = env->GetStaticMethodID(
232+
certificateFactoryClass.get(),
233+
"getInstance",
234+
"(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
235+
if(jni_failed(env, certificateFactoryGetInstanceMethod))
236+
{
237+
return false;
238+
}
239+
jmethodID generateCertificateMethod = env->GetMethodID(
240+
certificateFactoryClass.get(),
241+
"generateCertificate",
242+
"(Ljava/io/InputStream;)Ljava/security/cert/Certificate;");
243+
if(jni_failed(env, generateCertificateMethod))
244+
{
245+
return false;
246+
}
247+
248+
// X509Certificate
249+
java_local_ref<jclass> X509CertificateClass(env->FindClass("java/security/cert/X509Certificate"));
250+
if(jni_failed(env, X509CertificateClass))
251+
{
252+
return false;
253+
}
254+
255+
// TrustManagerFactory
256+
java_local_ref<jclass> trustManagerFactoryClass(env->FindClass("javax/net/ssl/TrustManagerFactory"));
257+
if(jni_failed(env, trustManagerFactoryClass))
258+
{
259+
return false;
260+
}
261+
jmethodID trustManagerFactoryGetInstanceMethod = env->GetStaticMethodID(
262+
trustManagerFactoryClass.get(),
263+
"getInstance",
264+
"(Ljava/lang/String;)Ljavax/net/ssl/TrustManagerFactory;");
265+
if(jni_failed(env, trustManagerFactoryGetInstanceMethod))
266+
{
267+
return false;
268+
}
269+
jmethodID trustManagerFactoryInitMethod = env->GetMethodID(
270+
trustManagerFactoryClass.get(),
271+
"init",
272+
"(Ljava/security/KeyStore;)V");
273+
if(jni_failed(env, trustManagerFactoryInitMethod))
274+
{
275+
return false;
276+
}
277+
jmethodID trustManagerFactoryGetTrustManagersMethod = env->GetMethodID(
278+
trustManagerFactoryClass.get(),
279+
"getTrustManagers",
280+
"()[Ljavax/net/ssl/TrustManager;");
281+
if(jni_failed(env, trustManagerFactoryGetTrustManagersMethod))
282+
{
283+
return false;
284+
}
285+
286+
// X509TrustManager
287+
java_local_ref<jclass> X509TrustManagerClass(env->FindClass("javax/net/ssl/X509TrustManager"));
288+
if(jni_failed(env, X509TrustManagerClass))
289+
{
290+
return false;
291+
}
292+
jmethodID X509TrustManagerCheckServerTrustedMethod = env->GetMethodID(
293+
X509TrustManagerClass.get(),
294+
"checkServerTrusted",
295+
"([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V");
296+
if(jni_failed(env, X509TrustManagerCheckServerTrustedMethod))
297+
{
298+
return false;
299+
}
300+
301+
// StrictHostnameVerifier
302+
java_local_ref<jclass> strictHostnameVerifierClass(env->FindClass("org/apache/http/conn/ssl/StrictHostnameVerifier"));
303+
if(jni_failed(env, strictHostnameVerifierClass))
304+
{
305+
return false;
306+
}
307+
jmethodID strictHostnameVerifierConstructorMethod = env->GetMethodID(strictHostnameVerifierClass.get(), "<init>", "()V");
308+
if(jni_failed(env, strictHostnameVerifierConstructorMethod))
309+
{
310+
return false;
311+
}
312+
jmethodID strictHostnameVerifierVerifyMethod = env->GetMethodID(
313+
strictHostnameVerifierClass.get(),
314+
"verify",
315+
"(Ljava/lang/String;Ljava/security/cert/X509Certificate;)V");
316+
if(jni_failed(env, strictHostnameVerifierVerifyMethod))
317+
{
318+
return false;
319+
}
320+
321+
// Create CertificateFactory
322+
java_local_ref<jstring> XDot509String(env->NewStringUTF("X.509"));
323+
if(jni_failed(env, XDot509String))
324+
{
325+
return false;
326+
}
327+
java_local_ref<jobject> certificateFactory(env->CallStaticObjectMethod(
328+
certificateFactoryClass.get(),
329+
certificateFactoryGetInstanceMethod,
330+
XDot509String.get()));
331+
if(jni_failed(env, certificateFactory))
332+
{
333+
return false;
334+
}
335+
336+
// Create Java array to store all the certs in.
337+
java_local_ref<jobjectArray> certsArray(env->NewObjectArray(certChain.size(), X509CertificateClass.get(), nullptr));
338+
if(jni_failed(env, certsArray))
339+
{
340+
return false;
341+
}
342+
343+
// For each certificate perform the following steps:
344+
// 1. Create ByteArrayInputStream backed by DER certificate bytes
345+
// 2. Create Certificate using CertificateFactory.generateCertificate
346+
// 3. Add Certificate to array
347+
int i = 0;
348+
for(const auto &certData : certChain)
349+
{
350+
java_local_ref<jbyteArray> byteArray(env->NewByteArray(certData.size()));
351+
if(jni_failed(env, byteArray))
352+
{
353+
return false;
354+
}
355+
env->SetByteArrayRegion(byteArray.get(), 0, certData.size(), reinterpret_cast<const jbyte *>(certData.c_str()));
356+
if(jni_failed(env))
357+
{
358+
return false;
359+
}
360+
java_local_ref<jobject> byteArrayInputStream(env->NewObject(
361+
byteArrayInputStreamClass.get(),
362+
byteArrayInputStreamConstructorMethod,
363+
byteArray.get()));
364+
if(jni_failed(env, byteArrayInputStream))
365+
{
366+
return false;
367+
}
368+
369+
java_local_ref<jobject> cert(env->CallObjectMethod(
370+
certificateFactory.get(),
371+
generateCertificateMethod,
372+
byteArrayInputStream.get()));
373+
if(jni_failed(env, cert))
374+
{
375+
return false;
376+
}
377+
378+
env->SetObjectArrayElement(certsArray.get(), i, cert.get());
379+
if(jni_failed(env))
380+
{
381+
return false;
382+
}
383+
++i;
384+
}
385+
386+
// Create TrustManagerFactory, init with Android system certs
387+
java_local_ref<jstring> X509String(env->NewStringUTF("X509"));
388+
if(jni_failed(env, X509String))
389+
{
390+
return false;
391+
}
392+
java_local_ref<jobject> trustFactoryManager(env->CallStaticObjectMethod(
393+
trustManagerFactoryClass.get(),
394+
trustManagerFactoryGetInstanceMethod,
395+
X509String.get()));
396+
if(jni_failed(env, trustFactoryManager))
397+
{
398+
return false;
399+
}
400+
env->CallVoidMethod(trustFactoryManager.get(), trustManagerFactoryInitMethod, nullptr);
401+
if(jni_failed(env))
402+
{
403+
return false;
404+
}
405+
406+
// Get TrustManager
407+
java_local_ref<jobjectArray> trustManagerArray(static_cast<jobjectArray>(
408+
env->CallObjectMethod(trustFactoryManager.get(), trustManagerFactoryGetTrustManagersMethod)));
409+
if(jni_failed(env, trustManagerArray))
410+
{
411+
return false;
412+
}
413+
java_local_ref<jobject> trustManager(env->GetObjectArrayElement(trustManagerArray.get(), 0));
414+
if(jni_failed(env, trustManager))
415+
{
416+
return false;
417+
}
418+
419+
// Validate certificate chain.
420+
java_local_ref<jstring> RSAString(env->NewStringUTF("RSA"));
421+
if(jni_failed(env, RSAString))
422+
{
423+
return false;
424+
}
425+
env->CallVoidMethod(
426+
trustManager.get(),
427+
X509TrustManagerCheckServerTrustedMethod,
428+
certsArray.get(),
429+
RSAString.get());
430+
if(jni_failed(env))
431+
{
432+
return false;
433+
}
434+
435+
// Verify hostname on certificate according to RFC 2818.
436+
java_local_ref<jobject> hostnameVerifier(env->NewObject(
437+
strictHostnameVerifierClass.get(), strictHostnameVerifierConstructorMethod));
438+
if(jni_failed(env, hostnameVerifier))
439+
{
440+
return false;
441+
}
442+
java_local_ref<jstring> hostNameString(env->NewStringUTF(hostName.c_str()));
443+
if(jni_failed(env, hostNameString))
444+
{
445+
return false;
446+
}
447+
java_local_ref<jobject> cert(env->GetObjectArrayElement(certsArray.get(), 0));
448+
if(jni_failed(env, cert))
449+
{
450+
return false;
451+
}
452+
env->CallVoidMethod(
453+
hostnameVerifier.get(),
454+
strictHostnameVerifierVerifyMethod,
455+
hostNameString.get(),
456+
cert.get());
457+
if(jni_failed(env))
458+
{
459+
return false;
460+
}
461+
462+
return true;
463+
}
464+
#endif
465+
134466
}}}}

0 commit comments

Comments
 (0)