Skip to content

Commit

Permalink
Managed classes for AndroidCrypto SSL and X509Certificates (#48674)
Browse files Browse the repository at this point in the history
  • Loading branch information
elinor-fung authored Feb 26, 2021
1 parent 6a80a38 commit bb9a9d1
Show file tree
Hide file tree
Showing 31 changed files with 1,486 additions and 941 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class JObjectLifetime
{
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_NewGlobalReference")]
internal static extern IntPtr NewGlobalReference(IntPtr obj);

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_DeleteGlobalReference")]
internal static extern void DeleteGlobalReference(IntPtr obj);

internal class SafeJObjectHandle : SafeHandle
{
public SafeJObjectHandle()
: base(IntPtr.Zero, ownsHandle: true)
{
}

internal SafeJObjectHandle(IntPtr ptr)
: base(IntPtr.Zero, ownsHandle: true)
{
SetHandle(ptr);
}

protected override bool ReleaseHandle()
{
Interop.JObjectLifetime.DeleteGlobalReference(handle);
SetHandle(IntPtr.Zero);
return true;
}

public override bool IsInvalid => handle == IntPtr.Zero;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

internal static partial class Interop
{
internal static partial class AndroidCrypto
{
private delegate int NegativeSizeReadMethod<in THandle>(THandle handle, byte[]? buf, int cBuf);
private static byte[] GetDynamicBuffer<THandle>(NegativeSizeReadMethod<THandle> method, THandle handle)
{
int negativeSize = method(handle, null, 0);
if (negativeSize > 0)
throw new CryptographicException();

byte[] bytes = new byte[-negativeSize];
int ret = method(handle, bytes, bytes.Length);
if (ret != 1)
throw new CryptographicException();

return bytes;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class AndroidCrypto
{
[DllImport(Interop.Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamRelease")]
internal static extern void SSLStreamRelease(IntPtr ptr);

internal class SslException : Exception
{
internal SslException()
{
}

internal SslException(int errorCode)
{
HResult = errorCode;
}
}
}
}

namespace System.Net
{
internal sealed class SafeSslHandle : SafeHandle
{
public SafeSslHandle()
: base(IntPtr.Zero, ownsHandle: true)
{
}

protected override bool ReleaseHandle()
{
Interop.AndroidCrypto.SSLStreamRelease(handle);
SetHandle(IntPtr.Zero);
return true;
}

public override bool IsInvalid => handle == IntPtr.Zero;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,131 +11,61 @@ internal static partial class Interop
{
internal static partial class AndroidCrypto
{
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_DecodeX509")]
internal static extern SafeX509Handle DecodeX509(ref byte buf, int len);
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509Decode")]
internal static extern SafeX509Handle X509Decode(ref byte buf, int len);

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_EncodeX509")]
private static extern int EncodeX509(SafeX509Handle x, [Out] byte[]? buf, int len);
internal static byte[] EncodeX509(SafeX509Handle x)
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509Encode")]
private static extern int X509Encode(SafeX509Handle x, [Out] byte[]? buf, int len);
internal static byte[] X509Encode(SafeX509Handle x)
{
return Crypto.GetDynamicBuffer((ptr, buf, i) => EncodeX509(ptr, buf, i), x);
return GetDynamicBuffer((ptr, buf, i) => X509Encode(ptr, buf, i), x);
}

internal struct X509BasicInformation
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509DecodeCollection")]
private static extern int X509DecodeCollection(ref byte buf, int bufLen, IntPtr[]? ptrs, int handlesLen);
internal static SafeX509Handle[] X509DecodeCollection(ReadOnlySpan<byte> data)
{
public int Version { get; }
public DateTime NotAfter { get; }
public DateTime NotBefore { get; }

public X509BasicInformation(int version, DateTime notAfter, DateTime notBefore)
{
Version = version;
NotAfter = notAfter;
NotBefore = notBefore;
}

internal struct Interop
{
public int Version;
public long NotAfter; // In milliseconds since Unix epoch
public long NotBefore; // In milliseconds since Unix epoch
}
}

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509GetBasicInformation")]
private static extern byte X509GetBasicInformationImpl(SafeX509Handle x509, out X509BasicInformation.Interop info);
internal static X509BasicInformation X509GetBasicInformation(SafeX509Handle x509)
{
X509BasicInformation.Interop info;
byte success = X509GetBasicInformationImpl(x509, out info);
if (success == 0 || info.Version < 1)
ref byte buf = ref MemoryMarshal.GetReference(data);
int negativeSize = X509DecodeCollection(ref buf, data.Length, null, 0);
if (negativeSize > 0)
throw new CryptographicException();

return new X509BasicInformation(
info.Version,
DateTime.UnixEpoch.AddMilliseconds(info.NotAfter),
DateTime.UnixEpoch.AddMilliseconds(info.NotBefore));
}

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509GetPublicKeyAlgorithm")]
private static extern int X509GetPublicKeyAlgorithm(SafeX509Handle x509, byte[]? buf, int len);
internal static string X509GetPublicKeyAlgorithm(SafeX509Handle x509)
{
// Null terminator is included in byte array, so we ignore the last byte.
byte[] bytes = Crypto.GetDynamicBuffer((handle, buf, i) => X509GetPublicKeyAlgorithm(handle, buf, i), x509);
if (bytes.Length <= 1)
throw Interop.Crypto.CreateOpenSslCryptographicException();

return System.Text.Encoding.UTF8.GetString(bytes[..^1]);
}

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509GetPublicKeyBytes")]
private static extern int X509GetPublicKeyBytes(SafeX509Handle x509, byte[]? buf, int len);
internal static byte[] X509GetPublicKeyBytes(SafeX509Handle x509)
{
return Crypto.GetDynamicBuffer((handle, buf, i) => X509GetPublicKeyBytes(handle, buf, i), x509);
}
IntPtr[] ptrs = new IntPtr[-negativeSize];

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509GetPublicKeyParameterBytes")]
private static extern int X509GetPublicKeyParameterBytes(SafeX509Handle x509, byte[]? buf, int len);
internal static byte[] X509GetPublicKeyParameterBytes(SafeX509Handle x509)
{
return Crypto.GetDynamicBuffer((handle, buf, i) => X509GetPublicKeyParameterBytes(handle, buf, i), x509);
}

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509GetSerialNumber")]
private static extern int X509GetSerialNumber(SafeX509Handle x509, [Out] byte[]? buf, int len);
internal static byte[] X509GetSerialNumber(SafeX509Handle x509)
{
return Crypto.GetDynamicBuffer((handle, buf, i) => X509GetSerialNumber(handle, buf, i), x509);
}
int ret = X509DecodeCollection(ref buf, data.Length, ptrs, ptrs.Length);
if (ret != 1)
throw new CryptographicException();

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509GetSignatureAlgorithm")]
private static extern int X509GetSignatureAlgorithm(SafeX509Handle x509, byte[]? buf, int len);
internal static string X509GetSignatureAlgorithm(SafeX509Handle x509)
{
// Null terminator is included in byte array, so we ignore the last byte.
byte[] oidBytes = Crypto.GetDynamicBuffer((handle, buf, i) => X509GetSignatureAlgorithm(handle, buf, i), x509);
if (oidBytes.Length <= 1)
throw Interop.Crypto.CreateOpenSslCryptographicException();

return System.Text.Encoding.UTF8.GetString(oidBytes[..^1]);
}
SafeX509Handle[] handles = new SafeX509Handle[ptrs.Length];
for (var i = 0; i < handles.Length; i++)
{
handles[i] = new SafeX509Handle(ptrs[i]);
}

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509GetThumbprint")]
private static extern int X509GetThumbprint(SafeX509Handle x509, byte[]? buf, int len);
internal static byte[] X509GetThumbprint(SafeX509Handle x509)
{
return Crypto.GetDynamicBuffer((handle, buf, i) => X509GetThumbprint(handle, buf, i), x509);
return handles;
}

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509GetIssuerNameBytes")]
internal static extern int X509GetIssuerNameBytes(SafeX509Handle x509, byte[]? buf, int len);
internal static X500DistinguishedName X509GetIssuerName(SafeX509Handle x509)
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509GetContentType")]
private static extern X509ContentType X509GetContentType(ref byte buf, int len);
internal static X509ContentType X509GetContentType(ReadOnlySpan<byte> data)
{
byte[] buf = Crypto.GetDynamicBuffer((handle, buf, i) => X509GetIssuerNameBytes(handle, buf, i), x509);
return new X500DistinguishedName(buf);
return X509GetContentType(ref MemoryMarshal.GetReference(data), data.Length);
}
}
}

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509GetSubjectNameBytes")]
internal static extern int X509GetSubjectNameBytes(SafeX509Handle x509, byte[]? buf, int len);
internal static X500DistinguishedName X509GetSubjectName(SafeX509Handle x509)
namespace System.Security.Cryptography.X509Certificates
{
internal sealed class SafeX509Handle : Interop.JObjectLifetime.SafeJObjectHandle
{
public SafeX509Handle()
{
byte[] buf = Crypto.GetDynamicBuffer((handle, buf, i) => X509GetSubjectNameBytes(handle, buf, i), x509);
return new X500DistinguishedName(buf);
}

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509EnumExtensions")]
internal static unsafe extern void X509EnumExtensions(
SafeX509Handle x,
delegate* unmanaged<byte*, int, byte*, int, byte, void*, void> callback,
void* callbackContext);

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509FindExtensionData")]
private static extern int X509FindExtensionData(SafeX509Handle x509, string oid, [Out] byte[]? buf, int len);
internal static byte[] X509FindExtensionData(SafeX509Handle x509, string oid)
internal SafeX509Handle(IntPtr ptr)
: base(ptr)
{
return Crypto.GetDynamicBuffer((handle, buf, i) => X509FindExtensionData(handle, oid, buf, i), x509);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,8 @@ private static bool OpenSslGetTlsSupport(SslProtocols protocol)

private static bool GetTls10Support()
{
// on Windows and macOS TLS1.0/1.1 are supported.
if (IsWindows || IsOSXLike)
// on Windows, macOS, and Android TLS1.0/1.1 are supported.
if (IsWindows || IsOSXLike || IsAndroid)
{
return true;
}
Expand All @@ -295,13 +295,13 @@ private static bool GetTls10Support()

private static bool GetTls11Support()
{
// on Windows and macOS TLS1.0/1.1 are supported.
// on Windows, macOS, and Android TLS1.0/1.1 are supported.
// TLS 1.1 and 1.2 can work on Windows7 but it is not enabled by default.
if (IsWindows)
{
return !IsWindows7;
}
else if (IsOSXLike)
else if (IsOSXLike || IsAndroid)
{
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ add_compile_options(-Wno-unused-parameter)
add_compile_options(-Wno-unused-function)

set(NATIVECRYPTO_SOURCES
pal_jni.c
pal_misc.c
pal_bignum.c
pal_err.c
pal_evp.c
pal_evp_cipher.c
pal_hmac.c
pal_bignum.c
pal_ssl.c
pal_jni.c
pal_lifetime.c
pal_misc.c
pal_rsa.c
pal_err.c
pal_ssl.c
pal_sslstream.c
pal_x509.c
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ JavaVM* gJvm;
// java/io/ByteArrayInputStream
jclass g_ByteArrayInputStreamClass;
jmethodID g_ByteArrayInputStreamCtor;
jmethodID g_ByteArrayInputStreamReset;

// java/lang/Enum
jclass g_Enum;
Expand Down Expand Up @@ -99,6 +100,8 @@ jmethodID g_keyPairGenGenKeyPairMethod;
jclass g_CertFactoryClass;
jmethodID g_CertFactoryGetInstance;
jmethodID g_CertFactoryGenerateCertificate;
jmethodID g_CertFactoryGenerateCertificates;
jmethodID g_CertFactoryGenerateCertPath;
jmethodID g_CertFactoryGenerateCRL;

// java/security/cert/X509Certificate
Expand Down Expand Up @@ -151,6 +154,11 @@ jmethodID g_KeyFactoryGenPublicMethod;
jclass g_X509EncodedKeySpecClass;
jmethodID g_X509EncodedKeySpecCtor;

// java/util/Collection
jclass g_CollectionClass;
jmethodID g_CollectionIterator;
jmethodID g_CollectionSize;

// java/util/Date
jclass g_DateClass;
jmethodID g_DateGetTime;
Expand Down Expand Up @@ -331,6 +339,7 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
// cache some classes and methods while we're in the thread-safe JNI_OnLoad
g_ByteArrayInputStreamClass = GetClassGRef(env, "java/io/ByteArrayInputStream");
g_ByteArrayInputStreamCtor = GetMethod(env, false, g_ByteArrayInputStreamClass, "<init>", "([B)V");
g_ByteArrayInputStreamReset = GetMethod(env, false, g_ByteArrayInputStreamClass, "reset", "()V");

g_Enum = GetClassGRef(env, "java/lang/Enum");
g_EnumOrdinal = GetMethod(env, false, g_Enum, "ordinal", "()I");
Expand Down Expand Up @@ -390,6 +399,8 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
g_CertFactoryClass = GetClassGRef(env, "java/security/cert/CertificateFactory");
g_CertFactoryGetInstance = GetMethod(env, true, g_CertFactoryClass, "getInstance", "(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
g_CertFactoryGenerateCertificate = GetMethod(env, false, g_CertFactoryClass, "generateCertificate", "(Ljava/io/InputStream;)Ljava/security/cert/Certificate;");
g_CertFactoryGenerateCertificates = GetMethod(env, false, g_CertFactoryClass, "generateCertificates", "(Ljava/io/InputStream;)Ljava/util/Collection;");
g_CertFactoryGenerateCertPath = GetMethod(env, false, g_CertFactoryClass, "generateCertPath", "(Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/cert/CertPath;");
g_CertFactoryGenerateCRL = GetMethod(env, false, g_CertFactoryClass, "generateCRL", "(Ljava/io/InputStream;)Ljava/security/cert/CRL;");

g_X509CertClass = GetClassGRef(env, "java/security/cert/X509Certificate");
Expand Down Expand Up @@ -449,6 +460,10 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
g_X509EncodedKeySpecClass = GetClassGRef(env, "java/security/spec/X509EncodedKeySpec");
g_X509EncodedKeySpecCtor = GetMethod(env, false, g_X509EncodedKeySpecClass, "<init>", "([B)V");

g_CollectionClass = GetClassGRef(env, "java/util/Collection");
g_CollectionIterator = GetMethod(env, false, g_CollectionClass, "iterator", "()Ljava/util/Iterator;");
g_CollectionSize = GetMethod(env, false, g_CollectionClass, "size", "()I");

g_DateClass = GetClassGRef(env, "java/util/Date");
g_DateGetTime = GetMethod(env, false, g_DateClass, "getTime", "()J");

Expand Down
Loading

0 comments on commit bb9a9d1

Please sign in to comment.