94 lines
3.7 KiB
C#
94 lines
3.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
internal static partial class Interop
|
|
{
|
|
internal static partial class Crypto
|
|
{
|
|
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ErrClearError")]
|
|
internal static extern ulong ErrClearError();
|
|
|
|
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ErrGetErrorAlloc")]
|
|
private static extern ulong ErrGetErrorAlloc([MarshalAs(UnmanagedType.Bool)] out bool isAllocFailure);
|
|
|
|
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ErrErrorStringN")]
|
|
private static extern unsafe void ErrErrorStringN(ulong e, byte* buf, int len);
|
|
|
|
private static unsafe string ErrErrorStringN(ulong error)
|
|
{
|
|
var buffer = new byte[1024];
|
|
fixed (byte* buf = &buffer[0])
|
|
{
|
|
ErrErrorStringN(error, buf, buffer.Length);
|
|
return Marshal.PtrToStringAnsi((IntPtr)buf);
|
|
}
|
|
}
|
|
|
|
|
|
internal static Exception CreateOpenSslCryptographicException()
|
|
{
|
|
// The Windows cryptography library reports error codes through
|
|
// Marshal.GetLastWin32Error, which has a single value when the
|
|
// function exits, last writer wins.
|
|
//
|
|
// OpenSSL maintains an error queue. Calls to ERR_get_error read
|
|
// values out of the queue in the order that ERR_set_error wrote
|
|
// them. Nothing enforces that a single call into an OpenSSL
|
|
// function will guarantee at-most one error being set.
|
|
//
|
|
// In order to maintain parity in how error flows look between the
|
|
// Windows code and the OpenSSL-calling code, drain the queue
|
|
// whenever an Exception is desired, and report the exception
|
|
// related to the last value in the queue.
|
|
bool isAllocFailure;
|
|
ulong error = ErrGetErrorAlloc(out isAllocFailure);
|
|
ulong lastRead = error;
|
|
bool lastIsAllocFailure = isAllocFailure;
|
|
|
|
// 0 (there's no named constant) is only returned when the calls
|
|
// to ERR_get_error exceed the calls to ERR_set_error.
|
|
while (lastRead != 0)
|
|
{
|
|
error = lastRead;
|
|
isAllocFailure = lastIsAllocFailure;
|
|
|
|
lastRead = ErrGetErrorAlloc(out lastIsAllocFailure);
|
|
}
|
|
|
|
// If we're in an error flow which results in an Exception, but
|
|
// no calls to ERR_set_error were made, throw the unadorned
|
|
// CryptographicException.
|
|
if (error == 0)
|
|
{
|
|
return new CryptographicException();
|
|
}
|
|
|
|
if (isAllocFailure)
|
|
{
|
|
return new OutOfMemoryException();
|
|
}
|
|
|
|
// Even though ErrGetError returns ulong (C++ unsigned long), we
|
|
// really only expect error codes in the UInt32 range
|
|
Debug.Assert(error <= uint.MaxValue, "ErrGetError should only return error codes in the UInt32 range.");
|
|
|
|
// If there was an error code, and it wasn't something handled specially,
|
|
// use the OpenSSL error string as the message to a CryptographicException.
|
|
return new OpenSslCryptographicException(unchecked((int)error), ErrErrorStringN(error));
|
|
}
|
|
|
|
private sealed class OpenSslCryptographicException : CryptographicException
|
|
{
|
|
internal OpenSslCryptographicException(int errorCode, string message)
|
|
: base(message)
|
|
{
|
|
HResult = errorCode;
|
|
}
|
|
}
|
|
}
|
|
}
|