/Stack/Opc.Ua.Core/Security/Certificates/RsaUtils.cs

https://github.com/OPCFoundation/UA-.NETStandard · C# · 422 lines · 301 code · 44 blank · 77 comment · 27 complexity · caba4bcf830246671511bbb936db6738 MD5 · raw file

  1. /* Copyright (c) 1996-2019 The OPC Foundation. All rights reserved.
  2. The source code in this file is covered under a dual-license scenario:
  3. - RCL: for OPC Foundation members in good-standing
  4. - GPL V2: everybody else
  5. RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
  6. GNU General Public License as published by the Free Software Foundation;
  7. version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
  8. This source code is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. */
  12. using System;
  13. using System.IO;
  14. using System.Security.Cryptography;
  15. using System.Security.Cryptography.X509Certificates;
  16. namespace Opc.Ua
  17. {
  18. /// <summary>
  19. /// Defines internal functions to implement RSA cryptography.
  20. /// </summary>
  21. internal static class RsaUtils
  22. {
  23. #region Public Enum
  24. public enum Padding
  25. {
  26. Pkcs1,
  27. OaepSHA1,
  28. OaepSHA256
  29. };
  30. internal static RSAEncryptionPadding GetRSAEncryptionPadding(Padding padding)
  31. {
  32. switch (padding)
  33. {
  34. case Padding.Pkcs1: return RSAEncryptionPadding.Pkcs1;
  35. case Padding.OaepSHA1: return RSAEncryptionPadding.OaepSHA1;
  36. case Padding.OaepSHA256: return RSAEncryptionPadding.OaepSHA256;
  37. }
  38. throw new ServiceResultException("Invalid Padding");
  39. }
  40. #endregion
  41. #region Public Methods
  42. /// <summary>
  43. /// Return the plaintext block size for RSA OAEP encryption.
  44. /// </summary>
  45. internal static int GetPlainTextBlockSize(X509Certificate2 encryptingCertificate, Padding padding)
  46. {
  47. RSA rsa = null;
  48. try
  49. {
  50. rsa = encryptingCertificate.GetRSAPublicKey();
  51. return GetPlainTextBlockSize(rsa, padding);
  52. }
  53. finally
  54. {
  55. RsaUtils.RSADispose(rsa);
  56. }
  57. }
  58. /// <summary>
  59. /// Return the plaintext block size for RSA OAEP encryption.
  60. /// </summary>
  61. internal static int GetPlainTextBlockSize(RSA rsa, Padding padding)
  62. {
  63. if (rsa != null)
  64. {
  65. switch (padding)
  66. {
  67. case Padding.Pkcs1: return rsa.KeySize / 8 - 11;
  68. case Padding.OaepSHA1: return rsa.KeySize / 8 - 42;
  69. case Padding.OaepSHA256: return rsa.KeySize / 8 - 66;
  70. }
  71. }
  72. return -1;
  73. }
  74. /// <summary>
  75. /// Return the ciphertext block size for RSA OAEP encryption.
  76. /// </summary>
  77. internal static int GetCipherTextBlockSize(X509Certificate2 encryptingCertificate, Padding padding)
  78. {
  79. RSA rsa = null;
  80. try
  81. {
  82. rsa = encryptingCertificate.GetRSAPublicKey();
  83. return GetCipherTextBlockSize(rsa, padding);
  84. }
  85. finally
  86. {
  87. RsaUtils.RSADispose(rsa);
  88. }
  89. }
  90. /// <summary>
  91. /// Return the ciphertext block size for RSA OAEP encryption.
  92. /// </summary>
  93. internal static int GetCipherTextBlockSize(RSA rsa, Padding padding)
  94. {
  95. if (rsa != null)
  96. {
  97. return rsa.KeySize / 8;
  98. }
  99. return -1;
  100. }
  101. /// <summary>
  102. /// Returns the length of a RSA PKCS#1 v1.5 signature of a digest.
  103. /// </summary>
  104. internal static int GetSignatureLength(X509Certificate2 signingCertificate)
  105. {
  106. RSA rsa = null;
  107. try
  108. {
  109. rsa = signingCertificate.GetRSAPublicKey();
  110. if (rsa == null)
  111. {
  112. throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "No public key for certificate.");
  113. }
  114. return rsa.KeySize / 8;
  115. }
  116. finally
  117. {
  118. RsaUtils.RSADispose(rsa);
  119. }
  120. }
  121. /// <summary>
  122. /// Computes a RSA signature.
  123. /// </summary>
  124. internal static byte[] Rsa_Sign(
  125. ArraySegment<byte> dataToSign,
  126. X509Certificate2 signingCertificate,
  127. HashAlgorithmName hashAlgorithm,
  128. RSASignaturePadding rsaSignaturePadding)
  129. {
  130. RSA rsa = null;
  131. try
  132. {
  133. // extract the private key.
  134. rsa = signingCertificate.GetRSAPrivateKey();
  135. if (rsa == null)
  136. {
  137. throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "No private key for certificate.");
  138. }
  139. // create the signature.
  140. return rsa.SignData(dataToSign.Array, dataToSign.Offset, dataToSign.Count, hashAlgorithm, rsaSignaturePadding);
  141. }
  142. finally
  143. {
  144. RsaUtils.RSADispose(rsa);
  145. }
  146. }
  147. /// <summary>
  148. /// Verifies a RSA signature.
  149. /// </summary>
  150. internal static bool Rsa_Verify(
  151. ArraySegment<byte> dataToVerify,
  152. byte[] signature,
  153. X509Certificate2 signingCertificate,
  154. HashAlgorithmName hashAlgorithm,
  155. RSASignaturePadding rsaSignaturePadding)
  156. {
  157. RSA rsa = null;
  158. try
  159. {
  160. // extract the public key.
  161. rsa = signingCertificate.GetRSAPublicKey();
  162. if (rsa == null)
  163. {
  164. throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "No public key for certificate.");
  165. }
  166. // verify signature.
  167. return rsa.VerifyData(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count, signature, hashAlgorithm, rsaSignaturePadding);
  168. }
  169. finally
  170. {
  171. RsaUtils.RSADispose(rsa);
  172. }
  173. }
  174. /// <summary>
  175. /// Encrypts the data using RSA encryption.
  176. /// </summary>
  177. internal static byte[] Encrypt(
  178. byte[] dataToEncrypt,
  179. X509Certificate2 encryptingCertificate,
  180. Padding padding)
  181. {
  182. RSA rsa = null;
  183. try
  184. {
  185. rsa = encryptingCertificate.GetRSAPublicKey();
  186. if (rsa == null)
  187. {
  188. throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "No public key for certificate.");
  189. }
  190. int plaintextBlockSize = GetPlainTextBlockSize(rsa, padding);
  191. int blockCount = ((dataToEncrypt.Length + 4) / plaintextBlockSize) + 1;
  192. int plainTextSize = blockCount * plaintextBlockSize;
  193. int cipherTextSize = blockCount * GetCipherTextBlockSize(rsa, padding);
  194. byte[] plainText = new byte[plainTextSize];
  195. // encode length.
  196. plainText[0] = (byte)((0x000000FF & dataToEncrypt.Length));
  197. plainText[1] = (byte)((0x0000FF00 & dataToEncrypt.Length) >> 8);
  198. plainText[2] = (byte)((0x00FF0000 & dataToEncrypt.Length) >> 16);
  199. plainText[3] = (byte)((0xFF000000 & dataToEncrypt.Length) >> 24);
  200. // copy data.
  201. Array.Copy(dataToEncrypt, 0, plainText, 4, dataToEncrypt.Length);
  202. byte[] buffer = new byte[cipherTextSize];
  203. ArraySegment<byte> cipherText = Encrypt(new ArraySegment<byte>(plainText), rsa, padding, new ArraySegment<byte>(buffer));
  204. System.Diagnostics.Debug.Assert(cipherText.Count == buffer.Length);
  205. return buffer;
  206. }
  207. finally
  208. {
  209. RsaUtils.RSADispose(rsa);
  210. }
  211. }
  212. /// <summary>
  213. /// Encrypts the data using RSA PKCS#1 v1.5 or OAEP encryption.
  214. /// </summary>
  215. private static ArraySegment<byte> Encrypt(
  216. ArraySegment<byte> dataToEncrypt,
  217. RSA rsa,
  218. Padding padding,
  219. ArraySegment<byte> outputBuffer)
  220. {
  221. int inputBlockSize = GetPlainTextBlockSize(rsa, padding);
  222. int outputBlockSize = GetCipherTextBlockSize(rsa, padding);
  223. // verify the input data is the correct block size.
  224. if (dataToEncrypt.Count % inputBlockSize != 0)
  225. {
  226. Utils.Trace("Message is not an integral multiple of the block size. Length = {0}, BlockSize = {1}.", dataToEncrypt.Count, inputBlockSize);
  227. }
  228. byte[] encryptedBuffer = outputBuffer.Array;
  229. RSAEncryptionPadding rsaPadding = GetRSAEncryptionPadding(padding);
  230. using (MemoryStream ostrm = new MemoryStream(
  231. encryptedBuffer,
  232. outputBuffer.Offset,
  233. outputBuffer.Count))
  234. {
  235. // encrypt body.
  236. byte[] input = new byte[inputBlockSize];
  237. for (int ii = dataToEncrypt.Offset; ii < dataToEncrypt.Offset + dataToEncrypt.Count; ii += inputBlockSize)
  238. {
  239. Array.Copy(dataToEncrypt.Array, ii, input, 0, input.Length);
  240. byte[] cipherText = rsa.Encrypt(input, rsaPadding);
  241. ostrm.Write(cipherText, 0, cipherText.Length);
  242. }
  243. }
  244. // return buffer
  245. return new ArraySegment<byte>(
  246. encryptedBuffer,
  247. outputBuffer.Offset,
  248. (dataToEncrypt.Count / inputBlockSize) * outputBlockSize);
  249. }
  250. /// <summary>
  251. /// Decrypts the data using RSA encryption.
  252. /// </summary>
  253. internal static byte[] Decrypt(
  254. ArraySegment<byte> dataToDecrypt,
  255. X509Certificate2 encryptingCertificate,
  256. Padding padding)
  257. {
  258. RSA rsa = null;
  259. try
  260. {
  261. rsa = encryptingCertificate.GetRSAPrivateKey();
  262. if (rsa == null)
  263. {
  264. throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "No private key for certificate.");
  265. }
  266. int plainTextSize = dataToDecrypt.Count / GetCipherTextBlockSize(rsa, padding);
  267. plainTextSize *= GetPlainTextBlockSize(encryptingCertificate, padding);
  268. byte[] buffer = new byte[plainTextSize];
  269. ArraySegment<byte> plainText = Decrypt(dataToDecrypt, rsa, padding, new ArraySegment<byte>(buffer));
  270. System.Diagnostics.Debug.Assert(plainText.Count == buffer.Length);
  271. // decode length.
  272. int length = 0;
  273. length += (((int)plainText.Array[plainText.Offset + 0]));
  274. length += (((int)plainText.Array[plainText.Offset + 1]) << 8);
  275. length += (((int)plainText.Array[plainText.Offset + 2]) << 16);
  276. length += (((int)plainText.Array[plainText.Offset + 3]) << 24);
  277. if (length > (plainText.Count - plainText.Offset - 4))
  278. {
  279. throw ServiceResultException.Create(StatusCodes.BadEndOfStream, "Could not decrypt data. Invalid total length.");
  280. }
  281. byte[] decryptedData = new byte[length];
  282. Array.Copy(plainText.Array, plainText.Offset + 4, decryptedData, 0, length);
  283. return decryptedData;
  284. }
  285. finally
  286. {
  287. RsaUtils.RSADispose(rsa);
  288. }
  289. }
  290. /// <summary>
  291. /// Decrypts the message using RSA encryption.
  292. /// </summary>
  293. private static ArraySegment<byte> Decrypt(
  294. ArraySegment<byte> dataToDecrypt,
  295. RSA rsa,
  296. Padding padding,
  297. ArraySegment<byte> outputBuffer)
  298. {
  299. int inputBlockSize = GetCipherTextBlockSize(rsa, padding);
  300. int outputBlockSize = GetPlainTextBlockSize(rsa, padding);
  301. // verify the input data is the correct block size.
  302. if (dataToDecrypt.Count % inputBlockSize != 0)
  303. {
  304. Utils.Trace("Message is not an integral multiple of the block size. Length = {0}, BlockSize = {1}.", dataToDecrypt.Count, inputBlockSize);
  305. }
  306. byte[] decryptedBuffer = outputBuffer.Array;
  307. RSAEncryptionPadding rsaPadding = GetRSAEncryptionPadding(padding);
  308. using (MemoryStream ostrm = new MemoryStream(
  309. decryptedBuffer,
  310. outputBuffer.Offset,
  311. outputBuffer.Count))
  312. {
  313. // decrypt body.
  314. byte[] input = new byte[inputBlockSize];
  315. for (int ii = dataToDecrypt.Offset; ii < dataToDecrypt.Offset + dataToDecrypt.Count; ii += inputBlockSize)
  316. {
  317. Array.Copy(dataToDecrypt.Array, ii, input, 0, input.Length);
  318. byte[] plainText = rsa.Decrypt(input, rsaPadding);
  319. ostrm.Write(plainText, 0, plainText.Length);
  320. }
  321. }
  322. // return buffers.
  323. return new ArraySegment<byte>(decryptedBuffer, outputBuffer.Offset, (dataToDecrypt.Count / inputBlockSize) * outputBlockSize);
  324. }
  325. /// <summary>
  326. /// Helper to test for RSASignaturePadding.Pss support, some platforms do not support it.
  327. /// </summary>
  328. internal static bool TryVerifyRSAPssSign(RSA publicKey, RSA privateKey)
  329. {
  330. try
  331. {
  332. Opc.Ua.Test.RandomSource randomSource = new Opc.Ua.Test.RandomSource();
  333. int blockSize = 0x10;
  334. byte[] testBlock = new byte[blockSize];
  335. randomSource.NextBytes(testBlock, 0, blockSize);
  336. byte[] signature = privateKey.SignData(testBlock, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
  337. return publicKey.VerifyData(testBlock, signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
  338. }
  339. catch
  340. {
  341. return false;
  342. }
  343. }
  344. /// <summary>
  345. /// Lazy helper to allow runtime to check for Pss support.
  346. /// </summary>
  347. internal static readonly Lazy<bool> IsSupportingRSAPssSign = new Lazy<bool>(() => {
  348. #if NETFRAMEWORK
  349. // The Pss check returns false on .Net4.6/4.7, although it is always supported with certs.
  350. // but not supported with Mono
  351. return !Utils.IsRunningOnMono();
  352. #else
  353. using (var rsa = RSA.Create())
  354. {
  355. return RsaUtils.TryVerifyRSAPssSign(rsa, rsa);
  356. }
  357. #endif
  358. });
  359. /// <summary>
  360. /// Dispose RSA object only if not running on Mono runtime.
  361. /// Workaround due to a Mono bug in the X509Certificate2 implementation of RSA.
  362. /// see also: https://github.com/mono/mono/issues/6306
  363. /// On Mono GetRSAPrivateKey/GetRSAPublickey returns a reference instead of a disposable object.
  364. /// Calling Dispose on RSA makes the X509Certificate2 keys unusable on Mono.
  365. /// Only call dispose when using .Net and .Net Core runtimes.
  366. /// </summary>
  367. /// <param name="rsa">RSA object returned by GetRSAPublicKey/GetRSAPrivateKey</param>
  368. internal static void RSADispose(RSA rsa)
  369. {
  370. if (rsa != null &&
  371. !Utils.IsRunningOnMono())
  372. {
  373. rsa.Dispose();
  374. }
  375. }
  376. #endregion
  377. }
  378. }