Skip to content

Commit

Permalink
[AndroidCrypto] Implement X509 chain building (#49532) (#50135)
Browse files Browse the repository at this point in the history
  • Loading branch information
elinor-fung authored Mar 29, 2021
1 parent 1b31080 commit 58cf4f2
Show file tree
Hide file tree
Showing 25 changed files with 2,103 additions and 301 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// 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.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

internal static partial class Interop
{
internal static partial class AndroidCrypto
{
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainCreateContext")]
internal static extern SafeX509ChainContextHandle X509ChainCreateContext(
SafeX509Handle cert,
IntPtr[] extraStore,
int extraStoreLen);

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainDestroyContext")]
internal static extern void X509ChainDestroyContext(IntPtr ctx);

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainBuild")]
[return: MarshalAs(UnmanagedType.U1)]
internal static extern bool X509ChainBuild(
SafeX509ChainContextHandle ctx,
long timeInMsFromUnixEpoch);

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainGetCertificateCount")]
private static extern int X509ChainGetCertificateCount(SafeX509ChainContextHandle ctx);

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainGetCertificates")]
private static extern int X509ChainGetCertificates(
SafeX509ChainContextHandle ctx,
IntPtr[] certs,
int certsLen);

internal static X509Certificate2[] X509ChainGetCertificates(SafeX509ChainContextHandle ctx)
{
int count = Interop.AndroidCrypto.X509ChainGetCertificateCount(ctx);
var certPtrs = new IntPtr[count];

int res = Interop.AndroidCrypto.X509ChainGetCertificates(ctx, certPtrs, certPtrs.Length);
if (res != SUCCESS)
throw new CryptographicException();

var certs = new X509Certificate2[certPtrs.Length];
for (int i = 0; i < certs.Length; i++)
{
certs[i] = new X509Certificate2(certPtrs[i]);
}

return certs;
}

[StructLayout(LayoutKind.Sequential)]
internal struct ValidationError
{
public IntPtr Message; // UTF-16 string
public int Index;
public int Status;
}

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainGetErrorCount")]
private static extern int X509ChainGetErrorCount(SafeX509ChainContextHandle ctx);

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainGetErrors")]
private static unsafe extern int X509ChainGetErrors(
SafeX509ChainContextHandle ctx,
[Out] ValidationError[] errors,
int errorsLen);

internal static ValidationError[] X509ChainGetErrors(SafeX509ChainContextHandle ctx)
{
int count = Interop.AndroidCrypto.X509ChainGetErrorCount(ctx);
if (count == 0)
return Array.Empty<ValidationError>();

var errors = new ValidationError[count];
int res = Interop.AndroidCrypto.X509ChainGetErrors(ctx, errors, errors.Length);
if (res != SUCCESS)
throw new CryptographicException();

return errors;
}

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainSetCustomTrustStore")]
internal static extern int X509ChainSetCustomTrustStore(
SafeX509ChainContextHandle ctx,
IntPtr[] customTrustStore,
int customTrustStoreLen);

[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainValidate")]
internal static extern int X509ChainValidate(
SafeX509ChainContextHandle ctx,
X509RevocationMode revocationMode,
X509RevocationFlag revocationFlag,
out byte checkedRevocation);
}
}

namespace System.Security.Cryptography.X509Certificates
{
internal sealed class SafeX509ChainContextHandle : SafeHandle
{
public SafeX509ChainContextHandle()
: base(IntPtr.Zero, ownsHandle: true)
{
}

protected override bool ReleaseHandle()
{
Interop.AndroidCrypto.X509ChainDestroyContext(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 @@ -517,8 +517,9 @@ singleExtensions [1] EXPLICIT Extensions OPTIONAL }
}
else if (status == CertStatus.Revoked)
{
// Android does not support all precisions for seconds - just omit fractional seconds for testing on Android
writer.PushSequence(s_context1);
writer.WriteGeneralizedTime(revokedTime);
writer.WriteGeneralizedTime(revokedTime, omitFractionalSeconds: OperatingSystem.IsAndroid());
writer.PopSequence(s_context1);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,23 @@ internal void AddCertificateAuthority(CertificateAuthority authority)
{
if (authority.AiaHttpUri != null && authority.AiaHttpUri.StartsWith(UriPrefix))
{
_aiaPaths.Add(authority.AiaHttpUri.Substring(UriPrefix.Length - 1), authority);
string path = authority.AiaHttpUri.Substring(UriPrefix.Length - 1);
Trace($"Adding AIA path : {path}");
_aiaPaths.Add(path, authority);
}

if (authority.CdpUri != null && authority.CdpUri.StartsWith(UriPrefix))
{
_crlPaths.Add(authority.CdpUri.Substring(UriPrefix.Length - 1), authority);
string path = authority.CdpUri.Substring(UriPrefix.Length - 1);
Trace($"Adding CRL path : {path}");
_crlPaths.Add(path, authority);
}

if (authority.OcspUri != null && authority.OcspUri.StartsWith(UriPrefix))
{
_ocspAuthorities.Add((authority.OcspUri.Substring(UriPrefix.Length - 1), authority));
string path = authority.OcspUri.Substring(UriPrefix.Length - 1);
Trace($"Adding OCSP path : {path}");
_ocspAuthorities.Add((path, authority));
}
}

Expand Down Expand Up @@ -211,20 +217,18 @@ private void HandleRequest(HttpListenerContext context, ref bool responded)

if (url.StartsWith(prefix))
{
if (context.Request.HttpMethod == "GET")
byte[] reqBytes;
if (TryGetOcspRequestBytes(context.Request, prefix, out reqBytes))
{
ReadOnlyMemory<byte> certId;
ReadOnlyMemory<byte> nonce;

try
{
string base64 = HttpUtility.UrlDecode(url.Substring(prefix.Length + 1));
byte[] reqBytes = Convert.FromBase64String(base64);
DecodeOcspRequest(reqBytes, out certId, out nonce);
}
catch (Exception)
catch (Exception e)
{
Trace($"OcspRequest Decode failed ({url})");
Trace($"OcspRequest Decode failed ({url}) - {e}");
context.Response.StatusCode = 400;
context.Response.Close();
return;
Expand Down Expand Up @@ -291,6 +295,36 @@ private static HttpListener OpenListener(out string uriPrefix)
}
}

private static bool TryGetOcspRequestBytes(HttpListenerRequest request, string prefix, out byte[] requestBytes)
{
requestBytes = null;
try
{
if (request.HttpMethod == "GET")
{
string base64 = HttpUtility.UrlDecode(request.RawUrl.Substring(prefix.Length + 1));
requestBytes = Convert.FromBase64String(base64);
return true;
}
else if (request.HttpMethod == "POST" && request.ContentType == "application/ocsp-request")
{
using (System.IO.Stream stream = request.InputStream)
{
requestBytes = new byte[request.ContentLength64];
int read = stream.Read(requestBytes, 0, requestBytes.Length);
System.Diagnostics.Debug.Assert(read == requestBytes.Length);
return true;
}
}
}
catch (Exception e)
{
Trace($"Failed to get OCSP request bytes ({request.RawUrl}) - {e}");
}

return false;
}

private static void DecodeOcspRequest(
byte[] requestBytes,
out ReadOnlyMemory<byte> certId,
Expand Down
49 changes: 49 additions & 0 deletions src/libraries/Native/Unix/Common/pal_x509_types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#pragma once

// Matches managed X509ChainStatusFlags enum
enum
{
PAL_X509ChainNoError = 0,
PAL_X509ChainNotTimeValid = 0x00000001,
PAL_X509ChainNotTimeNested = 0x00000002,
PAL_X509ChainRevoked = 0x00000004,
PAL_X509ChainNotSignatureValid = 0x00000008,
PAL_X509ChainNotValidForUsage = 0x00000010,
PAL_X509ChainUntrustedRoot = 0x00000020,
PAL_X509ChainRevocationStatusUnknown = 0x00000040,
PAL_X509ChainCyclic = 0x00000080,
PAL_X509ChainInvalidExtension = 0x00000100,
PAL_X509ChainInvalidPolicyConstraints = 0x00000200,
PAL_X509ChainInvalidBasicConstraints = 0x00000400,
PAL_X509ChainInvalidNameConstraints = 0x00000800,
PAL_X509ChainHasNotSupportedNameConstraint = 0x00001000,
PAL_X509ChainHasNotDefinedNameConstraint = 0x00002000,
PAL_X509ChainHasNotPermittedNameConstraint = 0x00004000,
PAL_X509ChainHasExcludedNameConstraint = 0x00008000,
PAL_X509ChainPartialChain = 0x00010000,
PAL_X509ChainCtlNotTimeValid = 0x00020000,
PAL_X509ChainCtlNotSignatureValid = 0x00040000,
PAL_X509ChainCtlNotValidForUsage = 0x00080000,
PAL_X509ChainOfflineRevocation = 0x01000000,
PAL_X509ChainNoIssuanceChainPolicy = 0x02000000,
PAL_X509ChainExplicitDistrust = 0x04000000,
PAL_X509ChainHasNotSupportedCriticalExtension = 0x08000000,
PAL_X509ChainHasWeakSignature = 0x00100000,
};
typedef uint32_t PAL_X509ChainStatusFlags;

// Matches managed X509ContentType enum
enum
{
PAL_X509Unknown = 0,
PAL_Certificate = 1,
PAL_SerializedCert = 2,
PAL_Pkcs12 = 3,
PAL_SerializedStore = 4,
PAL_Pkcs7 = 5,
PAL_Authenticode = 6,
};
typedef uint32_t PAL_X509ContentType;
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ set(NATIVECRYPTO_SOURCES
pal_ssl.c
pal_sslstream.c
pal_x509.c
pal_x509chain.c
pal_x509store.c
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,6 @@
#include "pal_bignum.h"
#include "pal_misc.h"

#define INIT_LOCALS(name, ...) \
enum { __VA_ARGS__, count_##name }; \
jobject name[count_##name] = { 0 } \

#define RELEASE_LOCALS_ENV(name, releaseFn) \
do { \
for (int i = 0; i < count_##name; ++i) \
{ \
releaseFn(env, name[i]); \
} \
} while(0)

int32_t AndroidCryptoNative_DsaGenerateKey(jobject* dsa, int32_t bits)
{
assert(dsa);
Expand Down Expand Up @@ -226,7 +214,7 @@ int32_t AndroidCryptoNative_GetDsaParameters(
assert(gLength);
assert(yLength);
assert(xLength);

JNIEnv* env = GetJNIEnv();

INIT_LOCALS(loc, algName, keyFactory, publicKey, publicKeySpec, privateKey, privateKeySpec);
Expand Down Expand Up @@ -283,7 +271,7 @@ int32_t AndroidCryptoNative_DsaKeyCreateByExplicitParameters(
assert(false);
return 0;
}

JNIEnv* env = GetJNIEnv();

INIT_LOCALS(bn, P, Q, G, Y, X);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,6 @@
#include "pal_utilities.h"
#include "pal_misc.h"


#define INIT_LOCALS(name, ...) \
enum { __VA_ARGS__, count_##name }; \
jobject name[count_##name] = { 0 } \

#define RELEASE_LOCALS_ENV(name, releaseFn) \
do { \
for (int i = 0; i < count_##name; ++i) \
{ \
releaseFn(env, name[i]); \
} \
} while(0)

int32_t AndroidCryptoNative_GetECKeyParameters(const EC_KEY* key,
int32_t includePrivate,
jobject* qx,
Expand Down
Loading

0 comments on commit 58cf4f2

Please sign in to comment.