From: Andre Noll Date: Tue, 28 Aug 2018 18:38:52 +0000 (+0200) Subject: gcrypt: Add support for RFC4716 private keys. X-Git-Tag: v0.6.3~33^2~3 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=74f74cde7afdba9cfe316998aba9286764bb5d34 gcrypt: Add support for RFC4716 private keys. This teaches the gcrypt backend to parse private keys generated with ssh-keygen -m RFC4716. Support for the openssl backend will be implemented in a subsequent patch. The new find_openssh_bignum_offset() is independent of the gcrypt API. We put this function into crypt_common.c rather then into gcrypt.c so that openssh.c can also use it. --- diff --git a/crypt_backend.h b/crypt_backend.h index 6f800875..ae01fc14 100644 --- a/crypt_backend.h +++ b/crypt_backend.h @@ -10,3 +10,4 @@ int decode_public_key(const char *filename, unsigned char **blob, size_t *decoded_size); int check_private_key_file(const char *file); +int find_openssh_bignum_offset(const unsigned char *data, int len); diff --git a/crypt_common.c b/crypt_common.c index 2b2e06c1..277897f2 100644 --- a/crypt_common.c +++ b/crypt_common.c @@ -159,3 +159,74 @@ int hash_compare(unsigned char *h1, unsigned char *h2) } return 0; } + +/** + * Check header of an openssh private key and compute bignum offset. + * + * \param data The base64-decoded key. + * \param len The size of the decoded key. + * + * Several assumptions are made about the key. Most notably, we only support + * single unencrypted keys without comments. + * + * \return The offset at which the first bignum of the private key (the public + * exponent n) starts. Negative error code on failure. + */ +int find_openssh_bignum_offset(const unsigned char *data, int len) +{ + /* + * Unencrypted keys without comments always start with the below byte + * sequence. See PROTOCOL.key of the openssh package. + */ + static const unsigned char valid_openssh_header[] = { + /* string "openssh-key-v1" */ + 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2d, 0x6b, 0x65, + 0x79, 0x2d, 0x76, 0x31, + /* length of the cipher name */ + 0x00, 0x00, 0x00, 0x00, 0x04, + /* cipher name: "none" */ + 0x6e, 0x6f, 0x6e, 0x65, + /* length of the kdfname (only used for encrypted keys) */ + 0x00, 0x00, 0x00, 0x04, + /* kdfname: "none" */ + 0x6e, 0x6f, 0x6e, 0x65, + /* length of kdfoptions */ + 0x00, 0x00, 0x00, 0x00, + /* number of keys */ + 0x00, 0x00, 0x00, 0x01, + }; + uint32_t val; + const unsigned char *p, *end = data + len; + + if (len <= sizeof(valid_openssh_header) + 4) + return -E_OPENSSH_PARSE; + if (memcmp(data, valid_openssh_header, sizeof(valid_openssh_header))) + return -E_OPENSSH_PARSE; + p = data + sizeof(valid_openssh_header); + /* length of public key */ + val = read_u32_be(p); + if (val > end - p - 4) + return -E_OPENSSH_PARSE; + p += val + 4; + /* length of private key */ + val = read_u32_be(p); + if (val > end - p - 4) + return -E_OPENSSH_PARSE; + p += 4; + /* two equal random integers ("checkint") */ + if (p + 8 > end) + return -E_OPENSSH_PARSE; + if (read_u32_be(p) != read_u32_be(p + 4)) + return -E_OPENSSH_PARSE; + p += 8; + /* length of name of key type "ssh-rsa" */ + if (p + 11 > end) + return -E_OPENSSH_PARSE; + if (read_u32_be(p) != 7) + return -E_OPENSSH_PARSE; + if (memcmp(p + 4, "ssh-rsa", 7)) + return -E_OPENSSH_PARSE; + p += 11; + return p - data; +} + diff --git a/error.h b/error.h index c06d316d..a245fb36 100644 --- a/error.h +++ b/error.h @@ -167,6 +167,7 @@ PARA_ERROR(OGG_PACKET_IN, "ogg_stream_packetin() failed"), \ PARA_ERROR(OGG_STREAM_FLUSH, "ogg_stream_flush() failed"), \ PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \ + PARA_ERROR(OPENSSH_PARSE, "could not parse openssh private key"), \ PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \ PARA_ERROR(OPUS_DECODE, "opus decode error"), \ PARA_ERROR(OPUS_HEADER, "invalid opus header"), \ diff --git a/gcrypt.c b/gcrypt.c index 8431cc24..69c2c34f 100644 --- a/gcrypt.c +++ b/gcrypt.c @@ -12,6 +12,7 @@ #include "crypt_backend.h" #include "fd.h" #include "base64.h" +#include "portable_io.h" //#define GCRYPT_DEBUG 1 @@ -106,15 +107,23 @@ static const char *gcrypt_strerror(gcry_error_t gret) return gcry_strerror(gcry_err_code(gret)); } -/** Private keys start with this header. */ -#define PRIVATE_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----" -/** Private keys end with this footer. */ -#define PRIVATE_KEY_FOOTER "-----END RSA PRIVATE KEY-----" +/** Private PEM keys (legacy format) start with this header. */ +#define PRIVATE_PEM_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----" +/** Private OPENSSH keys (RFC4716) start with this header. */ +#define PRIVATE_OPENSSH_KEY_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----" +/** Private PEM keys (legacy format) end with this footer. */ +#define PRIVATE_PEM_KEY_FOOTER "-----END RSA PRIVATE KEY-----" +/** Private OPENSSH keys (RFC4716) end with this footer. */ +#define PRIVATE_OPENSSH_KEY_FOOTER "-----END OPENSSH PRIVATE KEY-----" +/** Legacy PEM keys (openssh-7.7 and earlier, paraslash.0.6.2 and earlier) */ +#define PKT_PEM (0) +/** OPENSSH keys (since openssh-7.8, paraslash.0.6.3) */ +#define PKT_OPENSSH (1) static int decode_private_key(const char *key_file, unsigned char **result, size_t *blob_size) { - int ret, ret2, i, j; + int ret, ret2, i, j, key_type; void *map; size_t map_size, key_size; unsigned char *blob = NULL; @@ -124,13 +133,22 @@ static int decode_private_key(const char *key_file, unsigned char **result, if (ret < 0) goto out; ret = -E_KEY_MARKER; - if (strncmp(map, PRIVATE_KEY_HEADER, strlen(PRIVATE_KEY_HEADER))) + if (strncmp(map, PRIVATE_PEM_KEY_HEADER, + strlen(PRIVATE_PEM_KEY_HEADER)) == 0) { + key_type = PKT_PEM; + begin = map + strlen(PRIVATE_PEM_KEY_HEADER); + footer = strstr(map, PRIVATE_PEM_KEY_FOOTER); + PARA_INFO_LOG("detected legacy PEM key %s\n", key_file); + } else if (strncmp(map, PRIVATE_OPENSSH_KEY_HEADER, + strlen(PRIVATE_OPENSSH_KEY_HEADER)) == 0) { + key_type = PKT_OPENSSH; + begin = map + strlen(PRIVATE_OPENSSH_KEY_HEADER); + footer = strstr(map, PRIVATE_OPENSSH_KEY_FOOTER); + PARA_INFO_LOG("detected openssh key %s\n", key_file); + } else goto unmap; - footer = strstr(map, PRIVATE_KEY_FOOTER); - ret = -E_KEY_MARKER; if (!footer) goto unmap; - begin = map + strlen(PRIVATE_KEY_HEADER); /* skip whitespace at the beginning */ for (; begin < footer; begin++) { if (para_isspace(*begin)) @@ -151,6 +169,9 @@ static int decode_private_key(const char *key_file, unsigned char **result, key[j] = '\0'; ret = base64_decode(key, j, (char **)&blob, blob_size); free(key); + if (ret < 0) + goto unmap; + ret = key_type; unmap: ret2 = para_munmap(map, map_size); if (ret >= 0 && ret2 < 0) @@ -208,7 +229,7 @@ static inline int get_long_form_num_length_bytes(unsigned char c) * bitsp because the latter does not include the ASN.1 prefix and a leading * zero is not considered as an additional byte for the number of bits. */ -static int read_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn, +static int read_pem_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn, unsigned *bitsp) { int i, bn_size; @@ -267,27 +288,27 @@ static int read_pem_rsa_params(unsigned char *start, unsigned char *end, unsigned bits; int ret; - ret = read_bignum(cp, end, &p->n, &bits); + ret = read_pem_bignum(cp, end, &p->n, &bits); if (ret < 0) return ret; cp += ret; - ret = read_bignum(cp, end, &p->e, NULL); + ret = read_pem_bignum(cp, end, &p->e, NULL); if (ret < 0) goto release_n; cp += ret; - ret = read_bignum(cp, end, &p->d, NULL); + ret = read_pem_bignum(cp, end, &p->d, NULL); if (ret < 0) goto release_e; cp += ret; - ret = read_bignum(cp, end, &p->p, NULL); + ret = read_pem_bignum(cp, end, &p->p, NULL); if (ret < 0) goto release_d; cp += ret; - ret = read_bignum(cp, end, &p->q, NULL); + ret = read_pem_bignum(cp, end, &p->q, NULL); if (ret < 0) goto release_p; cp += ret; - ret = read_bignum(cp, end, &p->u, NULL); + ret = read_pem_bignum(cp, end, &p->u, NULL); if (ret < 0) goto release_q; return bits; @@ -304,7 +325,7 @@ release_n: return ret; } -static int find_privkey_bignum_offset(const unsigned char *data, int len) +static int find_pem_bignum_offset(const unsigned char *data, int len) { const unsigned char *p = data, *end = data + len; @@ -356,12 +377,56 @@ static int read_openssh_bignum(unsigned char *start, unsigned char *end, return nscanned; } +static int read_openssh_rsa_params(unsigned char *start, unsigned char *end, + struct rsa_params *p) +{ + unsigned char *cp = start; + unsigned bits; + int ret; + + ret = read_openssh_bignum(cp, end, &p->n, &bits); + if (ret < 0) + return ret; + cp += ret; + ret = read_openssh_bignum(cp, end, &p->e, NULL); + if (ret < 0) + goto release_n; + cp += ret; + ret = read_openssh_bignum(cp, end, &p->d, NULL); + if (ret < 0) + goto release_e; + cp += ret; + ret = read_openssh_bignum(cp, end, &p->u, NULL); + if (ret < 0) + goto release_d; + cp += ret; + ret = read_openssh_bignum(cp, end, &p->p, NULL); + if (ret < 0) + goto release_u; + cp += ret; + ret = read_openssh_bignum(cp, end, &p->q, NULL); + if (ret < 0) + goto release_p; + return bits; +release_p: + gcry_mpi_release(p->p); +release_u: + gcry_mpi_release(p->u); +release_d: + gcry_mpi_release(p->d); +release_e: + gcry_mpi_release(p->e); +release_n: + gcry_mpi_release(p->n); + return ret; +} + static int get_private_key(const char *key_file, struct asymmetric_key **result) { struct rsa_params params; unsigned char *blob, *end; - int ret; unsigned bits; + int ret, key_type; gcry_error_t gret; size_t erroff, blob_size; gcry_sexp_t sexp; @@ -371,12 +436,19 @@ static int get_private_key(const char *key_file, struct asymmetric_key **result) ret = decode_private_key(key_file, &blob, &blob_size); if (ret < 0) return ret; + key_type = ret; end = blob + blob_size; - ret = find_privkey_bignum_offset(blob, blob_size); + if (key_type == PKT_PEM) + ret = find_pem_bignum_offset(blob, blob_size); + else + ret = find_openssh_bignum_offset(blob, blob_size); if (ret < 0) goto free_blob; PARA_INFO_LOG("reading RSA params at offset %d\n", ret); - ret = read_pem_rsa_params(blob + ret, end, ¶ms); + if (key_type == PKT_PEM) + ret = read_pem_rsa_params(blob + ret, end, ¶ms); + else + ret = read_openssh_rsa_params(blob + ret, end, ¶ms); if (ret < 0) goto free_blob; bits = ret;