+
+/**
+ * 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;
+}
+