Skip to content

Commit d0ff5ca

Browse files
authored
[AndroidCrypto] Implement X509 chain building (#49532)
1 parent cbc6aa6 commit d0ff5ca

File tree

25 files changed

+2103
-305
lines changed

25 files changed

+2103
-305
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Runtime.InteropServices;
7+
using System.Security.Cryptography;
8+
using System.Security.Cryptography.X509Certificates;
9+
10+
internal static partial class Interop
11+
{
12+
internal static partial class AndroidCrypto
13+
{
14+
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainCreateContext")]
15+
internal static extern SafeX509ChainContextHandle X509ChainCreateContext(
16+
SafeX509Handle cert,
17+
IntPtr[] extraStore,
18+
int extraStoreLen);
19+
20+
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainDestroyContext")]
21+
internal static extern void X509ChainDestroyContext(IntPtr ctx);
22+
23+
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainBuild")]
24+
[return: MarshalAs(UnmanagedType.U1)]
25+
internal static extern bool X509ChainBuild(
26+
SafeX509ChainContextHandle ctx,
27+
long timeInMsFromUnixEpoch);
28+
29+
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainGetCertificateCount")]
30+
private static extern int X509ChainGetCertificateCount(SafeX509ChainContextHandle ctx);
31+
32+
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainGetCertificates")]
33+
private static extern int X509ChainGetCertificates(
34+
SafeX509ChainContextHandle ctx,
35+
IntPtr[] certs,
36+
int certsLen);
37+
38+
internal static X509Certificate2[] X509ChainGetCertificates(SafeX509ChainContextHandle ctx)
39+
{
40+
int count = Interop.AndroidCrypto.X509ChainGetCertificateCount(ctx);
41+
var certPtrs = new IntPtr[count];
42+
43+
int res = Interop.AndroidCrypto.X509ChainGetCertificates(ctx, certPtrs, certPtrs.Length);
44+
if (res != SUCCESS)
45+
throw new CryptographicException();
46+
47+
var certs = new X509Certificate2[certPtrs.Length];
48+
for (int i = 0; i < certs.Length; i++)
49+
{
50+
certs[i] = new X509Certificate2(certPtrs[i]);
51+
}
52+
53+
return certs;
54+
}
55+
56+
[StructLayout(LayoutKind.Sequential)]
57+
internal struct ValidationError
58+
{
59+
public IntPtr Message; // UTF-16 string
60+
public int Index;
61+
public int Status;
62+
}
63+
64+
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainGetErrorCount")]
65+
private static extern int X509ChainGetErrorCount(SafeX509ChainContextHandle ctx);
66+
67+
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainGetErrors")]
68+
private static unsafe extern int X509ChainGetErrors(
69+
SafeX509ChainContextHandle ctx,
70+
[Out] ValidationError[] errors,
71+
int errorsLen);
72+
73+
internal static ValidationError[] X509ChainGetErrors(SafeX509ChainContextHandle ctx)
74+
{
75+
int count = Interop.AndroidCrypto.X509ChainGetErrorCount(ctx);
76+
if (count == 0)
77+
return Array.Empty<ValidationError>();
78+
79+
var errors = new ValidationError[count];
80+
int res = Interop.AndroidCrypto.X509ChainGetErrors(ctx, errors, errors.Length);
81+
if (res != SUCCESS)
82+
throw new CryptographicException();
83+
84+
return errors;
85+
}
86+
87+
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainSetCustomTrustStore")]
88+
internal static extern int X509ChainSetCustomTrustStore(
89+
SafeX509ChainContextHandle ctx,
90+
IntPtr[] customTrustStore,
91+
int customTrustStoreLen);
92+
93+
[DllImport(Libraries.CryptoNative, EntryPoint = "AndroidCryptoNative_X509ChainValidate")]
94+
internal static extern int X509ChainValidate(
95+
SafeX509ChainContextHandle ctx,
96+
X509RevocationMode revocationMode,
97+
X509RevocationFlag revocationFlag,
98+
out byte checkedRevocation);
99+
}
100+
}
101+
102+
namespace System.Security.Cryptography.X509Certificates
103+
{
104+
internal sealed class SafeX509ChainContextHandle : SafeHandle
105+
{
106+
public SafeX509ChainContextHandle()
107+
: base(IntPtr.Zero, ownsHandle: true)
108+
{
109+
}
110+
111+
protected override bool ReleaseHandle()
112+
{
113+
Interop.AndroidCrypto.X509ChainDestroyContext(handle);
114+
SetHandle(IntPtr.Zero);
115+
return true;
116+
}
117+
118+
public override bool IsInvalid => handle == IntPtr.Zero;
119+
}
120+
}

src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateAuthority.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,8 +517,9 @@ singleExtensions [1] EXPLICIT Extensions OPTIONAL }
517517
}
518518
else if (status == CertStatus.Revoked)
519519
{
520+
// Android does not support all precisions for seconds - just omit fractional seconds for testing on Android
520521
writer.PushSequence(s_context1);
521-
writer.WriteGeneralizedTime(revokedTime);
522+
writer.WriteGeneralizedTime(revokedTime, omitFractionalSeconds: OperatingSystem.IsAndroid());
522523
writer.PopSequence(s_context1);
523524
}
524525
else

src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/RevocationResponder.cs

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,23 @@ internal void AddCertificateAuthority(CertificateAuthority authority)
4848
{
4949
if (authority.AiaHttpUri != null && authority.AiaHttpUri.StartsWith(UriPrefix))
5050
{
51-
_aiaPaths.Add(authority.AiaHttpUri.Substring(UriPrefix.Length - 1), authority);
51+
string path = authority.AiaHttpUri.Substring(UriPrefix.Length - 1);
52+
Trace($"Adding AIA path : {path}");
53+
_aiaPaths.Add(path, authority);
5254
}
5355

5456
if (authority.CdpUri != null && authority.CdpUri.StartsWith(UriPrefix))
5557
{
56-
_crlPaths.Add(authority.CdpUri.Substring(UriPrefix.Length - 1), authority);
58+
string path = authority.CdpUri.Substring(UriPrefix.Length - 1);
59+
Trace($"Adding CRL path : {path}");
60+
_crlPaths.Add(path, authority);
5761
}
5862

5963
if (authority.OcspUri != null && authority.OcspUri.StartsWith(UriPrefix))
6064
{
61-
_ocspAuthorities.Add((authority.OcspUri.Substring(UriPrefix.Length - 1), authority));
65+
string path = authority.OcspUri.Substring(UriPrefix.Length - 1);
66+
Trace($"Adding OCSP path : {path}");
67+
_ocspAuthorities.Add((path, authority));
6268
}
6369
}
6470

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

212218
if (url.StartsWith(prefix))
213219
{
214-
if (context.Request.HttpMethod == "GET")
220+
byte[] reqBytes;
221+
if (TryGetOcspRequestBytes(context.Request, prefix, out reqBytes))
215222
{
216223
ReadOnlyMemory<byte> certId;
217224
ReadOnlyMemory<byte> nonce;
218-
219225
try
220226
{
221-
string base64 = HttpUtility.UrlDecode(url.Substring(prefix.Length + 1));
222-
byte[] reqBytes = Convert.FromBase64String(base64);
223227
DecodeOcspRequest(reqBytes, out certId, out nonce);
224228
}
225-
catch (Exception)
229+
catch (Exception e)
226230
{
227-
Trace($"OcspRequest Decode failed ({url})");
231+
Trace($"OcspRequest Decode failed ({url}) - {e}");
228232
context.Response.StatusCode = 400;
229233
context.Response.Close();
230234
return;
@@ -291,6 +295,36 @@ private static HttpListener OpenListener(out string uriPrefix)
291295
}
292296
}
293297

298+
private static bool TryGetOcspRequestBytes(HttpListenerRequest request, string prefix, out byte[] requestBytes)
299+
{
300+
requestBytes = null;
301+
try
302+
{
303+
if (request.HttpMethod == "GET")
304+
{
305+
string base64 = HttpUtility.UrlDecode(request.RawUrl.Substring(prefix.Length + 1));
306+
requestBytes = Convert.FromBase64String(base64);
307+
return true;
308+
}
309+
else if (request.HttpMethod == "POST" && request.ContentType == "application/ocsp-request")
310+
{
311+
using (System.IO.Stream stream = request.InputStream)
312+
{
313+
requestBytes = new byte[request.ContentLength64];
314+
int read = stream.Read(requestBytes, 0, requestBytes.Length);
315+
System.Diagnostics.Debug.Assert(read == requestBytes.Length);
316+
return true;
317+
}
318+
}
319+
}
320+
catch (Exception e)
321+
{
322+
Trace($"Failed to get OCSP request bytes ({request.RawUrl}) - {e}");
323+
}
324+
325+
return false;
326+
}
327+
294328
private static void DecodeOcspRequest(
295329
byte[] requestBytes,
296330
out ReadOnlyMemory<byte> certId,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#pragma once
5+
6+
// Matches managed X509ChainStatusFlags enum
7+
enum
8+
{
9+
PAL_X509ChainNoError = 0,
10+
PAL_X509ChainNotTimeValid = 0x00000001,
11+
PAL_X509ChainNotTimeNested = 0x00000002,
12+
PAL_X509ChainRevoked = 0x00000004,
13+
PAL_X509ChainNotSignatureValid = 0x00000008,
14+
PAL_X509ChainNotValidForUsage = 0x00000010,
15+
PAL_X509ChainUntrustedRoot = 0x00000020,
16+
PAL_X509ChainRevocationStatusUnknown = 0x00000040,
17+
PAL_X509ChainCyclic = 0x00000080,
18+
PAL_X509ChainInvalidExtension = 0x00000100,
19+
PAL_X509ChainInvalidPolicyConstraints = 0x00000200,
20+
PAL_X509ChainInvalidBasicConstraints = 0x00000400,
21+
PAL_X509ChainInvalidNameConstraints = 0x00000800,
22+
PAL_X509ChainHasNotSupportedNameConstraint = 0x00001000,
23+
PAL_X509ChainHasNotDefinedNameConstraint = 0x00002000,
24+
PAL_X509ChainHasNotPermittedNameConstraint = 0x00004000,
25+
PAL_X509ChainHasExcludedNameConstraint = 0x00008000,
26+
PAL_X509ChainPartialChain = 0x00010000,
27+
PAL_X509ChainCtlNotTimeValid = 0x00020000,
28+
PAL_X509ChainCtlNotSignatureValid = 0x00040000,
29+
PAL_X509ChainCtlNotValidForUsage = 0x00080000,
30+
PAL_X509ChainOfflineRevocation = 0x01000000,
31+
PAL_X509ChainNoIssuanceChainPolicy = 0x02000000,
32+
PAL_X509ChainExplicitDistrust = 0x04000000,
33+
PAL_X509ChainHasNotSupportedCriticalExtension = 0x08000000,
34+
PAL_X509ChainHasWeakSignature = 0x00100000,
35+
};
36+
typedef uint32_t PAL_X509ChainStatusFlags;
37+
38+
// Matches managed X509ContentType enum
39+
enum
40+
{
41+
PAL_X509Unknown = 0,
42+
PAL_Certificate = 1,
43+
PAL_SerializedCert = 2,
44+
PAL_Pkcs12 = 3,
45+
PAL_SerializedStore = 4,
46+
PAL_Pkcs7 = 5,
47+
PAL_Authenticode = 6,
48+
};
49+
typedef uint32_t PAL_X509ContentType;

src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ set(NATIVECRYPTO_SOURCES
2323
pal_ssl.c
2424
pal_sslstream.c
2525
pal_x509.c
26+
pal_x509chain.c
2627
pal_x509store.c
2728
)
2829

src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_dsa.c

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,6 @@
77
#include "pal_bignum.h"
88
#include "pal_misc.h"
99

10-
#define INIT_LOCALS(name, ...) \
11-
enum { __VA_ARGS__, count_##name }; \
12-
jobject name[count_##name] = { 0 } \
13-
14-
#define RELEASE_LOCALS_ENV(name, releaseFn) \
15-
do { \
16-
for (int i = 0; i < count_##name; ++i) \
17-
{ \
18-
releaseFn(env, name[i]); \
19-
} \
20-
} while(0)
21-
2210
int32_t AndroidCryptoNative_DsaGenerateKey(jobject* dsa, int32_t bits)
2311
{
2412
assert(dsa);
@@ -226,7 +214,7 @@ int32_t AndroidCryptoNative_GetDsaParameters(
226214
assert(gLength);
227215
assert(yLength);
228216
assert(xLength);
229-
217+
230218
JNIEnv* env = GetJNIEnv();
231219

232220
INIT_LOCALS(loc, algName, keyFactory, publicKey, publicKeySpec, privateKey, privateKeySpec);
@@ -283,7 +271,7 @@ int32_t AndroidCryptoNative_DsaKeyCreateByExplicitParameters(
283271
assert(false);
284272
return 0;
285273
}
286-
274+
287275
JNIEnv* env = GetJNIEnv();
288276

289277
INIT_LOCALS(bn, P, Q, G, Y, X);

src/libraries/Native/Unix/System.Security.Cryptography.Native.Android/pal_ecc_import_export.c

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,6 @@
88
#include "pal_utilities.h"
99
#include "pal_misc.h"
1010

11-
12-
#define INIT_LOCALS(name, ...) \
13-
enum { __VA_ARGS__, count_##name }; \
14-
jobject name[count_##name] = { 0 } \
15-
16-
#define RELEASE_LOCALS_ENV(name, releaseFn) \
17-
do { \
18-
for (int i = 0; i < count_##name; ++i) \
19-
{ \
20-
releaseFn(env, name[i]); \
21-
} \
22-
} while(0)
23-
2411
int32_t AndroidCryptoNative_GetECKeyParameters(const EC_KEY* key,
2512
int32_t includePrivate,
2613
jobject* qx,

0 commit comments

Comments
 (0)