}
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;
+}
+
#include "crypt_backend.h"
#include "fd.h"
#include "base64.h"
+#include "portable_io.h"
//#define GCRYPT_DEBUG 1
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;
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))
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)
* 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;
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;
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;
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;
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;