X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=crypt_common.c;h=ff24e356ab8d837f8d59d67a3c983d5264376db1;hb=62c0894fbb589dd45e69b7d9ef1fd152a9960d62;hp=1308af4148851246fbf328c2d4037a44dc43ac39;hpb=6a7393e2abdcf30dd4201b84a417d08f6136e8d6;p=paraslash.git diff --git a/crypt_common.c b/crypt_common.c index 1308af41..ff24e356 100644 --- a/crypt_common.c +++ b/crypt_common.c @@ -1,32 +1,28 @@ -/* - * Copyright (C) 2005-2011 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 the implementation. */ +/** \file crypt_common.c Crypto functions independent of openssl/libgcrypt. */ #include -#include #include "para.h" #include "error.h" #include "string.h" -#include "crypt_backend.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; @@ -46,181 +42,19 @@ size_t is_ssh_rsa_key(char *data, size_t size) } /* - * This base64/uudecode stuff below is taken from openssh-5.2p1, Copyright (c) - * 1996 by Internet Software Consortium. Portions Copyright (c) 1995 by - * International Business Machines, Inc. - */ - -static const char Base64[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static const char Pad64 = '='; -/* - * Skips all whitespace anywhere. Converts characters, four at a time, starting - * at (or after) src from base - 64 numbers into three 8 bit bytes in the - * target area. it returns the number of data bytes stored at the target, or -1 - * on error. - */ -static int base64_decode(char const *src, unsigned char *target, size_t targsize) -{ - unsigned int tarindex, state; - int ch; - char *pos; - - state = 0; - tarindex = 0; - - while ((ch = *src++) != '\0') { - if (para_isspace(ch)) /* Skip whitespace anywhere. */ - continue; - - if (ch == Pad64) - break; - - pos = strchr(Base64, ch); - if (pos == 0) /* A non-base64 character. */ - return -1; - - switch (state) { - case 0: - if (target) { - if (tarindex >= targsize) - return (-1); - target[tarindex] = (pos - Base64) << 2; - } - state = 1; - break; - case 1: - if (target) { - if (tarindex + 1 >= targsize) - return (-1); - target[tarindex] |= (pos - Base64) >> 4; - target[tarindex+1] = ((pos - Base64) & 0x0f) - << 4 ; - } - tarindex++; - state = 2; - break; - case 2: - if (target) { - if (tarindex + 1 >= targsize) - return (-1); - target[tarindex] |= (pos - Base64) >> 2; - target[tarindex+1] = ((pos - Base64) & 0x03) - << 6; - } - tarindex++; - state = 3; - break; - case 3: - if (target) { - if (tarindex >= targsize) - return (-1); - target[tarindex] |= (pos - Base64); - } - tarindex++; - state = 0; - break; - } - } - - /* - * We are done decoding Base-64 chars. Let's see if we ended - * on a byte boundary, and/or with erroneous trailing characters. - */ - - if (ch == Pad64) { /* We got a pad char. */ - ch = *src++; /* Skip it, get next. */ - switch (state) { - case 0: /* Invalid = in first position */ - case 1: /* Invalid = in second position */ - return (-1); - - case 2: /* Valid, means one byte of info */ - /* Skip any number of spaces. */ - for (; ch != '\0'; ch = *src++) - if (!isspace(ch)) - break; - /* Make sure there is another trailing = sign. */ - if (ch != Pad64) - return (-1); - ch = *src++; /* Skip the = */ - /* Fall through to "single trailing =" case. */ - /* FALLTHROUGH */ - - case 3: /* Valid, means two bytes of info */ - /* - * We know this char is an =. Is there anything but - * whitespace after it? - */ - for (; ch != '\0'; ch = *src++) - if (!isspace(ch)) - return (-1); - - /* - * Now make sure for cases 2 and 3 that the "extra" - * bits that slopped past the last full byte were - * zeros. If we don't check them, they become a - * subliminal channel. - */ - if (target && target[tarindex] != 0) - return (-1); - } - } else { - /* - * We ended by seeing the end of the string. Make sure we - * have no partial bytes lying around. - */ - if (state != 0) - return (-1); - } - - return (tarindex); -} - -int uudecode(const char *src, unsigned char *target, size_t targsize) -{ - int len; - char *encoded, *p; - - /* copy the 'readonly' source */ - encoded = para_strdup(src); - /* skip whitespace and data */ - for (p = encoded; *p == ' ' || *p == '\t'; p++) - ; - for (; *p != '\0' && *p != ' ' && *p != '\t'; p++) - ; - /* and remove trailing whitespace because base64_decode needs this */ - *p = '\0'; - len = base64_decode(encoded, target, targsize); - free(encoded); - return len >= 0? len : -E_BASE64; -} - -/* - * Can not use the inline functions of portable_io.h here because the byte - * order is different. + * Perform some sanity checks on the decoded ssh key. + * + * 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". */ -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; -} - -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; @@ -228,36 +62,80 @@ int check_ssh_key_header(const unsigned char *blob, int blen) return -E_SSH_KEY_HEADER; if (rlen < strlen(KEY_TYPE_TXT)) return -E_SSH_KEY_HEADER; - PARA_DEBUG_LOG("type: %s, rlen: %d\n", p, rlen); + PARA_DEBUG_LOG("type: %s, rlen: %u\n", p, rlen); if (strncmp((char *)p, KEY_TYPE_TXT, strlen(KEY_TYPE_TXT))) return -E_SSH_KEY_HEADER; return 4 + rlen; } -int check_key_file(const char *file, bool private_key) +/** + * 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. + * + * \param file The path of the key file. + * + * This checks whether the file exists and its permissions are restrictive + * enough. It is considered an error if we own the file and it is readable for + * others. + * + * \return Standard. + */ +int check_private_key_file(const char *file) { struct stat st; if (stat(file, &st) != 0) return -ERRNO_TO_PARA_ERROR(errno); - if (!private_key) - return 0; if ((st.st_uid == getuid()) && (st.st_mode & 077) != 0) return -E_KEY_PERM; return 1; } -/** - * Convert a hash value to ascii format. - * - * \param hash the hash value. - * \param asc Result pointer. - * - * \a asc must point to an area of at least 2 * \p HASH_SIZE + 1 bytes which - * will be filled by the function with the ascii representation of the hash - * value given by \a hash, and a terminating \p NULL byte. - */ -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"; @@ -269,16 +147,7 @@ void hash_to_asc(unsigned char *hash, char *asc) asc[2 * HASH_SIZE] = '\0'; } -/** - * Compare two hashes. - * - * \param h1 Pointer to the first hash value. - * \param h2 Pointer to the second hash value. - * - * \return 1, -1, or zero, depending on whether \a h1 is greater than, - * less than or equal to h2, respectively. - */ -int hash_compare(unsigned char *h1, unsigned char *h2) +int hash_compare(const unsigned char *h1, const unsigned char *h2) { int i; @@ -292,60 +161,159 @@ int hash_compare(unsigned char *h1, unsigned char *h2) } /** - * Receive a buffer, decrypt it and write terminating NULL byte. + * Check header of an openssh private key and compute bignum offset. * - * \param scc The context. - * \param buf The buffer to write the decrypted data to. - * \param size The size of \a buf. + * \param data The base64-decoded key. + * \param len The size of the decoded key. * - * Read at most \a size - 1 bytes from file descriptor given by \a scc, decrypt - * the received data and write a NULL byte at the end of the decrypted data. + * Several assumptions are made about the key. Most notably, we only support + * single unencrypted keys without comments. * - * \return The return value of the underlying call to \ref - * sc_recv_bin_buffer(). + * \return The offset at which the first bignum of the private key (the public + * exponent n) starts. Negative error code on failure. */ -int sc_recv_buffer(struct stream_cipher_context *scc, char *buf, size_t size) +int find_openssh_bignum_offset(const unsigned char *data, int len) { - int n; - - assert(size); - n = sc_recv_bin_buffer(scc, buf, size - 1); - if (n >= 0) - buf[n] = '\0'; - else - *buf = '\0'; - return n; + /* + * 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; } -/** - * Encrypt and send a \p NULL-terminated buffer. - * - * \param scc The context. - * \param buf The buffer to send. - * - * \return The return value of the underyling call to sc_send_bin_buffer(). - */ -int sc_send_buffer(struct stream_cipher_context *scc, const char *buf) -{ - return sc_send_bin_buffer(scc, buf, strlen(buf)); -} +/** 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-----" /** - * Format, encrypt and send a buffer. + * 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. * - * \param scc The context. - * \param fmt A format string. + * This only checks header and footer and base64-decodes the part in between. + * No attempt to read the decoded part is made. * - * \return The return value of the underyling call to sc_send_buffer(). + * \return Negative on errors, PKT_PEM or PKT_OPENSSH on success, indicating + * the type of key. */ -__printf_2_3 int sc_send_va_buffer(struct stream_cipher_context *scc, - const char *fmt, ...) +int decode_private_key(const char *key_file, unsigned char **result, + size_t *blob_size) { - char *msg; - int ret; - - PARA_VSPRINTF(fmt, msg); - ret = sc_send_buffer(scc, msg); - free(msg); + 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; } +