ssh-ecdsa-sk.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /* $OpenBSD: ssh-ecdsa-sk.c,v 1.8 2020/06/22 23:44:27 djm Exp $ */
  2. /*
  3. * Copyright (c) 2000 Markus Friedl. All rights reserved.
  4. * Copyright (c) 2010 Damien Miller. All rights reserved.
  5. * Copyright (c) 2019 Google Inc. All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. * 1. Redistributions of source code must retain the above copyright
  11. * notice, this list of conditions and the following disclaimer.
  12. * 2. Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  17. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  18. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  19. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  20. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  21. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  22. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  23. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  25. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. /* #define DEBUG_SK 1 */
  28. #include "includes.h"
  29. #include <sys/types.h>
  30. #ifdef WITH_OPENSSL
  31. #include <openssl/bn.h>
  32. #include <openssl/ec.h>
  33. #include <openssl/ecdsa.h>
  34. #include <openssl/evp.h>
  35. #endif
  36. #include <string.h>
  37. #include <stdio.h> /* needed for DEBUG_SK only */
  38. #include "openbsd-compat/openssl-compat.h"
  39. #include "sshbuf.h"
  40. #include "ssherr.h"
  41. #include "digest.h"
  42. #define SSHKEY_INTERNAL
  43. #include "sshkey.h"
  44. #ifndef OPENSSL_HAS_ECC
  45. /* ARGSUSED */
  46. int
  47. ssh_ecdsa_sk_verify(const struct sshkey *key,
  48. const u_char *signature, size_t signaturelen,
  49. const u_char *data, size_t datalen, u_int compat,
  50. struct sshkey_sig_details **detailsp)
  51. {
  52. return SSH_ERR_FEATURE_UNSUPPORTED;
  53. }
  54. #else /* OPENSSL_HAS_ECC */
  55. /*
  56. * Check FIDO/W3C webauthn signatures clientData field against the expected
  57. * format and prepare a hash of it for use in signature verification.
  58. *
  59. * webauthn signatures do not sign the hash of the message directly, but
  60. * instead sign a JSON-like "clientData" wrapper structure that contains the
  61. * message hash along with a other information.
  62. *
  63. * Fortunately this structure has a fixed format so it is possible to verify
  64. * that the hash of the signed message is present within the clientData
  65. * structure without needing to implement any JSON parsing.
  66. */
  67. static int
  68. webauthn_check_prepare_hash(const u_char *data, size_t datalen,
  69. const char *origin, const struct sshbuf *wrapper,
  70. uint8_t flags, const struct sshbuf *extensions,
  71. u_char *msghash, size_t msghashlen)
  72. {
  73. int r = SSH_ERR_INTERNAL_ERROR;
  74. struct sshbuf *chall = NULL, *m = NULL;
  75. if ((m = sshbuf_new()) == NULL ||
  76. (chall = sshbuf_from(data, datalen)) == NULL) {
  77. r = SSH_ERR_ALLOC_FAIL;
  78. goto out;
  79. }
  80. /*
  81. * Ensure origin contains no quote character and that the flags are
  82. * consistent with what we received
  83. */
  84. if (strchr(origin, '\"') != NULL ||
  85. (flags & 0x40) != 0 /* AD */ ||
  86. ((flags & 0x80) == 0 /* ED */) != (sshbuf_len(extensions) == 0)) {
  87. r = SSH_ERR_INVALID_FORMAT;
  88. goto out;
  89. }
  90. /*
  91. * Prepare the preamble to clientData that we expect, poking the
  92. * challenge and origin into their canonical positions in the
  93. * structure. The crossOrigin flag and any additional extension
  94. * fields present are ignored.
  95. */
  96. #define WEBAUTHN_0 "{\"type\":\"webauthn.get\",\"challenge\":\""
  97. #define WEBAUTHN_1 "\",\"origin\":\""
  98. #define WEBAUTHN_2 "\""
  99. if ((r = sshbuf_put(m, WEBAUTHN_0, sizeof(WEBAUTHN_0) - 1)) != 0 ||
  100. (r = sshbuf_dtourlb64(chall, m, 0)) != 0 ||
  101. (r = sshbuf_put(m, WEBAUTHN_1, sizeof(WEBAUTHN_1) - 1)) != 0 ||
  102. (r = sshbuf_put(m, origin, strlen(origin))) != 0 ||
  103. (r = sshbuf_put(m, WEBAUTHN_2, sizeof(WEBAUTHN_2) - 1)) != 0)
  104. goto out;
  105. #ifdef DEBUG_SK
  106. fprintf(stderr, "%s: received origin: %s\n", __func__, origin);
  107. fprintf(stderr, "%s: received clientData:\n", __func__);
  108. sshbuf_dump(wrapper, stderr);
  109. fprintf(stderr, "%s: expected clientData premable:\n", __func__);
  110. sshbuf_dump(m, stderr);
  111. #endif
  112. /* Check that the supplied clientData has the preamble we expect */
  113. if ((r = sshbuf_cmp(wrapper, 0, sshbuf_ptr(m), sshbuf_len(m))) != 0)
  114. goto out;
  115. /* Prepare hash of clientData */
  116. if ((r = ssh_digest_buffer(SSH_DIGEST_SHA256, wrapper,
  117. msghash, msghashlen)) != 0)
  118. goto out;
  119. /* success */
  120. r = 0;
  121. out:
  122. sshbuf_free(chall);
  123. sshbuf_free(m);
  124. return r;
  125. }
  126. /* ARGSUSED */
  127. int
  128. ssh_ecdsa_sk_verify(const struct sshkey *key,
  129. const u_char *signature, size_t signaturelen,
  130. const u_char *data, size_t datalen, u_int compat,
  131. struct sshkey_sig_details **detailsp)
  132. {
  133. ECDSA_SIG *sig = NULL;
  134. BIGNUM *sig_r = NULL, *sig_s = NULL;
  135. u_char sig_flags;
  136. u_char msghash[32], apphash[32], sighash[32];
  137. u_int sig_counter;
  138. int is_webauthn = 0, ret = SSH_ERR_INTERNAL_ERROR;
  139. struct sshbuf *b = NULL, *sigbuf = NULL, *original_signed = NULL;
  140. struct sshbuf *webauthn_wrapper = NULL, *webauthn_exts = NULL;
  141. char *ktype = NULL, *webauthn_origin = NULL;
  142. struct sshkey_sig_details *details = NULL;
  143. #ifdef DEBUG_SK
  144. char *tmp = NULL;
  145. #endif
  146. if (detailsp != NULL)
  147. *detailsp = NULL;
  148. if (key == NULL || key->ecdsa == NULL ||
  149. sshkey_type_plain(key->type) != KEY_ECDSA_SK ||
  150. signature == NULL || signaturelen == 0)
  151. return SSH_ERR_INVALID_ARGUMENT;
  152. if (key->ecdsa_nid != NID_X9_62_prime256v1)
  153. return SSH_ERR_INTERNAL_ERROR;
  154. /* fetch signature */
  155. if ((b = sshbuf_from(signature, signaturelen)) == NULL)
  156. return SSH_ERR_ALLOC_FAIL;
  157. if ((details = calloc(1, sizeof(*details))) == NULL) {
  158. ret = SSH_ERR_ALLOC_FAIL;
  159. goto out;
  160. }
  161. if (sshbuf_get_cstring(b, &ktype, NULL) != 0) {
  162. ret = SSH_ERR_INVALID_FORMAT;
  163. goto out;
  164. }
  165. if (strcmp(ktype, "webauthn-sk-ecdsa-sha2-nistp256@openssh.com") == 0)
  166. is_webauthn = 1;
  167. else if (strcmp(ktype, "sk-ecdsa-sha2-nistp256@openssh.com") != 0) {
  168. ret = SSH_ERR_INVALID_FORMAT;
  169. goto out;
  170. }
  171. if (sshbuf_froms(b, &sigbuf) != 0 ||
  172. sshbuf_get_u8(b, &sig_flags) != 0 ||
  173. sshbuf_get_u32(b, &sig_counter) != 0) {
  174. ret = SSH_ERR_INVALID_FORMAT;
  175. goto out;
  176. }
  177. if (is_webauthn) {
  178. if (sshbuf_get_cstring(b, &webauthn_origin, NULL) != 0 ||
  179. sshbuf_froms(b, &webauthn_wrapper) != 0 ||
  180. sshbuf_froms(b, &webauthn_exts) != 0) {
  181. ret = SSH_ERR_INVALID_FORMAT;
  182. goto out;
  183. }
  184. }
  185. if (sshbuf_len(b) != 0) {
  186. ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
  187. goto out;
  188. }
  189. /* parse signature */
  190. if (sshbuf_get_bignum2(sigbuf, &sig_r) != 0 ||
  191. sshbuf_get_bignum2(sigbuf, &sig_s) != 0) {
  192. ret = SSH_ERR_INVALID_FORMAT;
  193. goto out;
  194. }
  195. if (sshbuf_len(sigbuf) != 0) {
  196. ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
  197. goto out;
  198. }
  199. #ifdef DEBUG_SK
  200. fprintf(stderr, "%s: data: (len %zu)\n", __func__, datalen);
  201. /* sshbuf_dump_data(data, datalen, stderr); */
  202. fprintf(stderr, "%s: sig_r: %s\n", __func__, (tmp = BN_bn2hex(sig_r)));
  203. free(tmp);
  204. fprintf(stderr, "%s: sig_s: %s\n", __func__, (tmp = BN_bn2hex(sig_s)));
  205. free(tmp);
  206. fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
  207. __func__, sig_flags, sig_counter);
  208. if (is_webauthn) {
  209. fprintf(stderr, "%s: webauthn origin: %s\n", __func__,
  210. webauthn_origin);
  211. fprintf(stderr, "%s: webauthn_wrapper:\n", __func__);
  212. sshbuf_dump(webauthn_wrapper, stderr);
  213. }
  214. #endif
  215. if ((sig = ECDSA_SIG_new()) == NULL) {
  216. ret = SSH_ERR_ALLOC_FAIL;
  217. goto out;
  218. }
  219. if (!ECDSA_SIG_set0(sig, sig_r, sig_s)) {
  220. debug("(!ECDSA_SIG_set0(sig, sig_r, sig_s))");
  221. ret = SSH_ERR_LIBCRYPTO_ERROR;
  222. goto out;
  223. }
  224. sig_r = sig_s = NULL; /* transferred */
  225. /* Reconstruct data that was supposedly signed */
  226. if ((original_signed = sshbuf_new()) == NULL) {
  227. ret = SSH_ERR_ALLOC_FAIL;
  228. goto out;
  229. }
  230. if (is_webauthn) {
  231. if ((ret = webauthn_check_prepare_hash(data, datalen,
  232. webauthn_origin, webauthn_wrapper, sig_flags, webauthn_exts,
  233. msghash, sizeof(msghash))) != 0)
  234. goto out;
  235. } else if ((ret = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
  236. msghash, sizeof(msghash))) != 0)
  237. goto out;
  238. /* Application value is hashed before signature */
  239. if ((ret = ssh_digest_memory(SSH_DIGEST_SHA256, key->sk_application,
  240. strlen(key->sk_application), apphash, sizeof(apphash))) != 0)
  241. goto out;
  242. #ifdef DEBUG_SK
  243. fprintf(stderr, "%s: hashed application:\n", __func__);
  244. sshbuf_dump_data(apphash, sizeof(apphash), stderr);
  245. fprintf(stderr, "%s: hashed message:\n", __func__);
  246. sshbuf_dump_data(msghash, sizeof(msghash), stderr);
  247. #endif
  248. if ((ret = sshbuf_put(original_signed,
  249. apphash, sizeof(apphash))) != 0 ||
  250. (ret = sshbuf_put_u8(original_signed, sig_flags)) != 0 ||
  251. (ret = sshbuf_put_u32(original_signed, sig_counter)) != 0 ||
  252. (ret = sshbuf_putb(original_signed, webauthn_exts)) != 0 ||
  253. (ret = sshbuf_put(original_signed, msghash, sizeof(msghash))) != 0)
  254. goto out;
  255. /* Signature is over H(original_signed) */
  256. if ((ret = ssh_digest_buffer(SSH_DIGEST_SHA256, original_signed,
  257. sighash, sizeof(sighash))) != 0)
  258. goto out;
  259. details->sk_counter = sig_counter;
  260. details->sk_flags = sig_flags;
  261. #ifdef DEBUG_SK
  262. fprintf(stderr, "%s: signed buf:\n", __func__);
  263. sshbuf_dump(original_signed, stderr);
  264. fprintf(stderr, "%s: signed hash:\n", __func__);
  265. sshbuf_dump_data(sighash, sizeof(sighash), stderr);
  266. #endif
  267. /* Verify it */
  268. switch (ECDSA_do_verify(sighash, sizeof(sighash), sig, key->ecdsa)) {
  269. case 1:
  270. ret = 0;
  271. break;
  272. case 0:
  273. ret = SSH_ERR_SIGNATURE_INVALID;
  274. goto out;
  275. default:
  276. ret = SSH_ERR_LIBCRYPTO_ERROR;
  277. goto out;
  278. }
  279. /* success */
  280. if (detailsp != NULL) {
  281. *detailsp = details;
  282. details = NULL;
  283. }
  284. out:
  285. explicit_bzero(&sig_flags, sizeof(sig_flags));
  286. explicit_bzero(&sig_counter, sizeof(sig_counter));
  287. explicit_bzero(msghash, sizeof(msghash));
  288. explicit_bzero(sighash, sizeof(msghash));
  289. explicit_bzero(apphash, sizeof(apphash));
  290. sshkey_sig_details_free(details);
  291. sshbuf_free(webauthn_wrapper);
  292. sshbuf_free(webauthn_exts);
  293. free(webauthn_origin);
  294. sshbuf_free(original_signed);
  295. sshbuf_free(sigbuf);
  296. sshbuf_free(b);
  297. ECDSA_SIG_free(sig);
  298. BN_clear_free(sig_r);
  299. BN_clear_free(sig_s);
  300. free(ktype);
  301. return ret;
  302. }
  303. #endif /* OPENSSL_HAS_ECC */