RSA.sol 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.20;
  3. import {Math} from "../math/Math.sol";
  4. /**
  5. * @dev RSA PKCS#1 v1.5 signature verification implementation according to https://datatracker.ietf.org/doc/html/rfc8017[RFC8017].
  6. *
  7. * This library supports PKCS#1 v1.5 padding to avoid malleability via chosen plaintext attacks in practical implementations.
  8. * The padding follows the EMSA-PKCS1-v1_5-ENCODE encoding definition as per section 9.2 of the RFC. This padding makes
  9. * RSA semantically secure for signing messages.
  10. *
  11. * Inspired by https://github.com/adria0/SolRsaVerify/blob/79c6182cabb9102ea69d4a2e996816091d5f1cd1[Adrià Massanet's work] (GNU General Public License v3.0).
  12. */
  13. library RSA {
  14. /**
  15. * @dev Same as {pkcs1Sha256} but using SHA256 to calculate the digest of `data`.
  16. */
  17. function pkcs1Sha256(
  18. bytes memory data,
  19. bytes memory s,
  20. bytes memory e,
  21. bytes memory n
  22. ) internal view returns (bool) {
  23. return pkcs1Sha256(sha256(data), s, e, n);
  24. }
  25. /**
  26. * @dev Verifies a PKCSv1.5 signature given a digest according to the verification
  27. * method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017] with support
  28. * for explicit or implicit NULL parameters in the DigestInfo (no other optional parameters are supported).
  29. *
  30. * IMPORTANT: For security reason, this function requires the signature and modulus to have a length of at least 2048 bits.
  31. * If you use a smaller key, consider replacing it with a larger, more secure, one.
  32. *
  33. * WARNING: PKCS#1 v1.5 allows for replayability given the message may contain arbitrary optional parameters in the
  34. * DigestInfo. Consider using an onchain nonce or unique identifier to include in the message to prevent replay attacks.
  35. *
  36. * @param digest the digest to verify
  37. * @param s is a buffer containing the signature
  38. * @param e is the exponent of the public key
  39. * @param n is the modulus of the public key
  40. */
  41. function pkcs1Sha256(bytes32 digest, bytes memory s, bytes memory e, bytes memory n) internal view returns (bool) {
  42. unchecked {
  43. // cache and check length
  44. uint256 length = n.length;
  45. if (
  46. length < 0x100 || // Enforce 2048 bits minimum
  47. length != s.length // signature must have the same length as the finite field
  48. ) {
  49. return false;
  50. }
  51. // Verify that s < n to ensure there's only one valid signature for a given message
  52. for (uint256 i = 0; i < length; i += 0x20) {
  53. uint256 p = Math.min(i, length - 0x20);
  54. bytes32 sp = _unsafeReadBytes32(s, p);
  55. bytes32 np = _unsafeReadBytes32(n, p);
  56. if (sp < np) {
  57. // s < n in the upper bits (everything before is equal) → s < n globally: ok
  58. break;
  59. } else if (sp > np || p == length - 0x20) {
  60. // s > n in the upper bits (everything before is equal) → s > n globally: fail
  61. // or
  62. // s = n and we are looking at the lower bits → s = n globally: fail
  63. return false;
  64. }
  65. }
  66. // RSAVP1 https://datatracker.ietf.org/doc/html/rfc8017#section-5.2.2
  67. // The previous check guarantees that n > 0. Therefore modExp cannot revert.
  68. bytes memory buffer = Math.modExp(s, e, n);
  69. // Check that buffer is well encoded:
  70. // buffer ::= 0x00 | 0x01 | PS | 0x00 | DigestInfo
  71. //
  72. // With
  73. // - PS is padding filled with 0xFF
  74. // - DigestInfo ::= SEQUENCE {
  75. // digestAlgorithm AlgorithmIdentifier,
  76. // [optional algorithm parameters]
  77. // digest OCTET STRING
  78. // }
  79. // Get AlgorithmIdentifier from the DigestInfo, and set the config accordingly
  80. // - params: includes 00 + first part of DigestInfo
  81. // - mask: filter to check the params
  82. // - offset: length of the suffix (including digest)
  83. bytes32 params; // 0x00 | DigestInfo
  84. bytes32 mask;
  85. uint256 offset;
  86. // Digest is expected at the end of the buffer. Therefore if NULL param is present,
  87. // it should be at 32 (digest) + 2 bytes from the end. To those 34 bytes, we add the
  88. // OID (9 bytes) and its length (2 bytes) to get the position of the DigestInfo sequence,
  89. // which is expected to have a length of 0x31 when the NULL param is present or 0x2f if not.
  90. if (bytes1(_unsafeReadBytes32(buffer, length - 0x32)) == 0x31) {
  91. offset = 0x34;
  92. // 00 (1 byte) | SEQUENCE length (0x31) = 3031 (2 bytes) | SEQUENCE length (0x0d) = 300d (2 bytes) | OBJECT_IDENTIFIER length (0x09) = 0609 (2 bytes)
  93. // SHA256 OID = 608648016503040201 (9 bytes) | NULL = 0500 (2 bytes) (explicit) | OCTET_STRING length (0x20) = 0420 (2 bytes)
  94. params = 0x003031300d060960864801650304020105000420000000000000000000000000;
  95. mask = 0xffffffffffffffffffffffffffffffffffffffff000000000000000000000000; // (20 bytes)
  96. } else if (bytes1(_unsafeReadBytes32(buffer, length - 0x30)) == 0x2F) {
  97. offset = 0x32;
  98. // 00 (1 byte) | SEQUENCE length (0x2f) = 302f (2 bytes) | SEQUENCE length (0x0b) = 300b (2 bytes) | OBJECT_IDENTIFIER length (0x09) = 0609 (2 bytes)
  99. // SHA256 OID = 608648016503040201 (9 bytes) | NULL = <implicit> | OCTET_STRING length (0x20) = 0420 (2 bytes)
  100. params = 0x00302f300b060960864801650304020104200000000000000000000000000000;
  101. mask = 0xffffffffffffffffffffffffffffffffffff0000000000000000000000000000; // (18 bytes)
  102. } else {
  103. // unknown
  104. return false;
  105. }
  106. // Length is at least 0x100 and offset is at most 0x34, so this is safe. There is always some padding.
  107. uint256 paddingEnd = length - offset;
  108. // The padding has variable (arbitrary) length, so we check it byte per byte in a loop.
  109. // This is required to ensure non-malleability. Not checking would allow an attacker to
  110. // use the padding to manipulate the message in order to create a valid signature out of
  111. // multiple valid signatures.
  112. for (uint256 i = 2; i < paddingEnd; ++i) {
  113. if (bytes1(_unsafeReadBytes32(buffer, i)) != 0xFF) {
  114. return false;
  115. }
  116. }
  117. // All the other parameters are small enough to fit in a bytes32, so we can check them directly.
  118. return
  119. bytes2(0x0001) == bytes2(_unsafeReadBytes32(buffer, 0x00)) && // 00 | 01
  120. // PS was checked in the loop
  121. params == _unsafeReadBytes32(buffer, paddingEnd) & mask && // DigestInfo
  122. // Optional parameters are not checked
  123. digest == _unsafeReadBytes32(buffer, length - 0x20); // Digest
  124. }
  125. }
  126. /// @dev Reads a bytes32 from a bytes array without bounds checking.
  127. function _unsafeReadBytes32(bytes memory array, uint256 offset) private pure returns (bytes32 result) {
  128. // Memory safeness is guaranteed as long as the provided `array` is a Solidity-allocated bytes array
  129. // and `offset` is within bounds. This is the case for all calls to this private function from {pkcs1Sha256}.
  130. assembly ("memory-safe") {
  131. result := mload(add(add(array, 0x20), offset))
  132. }
  133. }
  134. }