345 lines
15 KiB
C
345 lines
15 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.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 = LTC_ARRAY_SIZE(pem_std_headers);
|
|
|
|
/* 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 = LTC_ARRAY_SIZE(pem_dek_infos);
|
|
|
|
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.ecb.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
|