Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit a8f70ba

Browse files
committed
Add support for reading PFX in X509Certificate2 and X509Certificate2Collection on Unix.
At this stage HasPrivateKey properties on Unix still returns false, that is still coming.
1 parent af32a21 commit a8f70ba

File tree

13 files changed

+346
-43
lines changed

13 files changed

+346
-43
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Runtime.InteropServices;
5+
6+
using Microsoft.Win32.SafeHandles;
7+
8+
internal static partial class Interop
9+
{
10+
internal static partial class libcrypto
11+
{
12+
[DllImport(Libraries.LibCrypto)]
13+
internal static extern SafeRsaHandle EVP_PKEY_get1_RSA(SafeEvpPkeyHandle pkey);
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class libcrypto
10+
{
11+
[DllImport(Libraries.LibCrypto)]
12+
internal static extern void EVP_PKEY_free(IntPtr pkey);
13+
}
14+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
using Microsoft.Win32.SafeHandles;
8+
9+
internal static partial class Interop
10+
{
11+
internal static partial class libcrypto
12+
{
13+
[DllImport(Libraries.LibCrypto)]
14+
internal static unsafe extern SafePkcs12Handle d2i_PKCS12(IntPtr zero, byte** ppin, int len);
15+
16+
[DllImport(Libraries.LibCrypto)]
17+
internal static extern SafePkcs12Handle d2i_PKCS12_bio(SafeBioHandle bio, IntPtr zero);
18+
19+
[DllImport(Libraries.LibCrypto)]
20+
internal static extern void PKCS12_free(IntPtr p12);
21+
22+
[DllImport(Libraries.CryptoInterop, CharSet = CharSet.Ansi)]
23+
[return: MarshalAs(UnmanagedType.Bool)]
24+
internal static extern bool PKCS12_parse(SafePkcs12Handle p12, string pass, out SafeEvpPkeyHandle pkey, out SafeX509Handle cert, out SafeX509StackHandle ca);
25+
}
26+
}

src/Common/src/Interop/Unix/libcrypto/Interop.d2i.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal static partial class libcrypto
1414

1515
internal unsafe delegate int I2DFunc<in THandle>(THandle handle, byte** @out);
1616

17-
internal static unsafe THandle OpenSslD2I<THandle>(D2IFunc<THandle> d2i, byte[] data)
17+
internal static unsafe THandle OpenSslD2I<THandle>(D2IFunc<THandle> d2i, byte[] data, bool checkHandle=true)
1818
where THandle : SafeHandle
1919
{
2020
// The OpenSSL d2i_* functions are set up for cascaded calls, so they increment *ppData while reading.
@@ -27,7 +27,10 @@ internal static unsafe THandle OpenSslD2I<THandle>(D2IFunc<THandle> d2i, byte[]
2727

2828
THandle handle = d2i(IntPtr.Zero, ppData, data.Length);
2929

30-
CheckValidOpenSslHandle(handle);
30+
if (checkHandle)
31+
{
32+
CheckValidOpenSslHandle(handle);
33+
}
3134

3235
return handle;
3336
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
namespace Microsoft.Win32.SafeHandles
8+
{
9+
internal sealed class SafeEvpPkeyHandle : SafeHandle
10+
{
11+
private SafeEvpPkeyHandle() :
12+
base(IntPtr.Zero, ownsHandle: true)
13+
{
14+
}
15+
16+
protected override bool ReleaseHandle()
17+
{
18+
Interop.libcrypto.EVP_PKEY_free(handle);
19+
SetHandle(IntPtr.Zero);
20+
return true;
21+
}
22+
23+
public override bool IsInvalid
24+
{
25+
get { return handle == IntPtr.Zero; }
26+
}
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
namespace Microsoft.Win32.SafeHandles
8+
{
9+
internal sealed class SafePkcs12Handle : SafeHandle
10+
{
11+
private SafePkcs12Handle() :
12+
base(IntPtr.Zero, ownsHandle: true)
13+
{
14+
}
15+
16+
protected override bool ReleaseHandle()
17+
{
18+
Interop.libcrypto.PKCS12_free(handle);
19+
SetHandle(IntPtr.Zero);
20+
return true;
21+
}
22+
23+
public override bool IsInvalid
24+
{
25+
get { return handle == IntPtr.Zero; }
26+
}
27+
}
28+
}

src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CertificatePal.cs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System;
5+
using System.Diagnostics;
56
using System.Security.Cryptography.X509Certificates;
7+
using Microsoft.Win32.SafeHandles;
68

79
namespace Internal.Cryptography.Pal
810
{
@@ -16,9 +18,64 @@ public static ICertificatePal FromHandle(IntPtr handle)
1618
return new OpenSslX509CertificateReader(Interop.libcrypto.X509_dup(handle));
1719
}
1820

19-
public static ICertificatePal FromBlob(byte[] rawData, string password, X509KeyStorageFlags keyStorageFlags)
21+
public static unsafe ICertificatePal FromBlob(byte[] rawData, string password, X509KeyStorageFlags keyStorageFlags)
2022
{
21-
return new OpenSslX509CertificateReader(rawData);
23+
// If we can see a hyphen, assume it's PEM. Otherwise try DER-X509, then fall back to DER-PKCS12.
24+
SafeX509Handle cert;
25+
26+
// PEM
27+
if (rawData[0] == '-')
28+
{
29+
using (SafeBioHandle bio = Interop.libcrypto.BIO_new(Interop.libcrypto.BIO_s_mem()))
30+
{
31+
Interop.libcrypto.CheckValidOpenSslHandle(bio);
32+
33+
Interop.libcrypto.BIO_write(bio, rawData, rawData.Length);
34+
cert = Interop.libcrypto.PEM_read_bio_X509_AUX(bio, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
35+
}
36+
37+
Interop.libcrypto.CheckValidOpenSslHandle(cert);
38+
}
39+
40+
// DER-X509
41+
cert = Interop.libcrypto.OpenSslD2I((ptr, b, i) => Interop.libcrypto.d2i_X509(ptr, b, i), rawData, checkHandle: false);
42+
43+
if (!cert.IsInvalid)
44+
{
45+
return new OpenSslX509CertificateReader(cert);
46+
}
47+
48+
// DER-PKCS12
49+
OpenSslPkcs12Reader pfx;
50+
51+
if (OpenSslPkcs12Reader.TryRead(rawData, out pfx))
52+
{
53+
using (pfx)
54+
{
55+
pfx.Decrypt(password);
56+
57+
ICertificatePal first = null;
58+
59+
foreach (OpenSslX509CertificateReader certPal in pfx.ReadCertificates())
60+
{
61+
// When requesting an X509Certificate2 from a PFX only the first entry is
62+
// returned. Other entries should be disposed.
63+
if (first == null)
64+
{
65+
first = certPal;
66+
}
67+
else
68+
{
69+
certPal.Dispose();
70+
}
71+
}
72+
73+
return first;
74+
}
75+
}
76+
77+
// Unsupported
78+
throw Interop.libcrypto.CreateOpenSslCryptographicException();
2279
}
2380

2481
public static ICertificatePal FromFile(string fileName, string password, X509KeyStorageFlags keyStorageFlags)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
7+
using Microsoft.Win32.SafeHandles;
8+
9+
namespace Internal.Cryptography.Pal
10+
{
11+
internal sealed class OpenSslPkcs12Reader : IDisposable
12+
{
13+
private readonly SafePkcs12Handle _pkcs12Handle;
14+
private SafeEvpPkeyHandle _evpPkeyHandle;
15+
private SafeX509Handle _x509Handle;
16+
private SafeX509StackHandle _caStackHandle;
17+
18+
private OpenSslPkcs12Reader(SafePkcs12Handle pkcs12Handle)
19+
{
20+
_pkcs12Handle = pkcs12Handle;
21+
}
22+
23+
public unsafe static bool TryRead(byte[] data, out OpenSslPkcs12Reader pkcs12Reader)
24+
{
25+
SafePkcs12Handle handle = Interop.libcrypto.OpenSslD2I(
26+
(ptr, b, i) => Interop.libcrypto.d2i_PKCS12(ptr, b, i),
27+
data,
28+
checkHandle: false);
29+
30+
if (!handle.IsInvalid)
31+
{
32+
pkcs12Reader = new OpenSslPkcs12Reader(handle);
33+
return true;
34+
}
35+
36+
pkcs12Reader = null;
37+
return false;
38+
}
39+
40+
public static bool TryRead(SafeBioHandle fileBio, out OpenSslPkcs12Reader pkcs12Reader)
41+
{
42+
SafePkcs12Handle p12 = Interop.libcrypto.d2i_PKCS12_bio(fileBio, IntPtr.Zero);
43+
44+
if (!p12.IsInvalid)
45+
{
46+
pkcs12Reader = new OpenSslPkcs12Reader(p12);
47+
return true;
48+
}
49+
50+
pkcs12Reader = null;
51+
return false;
52+
}
53+
54+
public void Dispose()
55+
{
56+
if (_caStackHandle != null)
57+
{
58+
_caStackHandle.Dispose();
59+
_caStackHandle = null;
60+
}
61+
62+
if (_x509Handle != null)
63+
{
64+
_x509Handle.Dispose();
65+
_x509Handle = null;
66+
}
67+
68+
if (_evpPkeyHandle != null)
69+
{
70+
_evpPkeyHandle.Dispose();
71+
_evpPkeyHandle = null;
72+
}
73+
74+
if (_pkcs12Handle != null)
75+
{
76+
_pkcs12Handle.Dispose();
77+
}
78+
}
79+
80+
public void Decrypt(string password)
81+
{
82+
bool parsed = Interop.libcrypto.PKCS12_parse(
83+
_pkcs12Handle,
84+
password,
85+
out _evpPkeyHandle,
86+
out _x509Handle,
87+
out _caStackHandle);
88+
89+
if (!parsed)
90+
{
91+
throw Interop.libcrypto.CreateOpenSslCryptographicException();
92+
}
93+
}
94+
95+
public List<OpenSslX509CertificateReader> ReadCertificates()
96+
{
97+
var certs = new List<OpenSslX509CertificateReader>();
98+
99+
if (_caStackHandle != null && !_caStackHandle.IsInvalid)
100+
{
101+
int caCertCount = Interop.NativeCrypto.GetX509StackFieldCount(_caStackHandle);
102+
103+
for (int i = 0; i < caCertCount; i++)
104+
{
105+
IntPtr certPtr = Interop.NativeCrypto.GetX509StackField(_caStackHandle, i);
106+
107+
if (certPtr != IntPtr.Zero)
108+
{
109+
// The STACK_OF(X509) still needs to be cleaned up, so duplicate the handle out of it.
110+
certs.Add(new OpenSslX509CertificateReader(Interop.libcrypto.X509_dup(certPtr)));
111+
}
112+
}
113+
}
114+
115+
if (_x509Handle != null && !_x509Handle.IsInvalid)
116+
{
117+
// The certificate and (if applicable) private key handles will be given over
118+
// to the OpenSslX509CertificateReader, and the fields here are thus nulled out to
119+
// prevent double-Dispose.
120+
OpenSslX509CertificateReader reader = new OpenSslX509CertificateReader(_x509Handle);
121+
_x509Handle = null;
122+
123+
certs.Add(reader);
124+
}
125+
126+
return certs;
127+
}
128+
}
129+
}

src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,41 +35,6 @@ internal OpenSslX509CertificateReader(SafeX509Handle handle)
3535
_cert = handle;
3636
}
3737

38-
internal unsafe OpenSslX509CertificateReader(byte[] data)
39-
{
40-
SafeX509Handle cert;
41-
42-
// If the first byte is a hyphen then this is likely PEM-encoded,
43-
// otherwise it's DER-encoded (or not a certificate).
44-
if (data[0] == '-')
45-
{
46-
using (SafeBioHandle bio = Interop.libcrypto.BIO_new(Interop.libcrypto.BIO_s_mem()))
47-
{
48-
Interop.libcrypto.CheckValidOpenSslHandle(bio);
49-
50-
Interop.libcrypto.BIO_write(bio, data, data.Length);
51-
cert = Interop.libcrypto.PEM_read_bio_X509_AUX(bio, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
52-
}
53-
}
54-
else
55-
{
56-
cert = Interop.libcrypto.OpenSslD2I(Interop.libcrypto.d2i_X509, data);
57-
}
58-
59-
Interop.libcrypto.CheckValidOpenSslHandle(cert);
60-
61-
// X509_check_purpose has the effect of populating the sha1_hash value,
62-
// and other "initialize" type things.
63-
bool init = Interop.libcrypto.X509_check_purpose(cert, -1, 0);
64-
65-
if (!init)
66-
{
67-
throw Interop.libcrypto.CreateOpenSslCryptographicException();
68-
}
69-
70-
_cert = cert;
71-
}
72-
7338
public bool HasPrivateKey
7439
{
7540
get { return false; }

0 commit comments

Comments
 (0)