Initial Android SSLStream impl (#47690)
authorEgor Bogatov <egorbo@gmail.com>
Fri, 12 Feb 2021 16:56:02 +0000 (19:56 +0300)
committerGitHub <noreply@github.com>
Fri, 12 Feb 2021 16:56:02 +0000 (19:56 +0300)
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/CMakeLists.txt
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_jni.c
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_jni.h
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_sslstream.c [new file with mode: 0644]
src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_sslstream.h [new file with mode: 0644]

index 3b6482a..e18769f 100644 (file)
@@ -5,6 +5,10 @@
 
 JavaVM* gJvm;
 
+// java/lang/Enum
+jclass    g_Enum;
+jmethodID g_EnumOrdinal;
+
 // java/security/SecureRandom
 jclass    g_randClass;
 jmethodID g_randCtor;
@@ -114,10 +118,53 @@ jmethodID g_X509EncodedKeySpecCtor;
 // com/android/org/conscrypt/NativeCrypto
 jclass    g_NativeCryptoClass;
 
+// javax/net/ssl/SSLEngine
+jclass    g_SSLEngine;
+jmethodID g_SSLEngineSetUseClientModeMethod;
+jmethodID g_SSLEngineGetSessionMethod;
+jmethodID g_SSLEngineBeginHandshakeMethod;
+jmethodID g_SSLEngineWrapMethod;
+jmethodID g_SSLEngineUnwrapMethod;
+jmethodID g_SSLEngineCloseInboundMethod;
+jmethodID g_SSLEngineCloseOutboundMethod;
+jmethodID g_SSLEngineGetHandshakeStatusMethod;
+
+// java/nio/ByteBuffer
+jclass    g_ByteBuffer;
+jmethodID g_ByteBufferAllocateMethod;
+jmethodID g_ByteBufferPutMethod;
+jmethodID g_ByteBufferPut2Method;
+jmethodID g_ByteBufferPut3Method;
+jmethodID g_ByteBufferFlipMethod;
+jmethodID g_ByteBufferGetMethod;
+jmethodID g_ByteBufferPutBufferMethod;
+jmethodID g_ByteBufferLimitMethod;
+jmethodID g_ByteBufferRemainingMethod;
+jmethodID g_ByteBufferCompactMethod;
+jmethodID g_ByteBufferPositionMethod;
+
+// javax/net/ssl/SSLContext
+jclass    g_SSLContext;
+jmethodID g_SSLContextGetInstanceMethod;
+jmethodID g_SSLContextInitMethod;
+jmethodID g_SSLContextCreateSSLEngineMethod;
+
+// javax/net/ssl/SSLSession
+jclass    g_SSLSession;
+jmethodID g_SSLSessionGetApplicationBufferSizeMethod;
+jmethodID g_SSLSessionGetPacketBufferSizeMethod;
+
+// javax/net/ssl/SSLEngineResult
+jclass    g_SSLEngineResult;
+jmethodID g_SSLEngineResultGetStatusMethod;
+jmethodID g_SSLEngineResultGetHandshakeStatusMethod;
+
+// javax/net/ssl/TrustManager
+jclass    g_TrustManager;
+
 jobject ToGRef(JNIEnv *env, jobject lref)
 {
-    if (!lref)
-        return NULL;
+    assert(lref && "object shouldn't be null");
     jobject gref = (*env)->NewGlobalRef(env, lref);
     (*env)->DeleteLocalRef(env, lref);
     return gref;
@@ -151,13 +198,18 @@ bool CheckJNIExceptions(JNIEnv* env)
 {
     if ((*env)->ExceptionCheck(env))
     {
-        (*env)->ExceptionDescribe(env); 
+        (*env)->ExceptionDescribe(env);
         (*env)->ExceptionClear(env);
         return true;
     }
     return false;
 }
 
+void AssertOnJNIExceptions(JNIEnv* env)
+{
+    assert(!CheckJNIExceptions(env));
+}
+
 void SaveTo(uint8_t* src, uint8_t** dst, size_t len)
 {
     assert(!(*dst));
@@ -199,7 +251,14 @@ JNIEnv* GetJNIEnv()
     return env;
 }
 
-PALEXPORT JNIEXPORT jint JNICALL
+int GetEnumAsInt(JNIEnv *env, jobject enumObj)
+{
+    int value = (*env)->CallIntMethod(env, enumObj, g_EnumOrdinal);
+    (*env)->DeleteLocalRef(env, enumObj);
+    return value;
+}
+
+JNIEXPORT jint JNICALL
 JNI_OnLoad(JavaVM *vm, void *reserved)
 {
     (void)reserved;
@@ -209,6 +268,10 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
     JNIEnv* env = GetJNIEnv();
 
     // cache some classes and methods while we're in the thread-safe JNI_OnLoad
+
+    g_Enum =                    GetClassGRef(env, "java/lang/Enum");
+    g_EnumOrdinal =             GetMethod(env, false, g_Enum, "ordinal", "()I");
+
     g_randClass =               GetClassGRef(env, "java/security/SecureRandom");
     g_randCtor =                GetMethod(env, false, g_randClass, "<init>", "()V");
     g_randNextBytesMethod =     GetMethod(env, false, g_randClass, "nextBytes", "([B)V");
@@ -298,5 +361,43 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
 
     g_NativeCryptoClass =              GetClassGRef(env, "com/android/org/conscrypt/NativeCrypto");
 
+    g_SSLEngine =                         GetClassGRef(env, "javax/net/ssl/SSLEngine");
+    g_SSLEngineSetUseClientModeMethod =   GetMethod(env, false, g_SSLEngine, "setUseClientMode", "(Z)V");
+    g_SSLEngineGetSessionMethod =         GetMethod(env, false, g_SSLEngine, "getSession", "()Ljavax/net/ssl/SSLSession;");
+    g_SSLEngineBeginHandshakeMethod =     GetMethod(env, false, g_SSLEngine, "beginHandshake", "()V");
+    g_SSLEngineWrapMethod =               GetMethod(env, false, g_SSLEngine, "wrap", "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)Ljavax/net/ssl/SSLEngineResult;");
+    g_SSLEngineUnwrapMethod =             GetMethod(env, false, g_SSLEngine, "unwrap", "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)Ljavax/net/ssl/SSLEngineResult;");
+    g_SSLEngineGetHandshakeStatusMethod = GetMethod(env, false, g_SSLEngine, "getHandshakeStatus", "()Ljavax/net/ssl/SSLEngineResult$HandshakeStatus;");
+    g_SSLEngineCloseInboundMethod =       GetMethod(env, false, g_SSLEngine, "closeInbound", "()V");
+    g_SSLEngineCloseOutboundMethod =      GetMethod(env, false, g_SSLEngine, "closeOutbound", "()V");
+
+    g_ByteBuffer =                        GetClassGRef(env, "java/nio/ByteBuffer");
+    g_ByteBufferAllocateMethod =          GetMethod(env, true,  g_ByteBuffer, "allocate", "(I)Ljava/nio/ByteBuffer;");
+    g_ByteBufferPutMethod =               GetMethod(env, false, g_ByteBuffer, "put", "(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;");
+    g_ByteBufferPut2Method =              GetMethod(env, false, g_ByteBuffer, "put", "([B)Ljava/nio/ByteBuffer;");
+    g_ByteBufferPut3Method =              GetMethod(env, false, g_ByteBuffer, "put", "([BII)Ljava/nio/ByteBuffer;");
+    g_ByteBufferFlipMethod =              GetMethod(env, false, g_ByteBuffer, "flip", "()Ljava/nio/Buffer;");
+    g_ByteBufferLimitMethod =             GetMethod(env, false, g_ByteBuffer, "limit", "()I");
+    g_ByteBufferGetMethod =               GetMethod(env, false, g_ByteBuffer, "get", "([B)Ljava/nio/ByteBuffer;");
+    g_ByteBufferPutBufferMethod =         GetMethod(env, false, g_ByteBuffer, "put", "(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;");
+    g_ByteBufferRemainingMethod =         GetMethod(env, false, g_ByteBuffer, "remaining", "()I");
+    g_ByteBufferCompactMethod =           GetMethod(env, false, g_ByteBuffer, "compact", "()Ljava/nio/ByteBuffer;");
+    g_ByteBufferPositionMethod =          GetMethod(env, false, g_ByteBuffer, "position", "()I");
+
+    g_SSLContext =                        GetClassGRef(env, "javax/net/ssl/SSLContext");
+    g_SSLContextGetInstanceMethod =       GetMethod(env, true,  g_SSLContext, "getInstance", "(Ljava/lang/String;)Ljavax/net/ssl/SSLContext;");
+    g_SSLContextInitMethod =              GetMethod(env, false, g_SSLContext, "init", "([Ljavax/net/ssl/KeyManager;[Ljavax/net/ssl/TrustManager;Ljava/security/SecureRandom;)V");
+    g_SSLContextCreateSSLEngineMethod =   GetMethod(env, false, g_SSLContext, "createSSLEngine", "()Ljavax/net/ssl/SSLEngine;");
+
+    g_SSLSession =                               GetClassGRef(env, "javax/net/ssl/SSLSession");
+    g_SSLSessionGetApplicationBufferSizeMethod = GetMethod(env, false, g_SSLSession, "getApplicationBufferSize", "()I");
+    g_SSLSessionGetPacketBufferSizeMethod =      GetMethod(env, false, g_SSLSession, "getPacketBufferSize", "()I");
+
+    g_SSLEngineResult =                          GetClassGRef(env, "javax/net/ssl/SSLEngineResult");
+    g_SSLEngineResultGetStatusMethod =           GetMethod(env, false, g_SSLEngineResult, "getStatus", "()Ljavax/net/ssl/SSLEngineResult$Status;");
+    g_SSLEngineResultGetHandshakeStatusMethod =  GetMethod(env, false, g_SSLEngineResult, "getHandshakeStatus", "()Ljavax/net/ssl/SSLEngineResult$HandshakeStatus;");
+
+    g_TrustManager =                             GetClassGRef(env, "javax/net/ssl/TrustManager");
+
     return JNI_VERSION_1_6;
 }
index bdf22ed..f540ae5 100644 (file)
 
 extern JavaVM* gJvm;
 
+// java/lang/Enum
+extern jclass    g_Enum;
+extern jmethodID g_EnumOrdinal;
+
 // java/security/SecureRandom
 extern jclass    g_randClass;
 extern jmethodID g_randCtor;
@@ -123,6 +127,50 @@ extern jmethodID g_X509EncodedKeySpecCtor;
 // com/android/org/conscrypt/NativeCrypto
 extern jclass    g_NativeCryptoClass;
 
+// javax/net/ssl/SSLEngine
+extern jclass    g_SSLEngine;
+extern jmethodID g_SSLEngineSetUseClientModeMethod;
+extern jmethodID g_SSLEngineGetSessionMethod;
+extern jmethodID g_SSLEngineBeginHandshakeMethod;
+extern jmethodID g_SSLEngineWrapMethod;
+extern jmethodID g_SSLEngineUnwrapMethod;
+extern jmethodID g_SSLEngineCloseInboundMethod;
+extern jmethodID g_SSLEngineCloseOutboundMethod;
+extern jmethodID g_SSLEngineGetHandshakeStatusMethod;
+
+// java/nio/ByteBuffer
+extern jclass    g_ByteBuffer;
+extern jmethodID g_ByteBufferAllocateMethod;
+extern jmethodID g_ByteBufferPutMethod;
+extern jmethodID g_ByteBufferPut2Method;
+extern jmethodID g_ByteBufferPut3Method;
+extern jmethodID g_ByteBufferFlipMethod;
+extern jmethodID g_ByteBufferGetMethod;
+extern jmethodID g_ByteBufferLimitMethod;
+extern jmethodID g_ByteBufferRemainingMethod;
+extern jmethodID g_ByteBufferPutBufferMethod;
+extern jmethodID g_ByteBufferCompactMethod;
+extern jmethodID g_ByteBufferPositionMethod;
+
+// javax/net/ssl/SSLContext
+extern jclass    g_SSLContext;
+extern jmethodID g_SSLContextGetInstanceMethod;
+extern jmethodID g_SSLContextInitMethod;
+extern jmethodID g_SSLContextCreateSSLEngineMethod;
+
+// javax/net/ssl/SSLSession
+extern jclass    g_SSLSession;
+extern jmethodID g_SSLSessionGetApplicationBufferSizeMethod;
+extern jmethodID g_SSLSessionGetPacketBufferSizeMethod;
+
+// javax/net/ssl/SSLEngineResult
+extern jclass    g_SSLEngineResult;
+extern jmethodID g_SSLEngineResultGetStatusMethod;
+extern jmethodID g_SSLEngineResultGetHandshakeStatusMethod;
+
+// javax/net/ssl/TrustManager
+extern jclass    g_TrustManager;
+
 // JNI helpers
 #define LOG_DEBUG(fmt, ...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "DOTNET", "%s: " fmt, __FUNCTION__, ## __VA_ARGS__))
 #define LOG_INFO(fmt, ...) ((void)__android_log_print(ANDROID_LOG_INFO, "DOTNET", "%s: " fmt, __FUNCTION__, ## __VA_ARGS__))
@@ -135,6 +183,8 @@ jobject AddGRef(JNIEnv *env, jobject gref);
 void ReleaseGRef(JNIEnv *env, jobject gref);
 jclass GetClassGRef(JNIEnv *env, const char* name);
 bool CheckJNIExceptions(JNIEnv* env);
+void AssertOnJNIExceptions(JNIEnv* env);
 jmethodID GetMethod(JNIEnv *env, bool isStatic, jclass klass, const char* name, const char* sig);
 jfieldID GetField(JNIEnv *env, bool isStatic, jclass klass, const char* name, const char* sig);
 JNIEnv* GetJNIEnv(void);
+int GetEnumAsInt(JNIEnv *env, jobject enumObj);
diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_sslstream.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_sslstream.c
new file mode 100644 (file)
index 0000000..a57ff6d
--- /dev/null
@@ -0,0 +1,409 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include "pal_sslstream.h"
+
+void checkHandshakeStatus(JNIEnv* env, SSLStream* sslStream, int handshakeStatus);
+
+int getHandshakeStatus(JNIEnv* env, SSLStream* sslStream, jobject engineResult)
+{
+    AssertOnJNIExceptions(env);
+    int status = -1;
+    if (engineResult)
+        status = GetEnumAsInt(env, (*env)->CallObjectMethod(env, engineResult, g_SSLEngineResultGetHandshakeStatusMethod));
+    else
+        status = GetEnumAsInt(env, (*env)->CallObjectMethod(env, sslStream->sslEngine, g_SSLEngineGetHandshakeStatusMethod));
+    AssertOnJNIExceptions(env);
+    return status;
+}
+
+void close(JNIEnv* env, SSLStream* sslStream) {
+    /*
+        sslEngine.closeOutbound();
+        checkHandshakeStatus();
+    */
+
+    AssertOnJNIExceptions(env);
+    (*env)->CallVoidMethod(env, sslStream->sslEngine, g_SSLEngineCloseOutboundMethod);
+    checkHandshakeStatus(env, sslStream, getHandshakeStatus(env, sslStream, NULL));
+}
+
+void handleEndOfStream(JNIEnv* env, SSLStream* sslStream)  {
+    /*
+        sslEngine.closeInbound();
+        close();
+    */
+
+    AssertOnJNIExceptions(env);
+    (*env)->CallVoidMethod(env, sslStream->sslEngine, g_SSLEngineCloseInboundMethod);
+    close(env, sslStream);
+    AssertOnJNIExceptions(env);
+}
+
+void flush(JNIEnv* env, SSLStream* sslStream)
+{
+    /*
+        netOutBuffer.flip();
+        byte[] data = new byte[netOutBuffer.limit()];
+        netOutBuffer.get(data);
+        WriteToOutputStream(data, 0, data.length);
+        netOutBuffer.compact();
+    */
+
+    AssertOnJNIExceptions(env);
+
+    // DeleteLocalRef because we don't need the return value (Buffer)
+    (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->netOutBuffer, g_ByteBufferFlipMethod));
+    int bufferLimit = (*env)->CallIntMethod(env, sslStream->netOutBuffer, g_ByteBufferLimitMethod);
+    jbyteArray data = (*env)->NewByteArray(env, bufferLimit);
+
+    // DeleteLocalRef because we don't need the return value (Buffer)
+    (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->netOutBuffer, g_ByteBufferGetMethod, data));
+
+    uint8_t* dataPtr = malloc(bufferLimit);
+    (*env)->GetByteArrayRegion(env, data, 0, bufferLimit, (jbyte*) dataPtr);
+    sslStream->streamWriter(dataPtr, 0, bufferLimit);
+    free(dataPtr);
+    // DeleteLocalRef because we don't need the return value (Buffer)
+    (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->netOutBuffer, g_ByteBufferCompactMethod));
+    AssertOnJNIExceptions(env);
+}
+
+jobject ensureRemaining(JNIEnv* env, SSLStream* sslStream, jobject oldBuffer, int newRemaining)
+{
+    /*
+        if (oldBuffer.remaining() < newRemaining) {
+            oldBuffer.flip();
+            final ByteBuffer newBuffer = ByteBuffer.allocate(oldBuffer.remaining() + newRemaining);
+            newBuffer.put(oldBuffer);
+            return newBuffer;
+        } else {
+            return oldBuffer;
+        }
+    */
+
+    AssertOnJNIExceptions(env);
+
+    int oldRemaining = (*env)->CallIntMethod(env, oldBuffer, g_ByteBufferRemainingMethod);
+    if (oldRemaining < newRemaining)
+    {
+        // DeleteLocalRef because we don't need the return value (Buffer)
+        (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, oldBuffer, g_ByteBufferFlipMethod));
+        jobject newBuffer = ToGRef(env, (*env)->CallStaticObjectMethod(env, g_ByteBuffer, g_ByteBufferAllocateMethod, oldRemaining + newRemaining));
+        // DeleteLocalRef because we don't need the return value (Buffer)
+        (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, newBuffer, g_ByteBufferPutBufferMethod, oldBuffer));
+        ReleaseGRef(env, oldBuffer);
+        return newBuffer;
+    }
+    else
+    {
+        return oldBuffer;
+    }
+}
+
+void doWrap(JNIEnv* env, SSLStream* sslStream)
+{
+    /*
+        appOutBuffer.flip();
+        final SSLEngineResult result;
+        try {
+            result = sslEngine.wrap(appOutBuffer, netOutBuffer);
+        } catch (SSLException e) {
+            return;
+        }
+        appOutBuffer.compact();
+
+        final SSLEngineResult.Status status = result.getStatus();
+        switch (status) {
+            case OK:
+                flush();
+                checkHandshakeStatus(result.getHandshakeStatus());
+                if (appOutBuffer.position() > 0) doWrap();
+                break;
+            case CLOSED:
+                flush();
+                checkHandshakeStatus(result.getHandshakeStatus());
+                close();
+                break;
+            case BUFFER_OVERFLOW:
+                netOutBuffer = ensureRemaining(netOutBuffer, sslEngine.getSession().getPacketBufferSize());
+                doWrap();
+                break;
+        }
+    */
+
+    AssertOnJNIExceptions(env);
+
+    (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->appOutBuffer, g_ByteBufferFlipMethod));
+    jobject sslEngineResult = (*env)->CallObjectMethod(env, sslStream->sslEngine, g_SSLEngineWrapMethod, sslStream->appOutBuffer, sslStream->netOutBuffer);
+    (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->appOutBuffer, g_ByteBufferCompactMethod));
+
+    int status = GetEnumAsInt(env, (*env)->CallObjectMethod(env, sslEngineResult, g_SSLEngineResultGetStatusMethod));
+    switch (status)
+    {
+        case STATUS__OK:
+            flush(env, sslStream);
+            checkHandshakeStatus(env, sslStream, getHandshakeStatus(env, sslStream, sslEngineResult));
+            if ((*env)->CallIntMethod(env, sslStream->appOutBuffer, g_ByteBufferPositionMethod) > 0)
+                doWrap(env, sslStream);
+            break;
+        case STATUS__CLOSED:
+            flush(env, sslStream);
+            checkHandshakeStatus(env, sslStream, getHandshakeStatus(env, sslStream, sslEngineResult));
+            close(env, sslStream);
+            break;
+        case STATUS__BUFFER_OVERFLOW:
+            sslStream->netOutBuffer = ensureRemaining(env, sslStream, sslStream->netOutBuffer, (*env)->CallIntMethod(env, sslStream->sslSession, g_SSLSessionGetPacketBufferSizeMethod));
+            doWrap(env, sslStream);
+            break;
+    }
+}
+
+void doUnwrap(JNIEnv* env, SSLStream* sslStream)
+{
+    /*
+        if (netInBuffer.position() == 0)
+        {
+            byte[] tmp = new byte[netInBuffer.limit()];
+
+            int count = ReadFromInputStream(tmp, 0, tmp.length);
+            if (count == -1) {
+                handleEndOfStream();
+                return;
+            }
+            netInBuffer.put(tmp, 0, count);
+        }
+
+        netInBuffer.flip();
+        final SSLEngineResult result;
+        try {
+            result = sslEngine.unwrap(netInBuffer, appInBuffer);
+        } catch (SSLException e) {
+            return;
+        }
+        netInBuffer.compact();
+        final SSLEngineResult.Status status = result.getStatus();
+        switch (status) {
+            case OK:
+                checkHandshakeStatus(result.getHandshakeStatus());
+                break;
+            case CLOSED:
+                checkHandshakeStatus(result.getHandshakeStatus());
+                close();
+                break;
+            case BUFFER_UNDERFLOW:
+                netInBuffer = ensureRemaining(netInBuffer, sslEngine.getSession().getPacketBufferSize());
+                doUnwrap();
+                break;
+            case BUFFER_OVERFLOW:
+                appInBuffer = ensureRemaining(appInBuffer, sslEngine.getSession().getApplicationBufferSize());
+                doUnwrap();
+                break;
+        }
+    */
+
+    AssertOnJNIExceptions(env);
+
+    if ((*env)->CallIntMethod(env, sslStream->netInBuffer, g_ByteBufferPositionMethod) == 0)
+    {
+        int netInBufferLimit = (*env)->CallIntMethod(env, sslStream->netInBuffer, g_ByteBufferLimitMethod);
+        jbyteArray tmp = (*env)->NewByteArray(env, netInBufferLimit);
+        uint8_t* tmpNative = malloc(netInBufferLimit);
+        int count = sslStream->streamReader(tmpNative, 0, netInBufferLimit);
+        if (count == -1)
+        {
+            handleEndOfStream(env, sslStream);
+            return;
+        }
+        (*env)->SetByteArrayRegion(env, tmp, 0, count, (jbyte*)(tmpNative));
+        (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->netInBuffer, g_ByteBufferPut3Method, tmp, 0, count));
+        free(tmpNative);
+        (*env)->DeleteLocalRef(env, tmp);
+    }
+
+    (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->netInBuffer, g_ByteBufferFlipMethod));
+    jobject sslEngineResult = (*env)->CallObjectMethod(env, sslStream->sslEngine, g_SSLEngineUnwrapMethod, sslStream->netInBuffer, sslStream->appInBuffer);
+    (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->netInBuffer, g_ByteBufferCompactMethod));
+
+    int status = GetEnumAsInt(env, (*env)->CallObjectMethod(env, sslEngineResult, g_SSLEngineResultGetStatusMethod));
+    switch (status)
+    {
+        case STATUS__OK:
+            checkHandshakeStatus(env, sslStream, getHandshakeStatus(env, sslStream, sslEngineResult));
+            break;
+        case STATUS__CLOSED:
+            checkHandshakeStatus(env, sslStream, getHandshakeStatus(env, sslStream, sslEngineResult));
+            close(env, sslStream);
+            break;
+        case STATUS__BUFFER_UNDERFLOW:
+            sslStream->netInBuffer = ensureRemaining(env, sslStream, sslStream->netInBuffer, (*env)->CallIntMethod(env, sslStream->sslSession, g_SSLSessionGetPacketBufferSizeMethod));
+            doUnwrap(env, sslStream);
+            break;
+        case STATUS__BUFFER_OVERFLOW:
+            sslStream->appInBuffer = ensureRemaining(env, sslStream, sslStream->appInBuffer, (*env)->CallIntMethod(env, sslStream->sslSession, g_SSLSessionGetApplicationBufferSizeMethod));
+            doUnwrap(env, sslStream);
+            break;
+    }
+}
+
+void checkHandshakeStatus(JNIEnv* env, SSLStream* sslStream, int handshakeStatus)
+{
+    /*
+        switch (handshakeStatus) {
+            case NEED_WRAP:
+                doWrap();
+                break;
+            case NEED_UNWRAP:
+                doUnwrap();
+                break;
+            case NEED_TASK:
+                Runnable task;
+                while ((task = sslEngine.getDelegatedTask()) != null) task.run();
+                checkHandshakeStatus();
+                break;
+        }
+    */
+
+    AssertOnJNIExceptions(env);
+    switch (handshakeStatus)
+    {
+        case HANDSHAKE_STATUS__NEED_WRAP:
+            doWrap(env, sslStream);
+            break;
+        case HANDSHAKE_STATUS__NEED_UNWRAP:
+            doUnwrap(env, sslStream);
+            break;
+        case HANDSHAKE_STATUS__NEED_TASK:
+            assert(0 && "unexpected NEED_TASK handshake status");
+    }
+}
+
+SSLStream* AndroidCrypto_CreateSSLStreamAndStartHandshake(STREAM_READER streamReader, STREAM_WRITER streamWriter, int tlsVersion, int appOutBufferSize, int appInBufferSize)
+{
+    JNIEnv* env = GetJNIEnv();
+    /*
+        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
+        sslContext.init(null, new TrustManage r[]{trustAllCerts}, null);
+        this.sslEngine = sslContext.createSSLEngine();
+        this.sslEngine.setUseClientMode(true);
+        SSLSession sslSession = sslEngine.getSession();
+        final int applicationBufferSize = sslSession.getApplicationBufferSize();
+        final int packetBufferSize = sslSession.getPacketBufferSize();
+        this.appOutBuffer = ByteBuffer.allocate(appOutBufferSize);
+        this.netOutBuffer = ByteBuffer.allocate(packetBufferSize);
+        this.netInBuffer =  ByteBuffer.allocate(packetBufferSize);
+        this.appInBuffer =  ByteBuffer.allocate(Math.max(applicationBufferSize, appInBufferSize));
+        sslEngine.beginHandshake();
+    */
+
+    SSLStream* sslStream = malloc(sizeof(SSLStream));
+
+    jobject tlsVerStr = NULL;
+    if (tlsVersion == 11)
+        tlsVerStr = JSTRING("TLSv1.1");
+    else if (tlsVersion == 12)
+        tlsVerStr = JSTRING("TLSv1.2");
+    else if (tlsVersion == 13)
+        tlsVerStr = JSTRING("TLSv1.3");
+    else
+        assert(0 && "unknown tlsVersion");
+
+    sslStream->sslContext = ToGRef(env, (*env)->CallStaticObjectMethod(env, g_SSLContext, g_SSLContextGetInstanceMethod, tlsVerStr));
+
+    // TODO: set TrustManager[] argument to be able to intercept cert validation process (and callback to C#).
+    (*env)->CallVoidMethod(env, sslStream->sslContext, g_SSLContextInitMethod, NULL, NULL, NULL);
+    sslStream->sslEngine  = ToGRef(env, (*env)->CallObjectMethod(env, sslStream->sslContext, g_SSLContextCreateSSLEngineMethod));
+    sslStream->sslSession = ToGRef(env, (*env)->CallObjectMethod(env, sslStream->sslEngine, g_SSLEngineGetSessionMethod));
+
+    int applicationBufferSize = (*env)->CallIntMethod(env, sslStream->sslSession, g_SSLSessionGetApplicationBufferSizeMethod);
+    int packetBufferSize = (*env)->CallIntMethod(env, sslStream->sslSession, g_SSLSessionGetPacketBufferSizeMethod);
+
+    (*env)->CallVoidMethod(env, sslStream->sslEngine, g_SSLEngineSetUseClientModeMethod, true);
+
+    sslStream->appOutBuffer = ToGRef(env, (*env)->CallStaticObjectMethod(env, g_ByteBuffer, g_ByteBufferAllocateMethod, appOutBufferSize));
+    sslStream->netOutBuffer = ToGRef(env, (*env)->CallStaticObjectMethod(env, g_ByteBuffer, g_ByteBufferAllocateMethod, packetBufferSize));
+    sslStream->appInBuffer =  ToGRef(env, (*env)->CallStaticObjectMethod(env, g_ByteBuffer, g_ByteBufferAllocateMethod,
+            applicationBufferSize > appInBufferSize ? applicationBufferSize : appInBufferSize));
+    sslStream->netInBuffer =  ToGRef(env, (*env)->CallStaticObjectMethod(env, g_ByteBuffer, g_ByteBufferAllocateMethod, packetBufferSize));
+
+    sslStream->streamReader = streamReader;
+    sslStream->streamWriter = streamWriter;
+
+    (*env)->CallVoidMethod(env, sslStream->sslEngine, g_SSLEngineBeginHandshakeMethod);
+
+    checkHandshakeStatus(env, sslStream, getHandshakeStatus(env, sslStream, NULL));
+    (*env)->DeleteLocalRef(env, tlsVerStr);
+    AssertOnJNIExceptions(env);
+    return sslStream;
+}
+
+int AndroidCrypto_SSLStreamRead(SSLStream* sslStream, uint8_t* buffer, int offset, int length)
+{
+    JNIEnv* env = GetJNIEnv();
+
+    /*
+        while (true) {
+            appInBuffer.flip();
+            try {
+                if (appInBuffer.remaining() > 0) {
+                    byte[] data = new byte[appInBuffer.remaining()];
+                    appInBuffer.get(data);
+                    return data;
+                }
+            } finally {
+                appInBuffer.compact();
+            }
+            doUnwrap();
+        }
+    */
+
+    AssertOnJNIExceptions(env);
+    (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->appInBuffer, g_ByteBufferFlipMethod));
+    int rem = (*env)->CallIntMethod(env, sslStream->appInBuffer, g_ByteBufferRemainingMethod);
+    if (rem > 0)
+    {
+        jbyteArray data = (*env)->NewByteArray(env, rem);
+        (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->appInBuffer, g_ByteBufferGetMethod, data));
+        (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->appInBuffer, g_ByteBufferCompactMethod));
+        (*env)->GetByteArrayRegion(env, data, 0, rem, (jbyte*) buffer);
+        AssertOnJNIExceptions(env);
+        return rem;
+    }
+    else
+    {
+        (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->appInBuffer, g_ByteBufferCompactMethod));
+        doUnwrap(env, sslStream);
+        AssertOnJNIExceptions(env);
+        return AndroidCrypto_SSLStreamRead(sslStream, buffer, offset, length);
+    }
+}
+
+void AndroidCrypto_SSLStreamWrite(SSLStream* sslStream, uint8_t* buffer, int offset, int length)
+{
+    /*
+        appOutBuffer.put(message);
+        doWrap();
+    */
+
+    JNIEnv* env = GetJNIEnv();
+    jbyteArray data = (*env)->NewByteArray(env, length);
+    (*env)->SetByteArrayRegion(env, data, 0, length, (jbyte*)(buffer + offset));
+    (*env)->DeleteLocalRef(env, (*env)->CallObjectMethod(env, sslStream->appOutBuffer, g_ByteBufferPut2Method, data));
+    (*env)->DeleteLocalRef(env, data);
+    doWrap(env, sslStream);
+    AssertOnJNIExceptions(env);
+}
+
+void AndroidCrypto_Dispose(SSLStream* sslStream)
+{
+    JNIEnv* env = GetJNIEnv();
+    ReleaseGRef(env, sslStream->sslContext);
+    ReleaseGRef(env, sslStream->sslEngine);
+    ReleaseGRef(env, sslStream->sslSession);
+    ReleaseGRef(env, sslStream->appOutBuffer);
+    ReleaseGRef(env, sslStream->netOutBuffer);
+    ReleaseGRef(env, sslStream->netInBuffer);
+    ReleaseGRef(env, sslStream->appInBuffer);
+    free(sslStream);
+    AssertOnJNIExceptions(env);
+}
diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_sslstream.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_sslstream.h
new file mode 100644 (file)
index 0000000..8739e94
--- /dev/null
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma once
+
+#include "pal_jni.h"
+
+typedef void (*STREAM_WRITER)(uint8_t*, uint32_t, uint32_t);
+typedef int  (*STREAM_READER)(uint8_t*, uint32_t, uint32_t);
+
+typedef struct SSLStream
+{
+    jobject sslContext;
+    jobject sslEngine;
+    jobject sslSession;
+    jobject appOutBuffer;
+    jobject netOutBuffer;
+    jobject appInBuffer;
+    jobject netInBuffer;
+    STREAM_READER streamReader;
+    STREAM_WRITER streamWriter;
+} SSLStream;
+
+#define TLS11 11
+#define TLS12 12
+#define TLS13 13
+
+// javax/net/ssl/SSLEngineResult$HandshakeStatus
+#define HANDSHAKE_STATUS__NOT_HANDSHAKING 0
+#define HANDSHAKE_STATUS__FINISHED 1
+#define HANDSHAKE_STATUS__NEED_TASK 2
+#define HANDSHAKE_STATUS__NEED_WRAP 3
+#define HANDSHAKE_STATUS__NEED_UNWRAP 4
+
+// javax/net/ssl/SSLEngineResult$Status
+#define STATUS__BUFFER_UNDERFLOW 0
+#define STATUS__BUFFER_OVERFLOW 1
+#define STATUS__OK 2
+#define STATUS__CLOSED 3
+
+PALEXPORT SSLStream* AndroidCrypto_CreateSSLStreamAndStartHandshake(STREAM_READER streamReader, STREAM_WRITER streamWriter, int tlsVersion, int appOutBufferSize, int appInBufferSize);
+PALEXPORT int  AndroidCrypto_SSLStreamRead(SSLStream* sslStream, uint8_t* buffer, int offset, int length);
+PALEXPORT void AndroidCrypto_SSLStreamWrite(SSLStream* sslStream, uint8_t* buffer, int offset, int length);
+PALEXPORT void AndroidCrypto_Dispose(SSLStream* sslStream);