X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=crypt_common.c;h=ff24e356ab8d837f8d59d67a3c983d5264376db1;hp=1fd8189ca3c5a201bd2d227bb366bd800757a5a5;hb=3685a9093ae12ff9ce02fc58e607eb9b63894443;hpb=8eb77a20423c3dcc44c4584a082e981391272cb2 diff --git a/crypt_common.c b/crypt_common.c index 1fd8189c..ff24e356 100644 --- a/crypt_common.c +++ b/crypt_common.c @@ -1,8 +1,4 @@ -/* - * Copyright (C) 2005 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 2005 Andre Noll , see file COPYING. */ /** \file crypt_common.c Crypto functions independent of openssl/libgcrypt. */ @@ -13,20 +9,20 @@ #include "string.h" #include "crypt.h" #include "crypt_backend.h" +#include "portable_io.h" +#include "fd.h" +#include "base64.h" /** If the key begins with this text, we treat it as an ssh key. */ #define KEY_TYPE_TXT "ssh-rsa" -/** - * Check if given buffer starts with a ssh rsa key signature. - * - * \param data The buffer. - * \param size Number of data bytes. +/* + * Check if the given buffer starts with an ssh rsa key signature. * - * \return Number of header bytes to be skipped on success, zero if - * ssh rsa signature was not found. + * Returns number of header bytes to be skipped on success, zero if no ssh rsa + * signature was found. */ -size_t is_ssh_rsa_key(char *data, size_t size) +static size_t is_ssh_rsa_key(char *data, size_t size) { char *cp; @@ -45,50 +41,20 @@ size_t is_ssh_rsa_key(char *data, size_t size) return cp - data; } -/** - * Read a 4-byte number from a buffer in big-endian format. - * - * \param vp The buffer. - * - * The byte-order of the buffer is expected to be big-endian, unlike read_u32() - * of portable_io.h which expects little endian. - * - * \return The 32 bit number given by \a vp. - */ -uint32_t read_ssh_u32(const void *vp) -{ - const unsigned char *p = (const unsigned char *)vp; - uint32_t v; - - v = (uint32_t)p[0] << 24; - v |= (uint32_t)p[1] << 16; - v |= (uint32_t)p[2] << 8; - v |= (uint32_t)p[3]; - - return v; -} - -/** - * Sanity checks for the header of an ssh key. - * - * \param blob The buffer. - * \param blen The number of bytes of \a blob. - * - * This performs some checks to make sure we really have an ssh key. It also - * computes the offset in bytes of the start of the key values (modulus, - * exponent..). +/* + * Perform some sanity checks on the decoded ssh key. * - * \return The number of bytes to skip until the start of the first encoded - * number (usually 11). + * This function returns the size of the header. Usually, the header is 11 + * bytes long: four bytes for the length field, and the string "ssh-rsa". */ -int check_ssh_key_header(const unsigned char *blob, int blen) +static int check_ssh_key_header(const unsigned char *blob, int blen) { const unsigned char *p = blob, *end = blob + blen; uint32_t rlen; if (p + 4 > end) return -E_SSH_KEY_HEADER; - rlen = read_ssh_u32(p); + rlen = read_u32_be(p); p += 4; if (p + rlen < p) return -E_SSH_KEY_HEADER; @@ -102,6 +68,51 @@ int check_ssh_key_header(const unsigned char *blob, int blen) return 4 + rlen; } +/** + * Perform sanity checks and base64-decode an ssh-rsa key. + * + * \param filename The public key file (usually id_rsa.pub). + * \param blob Pointer to base64-decoded blob is returned here. + * \param decoded_size The size of the decoded blob. + * + * The memory pointed at by the returned blob pointer has to be freed by the + * caller. + * + * \return On success, the offset in bytes of the start of the key values + * (modulus, exponent..). This is the number of bytes to skip from the blob + * until the start of the first encoded number. On failure, a negative error + * code is returned. + * + * \sa \ref uudecode(). + */ +int decode_public_key(const char *filename, unsigned char **blob, + size_t *decoded_size) +{ + int ret, ret2; + void *map; + size_t map_size; + + ret = mmap_full_file(filename, O_RDONLY, &map, &map_size, NULL); + if (ret < 0) + return ret; + ret = is_ssh_rsa_key(map, map_size); + if (ret == 0) { + ret = -E_SSH_PARSE; + goto unmap; + } + ret = uudecode(map + ret, map_size - ret, (char **)blob, decoded_size); + if (ret < 0) + goto unmap; + ret = check_ssh_key_header(*blob, *decoded_size); + if (ret < 0) + goto unmap; +unmap: + ret2 = para_munmap(map, map_size); + if (ret >= 0 && ret2 < 0) + ret = ret2; + return ret; +} + /** * Check existence and permissions of a private key file. * @@ -124,7 +135,7 @@ int check_private_key_file(const char *file) return 1; } -void hash_to_asc(unsigned char *hash, char *asc) +void hash_to_asc(const unsigned char *hash, char *asc) { int i; const char hexchar[] = "0123456789abcdef"; @@ -136,7 +147,7 @@ void hash_to_asc(unsigned char *hash, char *asc) asc[2 * HASH_SIZE] = '\0'; } -int hash_compare(unsigned char *h1, unsigned char *h2) +int hash_compare(const unsigned char *h1, const unsigned char *h2) { int i; @@ -148,3 +159,161 @@ 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; +} + +/** 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-----" + +/** + * Decode an openssh-v1 (aka RFC4716) or PEM (aka ASN.1) private key. + * + * \param key_file The private key file (usually id_rsa). + * \param result Pointer to base64-decoded blob is returned here. + * \param blob_size The size of the decoded blob. + * + * This only checks header and footer and base64-decodes the part in between. + * No attempt to read the decoded part is made. + * + * \return Negative on errors, PKT_PEM or PKT_OPENSSH on success, indicating + * the type of key. + */ +int decode_private_key(const char *key_file, unsigned char **result, + size_t *blob_size) +{ + int ret, ret2, i, j, key_type; + void *map; + size_t map_size, key_size; + unsigned char *blob = NULL; + char *begin, *footer, *key; + + ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL); + if (ret < 0) + goto out; + ret = -E_KEY_MARKER; + 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; + if (!footer) + goto unmap; + /* skip whitespace at the beginning */ + for (; begin < footer; begin++) { + if (para_isspace(*begin)) + continue; + break; + } + ret = -E_KEY_MARKER; + if (begin >= footer) + goto unmap; + + key_size = footer - begin; + key = para_malloc(key_size + 1); + for (i = 0, j = 0; begin + i < footer; i++) { + if (para_isspace(begin[i])) + continue; + key[j++] = begin[i]; + } + 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) + ret = ret2; + if (ret < 0) { + free(blob); + blob = NULL; + } +out: + *result = blob; + return ret; +} +