validate_manual_hostkey.c 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. /*
  2. * Validate a manual host key specification (either entered in the
  3. * GUI, or via -hostkey). If valid, we return true, and update 'key'
  4. * to contain a canonicalised version of the key string in 'key'
  5. * (which is guaranteed to take up at most as much space as the
  6. * original version), suitable for putting into the Conf. If not
  7. * valid, we return false.
  8. */
  9. #include <string.h>
  10. #include <ctype.h>
  11. #include "putty.h"
  12. #include "misc.h"
  13. #define BASE64_CHARS_NOEQ \
  14. "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
  15. #define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "="
  16. bool validate_manual_hostkey(char *key)
  17. {
  18. char *p, *q, *r, *s;
  19. /*
  20. * Step through the string word by word, looking for a word that's
  21. * in one of the formats we like.
  22. */
  23. p = key;
  24. while ((p += strspn(p, " \t"))[0]) {
  25. q = p;
  26. p += strcspn(p, " \t");
  27. if (*p) *p++ = '\0';
  28. /*
  29. * Now q is our word.
  30. */
  31. if (strstartswith(q, "SHA256:")) {
  32. /* Test for a valid SHA256 key fingerprint. */
  33. r = q + 7;
  34. if (strspn(r, BASE64_CHARS_NOEQ) == 43) {
  35. memmove(key, q, 50); /* 7-char prefix + 43-char base64 */
  36. key[50] = '\0';
  37. return true;
  38. }
  39. }
  40. r = q;
  41. if (strstartswith(r, "MD5:"))
  42. r += 4;
  43. if (strlen(r) == 16*3 - 1 &&
  44. r[strspn(r, "0123456789abcdefABCDEF:")] == 0) {
  45. /*
  46. * Test for a valid MD5 key fingerprint. Check the colons
  47. * are in the right places, and if so, return the same
  48. * fingerprint canonicalised into lowercase.
  49. */
  50. int i;
  51. for (i = 0; i < 16; i++)
  52. if (r[3*i] == ':' || r[3*i+1] == ':')
  53. goto not_fingerprint; /* sorry */
  54. for (i = 0; i < 15; i++)
  55. if (r[3*i+2] != ':')
  56. goto not_fingerprint; /* sorry */
  57. for (i = 0; i < 16*3 - 1; i++)
  58. key[i] = tolower((unsigned char)r[i]);
  59. key[16*3 - 1] = '\0';
  60. return true;
  61. }
  62. not_fingerprint:;
  63. /*
  64. * Before we check for a public-key blob, trim newlines out of
  65. * the middle of the word, in case someone's managed to paste
  66. * in a public-key blob _with_ them.
  67. */
  68. for (r = s = q; *r; r++)
  69. if (*r != '\n' && *r != '\r')
  70. *s++ = *r;
  71. *s = '\0';
  72. if (strlen(q) % 4 == 0 && strlen(q) > 2*4 &&
  73. q[strspn(q, BASE64_CHARS_ALL)] == 0) {
  74. /*
  75. * Might be a base64-encoded SSH-2 public key blob. Check
  76. * that it starts with a sensible algorithm string. No
  77. * canonicalisation is necessary for this string type.
  78. *
  79. * The algorithm string must be at most 64 characters long
  80. * (RFC 4251 section 6).
  81. */
  82. unsigned char decoded[6];
  83. unsigned alglen;
  84. int minlen;
  85. int len = 0;
  86. len += base64_decode_atom(q, decoded+len);
  87. if (len < 3)
  88. goto not_ssh2_blob; /* sorry */
  89. len += base64_decode_atom(q+4, decoded+len);
  90. if (len < 4)
  91. goto not_ssh2_blob; /* sorry */
  92. alglen = GET_32BIT_MSB_FIRST(decoded);
  93. if (alglen > 64)
  94. goto not_ssh2_blob; /* sorry */
  95. minlen = ((alglen + 4) + 2) / 3;
  96. if (strlen(q) < minlen)
  97. goto not_ssh2_blob; /* sorry */
  98. size_t base64_len = strspn(q, BASE64_CHARS_ALL);
  99. memmove(key, q, base64_len);
  100. key[base64_len] = '\0';
  101. return true;
  102. }
  103. not_ssh2_blob:;
  104. }
  105. return false;
  106. }