Replaced system SQLite with SQLCipher to support encrypted database

This commit is contained in:
Oleksii Zghurskyi
2025-06-07 18:11:17 +03:00
parent f4198d62a7
commit 177d74700f
534 changed files with 362771 additions and 21 deletions

View File

@@ -0,0 +1,344 @@
/* 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.c
Const declarations for PEM, Steffen Jaeckel
*/
#ifdef LTC_PEM
const struct pem_header_id pem_std_headers[] = {
{
/* PKCS#8 encrypted */
SET_CSTR(.start, "-----BEGIN ENCRYPTED PRIVATE KEY-----"),
SET_CSTR(.end, "-----END ENCRYPTED PRIVATE KEY-----"),
.has_more_headers = no,
.flags = pf_encrypted_pkcs8,
},
{
/* PKCS#8 plain */
SET_CSTR(.start, "-----BEGIN PRIVATE KEY-----"),
SET_CSTR(.end, "-----END PRIVATE KEY-----"),
.has_more_headers = no,
.flags = pf_pkcs8,
},
{
/* X.509 Certificates */
SET_CSTR(.start, "-----BEGIN CERTIFICATE-----"),
SET_CSTR(.end, "-----END CERTIFICATE-----"),
.has_more_headers = no,
.flags = pf_x509,
},
{
/* Regular (plain) public keys */
SET_CSTR(.start, "-----BEGIN PUBLIC KEY-----"),
SET_CSTR(.end, "-----END PUBLIC KEY-----"),
.has_more_headers = no,
.flags = pf_public,
},
{
SET_CSTR(.start, "-----BEGIN RSA PUBLIC KEY-----"),
SET_CSTR(.end, "-----END RSA PUBLIC KEY-----"),
.has_more_headers = no,
.pka = LTC_PKA_RSA,
.flags = pf_public,
},
/* Regular plain or encrypted private keys */
{
SET_CSTR(.start, "-----BEGIN RSA PRIVATE KEY-----"),
SET_CSTR(.end, "-----END RSA PRIVATE KEY-----"),
.has_more_headers = maybe,
.pka = LTC_PKA_RSA,
},
{
SET_CSTR(.start, "-----BEGIN EC PRIVATE KEY-----"),
SET_CSTR(.end, "-----END EC PRIVATE KEY-----"),
.has_more_headers = maybe,
.pka = LTC_PKA_EC,
},
{
SET_CSTR(.start, "-----BEGIN DSA PRIVATE KEY-----"),
SET_CSTR(.end, "-----END DSA PRIVATE KEY-----"),
.has_more_headers = maybe,
.pka = LTC_PKA_DSA,
},
};
const unsigned long pem_std_headers_num = sizeof(pem_std_headers)/sizeof(pem_std_headers[0]);
/* Encrypted PEM files */
const struct str pem_proc_type_encrypted = { SET_CSTR(, "Proc-Type: 4,ENCRYPTED") };
#if defined(LTC_SSH)
const struct str pem_ssh_comment = { SET_CSTR(, "Comment: ") };
#endif
const struct str pem_dek_info_start = { SET_CSTR(, "DEK-Info: ") };
const struct blockcipher_info pem_dek_infos[] =
{
{ .name = "AES-128-CBC,", .algo = "aes", .keylen = 128 / 8, .mode = cm_cbc, },
{ .name = "AES-192-CBC,", .algo = "aes", .keylen = 192 / 8, .mode = cm_cbc, },
{ .name = "AES-256-CBC,", .algo = "aes", .keylen = 256 / 8, .mode = cm_cbc, },
{ .name = "AES-128-CFB,", .algo = "aes", .keylen = 128 / 8, .mode = cm_cfb, },
{ .name = "AES-192-CFB,", .algo = "aes", .keylen = 192 / 8, .mode = cm_cfb, },
{ .name = "AES-256-CFB,", .algo = "aes", .keylen = 256 / 8, .mode = cm_cfb, },
{ .name = "AES-128-CFB1,", .algo = "aes", .keylen = 128 / 8, .mode = cm_cfb1, },
{ .name = "AES-192-CFB1,", .algo = "aes", .keylen = 192 / 8, .mode = cm_cfb1, },
{ .name = "AES-256-CFB1,", .algo = "aes", .keylen = 256 / 8, .mode = cm_cfb1, },
{ .name = "AES-128-CFB8,", .algo = "aes", .keylen = 128 / 8, .mode = cm_cfb8, },
{ .name = "AES-192-CFB8,", .algo = "aes", .keylen = 192 / 8, .mode = cm_cfb8, },
{ .name = "AES-256-CFB8,", .algo = "aes", .keylen = 256 / 8, .mode = cm_cfb8, },
{ .name = "AES-128-CTR,", .algo = "aes", .keylen = 128 / 8, .mode = cm_ctr, },
{ .name = "AES-192-CTR,", .algo = "aes", .keylen = 192 / 8, .mode = cm_ctr, },
{ .name = "AES-256-CTR,", .algo = "aes", .keylen = 256 / 8, .mode = cm_ctr, },
{ .name = "AES-128-OFB,", .algo = "aes", .keylen = 128 / 8, .mode = cm_ofb, },
{ .name = "AES-192-OFB,", .algo = "aes", .keylen = 192 / 8, .mode = cm_ofb, },
{ .name = "AES-256-OFB,", .algo = "aes", .keylen = 256 / 8, .mode = cm_ofb, },
{ .name = "BF-CBC,", .algo = "blowfish", .keylen = 128 / 8, .mode = cm_cbc, },
{ .name = "BF-CFB,", .algo = "blowfish", .keylen = 128 / 8, .mode = cm_cfb, },
{ .name = "BF-OFB,", .algo = "blowfish", .keylen = 128 / 8, .mode = cm_ofb, },
{ .name = "CAMELLIA-128-CBC,", .algo = "camellia", .keylen = 128 / 8, .mode = cm_cbc, },
{ .name = "CAMELLIA-192-CBC,", .algo = "camellia", .keylen = 192 / 8, .mode = cm_cbc, },
{ .name = "CAMELLIA-256-CBC,", .algo = "camellia", .keylen = 256 / 8, .mode = cm_cbc, },
{ .name = "CAMELLIA-128-CFB,", .algo = "camellia", .keylen = 128 / 8, .mode = cm_cfb, },
{ .name = "CAMELLIA-192-CFB,", .algo = "camellia", .keylen = 192 / 8, .mode = cm_cfb, },
{ .name = "CAMELLIA-256-CFB,", .algo = "camellia", .keylen = 256 / 8, .mode = cm_cfb, },
{ .name = "CAMELLIA-128-CFB1,", .algo = "camellia", .keylen = 128 / 8, .mode = cm_cfb1, },
{ .name = "CAMELLIA-192-CFB1,", .algo = "camellia", .keylen = 192 / 8, .mode = cm_cfb1, },
{ .name = "CAMELLIA-256-CFB1,", .algo = "camellia", .keylen = 256 / 8, .mode = cm_cfb1, },
{ .name = "CAMELLIA-128-CFB8,", .algo = "camellia", .keylen = 128 / 8, .mode = cm_cfb8, },
{ .name = "CAMELLIA-192-CFB8,", .algo = "camellia", .keylen = 192 / 8, .mode = cm_cfb8, },
{ .name = "CAMELLIA-256-CFB8,", .algo = "camellia", .keylen = 256 / 8, .mode = cm_cfb8, },
{ .name = "CAMELLIA-128-CTR,", .algo = "camellia", .keylen = 128 / 8, .mode = cm_ctr, },
{ .name = "CAMELLIA-192-CTR,", .algo = "camellia", .keylen = 192 / 8, .mode = cm_ctr, },
{ .name = "CAMELLIA-256-CTR,", .algo = "camellia", .keylen = 256 / 8, .mode = cm_ctr, },
{ .name = "CAMELLIA-128-OFB,", .algo = "camellia", .keylen = 128 / 8, .mode = cm_ofb, },
{ .name = "CAMELLIA-192-OFB,", .algo = "camellia", .keylen = 192 / 8, .mode = cm_ofb, },
{ .name = "CAMELLIA-256-OFB,", .algo = "camellia", .keylen = 256 / 8, .mode = cm_ofb, },
{ .name = "CAST5-CBC,", .algo = "cast5", .keylen = 128 / 8, .mode = cm_cbc, },
{ .name = "CAST5-CFB,", .algo = "cast5", .keylen = 128 / 8, .mode = cm_cfb, },
{ .name = "CAST5-OFB,", .algo = "cast5", .keylen = 128 / 8, .mode = cm_ofb, },
{ .name = "ChaCha20,", .algo = "chacha20", .keylen = 256 / 8, .mode = cm_stream, },
{ .name = "DES-EDE-CBC,", .algo = "3des", .keylen = 128 / 8, .mode = cm_cbc, },
{ .name = "DES-EDE-CFB,", .algo = "3des", .keylen = 128 / 8, .mode = cm_cfb, },
{ .name = "DES-EDE-OFB,", .algo = "3des", .keylen = 128 / 8, .mode = cm_ofb, },
{ .name = "DES-EDE3-CBC,", .algo = "3des", .keylen = 192 / 8, .mode = cm_cbc, },
{ .name = "DES-EDE3-CFB,", .algo = "3des", .keylen = 192 / 8, .mode = cm_cfb, },
{ .name = "DES-EDE3-CFB1,", .algo = "3des", .keylen = 192 / 8, .mode = cm_cfb1, },
{ .name = "DES-EDE3-CFB8,", .algo = "3des", .keylen = 192 / 8, .mode = cm_cfb8, },
{ .name = "DES-EDE3-OFB,", .algo = "3des", .keylen = 192 / 8, .mode = cm_ofb, },
{ .name = "DES-CBC,", .algo = "des", .keylen = 64 / 8, .mode = cm_cbc, },
{ .name = "DES-CFB,", .algo = "des", .keylen = 64 / 8, .mode = cm_cfb, },
{ .name = "DES-CFB1,", .algo = "des", .keylen = 64 / 8, .mode = cm_cfb1, },
{ .name = "DES-CFB8,", .algo = "des", .keylen = 64 / 8, .mode = cm_cfb8, },
{ .name = "DES-OFB,", .algo = "des", .keylen = 64 / 8, .mode = cm_ofb, },
{ .name = "DESX-CBC,", .algo = "desx", .keylen = 192 / 8, .mode = cm_cbc, },
{ .name = "IDEA-CBC,", .algo = "idea", .keylen = 128 / 8, .mode = cm_cbc, },
{ .name = "IDEA-CFB,", .algo = "idea", .keylen = 128 / 8, .mode = cm_cfb, },
{ .name = "IDEA-OFB,", .algo = "idea", .keylen = 128 / 8, .mode = cm_ofb, },
{ .name = "RC5-CBC,", .algo = "rc5", .keylen = 128 / 8, .mode = cm_cbc, },
{ .name = "RC5-CFB,", .algo = "rc5", .keylen = 128 / 8, .mode = cm_cfb, },
{ .name = "RC5-OFB,", .algo = "rc5", .keylen = 128 / 8, .mode = cm_ofb, },
{ .name = "RC2-40-CBC,", .algo = "rc2", .keylen = 40 / 8, .mode = cm_cbc, },
{ .name = "RC2-64-CBC,", .algo = "rc2", .keylen = 64 / 8, .mode = cm_cbc, },
{ .name = "RC2-CBC,", .algo = "rc2", .keylen = 128 / 8, .mode = cm_cbc, },
{ .name = "RC2-CFB,", .algo = "rc2", .keylen = 128 / 8, .mode = cm_cfb, },
{ .name = "RC2-OFB,", .algo = "rc2", .keylen = 128 / 8, .mode = cm_ofb, },
{ .name = "SEED-CBC,", .algo = "seed", .keylen = 128 / 8, .mode = cm_cbc, },
{ .name = "SEED-CFB,", .algo = "seed", .keylen = 128 / 8, .mode = cm_cfb, },
{ .name = "SEED-OFB,", .algo = "seed", .keylen = 128 / 8, .mode = cm_ofb, },
};
const unsigned long pem_dek_infos_num = sizeof(pem_dek_infos)/sizeof(pem_dek_infos[0]);
int pem_decrypt(unsigned char *data, unsigned long *datalen,
unsigned char *key, unsigned long keylen,
unsigned char *iv, unsigned long ivlen,
unsigned char *tag, unsigned long taglen,
const struct blockcipher_info *info,
enum padding_type padding)
{
int err, cipher = -1;
struct {
union {
#ifdef LTC_CBC_MODE
symmetric_CBC cbc;
#endif
#ifdef LTC_CFB_MODE
symmetric_CFB cfb;
#endif
#ifdef LTC_CTR_MODE
symmetric_CTR ctr;
#endif
#ifdef LTC_OFB_MODE
symmetric_OFB ofb;
#endif
} ctx;
} s;
enum cipher_mode mode = info->mode & cm_modes;
if (mode != cm_stream) {
cipher = find_cipher(info->algo);
if (cipher == -1) {
return CRYPT_INVALID_CIPHER;
}
}
switch (info->mode) {
case cm_cbc:
#ifdef LTC_CBC_MODE
LTC_ARGCHK(ivlen == (unsigned long)cipher_descriptor[cipher].block_length);
if ((err = cbc_start(cipher, iv, key, keylen, 0, &s.ctx.cbc)) != CRYPT_OK) {
goto error_out;
}
if ((err = cbc_decrypt(data, data, *datalen, &s.ctx.cbc)) != CRYPT_OK) {
goto error_out;
}
if ((err = cbc_done(&s.ctx.cbc)) != CRYPT_OK) {
goto error_out;
}
if ((err = padding_depad(data, datalen, padding | s.ctx.cbc.blocklen)) != CRYPT_OK) {
goto error_out;
}
#else
return CRYPT_INVALID_CIPHER;
#endif
break;
case cm_cfb:
case cm_cfb1:
case cm_cfb8:
#ifdef LTC_CFB_MODE
if (info->mode == cm_cfb) {
if ((err = cfb_start(cipher, iv, key, keylen, 0, &s.ctx.cfb)) != CRYPT_OK) {
goto error_out;
}
} else {
if ((err = cfb_start_ex(cipher, iv, key, keylen, 0, info->mode == cm_cfb1 ? 1 : 8, &s.ctx.cfb)) != CRYPT_OK) {
goto error_out;
}
}
if ((err = cfb_decrypt(data, data, *datalen, &s.ctx.cfb)) != CRYPT_OK) {
goto error_out;
}
if ((err = cfb_done(&s.ctx.cfb)) != CRYPT_OK) {
goto error_out;
}
#else
return CRYPT_INVALID_CIPHER;
#endif
break;
case cm_ctr:
#ifdef LTC_CTR_MODE
if ((err = ctr_start(cipher, iv, key, keylen, 0, CTR_COUNTER_BIG_ENDIAN, &s.ctx.ctr)) != CRYPT_OK) {
goto error_out;
}
if ((err = ctr_decrypt(data, data, *datalen, &s.ctx.ctr)) != CRYPT_OK) {
goto error_out;
}
if ((err = ctr_done(&s.ctx.ctr)) != CRYPT_OK) {
goto error_out;
}
#else
return CRYPT_INVALID_CIPHER;
#endif
break;
case cm_ofb:
#ifdef LTC_OFB_MODE
if ((err = ofb_start(cipher, iv, key, keylen, 0, &s.ctx.ofb)) != CRYPT_OK) {
goto error_out;
}
if ((err = ofb_decrypt(data, data, *datalen, &s.ctx.ofb)) != CRYPT_OK) {
goto error_out;
}
if ((err = ofb_done(&s.ctx.ofb)) != CRYPT_OK) {
goto error_out;
}
#else
return CRYPT_INVALID_CIPHER;
#endif
break;
case cm_gcm:
#ifdef LTC_GCM_MODE
if ((err = gcm_memory(cipher,
key, keylen,
iv, ivlen,
NULL, 0,
data, *datalen, data,
tag, &taglen,
GCM_DECRYPT)) != CRYPT_OK) {
goto error_out;
}
#else
LTC_UNUSED_PARAM(tag);
LTC_UNUSED_PARAM(taglen);
return CRYPT_INVALID_CIPHER;
#endif
break;
case cm_stream:
#ifdef LTC_CHACHA
LTC_ARGCHK(ivlen == 16);
if ((err = chacha_memory(key, keylen, 20,
iv, ivlen, 0,
data, *datalen, data)) != CRYPT_OK) {
goto error_out;
}
#else
return CRYPT_INVALID_CIPHER;
#endif
break;
case cm_stream_openssh:
#ifdef LTC_CHACHA20POLY1305_MODE
if ((err = chacha20poly1305_memory(key, 32,
iv, ivlen,
NULL, 0,
data, *datalen, data,
tag, &taglen,
CHACHA20POLY1305_DECRYPT | CHACHA20POLY1305_OPENSSH_COMPAT)) != CRYPT_OK) {
goto error_out;
}
#else
return CRYPT_INVALID_CIPHER;
#endif
break;
default:
err = CRYPT_INVALID_ARG;
break;
}
error_out:
return err;
}
#ifndef LTC_NO_FILE
int pem_decode_filehandle(FILE *f, ltc_pka_key *k, const password_ctx *pw_ctx)
{
int err = pem_decode_pkcs_filehandle(f, k, pw_ctx);
if (err == CRYPT_OK || err != CRYPT_UNKNOWN_PEM)
return err;
#if defined(LTC_SSH)
rewind(f);
err = pem_decode_openssh_filehandle(f, k, pw_ctx);
#endif
return err;
}
#endif
int pem_decode(const void *buf, unsigned long len, ltc_pka_key *k, const password_ctx *pw_ctx)
{
int err = pem_decode_pkcs(buf, len, k, pw_ctx);
if (err == CRYPT_OK || err != CRYPT_UNKNOWN_PEM)
return err;
#if defined(LTC_SSH)
err = pem_decode_openssh(buf, len, k, pw_ctx);
#endif
return err;
}
#endif /* LTC_PEM */
#pragma clang diagnostic pop

View File

@@ -0,0 +1,298 @@
/* 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_decode.c
Decode a PEM file, Steffen Jaeckel
*/
#ifdef LTC_PEM
extern const struct pem_header_id pem_std_headers[];
extern const unsigned long pem_std_headers_num;
static int s_decrypt_pem(unsigned char *pem, unsigned long *l, const struct pem_headers *hdr)
{
unsigned char iv[MAXBLOCKSIZE], key[MAXBLOCKSIZE];
unsigned long ivlen, klen;
int err;
if (hdr->info.keylen > sizeof(key)) {
return CRYPT_BUFFER_OVERFLOW;
}
if (!hdr->pw->pw) {
return CRYPT_INVALID_ARG;
}
ivlen = sizeof(iv);
if ((err = base16_decode(hdr->info.iv, XSTRLEN(hdr->info.iv), iv, &ivlen)) != CRYPT_OK) {
return err;
}
klen = hdr->info.keylen;
if ((err = pkcs_5_alg1_openssl(hdr->pw->pw, hdr->pw->l, iv, 1, find_hash("md5"), key, &klen))) {
return err;
}
err = pem_decrypt(pem, l, key, klen, iv, ivlen, NULL, 0, &hdr->info, LTC_PAD_PKCS7);
zeromem(key, sizeof(key));
zeromem(iv, sizeof(iv));
return err;
}
static int s_get_pka(ltc_asn1_list *pub, enum ltc_pka_id *pka)
{
der_flexi_check flexi_should[4];
ltc_asn1_list *seqid, *id;
enum ltc_oid_id oid_id;
int err;
unsigned long n = 0;
LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_SEQUENCE, &seqid);
LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_BIT_STRING, NULL);
LTC_SET_DER_FLEXI_CHECK(flexi_should, n, LTC_ASN1_EOL, NULL);
if ((err = der_flexi_sequence_cmp(pub, flexi_should)) != CRYPT_OK) {
return err;
}
n = 0;
LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_OBJECT_IDENTIFIER, &id);
LTC_SET_DER_FLEXI_CHECK(flexi_should, n, LTC_ASN1_EOL, NULL);
err = der_flexi_sequence_cmp(seqid, flexi_should);
if (err != CRYPT_OK && err != CRYPT_INPUT_TOO_LONG) {
return err;
}
if ((err = pk_get_oid_from_asn1(id, &oid_id)) != CRYPT_OK) {
return err;
}
return pk_get_pka_id(oid_id, pka);
}
typedef int (*import_fn)(const unsigned char *, unsigned long, void*);
static const import_fn s_import_x509_fns[LTC_PKA_NUM] = {
#ifdef LTC_MRSA
[LTC_PKA_RSA] = (import_fn)rsa_import_x509,
#endif
#ifdef LTC_MECC
[LTC_PKA_EC] = (import_fn)ecc_import_x509,
#endif
#ifdef LTC_CURVE25519
[LTC_PKA_X25519] = (import_fn)x25519_import_x509,
[LTC_PKA_ED25519] = (import_fn)ed25519_import_x509,
#endif
};
static int s_import_x509(unsigned char *pem, unsigned long l, ltc_pka_key *k)
{
enum ltc_pka_id pka = LTC_PKA_UNDEF;
ltc_asn1_list *d, *spki;
int err;
if ((err = x509_decode_spki(pem, l, &d, &spki)) != CRYPT_OK) {
return err;
}
err = s_get_pka(spki, &pka);
der_free_sequence_flexi(d);
if (err != CRYPT_OK) {
return err;
}
if (pka < 0
|| pka > sizeof(s_import_x509_fns)/sizeof(s_import_x509_fns[0])
|| s_import_x509_fns[pka] == NULL) {
return CRYPT_PK_INVALID_TYPE;
}
if ((err = s_import_x509_fns[pka](pem, l, &k->u)) == CRYPT_OK) {
k->id = pka;
}
return err;
}
static int s_import_pkcs8(unsigned char *pem, unsigned long l, ltc_pka_key *k, const password_ctx *pw_ctx)
{
int err;
enum ltc_oid_id pka;
ltc_asn1_list *alg_id, *priv_key;
ltc_asn1_list *p8_asn1 = NULL;
if ((err = pkcs8_decode_flexi(pem, l, pw_ctx, &p8_asn1)) != CRYPT_OK) {
goto cleanup;
}
if ((err = pkcs8_get_children(p8_asn1, &pka, &alg_id, &priv_key)) != CRYPT_OK) {
goto cleanup;
}
switch (pka) {
#ifdef LTC_MDH
case LTC_OID_DH:
err = dh_import_pkcs8_asn1(alg_id, priv_key, &k->u.dh);
k->id = LTC_PKA_DH;
break;
#endif
#ifdef LTC_MDSA
case LTC_OID_DSA:
err = dsa_import_pkcs8_asn1(alg_id, priv_key, &k->u.dsa);
k->id = LTC_PKA_DSA;
break;
#endif
#ifdef LTC_MRSA
case LTC_OID_RSA:
err = rsa_import_pkcs8_asn1(alg_id, priv_key, &k->u.rsa);
k->id = LTC_PKA_RSA;
break;
#endif
#ifdef LTC_MECC
case LTC_OID_EC:
err = ecc_import_pkcs8_asn1(alg_id, priv_key, &k->u.ecc);
k->id = LTC_PKA_EC;
break;
#endif
#ifdef LTC_CURVE25519
case LTC_OID_X25519:
err = x25519_import_pkcs8_asn1(alg_id, priv_key, &k->u.x25519);
k->id = LTC_PKA_X25519;
break;
case LTC_OID_ED25519:
err = ed25519_import_pkcs8_asn1(alg_id, priv_key, &k->u.ed25519);
k->id = LTC_PKA_ED25519;
break;
#endif
default:
err = CRYPT_PK_INVALID_TYPE;
}
cleanup:
if (p8_asn1) {
der_sequence_free(p8_asn1);
}
return err;
}
static int s_extract_pka(unsigned char *pem, unsigned long w, enum ltc_pka_id *pka)
{
ltc_asn1_list *pub;
int err = CRYPT_ERROR;
if ((err = der_decode_sequence_flexi(pem, &w, &pub)) != CRYPT_OK) {
return err;
}
err = s_get_pka(pub, pka);
der_sequence_free(pub);
return err;
}
static const import_fn s_import_openssl_fns[LTC_PKA_NUM] = {
#ifdef LTC_MRSA
[LTC_PKA_RSA] = (import_fn)rsa_import,
#endif
#ifdef LTC_MDSA
[LTC_PKA_DSA] = (import_fn)dsa_import,
#endif
#ifdef LTC_MECC
[LTC_PKA_EC] = (import_fn)ecc_import_openssl,
#endif
#ifdef LTC_CURVE25519
[LTC_PKA_X25519] = (import_fn)x25519_import,
[LTC_PKA_ED25519] = (import_fn)ed25519_import,
#endif
};
static int s_decode(struct get_char *g, ltc_pka_key *k, const password_ctx *pw_ctx)
{
unsigned char *pem = NULL;
unsigned long w, l, n;
int err = CRYPT_ERROR;
struct pem_headers hdr = { 0 };
struct password pw = { 0 };
enum ltc_pka_id pka;
XMEMSET(k, 0, sizeof(*k));
w = LTC_PEM_READ_BUFSIZE * 2;
retry:
pem = XREALLOC(pem, w);
for (n = 0; n < pem_std_headers_num; ++n) {
hdr.id = &pem_std_headers[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;
l = w;
if (hdr.id->flags & pf_pkcs8) {
err = s_import_pkcs8(pem, l, k, pw_ctx);
goto cleanup;
} else if (hdr.id->flags == pf_x509) {
err = s_import_x509(pem, l, k);
goto cleanup;
} else if ((hdr.id->flags & pf_public) && hdr.id->pka == LTC_PKA_UNDEF) {
if ((err = s_extract_pka(pem, w, &pka)) != CRYPT_OK) {
goto cleanup;
}
} else if (hdr.encrypted) {
if ((pw_ctx == NULL) || (pw_ctx->callback == NULL)) {
err = CRYPT_PW_CTX_MISSING;
goto cleanup;
}
hdr.pw = &pw;
if (pw_ctx->callback(&hdr.pw->pw, &hdr.pw->l, pw_ctx->userdata)) {
err = CRYPT_ERROR;
goto cleanup;
}
if ((err = s_decrypt_pem(pem, &l, &hdr)) != CRYPT_OK) {
goto cleanup;
}
pka = hdr.id->pka;
} else {
pka = hdr.id->pka;
}
if (pka < 0
|| pka > sizeof(s_import_openssl_fns)/sizeof(s_import_openssl_fns[0])
|| s_import_openssl_fns[pka] == NULL) {
err = CRYPT_PK_INVALID_TYPE;
goto cleanup;
}
if ((err = s_import_openssl_fns[pka](pem, l, &k->u)) == CRYPT_OK) {
k->id = pka;
}
cleanup:
password_free(hdr.pw, pw_ctx);
XFREE(pem);
return err;
}
#ifndef LTC_NO_FILE
int pem_decode_pkcs_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(&g, k, pw_ctx);
}
}
#endif /* LTC_NO_FILE */
int pem_decode_pkcs(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(&g, k, pw_ctx);
}
}
#endif /* LTC_PEM */
#pragma clang diagnostic pop

View File

@@ -0,0 +1,244 @@
/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
/* SPDX-License-Identifier: Unlicense */
#include "tomcrypt_private.h"
/**
@file pem_read.c
Read and interpret a PEM file, Steffen Jaeckel
*/
#ifdef LTC_PEM
extern const struct str pem_proc_type_encrypted;
#ifdef LTC_SSH
extern const struct str pem_ssh_comment;
#endif
extern const struct str pem_dek_info_start;
extern const struct blockcipher_info pem_dek_infos[];
extern const unsigned long pem_dek_infos_num;
#ifndef LTC_NO_FILE
int pem_get_char_from_file(struct get_char *g)
{
return getc(g->data.f);
}
#endif /* LTC_NO_FILE */
int pem_get_char_from_buf(struct get_char *g)
{
int ret;
if (g->data.buf.work == g->data.buf.end) {
return -1;
}
ret = *g->data.buf.work;
g->data.buf.work++;
return ret;
}
static void s_unget_line(char *buf, unsigned long buflen, struct get_char *g)
{
if (buflen > sizeof(g->unget_buf_))
return;
g->unget_buf.p = g->unget_buf_;
COPY_STR(g->unget_buf, buf, buflen);
}
static void s_tts(char *buf, unsigned long *buflen)
{
while(1) {
unsigned long blen = *buflen;
if (blen < 2)
return;
blen--;
switch (buf[blen]) {
case ' ':
case '\t':
buf[blen] = '\0';
*buflen = blen;
break;
default:
return;
}
}
}
static char* s_get_line(char *buf, unsigned long *buflen, struct get_char *g)
{
unsigned long blen = 0;
int c = -1, c_;
if (g->unget_buf.p) {
if (*buflen < g->unget_buf.len) {
return NULL;
}
XMEMCPY(buf, g->unget_buf.p, g->unget_buf.len);
*buflen = g->unget_buf.len;
RESET_STR(g->unget_buf);
return buf;
}
while(blen < *buflen) {
c_ = c;
c = g->get(g);
if (c == '\n') {
buf[blen] = '\0';
if (c_ == '\r') {
buf[--blen] = '\0';
}
s_tts(buf, &blen);
*buflen = blen;
return buf;
}
if (c == -1 || c == '\0') {
buf[blen] = '\0';
s_tts(buf, &blen);
*buflen = blen;
return buf;
}
buf[blen] = c;
blen++;
}
return NULL;
}
static LTC_INLINE int s_fits_buf(void *dest, unsigned long to_write, void *end)
{
unsigned char *d = dest;
unsigned char *e = end;
unsigned char *w = d + to_write;
if (w < d || w > e)
return 0;
return 1;
}
static int s_pem_decode_headers(struct pem_headers *hdr, struct get_char *g)
{
char buf[LTC_PEM_DECODE_BUFSZ], *alg_start;
unsigned long slen, tmplen, n;
int has_more_headers = hdr->id->has_more_headers == no ? 0 : 3;
/* Make sure the PEM has the appropriate extension headers if required.
*
* ```
* Proc-Type: 4,ENCRYPTED[\r]\n
* DEK-Info: <algorithm>,<IV>[\r]\n
* [\r]\n
* ```
*/
while (has_more_headers) {
slen = sizeof(buf);
if (!s_get_line(buf, &slen, g) || (has_more_headers > 1 && slen == 0)) {
return CRYPT_INVALID_PACKET;
}
switch (has_more_headers) {
case 3:
if (XMEMCMP(buf, pem_proc_type_encrypted.p, pem_proc_type_encrypted.len)) {
#ifdef LTC_SSH
if (XMEMCMP(buf, pem_ssh_comment.p, pem_ssh_comment.len))
#endif
s_unget_line(buf, slen, g);
if (hdr->id->has_more_headers == maybe)
return CRYPT_OK;
else
return CRYPT_INVALID_PACKET;
}
hdr->encrypted = 1;
break;
case 2:
hdr->info.algo = NULL;
if (XMEMCMP(buf, pem_dek_info_start.p, pem_dek_info_start.len))
return CRYPT_INVALID_PACKET;
alg_start = &buf[pem_dek_info_start.len];
for (n = 0; n < pem_dek_infos_num; ++n) {
unsigned long namelen = XSTRLEN(pem_dek_infos[n].name);
if (slen >= namelen + pem_dek_info_start.len && !XMEMCMP(alg_start, pem_dek_infos[n].name, namelen)) {
char *iv = alg_start + namelen;
hdr->info = pem_dek_infos[n];
tmplen = XSTRLEN(iv);
if (tmplen > sizeof(hdr->info.iv))
return CRYPT_INVALID_KEYSIZE;
XMEMCPY(hdr->info.iv, iv, tmplen);
break;
}
}
if (hdr->info.algo == NULL) {
return CRYPT_INVALID_CIPHER;
}
break;
case 1:
/* Make sure that there's an empty line in between */
if (buf[0] != '\0')
return CRYPT_INVALID_PACKET;
break;
default:
return CRYPT_INVALID_CIPHER;
}
has_more_headers--;
}
return CRYPT_OK;
}
int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_char *g)
{
char buf[LTC_PEM_DECODE_BUFSZ];
char *wpem = pem;
char *end = wpem + *w;
unsigned long slen, linelen;
int err, hdr_ok = 0;
int would_overflow = 0;
unsigned char empty_lines = 0;
linelen = sizeof(buf);
if (s_get_line(buf, &linelen, g) == NULL) {
return CRYPT_INVALID_PACKET;
}
if (hdr->id->start.len != linelen || XMEMCMP(buf, hdr->id->start.p, hdr->id->start.len)) {
s_unget_line(buf, linelen, g);
return CRYPT_UNKNOWN_PEM;
}
hdr->encrypted = hdr->id->flags & pf_encrypted;
if ((err = s_pem_decode_headers(hdr, g)) != CRYPT_OK)
return err;
/* Read the base64 encoded part of the PEM */
slen = sizeof(buf);
while (s_get_line(buf, &slen, g)) {
if (slen == hdr->id->end.len && !XMEMCMP(buf, hdr->id->end.p, slen)) {
hdr_ok = 1;
break;
}
if (!slen) {
if (empty_lines)
break;
empty_lines++;
}
if (!would_overflow && s_fits_buf(wpem, slen, end)) {
XMEMCPY(wpem, buf, slen);
} else {
would_overflow = 1;
}
wpem += slen;
slen = sizeof(buf);
}
if (!hdr_ok)
return CRYPT_INVALID_PACKET;
if (would_overflow || !s_fits_buf(wpem, 1, end)) {
/* NUL termination */
wpem++;
/* prevent a wrap-around */
if (wpem < (char*)pem)
return CRYPT_OVERFLOW;
*w = wpem - (char*)pem;
return CRYPT_BUFFER_OVERFLOW;
}
*w = wpem - (char*)pem;
*wpem++ = '\0';
if ((err = base64_strict_decode(pem, *w, pem, w)) != CRYPT_OK) {
return err;
}
return CRYPT_OK;
}
#endif /* LTC_PEM */

View File

@@ -0,0 +1,861 @@
/* 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 = sizeof(ssh_ciphers)/sizeof(ssh_ciphers[0]);
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 < sizeof(ssh_pkas)/sizeof(ssh_pkas[0]); ++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 == sizeof(ssh_pkas)/sizeof(ssh_pkas[0])) {
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 < sizeof(elements)/sizeof(elements[0]) && 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 < sizeof(ssh_pkas)/sizeof(ssh_pkas[0]); ++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 = sizeof(pem_openssh)/sizeof(pem_openssh[0]);
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);
fseek(f, 0, SEEK_END);
tot_data = ftell(f);
rewind(f);
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