diff options
30 files changed, 1988 insertions, 0 deletions
diff --git a/src/Common/src/Interop/Unix/libcrypto/Interop.ASN1.cs b/src/Common/src/Interop/Unix/libcrypto/Interop.ASN1.cs new file mode 100644 index 0000000000..6549a3aa5f --- /dev/null +++ b/src/Common/src/Interop/Unix/libcrypto/Interop.ASN1.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; + +using Microsoft.Win32.SafeHandles; + +// ASN1_INTEGER_get returns a long. +// On Linux/MacOS x64 that means sizeof(void*) +using NativeLong = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class libcrypto + { + internal const int NID_undef = 0; + + [DllImport(Libraries.LibCrypto)] + internal static extern NativeLong ASN1_INTEGER_get(IntPtr a); + + [DllImport(Libraries.LibCrypto, CharSet = CharSet.Ansi)] + internal static extern SafeAsn1ObjectHandle OBJ_txt2obj(string s, [MarshalAs(UnmanagedType.Bool)] bool no_name); + + [DllImport(Libraries.LibCrypto, CharSet = CharSet.Ansi)] + private static extern int OBJ_obj2txt(StringBuilder buf, int buf_len, IntPtr a, [MarshalAs(UnmanagedType.Bool)] bool no_name); + + [DllImport(Libraries.LibCrypto, CharSet = CharSet.Ansi)] + internal static extern int OBJ_txt2nid(string s); + + [DllImport(Libraries.LibCrypto, CharSet = CharSet.Ansi)] + internal static extern int OBJ_ln2nid(string ln); + + [DllImport(Libraries.LibCrypto, CharSet = CharSet.Ansi)] + internal static extern int OBJ_sn2nid(string sn); + + [DllImport(Libraries.LibCrypto, CharSet = CharSet.Ansi)] + internal static extern string OBJ_nid2ln(int n); + + // Returns shared pointers, should not be tracked as a SafeHandle. + [DllImport(Libraries.LibCrypto)] + internal static extern IntPtr OBJ_nid2obj(int nid); + + [DllImport(Libraries.LibCrypto)] + internal static extern void ASN1_OBJECT_free(IntPtr o); + + [DllImport(Libraries.LibCrypto)] + internal static unsafe extern SafeAsn1BitStringHandle d2i_ASN1_BIT_STRING(IntPtr zero, byte** ppin, int len); + + [DllImport(Libraries.LibCrypto)] + internal static extern void ASN1_BIT_STRING_free(IntPtr o); + + [DllImport(Libraries.LibCrypto)] + internal static unsafe extern SafeAsn1OctetStringHandle d2i_ASN1_OCTET_STRING(IntPtr zero, byte** ppin, int len); + + [DllImport(Libraries.LibCrypto)] + internal static extern SafeAsn1OctetStringHandle ASN1_OCTET_STRING_new(); + + [DllImport(Libraries.LibCrypto)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ASN1_OCTET_STRING_set(SafeAsn1OctetStringHandle o, byte[] d, int len); + + [DllImport(Libraries.LibCrypto)] + internal static extern void ASN1_OCTET_STRING_free(IntPtr o); + + internal static string OBJ_obj2txt_helper(IntPtr asn1ObjectPtr) + { + // OBJ_obj2txt returns the number of bytes that should have been in the answer, but it does not accept + // a NULL buffer. The documentation says "A buffer length of 80 should be more than enough to handle + // any OID encountered in practice", so start with a buffer of size 80, and try again if required. + // Therefore, 512 should be quite sufficient. + StringBuilder buf = new StringBuilder(80); + + int bytesNeeded = OBJ_obj2txt(buf, buf.Capacity, asn1ObjectPtr, true); + + if (bytesNeeded < 0) + { + throw new CryptographicException(GetOpenSslErrorString()); + } + + Debug.Assert(bytesNeeded != 0, "OBJ_obj2txt reported a zero-length response"); + + if (bytesNeeded >= buf.Capacity) + { + int initialBytesNeeded = bytesNeeded; + + // bytesNeeded does not count the \0 which will be written on the end (based on OpenSSL 1.0.1f), + // so make sure to leave room for it. + buf.EnsureCapacity(bytesNeeded + 1); + + bytesNeeded = OBJ_obj2txt(buf, buf.Capacity, asn1ObjectPtr, true); + + if (bytesNeeded < 0) + { + throw new CryptographicException(GetOpenSslErrorString()); + } + + Debug.Assert( + bytesNeeded == initialBytesNeeded, + "OBJ_obj2txt changed the required number of bytes for the realloc call"); + + if (bytesNeeded >= buf.Capacity) + { + // OBJ_obj2txt is demanding yet more memory + throw new CryptographicException(); + } + } + + return buf.ToString(); + } + } +} diff --git a/src/Common/src/Interop/Unix/libcrypto/Interop.BIO.cs b/src/Common/src/Interop/Unix/libcrypto/Interop.BIO.cs new file mode 100644 index 0000000000..4760e1bafc --- /dev/null +++ b/src/Common/src/Interop/Unix/libcrypto/Interop.BIO.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; +using System.Text; + +using Microsoft.Win32.SafeHandles; + +using NativeLong=System.IntPtr; + +internal static partial class Interop +{ + internal static partial class libcrypto + { + private const int BIO_CTRL_INFO = 3; + + [DllImport(Libraries.LibCrypto)] + internal static extern SafeBioHandle BIO_new(IntPtr type); + + [DllImport(Libraries.LibCrypto)] + internal static extern IntPtr BIO_s_mem(); + + [DllImport(Libraries.LibCrypto)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool BIO_free(IntPtr a); + + [DllImport(Libraries.LibCrypto, CharSet = CharSet.Ansi)] + internal static extern int BIO_gets(SafeBioHandle b, StringBuilder buf, int size); + + [DllImport(Libraries.LibCrypto)] + private static extern NativeLong BIO_ctrl(SafeBioHandle bio, int cmd, NativeLong larg, IntPtr parg); + + internal static int GetMemoryBioSize(SafeBioHandle bio) + { + // This method is equivalent to BIO_get_mem_data(bio, NULL), except not a macro, + // and doesn't expose the NULL. + return BIO_ctrl(bio, BIO_CTRL_INFO, IntPtr.Zero, IntPtr.Zero).ToInt32(); + } + } +} diff --git a/src/Common/src/Interop/Unix/libcrypto/Interop.X509Ext.cs b/src/Common/src/Interop/Unix/libcrypto/Interop.X509Ext.cs new file mode 100644 index 0000000000..e18ac1c2bc --- /dev/null +++ b/src/Common/src/Interop/Unix/libcrypto/Interop.X509Ext.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class libcrypto + { + [DllImport(Libraries.LibCrypto)] + internal static extern SafeX509ExtensionHandle X509_EXTENSION_create_by_OBJ( + IntPtr zero, + SafeAsn1ObjectHandle oid, + [MarshalAs(UnmanagedType.Bool)] bool critical, + SafeAsn1OctetStringHandle data); + + [DllImport(Libraries.LibCrypto)] + internal static extern int X509_EXTENSION_free(IntPtr x); + + [DllImport(Libraries.LibCrypto)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool X509V3_EXT_print(SafeBioHandle buf, SafeX509ExtensionHandle ext, X509V3ExtPrintFlags flags, int indent); + + [DllImport(Libraries.LibCrypto)] + internal static unsafe extern SafeBasicConstraintsHandle d2i_BASIC_CONSTRAINTS(IntPtr zero, byte** ppin, int len); + + [DllImport(Libraries.LibCrypto)] + internal static extern void BASIC_CONSTRAINTS_free(IntPtr a); + + [DllImport(Libraries.LibCrypto)] + internal static unsafe extern SafeEkuExtensionHandle d2i_EXTENDED_KEY_USAGE(IntPtr zero, byte** ppin, int len); + + [DllImport(Libraries.LibCrypto)] + internal static extern void EXTENDED_KEY_USAGE_free(IntPtr a); + + [StructLayout(LayoutKind.Sequential)] + internal struct BASIC_CONSTRAINTS + { + // This is a boolean, but [MarshalAs(UnmanagedType.Bool)] produced a "true != true" answer. + internal int CA; + internal IntPtr pathlen; + } + + // As of 2015-04-11 there is no documentation on openssl.org or within the header files + // which describes possible values of "flags" to X509V3_EXT_print. + // + // The flag which would be the most likely candidate is X509V3_EXT_MULTILINE, but that is a + // flag on the extension definition (and a test call providing that value had no discernable + // effect on OpenSSL 1.0.1f). + [Flags] + internal enum X509V3ExtPrintFlags + { + None = 0, + } + } +} diff --git a/src/Common/src/Interop/Windows/Crypt32/OidInfo.cs b/src/Common/src/Interop/Windows/Crypt32/OidInfo.cs new file mode 100644 index 0000000000..a8a320bc5b --- /dev/null +++ b/src/Common/src/Interop/Windows/Crypt32/OidInfo.cs @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Text; +using global::System.Diagnostics; +using global::System.Diagnostics.Contracts; +using global::System.Collections.Generic; +using global::System.Security.Cryptography; +using global::System.Runtime.InteropServices; + +namespace Internal.NativeCrypto +{ + [StructLayout(LayoutKind.Sequential)] + internal struct CRYPT_OID_INFO + { + public int cbSize; + public IntPtr pszOID; + public IntPtr pwszName; + public OidGroup dwGroupId; + public int AlgId; + public int cbData; + public IntPtr pbData; + + public String OID + { + get + { + return Marshal.PtrToStringAnsi(pszOID); + } + } + + public String Name + { + get + { + return Marshal.PtrToStringUni(pwszName); + } + } + } + + internal enum CryptOidInfoKeyType : int + { + CRYPT_OID_INFO_OID_KEY = 1, + CRYPT_OID_INFO_NAME_KEY = 2, + CRYPT_OID_INFO_ALGID_KEY = 3, + } + + internal static partial class OidInfo + { + private const String Capi2Dll = "Crypt32.dll"; + + public static CRYPT_OID_INFO FindOidInfo(CryptOidInfoKeyType keyType, String key, OidGroup group, bool fallBackToAllGroups) + { + const OidGroup CRYPT_OID_DISABLE_SEARCH_DS_FLAG = unchecked((OidGroup)0x80000000); + Debug.Assert(key != null); + + IntPtr rawKey = IntPtr.Zero; + + try + { + if (keyType == CryptOidInfoKeyType.CRYPT_OID_INFO_OID_KEY) + { + rawKey = Marshal.StringToCoTaskMemAnsi(key); + } + else if (keyType == CryptOidInfoKeyType.CRYPT_OID_INFO_NAME_KEY) + { + rawKey = Marshal.StringToCoTaskMemUni(key); + } + else + { + throw new NotSupportedException(); + } + + // If the group alone isn't sufficient to suppress an active directory lookup, then our + // first attempt should also include the suppression flag + if (!OidGroupWillNotUseActiveDirectory(group)) + { + OidGroup localGroup = group | CRYPT_OID_DISABLE_SEARCH_DS_FLAG; + IntPtr localOidInfo = CryptFindOIDInfo(keyType, rawKey, localGroup); + if (localOidInfo != IntPtr.Zero) + { + return (CRYPT_OID_INFO)Marshal.PtrToStructure(localOidInfo, typeof(CRYPT_OID_INFO)); + } + } + + // Attempt to query with a specific group, to make try to avoid an AD lookup if possible + IntPtr fullOidInfo = CryptFindOIDInfo(keyType, rawKey, group); + if (fullOidInfo != IntPtr.Zero) + { + return (CRYPT_OID_INFO)Marshal.PtrToStructure(fullOidInfo, typeof(CRYPT_OID_INFO)); + } + + if (fallBackToAllGroups && group != OidGroup.All) + { + // Finally, for compatibility with previous runtimes, if we have a group specified retry the + // query with no group + IntPtr allGroupOidInfo = CryptFindOIDInfo(keyType, rawKey, OidGroup.All); + if (allGroupOidInfo != IntPtr.Zero) + { + return (CRYPT_OID_INFO)Marshal.PtrToStructure(fullOidInfo, typeof(CRYPT_OID_INFO)); + } + } + + // Otherwise the lookup failed. + return new CRYPT_OID_INFO() { AlgId = -1 }; + } + finally + { + if (rawKey != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(rawKey); + } + } + } + + private static bool OidGroupWillNotUseActiveDirectory(OidGroup group) + { + // These groups will never cause an Active Directory query + return group == OidGroup.HashAlgorithm || + group == OidGroup.EncryptionAlgorithm || + group == OidGroup.PublicKeyAlgorithm || + group == OidGroup.SignatureAlgorithm || + group == OidGroup.Attribute || + group == OidGroup.ExtensionOrAttribute || + group == OidGroup.KeyDerivationFunction; + } + + [DllImport(Capi2Dll, CharSet = CharSet.Unicode)] + private static extern IntPtr CryptFindOIDInfo(CryptOidInfoKeyType dwKeyType, IntPtr pvKey, OidGroup group); + } +} + diff --git a/src/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs b/src/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs new file mode 100644 index 0000000000..e23df85837 --- /dev/null +++ b/src/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace Microsoft.Win32.SafeHandles +{ + [SecurityCritical] + internal sealed class SafeAsn1ObjectHandle : SafeHandle + { + private SafeAsn1ObjectHandle() : + base(IntPtr.Zero, ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + Interop.libcrypto.ASN1_OBJECT_free(handle); + SetHandle(IntPtr.Zero); + return true; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + } + + [SecurityCritical] + internal sealed class SafeAsn1BitStringHandle : SafeHandle + { + private SafeAsn1BitStringHandle() : + base(IntPtr.Zero, ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + Interop.libcrypto.ASN1_BIT_STRING_free(handle); + SetHandle(IntPtr.Zero); + return true; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + } + + [SecurityCritical] + internal sealed class SafeAsn1OctetStringHandle : SafeHandle + { + private SafeAsn1OctetStringHandle() : + base(IntPtr.Zero, ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + Interop.libcrypto.ASN1_OCTET_STRING_free(handle); + return true; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + } +} diff --git a/src/Common/src/Microsoft/Win32/SafeHandles/SafeBioHandle.Unix.cs b/src/Common/src/Microsoft/Win32/SafeHandles/SafeBioHandle.Unix.cs new file mode 100644 index 0000000000..caf0cb7d01 --- /dev/null +++ b/src/Common/src/Microsoft/Win32/SafeHandles/SafeBioHandle.Unix.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Security; +using System.Runtime.InteropServices; + +namespace Microsoft.Win32.SafeHandles +{ + [SecurityCritical] + internal sealed class SafeBioHandle : SafeHandle + { + private SafeBioHandle() : + base(IntPtr.Zero, ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + Interop.libcrypto.BIO_free(handle); + return true; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + } +} diff --git a/src/Common/src/Microsoft/Win32/SafeHandles/X509ExtensionSafeHandles.Unix.cs b/src/Common/src/Microsoft/Win32/SafeHandles/X509ExtensionSafeHandles.Unix.cs new file mode 100644 index 0000000000..a18a278a1f --- /dev/null +++ b/src/Common/src/Microsoft/Win32/SafeHandles/X509ExtensionSafeHandles.Unix.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace Microsoft.Win32.SafeHandles +{ + [SecurityCritical] + internal sealed class SafeX509ExtensionHandle : SafeHandle + { + private SafeX509ExtensionHandle() : + base(IntPtr.Zero, ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + Interop.libcrypto.X509_EXTENSION_free(handle); + SetHandle(IntPtr.Zero); + return true; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + } + + [SecurityCritical] + internal sealed class SafeBasicConstraintsHandle : SafeHandle + { + private SafeBasicConstraintsHandle() : + base(IntPtr.Zero, ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + Interop.libcrypto.BASIC_CONSTRAINTS_free(handle); + SetHandle(IntPtr.Zero); + return true; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + } + + [SecurityCritical] + internal sealed class SafeEkuExtensionHandle : SafeHandle + { + private SafeEkuExtensionHandle() : + base(IntPtr.Zero, ownsHandle: true) + { + } + + protected override bool ReleaseHandle() + { + Interop.libcrypto.EXTENDED_KEY_USAGE_free(handle); + SetHandle(IntPtr.Zero); + return true; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + } +}
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/System.Security.Cryptography.Encoding.sln b/src/System.Security.Cryptography.Encoding/System.Security.Cryptography.Encoding.sln new file mode 100644 index 0000000000..237228d4b5 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/System.Security.Cryptography.Encoding.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.22911.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Security.Cryptography.Encoding", "src\System.Security.Cryptography.Encoding.csproj", "{AA81E343-5E54-40B0-9381-C459419BE780}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Security.Cryptography.Encoding.Tests", "tests\System.Security.Cryptography.Encoding.Tests.csproj", "{0581E9FA-D639-4B88-96D8-D092760F90B0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Linux_Debug|Any CPU = Linux_Debug|Any CPU + Linux_Release|Any CPU = Linux_Release|Any CPU + OSX_Debug|Any CPU = OSX_Debug|Any CPU + OSX_Release|Any CPU = OSX_Release|Any CPU + Windows_Debug|Any CPU = Windows_Debug|Any CPU + Windows_Release|Any CPU = Windows_Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA81E343-5E54-40B0-9381-C459419BE780}.Linux_Debug|Any CPU.ActiveCfg = Linux_Debug|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.Linux_Debug|Any CPU.Build.0 = Linux_Debug|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.Linux_Release|Any CPU.ActiveCfg = Linux_Release|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.Linux_Release|Any CPU.Build.0 = Linux_Release|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.OSX_Debug|Any CPU.ActiveCfg = OSX_Debug|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.OSX_Debug|Any CPU.Build.0 = OSX_Debug|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.OSX_Release|Any CPU.ActiveCfg = OSX_Release|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.OSX_Release|Any CPU.Build.0 = OSX_Release|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.Windows_Debug|Any CPU.ActiveCfg = Windows_Debug|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.Windows_Debug|Any CPU.Build.0 = Windows_Debug|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.Windows_Release|Any CPU.ActiveCfg = Windows_Release|Any CPU + {AA81E343-5E54-40B0-9381-C459419BE780}.Windows_Release|Any CPU.Build.0 = Windows_Release|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.Linux_Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.Linux_Debug|Any CPU.Build.0 = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.Linux_Release|Any CPU.ActiveCfg = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.Linux_Release|Any CPU.Build.0 = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.OSX_Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.OSX_Debug|Any CPU.Build.0 = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.OSX_Release|Any CPU.ActiveCfg = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.OSX_Release|Any CPU.Build.0 = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.Windows_Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.Windows_Debug|Any CPU.Build.0 = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.Windows_Release|Any CPU.ActiveCfg = Debug|Any CPU + {0581E9FA-D639-4B88-96D8-D092760F90B0}.Windows_Release|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.Unix.cs b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.Unix.cs new file mode 100644 index 0000000000..87986509c5 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.Unix.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Internal.Cryptography +{ + internal abstract partial class AsnFormatter + { + private static readonly AsnFormatter s_instance = new OpenSslAsnFormatter(); + + // ----------------------------- + // ---- PAL layer ends here ---- + // ----------------------------- + } +}
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.Windows.cs b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.Windows.cs new file mode 100644 index 0000000000..041a561d28 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.Windows.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Internal.Cryptography +{ + internal abstract partial class AsnFormatter + { + private static readonly AsnFormatter s_instance = new CngAsnFormatter(); + + // ----------------------------- + // ---- PAL layer ends here ---- + // ----------------------------- + } +}
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.cs b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.cs new file mode 100644 index 0000000000..faf799d9e0 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Security.Cryptography; + +namespace Internal.Cryptography +{ + internal abstract partial class AsnFormatter + { + private static readonly char[] s_hexValues = + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + internal static AsnFormatter Instance { get { return s_instance; } } + + public string Format(Oid oid, byte[] rawData, bool multiLine) + { + return FormatNative(oid, rawData, multiLine) ?? EncodeHexString(rawData); + } + + protected abstract string FormatNative(Oid oid, byte[] rawData, bool multiLine); + + protected static string EncodeHexString(byte[] sArray, bool spaceSeparated = false) + { + return EncodeHexString(sArray, 0, (uint)sArray.Length, spaceSeparated); + } + + private static string EncodeHexString(byte[] sArray, uint start, uint end, bool spaceSeparated) + { + string result = null; + + if (sArray != null) + { + uint len = (end - start) * 2; + + if (spaceSeparated) + { + // There will be n-1 spaces between n bytes. + len += (end - start - 1); + } + + char[] hexOrder = new char[len]; + + for (uint i = start, j = 0; i < end; i++) + { + if (spaceSeparated && i > start) + { + hexOrder[j++] = ' '; + } + + uint digit = (uint)((sArray[i] & 0xf0) >> 4); + hexOrder[j++] = s_hexValues[digit]; + + digit = (uint)(sArray[i] & 0x0f); + hexOrder[j++] = s_hexValues[digit]; + } + + result = new string(hexOrder); + } + + return result; + } + } +} diff --git a/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/CngAsnFormatter.cs b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/CngAsnFormatter.cs new file mode 100644 index 0000000000..3d51a5e06b --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/CngAsnFormatter.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Security.Cryptography; + +using Internal.NativeCrypto; + +namespace Internal.Cryptography +{ + internal sealed class CngAsnFormatter : AsnFormatter + { + protected override string FormatNative(Oid oid, byte[] rawData, bool multiLine) + { + // If OID is not present, then we can force CryptFormatObject + // to use hex formatting by providing an empty OID string. + String oidValue = String.Empty; + if (oid != null && oid.Value != null) + oidValue = oid.Value; + + return Cng.CryptFormatObject(oidValue, rawData, multiLine); + } + } +}
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/Helpers.cs b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/Helpers.cs new file mode 100644 index 0000000000..a9c72e3708 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/Helpers.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.Security.Cryptography; + +namespace Internal.Cryptography +{ + internal static class Helpers + { + public static byte[] CloneByteArray(this byte[] src) + { + return (byte[])(src.Clone()); + } + } +} + diff --git a/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Unix.cs b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Unix.cs new file mode 100644 index 0000000000..d7afa49349 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Unix.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Internal.Cryptography +{ + internal static partial class OidLookup + { + private static string NativeOidToFriendlyName(string oid, OidGroup oidGroup, bool fallBackToAllGroups) + { + // We're going to call OBJ_txt2nid, then OBJ_nid2obj. Anyone proficient in factor/label reduction + // would see that this should be equivalent to OBJ_txt2obj, but it isn't. + // + // OBJ_txt2obj, when given an OID value dotted string will decode the dotted string and return + // a blank(ish) object (which would need to be freed). We could then pass that to OBJ_obj2nid to + // look up the internal identifier. Then, if we got a NID which wasn't NID_undef we would know + // there was a match, and could call OBJ_nid2obj to get the shared pointer to the definition. + // + // In this case, the composition of functions that we want is OBJ_obj2nid(OBJ_txt2obj) => OBJ_txt2nid. + + int nid = Interop.libcrypto.OBJ_txt2nid(oid); + + if (nid == Interop.libcrypto.NID_undef) + { + return null; + } + + return Interop.libcrypto.OBJ_nid2ln(nid); + } + + private static string NativeFriendlyNameToOid(string friendlyName, OidGroup oidGroup, bool fallBackToAllGroups) + { + int nid = Interop.libcrypto.OBJ_ln2nid(friendlyName); + + if (nid == Interop.libcrypto.NID_undef) + { + nid = Interop.libcrypto.OBJ_sn2nid(friendlyName); + } + + if (nid == Interop.libcrypto.NID_undef) + { + return null; + } + + IntPtr sharedObject = Interop.libcrypto.OBJ_nid2obj(nid); + + if (sharedObject == IntPtr.Zero) + { + return null; + } + + return Interop.libcrypto.OBJ_obj2txt_helper(sharedObject); + } + + private static string FindFriendlyNameAlias(string userValue) + { + foreach (FriendlyNameAlias alias in s_friendlyNameAliases) + { + if (userValue.Equals(alias.Windows, StringComparison.OrdinalIgnoreCase)) + { + return alias.OpenSsl; + } + } + + return null; + } + + // ----------------------------- + // ---- PAL layer ends here ---- + // ----------------------------- + } +}
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Windows.cs b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Windows.cs new file mode 100644 index 0000000000..ad6d19d897 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.Windows.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Security.Cryptography; + +using Internal.NativeCrypto; + +namespace Internal.Cryptography +{ + internal static partial class OidLookup + { + private static string NativeOidToFriendlyName(string oid, OidGroup oidGroup, bool fallBackToAllGroups) + { + CRYPT_OID_INFO oidInfo = OidInfo.FindOidInfo(CryptOidInfoKeyType.CRYPT_OID_INFO_OID_KEY, oid, oidGroup, fallBackToAllGroups); + return oidInfo.Name; + } + + private static string NativeFriendlyNameToOid(string friendlyName, OidGroup oidGroup, bool fallBackToAllGroups) + { + CRYPT_OID_INFO oidInfo = OidInfo.FindOidInfo(CryptOidInfoKeyType.CRYPT_OID_INFO_NAME_KEY, friendlyName, oidGroup, fallBackToAllGroups); + return oidInfo.OID; + } + + private static string FindFriendlyNameAlias(string userValue) + { + foreach (FriendlyNameAlias alias in s_friendlyNameAliases) + { + if (userValue.Equals(alias.OpenSsl, StringComparison.OrdinalIgnoreCase)) + { + return alias.Windows; + } + } + + return null; + } + + // ----------------------------- + // ---- PAL layer ends here ---- + // ----------------------------- + } +}
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.cs b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.cs new file mode 100644 index 0000000000..44ec448233 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Security.Cryptography; + +namespace Internal.Cryptography +{ + internal static partial class OidLookup + { + private struct FriendlyNameAlias + { + internal string Windows { get; set; } + internal string OpenSsl { get; set; } + } + + // The Windows cryptography API "Friendly Names" don't use the same name as the OIDs were given + // in their declaring RFCs. If a .NET developer learned to call the algorithm "sha1RSA" then we + // want to understand that it should be called "sha1WithRSAEncryption" when calling into OpenSSL. + // + // The canonical form of the Windows versions come from https://msdn.microsoft.com/en-us/library/ff635603.aspx + private static readonly FriendlyNameAlias[] s_friendlyNameAliases = + { + new FriendlyNameAlias { Windows = "RSA", OpenSsl = "rsaEncryption" }, // RFC 2313 + new FriendlyNameAlias { Windows = "md2RSA", OpenSsl = "md2WithRSAEncryption" }, // RFC 2313 + new FriendlyNameAlias { Windows = "md4RSA", OpenSsl = "md4WithRSAEncryption" }, // RFC 2313 + new FriendlyNameAlias { Windows = "md5RSA", OpenSsl = "md5WithRSAEncryption" }, // RFC 2313 + new FriendlyNameAlias { Windows = "sha1RSA", OpenSsl = "sha1WithRSAEncryption" }, // RFC 3447 + new FriendlyNameAlias { Windows = "sha256RSA", OpenSsl = "sha256WithRSAEncryption" }, // RFC 3447 + new FriendlyNameAlias { Windows = "sha384RSA", OpenSsl = "sha384WithRSAEncryption" }, // RFC 3447 + new FriendlyNameAlias { Windows = "sha512RSA", OpenSsl = "sha512WithRSAEncryption" }, // RFC 3447 + + new FriendlyNameAlias { Windows = "DSA", OpenSsl = "dsaEncryption" }, // RFC 3370 calls this "id-dsa" + new FriendlyNameAlias { Windows = "sha1DSA", OpenSsl = "dsaWithSHA1" }, // RFC 3370 calls this "id-dsa-with-sha1" + + // We can keep going for as many things as we want to support. + }; + + // + // Attempts to map a friendly name to an OID. Returns null if not a known name. + // + public static string ToFriendlyName(string oid, OidGroup oidGroup, bool fallBackToAllGroups) + { + if (oid == null) + throw new ArgumentNullException("oid"); + + return NativeOidToFriendlyName(oid, oidGroup, fallBackToAllGroups); + } + + // + // Attempts to retrieve the friendly name for an OID. Returns null if not a known or valid OID. + // + public static string ToOid(string friendlyName, OidGroup oidGroup, bool fallBackToAllGroups) + { + if (friendlyName == null) + throw new ArgumentNullException("friendlyName"); + + string oid = NativeFriendlyNameToOid(friendlyName, oidGroup, fallBackToAllGroups); + + if (oid == null) + { + string alias = FindFriendlyNameAlias(friendlyName); + + if (alias != null) + { + oid = NativeFriendlyNameToOid(alias, oidGroup, fallBackToAllGroups); + } + } + + return oid; + } + } +} diff --git a/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OpenSslAsnFormatter.cs b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OpenSslAsnFormatter.cs new file mode 100644 index 0000000000..5cc86d023c --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OpenSslAsnFormatter.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Security.Cryptography; +using System.Text; + +using Microsoft.Win32.SafeHandles; + +namespace Internal.Cryptography +{ + internal sealed class OpenSslAsnFormatter : AsnFormatter + { + protected override string FormatNative(Oid oid, byte[] rawData, bool multiLine) + { + if (oid == null || string.IsNullOrEmpty(oid.Value)) + { + return EncodeHexString(rawData, true); + } + + // The established behavior for this method is to return the native answer, if possible, + // or to return null and let rawData get hex-encoded. CryptographicException should not + // be raised. + + using (SafeAsn1ObjectHandle asnOid = Interop.libcrypto.OBJ_txt2obj(oid.Value, true)) + using (SafeAsn1OctetStringHandle octetString = Interop.libcrypto.ASN1_OCTET_STRING_new()) + { + if (asnOid.IsInvalid || octetString.IsInvalid) + { + return null; + } + + if (!Interop.libcrypto.ASN1_OCTET_STRING_set(octetString, rawData, rawData.Length)) + { + return null; + } + + using (SafeBioHandle bio = Interop.libcrypto.BIO_new(Interop.libcrypto.BIO_s_mem())) + using (SafeX509ExtensionHandle x509Ext = Interop.libcrypto.X509_EXTENSION_create_by_OBJ(IntPtr.Zero, asnOid, false, octetString)) + { + if (bio.IsInvalid || x509Ext.IsInvalid) + { + return null; + } + + if (!Interop.libcrypto.X509V3_EXT_print(bio, x509Ext, Interop.libcrypto.X509V3ExtPrintFlags.None, 0)) + { + return null; + } + + int printLen = Interop.libcrypto.GetMemoryBioSize(bio); + + // Account for the null terminator that it'll want to write. + StringBuilder builder = new StringBuilder(printLen + 1); + Interop.libcrypto.BIO_gets(bio, builder, builder.Capacity); + + return builder.ToString(); + } + } + } + } +}
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/src/Resources/Strings.resx b/src/System.Security.Cryptography.Encoding/src/Resources/Strings.resx new file mode 100644 index 0000000000..c7396653a9 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/Resources/Strings.resx @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="Cryptography_Oid_InvalidValue" xml:space="preserve"> + <value>The OID value is invalid.</value> + </data> + <data name="Cryptography_Oid_InvalidName" xml:space="preserve"> + <value>No OID value matches this name.</value> + </data> + <data name="Arg_RankMultiDimNotSupported" xml:space="preserve"> + <value>Only single dimensional arrays are supported for the requested action.</value> + </data> + <data name="ArgumentOutOfRange_Index" xml:space="preserve"> + <value>Index was out of range. Must be non-negative and less than the size of the collection.</value> + </data> + <data name="Argument_InvalidOffLen" xml:space="preserve"> + <value>Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj b/src/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj new file mode 100644 index 0000000000..96e5c2f4e8 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Windows_Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{AA81E343-5E54-40B0-9381-C459419BE780}</ProjectGuid> + <OutputType>Library</OutputType> + <AssemblyName>System.Security.Cryptography.Encoding</AssemblyName> + <AssemblyVersion>4.0.0.0</AssemblyVersion> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <CLSCompliant>false</CLSCompliant> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Linux_Debug|AnyCPU' " /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Linux_Release|AnyCPU' " /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'OSX_Debug|AnyCPU' " /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'OSX_Release|AnyCPU' " /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Windows_Debug|AnyCPU' " /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Windows_Release|AnyCPU' " /> + <ItemGroup> + <Compile Include="Internal\Cryptography\AsnFormatter.cs" /> + <Compile Include="Internal\Cryptography\Helpers.cs" /> + <Compile Include="Internal\Cryptography\OidLookup.cs" /> + <Compile Include="System\Security\Cryptography\AsnEncodedData.cs" /> + <Compile Include="System\Security\Cryptography\Oid.cs" /> + <Compile Include="System\Security\Cryptography\OidCollection.cs" /> + <Compile Include="System\Security\Cryptography\OidEnumerator.cs" /> + <Compile Include="System\Security\Cryptography\OidGroup.cs" /> + </ItemGroup> + <ItemGroup> + <Compile Include="$(CommonPath)\System\ArrayT.cs"> + <Link>Common\System\ArrayT.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\System\Collections\Generic\LowLevelList.cs"> + <Link>Common\System\Collections\Generic\LowLevelList.cs</Link> + </Compile> + </ItemGroup> + <ItemGroup Condition=" '$(TargetsWindows)' == 'true' "> + <Compile Include="Internal\Cryptography\AsnFormatter.Windows.cs" /> + <Compile Include="Internal\Cryptography\CngAsnFormatter.cs" /> + <Compile Include="Internal\Cryptography\OidLookup.Windows.cs" /> + <Compile Include="$(CommonPath)\Interop\Windows\BCrypt\Cng.cs"> + <Link>Common\Interop\Windows\BCrypt\Cng.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Windows\Crypt32\OidInfo.cs"> + <Link>Common\Interop\Windows\Crypt32\OidInfo.cs</Link> + </Compile> + </ItemGroup> + <ItemGroup Condition=" '$(TargetsUnix)' == 'true' "> + <Compile Include="Internal\Cryptography\AsnFormatter.Unix.cs" /> + <Compile Include="Internal\Cryptography\OidLookup.Unix.cs" /> + <Compile Include="Internal\Cryptography\OpenSslAsnFormatter.cs" /> + <Compile Include="$(CommonPath)\Interop\Unix\Interop.Libraries.cs"> + <Link>Common\Interop\Unix\Interop.Libraries.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libcoreclr\Interop.EnsureOpenSslInitialized.cs"> + <Link>Common\Interop\Unix\libcoreclr\Interop.EnsureOpenSslInitialized.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libcrypto\Interop.ASN1.cs"> + <Link>Common\Interop\Unix\libcrypto\Interop.ASN1.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libcrypto\Interop.BIO.cs"> + <Link>Common\Interop\Unix\libcrypto\Interop.BIO.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libcrypto\Interop.ERR.cs"> + <Link>Common\Interop\Unix\libcrypto\Interop.ERR.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libcrypto\Interop.Initialization.cs"> + <Link>Common\Interop\Unix\libcrypto\Interop.Initialization.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Interop\Unix\libcrypto\Interop.X509Ext.cs"> + <Link>Common\Interop\Unix\libcrypto\Interop.X509Ext.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs"> + <Link>Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\SafeBioHandle.Unix.cs"> + <Link>Common\Microsoft\Win32\SafeHandles\SafeBioHandle.Unix.cs</Link> + </Compile> + <Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\X509ExtensionSafeHandles.Unix.cs"> + <Link>Common\Microsoft\Win32\SafeHandles\X509ExtensionSafeHandles.Unix.cs</Link> + </Compile> + </ItemGroup> + <ItemGroup> + <None Include="project.json" /> + </ItemGroup> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> diff --git a/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/AsnEncodedData.cs b/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/AsnEncodedData.cs new file mode 100644 index 0000000000..1e64ccdf02 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/AsnEncodedData.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; + +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + public class AsnEncodedData + { + protected AsnEncodedData() + { + } + + public AsnEncodedData(byte[] rawData) + { + Reset(null, rawData); + } + + public AsnEncodedData(AsnEncodedData asnEncodedData) + { + if (asnEncodedData == null) + throw new ArgumentNullException("asnEncodedData"); + Reset(asnEncodedData._oid, asnEncodedData._rawData); + } + + public AsnEncodedData(Oid oid, byte[] rawData) + { + Reset(oid, rawData); + } + + public AsnEncodedData(String oid, byte[] rawData) + { + Reset(new Oid(oid), rawData); + } + + public Oid Oid + { + get + { + return _oid; + } + + set + { + _oid = (value == null) ? null : new Oid(value); + } + } + + public byte[] RawData + { + get + { + // Desktop compat demands we return the array without copying. + return _rawData; + } + + set + { + if (value == null) + throw new ArgumentNullException("value"); + _rawData = value.CloneByteArray(); + } + } + + public virtual void CopyFrom(AsnEncodedData asnEncodedData) + { + if (asnEncodedData == null) + throw new ArgumentNullException("asnEncodedData"); + Reset(asnEncodedData._oid, asnEncodedData._rawData); + } + + public virtual String Format(bool multiLine) + { + // Return empty string if no data to format. + if (_rawData == null || _rawData.Length == 0) + return String.Empty; + + return AsnFormatter.Instance.Format(_oid, _rawData, multiLine); + } + + private void Reset(Oid oid, byte[] rawData) + { + this.Oid = oid; + this.RawData = rawData; + } + + private Oid _oid = null; + private byte[] _rawData = null; + } +} diff --git a/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/Oid.cs b/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/Oid.cs new file mode 100644 index 0000000000..bf7707abde --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/Oid.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; + +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + public sealed class Oid + { + public Oid(String oid) + { + // If we were passed the friendly name, retrieve the value String. + String oidValue = OidLookup.ToOid(oid, OidGroup.All, fallBackToAllGroups: false); + if (oidValue == null) + { + oidValue = oid; + } + this.Value = oidValue; + + _group = OidGroup.All; + } + + public Oid(String value, String friendlyName) + { + _value = value; + _friendlyName = friendlyName; + } + + public Oid(Oid oid) + { + if (oid == null) + throw new ArgumentNullException("oid"); + _value = oid._value; + _friendlyName = oid._friendlyName; + _group = oid._group; + } + + public static Oid FromFriendlyName(String friendlyName, OidGroup group) + { + if (friendlyName == null) + { + throw new ArgumentNullException("friendlyName"); + } + + String oidValue = OidLookup.ToOid(friendlyName, group, fallBackToAllGroups: false); + if (oidValue == null) + throw new CryptographicException(SR.Cryptography_Oid_InvalidName); + + return new Oid(oidValue, friendlyName, group); + } + + public static Oid FromOidValue(String oidValue, OidGroup group) + { + if (oidValue == null) + throw new ArgumentNullException("oidValue"); + + String friendlyName = OidLookup.ToFriendlyName(oidValue, group, fallBackToAllGroups: false); + if (friendlyName == null) + throw new CryptographicException(SR.Cryptography_Oid_InvalidValue); + + return new Oid(oidValue, friendlyName, group); + } + + public String Value + { + get { return _value; } + set { _value = value; } + } + + public String FriendlyName + { + get + { + if (_friendlyName == null && _value != null) + { + _friendlyName = OidLookup.ToFriendlyName(_value, _group, fallBackToAllGroups: true); + } + + return _friendlyName; + } + set + { + _friendlyName = value; + // If we can find the matching OID value, then update it as well + if (_friendlyName != null) + { + // If FindOidInfo fails, we return a null String + String oidValue = OidLookup.ToOid(_friendlyName, _group, fallBackToAllGroups: true); + if (oidValue != null) + { + _value = oidValue; + } + } + } + } + + private Oid(String value, String friendlyName, OidGroup group) + { + Debug.Assert(value != null); + Debug.Assert(friendlyName != null); + + _value = value; + _friendlyName = friendlyName; + _group = group; + } + + private String _value = null; + private String _friendlyName = null; + private OidGroup _group = OidGroup.All; + } +} diff --git a/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/OidCollection.cs b/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/OidCollection.cs new file mode 100644 index 0000000000..337a4dfaa0 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/OidCollection.cs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + public sealed class OidCollection : ICollection + { + public OidCollection() + { + _list = new LowLevelListWithIList<Oid>(); + } + + public int Add(Oid oid) + { + int count = _list.Count; + _list.Add(oid); + return count; + } + + public Oid this[int index] + { + get + { + return _list[index]; + } + } + + // Indexer using an OID friendly name or value. + public Oid this[String oid] + { + get + { + // If we were passed the friendly name, retrieve the value String. + String oidValue = OidLookup.ToOid(oid, OidGroup.All, fallBackToAllGroups: false); + if (oidValue == null) + { + oidValue = oid; + } + foreach (Oid entry in _list) + { + if (entry.Value == oidValue) + return entry; + } + return null; + } + } + + public int Count + { + get + { + return _list.Count; + } + } + + public OidEnumerator GetEnumerator() + { + return new OidEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new OidEnumerator(this); + } + + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + throw new ArgumentNullException("array"); + if (array.Rank != 1) + throw new ArgumentException(SR.Arg_RankMultiDimNotSupported); + if (index < 0 || index >= array.Length) + throw new ArgumentOutOfRangeException("index", SR.ArgumentOutOfRange_Index); + if (index + this.Count > array.Length) + throw new ArgumentException(SR.Argument_InvalidOffLen); + + for (int i = 0; i < this.Count; i++) + { + array.SetValue(this[i], index); + index++; + } + } + + public void CopyTo(Oid[] array, int index) + { + ((ICollection)this).CopyTo(array, index); + } + + bool ICollection.IsSynchronized + { + get + { + return false; + } + } + + Object ICollection.SyncRoot + { + get + { + return this; + } + } + + private LowLevelListWithIList<Oid> _list; + } +} diff --git a/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/OidEnumerator.cs b/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/OidEnumerator.cs new file mode 100644 index 0000000000..392e8e498f --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/OidEnumerator.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.Collections; + +namespace System.Security.Cryptography +{ + public sealed class OidEnumerator : IEnumerator + { + internal OidEnumerator(OidCollection oids) + { + _oids = oids; + _current = -1; + } + + public Oid Current + { + get + { + return _oids[_current]; + } + } + + Object IEnumerator.Current + { + get + { + return _oids[_current]; + } + } + + public bool MoveNext() + { + if (_current == _oids.Count - 1) + return false; + _current++; + return true; + } + + public void Reset() + { + _current = -1; + } + + private OidCollection _oids; + private int _current; + } +} diff --git a/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/OidGroup.cs b/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/OidGroup.cs new file mode 100644 index 0000000000..a9d1d7ad94 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/System/Security/Cryptography/OidGroup.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; + +namespace System.Security.Cryptography +{ + // Values taken from wincrypt.h + public enum OidGroup + { + All = 0, + HashAlgorithm = 1, + EncryptionAlgorithm = 2, + PublicKeyAlgorithm = 3, + SignatureAlgorithm = 4, + Attribute = 5, + ExtensionOrAttribute = 6, + EnhancedKeyUsage = 7, + Policy = 8, + Template = 9, + KeyDerivationFunction = 10 + } +} diff --git a/src/System.Security.Cryptography.Encoding/src/project.json b/src/System.Security.Cryptography.Encoding/src/project.json new file mode 100644 index 0000000000..972f4933d4 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/src/project.json @@ -0,0 +1,13 @@ +{ + "dependencies": { + "System.Diagnostics.Contracts": "4.0.0-beta-*", + "System.Diagnostics.Debug": "4.0.10-beta-*", + "System.Resources.ResourceManager": "4.0.0-beta-*", + "System.Runtime": "4.0.20-beta-*", + "System.Runtime.InteropServices": "4.0.20-beta-*", + "System.Security.Cryptography.Encryption": "4.0.0-beta-*", + }, + "frameworks": { + "dnxcore50": {} + } +} diff --git a/src/System.Security.Cryptography.Encoding/tests/AsnEncodedData.cs b/src/System.Security.Cryptography.Encoding/tests/AsnEncodedData.cs new file mode 100644 index 0000000000..6f938b7957 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/tests/AsnEncodedData.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Text; +using Xunit; + +namespace System.Security.Cryptography.Encoding.Tests +{ + public class AsnEncodedDataTests + { + [Fact] + public static void FormatUnknownData() + { + byte[] rawData = { 0x41, 0x42, 0x43 }; + AsnEncodedData a = new AsnEncodedData(rawData); + a.Oid = null; + String s = a.Format(true); + Assert.Equal("41 42 43", s); + return; + } + + [Fact] + public static void FormatInvalidTypedData() + { + // This passes in data in an illegal format. AsnEncodedData.Format() swallows the error and falls back to a simple hex-encoding scheme. + byte[] rawData = { 0x41, 0x42, 0x43 }; + AsnEncodedData a = new AsnEncodedData(rawData); + a.Oid = new Oid("1.3.6.1.4.1.311.2.1.27"); //SPC_FINANCIAL_CRITERIA_OBJID + String s = a.Format(true); + Assert.Equal("414243", s); + return; + } + + [Fact] + public static void TestSubjectAlternativeName() + { + byte[] sanExtension = + { + 0x30, 0x31, 0x82, 0x0B, 0x65, 0x78, 0x61, 0x6D, + 0x70, 0x6C, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x82, + 0x0F, 0x73, 0x75, 0x62, 0x2E, 0x65, 0x78, 0x61, + 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x6F, 0x72, 0x67, + 0x82, 0x11, 0x2A, 0x2E, 0x73, 0x75, 0x62, 0x2E, + 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, + 0x6F, 0x72, 0x67, + }; + + AsnEncodedData asnData = new AsnEncodedData( + new Oid("2.5.29.17"), + sanExtension); + + string s = asnData.Format(false); + // Windows says: "DNS Name=example.org, DNS Name=sub.example.org, DNS Name=*.sub.example.org" + // X-Plat (OpenSSL) says: "DNS:example.org, DNS:sub.example.org, DNS:*.sub.example.org". + // This keeps the parsing generalized until we can get them to converge + string[] parts = s.Split(new[] { ':', '=', ',' }, StringSplitOptions.RemoveEmptyEntries); + // Parts is now { header, data, header, data, header, data }. + string[] output = new string[parts.Length / 2]; + + for (int i = 0; i < output.Length; i++) + { + output[i] = parts[2 * i + 1]; + } + + Assert.Equal(new[] { "example.org", "sub.example.org", "*.sub.example.org" }, output); + } + } +}
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/tests/Oid.cs b/src/System.Security.Cryptography.Encoding/tests/Oid.cs new file mode 100644 index 0000000000..d7c5614767 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/tests/Oid.cs @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Text; +using Xunit; + +namespace System.Security.Cryptography.Encoding.Tests +{ + public class OidTests + { + [Fact] + public static void TestStrConstructor() + { + Oid oid; + + Assert.Throws<ArgumentNullException>(() => oid = new Oid((string)null)); + + oid = new Oid(SHA1_Oid); + Assert.Equal(SHA1_Name, oid.FriendlyName); + Assert.Equal(SHA1_Oid, oid.Value); + + // Though the parameter is supposed to be an OID, the constructor will also accept a friendly name. + oid = new Oid(SHA1_Name); + Assert.Equal(SHA1_Name, oid.FriendlyName); + Assert.Equal(SHA1_Oid, oid.Value); + + // No validation done on OID (other than the null check.) + oid = new Oid(Bogus_Name); + Assert.Equal(null, oid.FriendlyName); + Assert.Equal(Bogus_Name, oid.Value); + + return; + } + + [Fact] + public static void TestStrStrConstructor() + { + Oid oid; + + // No validation at all. + oid = new Oid((string)null, (string)null); + Assert.Equal(null, oid.FriendlyName); + Assert.Equal(null, oid.Value); + + // Can omit friendly-name - FriendlyName property demand-computes it. + oid = new Oid(SHA1_Oid, (string)null); + Assert.Equal(SHA1_Name, oid.FriendlyName); + Assert.Equal(SHA1_Oid, oid.Value); + + oid = new Oid(SHA1_Oid, "BOGUS-NAME"); + Assert.Equal("BOGUS-NAME", oid.FriendlyName); + Assert.Equal(SHA1_Oid, oid.Value); + + // Can omit oid, Value property does no on-demand conversion. + oid = new Oid((string)null, SHA1_Name); + Assert.Equal(SHA1_Name, oid.FriendlyName); + Assert.Equal(null, oid.Value); + + oid = new Oid("BOGUS-OID", SHA1_Name); + Assert.Equal(SHA1_Name, oid.FriendlyName); + Assert.Equal("BOGUS-OID", oid.Value); + + + return; + } + + [Fact] + public static void TestValueProperty() + { + Oid oid = new Oid(null, null); + + // Value property is just a field exposed as a property - no extra policy at all. + + oid.Value = "BOGUS"; + Assert.Equal("BOGUS", oid.Value); + + oid.Value = null; + Assert.Equal(null, oid.Value); + + return; + } + + [Fact] + public static void TestFriendlyNameProperty() + { + Oid oid; + + oid = new Oid(null, null); + + // Friendly name property can initialize itself from the Value (but only + // if it was originally null.) + + oid.Value = SHA1_Oid; + Assert.Equal(SHA1_Name, oid.FriendlyName); + + oid.Value = SHA256_Oid; + Assert.Equal(SHA1_Name, oid.FriendlyName); + + oid.Value = null; + Assert.Equal(SHA1_Name, oid.FriendlyName); + + oid.Value = Bogus_Name; + Assert.Equal(SHA1_Name, oid.FriendlyName); + + // Setting the FriendlyName can also updates the value if there a valid OID for the new name. + oid.FriendlyName = Bogus_Name; + Assert.Equal(Bogus_Name, oid.FriendlyName); + Assert.Equal(Bogus_Name, oid.Value); + + oid.FriendlyName = SHA1_Name; + Assert.Equal(SHA1_Name, oid.FriendlyName); + Assert.Equal(SHA1_Oid, oid.Value); + + oid.FriendlyName = SHA256_Name; + Assert.Equal(SHA256_Name, oid.FriendlyName); + Assert.Equal(SHA256_Oid, oid.Value); + } + + [Fact] + public static void TestFromFriendlyName() + { + Oid oid; + + oid = Oid.FromFriendlyName(SHA1_Name, OidGroup.HashAlgorithm); + Assert.Equal(SHA1_Name, oid.FriendlyName); + Assert.Equal(SHA1_Oid, oid.Value); + + oid = Oid.FromFriendlyName(SHA256_Name, OidGroup.HashAlgorithm); + Assert.Equal(SHA256_Name, oid.FriendlyName); + Assert.Equal(SHA256_Oid, oid.Value); + + Assert.Throws<ArgumentNullException>(() => Oid.FromFriendlyName(null, OidGroup.HashAlgorithm)); + Assert.Throws<CryptographicException>(() => Oid.FromFriendlyName(Bogus_Name, OidGroup.HashAlgorithm)); + + // Oid group is implemented strictly - no fallback to OidGroup.All as with many other parts of Crypto. + Assert.Throws<CryptographicException>(() => Oid.FromFriendlyName(SHA1_Name, OidGroup.Policy)); + } + + [Fact] + public static void TestFromOidValue() + { + Oid oid; + + oid = Oid.FromOidValue(SHA1_Oid, OidGroup.HashAlgorithm); + Assert.Equal(SHA1_Name, oid.FriendlyName); + Assert.Equal(SHA1_Oid, oid.Value); + + oid = Oid.FromOidValue(SHA256_Oid, OidGroup.HashAlgorithm); + Assert.Equal(SHA256_Name, oid.FriendlyName); + Assert.Equal(SHA256_Oid, oid.Value); + + Assert.Throws<ArgumentNullException>(() => Oid.FromOidValue(null, OidGroup.HashAlgorithm)); + Assert.Throws<CryptographicException>(() => Oid.FromOidValue(Bogus_Name, OidGroup.HashAlgorithm)); + + // Oid group is implemented strictly - no fallback to OidGroup.All as with many other parts of Crypto. + Assert.Throws<CryptographicException>(() => Oid.FromOidValue(SHA1_Oid, OidGroup.Policy)); + } + + [Fact] + public static void TestKnownValues() + { + Oid oid; + oid = Oid.FromFriendlyName(SHA1_Name, OidGroup.All); + Assert.Equal(SHA1_Name, oid.FriendlyName); + Assert.Equal(SHA1_Oid, oid.Value); + + oid = Oid.FromFriendlyName(SHA256_Name, OidGroup.All); + Assert.Equal(SHA256_Name, oid.FriendlyName); + Assert.Equal(SHA256_Oid, oid.Value); + + // Note that oid lookup is case-insensitive, and we store the name in the form it was input to the constructor (rather than "normalizing" it + // to the official casing.) + oid = Oid.FromFriendlyName("MD5", OidGroup.All); + Assert.Equal("MD5", oid.FriendlyName); + Assert.Equal("1.2.840.113549.2.5", oid.Value); + + oid = Oid.FromFriendlyName("sha384", OidGroup.All); + Assert.Equal("sha384", oid.FriendlyName); + Assert.Equal("2.16.840.1.101.3.4.2.2", oid.Value); + + oid = Oid.FromFriendlyName("sha512", OidGroup.All); + Assert.Equal("sha512", oid.FriendlyName); + Assert.Equal("2.16.840.1.101.3.4.2.3", oid.Value); + + oid = Oid.FromFriendlyName("3des", OidGroup.All); + Assert.Equal("3des", oid.FriendlyName); + Assert.Equal("1.2.840.113549.3.7", oid.Value); + } + + private const string SHA1_Name = "sha1"; + private const string SHA1_Oid = "1.3.14.3.2.26"; + + private const string SHA256_Name = "sha256"; + private const string SHA256_Oid = "2.16.840.1.101.3.4.2.1"; + + private const string Bogus_Name = "BOGUS_BOGUS_BOGUS_BOGUS"; + } +}
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/tests/OidCollection.cs b/src/System.Security.Cryptography.Encoding/tests/OidCollection.cs new file mode 100644 index 0000000000..e55a264323 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/tests/OidCollection.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Text; +using System.Collections; +using Xunit; + +namespace System.Security.Cryptography.Encoding.Tests +{ + public class OidCollectionTests + { + [Fact] + public static void TestOidCollection() + { + int i; + OidCollection c = new OidCollection(); + Assert.Equal(0, c.Count); + + Oid o0 = new Oid(SHA1_Oid, SHA1_Name); + i = c.Add(o0); + Assert.Equal(0, i); + + Oid o1 = new Oid(SHA256_Oid, SHA256_Name); + i = c.Add(o1); + Assert.Equal(1, i); + + Assert.Equal(2, c.Count); + + Assert.True(Object.ReferenceEquals(o0, c[0])); + Assert.True(Object.ReferenceEquals(o1, c[1])); + Assert.Throws<ArgumentOutOfRangeException>(() => GC.KeepAlive(c[-1])); + Assert.Throws<ArgumentOutOfRangeException>(() => GC.KeepAlive(c[c.Count])); + + Oid o2 = new Oid(SHA1_Oid, SHA1_Name); + i = c.Add(o2); + Assert.Equal(2, i); + + // If there multiple matches, the one with the lowest index wins. + Assert.True(Object.ReferenceEquals(o0, c[SHA1_Name])); + Assert.True(Object.ReferenceEquals(o0, c[SHA1_Oid])); + + Assert.True(Object.ReferenceEquals(o1, c[SHA256_Name])); + Assert.True(Object.ReferenceEquals(o1, c[SHA256_Oid])); + + Oid o3 = new Oid(null, null); + i = c.Add(o3); + Assert.Equal(3, i); + Assert.Throws<ArgumentNullException>(() => GC.KeepAlive(c[null])); + + Object o = c["BOGUSBOGUS"]; + Assert.Null(c["BOGUSBOGUS"]); + + Oid[] a = new Oid[10]; + for (int j = 0; j < a.Length; j++) + { + a[j] = new Oid(null, null); + } + Oid[] a2 = (Oid[])(a.Clone()); + + c.CopyTo(a2, 3); + Assert.Equal(a[0], a2[0]); + Assert.Equal(a[1], a2[1]); + Assert.Equal(a[2], a2[2]); + Assert.Equal(o0, a2[3]); + Assert.Equal(o1, a2[4]); + Assert.Equal(o2, a2[5]); + Assert.Equal(o3, a2[6]); + Assert.Equal(a[7], a2[7]); + Assert.Equal(a[8], a2[8]); + Assert.Equal(a[9], a2[9]); + + Assert.Throws<ArgumentNullException>(() => c.CopyTo(null, 0)); + Assert.Throws<ArgumentNullException>(() => c.CopyTo(null, -1)); + Assert.Throws<ArgumentOutOfRangeException>(() => c.CopyTo(a, -1)); + Assert.Throws<ArgumentException>(() => c.CopyTo(a, 7)); + Assert.Throws<ArgumentOutOfRangeException>(() => c.CopyTo(a, 1000)); + + ICollection ic = c; + Assert.Throws<ArgumentException>(() => ic.CopyTo(new Oid[4, 3], 0)); + Assert.Throws<InvalidCastException>(() => ic.CopyTo(new string[100], 0)); + + return; + } + + private const string SHA1_Name = "sha1"; + private const string SHA1_Oid = "1.3.14.3.2.26"; + + private const string SHA256_Name = "sha256"; + private const string SHA256_Oid = "2.16.840.1.101.3.4.2.1"; + } +}
\ No newline at end of file diff --git a/src/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj b/src/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj new file mode 100644 index 0000000000..53339ebbcc --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{0581E9FA-D639-4B88-96D8-D092760F90B0}</ProjectGuid> + <OutputType>Library</OutputType> + <AssemblyName>System.Security.Cryptography.Encoding.Tests</AssemblyName> + <RootNamespace>System.Security.Cryptography.Encoding.Tests</RootNamespace> + </PropertyGroup> + <ItemGroup> + <ProjectReference Include="..\src\System.Security.Cryptography.Encoding.csproj"> + <Project>{AA81E343-5E54-40B0-9381-C459419BE780}</Project> + <Name>System.Security.Cryptography.Encoding</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Compile Include="AsnEncodedData.cs" /> + <Compile Include="Oid.cs" /> + <Compile Include="OidCollection.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="project.json" /> + </ItemGroup> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> diff --git a/src/System.Security.Cryptography.Encoding/tests/project.json b/src/System.Security.Cryptography.Encoding/tests/project.json new file mode 100644 index 0000000000..9c4c015c85 --- /dev/null +++ b/src/System.Security.Cryptography.Encoding/tests/project.json @@ -0,0 +1,18 @@ +{ + "dependencies": { + "System.Console": "4.0.0-beta-*", + "System.IO": "4.0.10-beta-*", + "System.Runtime": "4.0.20-beta-*", + "System.Runtime.Extensions": "4.0.10-beta-*", + "System.Security.Cryptography.Encryption": "4.0.0-beta-*", + "xunit": "2.0.0-beta5-build2785", + "xunit.abstractions.netcore": "1.0.0-prerelease", + "xunit.assert": "2.0.0-beta5-build2785", + "xunit.core.netcore": "1.0.1-prerelease", + "xunit.runner.visualstudio": "0.99.9-build1021", + "xunit.netcore.extensions": "1.0.0-prerelease-*" + }, + "frameworks": { + "dnxcore50": {} + } +} |