Files
data-lite-c/Sources/DataLiteC/libtomcrypt/misc/pem/pem_ssh.c
2025-10-24 19:33:21 +03:00

864 lines
27 KiB
C

/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
/* SPDX-License-Identifier: Unlicense */
#include "tomcrypt_private.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
/**
@file pem_ssh.c
SSH specific functionality to process PEM files, Steffen Jaeckel
The basic format of the key is described here:
https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
*/
#if defined(LTC_PEM_SSH)
/* Table as of
* https://www.iana.org/assignments/ssh-parameters/ssh-parameters.xhtml#ssh-parameters-17
*/
const struct blockcipher_info ssh_ciphers[] =
{
{ .name = "none", .algo = "", .keylen = 0, .mode = cm_none },
{ .name = "aes128-cbc", .algo = "aes", .keylen = 128 / 8, .mode = cm_cbc },
{ .name = "aes128-ctr", .algo = "aes", .keylen = 128 / 8, .mode = cm_ctr },
{ .name = "aes192-cbc", .algo = "aes", .keylen = 192 / 8, .mode = cm_cbc },
{ .name = "aes192-ctr", .algo = "aes", .keylen = 192 / 8, .mode = cm_ctr },
{ .name = "aes256-cbc", .algo = "aes", .keylen = 256 / 8, .mode = cm_cbc },
{ .name = "aes256-ctr", .algo = "aes", .keylen = 256 / 8, .mode = cm_ctr },
{ .name = "aes128-gcm@openssh.com", .algo = "aes", .keylen = 128 / 8, .mode = cm_gcm },
{ .name = "aes256-gcm@openssh.com", .algo = "aes", .keylen = 256 / 8, .mode = cm_gcm },
{ .name = "blowfish128-cbc", .algo = "blowfish", .keylen = 128 / 8, .mode = cm_cbc },
{ .name = "blowfish128-ctr", .algo = "blowfish", .keylen = 128 / 8, .mode = cm_ctr },
/* The algo name doesn't matter, it's only used in pem-info */
{ .name = "chacha20-poly1305@openssh.com", .algo = "c20p1305", .keylen = 256 / 8, .mode = cm_stream | cm_openssh },
{ .name = "des-cbc", .algo = "des", .keylen = 64 / 8, .mode = cm_cbc },
{ .name = "3des-cbc", .algo = "3des", .keylen = 192 / 8, .mode = cm_cbc },
{ .name = "3des-ctr", .algo = "3des", .keylen = 192 / 8, .mode = cm_ctr },
{ .name = "serpent128-cbc", .algo = "serpent", .keylen = 128 / 8, .mode = cm_cbc },
{ .name = "serpent128-ctr", .algo = "serpent", .keylen = 128 / 8, .mode = cm_ctr },
{ .name = "serpent192-cbc", .algo = "serpent", .keylen = 192 / 8, .mode = cm_cbc },
{ .name = "serpent192-ctr", .algo = "serpent", .keylen = 192 / 8, .mode = cm_ctr },
{ .name = "serpent256-cbc", .algo = "serpent", .keylen = 256 / 8, .mode = cm_cbc },
{ .name = "serpent256-ctr", .algo = "serpent", .keylen = 256 / 8, .mode = cm_ctr },
{ .name = "twofish128-cbc", .algo = "twofish", .keylen = 128 / 8, .mode = cm_cbc },
{ .name = "twofish128-ctr", .algo = "twofish", .keylen = 128 / 8, .mode = cm_ctr },
{ .name = "twofish192-cbc", .algo = "twofish", .keylen = 192 / 8, .mode = cm_cbc },
{ .name = "twofish192-ctr", .algo = "twofish", .keylen = 192 / 8, .mode = cm_ctr },
{ .name = "twofish-cbc", .algo = "twofish", .keylen = 256 / 8, .mode = cm_cbc },
{ .name = "twofish256-cbc", .algo = "twofish", .keylen = 256 / 8, .mode = cm_cbc },
{ .name = "twofish256-ctr", .algo = "twofish", .keylen = 256 / 8, .mode = cm_ctr },
};
const unsigned long ssh_ciphers_num = LTC_ARRAY_SIZE(ssh_ciphers);
struct kdf_options {
const char *name;
const struct blockcipher_info *cipher;
unsigned char salt[64];
unsigned long saltlen;
ulong32 num_rounds;
struct password pw;
};
#ifdef LTC_MECC
static int s_ssh_find_ecc(const char *pka, const ltc_ecc_curve **curve)
{
int err;
const char* prefix = "ecdsa-sha2-";
unsigned long prefixlen = XSTRLEN(prefix);
if (strstr(pka, prefix) == NULL) return CRYPT_PK_INVALID_TYPE;
if ((err = ecc_find_curve(pka + prefixlen, curve)) != CRYPT_OK) return err;
return CRYPT_OK;
}
static int s_ssh_find_init_ecc(const char *pka, ltc_pka_key *key)
{
int err;
const ltc_ecc_curve *cu;
if ((err = s_ssh_find_ecc(pka, &cu)) != CRYPT_OK) return err;
return ecc_set_curve(cu, &key->u.ecc);
}
static int s_ssh_decode_ecdsa(const unsigned char *in, unsigned long *inlen, ltc_pka_key *pka_key, enum pem_flags type)
{
int err;
unsigned char groupname[64], buf0[512], buf1[512];
unsigned long groupnamelen = sizeof(groupname), buf0len = sizeof(buf0), buf1len = sizeof(buf1);
unsigned long remaining, cur_len, keylen;
const unsigned char *p, *key;
p = in;
cur_len = *inlen;
remaining = *inlen;
err = ssh_decode_sequence_multi(p, &cur_len,
LTC_SSHDATA_STRING, groupname, &groupnamelen,
LTC_SSHDATA_STRING, buf0, &buf0len,
LTC_SSHDATA_STRING, buf1, &buf1len,
LTC_SSHDATA_EOL, LTC_NULL);
if (err == CRYPT_OK) {
key = buf1;
keylen = buf1len;
} else if (err == CRYPT_BUFFER_OVERFLOW && buf0len != sizeof(buf0) && buf1len == sizeof(buf1)) {
key = buf0;
keylen = buf0len;
} else {
goto cleanup;
}
remaining -= cur_len;
cur_len = remaining;
err = ecc_set_key(key, keylen, type == pf_public ? PK_PUBLIC : PK_PRIVATE, &pka_key->u.ecc);
cleanup:
zeromem(groupname, sizeof(groupname));
zeromem(buf0, sizeof(buf0));
zeromem(buf1, sizeof(buf1));
if (err == CRYPT_OK) {
pka_key->id = LTC_PKA_EC;
*inlen -= remaining;
}
return err;
}
#endif
#ifdef LTC_CURVE25519
static int s_ssh_decode_ed25519(const unsigned char *in, unsigned long *inlen, ltc_pka_key *key, enum pem_flags type)
{
int err;
unsigned char pubkey[64], privkey[96];
unsigned long pubkeylen = sizeof(pubkey), privkeylen = sizeof(privkey);
unsigned long remaining, cur_len;
const unsigned char *p;
p = in;
cur_len = *inlen;
remaining = *inlen;
if ((err = ssh_decode_sequence_multi(p, &cur_len,
LTC_SSHDATA_STRING, pubkey, &pubkeylen,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
goto cleanup;
}
if (type == pf_public) {
if ((err = ed25519_import_raw(pubkey, pubkeylen, PK_PUBLIC, &key->u.ed25519)) != CRYPT_OK) {
goto cleanup;
}
key->id = LTC_PKA_ED25519;
goto cleanup;
}
p += cur_len;
remaining -= cur_len;
cur_len = remaining;
if ((err = ssh_decode_sequence_multi(p, &cur_len,
LTC_SSHDATA_STRING, privkey, &privkeylen,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
goto cleanup;
}
if ((err = ed25519_import_raw(privkey, privkeylen, PK_PRIVATE, &key->u.ed25519)) != CRYPT_OK) {
goto cleanup;
}
key->id = LTC_PKA_ED25519;
cleanup:
zeromem(pubkey, sizeof(pubkey));
zeromem(privkey, sizeof(privkey));
if (err == CRYPT_OK) {
remaining -= cur_len;
*inlen -= remaining;
}
return err;
}
#endif
#ifdef LTC_MRSA
static int s_ssh_decode_dsa(const unsigned char *in, unsigned long *inlen, ltc_pka_key *key, enum pem_flags type)
{
int err, stat;
unsigned long remaining, cur_len;
const unsigned char *p;
if ((err = dsa_int_init(&key->u.dsa)) != CRYPT_OK) {
return err;
}
p = in;
cur_len = *inlen;
remaining = *inlen;
if ((err = ssh_decode_sequence_multi(p, &cur_len,
LTC_SSHDATA_MPINT, key->u.dsa.p,
LTC_SSHDATA_MPINT, key->u.dsa.q,
LTC_SSHDATA_MPINT, key->u.dsa.g,
LTC_SSHDATA_MPINT, key->u.dsa.y,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
goto cleanup;
}
key->u.dsa.qord = ltc_mp_unsigned_bin_size(key->u.dsa.q);
if ((err = dsa_int_validate_pqg(&key->u.dsa, &stat)) != CRYPT_OK) {
goto cleanup;
}
if (stat == 0) {
err = CRYPT_INVALID_PACKET;
goto cleanup;
}
if (type == pf_public) {
key->id = LTC_PKA_DSA;
key->u.dsa.type = PK_PUBLIC;
goto cleanup;
}
p += cur_len;
remaining -= cur_len;
cur_len = remaining;
if ((err = ssh_decode_sequence_multi(p, &cur_len,
LTC_SSHDATA_MPINT, key->u.dsa.x,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
goto cleanup;
}
key->id = LTC_PKA_DSA;
key->u.dsa.type = PK_PRIVATE;
cleanup:
if (err != CRYPT_OK) {
dsa_free(&key->u.dsa);
} else {
remaining -= cur_len;
*inlen -= remaining;
}
return err;
}
#endif
#ifdef LTC_MRSA
static int s_ssh_decode_rsa(const unsigned char *in, unsigned long *inlen, ltc_pka_key *key, enum pem_flags type)
{
int err;
void *tmp1, *tmp2;
unsigned long remaining, cur_len;
const unsigned char *p;
if ((err = rsa_init(&key->u.rsa)) != CRYPT_OK) {
return err;
}
p = in;
cur_len = *inlen;
remaining = *inlen;
/* ssh-rsa public and private keys contain `e` and `N` in a different order
* public contains `e`, then `N`
* private contains `N`, then `e`
* change the order later on if we import a public key */
if ((err = ssh_decode_sequence_multi(p, &cur_len,
LTC_SSHDATA_MPINT, key->u.rsa.N,
LTC_SSHDATA_MPINT, key->u.rsa.e,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
goto cleanup;
}
p += cur_len;
remaining -= cur_len;
cur_len = remaining;
if (type == pf_public) {
/* c.f. comment above */
void *exch = key->u.rsa.N;
key->u.rsa.N = key->u.rsa.e;
key->u.rsa.e = exch;
key->id = LTC_PKA_RSA;
key->u.rsa.type = PK_PUBLIC;
*inlen -= remaining;
goto cleanup;
}
if ((err = ltc_mp_init_multi(&tmp1, &tmp2, LTC_NULL)) != CRYPT_OK) {
goto cleanup;
}
if ((err = ssh_decode_sequence_multi(p, &cur_len,
LTC_SSHDATA_MPINT, key->u.rsa.d,
LTC_SSHDATA_MPINT, key->u.rsa.qP,
LTC_SSHDATA_MPINT, key->u.rsa.p,
LTC_SSHDATA_MPINT, key->u.rsa.q,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
goto cleanup_tmps;
}
if ((err = ltc_mp_sub_d(key->u.rsa.p, 1, tmp1)) != CRYPT_OK) { goto cleanup_tmps; } /* tmp1 = q-1 */
if ((err = ltc_mp_sub_d(key->u.rsa.q, 1, tmp2)) != CRYPT_OK) { goto cleanup_tmps; } /* tmp2 = p-1 */
if ((err = ltc_mp_mod(key->u.rsa.d, tmp1, key->u.rsa.dP)) != CRYPT_OK) { goto cleanup_tmps; } /* dP = d mod p-1 */
if ((err = ltc_mp_mod(key->u.rsa.d, tmp2, key->u.rsa.dQ)) != CRYPT_OK) { goto cleanup_tmps; } /* dQ = d mod q-1 */
key->id = LTC_PKA_RSA;
key->u.rsa.type = PK_PRIVATE;
cleanup_tmps:
ltc_mp_deinit_multi(tmp2, tmp1, LTC_NULL);
cleanup:
if (err != CRYPT_OK) {
rsa_free(&key->u.rsa);
} else {
remaining -= cur_len;
*inlen -= remaining;
}
return err;
}
#endif
struct ssh_pka {
struct str name;
enum ltc_pka_id id;
int (*find)(const char*, const ltc_ecc_curve **);
int (*init)(const char*, ltc_pka_key*);
int (*decode)(const unsigned char*, unsigned long*, ltc_pka_key*, enum pem_flags);
};
struct ssh_pka ssh_pkas[] = {
#ifdef LTC_CURVE25519
{ SET_CSTR(.name, "ssh-ed25519"),
LTC_PKA_ED25519,
NULL,
NULL,
s_ssh_decode_ed25519 },
#endif
#ifdef LTC_MRSA
{ SET_CSTR(.name, "ssh-rsa"),
LTC_PKA_RSA,
NULL,
NULL,
s_ssh_decode_rsa },
#endif
#ifdef LTC_MDSA
{ SET_CSTR(.name, "ssh-dss"),
LTC_PKA_DSA,
NULL,
NULL,
s_ssh_decode_dsa },
#endif
#ifdef LTC_MECC
{ { NULL, 0 },
LTC_PKA_EC,
s_ssh_find_ecc,
s_ssh_find_init_ecc,
s_ssh_decode_ecdsa },
#endif
};
static int s_decode_key(const unsigned char *in, unsigned long *inlen, ltc_pka_key *key, char **comment, enum pem_flags type)
{
int err;
ulong32 check1, check2;
unsigned char pka[64];
unsigned long pkalen = sizeof(pka);
unsigned long remaining, cur_len;
const unsigned char *p;
unsigned long n;
LTC_ARGCHK(in != NULL);
LTC_ARGCHK(inlen != NULL);
LTC_ARGCHK(key != NULL);
p = in;
cur_len = *inlen;
remaining = *inlen;
if (type != pf_public) {
if ((err = ssh_decode_sequence_multi(p, &cur_len,
LTC_SSHDATA_UINT32, &check1,
LTC_SSHDATA_UINT32, &check2,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
return err;
}
if (check1 != check2) {
return CRYPT_INVALID_PACKET;
}
p += cur_len;
remaining -= cur_len;
cur_len = remaining;
}
if ((err = ssh_decode_sequence_multi(p, &cur_len,
LTC_SSHDATA_STRING, pka, &pkalen,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
return err;
}
p += cur_len;
remaining -= cur_len;
cur_len = remaining;
for (n = 0; n < LTC_ARRAY_SIZE(ssh_pkas); ++n) {
if (ssh_pkas[n].name.p != NULL) {
if (pkalen != ssh_pkas[n].name.len
|| XMEMCMP(pka, ssh_pkas[n].name.p, ssh_pkas[n].name.len) != 0) continue;
} else {
if ((ssh_pkas[n].init == NULL) ||
(ssh_pkas[n].init((char*)pka, key) != CRYPT_OK)) continue;
}
if ((err = ssh_pkas[n].decode(p, &cur_len, key, type)) != CRYPT_OK) {
return err;
}
break;
}
if (n == LTC_ARRAY_SIZE(ssh_pkas)) {
return CRYPT_PK_INVALID_TYPE;
}
p += cur_len;
remaining -= cur_len;
cur_len = remaining;
if (cur_len != 0 && comment) {
unsigned long commentlen = cur_len;
char *c = XMALLOC(commentlen);
if (c == NULL) {
return CRYPT_MEM;
}
if ((err = ssh_decode_sequence_multi(p, &cur_len,
LTC_SSHDATA_STRING, c, &commentlen,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
return err;
}
if (commentlen == 0) {
XFREE(c);
} else {
*comment = c;
}
}
p += cur_len;
remaining -= cur_len;
return remaining ? padding_depad(p, &remaining, LTC_PAD_SSH) : CRYPT_OK;
}
static LTC_INLINE void skip_spaces(char **r, unsigned long *l)
{
while(*l && (**r == ' ' || **r == '\t')) {
(*r)++;
(*l)--;
}
}
static LTC_INLINE void skip_chars(char **r, unsigned long *l)
{
while(*l && (**r != ' ' && **r != '\t')) {
(*l)--;
if (**r == '\n' || **r == '\r') {
*l = 0;
} else {
(*r)++;
}
}
}
static LTC_INLINE void skip_to_eol(char **r, unsigned long *l)
{
while(*l && (**r != '\n' && **r != '\r')) {
(*l)--;
(*r)++;
}
}
static int s_parse_line(char *line, unsigned long *len, ltc_pka_key *key, char **comment)
{
int err;
unsigned long n, rlen, olen;
enum authorized_keys_elements {
ake_algo_name = 0,
ake_b64_encoded_key = 1,
ake_comment = 2
};
struct str elements[3] = { 0 };
char *r = line;
unsigned char *buf = NULL;
rlen = *len;
/* Chop up string into the three authorized_keys_elements */
for (n = 0; n < LTC_ARRAY_SIZE(elements) && rlen; ++n) {
skip_spaces(&r, &rlen);
elements[n].p = r;
if (n != 2)
skip_chars(&r, &rlen);
else
skip_to_eol(&r, &rlen);
elements[n].len = r - elements[n].p;
*r = '\0';
r++;
}
for (n = 0; n < LTC_ARRAY_SIZE(ssh_pkas); ++n) {
if (ssh_pkas[n].name.p != NULL) {
if (elements[ake_algo_name].len != ssh_pkas[n].name.len
|| XMEMCMP(elements[ake_algo_name].p, ssh_pkas[n].name.p, ssh_pkas[n].name.len) != 0) continue;
} else {
if ((ssh_pkas[n].find == NULL) ||
(ssh_pkas[n].find(elements[ake_algo_name].p, NULL) != CRYPT_OK)) continue;
}
olen = elements[ake_b64_encoded_key].len;
buf = XMALLOC(olen);
if (buf == NULL) {
return CRYPT_MEM;
}
if ((err = base64_strict_decode(elements[ake_b64_encoded_key].p, elements[ake_b64_encoded_key].len, buf, &olen)) == CRYPT_OK) {
err = s_decode_key(buf, &olen, key, comment, pf_public);
if (err == CRYPT_OK && key->id != ssh_pkas[n].id) {
err = CRYPT_PK_INVALID_TYPE;
}
}
XFREE(buf);
if (err == CRYPT_OK) {
/* Only use the comment that was maybe in the text we just processed, in case when
* there was no comment inside the SSH key.
*/
if (*comment == NULL && elements[ake_comment].p) {
*comment = XMALLOC(elements[ake_comment].len + 1);
if (*comment == NULL) {
return CRYPT_MEM;
}
XMEMCPY(*comment, elements[ake_comment].p, elements[ake_comment].len);
(*comment)[elements[ake_comment].len] = '\0';
}
*len = r - line;
return CRYPT_OK;
}
}
return CRYPT_PK_INVALID_TYPE;
}
static int s_read_authorized_keys(const void *buf, unsigned long len, ssh_authorized_key_cb cb, void *ctx)
{
char *s;
int err;
unsigned long clen = len;
ltc_pka_key *key = XCALLOC(1, sizeof(*key));
char *comment = NULL;
void *cpy = XMALLOC(len);
if (key == NULL || cpy == NULL) {
if (cpy)
XFREE(cpy);
if (key)
XFREE(key);
return CRYPT_MEM;
}
XMEMCPY(cpy, buf, len);
s = cpy;
while (clen && (err = s_parse_line(s, &clen, key, &comment)) == CRYPT_OK) {
if (cb(key, comment, ctx)) {
break;
}
s += clen;
len -= clen;
clen = len;
key = XCALLOC(1, sizeof(*key));
if (key == NULL) {
err = CRYPT_MEM;
break;
}
if (comment) {
XFREE(comment);
comment = NULL;
}
}
if (comment)
XFREE(comment);
if (cpy)
XFREE(cpy);
if (key)
XFREE(key);
return err;
}
static int s_decrypt_private_keys(unsigned char *in, unsigned long *inlen,
unsigned char *tag, unsigned long taglen,
struct kdf_options *opts)
{
int err, cipher;
unsigned long symkey_len, iv_len;
unsigned char symkey[MAXBLOCKSIZE], *iv, iv_[8] = { 0 };
enum cipher_mode mode = opts->cipher->mode & cm_modes;
LTC_ARGCHK(in != NULL);
LTC_ARGCHK(inlen != NULL);
LTC_ARGCHK(opts != NULL);
if (mode != cm_stream) {
cipher = find_cipher(opts->cipher->algo);
if (cipher == -1) {
return CRYPT_INVALID_CIPHER;
}
iv = symkey + opts->cipher->keylen;
iv_len = mode == cm_gcm ? 12 : cipher_descriptor[cipher].block_length;
symkey_len = opts->cipher->keylen + iv_len;
} else {
iv = iv_;
iv_len = sizeof(iv_);
symkey_len = 64;
}
if (sizeof(symkey) < symkey_len) {
return CRYPT_OVERFLOW;
}
if ((err = bcrypt_pbkdf_openbsd(opts->pw.pw, opts->pw.l, opts->salt, opts->saltlen,
opts->num_rounds, find_hash("sha512"), symkey, &symkey_len)) != CRYPT_OK) {
return err;
}
err = pem_decrypt(in, inlen,
symkey, opts->cipher->keylen,
iv, iv_len,
tag, taglen,
opts->cipher, LTC_PAD_SSH);
zeromem(symkey, sizeof(symkey));
return err;
}
static int s_decode_header(unsigned char *in, unsigned long *inlen, struct kdf_options *opts)
{
int err;
unsigned char ciphername[64], kdfname[64], kdfoptions[128], pubkey1[2048];
unsigned long ciphernamelen = sizeof(ciphername), kdfnamelen = sizeof(kdfname);
unsigned long kdfoptionslen = sizeof(kdfoptions), pubkey1len = sizeof(pubkey1);
ulong32 num_keys;
unsigned long i;
void *magic = strstr((const char*)in, "openssh-key-v1");
unsigned long slen = XSTRLEN("openssh-key-v1");
unsigned char *start = &in[slen + 1];
unsigned long len = *inlen - slen - 1;
if (magic == NULL || magic != in) {
return CRYPT_INVALID_PACKET;
}
if ((err = ssh_decode_sequence_multi(start, &len,
LTC_SSHDATA_STRING, ciphername, &ciphernamelen,
LTC_SSHDATA_STRING, kdfname, &kdfnamelen,
LTC_SSHDATA_STRING, kdfoptions, &kdfoptionslen,
LTC_SSHDATA_UINT32, &num_keys,
LTC_SSHDATA_STRING, pubkey1, &pubkey1len,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
return err;
}
if (num_keys != 1) {
return CRYPT_INVALID_PACKET;
}
*inlen = len + slen + 1;
for (i = 0; i < ssh_ciphers_num; ++i) {
if (XSTRCMP((char*)ciphername, ssh_ciphers[i].name) == 0) {
opts->cipher = &ssh_ciphers[i];
break;
}
}
if (opts->cipher == NULL) {
return CRYPT_INVALID_CIPHER;
}
if (XSTRCMP((char*)kdfname, "none") == 0) {
/* NOP */
opts->name = "none";
} else if (XSTRCMP((char*)kdfname, "bcrypt") == 0) {
opts->name = "bcrypt";
opts->saltlen = sizeof(opts->salt);
len = kdfoptionslen;
if ((err = ssh_decode_sequence_multi(kdfoptions, &len,
LTC_SSHDATA_STRING, opts->salt, &opts->saltlen,
LTC_SSHDATA_UINT32, &opts->num_rounds,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
return err;
}
if (len != kdfoptionslen) {
return CRYPT_INPUT_TOO_LONG;
}
} else {
return CRYPT_INVALID_PACKET;
}
return err;
}
static const struct pem_header_id pem_openssh[] = {
{
SET_CSTR(.start, "-----BEGIN OPENSSH PRIVATE KEY-----"),
SET_CSTR(.end, "-----END OPENSSH PRIVATE KEY-----"),
.has_more_headers = no
},
{
SET_CSTR(.start, "---- BEGIN SSH2 PUBLIC KEY ----"),
SET_CSTR(.end, "---- END SSH2 PUBLIC KEY ----"),
.has_more_headers = maybe,
.flags = pf_public
},
};
static const unsigned long pem_openssh_num = LTC_ARRAY_SIZE(pem_openssh);
static int s_decode_openssh(struct get_char *g, ltc_pka_key *k, const password_ctx *pw_ctx)
{
unsigned char *pem = NULL, *p, *privkey = NULL, *tag;
unsigned long n, w, l, privkey_len, taglen;
int err;
struct pem_headers hdr;
struct kdf_options opts = { 0 };
XMEMSET(k, 0, sizeof(*k));
w = LTC_PEM_READ_BUFSIZE * 2;
retry:
pem = XREALLOC(pem, w);
for (n = 0; n < pem_openssh_num; ++n) {
hdr.id = &pem_openssh[n];
err = pem_read(pem, &w, &hdr, g);
if (err == CRYPT_BUFFER_OVERFLOW) {
goto retry;
} else if (err == CRYPT_OK) {
break;
} else if (err != CRYPT_UNKNOWN_PEM) {
goto cleanup;
}
hdr.id = NULL;
}
/* id not found */
if (hdr.id == NULL) {
goto cleanup;
}
p = pem;
l = w;
if (hdr.id->flags != pf_public) {
if ((err = s_decode_header(pem, &w, &opts)) != CRYPT_OK) {
goto cleanup;
}
p = pem + w;
l -= w;
w = l;
privkey_len = l;
privkey = XMALLOC(privkey_len);
if (privkey == NULL) {
return CRYPT_MEM;
}
if ((err = ssh_decode_sequence_multi(p, &w,
LTC_SSHDATA_STRING, privkey, &privkey_len,
LTC_SSHDATA_EOL, LTC_NULL)) != CRYPT_OK) {
goto cleanup;
}
if (XSTRCMP(opts.name, "none") != 0) {
if ((pw_ctx == NULL) || (pw_ctx->callback == NULL)) {
err = CRYPT_PW_CTX_MISSING;
goto cleanup;
}
if (pw_ctx->callback(&opts.pw.pw, &opts.pw.l, pw_ctx->userdata)) {
err = CRYPT_ERROR;
goto cleanup;
}
tag = p + w;
taglen = l - w;
w = privkey_len;
if ((err = s_decrypt_private_keys(privkey, &privkey_len,
tag, taglen,
&opts)) != CRYPT_OK) {
goto cleanup;
}
zeromem(opts.pw.pw, opts.pw.l);
}
p = privkey;
w = privkey_len;
}
if ((err = s_decode_key(p, &w, k, NULL, hdr.id->flags)) != CRYPT_OK) {
goto cleanup;
}
cleanup:
password_free(&opts.pw, pw_ctx);
if (privkey) {
zeromem(privkey, privkey_len);
XFREE(privkey);
}
XFREE(pem);
return err;
}
#ifndef LTC_NO_FILE
int pem_decode_openssh_filehandle(FILE *f, ltc_pka_key *k, const password_ctx *pw_ctx)
{
LTC_ARGCHK(f != NULL);
LTC_ARGCHK(k != NULL);
{
struct get_char g = { .get = pem_get_char_from_file, .data.f = f };
return s_decode_openssh(&g, k, pw_ctx);
}
}
int ssh_read_authorized_keys_filehandle(FILE *f, ssh_authorized_key_cb cb, void *ctx)
{
size_t tot_data;
void *buf;
int err;
LTC_ARGCHK(f != NULL);
LTC_ARGCHK(cb != NULL);
if (fseek(f, 0, SEEK_END) == -1)
return CRYPT_ERROR;
tot_data = ftell(f);
if (fseek(f, 0, SEEK_SET) == -1)
return CRYPT_ERROR;
buf = XMALLOC(tot_data);
if (buf == NULL) {
return CRYPT_MEM;
}
if (fread(buf, 1, tot_data, f) != tot_data) {
err = CRYPT_ERROR;
} else {
err = s_read_authorized_keys(buf, tot_data, cb, ctx);
}
XFREE(buf);
return err;
}
#endif /* LTC_NO_FILE */
int pem_decode_openssh(const void *buf, unsigned long len, ltc_pka_key *k, const password_ctx *pw_ctx)
{
LTC_ARGCHK(buf != NULL);
LTC_ARGCHK(len != 0);
LTC_ARGCHK(k != NULL);
{
struct get_char g = { .get = pem_get_char_from_buf, SET_BUFP(.data.buf, buf, len) };
return s_decode_openssh(&g, k, pw_ctx);
}
}
int ssh_read_authorized_keys(const void *buf, unsigned long len, ssh_authorized_key_cb cb, void *ctx)
{
LTC_ARGCHK(buf != NULL);
LTC_ARGCHK(len != 0);
LTC_ARGCHK(cb != NULL);
return s_read_authorized_keys(buf, len, cb, ctx);
}
#endif /* defined(LTC_PEM_SSH) */
#pragma clang diagnostic pop