SecureString.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Diagnostics;
  5. using System.Runtime.InteropServices;
  6. using System.Threading;
  7. namespace System.Security
  8. {
  9. public sealed partial class SecureString : IDisposable
  10. {
  11. private const int MaxLength = 65536;
  12. private readonly object _methodLock = new object();
  13. private UnmanagedBuffer? _buffer;
  14. private int _decryptedLength;
  15. private bool _encrypted;
  16. private bool _readOnly;
  17. public SecureString()
  18. {
  19. Initialize(ReadOnlySpan<char>.Empty);
  20. }
  21. [CLSCompliant(false)]
  22. public unsafe SecureString(char* value, int length)
  23. {
  24. if (value == null)
  25. {
  26. throw new ArgumentNullException(nameof(value));
  27. }
  28. if (length < 0)
  29. {
  30. throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
  31. }
  32. if (length > MaxLength)
  33. {
  34. throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Length);
  35. }
  36. Initialize(new ReadOnlySpan<char>(value, length));
  37. }
  38. private void Initialize(ReadOnlySpan<char> value)
  39. {
  40. _buffer = UnmanagedBuffer.Allocate(GetAlignedByteSize(value.Length));
  41. _decryptedLength = value.Length;
  42. SafeBuffer? bufferToRelease = null;
  43. try
  44. {
  45. Span<char> span = AcquireSpan(ref bufferToRelease);
  46. value.CopyTo(span);
  47. }
  48. finally
  49. {
  50. ProtectMemory();
  51. bufferToRelease?.DangerousRelease();
  52. }
  53. }
  54. private SecureString(SecureString str)
  55. {
  56. Debug.Assert(str._buffer != null, "Expected other SecureString's buffer to be non-null");
  57. Debug.Assert(str._encrypted, "Expected to be used only on encrypted SecureStrings");
  58. _buffer = UnmanagedBuffer.Allocate((int)str._buffer.ByteLength);
  59. Debug.Assert(_buffer != null);
  60. UnmanagedBuffer.Copy(str._buffer, _buffer, str._buffer.ByteLength);
  61. _decryptedLength = str._decryptedLength;
  62. _encrypted = str._encrypted;
  63. }
  64. public int Length
  65. {
  66. get
  67. {
  68. EnsureNotDisposed();
  69. return Volatile.Read(ref _decryptedLength);
  70. }
  71. }
  72. private void EnsureCapacity(int capacity)
  73. {
  74. if (capacity > MaxLength)
  75. {
  76. throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_Capacity);
  77. }
  78. Debug.Assert(_buffer != null);
  79. if ((uint)capacity * sizeof(char) <= _buffer.ByteLength)
  80. {
  81. return;
  82. }
  83. UnmanagedBuffer oldBuffer = _buffer;
  84. UnmanagedBuffer newBuffer = UnmanagedBuffer.Allocate(GetAlignedByteSize(capacity));
  85. UnmanagedBuffer.Copy(oldBuffer, newBuffer, (uint)_decryptedLength * sizeof(char));
  86. _buffer = newBuffer;
  87. oldBuffer.Dispose();
  88. }
  89. public void AppendChar(char c)
  90. {
  91. lock (_methodLock)
  92. {
  93. EnsureNotDisposed();
  94. EnsureNotReadOnly();
  95. Debug.Assert(_buffer != null);
  96. SafeBuffer? bufferToRelease = null;
  97. try
  98. {
  99. UnprotectMemory();
  100. EnsureCapacity(_decryptedLength + 1);
  101. Span<char> span = AcquireSpan(ref bufferToRelease);
  102. span[_decryptedLength] = c;
  103. _decryptedLength++;
  104. }
  105. finally
  106. {
  107. ProtectMemory();
  108. bufferToRelease?.DangerousRelease();
  109. }
  110. }
  111. }
  112. // clears the current contents. Only available if writable
  113. public void Clear()
  114. {
  115. lock (_methodLock)
  116. {
  117. EnsureNotDisposed();
  118. EnsureNotReadOnly();
  119. Debug.Assert(_buffer != null);
  120. _decryptedLength = 0;
  121. SafeBuffer? bufferToRelease = null;
  122. try
  123. {
  124. Span<char> span = AcquireSpan(ref bufferToRelease);
  125. span.Clear();
  126. }
  127. finally
  128. {
  129. bufferToRelease?.DangerousRelease();
  130. }
  131. }
  132. }
  133. // Do a deep-copy of the SecureString
  134. public SecureString Copy()
  135. {
  136. lock (_methodLock)
  137. {
  138. EnsureNotDisposed();
  139. return new SecureString(this);
  140. }
  141. }
  142. public void Dispose()
  143. {
  144. lock (_methodLock)
  145. {
  146. if (_buffer != null)
  147. {
  148. _buffer.Dispose();
  149. _buffer = null;
  150. }
  151. }
  152. }
  153. public void InsertAt(int index, char c)
  154. {
  155. lock (_methodLock)
  156. {
  157. if (index < 0 || index > _decryptedLength)
  158. {
  159. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_IndexString);
  160. }
  161. EnsureNotDisposed();
  162. EnsureNotReadOnly();
  163. Debug.Assert(_buffer != null);
  164. SafeBuffer? bufferToRelease = null;
  165. try
  166. {
  167. UnprotectMemory();
  168. EnsureCapacity(_decryptedLength + 1);
  169. Span<char> span = AcquireSpan(ref bufferToRelease);
  170. span.Slice(index, _decryptedLength - index).CopyTo(span.Slice(index + 1));
  171. span[index] = c;
  172. _decryptedLength++;
  173. }
  174. finally
  175. {
  176. ProtectMemory();
  177. bufferToRelease?.DangerousRelease();
  178. }
  179. }
  180. }
  181. public bool IsReadOnly()
  182. {
  183. EnsureNotDisposed();
  184. return Volatile.Read(ref _readOnly);
  185. }
  186. public void MakeReadOnly()
  187. {
  188. EnsureNotDisposed();
  189. Volatile.Write(ref _readOnly, true);
  190. }
  191. public void RemoveAt(int index)
  192. {
  193. lock (_methodLock)
  194. {
  195. if (index < 0 || index >= _decryptedLength)
  196. {
  197. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_IndexString);
  198. }
  199. EnsureNotDisposed();
  200. EnsureNotReadOnly();
  201. Debug.Assert(_buffer != null);
  202. SafeBuffer? bufferToRelease = null;
  203. try
  204. {
  205. UnprotectMemory();
  206. Span<char> span = AcquireSpan(ref bufferToRelease);
  207. span.Slice(index + 1, _decryptedLength - (index + 1)).CopyTo(span.Slice(index));
  208. _decryptedLength--;
  209. }
  210. finally
  211. {
  212. ProtectMemory();
  213. bufferToRelease?.DangerousRelease();
  214. }
  215. }
  216. }
  217. public void SetAt(int index, char c)
  218. {
  219. lock (_methodLock)
  220. {
  221. if (index < 0 || index >= _decryptedLength)
  222. {
  223. throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_IndexString);
  224. }
  225. EnsureNotDisposed();
  226. EnsureNotReadOnly();
  227. Debug.Assert(_buffer != null);
  228. SafeBuffer? bufferToRelease = null;
  229. try
  230. {
  231. UnprotectMemory();
  232. Span<char> span = AcquireSpan(ref bufferToRelease);
  233. span[index] = c;
  234. }
  235. finally
  236. {
  237. ProtectMemory();
  238. bufferToRelease?.DangerousRelease();
  239. }
  240. }
  241. }
  242. private unsafe Span<char> AcquireSpan(ref SafeBuffer? bufferToRelease)
  243. {
  244. SafeBuffer buffer = _buffer!;
  245. bool ignore = false;
  246. buffer.DangerousAddRef(ref ignore);
  247. bufferToRelease = buffer;
  248. return new Span<char>((byte*)buffer.DangerousGetHandle(), (int)(buffer.ByteLength / 2));
  249. }
  250. private void EnsureNotReadOnly()
  251. {
  252. if (_readOnly)
  253. {
  254. throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
  255. }
  256. }
  257. private void EnsureNotDisposed()
  258. {
  259. if (_buffer == null)
  260. {
  261. throw new ObjectDisposedException(GetType().Name);
  262. }
  263. }
  264. internal unsafe IntPtr MarshalToBSTR()
  265. {
  266. lock (_methodLock)
  267. {
  268. EnsureNotDisposed();
  269. UnprotectMemory();
  270. SafeBuffer? bufferToRelease = null;
  271. IntPtr ptr = IntPtr.Zero;
  272. int length = 0;
  273. try
  274. {
  275. Span<char> span = AcquireSpan(ref bufferToRelease);
  276. length = _decryptedLength;
  277. ptr = Marshal.AllocBSTR(length);
  278. span.Slice(0, length).CopyTo(new Span<char>((void*)ptr, length));
  279. IntPtr result = ptr;
  280. ptr = IntPtr.Zero;
  281. return result;
  282. }
  283. finally
  284. {
  285. // If we failed for any reason, free the new buffer
  286. if (ptr != IntPtr.Zero)
  287. {
  288. new Span<char>((void*)ptr, length).Clear();
  289. Marshal.FreeBSTR(ptr);
  290. }
  291. ProtectMemory();
  292. bufferToRelease?.DangerousRelease();
  293. }
  294. }
  295. }
  296. internal unsafe IntPtr MarshalToString(bool globalAlloc, bool unicode)
  297. {
  298. lock (_methodLock)
  299. {
  300. EnsureNotDisposed();
  301. UnprotectMemory();
  302. SafeBuffer? bufferToRelease = null;
  303. IntPtr ptr = IntPtr.Zero;
  304. int byteLength = 0;
  305. try
  306. {
  307. Span<char> span = AcquireSpan(ref bufferToRelease).Slice(0, _decryptedLength);
  308. if (unicode)
  309. {
  310. byteLength = (span.Length + 1) * sizeof(char);
  311. }
  312. else
  313. {
  314. byteLength = Marshal.GetAnsiStringByteCount(span);
  315. }
  316. if (globalAlloc)
  317. {
  318. ptr = Marshal.AllocHGlobal(byteLength);
  319. }
  320. else
  321. {
  322. ptr = Marshal.AllocCoTaskMem(byteLength);
  323. }
  324. if (unicode)
  325. {
  326. Span<char> resultSpan = new Span<char>((void*)ptr, byteLength / sizeof(char));
  327. span.CopyTo(resultSpan);
  328. resultSpan[resultSpan.Length - 1] = '\0';
  329. }
  330. else
  331. {
  332. Marshal.GetAnsiStringBytes(span, new Span<byte>((void*)ptr, byteLength));
  333. }
  334. IntPtr result = ptr;
  335. ptr = IntPtr.Zero;
  336. return result;
  337. }
  338. finally
  339. {
  340. // If we failed for any reason, free the new buffer
  341. if (ptr != IntPtr.Zero)
  342. {
  343. new Span<byte>((void*)ptr, byteLength).Clear();
  344. if (globalAlloc)
  345. {
  346. Marshal.FreeHGlobal(ptr);
  347. }
  348. else
  349. {
  350. Marshal.FreeCoTaskMem(ptr);
  351. }
  352. }
  353. ProtectMemory();
  354. bufferToRelease?.DangerousRelease();
  355. }
  356. }
  357. }
  358. /// <summary>SafeBuffer for managing memory meant to be kept confidential.</summary>
  359. private sealed class UnmanagedBuffer : SafeBuffer
  360. {
  361. // A local copy of byte length to be able to access it in ReleaseHandle without the risk of throwing exceptions
  362. private int _byteLength;
  363. private UnmanagedBuffer() : base(true) { }
  364. public static UnmanagedBuffer Allocate(int byteLength)
  365. {
  366. Debug.Assert(byteLength >= 0);
  367. UnmanagedBuffer buffer = new UnmanagedBuffer();
  368. buffer.SetHandle(Marshal.AllocHGlobal(byteLength));
  369. buffer.Initialize((ulong)byteLength);
  370. buffer._byteLength = byteLength;
  371. return buffer;
  372. }
  373. internal static unsafe void Copy(UnmanagedBuffer source, UnmanagedBuffer destination, ulong bytesLength)
  374. {
  375. if (bytesLength == 0)
  376. {
  377. return;
  378. }
  379. byte* srcPtr = null, dstPtr = null;
  380. try
  381. {
  382. source.AcquirePointer(ref srcPtr);
  383. destination.AcquirePointer(ref dstPtr);
  384. Buffer.MemoryCopy(srcPtr, dstPtr, destination.ByteLength, bytesLength);
  385. }
  386. finally
  387. {
  388. if (dstPtr != null)
  389. {
  390. destination.ReleasePointer();
  391. }
  392. if (srcPtr != null)
  393. {
  394. source.ReleasePointer();
  395. }
  396. }
  397. }
  398. protected override unsafe bool ReleaseHandle()
  399. {
  400. new Span<byte>((void*)handle, _byteLength).Clear();
  401. Marshal.FreeHGlobal(handle);
  402. return true;
  403. }
  404. }
  405. }
  406. }