/* SPDX-License-Identifier: GPL-2.0 */ /** \file crypt_common.c Crypto functions independent of openssl/libgcrypt. */ #include "para.h" #include "error.h" #include "string.h" #include "crypt.h" #include "crypt_backend.h" #include "portable_io.h" #include "fd.h" static const unsigned char base64_tab[256] = { 255, 255, 255, 255, 255, 255, 255, 255, /* 00-07 */ 255, 255, 255, 255, 255, 255, 255, 255, /* 08-0f */ 255, 255, 255, 255, 255, 255, 255, 255, /* 10-17 */ 255, 255, 255, 255, 255, 255, 255, 255, /* 18-1f */ 255, 255, 255, 255, 255, 255, 255, 255, /* 20-2f */ 255, 255, 255, 62, 255, 255, 255, 63, /* 28-2f */ 52 , 53, 54, 55, 56, 57, 58, 59, /* 30-37 */ 60 , 61, 255, 255, 255, 255, 255, 255, /* 38-3f */ 255, 0, 1, 2, 3, 4, 5, 6, /* 40-47 */ 7 , 8, 9, 10, 11, 12, 13, 14, /* 48-4f */ 15 , 16, 17, 18, 19, 20, 21, 22, /* 50-57 */ 23 , 24, 25, 255, 255, 255, 255, 255, /* 58-5f */ 255, 26, 27, 28, 29, 30, 31, 32, /* 60-6f */ 33 , 34, 35, 36, 37, 38, 39, 40, /* 68-6f */ 41 , 42, 43, 44, 45, 46, 47, 48, /* 70-77 */ 49 , 50, 51, 255, 255, 255, 255, 255, /* 78-7f */ 255, 255, 255, 255, 255, 255, 255, 255, /* 80-87 */ 255, 255, 255, 255, 255, 255, 255, 255, /* 88-8f */ 255, 255, 255, 255, 255, 255, 255, 255, /* 90-97 */ 255, 255, 255, 255, 255, 255, 255, 255, /* 98-9f */ 255, 255, 255, 255, 255, 255, 255, 255, /* a0-a7 */ 255, 255, 255, 255, 255, 255, 255, 255, /* a8-af */ 255, 255, 255, 255, 255, 255, 255, 255, /* b0-b7 */ 255, 255, 255, 255, 255, 255, 255, 255, /* b8-bf */ 255, 255, 255, 255, 255, 255, 255, 255, /* c0-c7 */ 255, 255, 255, 255, 255, 255, 255, 255, /* c8-cf */ 255, 255, 255, 255, 255, 255, 255, 255, /* d0-d7 */ 255, 255, 255, 255, 255, 255, 255, 255, /* d8-df */ 255, 255, 255, 255, 255, 255, 255, 255, /* e0-e7 */ 255, 255, 255, 255, 255, 255, 255, 255, /* e8-ef */ 255, 255, 255, 255, 255, 255, 255, 255, /* f0-f7 */ 255, 255, 255, 255, 255, 255, 255, 255, /* f8-ff */ }; /** Maximal possible size of the decoded data. */ #define BASE64_MAX_DECODED_SIZE(_encoded_size) ((_encoded_size) / 4 * 3) /* * The padding character which is appended to base64 encoded data. * * base64 encoded data is always an ascii string whose length is a multiple of * four. If the number of characters needed to encode the data is not a * multiple of four, the encoded string is padded with this character. The * number of pad characters is either 0, 1 or 2, but never 3, and this number * is a function of the number of bytes of the unencrypted data modulo 3. */ #define PAD64 '=' /* * This function is derived from openssh-5.2p1, Copyright (c) 1996 by Internet * Software Consortium. Portions Copyright (c) 1995 by International Business * Machines, Inc. * * The function skips all whitespace anywhere and converts four characters * at a time. It is OK to pass a NULL pointer as decoded_size. The result * is terminated with a NUL byte. */ static int base64_decode(char const *src, size_t encoded_size, char **result, size_t *decoded_size) { size_t i, j, state; /* source/target indices */ const char *end = src + encoded_size, *p; unsigned char *target, uch; target = alloc(BASE64_MAX_DECODED_SIZE(encoded_size) + 1); for ( i = 0, j = 0, state = 0; i < encoded_size && (uch = src[i]) != '\0'; i++ ) { if (para_isspace(uch)) /* Skip whitespace anywhere. */ continue; if (uch == PAD64) break; if (base64_tab[uch] == 255) /* A non-base64 character. */ goto fail; uch = base64_tab[uch]; switch (state) { case 0: target[j] = uch << 2; break; case 1: target[j] |= uch >> 4; j++; target[j] = (uch & 0x0f) << 4; break; case 2: target[j] |= uch >> 2; j++; target[j] = (uch & 0x03) << 6; break; case 3: target[j] |= uch; j++; break; } state = (state + 1) % 4; } p = (i < encoded_size)? src + i : NULL; /* * We are done decoding Base-64 chars. Let's see if we ended * on a byte boundary, and/or with erroneous trailing characters. */ if (p && *p == PAD64) { /* We got a pad char. Skip it, get next. */ p++; switch (state) { case 0: /* Invalid = in first position */ case 1: /* Invalid = in second position */ goto fail; case 2: /* Valid, means one byte of info */ /* Skip any number of spaces. */ for (; p < end && *p != '\0'; p++) if (!para_isspace(*p)) break; /* Make sure there is another trailing = sign. */ if (*p != PAD64) goto fail; /* Fall through to "single trailing =" case. */ p++; case 3: /* Valid, means two bytes of info */ /* * We know this char is an =. Is there anything but * whitespace after it? */ for (; p < end && *p != '\0'; p++) if (!para_isspace(*p)) goto fail; /* * 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[j] != 0) goto fail; } } else { /* * We ended by seeing the end of the string. Make sure we * have no partial bytes lying around. */ if (state != 0) goto fail; } /* success */ target[j] = '\0'; /* just to be sure */ if (decoded_size) *decoded_size = j; *result = (char *)target; return 1; fail: free(target); return -E_BASE64; } /* Decode a buffer using the uuencode Base64 algorithm. */ static int uudecode(char const *src, size_t encoded_size, char **result, size_t *decoded_size) { const char *end = src + encoded_size, *p; /* skip whitespace and data */ for (p = src; p < end && (*p == ' ' || *p == '\t'); p++) ; for (; p < end && *p != '\0' && *p != ' ' && *p != '\t'; p++) ; /* and remove trailing whitespace because base64_decode needs this */ return base64_decode(src, p - src, result, decoded_size); } /** If the key begins with this text, we treat it as an ssh key. */ #define KEY_TYPE_TXT "ssh-rsa" /* * Check if the given buffer starts with an ssh rsa key signature. * * Returns number of header bytes to be skipped on success, zero if no ssh rsa * signature was found. */ static size_t is_ssh_rsa_key(char *data, size_t size) { char *cp; if (size < strlen(KEY_TYPE_TXT) + 2) return 0; cp = memchr(data, ' ', size); if (cp == NULL) return 0; if (strncmp(KEY_TYPE_TXT, data, strlen(KEY_TYPE_TXT))) return 0; cp++; if (cp >= data + size) return 0; if (*cp == '\0') return 0; return cp - data; } /* * 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". */ 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_u32_be(p); p += 4; if (p + rlen < p) return -E_SSH_KEY_HEADER; if (p + rlen > end) return -E_SSH_KEY_HEADER; if (rlen < strlen(KEY_TYPE_TXT)) return -E_SSH_KEY_HEADER; 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; } /** * 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. */ 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); 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 ((st.st_uid == getuid()) && (st.st_mode & 077) != 0) return -E_KEY_PERM; return 1; } void hash_to_asc(const unsigned char *hash, char *asc) { int i; const char hexchar[] = "0123456789abcdef"; for (i = 0; i < HASH_SIZE; i++) { asc[2 * i] = hexchar[hash[i] >> 4]; asc[2 * i + 1] = hexchar[hash[i] & 0xf]; } asc[2 * HASH_SIZE] = '\0'; } int hash_compare(const unsigned char *h1, const unsigned char *h2) { int i; for (i = 0; i < HASH_SIZE; i++) { if (h1[i] < h2[i]) return -1; if (h1[i] > h2[i]) return 1; } 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 OPENSSH keys (RFC4716) start with this header. */ #define PRIVATE_OPENSSH_KEY_HEADER "-----BEGIN OPENSSH 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) 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 Standard. */ int decode_private_key(const char *key_file, unsigned char **result, size_t *blob_size) { int ret, ret2, i, j; 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_OPENSSH_KEY_HEADER, strlen(PRIVATE_OPENSSH_KEY_HEADER)) == 0) { 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 = alloc(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 = 1; 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; }