Extensions.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Globalization;
  5. using System.Net;
  6. using System.Net.Sockets;
  7. using System.Numerics;
  8. using System.Runtime.CompilerServices;
  9. using System.Text;
  10. using Renci.SshNet.Abstractions;
  11. using Renci.SshNet.Messages;
  12. namespace Renci.SshNet.Common
  13. {
  14. /// <summary>
  15. /// Collection of different extension methods.
  16. /// </summary>
  17. internal static class Extensions
  18. {
  19. internal static byte[] ToArray(this ServiceName serviceName)
  20. {
  21. switch (serviceName)
  22. {
  23. case ServiceName.UserAuthentication:
  24. return SshData.Ascii.GetBytes("ssh-userauth");
  25. case ServiceName.Connection:
  26. return SshData.Ascii.GetBytes("ssh-connection");
  27. default:
  28. throw new NotSupportedException(string.Format("Service name '{0}' is not supported.", serviceName));
  29. }
  30. }
  31. internal static ServiceName ToServiceName(this byte[] data)
  32. {
  33. var sshServiceName = SshData.Ascii.GetString(data, 0, data.Length);
  34. switch (sshServiceName)
  35. {
  36. case "ssh-userauth":
  37. return ServiceName.UserAuthentication;
  38. case "ssh-connection":
  39. return ServiceName.Connection;
  40. default:
  41. throw new NotSupportedException(string.Format("Service name '{0}' is not supported.", sshServiceName));
  42. }
  43. }
  44. internal static BigInteger ToBigInteger(this byte[] data)
  45. {
  46. #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
  47. return new BigInteger(data, isBigEndian: true);
  48. #else
  49. var reversed = new byte[data.Length];
  50. Buffer.BlockCopy(data, 0, reversed, 0, data.Length);
  51. return new BigInteger(reversed.Reverse());
  52. #endif
  53. }
  54. /// <summary>
  55. /// Initializes a new instance of the <see cref="BigInteger"/> structure using the SSH BigNum2 Format.
  56. /// </summary>
  57. public static BigInteger ToBigInteger2(this byte[] data)
  58. {
  59. #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
  60. return new BigInteger(data, isBigEndian: true, isUnsigned: true);
  61. #else
  62. if ((data[0] & (1 << 7)) != 0)
  63. {
  64. var buf = new byte[data.Length + 1];
  65. Buffer.BlockCopy(data, 0, buf, 1, data.Length);
  66. return new BigInteger(buf.Reverse());
  67. }
  68. return data.ToBigInteger();
  69. #endif
  70. }
  71. #if NETFRAMEWORK || NETSTANDARD2_0
  72. public static byte[] ToByteArray(this BigInteger bigInt, bool isUnsigned = false, bool isBigEndian = false)
  73. {
  74. var data = bigInt.ToByteArray();
  75. if (isUnsigned && data[data.Length - 1] == 0)
  76. {
  77. data = data.Take(data.Length - 1);
  78. }
  79. if (isBigEndian)
  80. {
  81. _ = data.Reverse();
  82. }
  83. return data;
  84. }
  85. #endif
  86. #if !NET6_0_OR_GREATER
  87. public static long GetBitLength(this BigInteger bigint)
  88. {
  89. // Taken from https://github.com/dotnet/runtime/issues/31308
  90. return (long)Math.Ceiling(BigInteger.Log(bigint.Sign < 0 ? -bigint : bigint + 1, 2));
  91. }
  92. #endif
  93. // See https://github.com/dotnet/runtime/blob/9b57a265c7efd3732b035bade005561a04767128/src/libraries/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs#L51
  94. public static byte[] ExportKeyParameter(this BigInteger value, int length)
  95. {
  96. var target = value.ToByteArray(isUnsigned: true, isBigEndian: true);
  97. // The BCL crypto is expecting exactly-sized byte arrays (sized to "length").
  98. // If our byte array is smaller than required, then size it up.
  99. // Otherwise, just return as is: if it is too large, we'll let the BCL throw the error.
  100. if (target.Length < length)
  101. {
  102. var correctlySized = new byte[length];
  103. Buffer.BlockCopy(target, 0, correctlySized, length - target.Length, target.Length);
  104. return correctlySized;
  105. }
  106. return target;
  107. }
  108. /// <summary>
  109. /// Reverses the sequence of the elements in the entire one-dimensional <see cref="Array"/>.
  110. /// </summary>
  111. /// <param name="array">The one-dimensional <see cref="Array"/> to reverse.</param>
  112. /// <returns>
  113. /// The <see cref="Array"/> with its elements reversed.
  114. /// </returns>
  115. internal static T[] Reverse<T>(this T[] array)
  116. {
  117. Array.Reverse(array);
  118. return array;
  119. }
  120. /// <summary>
  121. /// Prints out the specified bytes.
  122. /// </summary>
  123. /// <param name="bytes">The bytes.</param>
  124. internal static void DebugPrint(this IEnumerable<byte> bytes)
  125. {
  126. var sb = new StringBuilder();
  127. foreach (var b in bytes)
  128. {
  129. _ = sb.AppendFormat(CultureInfo.CurrentCulture, "0x{0:x2}, ", b);
  130. }
  131. Debug.WriteLine(sb.ToString());
  132. }
  133. internal static void ValidatePort(this uint value, [CallerArgumentExpression(nameof(value))] string argument = null)
  134. {
  135. if (value > IPEndPoint.MaxPort)
  136. {
  137. throw new ArgumentOutOfRangeException(argument,
  138. string.Format(CultureInfo.InvariantCulture, "Specified value cannot be greater than {0}.", IPEndPoint.MaxPort));
  139. }
  140. }
  141. internal static void ValidatePort(this int value, [CallerArgumentExpression(nameof(value))] string argument = null)
  142. {
  143. if (value < IPEndPoint.MinPort)
  144. {
  145. throw new ArgumentOutOfRangeException(argument, string.Format(CultureInfo.InvariantCulture, "Specified value cannot be less than {0}.", IPEndPoint.MinPort));
  146. }
  147. if (value > IPEndPoint.MaxPort)
  148. {
  149. throw new ArgumentOutOfRangeException(argument, string.Format(CultureInfo.InvariantCulture, "Specified value cannot be greater than {0}.", IPEndPoint.MaxPort));
  150. }
  151. }
  152. /// <summary>
  153. /// Returns a specified number of contiguous bytes from a given offset.
  154. /// </summary>
  155. /// <param name="value">The array to return a number of bytes from.</param>
  156. /// <param name="offset">The zero-based offset in <paramref name="value"/> at which to begin taking bytes.</param>
  157. /// <param name="count">The number of bytes to take from <paramref name="value"/>.</param>
  158. /// <returns>
  159. /// A <see cref="byte"/> array that contains the specified number of bytes at the specified offset
  160. /// of the input array.
  161. /// </returns>
  162. /// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
  163. /// <remarks>
  164. /// When <paramref name="offset"/> is zero and <paramref name="count"/> equals the length of <paramref name="value"/>,
  165. /// then <paramref name="value"/> is returned.
  166. /// </remarks>
  167. public static byte[] Take(this byte[] value, int offset, int count)
  168. {
  169. ThrowHelper.ThrowIfNull(value);
  170. if (count == 0)
  171. {
  172. return Array.Empty<byte>();
  173. }
  174. if (offset == 0 && value.Length == count)
  175. {
  176. return value;
  177. }
  178. var taken = new byte[count];
  179. Buffer.BlockCopy(value, offset, taken, 0, count);
  180. return taken;
  181. }
  182. /// <summary>
  183. /// Returns a specified number of contiguous bytes from the start of the specified byte array.
  184. /// </summary>
  185. /// <param name="value">The array to return a number of bytes from.</param>
  186. /// <param name="count">The number of bytes to take from <paramref name="value"/>.</param>
  187. /// <returns>
  188. /// A <see cref="byte"/> array that contains the specified number of bytes at the start of the input array.
  189. /// </returns>
  190. /// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
  191. /// <remarks>
  192. /// When <paramref name="count"/> equals the length of <paramref name="value"/>, then <paramref name="value"/>
  193. /// is returned.
  194. /// </remarks>
  195. public static byte[] Take(this byte[] value, int count)
  196. {
  197. ThrowHelper.ThrowIfNull(value);
  198. if (count == 0)
  199. {
  200. return Array.Empty<byte>();
  201. }
  202. if (value.Length == count)
  203. {
  204. return value;
  205. }
  206. var taken = new byte[count];
  207. Buffer.BlockCopy(value, 0, taken, 0, count);
  208. return taken;
  209. }
  210. public static bool IsEqualTo(this byte[] left, byte[] right)
  211. {
  212. ThrowHelper.ThrowIfNull(left);
  213. ThrowHelper.ThrowIfNull(right);
  214. return left.AsSpan().SequenceEqual(right);
  215. }
  216. /// <summary>
  217. /// Trims the leading zero from a byte array.
  218. /// </summary>
  219. /// <param name="value">The value.</param>
  220. /// <returns>
  221. /// <paramref name="value"/> without leading zeros.
  222. /// </returns>
  223. public static byte[] TrimLeadingZeros(this byte[] value)
  224. {
  225. ThrowHelper.ThrowIfNull(value);
  226. for (var i = 0; i < value.Length; i++)
  227. {
  228. if (value[i] == 0)
  229. {
  230. continue;
  231. }
  232. // if the first byte is non-zero, then we return the byte array as is
  233. if (i == 0)
  234. {
  235. return value;
  236. }
  237. var remainingBytes = value.Length - i;
  238. var cleaned = new byte[remainingBytes];
  239. Buffer.BlockCopy(value, i, cleaned, 0, remainingBytes);
  240. return cleaned;
  241. }
  242. return value;
  243. }
  244. /// <summary>
  245. /// Pads with leading zeros if needed.
  246. /// </summary>
  247. /// <param name="data">The data.</param>
  248. /// <param name="length">The length to pad to.</param>
  249. public static byte[] Pad(this byte[] data, int length)
  250. {
  251. if (length <= data.Length)
  252. {
  253. return data;
  254. }
  255. var newData = new byte[length];
  256. Buffer.BlockCopy(data, 0, newData, newData.Length - data.Length, data.Length);
  257. return newData;
  258. }
  259. public static byte[] Concat(this byte[] first, byte[] second)
  260. {
  261. if (first is null || first.Length == 0)
  262. {
  263. return second;
  264. }
  265. if (second is null || second.Length == 0)
  266. {
  267. return first;
  268. }
  269. var concat = new byte[first.Length + second.Length];
  270. Buffer.BlockCopy(first, 0, concat, 0, first.Length);
  271. Buffer.BlockCopy(second, 0, concat, first.Length, second.Length);
  272. return concat;
  273. }
  274. internal static bool CanRead(this Socket socket)
  275. {
  276. return SocketAbstraction.CanRead(socket);
  277. }
  278. internal static bool CanWrite(this Socket socket)
  279. {
  280. return SocketAbstraction.CanWrite(socket);
  281. }
  282. internal static bool IsConnected(this Socket socket)
  283. {
  284. if (socket is null)
  285. {
  286. return false;
  287. }
  288. return socket.Connected;
  289. }
  290. }
  291. }