ecc_p224.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. #include "ecc_p224.h"
  2. #include <Windows.h>
  3. #include <algorithm>
  4. #include <bcrypt.h>
  5. #include <vector>
  6. namespace sunrise::middleware::crypto::ecc {
  7. namespace {
  8. /** DER tag for a SEQUENCE. */
  9. constexpr std::byte kTagSequence{0x30};
  10. /** DER tag for an INTEGER. */
  11. constexpr std::byte kTagInteger{0x02};
  12. /** DER tag for a BIT STRING. */
  13. constexpr std::byte kTagBitString{0x03};
  14. /** The key encoding opens with a one-bit flag that is set only on a private key. */
  15. constexpr std::array<std::byte, 4> kPublicFlagBits{
  16. kTagBitString, std::byte{0x02}, std::byte{0x07}, std::byte{0x00}};
  17. /** A DER length below this fits in the single byte that follows the tag. */
  18. constexpr std::size_t kShortFormLimit = 0x80;
  19. /** Bits in one byte. */
  20. constexpr unsigned kByteBits = 8;
  21. /** Public key blobs name the curve rather than carry its parameters. */
  22. constexpr ULONG kGenericPublicMagic = 0x504B4345;
  23. /** @return True for a BCrypt status that reports success. */
  24. [[nodiscard]] bool succeeded(NTSTATUS status) noexcept {
  25. return status >= 0;
  26. }
  27. /**
  28. * Appends one positive integer in the minimal form DER requires.
  29. * A leading zero goes in when the top bit is set, so the value never reads as negative.
  30. * @param output Growing encoding.
  31. * @param value Field element, high byte first.
  32. */
  33. void append_integer(std::vector<std::byte>& output, std::span<const std::byte> value) noexcept {
  34. std::size_t first = 0;
  35. while (first + 1 < value.size() && value[first] == std::byte{0}) {
  36. ++first;
  37. }
  38. const std::span<const std::byte> trimmed = value.subspan(first);
  39. const bool pad = (std::to_integer<unsigned>(trimmed[0]) & 0x80U) != 0;
  40. output.push_back(kTagInteger);
  41. output.push_back(static_cast<std::byte>(trimmed.size() + (pad ? 1U : 0U)));
  42. if (pad) {
  43. output.push_back(std::byte{0});
  44. }
  45. output.insert(output.end(), trimmed.begin(), trimmed.end());
  46. }
  47. /**
  48. * Encodes one public key the way the peer's importer reads it.
  49. * @param x Affine x, high byte first.
  50. * @param y Affine y, high byte first.
  51. * @param output Receives the encoding, zero padded to the fixed field.
  52. * @return True when the encoding fits the field.
  53. */
  54. [[nodiscard]] bool encode_public_key(std::span<const std::byte> x,
  55. std::span<const std::byte> y,
  56. std::array<std::byte, kExportedKeySize>& output) noexcept {
  57. std::vector<std::byte> body;
  58. body.insert(body.end(), kPublicFlagBits.begin(), kPublicFlagBits.end());
  59. // The curve is identified by its field size, encoded as a plain integer.
  60. body.push_back(kTagInteger);
  61. body.push_back(std::byte{1});
  62. body.push_back(static_cast<std::byte>(kFieldSize));
  63. append_integer(body, x);
  64. append_integer(body, y);
  65. if (body.size() >= kShortFormLimit || body.size() + 2 > kExportedKeySize) {
  66. return false;
  67. }
  68. output = {};
  69. output[0] = kTagSequence;
  70. output[1] = static_cast<std::byte>(body.size());
  71. std::copy(body.begin(), body.end(), output.begin() + 2);
  72. return true;
  73. }
  74. /**
  75. * Reads one DER element header.
  76. * @param input Encoding.
  77. * @param cursor Position of the tag; advanced past the header on success.
  78. * @param tag Tag the element must carry.
  79. * @param length Receives the content length.
  80. * @return True when the element is present and its content fits the input.
  81. */
  82. [[nodiscard]] bool read_header(std::span<const std::byte> input,
  83. std::size_t& cursor,
  84. std::byte tag,
  85. std::size_t& length) noexcept {
  86. if (cursor + 2 > input.size() || input[cursor] != tag) {
  87. return false;
  88. }
  89. const auto declared = std::to_integer<std::size_t>(input[cursor + 1]);
  90. // Only the short form is accepted. Every element here is far below the long-form threshold.
  91. if (declared >= kShortFormLimit || cursor + 2 + declared > input.size()) {
  92. return false;
  93. }
  94. cursor += 2;
  95. length = declared;
  96. return true;
  97. }
  98. /**
  99. * Reads one integer into a fixed field, right aligned.
  100. * @param input Encoding.
  101. * @param cursor Position of the tag; advanced past the element on success.
  102. * @param output Receives the value, high byte first.
  103. * @return True when the element is an integer no wider than the field.
  104. */
  105. [[nodiscard]] bool read_field_integer(std::span<const std::byte> input,
  106. std::size_t& cursor,
  107. std::array<std::byte, kFieldSize>& output) noexcept {
  108. std::size_t length = 0;
  109. if (!read_header(input, cursor, kTagInteger, length) || length == 0) {
  110. return false;
  111. }
  112. std::span<const std::byte> value = input.subspan(cursor, length);
  113. if (!value.empty() && value[0] == std::byte{0}) {
  114. value = value.subspan(1);
  115. }
  116. if (value.size() > kFieldSize) {
  117. return false;
  118. }
  119. output = {};
  120. std::copy(value.begin(), value.end(), output.end() - static_cast<std::ptrdiff_t>(value.size()));
  121. cursor += length;
  122. return true;
  123. }
  124. /**
  125. * Reads the peer's exported public key.
  126. * @param input Peer key, padding included.
  127. * @param x Receives affine x.
  128. * @param y Receives affine y.
  129. * @return True when the encoding is a public key on this curve.
  130. */
  131. [[nodiscard]] bool decode_public_key(std::span<const std::byte> input,
  132. std::array<std::byte, kFieldSize>& x,
  133. std::array<std::byte, kFieldSize>& y) noexcept {
  134. std::size_t cursor = 0;
  135. std::size_t length = 0;
  136. if (!read_header(input, cursor, kTagSequence, length)) {
  137. return false;
  138. }
  139. if (!read_header(input, cursor, kTagBitString, length)) {
  140. return false;
  141. }
  142. cursor += length;
  143. std::size_t sizeLength = 0;
  144. if (!read_header(input, cursor, kTagInteger, sizeLength) || sizeLength != 1
  145. || input[cursor] != static_cast<std::byte>(kFieldSize)) {
  146. return false;
  147. }
  148. cursor += sizeLength;
  149. return read_field_integer(input, cursor, x) && read_field_integer(input, cursor, y);
  150. }
  151. /**
  152. * Opens the agreement algorithm bound to this curve.
  153. * @param output Receives the provider handle only on success.
  154. * @return True when Windows offers the curve.
  155. */
  156. [[nodiscard]] bool open_provider(BCRYPT_ALG_HANDLE& output) noexcept {
  157. BCRYPT_ALG_HANDLE algorithm = nullptr;
  158. if (!succeeded(BCryptOpenAlgorithmProvider(&algorithm, BCRYPT_ECDH_ALGORITHM, nullptr, 0))) {
  159. return false;
  160. }
  161. const auto* curve = reinterpret_cast<PUCHAR>(const_cast<wchar_t*>(BCRYPT_ECC_CURVE_SECP224R1));
  162. if (!succeeded(BCryptSetProperty(algorithm,
  163. BCRYPT_ECC_CURVE_NAME,
  164. const_cast<PUCHAR>(curve),
  165. sizeof(BCRYPT_ECC_CURVE_SECP224R1),
  166. 0))) {
  167. BCryptCloseAlgorithmProvider(algorithm, 0);
  168. return false;
  169. }
  170. output = algorithm;
  171. return true;
  172. }
  173. /**
  174. * Imports the peer's point as a public key.
  175. * @param algorithm Provider bound to the curve.
  176. * @param x Affine x.
  177. * @param y Affine y.
  178. * @param output Receives the key handle only on success.
  179. * @return True when Windows accepted the point.
  180. */
  181. [[nodiscard]] bool import_peer(BCRYPT_ALG_HANDLE algorithm,
  182. const std::array<std::byte, kFieldSize>& x,
  183. const std::array<std::byte, kFieldSize>& y,
  184. BCRYPT_KEY_HANDLE& output) noexcept {
  185. std::array<std::byte, sizeof(BCRYPT_ECCKEY_BLOB) + (2 * kFieldSize)> blob{};
  186. auto* header = reinterpret_cast<BCRYPT_ECCKEY_BLOB*>(blob.data());
  187. header->dwMagic = kGenericPublicMagic;
  188. header->cbKey = static_cast<ULONG>(kFieldSize);
  189. std::copy(x.begin(), x.end(), blob.begin() + sizeof(BCRYPT_ECCKEY_BLOB));
  190. std::copy(y.begin(), y.end(), blob.begin() + sizeof(BCRYPT_ECCKEY_BLOB) + kFieldSize);
  191. return succeeded(BCryptImportKeyPair(algorithm,
  192. nullptr,
  193. BCRYPT_ECCPUBLIC_BLOB,
  194. &output,
  195. reinterpret_cast<PUCHAR>(blob.data()),
  196. static_cast<ULONG>(blob.size()),
  197. 0));
  198. }
  199. /**
  200. * Runs the agreement and takes the raw secret.
  201. * @param ours Our private key.
  202. * @param theirs Peer's public key.
  203. * @param output Receives the x coordinate, high byte first.
  204. * @return True when Windows produced a full-width secret.
  205. */
  206. [[nodiscard]] bool raw_secret(BCRYPT_KEY_HANDLE ours,
  207. BCRYPT_KEY_HANDLE theirs,
  208. std::array<std::byte, kFieldSize>& output) noexcept {
  209. BCRYPT_SECRET_HANDLE secret = nullptr;
  210. if (!succeeded(BCryptSecretAgreement(ours, theirs, &secret, 0))) {
  211. return false;
  212. }
  213. ULONG produced = 0;
  214. const bool derived = succeeded(BCryptDeriveKey(secret,
  215. BCRYPT_KDF_RAW_SECRET,
  216. nullptr,
  217. reinterpret_cast<PUCHAR>(output.data()),
  218. static_cast<ULONG>(output.size()),
  219. &produced,
  220. 0));
  221. BCryptDestroySecret(secret);
  222. if (!derived || produced != output.size()) {
  223. // A short derive can still have written part of the secret.
  224. SecureZeroMemory(output.data(), output.size());
  225. return false;
  226. }
  227. // Windows hands the raw secret back low byte first; the peer reads it high byte first.
  228. std::reverse(output.begin(), output.end());
  229. return true;
  230. }
  231. } // namespace
  232. /** Generates one key pair and agrees a secret with the peer's exported public key. */
  233. bool agree(std::span<const std::byte> peerPublicKey, Agreement& output) noexcept {
  234. std::array<std::byte, kFieldSize> peerX{};
  235. std::array<std::byte, kFieldSize> peerY{};
  236. if (!decode_public_key(peerPublicKey, peerX, peerY)) {
  237. return false;
  238. }
  239. BCRYPT_ALG_HANDLE algorithm = nullptr;
  240. if (!open_provider(algorithm)) {
  241. return false;
  242. }
  243. BCRYPT_KEY_HANDLE ours = nullptr;
  244. BCRYPT_KEY_HANDLE theirs = nullptr;
  245. bool complete = false;
  246. if (succeeded(BCryptGenerateKeyPair(algorithm, &ours, kFieldSize * kByteBits, 0))
  247. && succeeded(BCryptFinalizeKeyPair(ours, 0))) {
  248. std::array<std::byte, sizeof(BCRYPT_ECCKEY_BLOB) + (2 * kFieldSize)> blob{};
  249. ULONG produced = 0;
  250. if (succeeded(BCryptExportKey(ours,
  251. nullptr,
  252. BCRYPT_ECCPUBLIC_BLOB,
  253. reinterpret_cast<PUCHAR>(blob.data()),
  254. static_cast<ULONG>(blob.size()),
  255. &produced,
  256. 0))
  257. && produced == blob.size()) {
  258. const std::span<const std::byte> point{blob.data() + sizeof(BCRYPT_ECCKEY_BLOB),
  259. 2 * kFieldSize};
  260. complete =
  261. encode_public_key(point.first(kFieldSize), point.last(kFieldSize), output.publicKey)
  262. && import_peer(algorithm, peerX, peerY, theirs)
  263. && raw_secret(ours, theirs, output.sharedSecret);
  264. }
  265. }
  266. if (theirs != nullptr) {
  267. BCryptDestroyKey(theirs);
  268. }
  269. if (ours != nullptr) {
  270. BCryptDestroyKey(ours);
  271. }
  272. BCryptCloseAlgorithmProvider(algorithm, 0);
  273. if (!complete) {
  274. SecureZeroMemory(output.sharedSecret.data(), output.sharedSecret.size());
  275. output = {};
  276. }
  277. return complete;
  278. }
  279. } // namespace sunrise::middleware::crypto::ecc