diff options
Diffstat (limited to 'src/mscorlib/shared/System/Security/SecureString.Unix.cs')
-rw-r--r-- | src/mscorlib/shared/System/Security/SecureString.Unix.cs | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/src/mscorlib/shared/System/Security/SecureString.Unix.cs b/src/mscorlib/shared/System/Security/SecureString.Unix.cs new file mode 100644 index 0000000000..0ef38e40ee --- /dev/null +++ b/src/mscorlib/shared/System/Security/SecureString.Unix.cs @@ -0,0 +1,295 @@ +// 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.Runtime.InteropServices; +using System.Text; + +namespace System.Security +{ + // SecureString attempts to provide a defense-in-depth solution. + // + // On Windows, this is done with several mechanisms: + // 1. keeping the data in unmanaged memory so that copies of it aren't implicitly made by the GC moving it around + // 2. zero'ing out that unmanaged memory so that the string is reliably removed from memory when done with it + // 3. encrypting the data while it's not being used (it's unencrypted to manipulate and use it) + // + // On Unix, we do 1 and 2, but we don't do 3 as there's no CryptProtectData equivalent. + + public sealed partial class SecureString + { + private UnmanagedBuffer _buffer; + + internal SecureString(SecureString str) + { + // Allocate enough space to store the provided string + EnsureCapacity(str._decryptedLength); + _decryptedLength = str._decryptedLength; + + // Copy the string into the newly allocated space + if (_decryptedLength > 0) + { + UnmanagedBuffer.Copy(str._buffer, _buffer, (ulong)(str._decryptedLength * sizeof(char))); + } + } + + private unsafe void InitializeSecureString(char* value, int length) + { + // Allocate enough space to store the provided string + EnsureCapacity(length); + _decryptedLength = length; + if (length == 0) + { + return; + } + + // Copy the string into the newly allocated space + byte* ptr = null; + try + { + _buffer.AcquirePointer(ref ptr); + Buffer.MemoryCopy(value, ptr, _buffer.ByteLength, (ulong)(length * sizeof(char))); + } + finally + { + if (ptr != null) + { + _buffer.ReleasePointer(); + } + } + } + + private void DisposeCore() + { + if (_buffer != null && !_buffer.IsInvalid) + { + _buffer.Dispose(); + _buffer = null; + } + } + + private void EnsureNotDisposed() + { + if (_buffer == null) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + private void ClearCore() + { + _decryptedLength = 0; + _buffer.Clear(); + } + + private unsafe void AppendCharCore(char c) + { + // Make sure we have enough space for the new character, then write it at the end. + EnsureCapacity(_decryptedLength + 1); + _buffer.Write((ulong)(_decryptedLength * sizeof(char)), c); + _decryptedLength++; + } + + private unsafe void InsertAtCore(int index, char c) + { + // Make sure we have enough space for the new character, then shift all of the characters above it and insert it. + EnsureCapacity(_decryptedLength + 1); + byte* ptr = null; + try + { + _buffer.AcquirePointer(ref ptr); + ptr += index * sizeof(char); + long bytesToShift = (_decryptedLength - index) * sizeof(char); + Buffer.MemoryCopy(ptr, ptr + sizeof(char), bytesToShift, bytesToShift); + *((char*)ptr) = c; + ++_decryptedLength; + } + finally + { + if (ptr != null) + { + _buffer.ReleasePointer(); + } + } + } + + private unsafe void RemoveAtCore(int index) + { + // Shift down all values above the specified index, then null out the empty space at the end. + byte* ptr = null; + try + { + _buffer.AcquirePointer(ref ptr); + ptr += index * sizeof(char); + long bytesToShift = (_decryptedLength - index - 1) * sizeof(char); + Buffer.MemoryCopy(ptr + sizeof(char), ptr, bytesToShift, bytesToShift); + *((char*)(ptr + bytesToShift)) = (char)0; + --_decryptedLength; + } + finally + { + if (ptr != null) + { + _buffer.ReleasePointer(); + } + } + } + + private void SetAtCore(int index, char c) + { + // Overwrite the character at the specified index + _buffer.Write((ulong)(index * sizeof(char)), c); + } + + internal unsafe IntPtr MarshalToStringCore(bool globalAlloc, bool unicode) + { + int length = _decryptedLength; + + byte* bufferPtr = null; + IntPtr stringPtr = IntPtr.Zero, result = IntPtr.Zero; + try + { + _buffer.AcquirePointer(ref bufferPtr); + if (unicode) + { + int resultLength = (length + 1) * sizeof(char); + stringPtr = globalAlloc ? Marshal.AllocHGlobal(resultLength) : Marshal.AllocCoTaskMem(resultLength); + Buffer.MemoryCopy( + source: bufferPtr, + destination: (byte*)stringPtr.ToPointer(), + destinationSizeInBytes: resultLength, + sourceBytesToCopy: length * sizeof(char)); + *(length + (char*)stringPtr) = '\0'; + } + else + { + int resultLength = Encoding.UTF8.GetByteCount((char*)bufferPtr, length) + 1; + stringPtr = globalAlloc ? Marshal.AllocHGlobal(resultLength) : Marshal.AllocCoTaskMem(resultLength); + int encodedLength = Encoding.UTF8.GetBytes((char*)bufferPtr, length, (byte*)stringPtr, resultLength); + Debug.Assert(encodedLength + 1 == resultLength, $"Expected encoded length to match result, got {encodedLength} != {resultLength}"); + *(resultLength - 1 + (byte*)stringPtr) = 0; + } + + result = stringPtr; + } + finally + { + // If there was a failure, such that result isn't initialized, + // release the string if we had one. + if (stringPtr != IntPtr.Zero && result == IntPtr.Zero) + { + UnmanagedBuffer.ZeroMemory((byte*)stringPtr, (ulong)(length * sizeof(char))); + MarshalFree(stringPtr, globalAlloc); + } + + if (bufferPtr != null) + { + _buffer.ReleasePointer(); + } + } + + return result; + } + + // ----------------------------- + // ---- PAL layer ends here ---- + // ----------------------------- + + private void EnsureCapacity(int capacity) + { + // Make sure the requested capacity doesn't exceed SecureString's defined limit + if (capacity > MaxLength) + { + throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_Capacity); + } + + // If we already have enough space allocated, we're done + if (_buffer != null && (capacity * sizeof(char)) <= (int)_buffer.ByteLength) + { + return; + } + + // We need more space, so allocate a new buffer, copy all our data into it, + // and then swap the new for the old. + UnmanagedBuffer newBuffer = UnmanagedBuffer.Allocate(capacity * sizeof(char)); + if (_buffer != null) + { + UnmanagedBuffer.Copy(_buffer, newBuffer, _buffer.ByteLength); + _buffer.Dispose(); + } + _buffer = newBuffer; + } + + /// <summary>SafeBuffer for managing memory meant to be kept confidential.</summary> + private sealed class UnmanagedBuffer : SafeBuffer + { + internal UnmanagedBuffer() : base(true) { } + + internal static UnmanagedBuffer Allocate(int bytes) + { + Debug.Assert(bytes >= 0); + UnmanagedBuffer buffer = new UnmanagedBuffer(); + buffer.SetHandle(Marshal.AllocHGlobal(bytes)); + buffer.Initialize((ulong)bytes); + return buffer; + } + + internal unsafe void Clear() + { + byte* ptr = null; + try + { + AcquirePointer(ref ptr); + ZeroMemory(ptr, ByteLength); + } + finally + { + if (ptr != null) + { + ReleasePointer(); + } + } + } + + internal static unsafe void Copy(UnmanagedBuffer source, UnmanagedBuffer destination, ulong bytesLength) + { + if (bytesLength == 0) + { + return; + } + + byte* srcPtr = null, dstPtr = null; + try + { + source.AcquirePointer(ref srcPtr); + destination.AcquirePointer(ref dstPtr); + Buffer.MemoryCopy(srcPtr, dstPtr, destination.ByteLength, bytesLength); + } + finally + { + if (dstPtr != null) + { + destination.ReleasePointer(); + } + if (srcPtr != null) + { + source.ReleasePointer(); + } + } + } + + protected override unsafe bool ReleaseHandle() + { + Marshal.FreeHGlobal(handle); + return true; + } + + internal static unsafe void ZeroMemory(byte* ptr, ulong len) + { + for (ulong i = 0; i < len; i++) *ptr++ = 0; + } + } + + } +} |