From 154ab21df59cb9a618ea704d43266d5e957d5b7d Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Mon, 26 Aug 2019 09:00:51 -0700 Subject: [PATCH 01/61] Move PfxAsn to Common for sharing with X509Certificates --- .../Cryptography}/Asn1/DigestInfoAsn.xml | 2 +- .../Cryptography}/Asn1/DigestInfoAsn.xml.cs | 2 +- .../Cryptography/Asn1/Pkcs12}/CertBagAsn.xml | 0 .../Asn1/Pkcs12}/CertBagAsn.xml.cs | 0 .../Cryptography/Asn1/Pkcs12}/MacData.xml | 4 +- .../Cryptography/Asn1/Pkcs12}/MacData.xml.cs | 6 +- .../Cryptography/Asn1/Pkcs12}/PfxAsn.xml | 6 +- .../Cryptography/Asn1/Pkcs12}/PfxAsn.xml.cs | 12 ++-- .../Cryptography/Asn1/Pkcs12}/SafeBagAsn.xml | 0 .../Asn1/Pkcs12}/SafeBagAsn.xml.cs | 0 .../Asn1/Pkcs7}/ContentInfoAsn.xml | 2 +- .../Asn1/Pkcs7}/ContentInfoAsn.xml.cs | 2 +- .../Cryptography/Pal/AnyOS/ManagedPal.Asn.cs | 3 +- .../Pal/AnyOS/ManagedPal.Decode.cs | 1 + .../src/Internal/Cryptography/PkcsHelpers.cs | 2 +- .../System.Security.Cryptography.Pkcs.csproj | 66 ++++++++++++------- .../Cryptography/Pkcs/Pkcs12Builder.cs | 1 + .../Security/Cryptography/Pkcs/Pkcs12Info.cs | 2 + .../Cryptography/Pkcs/Pkcs12SafeContents.cs | 1 + .../Pkcs/Rfc3161TimestampToken.cs | 1 + .../Security/Cryptography/Pkcs/SignedCms.cs | 1 + 21 files changed, 69 insertions(+), 45 deletions(-) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs => Common/src/System/Security/Cryptography}/Asn1/DigestInfoAsn.xml (91%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs => Common/src/System/Security/Cryptography}/Asn1/DigestInfoAsn.xml.cs (98%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs12}/CertBagAsn.xml (100%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs12}/CertBagAsn.xml.cs (100%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs12}/MacData.xml (88%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs12}/MacData.xml.cs (94%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs12}/PfxAsn.xml (79%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs12}/PfxAsn.xml.cs (83%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs12}/SafeBagAsn.xml (100%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs12}/SafeBagAsn.xml.cs (100%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs7}/ContentInfoAsn.xml (89%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs7}/ContentInfoAsn.xml.cs (98%) diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/DigestInfoAsn.xml b/src/Common/src/System/Security/Cryptography/Asn1/DigestInfoAsn.xml similarity index 91% rename from src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/DigestInfoAsn.xml rename to src/Common/src/System/Security/Cryptography/Asn1/DigestInfoAsn.xml index 2484455f82f4..06a0a07e8d45 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/DigestInfoAsn.xml +++ b/src/Common/src/System/Security/Cryptography/Asn1/DigestInfoAsn.xml @@ -2,7 +2,7 @@ + namespace="System.Security.Cryptography.Asn1"> - + diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/MacData.xml.cs b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/MacData.xml.cs similarity index 94% rename from src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/MacData.xml.cs rename to src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/MacData.xml.cs index a5b7368e9958..3c2d465e68c5 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/MacData.xml.cs +++ b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/MacData.xml.cs @@ -8,14 +8,14 @@ using System.Security.Cryptography; using System.Security.Cryptography.Asn1; -namespace System.Security.Cryptography.Pkcs.Asn1 +namespace System.Security.Cryptography.Asn1.Pkcs12 { [StructLayout(LayoutKind.Sequential)] internal partial struct MacData { private static readonly byte[] s_defaultIterationCount = { 0x02, 0x01, 0x01 }; - internal System.Security.Cryptography.Pkcs.Asn1.DigestInfoAsn Mac; + internal System.Security.Cryptography.Asn1.DigestInfoAsn Mac; internal ReadOnlyMemory MacSalt; internal int IterationCount; @@ -96,7 +96,7 @@ internal static void Decode(AsnReader reader, Asn1Tag expectedTag, out MacData d AsnReader sequenceReader = reader.ReadSequence(expectedTag); AsnReader defaultReader; - System.Security.Cryptography.Pkcs.Asn1.DigestInfoAsn.Decode(sequenceReader, out decoded.Mac); + System.Security.Cryptography.Asn1.DigestInfoAsn.Decode(sequenceReader, out decoded.Mac); if (sequenceReader.TryReadPrimitiveOctetStringBytes(out ReadOnlyMemory tmpMacSalt)) { diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/PfxAsn.xml b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.xml similarity index 79% rename from src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/PfxAsn.xml rename to src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.xml index f27a9879fa07..a46703b33d3a 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/PfxAsn.xml +++ b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.xml @@ -2,7 +2,7 @@ + namespace="System.Security.Cryptography.Asn1.Pkcs12"> - - + + diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/PfxAsn.xml.cs b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.xml.cs similarity index 83% rename from src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/PfxAsn.xml.cs rename to src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.xml.cs index 85d8a6113f6b..3ba2fc3a40e3 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/PfxAsn.xml.cs +++ b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.xml.cs @@ -8,14 +8,14 @@ using System.Security.Cryptography; using System.Security.Cryptography.Asn1; -namespace System.Security.Cryptography.Pkcs.Asn1 +namespace System.Security.Cryptography.Asn1.Pkcs12 { [StructLayout(LayoutKind.Sequential)] internal partial struct PfxAsn { internal byte Version; - internal System.Security.Cryptography.Pkcs.Asn1.ContentInfoAsn AuthSafe; - internal System.Security.Cryptography.Pkcs.Asn1.MacData? MacData; + internal System.Security.Cryptography.Asn1.Pkcs7.ContentInfoAsn AuthSafe; + internal System.Security.Cryptography.Asn1.Pkcs12.MacData? MacData; internal void Encode(AsnWriter writer) { @@ -73,12 +73,12 @@ internal static void Decode(AsnReader reader, Asn1Tag expectedTag, out PfxAsn de sequenceReader.ThrowIfNotEmpty(); } - System.Security.Cryptography.Pkcs.Asn1.ContentInfoAsn.Decode(sequenceReader, out decoded.AuthSafe); + System.Security.Cryptography.Asn1.Pkcs7.ContentInfoAsn.Decode(sequenceReader, out decoded.AuthSafe); if (sequenceReader.HasData && sequenceReader.PeekTag().HasSameClassAndValue(Asn1Tag.Sequence)) { - System.Security.Cryptography.Pkcs.Asn1.MacData tmpMacData; - System.Security.Cryptography.Pkcs.Asn1.MacData.Decode(sequenceReader, out tmpMacData); + System.Security.Cryptography.Asn1.Pkcs12.MacData tmpMacData; + System.Security.Cryptography.Asn1.Pkcs12.MacData.Decode(sequenceReader, out tmpMacData); decoded.MacData = tmpMacData; } diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/SafeBagAsn.xml b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/SafeBagAsn.xml similarity index 100% rename from src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/SafeBagAsn.xml rename to src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/SafeBagAsn.xml diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/SafeBagAsn.xml.cs b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/SafeBagAsn.xml.cs similarity index 100% rename from src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/SafeBagAsn.xml.cs rename to src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/SafeBagAsn.xml.cs diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/ContentInfoAsn.xml b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs7/ContentInfoAsn.xml similarity index 89% rename from src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/ContentInfoAsn.xml rename to src/Common/src/System/Security/Cryptography/Asn1/Pkcs7/ContentInfoAsn.xml index ebe39fada695..ff645a31166e 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/ContentInfoAsn.xml +++ b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs7/ContentInfoAsn.xml @@ -2,7 +2,7 @@ + namespace="System.Security.Cryptography.Asn1.Pkcs7"> + + Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml + @@ -556,10 +563,6 @@ System\Security\Cryptography\Pkcs\Asn1\CertificateChoiceAsn.xml - - - System\Security\Cryptography\Pkcs\Asn1\ContentInfoAsn.xml - System\Security\Cryptography\Pkcs\Asn1\EncapsulatedContentInfoAsn.xml @@ -649,36 +652,51 @@ Common\System\Security\Cryptography\KeyFormatHelper.cs + + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml + Common\System\Security\Cryptography\PasswordBasedEncryption.cs Common\System\Security\Cryptography\Pkcs12Kdf.cs - - - System\Security\Cryptography\Pkcs\Asn1\CertBagAsn.xml - - - - System\Security\Cryptography\Pkcs\Asn1\DigestInfoAsn.xml - System\Security\Cryptography\Pkcs\Asn1\EncryptedDataAsn.xml - - - System\Security\Cryptography\Pkcs\Asn1\MacData.xml - - - - System\Security\Cryptography\Pkcs\Asn1\PfxAsn.xml - - - - System\Security\Cryptography\Pkcs\Asn1\SafeBagAsn.xml - System\Security\Cryptography\Pkcs\Asn1\SecretBagAsn.xml diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Builder.cs b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Builder.cs index de795116e9d1..6cc9858d267f 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Builder.cs +++ b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Builder.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Asn1.Pkcs7; using System.Security.Cryptography.Pkcs.Asn1; using Internal.Cryptography; diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Info.cs b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Info.cs index 1455b5260b11..c69240780416 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Info.cs +++ b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Info.cs @@ -6,6 +6,8 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Asn1.Pkcs12; +using System.Security.Cryptography.Asn1.Pkcs7; using System.Security.Cryptography.Pkcs.Asn1; using Internal.Cryptography; diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs index cb90430d4d7b..0f50424ec6a5 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs +++ b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Asn1.Pkcs7; using System.Security.Cryptography.Pkcs.Asn1; using System.Security.Cryptography.X509Certificates; using Internal.Cryptography; diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Rfc3161TimestampToken.cs b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Rfc3161TimestampToken.cs index c224915fc827..d6c820bcf589 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Rfc3161TimestampToken.cs +++ b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Rfc3161TimestampToken.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Asn1.Pkcs7; using System.Security.Cryptography.Pkcs.Asn1; using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.Xml; diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs index 4942f526ce64..52c633a52267 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs +++ b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Asn1.Pkcs7; using System.Security.Cryptography.Pkcs.Asn1; using System.Security.Cryptography.X509Certificates; using Internal.Cryptography; From 31d5a9c7ef0064ff793ec4f0775ca1ca303580da Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Mon, 26 Aug 2019 09:02:03 -0700 Subject: [PATCH 02/61] Move VerifyMac logic to PfxAsn.manual.cs --- .../Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs | 84 +++++++++++++++++++ .../System.Security.Cryptography.Pkcs.csproj | 4 + .../Security/Cryptography/Pkcs/Pkcs12Info.cs | 68 +-------------- 3 files changed, 89 insertions(+), 67 deletions(-) create mode 100644 src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs diff --git a/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs new file mode 100644 index 000000000000..0856a0f314a0 --- /dev/null +++ b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Security.Cryptography.Pkcs; + +namespace System.Security.Cryptography.Asn1.Pkcs12 +{ + internal partial struct PfxAsn + { + internal bool VerifyMac( + ReadOnlySpan macPassword, + ReadOnlySpan authSafeContents) + { + Debug.Assert(MacData.HasValue); + + HashAlgorithmName hashAlgorithm; + int expectedOutputSize; + + string algorithmValue = MacData.Value.Mac.DigestAlgorithm.Algorithm.Value; + + switch (algorithmValue) + { + case Oids.Md5: + expectedOutputSize = 128 >> 3; + hashAlgorithm = HashAlgorithmName.MD5; + break; + case Oids.Sha1: + expectedOutputSize = 160 >> 3; + hashAlgorithm = HashAlgorithmName.SHA1; + break; + case Oids.Sha256: + expectedOutputSize = 256 >> 3; + hashAlgorithm = HashAlgorithmName.SHA256; + break; + case Oids.Sha384: + expectedOutputSize = 384 >> 3; + hashAlgorithm = HashAlgorithmName.SHA384; + break; + case Oids.Sha512: + expectedOutputSize = 512 >> 3; + hashAlgorithm = HashAlgorithmName.SHA512; + break; + default: + throw new CryptographicException( + SR.Format(SR.Cryptography_UnknownHashAlgorithm, algorithmValue)); + } + + if (MacData.Value.Mac.Digest.Length != expectedOutputSize) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + // Cannot use the ArrayPool or stackalloc here because CreateHMAC needs a properly bounded array. + byte[] derived = new byte[expectedOutputSize]; + + int iterationCount = + PasswordBasedEncryption.NormalizeIterationCount(MacData.Value.IterationCount); + + Pkcs12Kdf.DeriveMacKey( + macPassword, + hashAlgorithm, + iterationCount, + MacData.Value.MacSalt.Span, + derived); + + using (IncrementalHash hmac = IncrementalHash.CreateHMAC(hashAlgorithm, derived)) + { + hmac.AppendData(authSafeContents); + + if (!hmac.TryGetHashAndReset(derived, out int bytesWritten) || bytesWritten != expectedOutputSize) + { + Debug.Fail($"TryGetHashAndReset wrote {bytesWritten} bytes when {expectedOutputSize} was expected"); + throw new CryptographicException(); + } + + return CryptographicOperations.FixedTimeEquals( + derived, + MacData.Value.Mac.Digest.Span); + } + } + } +} diff --git a/src/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj b/src/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj index 889d57ada0b0..237216ca645b 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj +++ b/src/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj @@ -676,6 +676,10 @@ Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.manual.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml.cs Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Info.cs b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Info.cs index c69240780416..0422a6f9bd82 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Info.cs +++ b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Info.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Security.Cryptography.Asn1; using System.Security.Cryptography.Asn1.Pkcs12; using System.Security.Cryptography.Asn1.Pkcs7; @@ -42,72 +41,7 @@ public bool VerifyMac(ReadOnlySpan password) IntegrityMode)); } - Debug.Assert(_decoded.MacData.HasValue); - - HashAlgorithmName hashAlgorithm; - int expectedOutputSize; - - string algorithmValue = _decoded.MacData.Value.Mac.DigestAlgorithm.Algorithm.Value; - - switch (algorithmValue) - { - case Oids.Md5: - expectedOutputSize = 128 >> 3; - hashAlgorithm = HashAlgorithmName.MD5; - break; - case Oids.Sha1: - expectedOutputSize = 160 >> 3; - hashAlgorithm = HashAlgorithmName.SHA1; - break; - case Oids.Sha256: - expectedOutputSize = 256 >> 3; - hashAlgorithm = HashAlgorithmName.SHA256; - break; - case Oids.Sha384: - expectedOutputSize = 384 >> 3; - hashAlgorithm = HashAlgorithmName.SHA384; - break; - case Oids.Sha512: - expectedOutputSize = 512 >> 3; - hashAlgorithm = HashAlgorithmName.SHA512; - break; - default: - throw new CryptographicException( - SR.Format(SR.Cryptography_UnknownHashAlgorithm, algorithmValue)); - } - - if (_decoded.MacData.Value.Mac.Digest.Length != expectedOutputSize) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); - } - - // Cannot use the ArrayPool or stackalloc here because CreateHMAC needs a properly bounded array. - byte[] derived = new byte[expectedOutputSize]; - - int iterationCount = - PasswordBasedEncryption.NormalizeIterationCount(_decoded.MacData.Value.IterationCount); - - Pkcs12Kdf.DeriveMacKey( - password, - hashAlgorithm, - iterationCount, - _decoded.MacData.Value.MacSalt.Span, - derived); - - using (IncrementalHash hmac = IncrementalHash.CreateHMAC(hashAlgorithm, derived)) - { - hmac.AppendData(_authSafeContents.Span); - - if (!hmac.TryGetHashAndReset(derived, out int bytesWritten) || bytesWritten != expectedOutputSize) - { - Debug.Fail($"TryGetHashAndReset wrote {bytesWritten} bytes when {expectedOutputSize} was expected"); - throw new CryptographicException(); - } - - return CryptographicOperations.FixedTimeEquals( - derived, - _decoded.MacData.Value.Mac.Digest.Span); - } + return _decoded.VerifyMac(password, _authSafeContents.Span); } public static Pkcs12Info Decode( From ad7381896df2e337fbf4630a99ee8e18b9da228e Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Tue, 27 Aug 2019 10:21:43 -0700 Subject: [PATCH 03/61] Move EncryptedDataAsn to src/Common --- .../Asn1/Pkcs7}/EncryptedContentInfoAsn.xml | 2 +- .../Pkcs7}/EncryptedContentInfoAsn.xml.cs | 2 +- .../Asn1/Pkcs7}/EncryptedDataAsn.xml | 4 ++-- .../Asn1/Pkcs7}/EncryptedDataAsn.xml.cs | 6 ++--- .../System.Security.Cryptography.Pkcs.csproj | 22 ++++++++++++------- .../Pkcs/Asn1/EnvelopedDataAsn.xml | 2 +- .../Pkcs/Asn1/EnvelopedDataAsn.xml.cs | 4 ++-- 7 files changed, 24 insertions(+), 18 deletions(-) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs7}/EncryptedContentInfoAsn.xml (93%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs7}/EncryptedContentInfoAsn.xml.cs (98%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs7}/EncryptedDataAsn.xml (85%) rename src/{System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1 => Common/src/System/Security/Cryptography/Asn1/Pkcs7}/EncryptedDataAsn.xml.cs (92%) diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EncryptedContentInfoAsn.xml b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs7/EncryptedContentInfoAsn.xml similarity index 93% rename from src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EncryptedContentInfoAsn.xml rename to src/Common/src/System/Security/Cryptography/Asn1/Pkcs7/EncryptedContentInfoAsn.xml index a3ec51e60577..479357ccc1c9 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EncryptedContentInfoAsn.xml +++ b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs7/EncryptedContentInfoAsn.xml @@ -2,7 +2,7 @@ + namespace="System.Security.Cryptography.Asn1.Pkcs7"> - + diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EncryptedDataAsn.xml.cs b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs7/EncryptedDataAsn.xml.cs similarity index 92% rename from src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EncryptedDataAsn.xml.cs rename to src/Common/src/System/Security/Cryptography/Asn1/Pkcs7/EncryptedDataAsn.xml.cs index e200d8882efc..1186c71f0da8 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EncryptedDataAsn.xml.cs +++ b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs7/EncryptedDataAsn.xml.cs @@ -9,13 +9,13 @@ using System.Security.Cryptography; using System.Security.Cryptography.Asn1; -namespace System.Security.Cryptography.Pkcs.Asn1 +namespace System.Security.Cryptography.Asn1.Pkcs7 { [StructLayout(LayoutKind.Sequential)] internal partial struct EncryptedDataAsn { internal int Version; - internal System.Security.Cryptography.Pkcs.Asn1.EncryptedContentInfoAsn EncryptedContentInfo; + internal System.Security.Cryptography.Asn1.Pkcs7.EncryptedContentInfoAsn EncryptedContentInfo; internal System.Security.Cryptography.Asn1.AttributeAsn[] UnprotectedAttributes; internal void Encode(AsnWriter writer) @@ -82,7 +82,7 @@ internal static void Decode(AsnReader reader, Asn1Tag expectedTag, out Encrypted sequenceReader.ThrowIfNotEmpty(); } - System.Security.Cryptography.Pkcs.Asn1.EncryptedContentInfoAsn.Decode(sequenceReader, out decoded.EncryptedContentInfo); + System.Security.Cryptography.Asn1.Pkcs7.EncryptedContentInfoAsn.Decode(sequenceReader, out decoded.EncryptedContentInfo); if (sequenceReader.HasData && sequenceReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 1))) { diff --git a/src/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj b/src/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj index 237216ca645b..f04204ac5815 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj +++ b/src/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj @@ -66,6 +66,13 @@ Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml.cs Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml + @@ -75,10 +82,6 @@ - - - System\Security\Cryptography\Pkcs\Asn1\EncryptedContentInfoAsn.xml - System\Security\Cryptography\Pkcs\Asn1\EnvelopedDataAsn.xml @@ -691,16 +694,19 @@ Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml.cs Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml + Common\System\Security\Cryptography\PasswordBasedEncryption.cs Common\System\Security\Cryptography\Pkcs12Kdf.cs - - - System\Security\Cryptography\Pkcs\Asn1\EncryptedDataAsn.xml - System\Security\Cryptography\Pkcs\Asn1\SecretBagAsn.xml diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EnvelopedDataAsn.xml b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EnvelopedDataAsn.xml index 905ecb117879..54508f8c1ea4 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EnvelopedDataAsn.xml +++ b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EnvelopedDataAsn.xml @@ -22,7 +22,7 @@ - + diff --git a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EnvelopedDataAsn.xml.cs b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EnvelopedDataAsn.xml.cs index c3d4f0ba3068..e1dcb2c685c0 100644 --- a/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EnvelopedDataAsn.xml.cs +++ b/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/EnvelopedDataAsn.xml.cs @@ -17,7 +17,7 @@ internal partial struct EnvelopedDataAsn internal int Version; internal System.Security.Cryptography.Pkcs.Asn1.OriginatorInfoAsn? OriginatorInfo; internal System.Security.Cryptography.Pkcs.Asn1.RecipientInfoAsn[] RecipientInfos; - internal System.Security.Cryptography.Pkcs.Asn1.EncryptedContentInfoAsn EncryptedContentInfo; + internal System.Security.Cryptography.Asn1.Pkcs7.EncryptedContentInfoAsn EncryptedContentInfo; internal System.Security.Cryptography.Asn1.AttributeAsn[] UnprotectedAttributes; internal void Encode(AsnWriter writer) @@ -123,7 +123,7 @@ internal static void Decode(AsnReader reader, Asn1Tag expectedTag, out Enveloped decoded.RecipientInfos = tmpList.ToArray(); } - System.Security.Cryptography.Pkcs.Asn1.EncryptedContentInfoAsn.Decode(sequenceReader, out decoded.EncryptedContentInfo); + System.Security.Cryptography.Asn1.Pkcs7.EncryptedContentInfoAsn.Decode(sequenceReader, out decoded.EncryptedContentInfo); if (sequenceReader.HasData && sequenceReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 1))) { From 6ba2d9dcf346c2c0fa958c9f0c871ce8c9c03f78 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Mon, 16 Sep 2019 21:28:16 -0700 Subject: [PATCH 04/61] Loader works (all tests pass) Have some todos, and Windows won't compile... --- .../Interop.X509.cs | 2 +- .../Cryptography/Asn1/SpecifiedECDomain.xml | 4 +- .../Asn1/SpecifiedECDomain.xml.cs | 14 +- .../Cryptography/EccKeyFormatHelper.cs | 63 +++ .../Security/Cryptography/KeyBlobHelpers.cs | 35 ++ .../src/System/Security/Cryptography/Oids.cs | 1 + .../src/Internal/Cryptography/Helpers.cs | 32 +- .../Pal.Unix/OpenSslPkcs12Reader.cs | 531 +++++++++++++++--- .../Pal.Unix/OpenSslX509CertificateReader.cs | 55 +- .../Pal.Unix/OpenSslX509Encoder.cs | 19 +- .../Cryptography/Pal.Unix/StorePal.cs | 8 +- .../SafeHandles/SafePasswordHandle.Unix.cs | 34 +- ...urity.Cryptography.X509Certificates.csproj | 179 ++++-- .../X509Certificates/X509Certificate.cs | 4 +- .../tests/CtorTests.cs | 6 +- 15 files changed, 798 insertions(+), 189 deletions(-) diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509.cs index 8ffc70af6a54..1a871f788d51 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509.cs @@ -21,7 +21,7 @@ internal static partial class Crypto internal static extern SafeX509CrlHandle DecodeX509Crl(byte[] buf, int len); [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DecodeX509")] - internal static extern SafeX509Handle DecodeX509(byte[] buf, int len); + internal static extern SafeX509Handle DecodeX509(ref byte buf, int len); [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetX509DerSize")] internal static extern int GetX509DerSize(SafeX509Handle x); diff --git a/src/Common/src/System/Security/Cryptography/Asn1/SpecifiedECDomain.xml b/src/Common/src/System/Security/Cryptography/Asn1/SpecifiedECDomain.xml index 779f0fc6887f..e5b4252894b1 100644 --- a/src/Common/src/System/Security/Cryptography/Asn1/SpecifiedECDomain.xml +++ b/src/Common/src/System/Security/Cryptography/Asn1/SpecifiedECDomain.xml @@ -27,5 +27,5 @@ - - \ No newline at end of file + + diff --git a/src/Common/src/System/Security/Cryptography/Asn1/SpecifiedECDomain.xml.cs b/src/Common/src/System/Security/Cryptography/Asn1/SpecifiedECDomain.xml.cs index 25eed004269e..255b065de740 100644 --- a/src/Common/src/System/Security/Cryptography/Asn1/SpecifiedECDomain.xml.cs +++ b/src/Common/src/System/Security/Cryptography/Asn1/SpecifiedECDomain.xml.cs @@ -41,7 +41,12 @@ internal void Encode(AsnWriter writer, Asn1Tag tag) writer.WriteInteger(Cofactor.Value.Span); } - writer.WriteObjectIdentifier(Hash); + + if (Hash != null) + { + writer.WriteObjectIdentifier(Hash); + } + writer.PopSequence(tag); } @@ -100,7 +105,12 @@ internal static void Decode(AsnReader reader, Asn1Tag expectedTag, out Specified decoded.Cofactor = sequenceReader.ReadIntegerBytes(); } - decoded.Hash = sequenceReader.ReadObjectIdentifier(); + + if (sequenceReader.HasData && sequenceReader.PeekTag().HasSameClassAndValue(Asn1Tag.ObjectIdentifier)) + { + decoded.Hash = sequenceReader.ReadObjectIdentifier(); + } + sequenceReader.ThrowIfNotEmpty(); } diff --git a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index 0ec6169843e6..2727a4673722 100644 --- a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -231,6 +231,11 @@ private static void ValidateParameters(ECDomainParameters? keyParameters, in Alg private static ECCurve GetCurve(in ECDomainParameters domainParameters) { + if (domainParameters.Specified.HasValue) + { + return GetSpecifiedECCurve(domainParameters.Specified.Value); + } + if (domainParameters.Named == null) { throw new CryptographicException(SR.Cryptography_ECC_NamedCurvesOnly); @@ -254,6 +259,64 @@ private static ECCurve GetCurve(in ECDomainParameters domainParameters) return ECCurve.CreateFromOid(curveOid); } + private static ECCurve GetSpecifiedECCurve(in SpecifiedECDomain specifiedParameters) + { + // sec1-v2 C.3: + // + // Versions 1, 2, and 3 are defined. + // 1 is just data, 2 and 3 mean that a seed is required (with different reasons for why, + // but they're human-reasons, not technical ones). + if (specifiedParameters.Version < 1 || specifiedParameters.Version > 3) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + if (specifiedParameters.Version > 1 && !specifiedParameters.Curve.Seed.HasValue) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + if (specifiedParameters.FieldID.FieldType != Oids.EcPrimeField) + { + throw new CryptographicException("TODO"); + } + + AsnReader reader = new AsnReader(specifiedParameters.FieldID.Parameters, AsnEncodingRules.BER); + ReadOnlySpan primeValue = reader.ReadIntegerBytes().Span; + + if (primeValue[0] == 0) + { + primeValue = primeValue.Slice(1); + } + + ECCurve curve = new ECCurve + { + CurveType = ECCurve.ECCurveType.PrimeShortWeierstrass, + Prime = primeValue.ToArray(), + A = specifiedParameters.Curve.A.ToUnsignedIntegerBytes(primeValue.Length), + B = specifiedParameters.Curve.B.ToUnsignedIntegerBytes(primeValue.Length), + Order = specifiedParameters.Order.ToUnsignedIntegerBytes(), + }; + + ReadOnlySpan baseSpan = specifiedParameters.Base.Span; + + // We only understand the uncompressed point encoding, but that's almost always what's used. + if (baseSpan[0] != 0x04 || baseSpan.Length != 2 * primeValue.Length + 1) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + curve.G.X = baseSpan.Slice(1, primeValue.Length).ToArray(); + curve.G.Y = baseSpan.Slice(1 + primeValue.Length).ToArray(); + + if (specifiedParameters.Cofactor.HasValue) + { + curve.Cofactor = specifiedParameters.Cofactor.Value.ToUnsignedIntegerBytes(); + } + + return curve; + } + internal static AsnWriter WriteSubjectPublicKeyInfo(in ECParameters ecParameters) { ecParameters.Validate(); diff --git a/src/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs b/src/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs index 23ad0dc45364..fc46004d16db 100644 --- a/src/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs +++ b/src/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs @@ -10,6 +10,41 @@ namespace System.Security.Cryptography { internal static class KeyBlobHelpers { + internal static byte[] ToUnsignedIntegerBytes(this ReadOnlyMemory memory, int length) + { + if (memory.Length == length) + { + return memory.ToArray(); + } + + ReadOnlySpan span = memory.Span; + + if (memory.Length == length + 1) + { + if (span[0] == 0) + { + return span.Slice(1).ToArray(); + } + } + + // Otherwise too big? throw. + byte[] target = new byte[length]; + memory.Span.CopyTo(target.AsSpan(target.Length - length)); + return target; + } + + internal static byte[] ToUnsignedIntegerBytes(this ReadOnlyMemory memory) + { + ReadOnlySpan span = memory.Span; + + if (span.Length > 1 && span[0] == 0) + { + return span.Slice(1).ToArray(); + } + + return span.ToArray(); + } + internal static byte[] ExportKeyParameter(this BigInteger value, int length) { byte[] target = new byte[length]; diff --git a/src/Common/src/System/Security/Cryptography/Oids.cs b/src/Common/src/System/Security/Cryptography/Oids.cs index 1385dee2df94..d11c45d3510c 100644 --- a/src/Common/src/System/Security/Cryptography/Oids.cs +++ b/src/Common/src/System/Security/Cryptography/Oids.cs @@ -75,6 +75,7 @@ internal static partial class Oids // ECDSA CMS uses the combined signature+digest OID // https://tools.ietf.org/html/rfc5753#section-2.1.1 + internal const string EcPrimeField = "1.2.840.10045.1.1"; internal const string EcPublicKey = "1.2.840.10045.2.1"; internal const string ECDsaWithSha1 = "1.2.840.10045.4.1"; internal const string ECDsaWithSha256 = "1.2.840.10045.4.3.2"; diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs index 09f5ebfb78a7..00057d977e59 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs @@ -21,7 +21,7 @@ public static char[] ToHexArrayUpper(this byte[] bytes) return chars; } - private static void ToHexArrayUpper(byte[] bytes, Span chars) + private static void ToHexArrayUpper(ReadOnlySpan bytes, Span chars) { Debug.Assert(chars.Length >= bytes.Length * 2); int i = 0; @@ -34,7 +34,10 @@ private static void ToHexArrayUpper(byte[] bytes, Span chars) // Encode a byte array as an upper case hex string. public static string ToHexStringUpper(this byte[] bytes) => - string.Create(bytes.Length * 2, bytes, (chars, src) => ToHexArrayUpper(src, chars)); + ToHexStringUpper(bytes.AsMemory()); + + public static string ToHexStringUpper(this ReadOnlyMemory bytes) => + string.Create(bytes.Length * 2, bytes, (chars, src) => ToHexArrayUpper(src.Span, chars)); // Decode a hex string-encoded byte array passed to various X509 crypto api. // The parsing rules are overly forgiving but for compat reasons, they cannot be tightened. @@ -208,6 +211,31 @@ public static void ValidateDer(ReadOnlyMemory encodedValue) reader.ReadEncodedValue(); } } + + public static ReadOnlyMemory DecodeOctetStringAsMemory(ReadOnlyMemory encodedOctetString) + { + AsnReader reader = new AsnReader(encodedOctetString, AsnEncodingRules.BER); + + if (reader.PeekEncodedValue().Length != encodedOctetString.Length) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + if (reader.TryReadPrimitiveOctetStringBytes(out ReadOnlyMemory primitiveContents)) + { + return primitiveContents; + } + + byte[] tooBig = new byte[encodedOctetString.Length]; + + if (reader.TryCopyOctetStringBytes(tooBig, out int bytesWritten)) + { + return tooBig.AsMemory(0, bytesWritten); + } + + Debug.Fail("TryCopyOctetStringBytes failed with an over-allocated array"); + throw new CryptographicException(); + } } internal static class DictionaryStringHelper diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs index b754979071d6..81a2d0f5778b 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs @@ -4,22 +4,34 @@ using Microsoft.Win32.SafeHandles; using System; -using System.Collections.Generic; -using System.Security.Cryptography; +using System.Buffers; +using System.Diagnostics; using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Asn1.Pkcs12; +using System.Security.Cryptography.Asn1.Pkcs7; +using System.Security.Cryptography.Pkcs.Asn1; +using System.Threading; namespace Internal.Cryptography.Pal { internal sealed class OpenSslPkcs12Reader : IDisposable { - private readonly SafePkcs12Handle _pkcs12Handle; - private SafeEvpPKeyHandle _evpPkeyHandle; - private SafeX509Handle _x509Handle; - private SafeX509StackHandle _caStackHandle; + private const string DecryptedSentinel = nameof(OpenSslPkcs12Reader); + + private PfxAsn _pfxAsn; + private ContentInfoAsn[] _safeContentsValues; + private OpenSslX509CertificateReader[] _certs; - private OpenSslPkcs12Reader(SafePkcs12Handle pkcs12Handle) + private OpenSslPkcs12Reader(AsnReader reader) { - _pkcs12Handle = pkcs12Handle; + PfxAsn.Decode(reader, out _pfxAsn); + + if (_pfxAsn.AuthSafe.ContentType != Oids.Pkcs7Data) + { + throw new CryptographicException("Unsupported PFX."); + } } public static bool TryRead(byte[] data, out OpenSslPkcs12Reader pkcs12Reader) => @@ -28,141 +40,506 @@ public static bool TryRead(byte[] data, out OpenSslPkcs12Reader pkcs12Reader) => public static bool TryRead(byte[] data, out OpenSslPkcs12Reader pkcs12Reader, out Exception openSslException) => TryRead(data, out pkcs12Reader, out openSslException, captureException: true); - public static bool TryRead(SafeBioHandle fileBio, out OpenSslPkcs12Reader pkcs12Reader) => - TryRead(fileBio, out pkcs12Reader, out _, captureException: false); - public static bool TryRead(SafeBioHandle fileBio, out OpenSslPkcs12Reader pkcs12Reader, out Exception openSslException) => TryRead(fileBio, out pkcs12Reader, out openSslException, captureException: true); public void Dispose() { - if (_caStackHandle != null) + ContentInfoAsn[] rentedContents = Interlocked.Exchange(ref _safeContentsValues, null); + OpenSslX509CertificateReader[] rentedCerts = Interlocked.Exchange(ref _certs, null); + + if (rentedContents != null) { - _caStackHandle.Dispose(); - _caStackHandle = null; + for (int i = 0; i < rentedContents.Length; i++) + { + string contentType = rentedContents[0].ContentType; + + if (contentType == null) + { + break; + } + + if (contentType == DecryptedSentinel) + { + ReadOnlyMemory content = rentedContents[0].Content; + + if (!MemoryMarshal.TryGetArray(content, out ArraySegment segment)) + { + Debug.Fail("Couldn't unpack decrypted buffer."); + } + + CryptoPool.Return(segment.Array, segment.Count); + rentedContents[0].Content = default; + } + } + + ArrayPool.Shared.Return(rentedContents, clearArray: true); } - if (_x509Handle != null) + if (rentedCerts != null) { - _x509Handle.Dispose(); - _x509Handle = null; + ArrayPool.Shared.Return(rentedCerts, clearArray: true); } + } + + public unsafe void Decrypt(SafePasswordHandle password) + { + ReadOnlyMemory authSafeSpan = + Helpers.DecodeOctetStringAsMemory(_pfxAsn.AuthSafe.Content); - if (_evpPkeyHandle != null) + bool hasRef = false; + password.DangerousAddRef(ref hasRef); + + try { - _evpPkeyHandle.Dispose(); - _evpPkeyHandle = null; - } + ReadOnlySpan passwordChars = + new ReadOnlySpan((char*)password.DangerousGetHandle(), password.Length); - if (_pkcs12Handle != null) + if (_pfxAsn.MacData.HasValue) + { + VerifyAndDecrypt(passwordChars, authSafeSpan); + } + else + { + Decrypt(passwordChars, authSafeSpan); + } + } + catch (Exception e) + { + throw new CryptographicException("Bad password.", e); + } + finally { - _pkcs12Handle.Dispose(); + password.DangerousRelease(); } } - public void Decrypt(SafePasswordHandle password) + private void VerifyAndDecrypt(ReadOnlySpan password, ReadOnlyMemory authSafeContents) { - bool parsed = Interop.Crypto.Pkcs12Parse( - _pkcs12Handle, - password, - out _evpPkeyHandle, - out _x509Handle, - out _caStackHandle); + Debug.Assert(_pfxAsn.MacData.HasValue); + ReadOnlySpan authSafeSpan = authSafeContents.Span; - if (!parsed) + if (password.Length == 0) { - throw Interop.Crypto.CreateOpenSslCryptographicException(); + if (_pfxAsn.VerifyMac("", authSafeSpan)) + { + Decrypt("", authSafeContents); + return; + } + + if (_pfxAsn.VerifyMac(default, authSafeSpan)) + { + Decrypt(default, authSafeContents); + return; + } + } + else if (_pfxAsn.VerifyMac(password, authSafeSpan)) + { + Decrypt(password, authSafeContents); + return; } + + throw new CryptographicException("Password did not verify MAC."); } - public List ReadCertificates() + private void Decrypt(ReadOnlySpan password, ReadOnlyMemory authSafeContents) { - var certs = new List(); + if (_safeContentsValues == null) + { + // The expected number of ContentInfoAsns to read is 2, one encrypted (contains certs), + // and one plain (contains encrypted keys) + ContentInfoAsn[] rented = ArrayPool.Shared.Rent(10); + + AsnReader outer = new AsnReader(authSafeContents, AsnEncodingRules.BER); + AsnReader reader = outer.ReadSequence(); + outer.ThrowIfNotEmpty(); + int i = 0; + + while (reader.HasData) + { + GrowIfNeeded(ref rented, i); + ContentInfoAsn.Decode(reader, out rented[i]); + i++; + } + + rented.AsSpan(i).Clear(); + _safeContentsValues = rented; + } + + // The average PFX contains one cert, and one key. + // The next most common PFX contains 3 certs, and one key. + // + // Nothing requires that there be fewer keys than certs, + // but it's sort of nonsensical when loading this way. + CertBagAsn[] certBags = ArrayPool.Shared.Rent(10); + AttributeAsn[][] certBagAttrs = ArrayPool.Shared.Rent(10); + SafeBagAsn[] keyBags = ArrayPool.Shared.Rent(10); + AsymmetricAlgorithm[] keys = null; + int certBagIdx = 0; + int keyBagIdx = 0; - if (_caStackHandle != null && !_caStackHandle.IsInvalid) + try { - int caCertCount = Interop.Crypto.GetX509StackFieldCount(_caStackHandle); + for (int i = 0; i < _safeContentsValues.Length; i++) + { + string contentType = _safeContentsValues[i].ContentType; + bool process = false; + + if (contentType == null) + { + break; + } + + // Should enveloped throw here? + if (contentType == Oids.Pkcs7Data) + { + process = true; + } + else if (contentType == Oids.Pkcs7Encrypted) + { + DecryptSafeContents(password, ref _safeContentsValues[i]); + process = true; + } - for (int i = 0; i < caCertCount; i++) + if (process) + { + ProcessSafeContents( + _safeContentsValues[i], + ref certBags, + ref certBagAttrs, + ref certBagIdx, + ref keyBags, + ref keyBagIdx); + } + } + + _certs = ArrayPool.Shared.Rent(certBagIdx); + _certs.AsSpan().Clear(); + + keys = ArrayPool.Shared.Rent(keyBagIdx); + keys.AsSpan().Clear(); + + // Windows Compat: Load all keys before matching. + // Unloadable key? Unloadable PFX. + for (int i = keyBagIdx - 1; i >= 0; i--) { - IntPtr certPtr = Interop.Crypto.GetX509StackField(_caStackHandle, i); + keys[i] = LoadKey(keyBags[i], password); + } + + int certsCount = certBagIdx; - if (certPtr != IntPtr.Zero) + for (certBagIdx--; certBagIdx >= 0; certBagIdx--) + { + int matchingKeyIdx = -1; + + foreach (AttributeAsn attr in certBagAttrs[certBagIdx] ?? Array.Empty()) + { + if (attr.AttrType.Value == Oids.LocalKeyId && attr.AttrValues.Length > 0) + { + matchingKeyIdx = FindMatchingKey( + keyBags, + keyBagIdx, + Helpers.DecodeOctetStringAsMemory(attr.AttrValues[0]).Span); + + // Only try the first one. + break; + } + } + + ReadOnlyMemory x509Data = + Helpers.DecodeOctetStringAsMemory(certBags[certBagIdx].CertValue); + + if (!OpenSslX509CertificateReader.TryReadX509Der(x509Data.Span, out ICertificatePal pal)) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + _certs[certBagIdx] = (OpenSslX509CertificateReader)pal; + + if (matchingKeyIdx != -1) { - // The STACK_OF(X509) still needs to be cleaned up, so upref the handle out of it. - certs.Add(new OpenSslX509CertificateReader(Interop.Crypto.X509UpRef(certPtr))); + _certs[certBagIdx].SetPrivateKey(GetPrivateKey(keys[matchingKeyIdx])); } } + + Array.Reverse(_certs, 0, certsCount); } + catch + { + if (_certs != null) + { + foreach (OpenSslX509CertificateReader cert in _certs) + { + cert?.Dispose(); + } + } - if (_x509Handle != null && !_x509Handle.IsInvalid) + throw; + } + finally { - // The certificate and (if applicable) private key handles will be given over - // to the OpenSslX509CertificateReader, and the fields here are thus nulled out to - // prevent double-Dispose. - OpenSslX509CertificateReader reader = new OpenSslX509CertificateReader(_x509Handle); - _x509Handle = null; + ArrayPool.Shared.Return(certBags, clearArray: true); + ArrayPool.Shared.Return(certBagAttrs, clearArray: true); + ArrayPool.Shared.Return(keyBags, clearArray: true); + } + } - if (_evpPkeyHandle != null && !_evpPkeyHandle.IsInvalid) + private static int FindMatchingKey( + SafeBagAsn[] keyBags, + int keyBagCount, + ReadOnlySpan localKeyId) + { + for (int i = 0; i < keyBagCount; i++) + { + foreach (AttributeAsn attr in keyBags[i].BagAttributes) { - reader.SetPrivateKey(_evpPkeyHandle); - _evpPkeyHandle = null; + if (attr.AttrType.Value == Oids.LocalKeyId && attr.AttrValues.Length > 0) + { + ReadOnlyMemory curKeyId = + Helpers.DecodeOctetStringAsMemory(attr.AttrValues[0]); + + if (curKeyId.Span.SequenceEqual(localKeyId)) + { + return i; + } + } } + } + + return -1; + } + + private static void DecryptSafeContents( + ReadOnlySpan password, + ref ContentInfoAsn safeContentsAsn) + { + EncryptedDataAsn encryptedData = + EncryptedDataAsn.Decode(safeContentsAsn.Content, AsnEncodingRules.BER); + + // https://tools.ietf.org/html/rfc5652#section-8 + if (encryptedData.Version != 0 && encryptedData.Version != 2) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + // Since the contents are supposed to be the BER-encoding of an instance of + // SafeContents (https://tools.ietf.org/html/rfc7292#section-4.1) that implies the + // content type is simply "data", and that content is present. + if (encryptedData.EncryptedContentInfo.ContentType != Oids.Pkcs7Data) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } - certs.Add(reader); + if (!encryptedData.EncryptedContentInfo.EncryptedContent.HasValue) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } - return certs; + int encryptedValueLength = encryptedData.EncryptedContentInfo.EncryptedContent.Value.Length; + + byte[] destination = CryptoPool.Rent(encryptedValueLength); + + int written = PasswordBasedEncryption.Decrypt( + encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm, + password, + default, + encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span, + destination); + + safeContentsAsn.Content = destination.AsMemory(0, written); + safeContentsAsn.ContentType = DecryptedSentinel; } - private static bool TryRead(byte[] data, out OpenSslPkcs12Reader pkcs12Reader, out Exception openSslException, bool captureException) + private static void ProcessSafeContents( + in ContentInfoAsn safeContentsAsn, + ref CertBagAsn[] certBags, + ref AttributeAsn[][] certBagAttrs, + ref int certBagIdx, + ref SafeBagAsn[] keyBags, + ref int keyBagIdx) { - SafePkcs12Handle handle = Interop.Crypto.DecodePkcs12(data, data.Length); - openSslException = null; + ReadOnlyMemory contentData = safeContentsAsn.Content; - if (!handle.IsInvalid) + if (safeContentsAsn.ContentType == Oids.Pkcs7Data) { - pkcs12Reader = new OpenSslPkcs12Reader(handle); - return true; + contentData = Helpers.DecodeOctetStringAsMemory(contentData); } - handle.Dispose(); - pkcs12Reader = null; - if (captureException) + AsnReader outer = new AsnReader(contentData, AsnEncodingRules.BER); + AsnReader reader = outer.ReadSequence(); + outer.ThrowIfNotEmpty(); + + while (reader.HasData) + { + SafeBagAsn.Decode(reader, out SafeBagAsn bag); + + if (bag.BagId == Oids.Pkcs12CertBag) + { + CertBagAsn certBag = CertBagAsn.Decode(bag.BagValue, AsnEncodingRules.BER); + + if (certBag.CertId == Oids.Pkcs12X509CertBagType) + { + GrowIfNeeded(ref certBags, certBagIdx); + GrowIfNeeded(ref certBagAttrs, certBagIdx); + certBags[certBagIdx] = certBag; + certBagAttrs[certBagIdx] = bag.BagAttributes; + certBagIdx++; + } + } + else if (bag.BagId == Oids.Pkcs12KeyBag || bag.BagId == Oids.Pkcs12ShroudedKeyBag) + { + GrowIfNeeded(ref keyBags, keyBagIdx); + keyBags[keyBagIdx] = bag; + keyBagIdx++; + } + } + } + + private static AsymmetricAlgorithm LoadKey(SafeBagAsn safeBag, ReadOnlySpan password) + { + if (safeBag.BagId == Oids.Pkcs12ShroudedKeyBag) + { + ArraySegment decrypted = KeyFormatHelper.DecryptPkcs8( + password, + safeBag.BagValue, + out int localRead); + + try + { + if (localRead != safeBag.BagValue.Length) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + return LoadKey(decrypted.AsMemory()); + } + finally + { + CryptoPool.Return(decrypted.Array, clearSize: decrypted.Count); + } + } + + Debug.Assert(safeBag.BagId == Oids.Pkcs12KeyBag); + return LoadKey(safeBag.BagValue); + } + + private static AsymmetricAlgorithm LoadKey(in ReadOnlyMemory pkcs8) + { + PrivateKeyInfoAsn privateKeyInfo = PrivateKeyInfoAsn.Decode(pkcs8, AsnEncodingRules.BER); + AsymmetricAlgorithm key; + + switch (privateKeyInfo.PrivateKeyAlgorithm.Algorithm.Value) { - openSslException = Interop.Crypto.CreateOpenSslCryptographicException(); + case Oids.Rsa: + key = new RSAOpenSsl(); + break; + case Oids.Dsa: + key = new DSAOpenSsl(); + break; + case Oids.EcDiffieHellman: + case Oids.EcPublicKey: + key = new ECDiffieHellmanOpenSsl(); + break; + default: + throw new CryptographicException( + SR.Cryptography_UnknownAlgorithmIdentifier, + privateKeyInfo.PrivateKeyAlgorithm.Algorithm.Value); } - else + + key.ImportPkcs8PrivateKey(pkcs8.Span, out int bytesRead); + + if (bytesRead != pkcs8.Length) { - Interop.Crypto.ErrClearError(); + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } - return false; + return key; } - private static bool TryRead(SafeBioHandle fileBio, out OpenSslPkcs12Reader pkcs12Reader, out Exception openSslException, bool captureException) + private static SafeEvpPKeyHandle GetPrivateKey(AsymmetricAlgorithm key) { - SafePkcs12Handle p12 = Interop.Crypto.DecodePkcs12FromBio(fileBio); - openSslException = null; + if (key is RSAOpenSsl rsa) + return rsa.DuplicateKeyHandle(); + + if (key is DSAOpenSsl dsa) + return dsa.DuplicateKeyHandle(); - if (!p12.IsInvalid) + return ((ECDiffieHellmanOpenSsl)key).DuplicateKeyHandle(); + } + + public ArraySegment ReadCertificates() + { + Debug.Assert(_certs != null); + + int nullIdx = Array.IndexOf(_certs, null); + + if (nullIdx >= 0) { - pkcs12Reader = new OpenSslPkcs12Reader(p12); - return true; + return new ArraySegment( + _certs, + 0, + nullIdx); } - p12.Dispose(); - pkcs12Reader = null; - if (captureException) + return _certs; + } + + private static bool TryRead( + byte[] data, + out OpenSslPkcs12Reader pkcs12Reader, + out Exception openSslException, + bool captureException) + { + openSslException = null; + + try { - openSslException = Interop.Crypto.CreateOpenSslCryptographicException(); + AsnReader reader = new AsnReader(data, AsnEncodingRules.BER); + ReadOnlyMemory encodedData = reader.PeekEncodedValue(); + + if (encodedData.Length != data.Length) + { + reader = new AsnReader(encodedData, AsnEncodingRules.BER); + } + + pkcs12Reader = new OpenSslPkcs12Reader(reader); + return true; } - else + catch (CryptographicException e) { - Interop.Crypto.ErrClearError(); + if (captureException) + { + openSslException = e; + } + + pkcs12Reader = null; + return false; } + } + private static bool TryRead( + SafeBioHandle fileBio, + out OpenSslPkcs12Reader pkcs12Reader, + out Exception openSslException, + bool captureException) + { + _ = fileBio; + pkcs12Reader = null; + openSslException = captureException ? new NotImplementedException() : null; return false; } + + private static void GrowIfNeeded(ref T[] array, int idx) + { + T[] oldRent = array; + + if (idx >= array.Length) + { + T[] newRent = ArrayPool.Shared.Rent(array.Length * 2); + Array.Copy(oldRent, 0, newRent, 0, idx); + array = newRent; + ArrayPool.Shared.Return(oldRent, clearArray: true); + } + } } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs index f5f28cc663b9..a14fc38babd9 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -69,16 +71,36 @@ public static ICertificatePal FromBlob(byte[] rawData, SafePasswordHandle passwo public static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) { + ICertificatePal pal; + // If we can't open the file, fail right away. using (SafeBioHandle fileBio = Interop.Crypto.BioNewFile(fileName, "rb")) { Interop.Crypto.CheckValidOpenSslHandle(fileBio); - return FromBio(fileBio, password); + pal = FromBio(fileBio); + } + + if (pal == null) + { + PkcsFormatReader.TryReadPkcs12( + File.ReadAllBytes(fileName), + password, + out pal, + out Exception exception); + + if (exception != null) + { + throw exception; + } + + Debug.Assert(pal != null); } + + return pal; } - private static ICertificatePal FromBio(SafeBioHandle bio, SafePasswordHandle password) + private static ICertificatePal FromBio(SafeBioHandle bio) { int bioPosition = Interop.Crypto.BioTell(bio); @@ -114,28 +136,7 @@ private static ICertificatePal FromBio(SafeBioHandle bio, SafePasswordHandle pas return certPal; } - // Rewind, try again. - RewindBio(bio, bioPosition); - - // Capture the exception so in case of failure, the call to BioSeek does not override it. - Exception openSslException; - if (PkcsFormatReader.TryReadPkcs12(bio, password, out certPal, out openSslException)) - { - return certPal; - } - - // Since we aren't going to finish reading, leaving the buffer where it was when we got - // it seems better than leaving it in some arbitrary other position. - // - // Use BioSeek directly for the last seek attempt, because any failure here should instead - // report the already created (but not yet thrown) exception. - if (Interop.Crypto.BioSeek(bio, bioPosition) < 0) - { - Interop.Crypto.ErrClearError(); - } - - Debug.Assert(openSslException != null); - throw openSslException; + return null; } internal static void RewindBio(SafeBioHandle bio, int bioPosition) @@ -148,9 +149,11 @@ internal static void RewindBio(SafeBioHandle bio, int bioPosition) } } - internal static bool TryReadX509Der(byte[] rawData, out ICertificatePal certPal) + internal static bool TryReadX509Der(ReadOnlySpan rawData, out ICertificatePal certPal) { - SafeX509Handle certHandle = Interop.Crypto.DecodeX509(rawData, rawData.Length); + SafeX509Handle certHandle = Interop.Crypto.DecodeX509( + ref MemoryMarshal.GetReference(rawData), + rawData.Length); if (certHandle.IsInvalid) { diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509Encoder.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509Encoder.cs index 5d4ccda8f9cf..40eef919f81b 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509Encoder.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509Encoder.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; +using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.Asn1; using System.Security.Cryptography.X509Certificates; @@ -134,19 +135,17 @@ public X509ContentType GetCertContentType(string fileName) OpenSslX509CertificateReader.RewindBio(fileBio, bioPosition); } + } - // X509ContentType.Pkcs12 (aka PFX) - { - OpenSslPkcs12Reader pkcs12Reader; - - if (OpenSslPkcs12Reader.TryRead(fileBio, out pkcs12Reader)) - { - pkcs12Reader.Dispose(); + // X509ContentType.Pkcs12 (aka PFX) + { + OpenSslPkcs12Reader pkcs12Reader; - return X509ContentType.Pkcs12; - } + if (OpenSslPkcs12Reader.TryRead(File.ReadAllBytes(fileName), out pkcs12Reader)) + { + pkcs12Reader.Dispose(); - OpenSslX509CertificateReader.RewindBio(fileBio, bioPosition); + return X509ContentType.Pkcs12; } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs index ad3d5b074b5f..013c0d2bbf69 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.Win32.SafeHandles; @@ -56,11 +57,11 @@ public static ILoaderPal FromFile(string fileName, SafePasswordHandle password, { Interop.Crypto.CheckValidOpenSslHandle(bio); - return FromBio(bio, password); + return FromBio(fileName, bio, password); } } - private static ILoaderPal FromBio(SafeBioHandle bio, SafePasswordHandle password) + private static ILoaderPal FromBio(string fileName, SafeBioHandle bio, SafePasswordHandle password) { int bioPosition = Interop.Crypto.BioTell(bio); Debug.Assert(bioPosition >= 0); @@ -103,7 +104,8 @@ private static ILoaderPal FromBio(SafeBioHandle bio, SafePasswordHandle password // Capture the exception so in case of failure, the call to BioSeek does not override it. Exception openSslException; - if (PkcsFormatReader.TryReadPkcs12(bio, password, out certPals, out openSslException)) + byte[] data = File.ReadAllBytes(fileName); + if (PkcsFormatReader.TryReadPkcs12(data, password, out certPals, out openSslException)) { return ListToLoaderPal(certPals); } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs b/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs index bb4169132208..000816cb2729 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs @@ -10,19 +10,47 @@ namespace Microsoft.Win32.SafeHandles { internal partial class SafePasswordHandle { + internal int Length { get; private set; } + private bool _utf8; + + public SafePasswordHandle(string password, bool utf8) + : base(IntPtr.Zero, ownsHandle: true) + { + if (password != null) + { + SetHandle(Marshal.StringToHGlobalAnsi(password)); + _utf8 = true; + } + } + + public SafePasswordHandle(SecureString password, bool utf8) + : base(IntPtr.Zero, ownsHandle: true) + { + if (password != null) + { + SetHandle(Marshal.SecureStringToGlobalAllocAnsi(password)); + _utf8 = true; + } + } + private IntPtr CreateHandle(string password) { - return Marshal.StringToHGlobalAnsi(password); + Length = password?.Length ?? 0; + return Marshal.StringToHGlobalUni(password); } private IntPtr CreateHandle(SecureString password) { - return Marshal.SecureStringToGlobalAllocAnsi(password); + Length = password?.Length ?? 0; + return Marshal.SecureStringToGlobalAllocUnicode(password); } private void FreeHandle() { - Marshal.ZeroFreeGlobalAllocAnsi(handle); + if (_utf8) + Marshal.ZeroFreeGlobalAllocAnsi(handle); + else + Marshal.ZeroFreeGlobalAllocUnicode(handle); } } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index 87d788651c01..86c415e5a1b3 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -296,9 +296,6 @@ Common\Microsoft\Win32\SafeHandles\SafeBCryptKeyHandle.cs - - - Common\Interop\Unix\System.Native\Interop.PrintF.cs @@ -311,6 +308,121 @@ System\Security\Cryptography\X509Certificates\Asn1\DistributionPointNameAsn.xml + + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.manual.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml + + + Common\System\Security\Cryptography\Asn1\PBEParameter.xml + + + Common\System\Security\Cryptography\Asn1\PBEParameter.xml.cs + Common\System\Security\Cryptography\Asn1\PBEParameter.xml + + + Common\System\Security\Cryptography\Asn1\PBES2Params.xml + + + Common\System\Security\Cryptography\Asn1\PBES2Params.xml.cs + Common\System\Security\Cryptography\Asn1\PBES2Params.xml + + + Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml + + + Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml.cs + Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml + + + Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml + + + Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml.cs + Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml + + + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml + + + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml.cs + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml + + + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.manual.cs + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml + + + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml + + + Common\System\Security\Cryptography\KeyFormatHelper.cs + + + Common\System\Security\Cryptography\PasswordBasedEncryption.cs + + + Common\System\Security\Cryptography\Pkcs12Kdf.cs + @@ -581,13 +693,6 @@ Common\System\Security\Cryptography\Asn1\ECPrivateKey.xml.cs Common\System\Security\Cryptography\Asn1\ECPrivateKey.xml - - Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml - Common\System\Security\Cryptography\Asn1\FieldID.xml @@ -595,52 +700,6 @@ Common\System\Security\Cryptography\Asn1\FieldID.xml.cs Common\System\Security\Cryptography\Asn1\FieldID.xml - - Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\PBEParameter.xml - - - Common\System\Security\Cryptography\Asn1\PBEParameter.xml.cs - Common\System\Security\Cryptography\Asn1\PBEParameter.xml - - - Common\System\Security\Cryptography\Asn1\PBES2Params.xml - - - Common\System\Security\Cryptography\Asn1\PBES2Params.xml.cs - Common\System\Security\Cryptography\Asn1\PBES2Params.xml - - - Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml - - - Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml.cs - Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml - - - Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml - - - Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml.cs - Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml - - - Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml - - - Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml.cs - Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml - - - Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.manual.cs - Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml - Common\System\Security\Cryptography\Asn1\RSAPrivateKeyAsn.xml @@ -675,12 +734,20 @@ + + Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml + + diff --git a/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs b/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs index fede91c13a39..633a7f8e4c06 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs @@ -284,7 +284,7 @@ public virtual byte[] Export(X509ContentType contentType, string password) if (Pal == null) throw new CryptographicException(ErrorCode.E_POINTER); // Not the greatest error, but needed for backward compat. - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, true)) { return Pal.Export(contentType, safePasswordHandle); } @@ -298,7 +298,7 @@ public virtual byte[] Export(X509ContentType contentType, SecureString password) if (Pal == null) throw new CryptographicException(ErrorCode.E_POINTER); // Not the greatest error, but needed for backward compat. - using (var safePasswordHandle = new SafePasswordHandle(password)) + using (var safePasswordHandle = new SafePasswordHandle(password, true)) { return Pal.Export(contentType, safePasswordHandle); } diff --git a/src/System.Security.Cryptography.X509Certificates/tests/CtorTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/CtorTests.cs index 26d807a7ea9f..b88aa3ec091d 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/CtorTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/CtorTests.cs @@ -367,11 +367,7 @@ public static void InvalidCertificateBlob() } else // Any Unix { - // OpenSSL encodes the function name into the error code. However, the function name differs - // between versions (OpenSSL 1.0, OpenSSL 1.1 and BoringSSL) and it's subject to change in - // the future, so don't test for the exact match and mask out the function code away. The - // component number (high 8 bits) and error code (low 12 bits) should remain the same. - Assert.Equal(0x0D00003A, ex.HResult & 0xFF000FFF); + Assert.Equal(new CryptographicException("message").HResult, ex.HResult); } } From 81da280130fd84d52667d65359c3acab482bfb56 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 18 Sep 2019 15:58:16 -0700 Subject: [PATCH 05/61] Both import and export pass, Windows should build again --- .../Security/Cryptography/CryptoPool.cs | 8 + .../Cryptography/EccKeyFormatHelper.cs | 3 - .../Cryptography/Pal.Unix/ExportProvider.cs | 541 ++++++++++++++++-- .../Pal.Unix/OpenSslPkcs12Reader.cs | 10 + .../SafeHandles/SafePasswordHandle.Unix.cs | 26 +- .../X509Certificates/X509Certificate.cs | 4 +- 6 files changed, 513 insertions(+), 79 deletions(-) diff --git a/src/Common/src/System/Security/Cryptography/CryptoPool.cs b/src/Common/src/System/Security/Cryptography/CryptoPool.cs index ffe7319ef4b3..200eb468423f 100644 --- a/src/Common/src/System/Security/Cryptography/CryptoPool.cs +++ b/src/Common/src/System/Security/Cryptography/CryptoPool.cs @@ -13,6 +13,14 @@ internal static class CryptoPool internal static byte[] Rent(int minimumLength) => ArrayPool.Shared.Rent(minimumLength); + internal static void Return(ArraySegment arraySegment) + { + Debug.Assert(arraySegment.Array != null); + Debug.Assert(arraySegment.Offset == 0); + + Return(arraySegment.Array, arraySegment.Count); + } + internal static void Return(byte[] array, int clearSize = ClearAll) { Debug.Assert(clearSize <= array.Length); diff --git a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index 2727a4673722..f89a1ca9ddcf 100644 --- a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -3,9 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Buffers; -using System.Diagnostics; -using System.Numerics; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography.Asn1; diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs index f8c78c3b290b..ca113aee1c97 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs @@ -4,15 +4,24 @@ using Microsoft.Win32.SafeHandles; using System; +using System.Buffers; using System.Diagnostics; +using System.Runtime.InteropServices; using System.Security.Cryptography; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.Pkcs.Asn1; using System.Security.Cryptography.X509Certificates; namespace Internal.Cryptography.Pal { internal sealed class ExportProvider : IExportPal { - private static readonly SafeEvpPKeyHandle InvalidPKeyHandle = new SafeEvpPKeyHandle(IntPtr.Zero, false); + private static readonly Asn1Tag ContextSpecific0 = + new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true); + + private static readonly PbeParameters s_windowsPbe = + new PbeParameters(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, HashAlgorithmName.SHA1, 2000); private ICertificatePalCore _singleCertPal; private X509Certificate2Collection _certs; @@ -74,84 +83,518 @@ private byte[] ExportX509Der() private byte[] ExportPfx(SafePasswordHandle password) { - using (SafeX509StackHandle publicCerts = Interop.Crypto.NewX509Stack()) + int certCount = 1; + + if (_singleCertPal == null) + { + Debug.Assert(_certs != null); + certCount = _certs.Count; + } + + CertBagAsn[] certBags = ArrayPool.Shared.Rent(certCount); + SafeBagAsn[] keyBags = ArrayPool.Shared.Rent(certCount); + AttributeAsn[] certAttrs = ArrayPool.Shared.Rent(certCount); + certAttrs.AsSpan(0, certCount).Clear(); + + AsnWriter tmpWriter = new AsnWriter(AsnEncodingRules.DER); + ArraySegment encodedAuthSafe = default; + + bool gotRef = false; + ReadOnlySpan passwordSpan; + password.DangerousAddRef(ref gotRef); + + unsafe + { + passwordSpan = new ReadOnlySpan( + (char*)password.DangerousGetHandle(), + password.Length); + } + + try { - SafeX509Handle privateCertHandle = SafeX509Handle.InvalidHandle; - SafeEvpPKeyHandle privateCertKeyHandle = InvalidPKeyHandle; + int keyIdx = 0; + int certIdx = 0; if (_singleCertPal != null) { - var certPal = (OpenSslX509CertificateReader)_singleCertPal; + BuildBags( + (OpenSslX509CertificateReader)_singleCertPal, + passwordSpan, + tmpWriter, + certBags, + certAttrs, + keyBags, + ref certIdx, + ref keyIdx); + } + else + { + foreach (X509Certificate2 cert in _certs) + { + BuildBags( + (OpenSslX509CertificateReader)cert.Pal, + passwordSpan, + tmpWriter, + certBags, + certAttrs, + keyBags, + ref certIdx, + ref keyIdx); + } + } + + encodedAuthSafe = EncodeAuthSafe( + tmpWriter, + keyBags, + keyIdx, + certBags, + certAttrs, + certIdx, + passwordSpan); + + return MacAndEncode(tmpWriter, encodedAuthSafe, passwordSpan); + } + finally + { + password.DangerousRelease(); + tmpWriter.Dispose(); + certAttrs.AsSpan(0, certCount).Clear(); + certBags.AsSpan(0, certCount).Clear(); + keyBags.AsSpan(0, certCount).Clear(); + ArrayPool.Shared.Return(certAttrs); + ArrayPool.Shared.Return(certBags); + ArrayPool.Shared.Return(keyBags); + + if (encodedAuthSafe.Array != null) + { + CryptoPool.Return(encodedAuthSafe); + } + } + } + + private static void BuildBags( + OpenSslX509CertificateReader certPal, + ReadOnlySpan passwordSpan, + AsnWriter tmpWriter, + CertBagAsn[] certBags, + AttributeAsn[] certAttrs, + SafeBagAsn[] keyBags, + ref int certIdx, + ref int keyIdx) + { + tmpWriter.WriteOctetString(certPal.RawData); + + certBags[certIdx] = new CertBagAsn + { + CertId = Oids.Pkcs12X509CertBagType, + CertValue = tmpWriter.Encode(), + }; - if (_singleCertPal.HasPrivateKey) + tmpWriter.Reset(); + + if (certPal.HasPrivateKey) + { + byte[] attrBytes = new byte[6]; + attrBytes[0] = (byte)UniversalTagNumber.OctetString; + attrBytes[1] = sizeof(int); + MemoryMarshal.Write(attrBytes.AsSpan(2), ref keyIdx); + + keyBags[keyIdx] = new SafeBagAsn + { + BagId = Oids.Pkcs12ShroudedKeyBag, + BagValue = ExportPkcs8(certPal.PrivateKeyHandle, passwordSpan), + BagAttributes = new[] { - privateCertHandle = certPal.SafeHandle; - privateCertKeyHandle = certPal.PrivateKeyHandle; + new AttributeAsn + { + AttrType = new Oid(Oids.LocalKeyId, null), + AttrValues = new ReadOnlyMemory[] + { + attrBytes, + } + } } - else + }; + + // Reuse the attribute between the cert and the key. + certAttrs[certIdx] = keyBags[keyIdx].BagAttributes[0]; + keyIdx++; + } + + certIdx++; + } + + private static unsafe ArraySegment EncodeAuthSafe( + AsnWriter tmpWriter, + SafeBagAsn[] keyBags, + int keyCount, + CertBagAsn[] certBags, + AttributeAsn[] certAttrs, + int certIdx, + ReadOnlySpan passwordSpan) + { + string encryptionAlgorithmOid = null; + bool certsIsPkcs12Encryption = false; + string certsHmacOid = null; + + ArraySegment encodedKeyContents = default; + ArraySegment encodedCertContents = default; + + try + { + if (keyCount > 0) + { + encodedKeyContents = EncodeKeys(tmpWriter, keyBags, keyCount); + } + + Span salt = stackalloc byte[16]; + RandomNumberGenerator.Fill(salt); + Span certContentsIv = stackalloc byte[8]; + + if (certIdx > 0) + { + encodedCertContents = EncodeCerts( + tmpWriter, + certBags, + certAttrs, + certIdx, + salt, + passwordSpan, + certContentsIv, + out certsHmacOid, + out encryptionAlgorithmOid, + out certsIsPkcs12Encryption); + } + + return EncodeAuthSafe( + tmpWriter, + encodedKeyContents, + encodedCertContents, + certsIsPkcs12Encryption, + certsHmacOid, + encryptionAlgorithmOid, + salt, + certContentsIv); + } + finally + { + if (encodedCertContents.Array != null) + { + CryptoPool.Return(encodedCertContents); + } + + if (encodedKeyContents.Array != null) + { + CryptoPool.Return(encodedKeyContents); + } + } + } + + private static ArraySegment EncodeKeys(AsnWriter tmpWriter, SafeBagAsn[] keyBags, int keyCount) + { + Debug.Assert(tmpWriter.GetEncodedLength() == 0); + tmpWriter.PushSequence(); + + for (int i = 0; i < keyCount; i++) + { + keyBags[i].Encode(tmpWriter); + } + + tmpWriter.PopSequence(); + ReadOnlySpan encodedKeys = tmpWriter.EncodeAsSpan(); + int length = encodedKeys.Length; + byte[] keyBuf = CryptoPool.Rent(length); + encodedKeys.CopyTo(keyBuf); + tmpWriter.Reset(); + + return new ArraySegment(keyBuf, 0, length); + } + + private static ArraySegment EncodeCerts( + AsnWriter tmpWriter, + CertBagAsn[] certBags, + AttributeAsn[] certAttrs, + int certCount, + Span salt, + ReadOnlySpan passwordSpan, + Span certContentsIv, + out string hmacOid, + out string encryptionAlgorithmOid, + out bool isPkcs12) + { + Debug.Assert(tmpWriter.GetEncodedLength() == 0); + tmpWriter.PushSequence(); + + PasswordBasedEncryption.InitiateEncryption( + s_windowsPbe, + out SymmetricAlgorithm cipher, + out hmacOid, + out encryptionAlgorithmOid, + out isPkcs12); + + using (cipher) + { + Debug.Assert(certContentsIv.Length * 8 == cipher.BlockSize); + + for (int i = certCount - 1; i >= 0; --i) + { + // Manually write the SafeBagAsn + // https://tools.ietf.org/html/rfc7292#section-4.2 + // + // SafeBag ::= SEQUENCE { + // bagId BAG-TYPE.&id ({PKCS12BagSet}) + // bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}), + // bagAttributes SET OF PKCS12Attribute OPTIONAL + // } + tmpWriter.PushSequence(); + + tmpWriter.WriteObjectIdentifier(Oids.Pkcs12CertBag); + + tmpWriter.PushSequence(ContextSpecific0); + certBags[i].Encode(tmpWriter); + tmpWriter.PopSequence(ContextSpecific0); + + if (certAttrs[i].AttrType != null) { - PushHandle(certPal.Handle, publicCerts); + tmpWriter.PushSetOf(); + certAttrs[i].Encode(tmpWriter); + tmpWriter.PopSetOf(); } - GC.KeepAlive(certPal); // ensure reader's safe handle isn't finalized while raw handle is in use + tmpWriter.PopSequence(); } - else + + tmpWriter.PopSequence(); + ReadOnlySpan contentsSpan = tmpWriter.EncodeAsSpan(); + + // The padding applied will add at most a block to the output, + // so ask for contentsSpan.Length + the number of bytes in a cipher block. + int cipherBlockBytes = cipher.BlockSize >> 3; + int requestedSize = checked(contentsSpan.Length + cipherBlockBytes); + byte[] certContents = CryptoPool.Rent(requestedSize); + + int encryptedLength = PasswordBasedEncryption.Encrypt( + passwordSpan, + ReadOnlySpan.Empty, + cipher, + isPkcs12, + contentsSpan, + s_windowsPbe, + salt, + certContents, + certContentsIv); + + Debug.Assert(encryptedLength <= requestedSize); + tmpWriter.Reset(); + + return new ArraySegment(certContents, 0, encryptedLength); + } + } + + private static ArraySegment EncodeAuthSafe( + AsnWriter tmpWriter, + ReadOnlyMemory encodedKeyContents, + ReadOnlyMemory encodedCertContents, + bool isPkcs12, + string hmacOid, + string encryptionAlgorithmOid, + Span salt, + Span certContentsIv) + { + Debug.Assert(tmpWriter.GetEncodedLength() == 0); + + tmpWriter.PushSequence(); + + if (!encodedKeyContents.IsEmpty) + { + tmpWriter.PushSequence(); + tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); + tmpWriter.PushSequence(ContextSpecific0); + + ReadOnlySpan keyContents = encodedKeyContents.Span; + tmpWriter.WriteOctetString(keyContents); + + tmpWriter.PopSequence(ContextSpecific0); + tmpWriter.PopSequence(); + } + + if (!encodedCertContents.IsEmpty) + { + tmpWriter.PushSequence(); + { - X509Certificate2 privateCert = null; + tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Encrypted); + + tmpWriter.PushSequence(ContextSpecific0); + tmpWriter.PushSequence(); - // Walk the collection backwards, because we're pushing onto a stack. - // This will cause the read order later to be the same as it was now. - for (int i = _certs.Count - 1; i >= 0; --i) { - X509Certificate2 cert = _certs[i]; + // No unprotected attributes: version 0 data + tmpWriter.WriteInteger(0); - if (cert.HasPrivateKey) - { - if (privateCert != null) - { - // OpenSSL's PKCS12 accelerator (PKCS12_create) only supports one - // private key. The data structure supports more than one, but - // being able to use that functionality requires a lot more code for - // a low-usage scenario. - throw new PlatformNotSupportedException(SR.NotSupported_Export_MultiplePrivateCerts); - } + tmpWriter.PushSequence(); - privateCert = cert; - var certPal = (OpenSslX509CertificateReader)cert.Pal; - privateCertHandle = certPal.SafeHandle; - privateCertKeyHandle = certPal.PrivateKeyHandle; - } - else { - PushHandle(cert.Handle, publicCerts); + tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); + + PasswordBasedEncryption.WritePbeAlgorithmIdentifier( + tmpWriter, + isPkcs12, + encryptionAlgorithmOid, + salt, + s_windowsPbe.IterationCount, + hmacOid, + certContentsIv); + + tmpWriter.WriteOctetString(ContextSpecific0, encodedCertContents.Span); + tmpWriter.PopSequence(); } + tmpWriter.PopSequence(); + tmpWriter.PopSequence(ContextSpecific0); } + + tmpWriter.PopSequence(); + } + } + + tmpWriter.PopSequence(); + + ReadOnlySpan authSafeSpan = tmpWriter.EncodeAsSpan(); + byte[] authSafe = CryptoPool.Rent(authSafeSpan.Length); + authSafeSpan.CopyTo(authSafe); + tmpWriter.Reset(); + + return new ArraySegment(authSafe, 0, authSafeSpan.Length); + } + + private static unsafe byte[] MacAndEncode( + AsnWriter tmpWriter, + ReadOnlyMemory encodedAuthSafe, + ReadOnlySpan passwordSpan) + { + // Windows/macOS compatibility: Use HMAC-SHA-1, + // other algorithms may not be understood + byte[] macKey = new byte[20]; + Span macSalt = stackalloc byte[20]; + Span macSpan = stackalloc byte[20]; + HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1; + RandomNumberGenerator.Fill(macSalt); + + Pkcs12Kdf.DeriveMacKey( + passwordSpan, + hashAlgorithm, + s_windowsPbe.IterationCount, + macSalt, + macKey); + + using (IncrementalHash mac = IncrementalHash.CreateHMAC(hashAlgorithm, macKey)) + { + mac.AppendData(encodedAuthSafe.Span); + + if (!mac.TryGetHashAndReset(macSpan, out int bytesWritten) || bytesWritten != macSpan.Length) + { + Debug.Fail($"TryGetHashAndReset wrote {bytesWritten} of {macSpan.Length} bytes"); + throw new CryptographicException(); + } + } + + // https://tools.ietf.org/html/rfc7292#section-4 + // + // PFX ::= SEQUENCE { + // version INTEGER {v3(3)}(v3,...), + // authSafe ContentInfo, + // macData MacData OPTIONAL + // } + Debug.Assert(tmpWriter.GetEncodedLength() == 0); + tmpWriter.PushSequence(); + + tmpWriter.WriteInteger(3); + + tmpWriter.PushSequence(); + { + tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); + + tmpWriter.PushSequence(ContextSpecific0); + { + tmpWriter.WriteOctetString(encodedAuthSafe.Span); + tmpWriter.PopSequence(ContextSpecific0); } - using (SafePkcs12Handle pkcs12 = Interop.Crypto.Pkcs12Create( - password, - privateCertKeyHandle, - privateCertHandle, - publicCerts)) + tmpWriter.PopSequence(); + } + + // https://tools.ietf.org/html/rfc7292#section-4 + // + // MacData ::= SEQUENCE { + // mac DigestInfo, + // macSalt OCTET STRING, + // iterations INTEGER DEFAULT 1 + // -- Note: The default is for historical reasons and its use is + // -- deprecated. + // } + tmpWriter.PushSequence(); + { + tmpWriter.PushSequence(); { - if (pkcs12.IsInvalid) + tmpWriter.PushSequence(); { - throw Interop.Crypto.CreateOpenSslCryptographicException(); + tmpWriter.WriteObjectIdentifier(Oids.Sha1); + tmpWriter.PopSequence(); } - byte[] result = Interop.Crypto.OpenSslEncode( - Interop.Crypto.GetPkcs12DerSize, - Interop.Crypto.EncodePkcs12, - pkcs12); - - // ensure cert handles aren't finalized while the raw handles are in use - GC.KeepAlive(_certs); - return result; + tmpWriter.WriteOctetString(macSpan); + tmpWriter.PopSequence(); } + tmpWriter.WriteOctetString(macSalt); + tmpWriter.WriteInteger(s_windowsPbe.IterationCount); + tmpWriter.PopSequence(); } + + tmpWriter.PopSequence(); + return tmpWriter.Encode(); + } + + private static byte[] ExportPkcs8( + SafeEvpPKeyHandle privateKey, + ReadOnlySpan password) + { + AsymmetricAlgorithm alg = null; + + try + { + alg = new RSAOpenSsl(privateKey); + } + catch (CryptographicException) + { + } + + if (alg == null) + { + try + { + alg = new ECDsaOpenSsl(privateKey); + } + catch (CryptographicException) + { + } + } + + if (alg == null) + { + try + { + alg = new DSAOpenSsl(privateKey); + } + catch (CryptographicException) + { + } + } + + Debug.Assert(alg != null); + return alg.ExportEncryptedPkcs8PrivateKey(password, s_windowsPbe); } private static void PushHandle(IntPtr certPtr, SafeX509StackHandle publicCerts) diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs index 81a2d0f5778b..67e05eff9379 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs @@ -278,6 +278,16 @@ private void Decrypt(ReadOnlySpan password, ReadOnlyMemory authSafeC } finally { + if (keys != null) + { + foreach (AsymmetricAlgorithm key in keys) + { + key?.Dispose(); + } + + ArrayPool.Shared.Return(keys); + } + ArrayPool.Shared.Return(certBags, clearArray: true); ArrayPool.Shared.Return(certBagAttrs, clearArray: true); ArrayPool.Shared.Return(keyBags, clearArray: true); diff --git a/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs b/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs index 000816cb2729..d22b778e1151 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs @@ -11,27 +11,6 @@ namespace Microsoft.Win32.SafeHandles internal partial class SafePasswordHandle { internal int Length { get; private set; } - private bool _utf8; - - public SafePasswordHandle(string password, bool utf8) - : base(IntPtr.Zero, ownsHandle: true) - { - if (password != null) - { - SetHandle(Marshal.StringToHGlobalAnsi(password)); - _utf8 = true; - } - } - - public SafePasswordHandle(SecureString password, bool utf8) - : base(IntPtr.Zero, ownsHandle: true) - { - if (password != null) - { - SetHandle(Marshal.SecureStringToGlobalAllocAnsi(password)); - _utf8 = true; - } - } private IntPtr CreateHandle(string password) { @@ -47,10 +26,7 @@ private IntPtr CreateHandle(SecureString password) private void FreeHandle() { - if (_utf8) - Marshal.ZeroFreeGlobalAllocAnsi(handle); - else - Marshal.ZeroFreeGlobalAllocUnicode(handle); + Marshal.ZeroFreeGlobalAllocUnicode(handle); } } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs b/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs index 633a7f8e4c06..fede91c13a39 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Certificate.cs @@ -284,7 +284,7 @@ public virtual byte[] Export(X509ContentType contentType, string password) if (Pal == null) throw new CryptographicException(ErrorCode.E_POINTER); // Not the greatest error, but needed for backward compat. - using (var safePasswordHandle = new SafePasswordHandle(password, true)) + using (var safePasswordHandle = new SafePasswordHandle(password)) { return Pal.Export(contentType, safePasswordHandle); } @@ -298,7 +298,7 @@ public virtual byte[] Export(X509ContentType contentType, SecureString password) if (Pal == null) throw new CryptographicException(ErrorCode.E_POINTER); // Not the greatest error, but needed for backward compat. - using (var safePasswordHandle = new SafePasswordHandle(password, true)) + using (var safePasswordHandle = new SafePasswordHandle(password)) { return Pal.Export(contentType, safePasswordHandle); } From c23c6ffee2fc3fb1681632422974be997903f4c4 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 19 Sep 2019 08:08:39 -0700 Subject: [PATCH 06/61] Re-enable some disabled PFX tests for Linux --- .../tests/CollectionTests.cs | 37 +------------------ 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs index 48a3a8ed8e8b..2b3a90f8004c 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs @@ -6,7 +6,6 @@ using System.Collections; using System.Collections.Generic; using System.IO; -using System.Runtime.InteropServices; using Xunit; namespace System.Security.Cryptography.X509Certificates.Tests @@ -632,7 +631,6 @@ public static void ImportFromFileTests(X509KeyStorageFlags storageFlags) } [Fact] - [ActiveIssue(2745, TestPlatforms.AnyUnix)] public static void ImportMultiplePrivateKeysPfx() { using (ImportedCollection ic = Cert.Import(TestData.MultiPrivateKeyPfx)) @@ -722,7 +720,6 @@ public static void ExportEmpty_Cert() } [Fact] - [ActiveIssue(2746, TestPlatforms.AnyUnix)] public static void ExportEmpty_Pkcs12() { var collection = new X509Certificate2Collection(); @@ -799,7 +796,6 @@ public static void MultipleImport() } [Fact] - [ActiveIssue(2743, TestPlatforms.AnyUnix & ~TestPlatforms.OSX)] public static void ExportMultiplePrivateKeys() { var collection = new X509Certificate2Collection(); @@ -813,37 +809,8 @@ public static void ExportMultiplePrivateKeys() int originalPrivateKeyCount = collection.OfType().Count(c => c.HasPrivateKey); Assert.Equal(2, originalPrivateKeyCount); - // Export, re-import. - byte[] exported; - - bool expectSuccess = - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || - RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - - try - { - exported = collection.Export(X509ContentType.Pkcs12); - } - catch (PlatformNotSupportedException) - { - // [ActiveIssue(2743, TestPlatforms.AnyUnix)] - // Our Unix builds can't export more than one private key in a single PFX, so this is - // their exit point. - // - // If Windows gets here, or any exception other than PlatformNotSupportedException is raised, - // let that fail the test. - if (expectSuccess) - { - throw; - } - - return; - } - - // As the other half of issue 2743, if we make it this far we better be Windows (or remove the catch - // above) - Assert.True(expectSuccess, "Test is expected to fail on this platform"); - + byte[] exported = collection.Export(X509ContentType.Pkcs12); + using (ImportedCollection ic = Cert.Import(exported)) { X509Certificate2Collection importedCollection = ic.Collection; From 347f2f061f4d5d10131fd5fda23893f2294cc93d Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 19 Sep 2019 08:10:23 -0700 Subject: [PATCH 07/61] Start prepping for move to shared Unix --- .../src/Internal/Cryptography/Pal.Unix/ExportProvider.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs index ca113aee1c97..bc8a747a553c 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs @@ -202,7 +202,7 @@ private static void BuildBags( keyBags[keyIdx] = new SafeBagAsn { BagId = Oids.Pkcs12ShroudedKeyBag, - BagValue = ExportPkcs8(certPal.PrivateKeyHandle, passwordSpan), + BagValue = ExportPkcs8(certPal, passwordSpan), BagAttributes = new[] { new AttributeAsn @@ -558,10 +558,11 @@ private static unsafe byte[] MacAndEncode( } private static byte[] ExportPkcs8( - SafeEvpPKeyHandle privateKey, + ICertificatePal certificatePal, ReadOnlySpan password) { AsymmetricAlgorithm alg = null; + SafeEvpPKeyHandle privateKey = ((OpenSslX509CertificateReader)certificatePal).PrivateKeyHandle; try { From e74a9f6caae0becdc7124ae1b07f403fa7dac65e Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 19 Sep 2019 09:37:33 -0700 Subject: [PATCH 08/61] Make the PFX exporter have no specific PAL references --- .../Cryptography/Pal.Unix/ExportProvider.cs | 547 +---------------- .../Pal.Unix/UnixExportProvider.cs | 566 ++++++++++++++++++ ...urity.Cryptography.X509Certificates.csproj | 1 + 3 files changed, 573 insertions(+), 541 deletions(-) create mode 100644 src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs index bc8a747a553c..4ce2fc4585ef 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs @@ -4,561 +4,26 @@ using Microsoft.Win32.SafeHandles; using System; -using System.Buffers; using System.Diagnostics; -using System.Runtime.InteropServices; using System.Security.Cryptography; -using System.Security.Cryptography.Asn1; -using System.Security.Cryptography.Pkcs; -using System.Security.Cryptography.Pkcs.Asn1; using System.Security.Cryptography.X509Certificates; namespace Internal.Cryptography.Pal { - internal sealed class ExportProvider : IExportPal + internal sealed class ExportProvider : UnixExportProvider { - private static readonly Asn1Tag ContextSpecific0 = - new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true); - - private static readonly PbeParameters s_windowsPbe = - new PbeParameters(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, HashAlgorithmName.SHA1, 2000); - - private ICertificatePalCore _singleCertPal; - private X509Certificate2Collection _certs; - internal ExportProvider(ICertificatePalCore singleCertPal) + : base(singleCertPal) { - _singleCertPal = singleCertPal; } internal ExportProvider(X509Certificate2Collection certs) + : base(certs) { - _certs = certs; - } - - public void Dispose() - { - // Don't dispose any of the resources, they're still owned by the caller. - _singleCertPal = null; - _certs = null; - } - - public byte[] Export(X509ContentType contentType, SafePasswordHandle password) - { - Debug.Assert(password != null); - switch (contentType) - { - case X509ContentType.Cert: - return ExportX509Der(); - case X509ContentType.Pfx: - return ExportPfx(password); - case X509ContentType.Pkcs7: - return ExportPkcs7(); - case X509ContentType.SerializedCert: - case X509ContentType.SerializedStore: - throw new PlatformNotSupportedException(SR.Cryptography_Unix_X509_SerializedExport); - default: - throw new CryptographicException(SR.Cryptography_X509_InvalidContentType); - } - } - - private byte[] ExportX509Der() - { - if (_singleCertPal != null) - { - return _singleCertPal.RawData; - } - - // Windows/Desktop compatibility: Exporting a collection (or store) as - // X509ContentType.Cert returns the equivalent of FirstOrDefault(), - // so anything past _certs[0] is ignored, and an empty collection is - // null (not an Exception) - if (_certs.Count == 0) - { - return null; - } - - return _certs[0].RawData; - } - - private byte[] ExportPfx(SafePasswordHandle password) - { - int certCount = 1; - - if (_singleCertPal == null) - { - Debug.Assert(_certs != null); - certCount = _certs.Count; - } - - CertBagAsn[] certBags = ArrayPool.Shared.Rent(certCount); - SafeBagAsn[] keyBags = ArrayPool.Shared.Rent(certCount); - AttributeAsn[] certAttrs = ArrayPool.Shared.Rent(certCount); - certAttrs.AsSpan(0, certCount).Clear(); - - AsnWriter tmpWriter = new AsnWriter(AsnEncodingRules.DER); - ArraySegment encodedAuthSafe = default; - - bool gotRef = false; - ReadOnlySpan passwordSpan; - password.DangerousAddRef(ref gotRef); - - unsafe - { - passwordSpan = new ReadOnlySpan( - (char*)password.DangerousGetHandle(), - password.Length); - } - - try - { - int keyIdx = 0; - int certIdx = 0; - - if (_singleCertPal != null) - { - BuildBags( - (OpenSslX509CertificateReader)_singleCertPal, - passwordSpan, - tmpWriter, - certBags, - certAttrs, - keyBags, - ref certIdx, - ref keyIdx); - } - else - { - foreach (X509Certificate2 cert in _certs) - { - BuildBags( - (OpenSslX509CertificateReader)cert.Pal, - passwordSpan, - tmpWriter, - certBags, - certAttrs, - keyBags, - ref certIdx, - ref keyIdx); - } - } - - encodedAuthSafe = EncodeAuthSafe( - tmpWriter, - keyBags, - keyIdx, - certBags, - certAttrs, - certIdx, - passwordSpan); - - return MacAndEncode(tmpWriter, encodedAuthSafe, passwordSpan); - } - finally - { - password.DangerousRelease(); - tmpWriter.Dispose(); - certAttrs.AsSpan(0, certCount).Clear(); - certBags.AsSpan(0, certCount).Clear(); - keyBags.AsSpan(0, certCount).Clear(); - ArrayPool.Shared.Return(certAttrs); - ArrayPool.Shared.Return(certBags); - ArrayPool.Shared.Return(keyBags); - - if (encodedAuthSafe.Array != null) - { - CryptoPool.Return(encodedAuthSafe); - } - } - } - - private static void BuildBags( - OpenSslX509CertificateReader certPal, - ReadOnlySpan passwordSpan, - AsnWriter tmpWriter, - CertBagAsn[] certBags, - AttributeAsn[] certAttrs, - SafeBagAsn[] keyBags, - ref int certIdx, - ref int keyIdx) - { - tmpWriter.WriteOctetString(certPal.RawData); - - certBags[certIdx] = new CertBagAsn - { - CertId = Oids.Pkcs12X509CertBagType, - CertValue = tmpWriter.Encode(), - }; - - tmpWriter.Reset(); - - if (certPal.HasPrivateKey) - { - byte[] attrBytes = new byte[6]; - attrBytes[0] = (byte)UniversalTagNumber.OctetString; - attrBytes[1] = sizeof(int); - MemoryMarshal.Write(attrBytes.AsSpan(2), ref keyIdx); - - keyBags[keyIdx] = new SafeBagAsn - { - BagId = Oids.Pkcs12ShroudedKeyBag, - BagValue = ExportPkcs8(certPal, passwordSpan), - BagAttributes = new[] - { - new AttributeAsn - { - AttrType = new Oid(Oids.LocalKeyId, null), - AttrValues = new ReadOnlyMemory[] - { - attrBytes, - } - } - } - }; - - // Reuse the attribute between the cert and the key. - certAttrs[certIdx] = keyBags[keyIdx].BagAttributes[0]; - keyIdx++; - } - - certIdx++; - } - - private static unsafe ArraySegment EncodeAuthSafe( - AsnWriter tmpWriter, - SafeBagAsn[] keyBags, - int keyCount, - CertBagAsn[] certBags, - AttributeAsn[] certAttrs, - int certIdx, - ReadOnlySpan passwordSpan) - { - string encryptionAlgorithmOid = null; - bool certsIsPkcs12Encryption = false; - string certsHmacOid = null; - - ArraySegment encodedKeyContents = default; - ArraySegment encodedCertContents = default; - - try - { - if (keyCount > 0) - { - encodedKeyContents = EncodeKeys(tmpWriter, keyBags, keyCount); - } - - Span salt = stackalloc byte[16]; - RandomNumberGenerator.Fill(salt); - Span certContentsIv = stackalloc byte[8]; - - if (certIdx > 0) - { - encodedCertContents = EncodeCerts( - tmpWriter, - certBags, - certAttrs, - certIdx, - salt, - passwordSpan, - certContentsIv, - out certsHmacOid, - out encryptionAlgorithmOid, - out certsIsPkcs12Encryption); - } - - return EncodeAuthSafe( - tmpWriter, - encodedKeyContents, - encodedCertContents, - certsIsPkcs12Encryption, - certsHmacOid, - encryptionAlgorithmOid, - salt, - certContentsIv); - } - finally - { - if (encodedCertContents.Array != null) - { - CryptoPool.Return(encodedCertContents); - } - - if (encodedKeyContents.Array != null) - { - CryptoPool.Return(encodedKeyContents); - } - } - } - - private static ArraySegment EncodeKeys(AsnWriter tmpWriter, SafeBagAsn[] keyBags, int keyCount) - { - Debug.Assert(tmpWriter.GetEncodedLength() == 0); - tmpWriter.PushSequence(); - - for (int i = 0; i < keyCount; i++) - { - keyBags[i].Encode(tmpWriter); - } - - tmpWriter.PopSequence(); - ReadOnlySpan encodedKeys = tmpWriter.EncodeAsSpan(); - int length = encodedKeys.Length; - byte[] keyBuf = CryptoPool.Rent(length); - encodedKeys.CopyTo(keyBuf); - tmpWriter.Reset(); - - return new ArraySegment(keyBuf, 0, length); - } - - private static ArraySegment EncodeCerts( - AsnWriter tmpWriter, - CertBagAsn[] certBags, - AttributeAsn[] certAttrs, - int certCount, - Span salt, - ReadOnlySpan passwordSpan, - Span certContentsIv, - out string hmacOid, - out string encryptionAlgorithmOid, - out bool isPkcs12) - { - Debug.Assert(tmpWriter.GetEncodedLength() == 0); - tmpWriter.PushSequence(); - - PasswordBasedEncryption.InitiateEncryption( - s_windowsPbe, - out SymmetricAlgorithm cipher, - out hmacOid, - out encryptionAlgorithmOid, - out isPkcs12); - - using (cipher) - { - Debug.Assert(certContentsIv.Length * 8 == cipher.BlockSize); - - for (int i = certCount - 1; i >= 0; --i) - { - // Manually write the SafeBagAsn - // https://tools.ietf.org/html/rfc7292#section-4.2 - // - // SafeBag ::= SEQUENCE { - // bagId BAG-TYPE.&id ({PKCS12BagSet}) - // bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}), - // bagAttributes SET OF PKCS12Attribute OPTIONAL - // } - tmpWriter.PushSequence(); - - tmpWriter.WriteObjectIdentifier(Oids.Pkcs12CertBag); - - tmpWriter.PushSequence(ContextSpecific0); - certBags[i].Encode(tmpWriter); - tmpWriter.PopSequence(ContextSpecific0); - - if (certAttrs[i].AttrType != null) - { - tmpWriter.PushSetOf(); - certAttrs[i].Encode(tmpWriter); - tmpWriter.PopSetOf(); - } - - tmpWriter.PopSequence(); - } - - tmpWriter.PopSequence(); - ReadOnlySpan contentsSpan = tmpWriter.EncodeAsSpan(); - - // The padding applied will add at most a block to the output, - // so ask for contentsSpan.Length + the number of bytes in a cipher block. - int cipherBlockBytes = cipher.BlockSize >> 3; - int requestedSize = checked(contentsSpan.Length + cipherBlockBytes); - byte[] certContents = CryptoPool.Rent(requestedSize); - - int encryptedLength = PasswordBasedEncryption.Encrypt( - passwordSpan, - ReadOnlySpan.Empty, - cipher, - isPkcs12, - contentsSpan, - s_windowsPbe, - salt, - certContents, - certContentsIv); - - Debug.Assert(encryptedLength <= requestedSize); - tmpWriter.Reset(); - - return new ArraySegment(certContents, 0, encryptedLength); - } - } - - private static ArraySegment EncodeAuthSafe( - AsnWriter tmpWriter, - ReadOnlyMemory encodedKeyContents, - ReadOnlyMemory encodedCertContents, - bool isPkcs12, - string hmacOid, - string encryptionAlgorithmOid, - Span salt, - Span certContentsIv) - { - Debug.Assert(tmpWriter.GetEncodedLength() == 0); - - tmpWriter.PushSequence(); - - if (!encodedKeyContents.IsEmpty) - { - tmpWriter.PushSequence(); - tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); - tmpWriter.PushSequence(ContextSpecific0); - - ReadOnlySpan keyContents = encodedKeyContents.Span; - tmpWriter.WriteOctetString(keyContents); - - tmpWriter.PopSequence(ContextSpecific0); - tmpWriter.PopSequence(); - } - - if (!encodedCertContents.IsEmpty) - { - tmpWriter.PushSequence(); - - { - tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Encrypted); - - tmpWriter.PushSequence(ContextSpecific0); - tmpWriter.PushSequence(); - - { - // No unprotected attributes: version 0 data - tmpWriter.WriteInteger(0); - - tmpWriter.PushSequence(); - - { - tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); - - PasswordBasedEncryption.WritePbeAlgorithmIdentifier( - tmpWriter, - isPkcs12, - encryptionAlgorithmOid, - salt, - s_windowsPbe.IterationCount, - hmacOid, - certContentsIv); - - tmpWriter.WriteOctetString(ContextSpecific0, encodedCertContents.Span); - tmpWriter.PopSequence(); - } - - tmpWriter.PopSequence(); - tmpWriter.PopSequence(ContextSpecific0); - } - - tmpWriter.PopSequence(); - } - } - - tmpWriter.PopSequence(); - - ReadOnlySpan authSafeSpan = tmpWriter.EncodeAsSpan(); - byte[] authSafe = CryptoPool.Rent(authSafeSpan.Length); - authSafeSpan.CopyTo(authSafe); - tmpWriter.Reset(); - - return new ArraySegment(authSafe, 0, authSafeSpan.Length); - } - - private static unsafe byte[] MacAndEncode( - AsnWriter tmpWriter, - ReadOnlyMemory encodedAuthSafe, - ReadOnlySpan passwordSpan) - { - // Windows/macOS compatibility: Use HMAC-SHA-1, - // other algorithms may not be understood - byte[] macKey = new byte[20]; - Span macSalt = stackalloc byte[20]; - Span macSpan = stackalloc byte[20]; - HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1; - RandomNumberGenerator.Fill(macSalt); - - Pkcs12Kdf.DeriveMacKey( - passwordSpan, - hashAlgorithm, - s_windowsPbe.IterationCount, - macSalt, - macKey); - - using (IncrementalHash mac = IncrementalHash.CreateHMAC(hashAlgorithm, macKey)) - { - mac.AppendData(encodedAuthSafe.Span); - - if (!mac.TryGetHashAndReset(macSpan, out int bytesWritten) || bytesWritten != macSpan.Length) - { - Debug.Fail($"TryGetHashAndReset wrote {bytesWritten} of {macSpan.Length} bytes"); - throw new CryptographicException(); - } - } - - // https://tools.ietf.org/html/rfc7292#section-4 - // - // PFX ::= SEQUENCE { - // version INTEGER {v3(3)}(v3,...), - // authSafe ContentInfo, - // macData MacData OPTIONAL - // } - Debug.Assert(tmpWriter.GetEncodedLength() == 0); - tmpWriter.PushSequence(); - - tmpWriter.WriteInteger(3); - - tmpWriter.PushSequence(); - { - tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); - - tmpWriter.PushSequence(ContextSpecific0); - { - tmpWriter.WriteOctetString(encodedAuthSafe.Span); - tmpWriter.PopSequence(ContextSpecific0); - } - - tmpWriter.PopSequence(); - } - - // https://tools.ietf.org/html/rfc7292#section-4 - // - // MacData ::= SEQUENCE { - // mac DigestInfo, - // macSalt OCTET STRING, - // iterations INTEGER DEFAULT 1 - // -- Note: The default is for historical reasons and its use is - // -- deprecated. - // } - tmpWriter.PushSequence(); - { - tmpWriter.PushSequence(); - { - tmpWriter.PushSequence(); - { - tmpWriter.WriteObjectIdentifier(Oids.Sha1); - tmpWriter.PopSequence(); - } - - tmpWriter.WriteOctetString(macSpan); - tmpWriter.PopSequence(); - } - - tmpWriter.WriteOctetString(macSalt); - tmpWriter.WriteInteger(s_windowsPbe.IterationCount); - - tmpWriter.PopSequence(); - } - - tmpWriter.PopSequence(); - return tmpWriter.Encode(); } - private static byte[] ExportPkcs8( - ICertificatePal certificatePal, + protected override byte[] ExportPkcs8( + ICertificatePalCore certificatePal, ReadOnlySpan password) { AsymmetricAlgorithm alg = null; @@ -612,7 +77,7 @@ private static void PushHandle(IntPtr certPtr, SafeX509StackHandle publicCerts) } } - private byte[] ExportPkcs7() + protected override byte[] ExportPkcs7() { // Pack all of the certificates into a new PKCS7*, export it to a byte[], // then free the PKCS7*, since we don't need it any more. diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs new file mode 100644 index 000000000000..6e27e82178a9 --- /dev/null +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs @@ -0,0 +1,566 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32.SafeHandles; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.Pkcs.Asn1; +using System.Security.Cryptography.X509Certificates; + +namespace Internal.Cryptography.Pal +{ + internal abstract class UnixExportProvider : IExportPal + { + private static readonly Asn1Tag ContextSpecific0 = + new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true); + + protected static readonly PbeParameters s_windowsPbe = + new PbeParameters(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, HashAlgorithmName.SHA1, 2000); + + protected ICertificatePalCore _singleCertPal; + protected X509Certificate2Collection _certs; + + internal UnixExportProvider(ICertificatePalCore singleCertPal) + { + _singleCertPal = singleCertPal; + } + + internal UnixExportProvider(X509Certificate2Collection certs) + { + _certs = certs; + } + + public void Dispose() + { + // Don't dispose any of the resources, they're still owned by the caller. + _singleCertPal = null; + _certs = null; + } + + protected abstract byte[] ExportPkcs7(); + + protected abstract byte[] ExportPkcs8( + ICertificatePalCore certificatePal, + ReadOnlySpan password); + + public byte[] Export(X509ContentType contentType, SafePasswordHandle password) + { + Debug.Assert(password != null); + switch (contentType) + { + case X509ContentType.Cert: + return ExportX509Der(); + case X509ContentType.Pfx: + return ExportPfx(password); + case X509ContentType.Pkcs7: + return ExportPkcs7(); + case X509ContentType.SerializedCert: + case X509ContentType.SerializedStore: + throw new PlatformNotSupportedException(SR.Cryptography_Unix_X509_SerializedExport); + default: + throw new CryptographicException(SR.Cryptography_X509_InvalidContentType); + } + } + + private byte[] ExportX509Der() + { + if (_singleCertPal != null) + { + return _singleCertPal.RawData; + } + + // Windows/Desktop compatibility: Exporting a collection (or store) as + // X509ContentType.Cert returns the equivalent of FirstOrDefault(), + // so anything past _certs[0] is ignored, and an empty collection is + // null (not an Exception) + if (_certs.Count == 0) + { + return null; + } + + return _certs[0].RawData; + } + + private byte[] ExportPfx(SafePasswordHandle password) + { + int certCount = 1; + + if (_singleCertPal == null) + { + Debug.Assert(_certs != null); + certCount = _certs.Count; + } + + CertBagAsn[] certBags = ArrayPool.Shared.Rent(certCount); + SafeBagAsn[] keyBags = ArrayPool.Shared.Rent(certCount); + AttributeAsn[] certAttrs = ArrayPool.Shared.Rent(certCount); + certAttrs.AsSpan(0, certCount).Clear(); + + AsnWriter tmpWriter = new AsnWriter(AsnEncodingRules.DER); + ArraySegment encodedAuthSafe = default; + + bool gotRef = false; + ReadOnlySpan passwordSpan; + password.DangerousAddRef(ref gotRef); + + unsafe + { + passwordSpan = new ReadOnlySpan( + (char*)password.DangerousGetHandle(), + password.Length); + } + + try + { + int keyIdx = 0; + int certIdx = 0; + + if (_singleCertPal != null) + { + BuildBags( + _singleCertPal, + passwordSpan, + tmpWriter, + certBags, + certAttrs, + keyBags, + ref certIdx, + ref keyIdx); + } + else + { + foreach (X509Certificate2 cert in _certs) + { + BuildBags( + cert.Pal, + passwordSpan, + tmpWriter, + certBags, + certAttrs, + keyBags, + ref certIdx, + ref keyIdx); + } + } + + encodedAuthSafe = EncodeAuthSafe( + tmpWriter, + keyBags, + keyIdx, + certBags, + certAttrs, + certIdx, + passwordSpan); + + return MacAndEncode(tmpWriter, encodedAuthSafe, passwordSpan); + } + finally + { + password.DangerousRelease(); + tmpWriter.Dispose(); + certAttrs.AsSpan(0, certCount).Clear(); + certBags.AsSpan(0, certCount).Clear(); + keyBags.AsSpan(0, certCount).Clear(); + ArrayPool.Shared.Return(certAttrs); + ArrayPool.Shared.Return(certBags); + ArrayPool.Shared.Return(keyBags); + + if (encodedAuthSafe.Array != null) + { + CryptoPool.Return(encodedAuthSafe); + } + } + } + + private void BuildBags( + ICertificatePalCore certPal, + ReadOnlySpan passwordSpan, + AsnWriter tmpWriter, + CertBagAsn[] certBags, + AttributeAsn[] certAttrs, + SafeBagAsn[] keyBags, + ref int certIdx, + ref int keyIdx) + { + tmpWriter.WriteOctetString(certPal.RawData); + + certBags[certIdx] = new CertBagAsn + { + CertId = Oids.Pkcs12X509CertBagType, + CertValue = tmpWriter.Encode(), + }; + + tmpWriter.Reset(); + + if (certPal.HasPrivateKey) + { + byte[] attrBytes = new byte[6]; + attrBytes[0] = (byte)UniversalTagNumber.OctetString; + attrBytes[1] = sizeof(int); + MemoryMarshal.Write(attrBytes.AsSpan(2), ref keyIdx); + + keyBags[keyIdx] = new SafeBagAsn + { + BagId = Oids.Pkcs12ShroudedKeyBag, + BagValue = ExportPkcs8(certPal, passwordSpan), + BagAttributes = new[] + { + new AttributeAsn + { + AttrType = new Oid(Oids.LocalKeyId, null), + AttrValues = new ReadOnlyMemory[] + { + attrBytes, + } + } + } + }; + + // Reuse the attribute between the cert and the key. + certAttrs[certIdx] = keyBags[keyIdx].BagAttributes[0]; + keyIdx++; + } + + certIdx++; + } + + private static unsafe ArraySegment EncodeAuthSafe( + AsnWriter tmpWriter, + SafeBagAsn[] keyBags, + int keyCount, + CertBagAsn[] certBags, + AttributeAsn[] certAttrs, + int certIdx, + ReadOnlySpan passwordSpan) + { + string encryptionAlgorithmOid = null; + bool certsIsPkcs12Encryption = false; + string certsHmacOid = null; + + ArraySegment encodedKeyContents = default; + ArraySegment encodedCertContents = default; + + try + { + if (keyCount > 0) + { + encodedKeyContents = EncodeKeys(tmpWriter, keyBags, keyCount); + } + + Span salt = stackalloc byte[16]; + RandomNumberGenerator.Fill(salt); + Span certContentsIv = stackalloc byte[8]; + + if (certIdx > 0) + { + encodedCertContents = EncodeCerts( + tmpWriter, + certBags, + certAttrs, + certIdx, + salt, + passwordSpan, + certContentsIv, + out certsHmacOid, + out encryptionAlgorithmOid, + out certsIsPkcs12Encryption); + } + + return EncodeAuthSafe( + tmpWriter, + encodedKeyContents, + encodedCertContents, + certsIsPkcs12Encryption, + certsHmacOid, + encryptionAlgorithmOid, + salt, + certContentsIv); + } + finally + { + if (encodedCertContents.Array != null) + { + CryptoPool.Return(encodedCertContents); + } + + if (encodedKeyContents.Array != null) + { + CryptoPool.Return(encodedKeyContents); + } + } + } + + private static ArraySegment EncodeKeys(AsnWriter tmpWriter, SafeBagAsn[] keyBags, int keyCount) + { + Debug.Assert(tmpWriter.GetEncodedLength() == 0); + tmpWriter.PushSequence(); + + for (int i = 0; i < keyCount; i++) + { + keyBags[i].Encode(tmpWriter); + } + + tmpWriter.PopSequence(); + ReadOnlySpan encodedKeys = tmpWriter.EncodeAsSpan(); + int length = encodedKeys.Length; + byte[] keyBuf = CryptoPool.Rent(length); + encodedKeys.CopyTo(keyBuf); + tmpWriter.Reset(); + + return new ArraySegment(keyBuf, 0, length); + } + + private static ArraySegment EncodeCerts( + AsnWriter tmpWriter, + CertBagAsn[] certBags, + AttributeAsn[] certAttrs, + int certCount, + Span salt, + ReadOnlySpan passwordSpan, + Span certContentsIv, + out string hmacOid, + out string encryptionAlgorithmOid, + out bool isPkcs12) + { + Debug.Assert(tmpWriter.GetEncodedLength() == 0); + tmpWriter.PushSequence(); + + PasswordBasedEncryption.InitiateEncryption( + s_windowsPbe, + out SymmetricAlgorithm cipher, + out hmacOid, + out encryptionAlgorithmOid, + out isPkcs12); + + using (cipher) + { + Debug.Assert(certContentsIv.Length * 8 == cipher.BlockSize); + + for (int i = certCount - 1; i >= 0; --i) + { + // Manually write the SafeBagAsn + // https://tools.ietf.org/html/rfc7292#section-4.2 + // + // SafeBag ::= SEQUENCE { + // bagId BAG-TYPE.&id ({PKCS12BagSet}) + // bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}), + // bagAttributes SET OF PKCS12Attribute OPTIONAL + // } + tmpWriter.PushSequence(); + + tmpWriter.WriteObjectIdentifier(Oids.Pkcs12CertBag); + + tmpWriter.PushSequence(ContextSpecific0); + certBags[i].Encode(tmpWriter); + tmpWriter.PopSequence(ContextSpecific0); + + if (certAttrs[i].AttrType != null) + { + tmpWriter.PushSetOf(); + certAttrs[i].Encode(tmpWriter); + tmpWriter.PopSetOf(); + } + + tmpWriter.PopSequence(); + } + + tmpWriter.PopSequence(); + ReadOnlySpan contentsSpan = tmpWriter.EncodeAsSpan(); + + // The padding applied will add at most a block to the output, + // so ask for contentsSpan.Length + the number of bytes in a cipher block. + int cipherBlockBytes = cipher.BlockSize >> 3; + int requestedSize = checked(contentsSpan.Length + cipherBlockBytes); + byte[] certContents = CryptoPool.Rent(requestedSize); + + int encryptedLength = PasswordBasedEncryption.Encrypt( + passwordSpan, + ReadOnlySpan.Empty, + cipher, + isPkcs12, + contentsSpan, + s_windowsPbe, + salt, + certContents, + certContentsIv); + + Debug.Assert(encryptedLength <= requestedSize); + tmpWriter.Reset(); + + return new ArraySegment(certContents, 0, encryptedLength); + } + } + + private static ArraySegment EncodeAuthSafe( + AsnWriter tmpWriter, + ReadOnlyMemory encodedKeyContents, + ReadOnlyMemory encodedCertContents, + bool isPkcs12, + string hmacOid, + string encryptionAlgorithmOid, + Span salt, + Span certContentsIv) + { + Debug.Assert(tmpWriter.GetEncodedLength() == 0); + + tmpWriter.PushSequence(); + + if (!encodedKeyContents.IsEmpty) + { + tmpWriter.PushSequence(); + tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); + tmpWriter.PushSequence(ContextSpecific0); + + ReadOnlySpan keyContents = encodedKeyContents.Span; + tmpWriter.WriteOctetString(keyContents); + + tmpWriter.PopSequence(ContextSpecific0); + tmpWriter.PopSequence(); + } + + if (!encodedCertContents.IsEmpty) + { + tmpWriter.PushSequence(); + + { + tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Encrypted); + + tmpWriter.PushSequence(ContextSpecific0); + tmpWriter.PushSequence(); + + { + // No unprotected attributes: version 0 data + tmpWriter.WriteInteger(0); + + tmpWriter.PushSequence(); + + { + tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); + + PasswordBasedEncryption.WritePbeAlgorithmIdentifier( + tmpWriter, + isPkcs12, + encryptionAlgorithmOid, + salt, + s_windowsPbe.IterationCount, + hmacOid, + certContentsIv); + + tmpWriter.WriteOctetString(ContextSpecific0, encodedCertContents.Span); + tmpWriter.PopSequence(); + } + + tmpWriter.PopSequence(); + tmpWriter.PopSequence(ContextSpecific0); + } + + tmpWriter.PopSequence(); + } + } + + tmpWriter.PopSequence(); + + ReadOnlySpan authSafeSpan = tmpWriter.EncodeAsSpan(); + byte[] authSafe = CryptoPool.Rent(authSafeSpan.Length); + authSafeSpan.CopyTo(authSafe); + tmpWriter.Reset(); + + return new ArraySegment(authSafe, 0, authSafeSpan.Length); + } + + private static unsafe byte[] MacAndEncode( + AsnWriter tmpWriter, + ReadOnlyMemory encodedAuthSafe, + ReadOnlySpan passwordSpan) + { + // Windows/macOS compatibility: Use HMAC-SHA-1, + // other algorithms may not be understood + byte[] macKey = new byte[20]; + Span macSalt = stackalloc byte[20]; + Span macSpan = stackalloc byte[20]; + HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1; + RandomNumberGenerator.Fill(macSalt); + + Pkcs12Kdf.DeriveMacKey( + passwordSpan, + hashAlgorithm, + s_windowsPbe.IterationCount, + macSalt, + macKey); + + using (IncrementalHash mac = IncrementalHash.CreateHMAC(hashAlgorithm, macKey)) + { + mac.AppendData(encodedAuthSafe.Span); + + if (!mac.TryGetHashAndReset(macSpan, out int bytesWritten) || bytesWritten != macSpan.Length) + { + Debug.Fail($"TryGetHashAndReset wrote {bytesWritten} of {macSpan.Length} bytes"); + throw new CryptographicException(); + } + } + + // https://tools.ietf.org/html/rfc7292#section-4 + // + // PFX ::= SEQUENCE { + // version INTEGER {v3(3)}(v3,...), + // authSafe ContentInfo, + // macData MacData OPTIONAL + // } + Debug.Assert(tmpWriter.GetEncodedLength() == 0); + tmpWriter.PushSequence(); + + tmpWriter.WriteInteger(3); + + tmpWriter.PushSequence(); + { + tmpWriter.WriteObjectIdentifier(Oids.Pkcs7Data); + + tmpWriter.PushSequence(ContextSpecific0); + { + tmpWriter.WriteOctetString(encodedAuthSafe.Span); + tmpWriter.PopSequence(ContextSpecific0); + } + + tmpWriter.PopSequence(); + } + + // https://tools.ietf.org/html/rfc7292#section-4 + // + // MacData ::= SEQUENCE { + // mac DigestInfo, + // macSalt OCTET STRING, + // iterations INTEGER DEFAULT 1 + // -- Note: The default is for historical reasons and its use is + // -- deprecated. + // } + tmpWriter.PushSequence(); + { + tmpWriter.PushSequence(); + { + tmpWriter.PushSequence(); + { + tmpWriter.WriteObjectIdentifier(Oids.Sha1); + tmpWriter.PopSequence(); + } + + tmpWriter.WriteOctetString(macSpan); + tmpWriter.PopSequence(); + } + + tmpWriter.WriteOctetString(macSalt); + tmpWriter.WriteInteger(s_windowsPbe.IterationCount); + + tmpWriter.PopSequence(); + } + + tmpWriter.PopSequence(); + return tmpWriter.Encode(); + } + } +} diff --git a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index 86c415e5a1b3..8441047044db 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -442,6 +442,7 @@ + From 9f5820e42cf158a386e0f5280c9b26b491320e79 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 19 Sep 2019 09:48:18 -0700 Subject: [PATCH 09/61] Re-enable a bunch of tests on macOS --- .../tests/CollectionTests.cs | 2 -- .../tests/ExportTests.cs | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs index 2b3a90f8004c..2a5f725cee2f 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/CollectionTests.cs @@ -730,7 +730,6 @@ public static void ExportEmpty_Pkcs12() } [Fact] - [ActiveIssue(16705, TestPlatforms.OSX)] public static void ExportUnrelatedPfx() { // Export multiple certificates which are not part of any kind of certificate chain. @@ -831,7 +830,6 @@ public static void ExportMultiplePrivateKeys() } [Fact] - [ActiveIssue(26397, TestPlatforms.OSX)] public static void CanAddMultipleCertsWithSinglePrivateKey() { using (var oneWithKey = new X509Certificate2(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable | Cert.EphemeralIfPossible)) diff --git a/src/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs index 24dfa156c5eb..c473d80895fa 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/ExportTests.cs @@ -48,7 +48,6 @@ public static void ExportAsSerializedCert_Unix() } [Fact] - [ActiveIssue(16705, TestPlatforms.OSX)] public static void ExportAsPfx() { using (X509Certificate2 c1 = new X509Certificate2(TestData.MsCertificate)) @@ -65,7 +64,6 @@ public static void ExportAsPfx() } [Fact] - [ActiveIssue(16705, TestPlatforms.OSX)] public static void ExportAsPfxWithPassword() { const string password = "Cotton"; @@ -84,7 +82,6 @@ public static void ExportAsPfxWithPassword() } [Fact] - [ActiveIssue(16705, TestPlatforms.OSX)] public static void ExportAsPfxVerifyPassword() { const string password = "Cotton"; From 12771bce409bf2042c05b4d9436bbaab51c384ad Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 19 Sep 2019 11:41:14 -0700 Subject: [PATCH 10/61] Fix macOS compile --- ...urity.Cryptography.X509Certificates.csproj | 184 +++++++++--------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index 8441047044db..7b0396a7d9f9 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -308,52 +308,6 @@ System\Security\Cryptography\X509Certificates\Asn1\DistributionPointNameAsn.xml - - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.manual.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml @@ -368,52 +322,6 @@ Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml - - Common\System\Security\Cryptography\Asn1\PBEParameter.xml - - - Common\System\Security\Cryptography\Asn1\PBEParameter.xml.cs - Common\System\Security\Cryptography\Asn1\PBEParameter.xml - - - Common\System\Security\Cryptography\Asn1\PBES2Params.xml - - - Common\System\Security\Cryptography\Asn1\PBES2Params.xml.cs - Common\System\Security\Cryptography\Asn1\PBES2Params.xml - - - Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml - - - Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml.cs - Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml - - - Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml - - - Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml.cs - Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml - - - Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml - - - Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml.cs - Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml - - - Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.manual.cs - Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml - - - Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml - Common\System\Security\Cryptography\KeyFormatHelper.cs @@ -735,6 +643,52 @@ + + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\EncryptedPrivateKeyInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\CertBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\MacData.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.manual.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\PfxAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs12\SafeBagAsn.xml + Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml @@ -742,6 +696,52 @@ Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml.cs Common\System\Security\Cryptography\Asn1\Pkcs7\ContentInfoAsn.xml + + Common\System\Security\Cryptography\Asn1\PBEParameter.xml + + + Common\System\Security\Cryptography\Asn1\PBEParameter.xml.cs + Common\System\Security\Cryptography\Asn1\PBEParameter.xml + + + Common\System\Security\Cryptography\Asn1\PBES2Params.xml + + + Common\System\Security\Cryptography\Asn1\PBES2Params.xml.cs + Common\System\Security\Cryptography\Asn1\PBES2Params.xml + + + Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml + + + Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml.cs + Common\System\Security\Cryptography\Asn1\Pbkdf2Params.xml + + + Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml + + + Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml.cs + Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml + + + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml + + + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml.cs + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml + + + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.manual.cs + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml + From 01624d8c800260eeb53d69fcc518fed25c84379f Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Fri, 20 Sep 2019 10:25:37 -0700 Subject: [PATCH 11/61] Change ExportPublicKeyAsPkcs12 to expect success on macOS --- .../tests/CertTests.cs | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/System.Security.Cryptography.X509Certificates/tests/CertTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/CertTests.cs index 20cfe52523c7..5770cd04b241 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/CertTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/CertTests.cs @@ -338,31 +338,16 @@ public static void ExportPublicKeyAsPkcs12() // Pre-condition: There's no private key Assert.False(publicOnly.HasPrivateKey); - // macOS 10.12 (Sierra) fails to create a PKCS#12 blob if it has no private keys within it. - bool shouldThrow = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + byte[] pkcs12Bytes = publicOnly.Export(X509ContentType.Pkcs12); - try + // Read it back as a collection, there should be only one cert, and it should + // be equal to the one we started with. + using (ImportedCollection ic = Cert.Import(pkcs12Bytes)) { - byte[] pkcs12Bytes = publicOnly.Export(X509ContentType.Pkcs12); + X509Certificate2Collection fromPfx = ic.Collection; - Assert.False(shouldThrow, "PKCS#12 export of a public-only certificate threw as expected"); - - // Read it back as a collection, there should be only one cert, and it should - // be equal to the one we started with. - using (ImportedCollection ic = Cert.Import(pkcs12Bytes)) - { - X509Certificate2Collection fromPfx = ic.Collection; - - Assert.Equal(1, fromPfx.Count); - Assert.Equal(publicOnly, fromPfx[0]); - } - } - catch (CryptographicException) - { - if (!shouldThrow) - { - throw; - } + Assert.Equal(1, fromPfx.Count); + Assert.Equal(publicOnly, fromPfx[0]); } } } From b1ef2da6181eb1db7a4a9cfce011ba4a1573bec7 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Fri, 20 Sep 2019 15:29:22 -0700 Subject: [PATCH 12/61] Apple p12 export uses UnixExportProvider (modulo SafePasswordHandle) --- .../OSX/Interop.CoreFoundation.CFData.cs | 25 ++---- .../src/Interop/OSX/Interop.CoreFoundation.cs | 39 ++++++++- .../Interop.SecKeyRef.Export.cs | 38 ++++---- .../Pal.OSX/AppleCertificatePal.cs | 33 +++++++ .../Pal.OSX/StorePal.ExportPal.cs | 87 +++---------------- .../Cryptography/Pal.Unix/ExportProvider.cs | 2 +- .../Pal.Unix/UnixExportProvider.cs | 12 +-- ...urity.Cryptography.X509Certificates.csproj | 2 +- 8 files changed, 119 insertions(+), 119 deletions(-) diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFData.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFData.cs index e4eee4e0781f..2d5713c740b2 100644 --- a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFData.cs +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFData.cs @@ -20,6 +20,13 @@ internal static partial class CoreFoundation [DllImport(Libraries.CoreFoundationLibrary)] private static extern CFIndex CFDataGetLength(SafeCFDataHandle cfData); + internal static unsafe Span CFDataDangerousGetSpan(SafeCFDataHandle cfData) + { + long length = CFDataGetLength(cfData).ToInt64(); + byte* dataBytes = CFDataGetBytePtr(cfData); + return new Span(dataBytes, checked((int)length)); + } + internal static byte[] CFGetData(SafeCFDataHandle cfData) { bool addedRef = false; @@ -27,23 +34,7 @@ internal static byte[] CFGetData(SafeCFDataHandle cfData) try { cfData.DangerousAddRef(ref addedRef); - long length = CFDataGetLength(cfData).ToInt64(); - - if (length == 0) - { - return Array.Empty(); - } - - byte[] bytes = new byte[length]; - - unsafe - { - byte* dataBytes = CFDataGetBytePtr(cfData); - Marshal.Copy((IntPtr)dataBytes, bytes, 0, bytes.Length); - } - - return bytes; - + return CFDataDangerousGetSpan(cfData).ToArray(); } finally { diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.cs index bbe4c1a8c08b..254e54eee6b6 100644 --- a/src/Common/src/Interop/OSX/Interop.CoreFoundation.cs +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.cs @@ -10,7 +10,7 @@ using CFStringRef = System.IntPtr; using CFArrayRef = System.IntPtr; - +using CFIndex = System.IntPtr; internal static partial class Interop { @@ -38,6 +38,24 @@ private enum CFStringBuiltInEncodings : uint kCFStringEncodingUTF32LE = 0x1c000100 } + /// + /// Creates a CFStringRef from a specified range of memory with a specified encoding. + /// Follows the "Create Rule" where if you create it, you delete it. + /// + /// Should be IntPtr.Zero + /// The pointer to the beginning of the encoded string. + /// The number of bytes in the encoding to read. + /// The encoding type. + /// Whether or not a BOM is present. + /// A CFStringRef on success, otherwise a SafeCreateHandle(IntPtr.Zero). + [DllImport(Interop.Libraries.CoreFoundationLibrary)] + private static extern SafeCreateHandle CFStringCreateWithBytes( + IntPtr alloc, + IntPtr bytes, + CFIndex numBytes, + CFStringBuiltInEncodings encoding, + bool isExternalRepresentation); + /// /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. /// @@ -86,6 +104,25 @@ internal static SafeCreateHandle CFStringCreateWithCString(IntPtr utf8str) return CFStringCreateWithCString(IntPtr.Zero, utf8str, CFStringBuiltInEncodings.kCFStringEncodingUTF8); } + /// + /// Creates a CFStringRef from a span of chars. + /// Follows the "Create Rule" where if you create it, you delete it. + /// + /// The chars to make a CFString version of. + /// A CFStringRef on success, otherwise a SafeCreateHandle(IntPtr.Zero). + internal static unsafe SafeCreateHandle CFStringCreateFromSpan(ReadOnlySpan source) + { + fixed (char* sourcePtr = source) + { + return CFStringCreateWithBytes( + IntPtr.Zero, + (IntPtr)sourcePtr, + new CFIndex(source.Length * 2), + CFStringBuiltInEncodings.kCFStringEncodingUTF16, + isExternalRepresentation: false); + } + } + /// /// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it. /// diff --git a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.Export.cs b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.Export.cs index 3c3345bfbae3..dbb5b7fe1f2e 100644 --- a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.Export.cs +++ b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.Export.cs @@ -23,13 +23,13 @@ private static extern int AppleCryptoNative_SecKeyExport( out SafeCFDataHandle cfDataOut, out int pOSStatus); - internal static byte[] SecKeyExport( + internal static SafeCFDataHandle SecKeyExportData( SafeSecKeyRefHandle key, bool exportPrivate, - string password) + ReadOnlySpan password) { SafeCreateHandle exportPassword = exportPrivate - ? CoreFoundation.CFStringCreateWithCString(password) + ? CoreFoundation.CFStringCreateFromSpan(password) : s_nullExportString; int ret; @@ -53,25 +53,29 @@ internal static byte[] SecKeyExport( } } - byte[] exportedData; + if (ret == 1) + { + return cfData; + } - using (cfData) + if (ret == 0) { - if (ret == 0) - { - throw CreateExceptionForOSStatus(osStatus); - } + throw CreateExceptionForOSStatus(osStatus); + } - if (ret != 1) - { - Debug.Fail($"AppleCryptoNative_SecKeyExport returned {ret}"); - throw new CryptographicException(); - } + Debug.Fail($"AppleCryptoNative_SecKeyExport returned {ret}"); + throw new CryptographicException(); + } - exportedData = CoreFoundation.CFGetData(cfData); + internal static byte[] SecKeyExport( + SafeSecKeyRefHandle key, + bool exportPrivate, + string password) + { + using (SafeCFDataHandle cfData = SecKeyExportData(key, exportPrivate, password)) + { + return CoreFoundation.CFGetData(cfData); } - - return exportedData; } } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs index 8d68a6473ff9..30eaaac6cc64 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Security.Cryptography; using System.Security.Cryptography.Apple; +using System.Security.Cryptography.Asn1; using System.Security.Cryptography.X509Certificates; using System.Text; using Microsoft.Win32.SafeHandles; @@ -359,6 +361,37 @@ public byte[] SubjectPublicKeyInfo } } + internal unsafe byte[] ExportPkcs8(ReadOnlySpan password) + { + Debug.Assert(_identityHandle != null); + + using (SafeSecKeyRefHandle key = Interop.AppleCrypto.X509GetPrivateKeyFromIdentity(_identityHandle)) + using (SafeCFDataHandle data = Interop.AppleCrypto.SecKeyExportData(key, exportPrivate: true, password)) + { + ReadOnlySpan systemExport = Interop.CoreFoundation.CFDataDangerousGetSpan(data); + + fixed (byte* ptr = systemExport) + { + using (PointerMemoryManager manager = new PointerMemoryManager(ptr, systemExport.Length)) + { + // Apple's PKCS8 export exports using PBES2, which Win7, Win8.1, and Apple all fail to + // understand in their PKCS12 readers, so re-encrypt using the Win7 PKCS12-PBE parameters. + // + // Since Apple only reliably exports keys with encrypted PKCS#8 there's not a + // "so export it plaintext and only encrypt it once" option. + using (AsnWriter writer = KeyFormatHelper.ReencryptPkcs8( + password, + manager.Memory, + password, + UnixExportProvider.WindowsPbe)) + { + return writer.Encode(); + } + } + } + } + } + public RSA GetRSAPrivateKey() { if (_identityHandle == null) diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.ExportPal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.ExportPal.cs index ba5dae2c87af..d28d9a7bb25b 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.ExportPal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.ExportPal.cs @@ -12,90 +12,19 @@ namespace Internal.Cryptography.Pal { internal sealed partial class StorePal { - private sealed class AppleCertificateExporter : IExportPal + private sealed class AppleCertificateExporter : UnixExportProvider { - private X509Certificate2Collection _certs; - private ICertificatePalCore _singleCertPal; - public AppleCertificateExporter(ICertificatePalCore cert) + : base(cert) { - _singleCertPal = cert; } public AppleCertificateExporter(X509Certificate2Collection certs) + : base(certs) { - _certs = certs; - } - - public void Dispose() - { - // Don't dispose any of the resources, they're still owned by the caller. - _singleCertPal = null; - _certs = null; - } - - public byte[] Export(X509ContentType contentType, SafePasswordHandle password) - { - Debug.Assert(password != null); - switch (contentType) - { - case X509ContentType.Cert: - return ExportX509Der(); - case X509ContentType.Pkcs12: - return ExportPkcs12(password); - case X509ContentType.Pkcs7: - return ExportPkcs7(); - case X509ContentType.SerializedCert: - case X509ContentType.SerializedStore: - throw new PlatformNotSupportedException(SR.Cryptography_Unix_X509_SerializedExport); - default: - throw new CryptographicException(SR.Cryptography_X509_InvalidContentType); - } - } - - private byte[] ExportX509Der() - { - if (_singleCertPal != null) - { - return _singleCertPal.RawData; - } - - // Windows/Desktop compatibility: Exporting a collection (or store) as - // X509ContentType.Cert returns the equivalent of FirstOrDefault(), - // so anything past _certs[0] is ignored, and an empty collection is - // null (not an Exception) - if (_certs.Count == 0) - { - return null; - } - - return _certs[0].RawData; } - private byte[] ExportPkcs12(SafePasswordHandle password) - { - IntPtr[] certHandles; - - if (_singleCertPal != null) - { - certHandles = new[] { _singleCertPal.Handle }; - } - else - { - certHandles = new IntPtr[_certs.Count]; - - for (int i = 0; i < _certs.Count; i++) - { - certHandles[i] = _certs[i].Handle; - } - } - - byte[] exported = Interop.AppleCrypto.X509ExportPfx(certHandles, password); - GC.KeepAlive(_certs); // ensure certs' safe handles aren't finalized while raw handles are in use - return exported; - } - - private byte[] ExportPkcs7() + protected override byte[] ExportPkcs7() { IntPtr[] certHandles; @@ -116,6 +45,12 @@ private byte[] ExportPkcs7() return Interop.AppleCrypto.X509ExportPkcs7(certHandles); } + + protected override byte[] ExportPkcs8(ICertificatePalCore certificatePal, ReadOnlySpan password) + { + AppleCertificatePal pal = (AppleCertificatePal)certificatePal; + return pal.ExportPkcs8(password); + } } } -} \ No newline at end of file +} diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs index 4ce2fc4585ef..986b6023db81 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ExportProvider.cs @@ -60,7 +60,7 @@ protected override byte[] ExportPkcs8( } Debug.Assert(alg != null); - return alg.ExportEncryptedPkcs8PrivateKey(password, s_windowsPbe); + return alg.ExportEncryptedPkcs8PrivateKey(password, WindowsPbe); } private static void PushHandle(IntPtr certPtr, SafeX509StackHandle publicCerts) diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs index 6e27e82178a9..6035f2e232d5 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs @@ -20,7 +20,7 @@ internal abstract class UnixExportProvider : IExportPal private static readonly Asn1Tag ContextSpecific0 = new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true); - protected static readonly PbeParameters s_windowsPbe = + internal static readonly PbeParameters WindowsPbe = new PbeParameters(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, HashAlgorithmName.SHA1, 2000); protected ICertificatePalCore _singleCertPal; @@ -332,7 +332,7 @@ private static ArraySegment EncodeCerts( tmpWriter.PushSequence(); PasswordBasedEncryption.InitiateEncryption( - s_windowsPbe, + WindowsPbe, out SymmetricAlgorithm cipher, out hmacOid, out encryptionAlgorithmOid, @@ -385,7 +385,7 @@ private static ArraySegment EncodeCerts( cipher, isPkcs12, contentsSpan, - s_windowsPbe, + WindowsPbe, salt, certContents, certContentsIv); @@ -448,7 +448,7 @@ private static ArraySegment EncodeAuthSafe( isPkcs12, encryptionAlgorithmOid, salt, - s_windowsPbe.IterationCount, + WindowsPbe.IterationCount, hmacOid, certContentsIv); @@ -490,7 +490,7 @@ private static unsafe byte[] MacAndEncode( Pkcs12Kdf.DeriveMacKey( passwordSpan, hashAlgorithm, - s_windowsPbe.IterationCount, + WindowsPbe.IterationCount, macSalt, macKey); @@ -554,7 +554,7 @@ private static unsafe byte[] MacAndEncode( } tmpWriter.WriteOctetString(macSalt); - tmpWriter.WriteInteger(s_windowsPbe.IterationCount); + tmpWriter.WriteInteger(WindowsPbe.IterationCount); tmpWriter.PopSequence(); } diff --git a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index 7b0396a7d9f9..55028922ce8a 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -350,7 +350,6 @@ - @@ -746,6 +745,7 @@ + From 7f47141781d18e0f1908c53e5b01c8b4d822f7e4 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Fri, 20 Sep 2019 18:14:31 -0700 Subject: [PATCH 13/61] Factor out UnixPkcs12Reader --- .../Pal.Unix/OpenSslPkcs12Reader.cs | 478 +--------------- .../Cryptography/Pal.Unix/PkcsFormatReader.cs | 86 +-- .../Cryptography/Pal.Unix/UnixPkcs12Reader.cs | 512 ++++++++++++++++++ 3 files changed, 546 insertions(+), 530 deletions(-) create mode 100644 src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs index 67e05eff9379..4b66ad43e1b5 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslPkcs12Reader.cs @@ -2,36 +2,27 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Win32.SafeHandles; using System; -using System.Buffers; -using System.Diagnostics; -using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.Asn1; -using System.Security.Cryptography.Asn1.Pkcs12; -using System.Security.Cryptography.Asn1.Pkcs7; -using System.Security.Cryptography.Pkcs.Asn1; -using System.Threading; namespace Internal.Cryptography.Pal { - internal sealed class OpenSslPkcs12Reader : IDisposable + internal sealed class OpenSslPkcs12Reader : UnixPkcs12Reader { - private const string DecryptedSentinel = nameof(OpenSslPkcs12Reader); - - private PfxAsn _pfxAsn; - private ContentInfoAsn[] _safeContentsValues; - private OpenSslX509CertificateReader[] _certs; - - private OpenSslPkcs12Reader(AsnReader reader) + private OpenSslPkcs12Reader(byte[] data) { - PfxAsn.Decode(reader, out _pfxAsn); + ParsePkcs12(data); + } - if (_pfxAsn.AuthSafe.ContentType != Oids.Pkcs7Data) + protected override ICertificatePalCore ReadX509Der(ReadOnlyMemory data) + { + if (OpenSslX509CertificateReader.TryReadX509Der(data.Span, out ICertificatePal ret)) { - throw new CryptographicException("Unsupported PFX."); + return ret; } + + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } public static bool TryRead(byte[] data, out OpenSslPkcs12Reader pkcs12Reader) => @@ -40,400 +31,7 @@ public static bool TryRead(byte[] data, out OpenSslPkcs12Reader pkcs12Reader) => public static bool TryRead(byte[] data, out OpenSslPkcs12Reader pkcs12Reader, out Exception openSslException) => TryRead(data, out pkcs12Reader, out openSslException, captureException: true); - public static bool TryRead(SafeBioHandle fileBio, out OpenSslPkcs12Reader pkcs12Reader, out Exception openSslException) => - TryRead(fileBio, out pkcs12Reader, out openSslException, captureException: true); - - public void Dispose() - { - ContentInfoAsn[] rentedContents = Interlocked.Exchange(ref _safeContentsValues, null); - OpenSslX509CertificateReader[] rentedCerts = Interlocked.Exchange(ref _certs, null); - - if (rentedContents != null) - { - for (int i = 0; i < rentedContents.Length; i++) - { - string contentType = rentedContents[0].ContentType; - - if (contentType == null) - { - break; - } - - if (contentType == DecryptedSentinel) - { - ReadOnlyMemory content = rentedContents[0].Content; - - if (!MemoryMarshal.TryGetArray(content, out ArraySegment segment)) - { - Debug.Fail("Couldn't unpack decrypted buffer."); - } - - CryptoPool.Return(segment.Array, segment.Count); - rentedContents[0].Content = default; - } - } - - ArrayPool.Shared.Return(rentedContents, clearArray: true); - } - - if (rentedCerts != null) - { - ArrayPool.Shared.Return(rentedCerts, clearArray: true); - } - } - - public unsafe void Decrypt(SafePasswordHandle password) - { - ReadOnlyMemory authSafeSpan = - Helpers.DecodeOctetStringAsMemory(_pfxAsn.AuthSafe.Content); - - bool hasRef = false; - password.DangerousAddRef(ref hasRef); - - try - { - ReadOnlySpan passwordChars = - new ReadOnlySpan((char*)password.DangerousGetHandle(), password.Length); - - if (_pfxAsn.MacData.HasValue) - { - VerifyAndDecrypt(passwordChars, authSafeSpan); - } - else - { - Decrypt(passwordChars, authSafeSpan); - } - } - catch (Exception e) - { - throw new CryptographicException("Bad password.", e); - } - finally - { - password.DangerousRelease(); - } - } - - private void VerifyAndDecrypt(ReadOnlySpan password, ReadOnlyMemory authSafeContents) - { - Debug.Assert(_pfxAsn.MacData.HasValue); - ReadOnlySpan authSafeSpan = authSafeContents.Span; - - if (password.Length == 0) - { - if (_pfxAsn.VerifyMac("", authSafeSpan)) - { - Decrypt("", authSafeContents); - return; - } - - if (_pfxAsn.VerifyMac(default, authSafeSpan)) - { - Decrypt(default, authSafeContents); - return; - } - } - else if (_pfxAsn.VerifyMac(password, authSafeSpan)) - { - Decrypt(password, authSafeContents); - return; - } - - throw new CryptographicException("Password did not verify MAC."); - } - - private void Decrypt(ReadOnlySpan password, ReadOnlyMemory authSafeContents) - { - if (_safeContentsValues == null) - { - // The expected number of ContentInfoAsns to read is 2, one encrypted (contains certs), - // and one plain (contains encrypted keys) - ContentInfoAsn[] rented = ArrayPool.Shared.Rent(10); - - AsnReader outer = new AsnReader(authSafeContents, AsnEncodingRules.BER); - AsnReader reader = outer.ReadSequence(); - outer.ThrowIfNotEmpty(); - int i = 0; - - while (reader.HasData) - { - GrowIfNeeded(ref rented, i); - ContentInfoAsn.Decode(reader, out rented[i]); - i++; - } - - rented.AsSpan(i).Clear(); - _safeContentsValues = rented; - } - - // The average PFX contains one cert, and one key. - // The next most common PFX contains 3 certs, and one key. - // - // Nothing requires that there be fewer keys than certs, - // but it's sort of nonsensical when loading this way. - CertBagAsn[] certBags = ArrayPool.Shared.Rent(10); - AttributeAsn[][] certBagAttrs = ArrayPool.Shared.Rent(10); - SafeBagAsn[] keyBags = ArrayPool.Shared.Rent(10); - AsymmetricAlgorithm[] keys = null; - int certBagIdx = 0; - int keyBagIdx = 0; - - try - { - for (int i = 0; i < _safeContentsValues.Length; i++) - { - string contentType = _safeContentsValues[i].ContentType; - bool process = false; - - if (contentType == null) - { - break; - } - - // Should enveloped throw here? - if (contentType == Oids.Pkcs7Data) - { - process = true; - } - else if (contentType == Oids.Pkcs7Encrypted) - { - DecryptSafeContents(password, ref _safeContentsValues[i]); - process = true; - } - - if (process) - { - ProcessSafeContents( - _safeContentsValues[i], - ref certBags, - ref certBagAttrs, - ref certBagIdx, - ref keyBags, - ref keyBagIdx); - } - } - - _certs = ArrayPool.Shared.Rent(certBagIdx); - _certs.AsSpan().Clear(); - - keys = ArrayPool.Shared.Rent(keyBagIdx); - keys.AsSpan().Clear(); - - // Windows Compat: Load all keys before matching. - // Unloadable key? Unloadable PFX. - for (int i = keyBagIdx - 1; i >= 0; i--) - { - keys[i] = LoadKey(keyBags[i], password); - } - - int certsCount = certBagIdx; - - for (certBagIdx--; certBagIdx >= 0; certBagIdx--) - { - int matchingKeyIdx = -1; - - foreach (AttributeAsn attr in certBagAttrs[certBagIdx] ?? Array.Empty()) - { - if (attr.AttrType.Value == Oids.LocalKeyId && attr.AttrValues.Length > 0) - { - matchingKeyIdx = FindMatchingKey( - keyBags, - keyBagIdx, - Helpers.DecodeOctetStringAsMemory(attr.AttrValues[0]).Span); - - // Only try the first one. - break; - } - } - - ReadOnlyMemory x509Data = - Helpers.DecodeOctetStringAsMemory(certBags[certBagIdx].CertValue); - - if (!OpenSslX509CertificateReader.TryReadX509Der(x509Data.Span, out ICertificatePal pal)) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); - } - - _certs[certBagIdx] = (OpenSslX509CertificateReader)pal; - - if (matchingKeyIdx != -1) - { - _certs[certBagIdx].SetPrivateKey(GetPrivateKey(keys[matchingKeyIdx])); - } - } - - Array.Reverse(_certs, 0, certsCount); - } - catch - { - if (_certs != null) - { - foreach (OpenSslX509CertificateReader cert in _certs) - { - cert?.Dispose(); - } - } - - throw; - } - finally - { - if (keys != null) - { - foreach (AsymmetricAlgorithm key in keys) - { - key?.Dispose(); - } - - ArrayPool.Shared.Return(keys); - } - - ArrayPool.Shared.Return(certBags, clearArray: true); - ArrayPool.Shared.Return(certBagAttrs, clearArray: true); - ArrayPool.Shared.Return(keyBags, clearArray: true); - } - } - - private static int FindMatchingKey( - SafeBagAsn[] keyBags, - int keyBagCount, - ReadOnlySpan localKeyId) - { - for (int i = 0; i < keyBagCount; i++) - { - foreach (AttributeAsn attr in keyBags[i].BagAttributes) - { - if (attr.AttrType.Value == Oids.LocalKeyId && attr.AttrValues.Length > 0) - { - ReadOnlyMemory curKeyId = - Helpers.DecodeOctetStringAsMemory(attr.AttrValues[0]); - - if (curKeyId.Span.SequenceEqual(localKeyId)) - { - return i; - } - } - } - } - - return -1; - } - - private static void DecryptSafeContents( - ReadOnlySpan password, - ref ContentInfoAsn safeContentsAsn) - { - EncryptedDataAsn encryptedData = - EncryptedDataAsn.Decode(safeContentsAsn.Content, AsnEncodingRules.BER); - - // https://tools.ietf.org/html/rfc5652#section-8 - if (encryptedData.Version != 0 && encryptedData.Version != 2) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); - } - - // Since the contents are supposed to be the BER-encoding of an instance of - // SafeContents (https://tools.ietf.org/html/rfc7292#section-4.1) that implies the - // content type is simply "data", and that content is present. - if (encryptedData.EncryptedContentInfo.ContentType != Oids.Pkcs7Data) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); - } - - if (!encryptedData.EncryptedContentInfo.EncryptedContent.HasValue) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); - } - - int encryptedValueLength = encryptedData.EncryptedContentInfo.EncryptedContent.Value.Length; - - byte[] destination = CryptoPool.Rent(encryptedValueLength); - - int written = PasswordBasedEncryption.Decrypt( - encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm, - password, - default, - encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span, - destination); - - safeContentsAsn.Content = destination.AsMemory(0, written); - safeContentsAsn.ContentType = DecryptedSentinel; - } - - private static void ProcessSafeContents( - in ContentInfoAsn safeContentsAsn, - ref CertBagAsn[] certBags, - ref AttributeAsn[][] certBagAttrs, - ref int certBagIdx, - ref SafeBagAsn[] keyBags, - ref int keyBagIdx) - { - ReadOnlyMemory contentData = safeContentsAsn.Content; - - if (safeContentsAsn.ContentType == Oids.Pkcs7Data) - { - contentData = Helpers.DecodeOctetStringAsMemory(contentData); - } - - AsnReader outer = new AsnReader(contentData, AsnEncodingRules.BER); - AsnReader reader = outer.ReadSequence(); - outer.ThrowIfNotEmpty(); - - while (reader.HasData) - { - SafeBagAsn.Decode(reader, out SafeBagAsn bag); - - if (bag.BagId == Oids.Pkcs12CertBag) - { - CertBagAsn certBag = CertBagAsn.Decode(bag.BagValue, AsnEncodingRules.BER); - - if (certBag.CertId == Oids.Pkcs12X509CertBagType) - { - GrowIfNeeded(ref certBags, certBagIdx); - GrowIfNeeded(ref certBagAttrs, certBagIdx); - certBags[certBagIdx] = certBag; - certBagAttrs[certBagIdx] = bag.BagAttributes; - certBagIdx++; - } - } - else if (bag.BagId == Oids.Pkcs12KeyBag || bag.BagId == Oids.Pkcs12ShroudedKeyBag) - { - GrowIfNeeded(ref keyBags, keyBagIdx); - keyBags[keyBagIdx] = bag; - keyBagIdx++; - } - } - } - - private static AsymmetricAlgorithm LoadKey(SafeBagAsn safeBag, ReadOnlySpan password) - { - if (safeBag.BagId == Oids.Pkcs12ShroudedKeyBag) - { - ArraySegment decrypted = KeyFormatHelper.DecryptPkcs8( - password, - safeBag.BagValue, - out int localRead); - - try - { - if (localRead != safeBag.BagValue.Length) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); - } - - return LoadKey(decrypted.AsMemory()); - } - finally - { - CryptoPool.Return(decrypted.Array, clearSize: decrypted.Count); - } - } - - Debug.Assert(safeBag.BagId == Oids.Pkcs12KeyBag); - return LoadKey(safeBag.BagValue); - } - - private static AsymmetricAlgorithm LoadKey(in ReadOnlyMemory pkcs8) + protected override AsymmetricAlgorithm LoadKey(ReadOnlyMemory pkcs8) { PrivateKeyInfoAsn privateKeyInfo = PrivateKeyInfoAsn.Decode(pkcs8, AsnEncodingRules.BER); AsymmetricAlgorithm key; @@ -466,7 +64,7 @@ private static AsymmetricAlgorithm LoadKey(in ReadOnlyMemory pkcs8) return key; } - private static SafeEvpPKeyHandle GetPrivateKey(AsymmetricAlgorithm key) + internal static SafeEvpPKeyHandle GetPrivateKey(AsymmetricAlgorithm key) { if (key is RSAOpenSsl rsa) return rsa.DuplicateKeyHandle(); @@ -477,23 +75,6 @@ private static SafeEvpPKeyHandle GetPrivateKey(AsymmetricAlgorithm key) return ((ECDiffieHellmanOpenSsl)key).DuplicateKeyHandle(); } - public ArraySegment ReadCertificates() - { - Debug.Assert(_certs != null); - - int nullIdx = Array.IndexOf(_certs, null); - - if (nullIdx >= 0) - { - return new ArraySegment( - _certs, - 0, - nullIdx); - } - - return _certs; - } - private static bool TryRead( byte[] data, out OpenSslPkcs12Reader pkcs12Reader, @@ -504,15 +85,7 @@ private static bool TryRead( try { - AsnReader reader = new AsnReader(data, AsnEncodingRules.BER); - ReadOnlyMemory encodedData = reader.PeekEncodedValue(); - - if (encodedData.Length != data.Length) - { - reader = new AsnReader(encodedData, AsnEncodingRules.BER); - } - - pkcs12Reader = new OpenSslPkcs12Reader(reader); + pkcs12Reader = new OpenSslPkcs12Reader(data); return true; } catch (CryptographicException e) @@ -526,30 +99,5 @@ private static bool TryRead( return false; } } - - private static bool TryRead( - SafeBioHandle fileBio, - out OpenSslPkcs12Reader pkcs12Reader, - out Exception openSslException, - bool captureException) - { - _ = fileBio; - pkcs12Reader = null; - openSslException = captureException ? new NotImplementedException() : null; - return false; - } - - private static void GrowIfNeeded(ref T[] array, int idx) - { - T[] oldRent = array; - - if (idx >= array.Length) - { - T[] newRent = ArrayPool.Shared.Rent(array.Length * 2); - Array.Copy(oldRent, 0, newRent, 0, idx); - array = newRent; - ArrayPool.Shared.Return(oldRent, clearArray: true); - } - } } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/PkcsFormatReader.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/PkcsFormatReader.cs index 3502af8fc5ab..e864d4f165c2 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/PkcsFormatReader.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/PkcsFormatReader.cs @@ -257,13 +257,6 @@ internal static bool TryReadPkcs12(byte[] rawData, SafePasswordHandle password, return TryReadPkcs12(rawData, password, true, out certPal, out ignored, out openSslException); } - internal static bool TryReadPkcs12(SafeBioHandle bio, SafePasswordHandle password, out ICertificatePal certPal, out Exception openSslException) - { - List ignored; - - return TryReadPkcs12(bio, password, true, out certPal, out ignored, out openSslException); - } - internal static bool TryReadPkcs12(byte[] rawData, SafePasswordHandle password, out List certPals, out Exception openSslException) { ICertificatePal ignored; @@ -271,13 +264,6 @@ internal static bool TryReadPkcs12(byte[] rawData, SafePasswordHandle password, return TryReadPkcs12(rawData, password, false, out ignored, out certPals, out openSslException); } - internal static bool TryReadPkcs12(SafeBioHandle bio, SafePasswordHandle password, out List certPals, out Exception openSslException) - { - ICertificatePal ignored; - - return TryReadPkcs12(bio, password, false, out ignored, out certPals, out openSslException); - } - private static bool TryReadPkcs12( byte[] rawData, SafePasswordHandle password, @@ -302,30 +288,6 @@ private static bool TryReadPkcs12( } } - private static bool TryReadPkcs12( - SafeBioHandle bio, - SafePasswordHandle password, - bool single, - out ICertificatePal readPal, - out List readCerts, - out Exception openSslException) - { - // DER-PKCS12 - OpenSslPkcs12Reader pfx; - - if (!OpenSslPkcs12Reader.TryRead(bio, out pfx, out openSslException)) - { - readPal = null; - readCerts = null; - return false; - } - - using (pfx) - { - return TryReadPkcs12(pfx, password, single, out readPal, out readCerts); - } - } - private static bool TryReadPkcs12( OpenSslPkcs12Reader pfx, SafePasswordHandle password, @@ -335,42 +297,36 @@ private static bool TryReadPkcs12( { pfx.Decrypt(password); - ICertificatePal first = null; - List certs = null; - - if (!single) + if (single) { - certs = new List(); + UnixPkcs12Reader.CertAndKey certAndKey = pfx.GetSingleCert(); + OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)certAndKey.Cert; + + if (certAndKey.Key != null) + { + pal.SetPrivateKey(OpenSslPkcs12Reader.GetPrivateKey(certAndKey.Key)); + } + + readPal = pal; + readCerts = null; + return true; } - foreach (OpenSslX509CertificateReader certPal in pfx.ReadCertificates()) + readPal = null; + List certs = new List(pfx.GetCertCount()); + + foreach (UnixPkcs12Reader.CertAndKey certAndKey in pfx.EnumerateAll()) { - if (single) - { - // When requesting an X509Certificate2 from a PFX only the first entry is - // returned. Other entries should be disposed. + OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)certAndKey.Cert; - if (first == null) - { - first = certPal; - } - else if (certPal.HasPrivateKey && !first.HasPrivateKey) - { - first.Dispose(); - first = certPal; - } - else - { - certPal.Dispose(); - } - } - else + if (certAndKey.Key != null) { - certs.Add(certPal); + pal.SetPrivateKey(OpenSslPkcs12Reader.GetPrivateKey(certAndKey.Key)); } + + certs.Add(pal); } - readPal = first; readCerts = certs; return true; } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs new file mode 100644 index 000000000000..1098a15cd3d4 --- /dev/null +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs @@ -0,0 +1,512 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Asn1.Pkcs12; +using System.Security.Cryptography.Asn1.Pkcs7; +using System.Security.Cryptography.Pkcs.Asn1; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal abstract class UnixPkcs12Reader : IDisposable + { + private const string DecryptedSentinel = nameof(UnixPkcs12Reader); + + private PfxAsn _pfxAsn; + private ContentInfoAsn[] _safeContentsValues; + private CertAndKey[] _certs; + private int _certCount; + + protected abstract ICertificatePalCore ReadX509Der(ReadOnlyMemory data); + protected abstract AsymmetricAlgorithm LoadKey(ReadOnlyMemory safeBagBagValue); + + protected void ParsePkcs12(byte[] data) + { + // RFC7292 specifies BER instead of DER + AsnReader reader = new AsnReader(data, AsnEncodingRules.BER); + ReadOnlyMemory encodedData = reader.PeekEncodedValue(); + + // Windows compatibility: Ignore trailing data. + if (encodedData.Length != data.Length) + { + reader = new AsnReader(encodedData, AsnEncodingRules.BER); + } + + PfxAsn.Decode(reader, out _pfxAsn); + + if (_pfxAsn.AuthSafe.ContentType != Oids.Pkcs7Data) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + } + + internal CertAndKey GetSingleCert() + { + CertAndKey[] certs = _certs; + Debug.Assert(certs != null); + + if (_certCount < 1) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + CertAndKey ret; + + for (int i = _certCount - 1; i >= 0; --i) + { + if (certs[i].Key != null) + { + ret = certs[i]; + certs[i] = default; + return ret; + } + } + + ret = certs[_certCount - 1]; + certs[_certCount - 1] = default; + return ret; + } + + internal int GetCertCount() + { + return _certCount; + } + + internal IEnumerable EnumerateAll() + { + while (_certCount > 0) + { + int idx = _certCount - 1; + CertAndKey ret = _certs[idx]; + _certs[idx] = default; + _certCount--; + yield return ret; + } + } + + public void Dispose() + { + ContentInfoAsn[] rentedContents = Interlocked.Exchange(ref _safeContentsValues, null); + CertAndKey[] rentedCerts = Interlocked.Exchange(ref _certs, null); + + if (rentedContents != null) + { + for (int i = 0; i < rentedContents.Length; i++) + { + string contentType = rentedContents[0].ContentType; + + if (contentType == null) + { + break; + } + + if (contentType == DecryptedSentinel) + { + ReadOnlyMemory content = rentedContents[0].Content; + + if (!MemoryMarshal.TryGetArray(content, out ArraySegment segment)) + { + Debug.Fail("Couldn't unpack decrypted buffer."); + } + + CryptoPool.Return(segment.Array, segment.Count); + rentedContents[0].Content = default; + } + } + + ArrayPool.Shared.Return(rentedContents, clearArray: true); + } + + if (rentedCerts != null) + { + for (int i = _certCount - 1; i >= 0; --i) + { + rentedCerts[i].Dispose(); + } + + ArrayPool.Shared.Return(rentedCerts, clearArray: true); + } + } + + public unsafe void Decrypt(SafePasswordHandle password) + { + ReadOnlyMemory authSafeSpan = + Helpers.DecodeOctetStringAsMemory(_pfxAsn.AuthSafe.Content); + + bool hasRef = false; + password.DangerousAddRef(ref hasRef); + + try + { + ReadOnlySpan passwordChars = + new ReadOnlySpan((char*)password.DangerousGetHandle(), password.Length); + + if (_pfxAsn.MacData.HasValue) + { + VerifyAndDecrypt(passwordChars, authSafeSpan); + } + else + { + Decrypt(passwordChars, authSafeSpan); + } + } + catch (Exception e) + { + throw new CryptographicException("Bad password.", e); + } + finally + { + password.DangerousRelease(); + } + } + + private void VerifyAndDecrypt(ReadOnlySpan password, ReadOnlyMemory authSafeContents) + { + Debug.Assert(_pfxAsn.MacData.HasValue); + ReadOnlySpan authSafeSpan = authSafeContents.Span; + + if (password.Length == 0) + { + if (_pfxAsn.VerifyMac("", authSafeSpan)) + { + Decrypt("", authSafeContents); + return; + } + + if (_pfxAsn.VerifyMac(default, authSafeSpan)) + { + Decrypt(default, authSafeContents); + return; + } + } + else if (_pfxAsn.VerifyMac(password, authSafeSpan)) + { + Decrypt(password, authSafeContents); + return; + } + + throw new CryptographicException("Password did not verify MAC."); + } + + private void Decrypt(ReadOnlySpan password, ReadOnlyMemory authSafeContents) + { + if (_safeContentsValues == null) + { + // The expected number of ContentInfoAsns to read is 2, one encrypted (contains certs), + // and one plain (contains encrypted keys) + ContentInfoAsn[] rented = ArrayPool.Shared.Rent(10); + + AsnReader outer = new AsnReader(authSafeContents, AsnEncodingRules.BER); + AsnReader reader = outer.ReadSequence(); + outer.ThrowIfNotEmpty(); + int i = 0; + + while (reader.HasData) + { + GrowIfNeeded(ref rented, i); + ContentInfoAsn.Decode(reader, out rented[i]); + i++; + } + + rented.AsSpan(i).Clear(); + _safeContentsValues = rented; + } + + // The average PFX contains one cert, and one key. + // The next most common PFX contains 3 certs, and one key. + // + // Nothing requires that there be fewer keys than certs, + // but it's sort of nonsensical when loading this way. + CertBagAsn[] certBags = ArrayPool.Shared.Rent(10); + AttributeAsn[][] certBagAttrs = ArrayPool.Shared.Rent(10); + SafeBagAsn[] keyBags = ArrayPool.Shared.Rent(10); + AsymmetricAlgorithm[] keys = null; + int certBagIdx = 0; + int keyBagIdx = 0; + + try + { + for (int i = 0; i < _safeContentsValues.Length; i++) + { + string contentType = _safeContentsValues[i].ContentType; + bool process = false; + + if (contentType == null) + { + break; + } + + // Should enveloped throw here? + if (contentType == Oids.Pkcs7Data) + { + process = true; + } + else if (contentType == Oids.Pkcs7Encrypted) + { + DecryptSafeContents(password, ref _safeContentsValues[i]); + process = true; + } + + if (process) + { + ProcessSafeContents( + _safeContentsValues[i], + ref certBags, + ref certBagAttrs, + ref certBagIdx, + ref keyBags, + ref keyBagIdx); + } + } + + _certs = ArrayPool.Shared.Rent(certBagIdx); + _certs.AsSpan().Clear(); + + keys = ArrayPool.Shared.Rent(keyBagIdx); + keys.AsSpan().Clear(); + + // Windows Compat: Load all keys before matching. + // Unloadable key? Unloadable PFX. + for (int i = keyBagIdx - 1; i >= 0; i--) + { + keys[i] = LoadKey(keyBags[i], password); + } + + int certsCount = certBagIdx; + + for (certBagIdx--; certBagIdx >= 0; certBagIdx--) + { + int matchingKeyIdx = -1; + + foreach (AttributeAsn attr in certBagAttrs[certBagIdx] ?? Array.Empty()) + { + if (attr.AttrType.Value == Oids.LocalKeyId && attr.AttrValues.Length > 0) + { + matchingKeyIdx = FindMatchingKey( + keyBags, + keyBagIdx, + Helpers.DecodeOctetStringAsMemory(attr.AttrValues[0]).Span); + + // Only try the first one. + break; + } + } + + ReadOnlyMemory x509Data = + Helpers.DecodeOctetStringAsMemory(certBags[certBagIdx].CertValue); + + _certs[certBagIdx].Cert = ReadX509Der(x509Data); + + if (matchingKeyIdx != -1) + { + _certs[certBagIdx].Key = keys[matchingKeyIdx]; + keys[matchingKeyIdx] = null; + } + } + + _certCount = certsCount; + } + catch + { + if (_certs != null) + { + foreach (CertAndKey certAndKey in _certs) + { + certAndKey.Dispose(); + } + } + + throw; + } + finally + { + if (keys != null) + { + foreach (AsymmetricAlgorithm key in keys) + { + key?.Dispose(); + } + + ArrayPool.Shared.Return(keys); + } + + ArrayPool.Shared.Return(certBags, clearArray: true); + ArrayPool.Shared.Return(certBagAttrs, clearArray: true); + ArrayPool.Shared.Return(keyBags, clearArray: true); + } + } + + private static int FindMatchingKey( + SafeBagAsn[] keyBags, + int keyBagCount, + ReadOnlySpan localKeyId) + { + for (int i = 0; i < keyBagCount; i++) + { + foreach (AttributeAsn attr in keyBags[i].BagAttributes) + { + if (attr.AttrType.Value == Oids.LocalKeyId && attr.AttrValues.Length > 0) + { + ReadOnlyMemory curKeyId = + Helpers.DecodeOctetStringAsMemory(attr.AttrValues[0]); + + if (curKeyId.Span.SequenceEqual(localKeyId)) + { + return i; + } + } + } + } + + return -1; + } + + private static void DecryptSafeContents( + ReadOnlySpan password, + ref ContentInfoAsn safeContentsAsn) + { + EncryptedDataAsn encryptedData = + EncryptedDataAsn.Decode(safeContentsAsn.Content, AsnEncodingRules.BER); + + // https://tools.ietf.org/html/rfc5652#section-8 + if (encryptedData.Version != 0 && encryptedData.Version != 2) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + // Since the contents are supposed to be the BER-encoding of an instance of + // SafeContents (https://tools.ietf.org/html/rfc7292#section-4.1) that implies the + // content type is simply "data", and that content is present. + if (encryptedData.EncryptedContentInfo.ContentType != Oids.Pkcs7Data) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + if (!encryptedData.EncryptedContentInfo.EncryptedContent.HasValue) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + int encryptedValueLength = encryptedData.EncryptedContentInfo.EncryptedContent.Value.Length; + + byte[] destination = CryptoPool.Rent(encryptedValueLength); + + int written = PasswordBasedEncryption.Decrypt( + encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm, + password, + default, + encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span, + destination); + + safeContentsAsn.Content = destination.AsMemory(0, written); + safeContentsAsn.ContentType = DecryptedSentinel; + } + + private static void ProcessSafeContents( + in ContentInfoAsn safeContentsAsn, + ref CertBagAsn[] certBags, + ref AttributeAsn[][] certBagAttrs, + ref int certBagIdx, + ref SafeBagAsn[] keyBags, + ref int keyBagIdx) + { + ReadOnlyMemory contentData = safeContentsAsn.Content; + + if (safeContentsAsn.ContentType == Oids.Pkcs7Data) + { + contentData = Helpers.DecodeOctetStringAsMemory(contentData); + } + + AsnReader outer = new AsnReader(contentData, AsnEncodingRules.BER); + AsnReader reader = outer.ReadSequence(); + outer.ThrowIfNotEmpty(); + + while (reader.HasData) + { + SafeBagAsn.Decode(reader, out SafeBagAsn bag); + + if (bag.BagId == Oids.Pkcs12CertBag) + { + CertBagAsn certBag = CertBagAsn.Decode(bag.BagValue, AsnEncodingRules.BER); + + if (certBag.CertId == Oids.Pkcs12X509CertBagType) + { + GrowIfNeeded(ref certBags, certBagIdx); + GrowIfNeeded(ref certBagAttrs, certBagIdx); + certBags[certBagIdx] = certBag; + certBagAttrs[certBagIdx] = bag.BagAttributes; + certBagIdx++; + } + } + else if (bag.BagId == Oids.Pkcs12KeyBag || bag.BagId == Oids.Pkcs12ShroudedKeyBag) + { + GrowIfNeeded(ref keyBags, keyBagIdx); + keyBags[keyBagIdx] = bag; + keyBagIdx++; + } + } + } + + private AsymmetricAlgorithm LoadKey(SafeBagAsn safeBag, ReadOnlySpan password) + { + if (safeBag.BagId == Oids.Pkcs12ShroudedKeyBag) + { + ArraySegment decrypted = KeyFormatHelper.DecryptPkcs8( + password, + safeBag.BagValue, + out int localRead); + + try + { + if (localRead != safeBag.BagValue.Length) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + return LoadKey(decrypted.AsMemory()); + } + finally + { + CryptoPool.Return(decrypted.Array, clearSize: decrypted.Count); + } + } + + Debug.Assert(safeBag.BagId == Oids.Pkcs12KeyBag); + return LoadKey(safeBag.BagValue); + } + + private static void GrowIfNeeded(ref T[] array, int idx) + { + T[] oldRent = array; + + if (idx >= array.Length) + { + T[] newRent = ArrayPool.Shared.Rent(array.Length * 2); + Array.Copy(oldRent, 0, newRent, 0, idx); + array = newRent; + ArrayPool.Shared.Return(oldRent, clearArray: true); + } + } + + internal struct CertAndKey + { + internal ICertificatePalCore Cert; + internal AsymmetricAlgorithm Key; + + internal void Dispose() + { + Cert?.Dispose(); + Key?.Dispose(); + } + } + } +} From 1783a5a19319b618dd36e99f8d822fa3aeb84639 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Mon, 23 Sep 2019 19:14:14 -0700 Subject: [PATCH 14/61] With some native changes, most of the cert tests pass now --- .../Interop.SecKeyRef.cs | 13 ++ .../Interop.X509.cs | 127 ++++++++++++++--- .../Pal.OSX/AppleCertificatePal.Pkcs12.cs | 131 ++++++++++++++++++ .../Pal.OSX/AppleCertificatePal.cs | 85 +++++++----- .../Cryptography/Pal.OSX/ApplePkcs12Reader.cs | 92 ++++++++++++ .../Pal.OSX/StorePal.LoaderPal.cs | 71 +++++++++- .../Internal/Cryptography/Pal.OSX/StorePal.cs | 46 +++--- ...urity.Cryptography.X509Certificates.csproj | 31 +++-- 8 files changed, 505 insertions(+), 91 deletions(-) create mode 100644 src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs create mode 100644 src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/ApplePkcs12Reader.cs diff --git a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs index c80d75c289d1..5b7ad2f8074e 100644 --- a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs +++ b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.SecKeyRef.cs @@ -357,5 +357,18 @@ namespace System.Security.Cryptography.Apple { internal sealed class SafeSecKeyRefHandle : SafeKeychainItemHandle { + protected override void Dispose(bool disposing) + { + if (disposing && SafeHandleCache.IsCachedInvalidHandle(this)) + { + return; + } + + base.Dispose(disposing); + } + + public static SafeSecKeyRefHandle InvalidHandle => + SafeHandleCache.GetInvalidHandle( + () => new SafeSecKeyRefHandle()); } } diff --git a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs index 85b3d29ac5df..216748ace354 100644 --- a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs +++ b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs @@ -85,6 +85,15 @@ private static extern int AppleCryptoNative_X509CopyWithPrivateKey( out SafeSecIdentityHandle pIdentityHandleOut, out int pOSStatus); + [DllImport(Libraries.AppleCryptoNative)] + private static extern int AppleCryptoNative_X509MoveToKeychain( + SafeSecCertificateHandle certHandle, + SafeKeychainHandle targetKeychain, + SafeSecKeyRefHandle privateKeyHandle, + bool exportable, + out SafeSecIdentityHandle pIdentityHandleOut, + out int pOSStatus); + internal static byte[] X509GetRawData(SafeSecCertificateHandle cert) { int osStatus; @@ -117,26 +126,62 @@ internal static SafeSecCertificateHandle X509ImportCertificate( bool exportable, out SafeSecIdentityHandle identityHandle) { - SafeSecCertificateHandle certHandle; - int osStatus; - int ret; + SafeCreateHandle cfPassphrase = null; + bool hasRef = false; - SafeCreateHandle cfPassphrase = s_nullExportString; - bool releasePassword = false; - - try + if (!importPassword.IsInvalid) { - if (!importPassword.IsInvalid) - { - importPassword.DangerousAddRef(ref releasePassword); - IntPtr passwordHandle = importPassword.DangerousGetHandle(); + importPassword.DangerousAddRef(ref hasRef); + IntPtr passwordHandle = importPassword.DangerousGetHandle(); - if (passwordHandle != IntPtr.Zero) + if (passwordHandle != IntPtr.Zero) + { + unsafe { - cfPassphrase = CoreFoundation.CFStringCreateWithCString(passwordHandle); + ReadOnlySpan passwordSpan = new ReadOnlySpan( + (char*)passwordHandle, + importPassword.Length); + + cfPassphrase = CoreFoundation.CFStringCreateFromSpan(passwordSpan); } } + } + + try + { + return X509ImportCertificate( + bytes, + contentType, + cfPassphrase, + keychain, + exportable, + out identityHandle); + } + finally + { + if (hasRef) + { + importPassword.DangerousRelease(); + } + } + } + internal static SafeSecCertificateHandle X509ImportCertificate( + byte[] bytes, + X509ContentType contentType, + SafeCreateHandle importPassword, + SafeKeychainHandle keychain, + bool exportable, + out SafeSecIdentityHandle identityHandle) + { + SafeSecCertificateHandle certHandle; + int osStatus; + int ret; + + SafeCreateHandle cfPassphrase = importPassword ?? s_nullExportString; + + try + { ret = AppleCryptoNative_X509ImportCertificate( bytes, bytes.Length, @@ -153,11 +198,6 @@ internal static SafeSecCertificateHandle X509ImportCertificate( } finally { - if (releasePassword) - { - importPassword.DangerousRelease(); - } - if (cfPassphrase != s_nullExportString) { cfPassphrase.Dispose(); @@ -385,6 +425,57 @@ internal static SafeSecIdentityHandle X509CopyWithPrivateKey( throw new CryptographicException(); } + internal static SafeSecIdentityHandle X509MoveToKeychain( + SafeSecCertificateHandle cert, + SafeKeychainHandle targetKeychain, + SafeSecKeyRefHandle privateKey, + bool exportable) + { + SafeSecIdentityHandle identityHandle; + int osStatus; + + int result = AppleCryptoNative_X509MoveToKeychain( + cert, + targetKeychain, + privateKey ?? SafeSecKeyRefHandle.InvalidHandle, + exportable, + out identityHandle, + out osStatus); + + if (result == 0) + { + identityHandle.Dispose(); + throw CreateExceptionForOSStatus(osStatus); + } + + if (result != 1) + { + Debug.Fail($"AppleCryptoNative_X509MoveToKeychain returned {result}"); + throw new CryptographicException(); + } + + if (privateKey?.IsInvalid == false) + { + if (identityHandle.IsInvalid) + { + identityHandle.Dispose(); + Debug.Fail($"{nameof(X509MoveToKeychain)} did not match cert to key"); + throw new CryptographicException(); + } + + return identityHandle; + } + + // If the cert in the PFX had no key, but it was imported with PersistKeySet (imports into + // the default keychain) and a matching private key was already there, then an + // identityHandle could be found. But that's not desirable, since neither Windows or Linux would + // do that matching. + // + // So dispose the handle, no matter what. + identityHandle.Dispose(); + return null; + } + private static byte[] X509Export(X509ContentType contentType, SafeCreateHandle cfPassphrase, IntPtr[] certHandles) { Debug.Assert(contentType == X509ContentType.Pkcs7 || contentType == X509ContentType.Pkcs12); diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs new file mode 100644 index 000000000000..ab6923bff85e --- /dev/null +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.Pkcs12.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed partial class AppleCertificatePal : ICertificatePal + { + private static ICertificatePal ImportPkcs12( + byte[] rawData, + SafePasswordHandle password, + bool exportable, + SafeKeychainHandle keychain) + { + using (AppleSslPkcs12Reader reader = new AppleSslPkcs12Reader(rawData)) + { + reader.Decrypt(password); + + UnixPkcs12Reader.CertAndKey certAndKey = reader.GetSingleCert(); + AppleCertificatePal pal = (AppleCertificatePal)certAndKey.Cert; + + SafeSecKeyRefHandle safeSecKeyRefHandle = + AppleSslPkcs12Reader.GetPrivateKey(certAndKey.Key); + + AppleCertificatePal newPal; + + using (safeSecKeyRefHandle) + { + // SecItemImport doesn't seem to respect non-exportable import for PKCS#8, + // only PKCS#12. + // + // So, as part of reading this PKCS#12 we now need to write the minimum + // PKCS#12 in a normalized form, and ask the OS to import it. + if (!exportable && safeSecKeyRefHandle != null) + { + return ImportPkcs12NonExportable(pal, safeSecKeyRefHandle, password, keychain); + } + + newPal = pal.MoveToKeychain(keychain, safeSecKeyRefHandle); + } + + // If no new PAL came back, it means we moved the cert, but had no private key. + return newPal ?? pal; + } + } + + internal static ICertificatePal ImportPkcs12NonExportable( + AppleCertificatePal cert, + SafeSecKeyRefHandle privateKey, + SafePasswordHandle password, + SafeKeychainHandle keychain) + { + Pkcs12SmallExport exporter = new Pkcs12SmallExport(new TempExportPal(cert), privateKey); + byte[] smallPfx = exporter.Export(X509ContentType.Pkcs12, password); + + SafeSecIdentityHandle identityHandle; + SafeSecCertificateHandle certHandle = Interop.AppleCrypto.X509ImportCertificate( + smallPfx, + X509ContentType.Pkcs12, + password, + keychain, + exportable: false, + out identityHandle); + + Debug.Assert(!identityHandle.IsInvalid, "Rebuilt PFX did not find a private key"); + Debug.Assert(certHandle.IsInvalid); + certHandle.Dispose(); + return new AppleCertificatePal(identityHandle); + } + + private sealed class Pkcs12SmallExport : UnixExportProvider + { + private readonly SafeSecKeyRefHandle _privateKey; + + internal Pkcs12SmallExport(ICertificatePalCore cert, SafeSecKeyRefHandle privateKey) + : base(cert) + { + Debug.Assert(!privateKey.IsInvalid); + _privateKey = privateKey; + } + + protected override byte[] ExportPkcs7() => throw new NotImplementedException(); + + protected override byte[] ExportPkcs8(ICertificatePalCore certificatePal, ReadOnlySpan password) + { + return AppleCertificatePal.ExportPkcs8(_privateKey, password); + } + } + + private sealed class TempExportPal : ICertificatePalCore + { + private readonly ICertificatePal _realPal; + + internal TempExportPal(AppleCertificatePal realPal) + { + _realPal = realPal; + } + + public bool HasPrivateKey => true; + + public void Dispose() + { + // No-op. + } + + // Forwarders to make the interface compliant. + public IntPtr Handle => _realPal.Handle; + public string Issuer => _realPal.Issuer; + public string Subject => _realPal.Subject; + public string LegacyIssuer => _realPal.LegacyIssuer; + public string LegacySubject => _realPal.LegacySubject; + public byte[] Thumbprint => _realPal.Thumbprint; + public string KeyAlgorithm => _realPal.KeyAlgorithm; + public byte[] KeyAlgorithmParameters => _realPal.KeyAlgorithmParameters; + public byte[] PublicKeyValue => _realPal.PublicKeyValue; + public byte[] SerialNumber => _realPal.SerialNumber; + public string SignatureAlgorithm => _realPal.SignatureAlgorithm; + public DateTime NotAfter => _realPal.NotAfter; + public DateTime NotBefore => _realPal.NotBefore; + public byte[] RawData => _realPal.RawData; + public byte[] Export(X509ContentType contentType, SafePasswordHandle password) => + _realPal.Export(contentType, password); + } + } +} diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs index 30eaaac6cc64..d21740153006 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs @@ -15,7 +15,7 @@ namespace Internal.Cryptography.Pal { - internal sealed class AppleCertificatePal : ICertificatePal + internal sealed partial class AppleCertificatePal : ICertificatePal { private SafeSecIdentityHandle _identityHandle; private SafeSecCertificateHandle _certHandle; @@ -92,10 +92,6 @@ public static ICertificatePal FromBlob( throw new CryptographicException(SR.Cryptography_X509_PKCS7_NoSigner); } - bool exportable = true; - - SafeKeychainHandle keychain; - if (contentType == X509ContentType.Pkcs12) { if ((keyStorageFlags & X509KeyStorageFlags.EphemeralKeySet) == X509KeyStorageFlags.EphemeralKeySet) @@ -103,51 +99,41 @@ public static ICertificatePal FromBlob( throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx); } - exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; + bool exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; bool persist = (keyStorageFlags & X509KeyStorageFlags.PersistKeySet) == X509KeyStorageFlags.PersistKeySet; - keychain = persist + SafeKeychainHandle keychain = persist ? Interop.AppleCrypto.SecKeychainCopyDefault() : Interop.AppleCrypto.CreateTemporaryKeychain(); - } - else - { - keychain = SafeTemporaryKeychainHandle.InvalidHandle; - password = SafePasswordHandle.InvalidHandle; - } - using (keychain) - { - SafeSecIdentityHandle identityHandle; - SafeSecCertificateHandle certHandle = Interop.AppleCrypto.X509ImportCertificate( - rawData, - contentType, - password, - keychain, - exportable, - out identityHandle); - - if (identityHandle.IsInvalid) + using (keychain) { - identityHandle.Dispose(); - return new AppleCertificatePal(certHandle); + return ImportPkcs12(rawData, password, exportable, keychain); } + } - if (contentType != X509ContentType.Pkcs12) - { - Debug.Fail("Non-PKCS12 import produced an identity handle"); - - identityHandle.Dispose(); - certHandle.Dispose(); - throw new CryptographicException(); - } + SafeSecIdentityHandle identityHandle; + SafeSecCertificateHandle certHandle = Interop.AppleCrypto.X509ImportCertificate( + rawData, + contentType, + SafePasswordHandle.InvalidHandle, + SafeTemporaryKeychainHandle.InvalidHandle, + exportable: true, + out identityHandle); - Debug.Assert(certHandle.IsInvalid); - certHandle.Dispose(); - return new AppleCertificatePal(identityHandle); + if (identityHandle.IsInvalid) + { + identityHandle.Dispose(); + return new AppleCertificatePal(certHandle); } + + Debug.Fail("Non-PKCS12 import produced an identity handle"); + + identityHandle.Dispose(); + certHandle.Dispose(); + throw new CryptographicException(); } public static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) @@ -366,6 +352,13 @@ internal unsafe byte[] ExportPkcs8(ReadOnlySpan password) Debug.Assert(_identityHandle != null); using (SafeSecKeyRefHandle key = Interop.AppleCrypto.X509GetPrivateKeyFromIdentity(_identityHandle)) + { + return ExportPkcs8(key, password); + } + } + + internal static unsafe byte[] ExportPkcs8(SafeSecKeyRefHandle key, ReadOnlySpan password) + { using (SafeCFDataHandle data = Interop.AppleCrypto.SecKeyExportData(key, exportPrivate: true, password)) { ReadOnlySpan systemExport = Interop.CoreFoundation.CFDataDangerousGetSpan(data); @@ -498,6 +491,22 @@ public ICertificatePal CopyWithPrivateKey(RSA privateKey) } } + internal AppleCertificatePal MoveToKeychain(SafeKeychainHandle keychain, SafeSecKeyRefHandle privateKey) + { + SafeSecIdentityHandle identity = Interop.AppleCrypto.X509MoveToKeychain( + _certHandle, + keychain, + privateKey, + exportable: false); + + if (identity != null) + { + return new AppleCertificatePal(identity); + } + + return null; + } + private ICertificatePal CopyWithPrivateKey(SecKeyPair keyPair) { if (keyPair.PrivateKey == null) diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/ApplePkcs12Reader.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/ApplePkcs12Reader.cs new file mode 100644 index 000000000000..1285d3d5c87f --- /dev/null +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/ApplePkcs12Reader.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography.Pal +{ + internal sealed class AppleSslPkcs12Reader : UnixPkcs12Reader + { + internal AppleSslPkcs12Reader(byte[] data) + { + ParsePkcs12(data); + } + + protected override ICertificatePalCore ReadX509Der(ReadOnlyMemory data) + { + SafeSecCertificateHandle certHandle = Interop.AppleCrypto.X509ImportCertificate( + data.ToArray(), + X509ContentType.Cert, + SafePasswordHandle.InvalidHandle, + SafeTemporaryKeychainHandle.InvalidHandle, + exportable: true, + out SafeSecIdentityHandle identityHandle); + + if (identityHandle.IsInvalid) + { + identityHandle.Dispose(); + return new AppleCertificatePal(certHandle); + } + + Debug.Fail("Non-PKCS12 import produced an identity handle"); + + identityHandle.Dispose(); + certHandle.Dispose(); + throw new CryptographicException(); + } + + protected override AsymmetricAlgorithm LoadKey(ReadOnlyMemory pkcs8) + { + PrivateKeyInfoAsn privateKeyInfo = PrivateKeyInfoAsn.Decode(pkcs8, AsnEncodingRules.BER); + AsymmetricAlgorithm key; + + switch (privateKeyInfo.PrivateKeyAlgorithm.Algorithm.Value) + { + case Oids.Rsa: + key = new RSAImplementation.RSASecurityTransforms(); + break; + case Oids.Dsa: + key = new DSAImplementation.DSASecurityTransforms(); + break; + case Oids.EcDiffieHellman: + case Oids.EcPublicKey: + key = new ECDsaImplementation.ECDsaSecurityTransforms(); + break; + default: + throw new CryptographicException( + SR.Cryptography_UnknownAlgorithmIdentifier, + privateKeyInfo.PrivateKeyAlgorithm.Algorithm.Value); + } + + key.ImportPkcs8PrivateKey(pkcs8.Span, out int bytesRead); + + if (bytesRead != pkcs8.Length) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + return key; + } + + internal static SafeSecKeyRefHandle GetPrivateKey(AsymmetricAlgorithm key) + { + if (key == null) + return null; + + if (key is RSAImplementation.RSASecurityTransforms rsa) + return rsa.GetKeys().PrivateKey; + + if (key is DSAImplementation.DSASecurityTransforms dsa) + return dsa.GetKeys().PrivateKey;; + + return ((ECDsaImplementation.ECDsaSecurityTransforms)key).GetKeys().PrivateKey; + } + } +} diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.LoaderPal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.LoaderPal.cs index 0657ce677aa9..a2d8600c84ca 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.LoaderPal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.LoaderPal.cs @@ -56,5 +56,74 @@ public void MoveTo(X509Certificate2Collection collection) } } } + + private sealed class ApplePkcs12CertLoader : ILoaderPal + { + private readonly AppleSslPkcs12Reader _pkcs12; + private readonly SafeKeychainHandle _keychain; + private readonly SafePasswordHandle _password; + private readonly bool _exportable; + + public ApplePkcs12CertLoader( + AppleSslPkcs12Reader pkcs12, + SafeKeychainHandle keychain, + SafePasswordHandle password, + bool exportable) + { + _pkcs12 = pkcs12; + _keychain = keychain; + _exportable = exportable; + + bool addedRef = false; + password.DangerousAddRef(ref addedRef); + _password = password; + } + + public void Dispose() + { + _pkcs12.Dispose(); + + // Only dispose the keychain if it's a temporary handle. + (_keychain as SafeTemporaryKeychainHandle)?.Dispose(); + + _password.DangerousRelease(); + } + + public void MoveTo(X509Certificate2Collection collection) + { + foreach (UnixPkcs12Reader.CertAndKey certAndKey in _pkcs12.EnumerateAll()) + { + AppleCertificatePal pal = (AppleCertificatePal)certAndKey.Cert; + SafeSecKeyRefHandle safeSecKeyRefHandle = + AppleSslPkcs12Reader.GetPrivateKey(certAndKey.Key); + + using (safeSecKeyRefHandle) + { + ICertificatePal newPal; + + // SecItemImport doesn't seem to respect non-exportable import for PKCS#8, + // only PKCS#12. + // + // So, as part of reading this PKCS#12 we now need to write the minimum + // PKCS#12 in a normalized form, and ask the OS to import it. + if (!_exportable && safeSecKeyRefHandle != null) + { + newPal = AppleCertificatePal.ImportPkcs12NonExportable( + pal, + safeSecKeyRefHandle, + _password, + _keychain); + } + else + { + newPal = pal.MoveToKeychain(_keychain, safeSecKeyRefHandle) ?? pal; + } + + X509Certificate2 cert = new X509Certificate2(newPal); + collection.Add(cert); + } + } + } + } } -} \ No newline at end of file +} diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs index d572c42c01c2..edf4df6eaf75 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/StorePal.cs @@ -32,9 +32,6 @@ public static ILoaderPal FromBlob(byte[] rawData, SafePasswordHandle password, X X509ContentType contentType = X509Certificate2.GetCertContentType(rawData); - SafeKeychainHandle keychain; - bool exportable = true; - if (contentType == X509ContentType.Pkcs12) { if ((keyStorageFlags & X509KeyStorageFlags.EphemeralKeySet) == X509KeyStorageFlags.EphemeralKeySet) @@ -42,36 +39,45 @@ public static ILoaderPal FromBlob(byte[] rawData, SafePasswordHandle password, X throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx); } - exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; + bool exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; bool persist = (keyStorageFlags & X509KeyStorageFlags.PersistKeySet) == X509KeyStorageFlags.PersistKeySet; - keychain = persist + SafeKeychainHandle keychain = persist ? Interop.AppleCrypto.SecKeychainCopyDefault() : Interop.AppleCrypto.CreateTemporaryKeychain(); + + return ImportPkcs12(rawData, password, exportable, keychain); } - else - { - keychain = SafeTemporaryKeychainHandle.InvalidHandle; - password = SafePasswordHandle.InvalidHandle; - } - // Only dispose tmpKeychain on the exception path, otherwise it's managed by AppleCertLoader. + SafeCFArrayHandle certs = Interop.AppleCrypto.X509ImportCollection( + rawData, + contentType, + password, + SafeTemporaryKeychainHandle.InvalidHandle, + exportable: true); + + // If the default keychain was used, null will be passed to the loader. + return new AppleCertLoader(certs, null); + } + + private static ILoaderPal ImportPkcs12( + byte[] rawData, + SafePasswordHandle password, + bool exportable, + SafeKeychainHandle keychain) + { + AppleSslPkcs12Reader reader = new AppleSslPkcs12Reader(rawData); + try { - SafeCFArrayHandle certs = Interop.AppleCrypto.X509ImportCollection( - rawData, - contentType, - password, - keychain, - exportable); - - // If the default keychain was used, null will be passed to the loader. - return new AppleCertLoader(certs, keychain as SafeTemporaryKeychainHandle); + reader.Decrypt(password); + return new ApplePkcs12CertLoader(reader, keychain, password, exportable); } catch { + reader.Dispose(); keychain.Dispose(); throw; } diff --git a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index 55028922ce8a..583740bd1ab3 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -308,20 +308,6 @@ System\Security\Cryptography\X509Certificates\Asn1\DistributionPointNameAsn.xml - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml - - - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs - Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml - Common\System\Security\Cryptography\KeyFormatHelper.cs @@ -630,6 +616,8 @@ Common\System\Security\Cryptography\Asn1\SpecifiedECDomain.xml + + @@ -723,6 +711,20 @@ Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml.cs Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedContentInfoAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml + + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml @@ -746,6 +748,7 @@ + From 23885100b7689b6b25f56e8707165aed3a753860 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Tue, 24 Sep 2019 08:12:50 -0700 Subject: [PATCH 15/61] Remove exportable from X509MoveToKeychain --- .../Interop.X509.cs | 5 +---- .../src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs index 216748ace354..d1df3855e91b 100644 --- a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs +++ b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs @@ -90,7 +90,6 @@ private static extern int AppleCryptoNative_X509MoveToKeychain( SafeSecCertificateHandle certHandle, SafeKeychainHandle targetKeychain, SafeSecKeyRefHandle privateKeyHandle, - bool exportable, out SafeSecIdentityHandle pIdentityHandleOut, out int pOSStatus); @@ -428,8 +427,7 @@ internal static SafeSecIdentityHandle X509CopyWithPrivateKey( internal static SafeSecIdentityHandle X509MoveToKeychain( SafeSecCertificateHandle cert, SafeKeychainHandle targetKeychain, - SafeSecKeyRefHandle privateKey, - bool exportable) + SafeSecKeyRefHandle privateKey) { SafeSecIdentityHandle identityHandle; int osStatus; @@ -438,7 +436,6 @@ internal static SafeSecIdentityHandle X509MoveToKeychain( cert, targetKeychain, privateKey ?? SafeSecKeyRefHandle.InvalidHandle, - exportable, out identityHandle, out osStatus); diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs index d21740153006..d877836e97d3 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/AppleCertificatePal.cs @@ -496,8 +496,7 @@ internal AppleCertificatePal MoveToKeychain(SafeKeychainHandle keychain, SafeSec SafeSecIdentityHandle identity = Interop.AppleCrypto.X509MoveToKeychain( _certHandle, keychain, - privateKey, - exportable: false); + privateKey); if (identity != null) { From 52e870153c8828f59325f159f695b5d849f7a3f0 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Tue, 24 Sep 2019 08:21:47 -0700 Subject: [PATCH 16/61] Add native support --- .../pal_x509.c | 154 ++++++++++++++++++ .../pal_x509.h | 18 ++ 2 files changed, 172 insertions(+) diff --git a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.c b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.c index ecd5b6c5806e..6e331d8eeb4b 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.c +++ b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.c @@ -719,3 +719,157 @@ int32_t AppleCryptoNative_X509CopyWithPrivateKey(SecCertificateRef cert, *pOSStatus = status; return status == noErr; } + +int32_t AppleCryptoNative_X509MoveToKeychain(SecCertificateRef cert, + SecKeychainRef targetKeychain, + SecKeyRef privateKey, + SecIdentityRef* pIdentityOut, + int32_t* pOSStatus) +{ + if (pIdentityOut != NULL) + *pIdentityOut = NULL; + if (pOSStatus != NULL) + *pOSStatus = noErr; + + if (cert == NULL || targetKeychain == NULL || pIdentityOut == NULL || pOSStatus == NULL) + { + return -1; + } + + SecKeychainRef curKeychain = NULL; + OSStatus status = SecKeychainItemCopyKeychain((SecKeychainItemRef)cert, &curKeychain); + + if (status == errSecNoSuchKeychain) + { + status = noErr; + } + else + { + if (curKeychain != NULL) + { + CFRelease(curKeychain); + } + + if (status == noErr) + { + // Usage error: The certificate should have been freshly imported by the PFX loader, + // and therefore have no keychain. + return -2; + } + } + + if (status == noErr && privateKey != NULL) + { + status = SecKeychainItemCopyKeychain((SecKeychainItemRef)privateKey, &curKeychain); + + if (status == errSecNoSuchKeychain) + { + status = AddKeyToKeychain(privateKey, targetKeychain); + } + else + { + if (curKeychain != NULL) + { + CFRelease(curKeychain); + } + + if (status == noErr) + { + // This is a usage error, the only expected call is from the PFX loader, + // which has an ephemeral key reference, therefore no keychain. + return -3; + } + } + } + + if (status == noErr) + { + status = SecCertificateAddToKeychain(cert, targetKeychain); + } + + if (status == noErr && privateKey != NULL) + { + CFMutableDictionaryRef query = NULL; + CFArrayRef searchList = NULL; + CFArrayRef itemMatch = NULL; + CFTypeRef result = NULL; + + if (status == noErr) + { + query = CFDictionaryCreateMutable( + kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + if (query == NULL) + { + status = errSecAllocate; + } + } + + if (status == noErr) + { + const void* constTargetKeychain = targetKeychain; + searchList = CFArrayCreate(NULL, (const void**)(&constTargetKeychain), 1, &kCFTypeArrayCallBacks); + + if (searchList == NULL) + { + status = errSecAllocate; + } + } + + if (status == noErr) + { + const void* constCert = cert; + itemMatch = CFArrayCreate(NULL, (const void**)(&constCert), 1, &kCFTypeArrayCallBacks); + + if (itemMatch == NULL) + { + status = errSecAllocate; + } + } + + if (status == noErr) + { + CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue); + CFDictionarySetValue(query, kSecMatchSearchList, searchList); + CFDictionarySetValue(query, kSecMatchItemList, itemMatch); + CFDictionarySetValue(query, kSecClass, kSecClassIdentity); + + status = SecItemCopyMatching(query, &result); + + if (status != noErr && result != NULL) + { + CFRelease(result); + result = NULL; + } + + if (result != NULL && status == noErr) + { + if (CFGetTypeID(result) != SecIdentityGetTypeID()) + { + status = errSecItemNotFound; + } + else + { + SecIdentityRef identity = (SecIdentityRef)CONST_CAST(void*, result); + CFRetain(identity); + *pIdentityOut = identity; + } + } + } + + if (result != NULL) + CFRelease(result); + + if (itemMatch != NULL) + CFRelease(itemMatch); + + if (searchList != NULL) + CFRelease(searchList); + + if (query != NULL) + CFRelease(query); + } + + *pOSStatus = status; + return status == noErr; +} diff --git a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.h b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.h index 1f468bd221c9..37510c13804a 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.h +++ b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509.h @@ -174,3 +174,21 @@ DLLEXPORT int32_t AppleCryptoNative_X509CopyWithPrivateKey(SecCertificateRef cer SecKeychainRef targetKeychain, SecIdentityRef* pIdentityOut, int32_t* pOSStatus); + +/* +Move the specified certificate to the target keychain. +If exportable is true, move the private key (if present) to the target keychain; +otherwise export the key and re-import it as non-exportable into the target keychain. +If the private key was specified then search for an identity. + +Returns 1 on success, 0 on failure, any other value indicates invalid state. + +Output: +pIdentityOut: Receives the SecIdentityRef of the mated cert/key pair, when applicable. +pOSStatus: Receives the result of the last executed system call. +*/ +DLLEXPORT int32_t AppleCryptoNative_X509MoveToKeychain(SecCertificateRef cert, + SecKeychainRef keychain, + SecKeyRef privateKey, + SecIdentityRef* pIdentityOut, + int32_t* pOSStatus); From 443c862267e8cb9bae03b0d8e60460c738db5342 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 25 Sep 2019 22:08:13 -0700 Subject: [PATCH 17/61] Reading char2-explicit works --- .../Cryptography/EccKeyFormatHelper.cs | 142 +++++++++++++++--- .../Security/Cryptography/KeyBlobHelpers.cs | 8 +- .../src/System/Security/Cryptography/Oids.cs | 3 + 3 files changed, 134 insertions(+), 19 deletions(-) diff --git a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index f89a1ca9ddcf..d5381c1845a7 100644 --- a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Collections; using System.Runtime.InteropServices; using System.Security.Cryptography.Asn1; @@ -10,6 +11,10 @@ namespace System.Security.Cryptography { internal static class EccKeyFormatHelper { + // This is the same limit that OpenSSL 1.0.2-1.1.1 has, + // there's no real point reading anything bigger than this (for now). + private const int MaxFieldBitSize = 661; + private static readonly string[] s_validOids = { Oids.EcPublicKey, @@ -273,38 +278,141 @@ private static ECCurve GetSpecifiedECCurve(in SpecifiedECDomain specifiedParamet throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } - if (specifiedParameters.FieldID.FieldType != Oids.EcPrimeField) + byte[] primeOrPoly; + bool prime; + + switch (specifiedParameters.FieldID.FieldType) { - throw new CryptographicException("TODO"); + case Oids.EcPrimeField: + prime = true; + AsnReader primeReader = new AsnReader(specifiedParameters.FieldID.Parameters, AsnEncodingRules.BER); + ReadOnlySpan primeValue = primeReader.ReadIntegerBytes().Span; + + if (primeValue[0] == 0) + { + primeValue = primeValue.Slice(1); + } + + if (primeValue.Length > (MaxFieldBitSize / 8)) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + primeOrPoly = primeValue.ToArray(); + break; + case Oids.EcChar2Field: + prime = false; + AsnReader char2Reader = new AsnReader(specifiedParameters.FieldID.Parameters, AsnEncodingRules.BER); + AsnReader innerReader = char2Reader.ReadSequence(); + char2Reader.ThrowIfNotEmpty(); + + // Characteristic-two ::= SEQUENCE + // { + // m INTEGER, -- Field size + // basis CHARACTERISTIC-TWO.&id({BasisTypes}), + // parameters CHARACTERISTIC-TWO.&Type({BasisTypes}{@basis}) + // } + + if (!innerReader.TryReadInt32(out int m) || m > MaxFieldBitSize) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + int k1; + int k2 = -1; + int k3 = -1; + + switch (innerReader.ReadObjectIdentifierAsString()) + { + case Oids.EcChar2TrinomialBasis: + // Trinomial ::= INTEGER + if (!innerReader.TryReadInt32(out k1) || k1 >= m || k1 < 1) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + break; + case Oids.EcChar2PentanomialBasis: + // Pentanomial ::= SEQUENCE + // { + // k1 INTEGER, -- k1 > 0 + // k2 INTEGER, -- k2 > k1 + // k3 INTEGER -- k3 > k2 + // } + AsnReader pentanomialReader = innerReader.ReadSequence(); + + if (!pentanomialReader.TryReadInt32(out k1) || + !pentanomialReader.TryReadInt32(out k2) || + !pentanomialReader.TryReadInt32(out k3) || + k1 < 1 || + k2 <= k1 || + k3 <= k2 || + k3 >= m) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + pentanomialReader.ThrowIfNotEmpty(); + + break; + default: + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + innerReader.ThrowIfNotEmpty(); + + BitArray poly = new BitArray(m + 1); + poly.Set(m, true); + poly.Set(k1, true); + poly.Set(0, true); + + if (k2 > 0) + { + poly.Set(k2, true); + poly.Set(k3, true); + } + + primeOrPoly = new byte[(m + 7) / 8]; + poly.CopyTo(primeOrPoly, 0); + Array.Reverse(primeOrPoly); + break; + default: + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } - AsnReader reader = new AsnReader(specifiedParameters.FieldID.Parameters, AsnEncodingRules.BER); - ReadOnlySpan primeValue = reader.ReadIntegerBytes().Span; + ECCurve curve; - if (primeValue[0] == 0) + if (prime) { - primeValue = primeValue.Slice(1); + curve = new ECCurve + { + CurveType = ECCurve.ECCurveType.PrimeShortWeierstrass, + Prime = primeOrPoly, + }; } - - ECCurve curve = new ECCurve + else { - CurveType = ECCurve.ECCurveType.PrimeShortWeierstrass, - Prime = primeValue.ToArray(), - A = specifiedParameters.Curve.A.ToUnsignedIntegerBytes(primeValue.Length), - B = specifiedParameters.Curve.B.ToUnsignedIntegerBytes(primeValue.Length), - Order = specifiedParameters.Order.ToUnsignedIntegerBytes(), - }; + curve = new ECCurve + { + CurveType = ECCurve.ECCurveType.Characteristic2, + Polynomial = primeOrPoly, + }; + } + + curve.A = specifiedParameters.Curve.A.ToUnsignedIntegerBytes(primeOrPoly.Length); + curve.B = specifiedParameters.Curve.B.ToUnsignedIntegerBytes(primeOrPoly.Length); + curve.Order = specifiedParameters.Order.ToUnsignedIntegerBytes(primeOrPoly.Length); ReadOnlySpan baseSpan = specifiedParameters.Base.Span; // We only understand the uncompressed point encoding, but that's almost always what's used. - if (baseSpan[0] != 0x04 || baseSpan.Length != 2 * primeValue.Length + 1) + if (baseSpan[0] != 0x04 || baseSpan.Length != 2 * primeOrPoly.Length + 1) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } - curve.G.X = baseSpan.Slice(1, primeValue.Length).ToArray(); - curve.G.Y = baseSpan.Slice(1 + primeValue.Length).ToArray(); + curve.G.X = baseSpan.Slice(1, primeOrPoly.Length).ToArray(); + curve.G.Y = baseSpan.Slice(1 + primeOrPoly.Length).ToArray(); if (specifiedParameters.Cofactor.HasValue) { diff --git a/src/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs b/src/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs index fc46004d16db..b5aab14da283 100644 --- a/src/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs +++ b/src/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs @@ -27,9 +27,13 @@ internal static byte[] ToUnsignedIntegerBytes(this ReadOnlyMemory memory, } } - // Otherwise too big? throw. + if (span.Length > length) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + byte[] target = new byte[length]; - memory.Span.CopyTo(target.AsSpan(target.Length - length)); + span.CopyTo(target.AsSpan(length - span.Length)); return target; } diff --git a/src/Common/src/System/Security/Cryptography/Oids.cs b/src/Common/src/System/Security/Cryptography/Oids.cs index d11c45d3510c..6717704fcc6d 100644 --- a/src/Common/src/System/Security/Cryptography/Oids.cs +++ b/src/Common/src/System/Security/Cryptography/Oids.cs @@ -76,6 +76,9 @@ internal static partial class Oids // ECDSA CMS uses the combined signature+digest OID // https://tools.ietf.org/html/rfc5753#section-2.1.1 internal const string EcPrimeField = "1.2.840.10045.1.1"; + internal const string EcChar2Field = "1.2.840.10045.1.2"; + internal const string EcChar2TrinomialBasis = "1.2.840.10045.1.2.3.2"; + internal const string EcChar2PentanomialBasis = "1.2.840.10045.1.2.3.3"; internal const string EcPublicKey = "1.2.840.10045.2.1"; internal const string ECDsaWithSha1 = "1.2.840.10045.4.1"; internal const string ECDsaWithSha256 = "1.2.840.10045.4.3.2"; From d7ce17ed5bbf95c9e4330a14c098234c6cad37a4 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 25 Sep 2019 22:09:21 -0700 Subject: [PATCH 18/61] Writing Char2-explicit ECPrivateKey works --- .../Cryptography/EccKeyFormatHelper.cs | 198 +++++++++++++++++- 1 file changed, 194 insertions(+), 4 deletions(-) diff --git a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index d5381c1845a7..733948a510d9 100644 --- a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Collections; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Cryptography.Asn1; @@ -502,24 +503,213 @@ private static void WriteEcParameters(in ECParameters ecParameters, AsnWriter wr writer.WriteObjectIdentifier(oid.Value); } + else if (ecParameters.Curve.IsCharacteristic2) + { + WriteChar2Parameters(ecParameters, writer); + } else { throw new CryptographicException(SR.Cryptography_ECC_NamedCurvesOnly); } } + private static void WriteChar2Parameters(in ECParameters ecParameters, AsnWriter writer) + { + int m; + int k1; + int k2; + int k3; + + m = k1 = k2 = k3 = -1; + byte[] polynomial = ecParameters.Curve.Polynomial; + + // The most significant byte needs a set bit, and the least significant bit must be set. + if (polynomial[0] == 0 || (polynomial[polynomial.Length - 1] & 1) != 1) + { + throw new CryptographicException(SR.Cryptography_InvalidECCharacteristic2Curve); + } + + // This loop is going to look for the set bits. + // There must be be either 3 (trinomial) or 5 (pentanomial). + for (int byteIdx = 0; byteIdx < polynomial.Length; byteIdx++) + { + byte val = polynomial[byteIdx]; + + for (int localBitIdx = 7; localBitIdx >= 0; localBitIdx--) + { + int test = 1 << localBitIdx; + + if ((val & test) == test) + { + int bitIndex = 8 * (polynomial.Length - 1 - byteIdx) + localBitIdx; + + if (m < 0) + { + m = bitIndex; + } + else if (k1 < 0) + { + k1 = bitIndex; + } + else if (k2 < 0) + { + k2 = bitIndex; + } + else if (k3 < 0) + { + k3 = bitIndex; + } + else if (byteIdx == polynomial.Length - 1 && bitIndex == 0) + { + // The bottom bit is always set, it's not considered a parameter. + } + else + { + throw new CryptographicException($"Failure at byteIdx={byteIdx}, localBitIndex={localBitIdx} for {BitConverter.ToString(polynomial)}"); + } + } + } + } + + if (k3 > 0) + { + // Pentanomial + } + else if (k2 > 0) + { + // There is no quatranomial + throw new CryptographicException(SR.Cryptography_InvalidECCharacteristic2Curve); + } + else if (k1 > 0) + { + // Trinomial + } + else + { + // No smaller bases exist + throw new CryptographicException(SR.Cryptography_InvalidECCharacteristic2Curve); + } + + // SpecifiedECDomain + writer.PushSequence(); + { + // version + // We don't know if the seed (if present) is verifiably random (2). + // We also don't know if the base point is verifiably random (3). + // So just be version 1. + writer.WriteInteger(1); + + // fieldId + writer.PushSequence(); + { + // id + writer.WriteObjectIdentifier(Oids.EcChar2Field); + + // Parameters (Characteristic-two) + writer.PushSequence(); + { + // m + writer.WriteInteger(m); + + if (k3 > 0) + { + writer.WriteObjectIdentifier(Oids.EcChar2PentanomialBasis); + + writer.PushSequence(); + { + writer.WriteInteger(k3); + writer.WriteInteger(k2); + writer.WriteInteger(k1); + + writer.PopSequence(); + } + } + else + { + Debug.Assert(k2 < 0); + Debug.Assert(k1 > 0); + + writer.WriteObjectIdentifier(Oids.EcChar2TrinomialBasis); + writer.WriteInteger(k1); + } + + writer.PopSequence(); + } + + writer.PopSequence(); + } + + // curve + WriteCurve(ecParameters.Curve, writer); + + // base + WriteUncompressedBasePoint(ecParameters, writer); + + // order + writer.WriteIntegerUnsigned(ecParameters.Curve.Order); + + // cofactor + if (ecParameters.Curve.Cofactor != null) + { + writer.WriteIntegerUnsigned(ecParameters.Curve.Cofactor); + } + + // hash is omitted. + + writer.PopSequence(); + } + } + + private static void WriteCurve(in ECCurve curve, AsnWriter writer) + { + writer.PushSequence(); + WriteFieldElement(curve.A, writer); + WriteFieldElement(curve.B, writer); + + if (curve.Seed != null) + { + writer.WriteBitString(curve.Seed); + } + + writer.PopSequence(); + } + + private static void WriteFieldElement(byte[] fieldElement, AsnWriter writer) + { + int start = 0; + + while (start < fieldElement.Length - 1 && fieldElement[start] == 0) + { + start++; + } + + writer.WriteOctetString(fieldElement.AsSpan(start)); + } + + private static void WriteUncompressedBasePoint(in ECParameters ecParameters, AsnWriter writer) + { + int basePointLength = ecParameters.Curve.G.X.Length * 2 + 1; + byte[] tmp = CryptoPool.Rent(basePointLength); + tmp[0] = 0x04; + ecParameters.Curve.G.X.CopyTo(tmp.AsSpan(1)); + ecParameters.Curve.G.Y.CopyTo(tmp.AsSpan(1 + ecParameters.Curve.G.X.Length)); + writer.WriteOctetString(tmp.AsSpan(0, basePointLength)); + // Non-sensitive data. + CryptoPool.Return(tmp, clearSize: 0); + } + private static void WriteUncompressedPublicKey(in ECParameters ecParameters, AsnWriter writer) { int publicKeyLength = ecParameters.Q.X.Length * 2 + 1; writer.WriteBitString( publicKeyLength, - ecParameters, - (publicKeyBytes, ecParams) => + ecParameters.Q, + (publicKeyBytes, point) => { publicKeyBytes[0] = 0x04; - ecParams.Q.X.AsSpan().CopyTo(publicKeyBytes.Slice(1)); - ecParams.Q.Y.AsSpan().CopyTo(publicKeyBytes.Slice(1 + ecParams.Q.X.Length)); + point.X.AsSpan().CopyTo(publicKeyBytes.Slice(1)); + point.Y.AsSpan().CopyTo(publicKeyBytes.Slice(1 + point.X.Length)); }); } From 4fd38f6b21c19dbdffafb98e027df6d905cdbc2c Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 25 Sep 2019 22:20:47 -0700 Subject: [PATCH 19/61] Reading and writing im/explicit char-2 works in all formats --- .../Cryptography/EccKeyFormatHelper.cs | 10 -- .../EC/ECKeyFileTests.cs | 120 ++++++++++++++++++ .../EC/EccTestBase.cs | 4 +- .../EC/EccTestData.cs | 37 ++++++ 4 files changed, 159 insertions(+), 12 deletions(-) diff --git a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index 733948a510d9..2ed4f9c1af16 100644 --- a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -427,11 +427,6 @@ internal static AsnWriter WriteSubjectPublicKeyInfo(in ECParameters ecParameters { ecParameters.Validate(); - if (!ecParameters.Curve.IsNamed) - { - throw new CryptographicException(SR.Cryptography_ECC_NamedCurvesOnly); - } - // Since the public key format for EC keys is not ASN.1, // write the SPKI structure manually. @@ -476,11 +471,6 @@ internal static AsnWriter WritePkcs8PrivateKey(in ECParameters ecParameters) throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey); } - if (!ecParameters.Curve.IsNamed) - { - throw new CryptographicException(SR.Cryptography_ECC_NamedCurvesOnly); - } - // Don't need the domain parameters because they're contained in the algId. using (AsnWriter ecPrivateKey = WriteEcPrivateKey(ecParameters, includeDomainParameters: false)) using (AsnWriter algorithmIdentifier = WriteAlgorithmIdentifier(ecParameters)) diff --git a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs index 020375293c04..030c8e0f67f2 100644 --- a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs +++ b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs @@ -22,6 +22,8 @@ public abstract class ECKeyFileTests where T : AsymmetricAlgorithm public static bool SupportsSect283k1 { get; } = IsCurveSupported(EccTestData.Sect283k1Key1.Curve.Oid); + public static bool SupportsC2pnb163v1 { get; } = IsCurveSupported(EccTestData.C2pnb163v1Key1.Curve.Oid); + private static bool IsCurveSupported(Oid oid) { return EcDiffieHellman.Tests.ECDiffieHellmanFactory.IsCurveValid(oid); @@ -389,6 +391,124 @@ public void ReadWriteSect283k1Key1SubjectPublicKeyInfo() SupportsSect283k1); } + [Fact] + public void ReadWriteC2pnb163v1ECPrivateKey() + { + ReadWriteBase64ECPrivateKey( + @" +MFYCAQEEFQD00koUBxIvRFlnvh2TwAk6ZTZ5hqAKBggqhkjOPQMAAaEuAywABAIR +Jy8cVYJCaIjpG9aSV3SUIyJIqgQnCDD3oQCa1nCojekr1ZJIzIE7RQ==", + EccTestData.C2pnb163v1Key1, + SupportsC2pnb163v1); + } + + [Fact] + public void ReadWriteC2pnb163v1Pkcs8() + { + ReadWriteBase64Pkcs8( + @" +MGYCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAAEETDBKAgEBBBUA9NJKFAcSL0RZZ74d +k8AJOmU2eYahLgMsAAQCEScvHFWCQmiI6RvWkld0lCMiSKoEJwgw96EAmtZwqI3p +K9WSSMyBO0U=", + EccTestData.C2pnb163v1Key1, + SupportsC2pnb163v1); + } + + [Fact] + public void ReadWriteC2pnb163v1EncryptedPkcs8() + { + ReadWriteBase64EncryptedPkcs8( + @" +MIGPMBsGCSqGSIb3DQEFAzAOBAjdV9IDq+L+5gICCAAEcI1e6RA8kMcYB+PvOcCU +Jj65nXTIrMPmZ0DmFMF9WBg0J+yzxgDhBVynpT2uJntY4FuDlvdpcLRK1EGLZYKf +qYc5zJMYkRZ178bE3DtfrP3UxD34YvbRl2aeu334+wJOm7ApXv81ugt4OoCiPhdg +wiA=", + "secret", + new PbeParameters( + PbeEncryptionAlgorithm.Aes192Cbc, + HashAlgorithmName.SHA512, + 1024), + EccTestData.C2pnb163v1Key1, + SupportsC2pnb163v1); + } + + [Fact] + public void ReadWriteC2pnb163v1SubjectPublicKeyInfo() + { + ReadWriteBase64SubjectPublicKeyInfo( + @" +MEMwEwYHKoZIzj0CAQYIKoZIzj0DAAEDLAAEAhEnLxxVgkJoiOkb1pJXdJQjIkiq +BCcIMPehAJrWcKiN6SvVkkjMgTtF", + EccTestData.C2pnb163v1Key1, + SupportsC2pnb163v1); + } + + [Fact] + public void ReadWriteC2pnb163v1ExplicitECPrivateKey() + { + ReadWriteBase64ECPrivateKey( + @" +MIIBBwIBAQQVAPTSShQHEi9EWWe+HZPACTplNnmGoIG6MIG3AgEBMCUGByqGSM49 +AQIwGgICAKMGCSqGSM49AQIDAzAJAgEBAgECAgEIMEQEFQclRrVDUjSkIuB4lnX0 +MsiUNd5SQgQUyVF9BtUkDTz/OMdLILbNTW+d1NkDFQDSwPsVdghg3vHu9NaW5naH +VhUXVAQrBAevaZiVRhA9eTKfzD10iA8zu+gDywHsIyEbWWat6h0/h/fqWEiu8LfK +nwIVBAAAAAAAAAAAAAHmD8iCHMdNrq/BAgECoS4DLAAEAhEnLxxVgkJoiOkb1pJX +dJQjIkiqBCcIMPehAJrWcKiN6SvVkkjMgTtF", + EccTestData.C2pnb163v1Key1Explicit, + SupportsC2pnb163v1); + } + + [Fact] + public void ReadWriteC2pnb163v1ExplicitPkcs8() + { + ReadWriteBase64Pkcs8( + @" +MIIBFwIBADCBwwYHKoZIzj0CATCBtwIBATAlBgcqhkjOPQECMBoCAgCjBgkqhkjO +PQECAwMwCQIBAQIBAgIBCDBEBBUHJUa1Q1I0pCLgeJZ19DLIlDXeUkIEFMlRfQbV +JA08/zjHSyC2zU1vndTZAxUA0sD7FXYIYN7x7vTWluZ2h1YVF1QEKwQHr2mYlUYQ +PXkyn8w9dIgPM7voA8sB7CMhG1lmreodP4f36lhIrvC3yp8CFQQAAAAAAAAAAAAB +5g/IghzHTa6vwQIBAgRMMEoCAQEEFQD00koUBxIvRFlnvh2TwAk6ZTZ5hqEuAywA +BAIRJy8cVYJCaIjpG9aSV3SUIyJIqgQnCDD3oQCa1nCojekr1ZJIzIE7RQ==", + EccTestData.C2pnb163v1Key1Explicit, + SupportsC2pnb163v1); + } + + [Fact] + public void ReadWriteC2pnb163v1ExplicitEncryptedPkcs8() + { + ReadWriteBase64EncryptedPkcs8( + @" +MIIBQTAbBgkqhkiG9w0BBQMwDgQI9+ZZnHaqxb0CAggABIIBIM+n6x/Q1hs5OW0F +oOKZmQ0mKNRKb23SMqwo0bJlxseIOVdYzOV2LH1hSWeJb7FMxo6OJXb2CpYSPqv1 +v3lhdLC5t/ViqAOhG70KF+Dy/vZr8rWXRFqy+OdqwxOes/lBsG+Ws9+uEk8+Gm2G +xMHXJNKliSUePlT3wC7z8bCkEvLF7hkGjEAgcABry5Ohq3W2by6Dnd8YWJNgeiW/ +Vu5rT1ThAus7w2TJjWrrEqBbIlQ9nm6/MMj9nYnVVfpPAOk/qX9Or7TmK+Sei88Q +staXBhfJk9ec8laiPpNbhHJSZ2Ph3Snb6SA7MYi5nIMP4RPxOM2eUet4/ueV1O3U +wxcZ+wOsnebIwy4ftKL+klh5EXv/9S5sCjC8g8J2cA6GmcZbiQ==", + "secret", + new PbeParameters( + PbeEncryptionAlgorithm.Aes192Cbc, + HashAlgorithmName.SHA512, + 1024), + EccTestData.C2pnb163v1Key1Explicit, + SupportsC2pnb163v1); + } + + [Fact] + public void ReadWriteC2pnb163v1ExplicitSubjectPublicKeyInfo() + { + ReadWriteBase64SubjectPublicKeyInfo( + @" +MIH0MIHDBgcqhkjOPQIBMIG3AgEBMCUGByqGSM49AQIwGgICAKMGCSqGSM49AQID +AzAJAgEBAgECAgEIMEQEFQclRrVDUjSkIuB4lnX0MsiUNd5SQgQUyVF9BtUkDTz/ +OMdLILbNTW+d1NkDFQDSwPsVdghg3vHu9NaW5naHVhUXVAQrBAevaZiVRhA9eTKf +zD10iA8zu+gDywHsIyEbWWat6h0/h/fqWEiu8LfKnwIVBAAAAAAAAAAAAAHmD8iC +HMdNrq/BAgECAywABAIRJy8cVYJCaIjpG9aSV3SUIyJIqgQnCDD3oQCa1nCojekr +1ZJIzIE7RQ==", + EccTestData.C2pnb163v1Key1Explicit, + SupportsC2pnb163v1); + } + [Fact] public void NoFuzzySubjectPublicKeyInfo() { diff --git a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestBase.cs b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestBase.cs index e723817b293d..76ee1008e9a9 100644 --- a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestBase.cs +++ b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestBase.cs @@ -203,7 +203,7 @@ internal static void CompareCurve(in ECCurve c1, in ECCurve c2) { if (c1.IsNamed) { - Assert.True(c2.IsNamed); + Assert.True(c2.IsNamed, "c2.IsNamed"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || string.IsNullOrEmpty(c1.Oid.Value)) @@ -217,7 +217,7 @@ internal static void CompareCurve(in ECCurve c1, in ECCurve c2) } else if (c1.IsExplicit) { - Assert.True(c2.IsExplicit); + Assert.True(c2.IsExplicit, "c2.IsExplicit"); Assert.Equal(c1.A, c2.A); Assert.Equal(c1.B, c2.B); Assert.Equal(c1.CurveType, c2.CurveType); diff --git a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestData.cs b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestData.cs index f266970c7522..72497f054203 100644 --- a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestData.cs +++ b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestData.cs @@ -238,5 +238,42 @@ internal static ECParameters GetNistP256ReferenceKey() }, D = "00B4F1AE1E7FDCD4B0E82053C08A908852B26231E6C01670FCC6C3EA2C5D3FED40EDF037".HexToByteArray(), }; + + internal static readonly ECParameters C2pnb163v1Key1 = + new ECParameters + { + Curve = ECCurve.CreateFromValue("1.2.840.10045.3.0.1"), + Q = + { + X = "0211272F1C5582426888E91BD692577494232248AA".HexToByteArray(), + Y = "04270830F7A1009AD670A88DE92BD59248CC813B45".HexToByteArray(), + }, + D = "00F4D24A1407122F445967BE1D93C0093A65367986".HexToByteArray(), + }; + + internal static readonly ECParameters C2pnb163v1Key1Explicit = + new ECParameters + { + Curve = + { + CurveType = ECCurve.ECCurveType.Characteristic2, + Polynomial = "080000000000000000000000000000000000000107".HexToByteArray(), + A = "072546B5435234A422E0789675F432C89435DE5242".HexToByteArray(), + B = "00C9517D06D5240D3CFF38C74B20B6CD4D6F9DD4D9".HexToByteArray(), + G = + { + X = "07AF69989546103D79329FCC3D74880F33BBE803CB".HexToByteArray(), + Y = "01EC23211B5966ADEA1D3F87F7EA5848AEF0B7CA9F".HexToByteArray(), + }, + Order = "0400000000000000000001E60FC8821CC74DAEAFC1".HexToByteArray(), + Cofactor = new byte[] { 2 }, + }, + Q = + { + X = "0211272F1C5582426888E91BD692577494232248AA".HexToByteArray(), + Y = "04270830F7A1009AD670A88DE92BD59248CC813B45".HexToByteArray(), + }, + D = "00F4D24A1407122F445967BE1D93C0093A65367986".HexToByteArray(), + }; } } From d7a7c5969956ec7d698da0a437626eb9772ee3a8 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 25 Sep 2019 23:16:00 -0700 Subject: [PATCH 20/61] Im/export of explicit prime works for all formats --- .../Cryptography/EccKeyFormatHelper.cs | 174 ++++++++++-------- .../EC/ECKeyFileTests.cs | 109 +++++++---- .../EC/EccTestData.cs | 18 ++ 3 files changed, 190 insertions(+), 111 deletions(-) diff --git a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index 2ed4f9c1af16..b1206df00b14 100644 --- a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -493,24 +493,117 @@ private static void WriteEcParameters(in ECParameters ecParameters, AsnWriter wr writer.WriteObjectIdentifier(oid.Value); } - else if (ecParameters.Curve.IsCharacteristic2) + else if (ecParameters.Curve.IsExplicit) { - WriteChar2Parameters(ecParameters, writer); + Debug.Assert(ecParameters.Curve.IsPrime || ecParameters.Curve.IsCharacteristic2); + WriteSpecifiedECDomain(ecParameters, writer); } else { - throw new CryptographicException(SR.Cryptography_ECC_NamedCurvesOnly); + throw new CryptographicException("implicit"); } } - private static void WriteChar2Parameters(in ECParameters ecParameters, AsnWriter writer) + private static void WriteSpecifiedECDomain(in ECParameters ecParameters, AsnWriter writer) { int m; int k1; int k2; int k3; - m = k1 = k2 = k3 = -1; + + if (ecParameters.Curve.IsCharacteristic2) + { + DetermineChar2Parameters(ecParameters, ref m, ref k1, ref k2, ref k3); + } + + // SpecifiedECDomain + writer.PushSequence(); + { + // version + // We don't know if the seed (if present) is verifiably random (2). + // We also don't know if the base point is verifiably random (3). + // So just be version 1. + writer.WriteInteger(1); + + // fieldId + writer.PushSequence(); + { + if (ecParameters.Curve.IsPrime) + { + writer.WriteObjectIdentifier(Oids.EcPrimeField); + writer.WriteIntegerUnsigned(ecParameters.Curve.Prime); + } + else + { + Debug.Assert(ecParameters.Curve.IsCharacteristic2); + + // id + writer.WriteObjectIdentifier(Oids.EcChar2Field); + + // Parameters (Characteristic-two) + writer.PushSequence(); + { + // m + writer.WriteInteger(m); + + if (k3 > 0) + { + writer.WriteObjectIdentifier(Oids.EcChar2PentanomialBasis); + + writer.PushSequence(); + { + writer.WriteInteger(k3); + writer.WriteInteger(k2); + writer.WriteInteger(k1); + + writer.PopSequence(); + } + } + else + { + Debug.Assert(k2 < 0); + Debug.Assert(k1 > 0); + + writer.WriteObjectIdentifier(Oids.EcChar2TrinomialBasis); + writer.WriteInteger(k1); + } + + writer.PopSequence(); + } + } + + writer.PopSequence(); + } + + // curve + WriteCurve(ecParameters.Curve, writer); + + // base + WriteUncompressedBasePoint(ecParameters, writer); + + // order + writer.WriteIntegerUnsigned(ecParameters.Curve.Order); + + // cofactor + if (ecParameters.Curve.Cofactor != null) + { + writer.WriteIntegerUnsigned(ecParameters.Curve.Cofactor); + } + + // hash is omitted. + + writer.PopSequence(); + } + } + + private static void DetermineChar2Parameters( + in ECParameters ecParameters, + ref int m, + ref int k1, + ref int k2, + ref int k3) + { byte[] polynomial = ecParameters.Curve.Polynomial; // The most significant byte needs a set bit, and the least significant bit must be set. @@ -555,7 +648,7 @@ private static void WriteChar2Parameters(in ECParameters ecParameters, AsnWriter } else { - throw new CryptographicException($"Failure at byteIdx={byteIdx}, localBitIndex={localBitIdx} for {BitConverter.ToString(polynomial)}"); + throw new CryptographicException(SR.Cryptography_InvalidECCharacteristic2Curve); } } } @@ -579,75 +672,6 @@ private static void WriteChar2Parameters(in ECParameters ecParameters, AsnWriter // No smaller bases exist throw new CryptographicException(SR.Cryptography_InvalidECCharacteristic2Curve); } - - // SpecifiedECDomain - writer.PushSequence(); - { - // version - // We don't know if the seed (if present) is verifiably random (2). - // We also don't know if the base point is verifiably random (3). - // So just be version 1. - writer.WriteInteger(1); - - // fieldId - writer.PushSequence(); - { - // id - writer.WriteObjectIdentifier(Oids.EcChar2Field); - - // Parameters (Characteristic-two) - writer.PushSequence(); - { - // m - writer.WriteInteger(m); - - if (k3 > 0) - { - writer.WriteObjectIdentifier(Oids.EcChar2PentanomialBasis); - - writer.PushSequence(); - { - writer.WriteInteger(k3); - writer.WriteInteger(k2); - writer.WriteInteger(k1); - - writer.PopSequence(); - } - } - else - { - Debug.Assert(k2 < 0); - Debug.Assert(k1 > 0); - - writer.WriteObjectIdentifier(Oids.EcChar2TrinomialBasis); - writer.WriteInteger(k1); - } - - writer.PopSequence(); - } - - writer.PopSequence(); - } - - // curve - WriteCurve(ecParameters.Curve, writer); - - // base - WriteUncompressedBasePoint(ecParameters, writer); - - // order - writer.WriteIntegerUnsigned(ecParameters.Curve.Order); - - // cofactor - if (ecParameters.Curve.Cofactor != null) - { - writer.WriteIntegerUnsigned(ecParameters.Curve.Cofactor); - } - - // hash is omitted. - - writer.PopSequence(); - } } private static void WriteCurve(in ECCurve curve, AsnWriter writer) diff --git a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs index 030c8e0f67f2..65bf6e1ce145 100644 --- a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs +++ b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs @@ -24,6 +24,10 @@ public abstract class ECKeyFileTests where T : AsymmetricAlgorithm public static bool SupportsC2pnb163v1 { get; } = IsCurveSupported(EccTestData.C2pnb163v1Key1.Curve.Oid); + // This would need to be virtualized if there was ever a platform that + // allowed explicit in ECDH or ECDSA but not the other. + public static bool SupportsExplicitCurves { get; } = EcDiffieHellman.Tests.ECDiffieHellmanFactory.ExplicitCurvesSupported; + private static bool IsCurveSupported(Oid oid) { return EcDiffieHellman.Tests.ECDiffieHellmanFactory.IsCurveValid(oid); @@ -227,18 +231,15 @@ public void ReadWriteNistP256ECPrivateKey() } [Fact] - public void ReadNistP256Explicit() + public void ReadWriteNistP256ExplicitECPrivateKey() { - byte[] explicitSPKI = Convert.FromBase64String(@" -MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA -AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA//// -///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd -NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5 -RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA -//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABIEB7OR0ZKbq1wz2mm4r09iG -kaMmLSLLpPdjXq/yZoCo2KErph1ZkjX2fZy01Y8Xg9PKQ+ePClq6piQHmTbAw6k="); + // null out the parameters that don't roundtrip. + ECParameters expected = EccTestData.GetNistP256ReferenceKeyExplicit(); + expected.Curve.Seed = null; + expected.Curve.Hash = null; - byte[] explicitECPrivateKey = Convert.FromBase64String(@" + ReadWriteBase64ECPrivateKey( + @" MIIBaAIBAQQgcKEsLbFoRe1W/2jPwhpHKz8E19aFG/Y0ny19WzRSs4qggfowgfcC AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA//////////////// MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr @@ -246,9 +247,21 @@ public void ReadNistP256Explicit() axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 YyVRAgEBoUQDQgAEgQHs5HRkpurXDPaabivT2IaRoyYtIsuk92Ner/JmgKjYoSum -HVmSNfZ9nLTVjxeD08pD548KWrqmJAeZNsDDqQ=="); +HVmSNfZ9nLTVjxeD08pD548KWrqmJAeZNsDDqQ==", + expected, + SupportsExplicitCurves); + } - byte[] explicitPkcs8 = Convert.FromBase64String(@" + [Fact] + public void ReadWriteNistP256ExplicitPkcs8() + { + // null out the parameters that don't roundtrip. + ECParameters expected = EccTestData.GetNistP256ReferenceKeyExplicit(); + expected.Curve.Seed = null; + expected.Curve.Hash = null; + + ReadWriteBase64Pkcs8( + @" MIIBeQIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA ///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV @@ -256,34 +269,58 @@ public void ReadNistP256Explicit() 9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBG0wawIBAQQgcKEsLbFoRe1W /2jPwhpHKz8E19aFG/Y0ny19WzRSs4qhRANCAASBAezkdGSm6tcM9ppuK9PYhpGj -Ji0iy6T3Y16v8maAqNihK6YdWZI19n2ctNWPF4PTykPnjwpauqYkB5k2wMOp"); - - using (T key = CreateKey()) - { - Assert.ThrowsAny( - () => key.ImportSubjectPublicKeyInfo(explicitSPKI, out _)); +Ji0iy6T3Y16v8maAqNihK6YdWZI19n2ctNWPF4PTykPnjwpauqYkB5k2wMOp", + expected, + SupportsExplicitCurves); + } - Assert.ThrowsAny( - () => ImportECPrivateKey(key, explicitECPrivateKey, out _)); + [Fact] + public void ReadWriteNistP256ExplicitEncryptedPkcs8() + { + // null out the parameters that don't roundtrip. + ECParameters expected = EccTestData.GetNistP256ReferenceKeyExplicit(); + expected.Curve.Seed = null; + expected.Curve.Hash = null; - // Win10 supports explicit curve PKCS8 on the CNG types. - if (!PlatformDetection.IsWindows10Version1607OrGreater) - { - Assert.ThrowsAny( - () => key.ImportPkcs8PrivateKey(explicitPkcs8, out _)); + ReadWriteBase64EncryptedPkcs8( + @" +MIIBoTAbBgkqhkiG9w0BBQMwDgQIQqYZ3N87K0ICAggABIIBgOHAWa6wz144p0uT +qZsQAbQcIpAFBQRC382dxiOHCV11OyZg264SmxS9iY1OEwIr/peACLu+Fk7zPKhv +Ox1hYz/OeLoKKdtBMqrp65JmH73jG8qeAMuYNj83AIERY7Cckuc2fEC2GTEJcNWs +olE+0p4H6yIvXI48NEQazj5w9zfOGvLmP6Kw6nX+SV3fzM9jHskU226LnDdokGVg +an6/hV1r+2+n2MujhfNzQd/5vW5zx7PN/1aMVMz3wUv9t8scDppeMR5CNCMkxlRA +cQ2lfx2vqFuY70EckgumDqm7AtKK2bLlA6XGTb8HuqKHA0l1zrul9AOBC1g33isD +5CJu1CCT34adV4E4G44uiRQUtf+K8m5Oeo8FI/gGBxdQyOh1k8TNsM+p32gTU8HH +89M5R+s1ayQI7jVPGHXm8Ch7lxvqo6FZAu6+vh23vTwVShUTpGYd0XguE6XKJjGx +eWDIWFuFRj58uAQ65/viFausHWt1BdywcwcyVRb2eLI5MR7DWA==", + "explicit", + new PbeParameters( + PbeEncryptionAlgorithm.Aes128Cbc, + HashAlgorithmName.SHA256, + 1234), + expected, + SupportsExplicitCurves); + } - Pkcs8PrivateKeyInfo builder = Pkcs8PrivateKeyInfo.Decode(explicitPkcs8, out _, skipCopy: true); - byte[] explicitEncryptedPkcs8 = builder.Encrypt( - "asdf", - new PbeParameters( - PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, - HashAlgorithmName.SHA1, - 2048)); + [Fact] + public void ReadWriteNistP256ExplicitSubjectPublicKeyInfo() + { + // null out the parameters that don't roundtrip. + ECParameters expected = EccTestData.GetNistP256ReferenceKeyExplicit(); + expected.Curve.Seed = null; + expected.Curve.Hash = null; - Assert.ThrowsAny( - () => key.ImportEncryptedPkcs8PrivateKey("asdf", explicitEncryptedPkcs8, out _)); - } - } + ReadWriteBase64SubjectPublicKeyInfo( + @" +MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA +AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA//// +///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd +NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5 +RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA +//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABIEB7OR0ZKbq1wz2mm4r09iG +kaMmLSLLpPdjXq/yZoCo2KErph1ZkjX2fZy01Y8Xg9PKQ+ePClq6piQHmTbAw6k=", + expected, + SupportsExplicitCurves); } [Fact] diff --git a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestData.cs b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestData.cs index 72497f054203..0fd07ac3c7b3 100644 --- a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestData.cs +++ b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/EccTestData.cs @@ -215,6 +215,24 @@ internal static ECParameters GetNistP256ReferenceKey() }; } + internal static ECParameters GetNistP256ReferenceKeyExplicit() + { + // From Suite B Implementers's Guide to FIPS 186-3 (ECDSA) + // Section D.1.1 + return new ECParameters + { + Curve = GetNistP256ExplicitCurve(), + + Q = + { + X = "8101ECE47464A6EAD70CF69A6E2BD3D88691A3262D22CBA4F7635EAFF26680A8".HexToByteArray(), + Y = "D8A12BA61D599235F67D9CB4D58F1783D3CA43E78F0A5ABAA624079936C0C3A9".HexToByteArray(), + }, + + D = "70A12C2DB16845ED56FF68CFC21A472B3F04D7D6851BF6349F2D7D5B3452B38A".HexToByteArray(), + }; + } + internal static readonly ECParameters BrainpoolP160r1Key1 = new ECParameters { From 4b4017db34e8191cc4ee0a2a1048c29e03b5f08d Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 25 Sep 2019 23:20:51 -0700 Subject: [PATCH 21/61] Fix exception --- .../src/System/Security/Cryptography/EccKeyFormatHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index b1206df00b14..a55b2da66683 100644 --- a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -500,7 +500,8 @@ private static void WriteEcParameters(in ECParameters ecParameters, AsnWriter wr } else { - throw new CryptographicException("implicit"); + throw new CryptographicException( + SR.Format(SR.Cryptography_CurveNotSupported, ecParameters.Curve.CurveType.ToString())); } } From a94d543c5683c867cee644c14f47ca8614b163c9 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 9 Oct 2019 13:13:37 -0700 Subject: [PATCH 22/61] Fix bad merge --- .../Cryptography/EccKeyFormatHelper.cs | 208 ------------------ .../EC/ECKeyFileTests.cs | 6 - 2 files changed, 214 deletions(-) diff --git a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index 6c7d64908305..cae5d7c10f2f 100644 --- a/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -726,214 +726,6 @@ private static void WriteUncompressedBasePoint(in ECParameters ecParameters, Asn CryptoPool.Return(tmp, clearSize: 0); } - private static void WriteSpecifiedECDomain(in ECParameters ecParameters, AsnWriter writer) - { - int m; - int k1; - int k2; - int k3; - m = k1 = k2 = k3 = -1; - - if (ecParameters.Curve.IsCharacteristic2) - { - DetermineChar2Parameters(ecParameters, ref m, ref k1, ref k2, ref k3); - } - - // SpecifiedECDomain - writer.PushSequence(); - { - // version - // We don't know if the seed (if present) is verifiably random (2). - // We also don't know if the base point is verifiably random (3). - // So just be version 1. - writer.WriteInteger(1); - - // fieldId - writer.PushSequence(); - { - if (ecParameters.Curve.IsPrime) - { - writer.WriteObjectIdentifier(Oids.EcPrimeField); - writer.WriteIntegerUnsigned(ecParameters.Curve.Prime); - } - else - { - Debug.Assert(ecParameters.Curve.IsCharacteristic2); - - // id - writer.WriteObjectIdentifier(Oids.EcChar2Field); - - // Parameters (Characteristic-two) - writer.PushSequence(); - { - // m - writer.WriteInteger(m); - - if (k3 > 0) - { - writer.WriteObjectIdentifier(Oids.EcChar2PentanomialBasis); - - writer.PushSequence(); - { - writer.WriteInteger(k3); - writer.WriteInteger(k2); - writer.WriteInteger(k1); - - writer.PopSequence(); - } - } - else - { - Debug.Assert(k2 < 0); - Debug.Assert(k1 > 0); - - writer.WriteObjectIdentifier(Oids.EcChar2TrinomialBasis); - writer.WriteInteger(k1); - } - - writer.PopSequence(); - } - } - - writer.PopSequence(); - } - - // curve - WriteCurve(ecParameters.Curve, writer); - - // base - WriteUncompressedBasePoint(ecParameters, writer); - - // order - writer.WriteIntegerUnsigned(ecParameters.Curve.Order); - - // cofactor - if (ecParameters.Curve.Cofactor != null) - { - writer.WriteIntegerUnsigned(ecParameters.Curve.Cofactor); - } - - // hash is omitted. - - writer.PopSequence(); - } - } - - private static void DetermineChar2Parameters( - in ECParameters ecParameters, - ref int m, - ref int k1, - ref int k2, - ref int k3) - { - byte[] polynomial = ecParameters.Curve.Polynomial; - - // The most significant byte needs a set bit, and the least significant bit must be set. - if (polynomial[0] == 0 || (polynomial[polynomial.Length - 1] & 1) != 1) - { - throw new CryptographicException(SR.Cryptography_InvalidECCharacteristic2Curve); - } - - // This loop is going to look for the set bits. - // There must be be either 3 (trinomial) or 5 (pentanomial). - for (int byteIdx = 0; byteIdx < polynomial.Length; byteIdx++) - { - byte val = polynomial[byteIdx]; - - for (int localBitIdx = 7; localBitIdx >= 0; localBitIdx--) - { - int test = 1 << localBitIdx; - - if ((val & test) == test) - { - int bitIndex = 8 * (polynomial.Length - 1 - byteIdx) + localBitIdx; - - if (m < 0) - { - m = bitIndex; - } - else if (k1 < 0) - { - k1 = bitIndex; - } - else if (k2 < 0) - { - k2 = bitIndex; - } - else if (k3 < 0) - { - k3 = bitIndex; - } - else if (byteIdx == polynomial.Length - 1 && bitIndex == 0) - { - // The bottom bit is always set, it's not considered a parameter. - } - else - { - throw new CryptographicException(SR.Cryptography_InvalidECCharacteristic2Curve); - } - } - } - } - - if (k3 > 0) - { - // Pentanomial - } - else if (k2 > 0) - { - // There is no quatranomial - throw new CryptographicException(SR.Cryptography_InvalidECCharacteristic2Curve); - } - else if (k1 > 0) - { - // Trinomial - } - else - { - // No smaller bases exist - throw new CryptographicException(SR.Cryptography_InvalidECCharacteristic2Curve); - } - } - - private static void WriteCurve(in ECCurve curve, AsnWriter writer) - { - writer.PushSequence(); - WriteFieldElement(curve.A, writer); - WriteFieldElement(curve.B, writer); - - if (curve.Seed != null) - { - writer.WriteBitString(curve.Seed); - } - - writer.PopSequence(); - } - - private static void WriteFieldElement(byte[] fieldElement, AsnWriter writer) - { - int start = 0; - - while (start < fieldElement.Length - 1 && fieldElement[start] == 0) - { - start++; - } - - writer.WriteOctetString(fieldElement.AsSpan(start)); - } - - private static void WriteUncompressedBasePoint(in ECParameters ecParameters, AsnWriter writer) - { - int basePointLength = ecParameters.Curve.G.X.Length * 2 + 1; - byte[] tmp = CryptoPool.Rent(basePointLength); - tmp[0] = 0x04; - ecParameters.Curve.G.X.CopyTo(tmp.AsSpan(1)); - ecParameters.Curve.G.Y.CopyTo(tmp.AsSpan(1 + ecParameters.Curve.G.X.Length)); - writer.WriteOctetString(tmp.AsSpan(0, basePointLength)); - // Non-sensitive data. - CryptoPool.Return(tmp, clearSize: 0); - } - private static void WriteUncompressedPublicKey(in ECParameters ecParameters, AsnWriter writer) { int publicKeyLength = ecParameters.Q.X.Length * 2 + 1; diff --git a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs index c5ebbf9ecbec..a6c71572a336 100644 --- a/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs +++ b/src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/EC/ECKeyFileTests.cs @@ -26,12 +26,6 @@ public abstract class ECKeyFileTests where T : AsymmetricAlgorithm // allowed explicit in ECDH or ECDSA but not the other. public static bool SupportsExplicitCurves { get; } = EcDiffieHellman.Tests.ECDiffieHellmanFactory.ExplicitCurvesSupported; - public static bool SupportsC2pnb163v1 { get; } = IsCurveSupported(EccTestData.C2pnb163v1Key1.Curve.Oid); - - // This would need to be virtualized if there was ever a platform that - // allowed explicit in ECDH or ECDSA but not the other. - public static bool SupportsExplicitCurves { get; } = EcDiffieHellman.Tests.ECDiffieHellmanFactory.ExplicitCurvesSupported; - private static bool IsCurveSupported(Oid oid) { return EcDiffieHellman.Tests.ECDiffieHellmanFactory.IsCurveValid(oid); From a259952f8b260616622999ae3ec4bfd2ba52ff8c Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 9 Oct 2019 14:53:31 -0700 Subject: [PATCH 23/61] Simplify SafePasswordHandle --- .../Interop.X509.cs | 14 +------- .../Pal.Unix/UnixExportProvider.cs | 10 ++---- .../Cryptography/Pal.Unix/UnixPkcs12Reader.cs | 5 ++- .../SafeHandles/SafePasswordHandle.Unix.cs | 32 ------------------ .../SafeHandles/SafePasswordHandle.Windows.cs | 28 ---------------- .../Win32/SafeHandles/SafePasswordHandle.cs | 33 +++++++++++++------ ...urity.Cryptography.X509Certificates.csproj | 2 -- 7 files changed, 28 insertions(+), 96 deletions(-) delete mode 100644 src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs delete mode 100644 src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Windows.cs diff --git a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs index d1df3855e91b..610bd642fc93 100644 --- a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs +++ b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.cs @@ -131,19 +131,7 @@ internal static SafeSecCertificateHandle X509ImportCertificate( if (!importPassword.IsInvalid) { importPassword.DangerousAddRef(ref hasRef); - IntPtr passwordHandle = importPassword.DangerousGetHandle(); - - if (passwordHandle != IntPtr.Zero) - { - unsafe - { - ReadOnlySpan passwordSpan = new ReadOnlySpan( - (char*)passwordHandle, - importPassword.Length); - - cfPassphrase = CoreFoundation.CFStringCreateFromSpan(passwordSpan); - } - } + cfPassphrase = CoreFoundation.CFStringCreateFromSpan(importPassword.DangerousGetSpan()); } try diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs index 6035f2e232d5..58888f41a606 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixExportProvider.cs @@ -106,18 +106,12 @@ private byte[] ExportPfx(SafePasswordHandle password) ArraySegment encodedAuthSafe = default; bool gotRef = false; - ReadOnlySpan passwordSpan; password.DangerousAddRef(ref gotRef); - unsafe - { - passwordSpan = new ReadOnlySpan( - (char*)password.DangerousGetHandle(), - password.Length); - } - try { + ReadOnlySpan passwordSpan = password.DangerousGetSpan(); + int keyIdx = 0; int certIdx = 0; diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs index 1098a15cd3d4..d8822f085fb2 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/UnixPkcs12Reader.cs @@ -137,7 +137,7 @@ public void Dispose() } } - public unsafe void Decrypt(SafePasswordHandle password) + public void Decrypt(SafePasswordHandle password) { ReadOnlyMemory authSafeSpan = Helpers.DecodeOctetStringAsMemory(_pfxAsn.AuthSafe.Content); @@ -147,8 +147,7 @@ public unsafe void Decrypt(SafePasswordHandle password) try { - ReadOnlySpan passwordChars = - new ReadOnlySpan((char*)password.DangerousGetHandle(), password.Length); + ReadOnlySpan passwordChars = password.DangerousGetSpan(); if (_pfxAsn.MacData.HasValue) { diff --git a/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs b/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs deleted file mode 100644 index d22b778e1151..000000000000 --- a/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Unix.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; -using System.Security; - -namespace Microsoft.Win32.SafeHandles -{ - internal partial class SafePasswordHandle - { - internal int Length { get; private set; } - - private IntPtr CreateHandle(string password) - { - Length = password?.Length ?? 0; - return Marshal.StringToHGlobalUni(password); - } - - private IntPtr CreateHandle(SecureString password) - { - Length = password?.Length ?? 0; - return Marshal.SecureStringToGlobalAllocUnicode(password); - } - - private void FreeHandle() - { - Marshal.ZeroFreeGlobalAllocUnicode(handle); - } - } -} diff --git a/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Windows.cs b/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Windows.cs deleted file mode 100644 index 93dce2ab136b..000000000000 --- a/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.Windows.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; -using System.Security; - -namespace Microsoft.Win32.SafeHandles -{ - internal partial class SafePasswordHandle - { - private IntPtr CreateHandle(string password) - { - return Marshal.StringToHGlobalUni(password); - } - - private IntPtr CreateHandle(SecureString password) - { - return Marshal.SecureStringToGlobalAllocUnicode(password); - } - - private void FreeHandle() - { - Marshal.ZeroFreeGlobalAllocUnicode(handle); - } - } -} diff --git a/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs b/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs index f9235193b43d..2d1ca236fb40 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafePasswordHandle.cs @@ -11,33 +11,35 @@ namespace Microsoft.Win32.SafeHandles /// /// Wrap a string- or SecureString-based object. A null value indicates IntPtr.Zero should be used. /// - internal sealed partial class SafePasswordHandle : SafeHandle + internal sealed partial class SafePasswordHandle : SafeHandleZeroOrMinusOneIsInvalid { + internal int Length { get; private set; } + public SafePasswordHandle(string password) - : base(IntPtr.Zero, ownsHandle: true) + : base(ownsHandle: true) { if (password != null) { - SetHandle(CreateHandle(password)); + handle = Marshal.StringToHGlobalUni(password); + Length = password.Length; } } public SafePasswordHandle(SecureString password) - : base(IntPtr.Zero, ownsHandle: true) + : base(ownsHandle: true) { if (password != null) { - SetHandle(CreateHandle(password)); + handle = Marshal.SecureStringToGlobalAllocUnicode(password); + Length = password.Length; } } protected override bool ReleaseHandle() { - if (handle != IntPtr.Zero) - { - FreeHandle(); - } + Marshal.ZeroFreeGlobalAllocUnicode(handle); SetHandle((IntPtr)(-1)); + Length = 0; return true; } @@ -51,7 +53,18 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - public override bool IsInvalid => handle == (IntPtr)(-1); + internal ReadOnlySpan DangerousGetSpan() + { + if (IsInvalid) + { + return default; + } + + unsafe + { + return new ReadOnlySpan((char*)handle, Length); + } + } public static SafePasswordHandle InvalidHandle => SafeHandleCache.GetInvalidHandle( diff --git a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index bf04b94d3df6..1a59ae1c83e5 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -246,7 +246,6 @@ - Common\Interop\Windows\Crypt32\Interop.CertCloseStore.cs @@ -748,7 +747,6 @@ - From d2881db473ab03824100da9cc00620bda8d7c63f Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 9 Oct 2019 14:53:52 -0700 Subject: [PATCH 24/61] Commit asn.xml generated changes --- .../Asn1/DistributionPointAsn.xml.cs | 19 +++++++++-------- .../Asn1/DistributionPointNameAsn.xml.cs | 21 ++++++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/Asn1/DistributionPointAsn.xml.cs b/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/Asn1/DistributionPointAsn.xml.cs index 699659ea9c32..a66cde66ba55 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/Asn1/DistributionPointAsn.xml.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/Asn1/DistributionPointAsn.xml.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#pragma warning disable SA1028 // ignore whitespace warnings for generated code using System; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -16,16 +17,16 @@ internal partial struct DistributionPointAsn internal System.Security.Cryptography.X509Certificates.Asn1.DistributionPointNameAsn? DistributionPoint; internal System.Security.Cryptography.X509Certificates.Asn1.ReasonFlagsAsn? Reasons; internal System.Security.Cryptography.Asn1.GeneralNameAsn[] CRLIssuer; - + internal void Encode(AsnWriter writer) { Encode(writer, Asn1Tag.Sequence); } - + internal void Encode(AsnWriter writer, Asn1Tag tag) { writer.PushSequence(tag); - + if (DistributionPoint.HasValue) { @@ -47,7 +48,7 @@ internal void Encode(AsnWriter writer, Asn1Tag tag) writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 2)); for (int i = 0; i < CRLIssuer.Length; i++) { - CRLIssuer[i].Encode(writer); + CRLIssuer[i].Encode(writer); } writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, 2)); @@ -60,11 +61,11 @@ internal static DistributionPointAsn Decode(ReadOnlyMemory encoded, AsnEnc { return Decode(Asn1Tag.Sequence, encoded, ruleSet); } - + internal static DistributionPointAsn Decode(Asn1Tag expectedTag, ReadOnlyMemory encoded, AsnEncodingRules ruleSet) { AsnReader reader = new AsnReader(encoded, ruleSet); - + Decode(reader, expectedTag, out DistributionPointAsn decoded); reader.ThrowIfNotEmpty(); return decoded; @@ -87,7 +88,7 @@ internal static void Decode(AsnReader reader, Asn1Tag expectedTag, out Distribut AsnReader sequenceReader = reader.ReadSequence(expectedTag); AsnReader explicitReader; AsnReader collectionReader; - + if (sequenceReader.HasData && sequenceReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 0))) { @@ -117,7 +118,7 @@ internal static void Decode(AsnReader reader, Asn1Tag expectedTag, out Distribut while (collectionReader.HasData) { - System.Security.Cryptography.Asn1.GeneralNameAsn.Decode(collectionReader, out tmpItem); + System.Security.Cryptography.Asn1.GeneralNameAsn.Decode(collectionReader, out tmpItem); tmpList.Add(tmpItem); } diff --git a/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/Asn1/DistributionPointNameAsn.xml.cs b/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/Asn1/DistributionPointNameAsn.xml.cs index 599175c20978..e64481a73d39 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/Asn1/DistributionPointNameAsn.xml.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/Asn1/DistributionPointNameAsn.xml.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#pragma warning disable SA1028 // ignore whitespace warnings for generated code using System; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -29,7 +30,7 @@ static DistributionPointNameAsn() usedTags.Add(tag, fieldName); }; - + ensureUniqueTag(new Asn1Tag(TagClass.ContextSpecific, 0), "FullName"); ensureUniqueTag(new Asn1Tag(TagClass.ContextSpecific, 1), "NameRelativeToCRLIssuer"); } @@ -37,18 +38,18 @@ static DistributionPointNameAsn() internal void Encode(AsnWriter writer) { - bool wroteValue = false; - + bool wroteValue = false; + if (FullName != null) { if (wroteValue) throw new CryptographicException(); - + writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); for (int i = 0; i < FullName.Length; i++) { - FullName[i].Encode(writer); + FullName[i].Encode(writer); } writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); @@ -59,7 +60,7 @@ internal void Encode(AsnWriter writer) { if (wroteValue) throw new CryptographicException(); - + // Validator for tag constraint for NameRelativeToCRLIssuer { if (!Asn1Tag.TryDecode(NameRelativeToCRLIssuer.Value.Span, out Asn1Tag validateTag, out _) || @@ -82,7 +83,7 @@ internal void Encode(AsnWriter writer) internal static DistributionPointNameAsn Decode(ReadOnlyMemory encoded, AsnEncodingRules ruleSet) { AsnReader reader = new AsnReader(encoded, ruleSet); - + Decode(reader, out DistributionPointNameAsn decoded); reader.ThrowIfNotEmpty(); return decoded; @@ -96,7 +97,7 @@ internal static void Decode(AsnReader reader, out DistributionPointNameAsn decod decoded = default; Asn1Tag tag = reader.PeekTag(); AsnReader collectionReader; - + if (tag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 0))) { @@ -108,7 +109,7 @@ internal static void Decode(AsnReader reader, out DistributionPointNameAsn decod while (collectionReader.HasData) { - System.Security.Cryptography.Asn1.GeneralNameAsn.Decode(collectionReader, out tmpItem); + System.Security.Cryptography.Asn1.GeneralNameAsn.Decode(collectionReader, out tmpItem); tmpList.Add(tmpItem); } From dfee702b3865956b033a1577a54b273e795a7c21 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 10 Oct 2019 08:44:33 -0700 Subject: [PATCH 25/61] Address missed namespace renames. --- .../System/Security/Cryptography/Asn1/Pkcs12/CertBagAsn.xml | 2 +- .../System/Security/Cryptography/Asn1/Pkcs12/CertBagAsn.xml.cs | 2 +- .../System/Security/Cryptography/Asn1/Pkcs12/SafeBagAsn.xml | 2 +- .../System/Security/Cryptography/Asn1/Pkcs12/SafeBagAsn.xml.cs | 2 +- .../src/System/Security/Cryptography/Pkcs/Pkcs12CertBag.cs | 2 +- .../System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs | 3 +-- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/CertBagAsn.xml b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/CertBagAsn.xml index 4b0445f95fa7..1a9fc1ca4e65 100644 --- a/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/CertBagAsn.xml +++ b/src/Common/src/System/Security/Cryptography/Asn1/Pkcs12/CertBagAsn.xml @@ -2,7 +2,7 @@ + namespace="System.Security.Cryptography.Asn1.Pkcs12">