4cc6522a85
Now that we have padding verification after decryption and since this can be used to validate the password as well there is no need to parse ASN1 content any more, so we can simplify/remove that dependency. Signed-off-by: Valerio Setti <valerio.setti@nordicsemi.no>
545 lines
14 KiB
C
545 lines
14 KiB
C
/*
|
|
* Privacy Enhanced Mail (PEM) decoding
|
|
*
|
|
* Copyright The Mbed TLS Contributors
|
|
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "common.h"
|
|
|
|
#if defined(MBEDTLS_PEM_PARSE_C) || defined(MBEDTLS_PEM_WRITE_C)
|
|
|
|
#include "mbedtls/pem.h"
|
|
#include "mbedtls/base64.h"
|
|
#include "mbedtls/des.h"
|
|
#include "mbedtls/aes.h"
|
|
#include "mbedtls/md.h"
|
|
#include "mbedtls/cipher.h"
|
|
#include "mbedtls/platform_util.h"
|
|
#include "mbedtls/error.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "mbedtls/platform.h"
|
|
|
|
#if defined(MBEDTLS_USE_PSA_CRYPTO)
|
|
#include "psa/crypto.h"
|
|
#endif
|
|
|
|
#if defined(MBEDTLS_MD_CAN_MD5) && \
|
|
defined(MBEDTLS_CIPHER_MODE_CBC) && \
|
|
(defined(MBEDTLS_DES_C) || defined(MBEDTLS_AES_C))
|
|
#define PEM_RFC1421
|
|
#endif /* MBEDTLS_MD_CAN_MD5 &&
|
|
MBEDTLS_CIPHER_MODE_CBC &&
|
|
( MBEDTLS_AES_C || MBEDTLS_DES_C ) */
|
|
|
|
#if defined(MBEDTLS_PEM_PARSE_C)
|
|
void mbedtls_pem_init(mbedtls_pem_context *ctx)
|
|
{
|
|
memset(ctx, 0, sizeof(mbedtls_pem_context));
|
|
}
|
|
|
|
#if defined(PEM_RFC1421)
|
|
/*
|
|
* Read a 16-byte hex string and convert it to binary
|
|
*/
|
|
static int pem_get_iv(const unsigned char *s, unsigned char *iv,
|
|
size_t iv_len)
|
|
{
|
|
size_t i, j, k;
|
|
|
|
memset(iv, 0, iv_len);
|
|
|
|
for (i = 0; i < iv_len * 2; i++, s++) {
|
|
if (*s >= '0' && *s <= '9') {
|
|
j = *s - '0';
|
|
} else
|
|
if (*s >= 'A' && *s <= 'F') {
|
|
j = *s - '7';
|
|
} else
|
|
if (*s >= 'a' && *s <= 'f') {
|
|
j = *s - 'W';
|
|
} else {
|
|
return MBEDTLS_ERR_PEM_INVALID_ENC_IV;
|
|
}
|
|
|
|
k = ((i & 1) != 0) ? j : j << 4;
|
|
|
|
iv[i >> 1] = (unsigned char) (iv[i >> 1] | k);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pem_pbkdf1(unsigned char *key, size_t keylen,
|
|
unsigned char *iv,
|
|
const unsigned char *pwd, size_t pwdlen)
|
|
{
|
|
mbedtls_md_context_t md5_ctx;
|
|
const mbedtls_md_info_t *md5_info;
|
|
unsigned char md5sum[16];
|
|
size_t use_len;
|
|
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
|
|
|
|
mbedtls_md_init(&md5_ctx);
|
|
|
|
/* Prepare the context. (setup() errors gracefully on NULL info.) */
|
|
md5_info = mbedtls_md_info_from_type(MBEDTLS_MD_MD5);
|
|
if ((ret = mbedtls_md_setup(&md5_ctx, md5_info, 0)) != 0) {
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* key[ 0..15] = MD5(pwd || IV)
|
|
*/
|
|
if ((ret = mbedtls_md_starts(&md5_ctx)) != 0) {
|
|
goto exit;
|
|
}
|
|
if ((ret = mbedtls_md_update(&md5_ctx, pwd, pwdlen)) != 0) {
|
|
goto exit;
|
|
}
|
|
if ((ret = mbedtls_md_update(&md5_ctx, iv, 8)) != 0) {
|
|
goto exit;
|
|
}
|
|
if ((ret = mbedtls_md_finish(&md5_ctx, md5sum)) != 0) {
|
|
goto exit;
|
|
}
|
|
|
|
if (keylen <= 16) {
|
|
memcpy(key, md5sum, keylen);
|
|
goto exit;
|
|
}
|
|
|
|
memcpy(key, md5sum, 16);
|
|
|
|
/*
|
|
* key[16..23] = MD5(key[ 0..15] || pwd || IV])
|
|
*/
|
|
if ((ret = mbedtls_md_starts(&md5_ctx)) != 0) {
|
|
goto exit;
|
|
}
|
|
if ((ret = mbedtls_md_update(&md5_ctx, md5sum, 16)) != 0) {
|
|
goto exit;
|
|
}
|
|
if ((ret = mbedtls_md_update(&md5_ctx, pwd, pwdlen)) != 0) {
|
|
goto exit;
|
|
}
|
|
if ((ret = mbedtls_md_update(&md5_ctx, iv, 8)) != 0) {
|
|
goto exit;
|
|
}
|
|
if ((ret = mbedtls_md_finish(&md5_ctx, md5sum)) != 0) {
|
|
goto exit;
|
|
}
|
|
|
|
use_len = 16;
|
|
if (keylen < 32) {
|
|
use_len = keylen - 16;
|
|
}
|
|
|
|
memcpy(key + 16, md5sum, use_len);
|
|
|
|
exit:
|
|
mbedtls_md_free(&md5_ctx);
|
|
mbedtls_platform_zeroize(md5sum, 16);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(MBEDTLS_DES_C)
|
|
/*
|
|
* Decrypt with DES-CBC, using PBKDF1 for key derivation
|
|
*/
|
|
static int pem_des_decrypt(unsigned char des_iv[8],
|
|
unsigned char *buf, size_t buflen,
|
|
const unsigned char *pwd, size_t pwdlen)
|
|
{
|
|
mbedtls_des_context des_ctx;
|
|
unsigned char des_key[8];
|
|
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
|
|
|
|
mbedtls_des_init(&des_ctx);
|
|
|
|
if ((ret = pem_pbkdf1(des_key, 8, des_iv, pwd, pwdlen)) != 0) {
|
|
goto exit;
|
|
}
|
|
|
|
if ((ret = mbedtls_des_setkey_dec(&des_ctx, des_key)) != 0) {
|
|
goto exit;
|
|
}
|
|
ret = mbedtls_des_crypt_cbc(&des_ctx, MBEDTLS_DES_DECRYPT, buflen,
|
|
des_iv, buf, buf);
|
|
|
|
exit:
|
|
mbedtls_des_free(&des_ctx);
|
|
mbedtls_platform_zeroize(des_key, 8);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Decrypt with 3DES-CBC, using PBKDF1 for key derivation
|
|
*/
|
|
static int pem_des3_decrypt(unsigned char des3_iv[8],
|
|
unsigned char *buf, size_t buflen,
|
|
const unsigned char *pwd, size_t pwdlen)
|
|
{
|
|
mbedtls_des3_context des3_ctx;
|
|
unsigned char des3_key[24];
|
|
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
|
|
|
|
mbedtls_des3_init(&des3_ctx);
|
|
|
|
if ((ret = pem_pbkdf1(des3_key, 24, des3_iv, pwd, pwdlen)) != 0) {
|
|
goto exit;
|
|
}
|
|
|
|
if ((ret = mbedtls_des3_set3key_dec(&des3_ctx, des3_key)) != 0) {
|
|
goto exit;
|
|
}
|
|
ret = mbedtls_des3_crypt_cbc(&des3_ctx, MBEDTLS_DES_DECRYPT, buflen,
|
|
des3_iv, buf, buf);
|
|
|
|
exit:
|
|
mbedtls_des3_free(&des3_ctx);
|
|
mbedtls_platform_zeroize(des3_key, 24);
|
|
|
|
return ret;
|
|
}
|
|
#endif /* MBEDTLS_DES_C */
|
|
|
|
#if defined(MBEDTLS_AES_C)
|
|
/*
|
|
* Decrypt with AES-XXX-CBC, using PBKDF1 for key derivation
|
|
*/
|
|
static int pem_aes_decrypt(unsigned char aes_iv[16], unsigned int keylen,
|
|
unsigned char *buf, size_t buflen,
|
|
const unsigned char *pwd, size_t pwdlen)
|
|
{
|
|
mbedtls_aes_context aes_ctx;
|
|
unsigned char aes_key[32];
|
|
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
|
|
|
|
mbedtls_aes_init(&aes_ctx);
|
|
|
|
if ((ret = pem_pbkdf1(aes_key, keylen, aes_iv, pwd, pwdlen)) != 0) {
|
|
goto exit;
|
|
}
|
|
|
|
if ((ret = mbedtls_aes_setkey_dec(&aes_ctx, aes_key, keylen * 8)) != 0) {
|
|
goto exit;
|
|
}
|
|
ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, buflen,
|
|
aes_iv, buf, buf);
|
|
|
|
exit:
|
|
mbedtls_aes_free(&aes_ctx);
|
|
mbedtls_platform_zeroize(aes_key, keylen);
|
|
|
|
return ret;
|
|
}
|
|
#endif /* MBEDTLS_AES_C */
|
|
|
|
#if defined(MBEDTLS_DES_C) || defined(MBEDTLS_AES_C)
|
|
static int pem_check_pkcs_padding(unsigned char *input, size_t input_len, size_t *data_len)
|
|
{
|
|
/* input_len > 0 is guaranteed by mbedtls_pem_read_buffer(). */
|
|
size_t pad_len = input[input_len - 1];
|
|
size_t i;
|
|
|
|
if (pad_len > input_len) {
|
|
return MBEDTLS_ERR_PEM_BAD_INPUT_DATA;
|
|
}
|
|
|
|
*data_len = input_len - pad_len;
|
|
|
|
for (i = *data_len; i < input_len; i++) {
|
|
if (input[i] != pad_len) {
|
|
return MBEDTLS_ERR_PEM_BAD_INPUT_DATA;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* MBEDTLS_DES_C || MBEDTLS_AES_C */
|
|
|
|
#endif /* PEM_RFC1421 */
|
|
|
|
int mbedtls_pem_read_buffer(mbedtls_pem_context *ctx, const char *header, const char *footer,
|
|
const unsigned char *data, const unsigned char *pwd,
|
|
size_t pwdlen, size_t *use_len)
|
|
{
|
|
int ret, enc;
|
|
size_t len;
|
|
unsigned char *buf;
|
|
const unsigned char *s1, *s2, *end;
|
|
#if defined(PEM_RFC1421)
|
|
unsigned char pem_iv[16];
|
|
mbedtls_cipher_type_t enc_alg = MBEDTLS_CIPHER_NONE;
|
|
#else
|
|
((void) pwd);
|
|
((void) pwdlen);
|
|
#endif /* PEM_RFC1421 */
|
|
|
|
if (ctx == NULL) {
|
|
return MBEDTLS_ERR_PEM_BAD_INPUT_DATA;
|
|
}
|
|
|
|
s1 = (unsigned char *) strstr((const char *) data, header);
|
|
|
|
if (s1 == NULL) {
|
|
return MBEDTLS_ERR_PEM_NO_HEADER_FOOTER_PRESENT;
|
|
}
|
|
|
|
s2 = (unsigned char *) strstr((const char *) data, footer);
|
|
|
|
if (s2 == NULL || s2 <= s1) {
|
|
return MBEDTLS_ERR_PEM_NO_HEADER_FOOTER_PRESENT;
|
|
}
|
|
|
|
s1 += strlen(header);
|
|
if (*s1 == ' ') {
|
|
s1++;
|
|
}
|
|
if (*s1 == '\r') {
|
|
s1++;
|
|
}
|
|
if (*s1 == '\n') {
|
|
s1++;
|
|
} else {
|
|
return MBEDTLS_ERR_PEM_NO_HEADER_FOOTER_PRESENT;
|
|
}
|
|
|
|
end = s2;
|
|
end += strlen(footer);
|
|
if (*end == ' ') {
|
|
end++;
|
|
}
|
|
if (*end == '\r') {
|
|
end++;
|
|
}
|
|
if (*end == '\n') {
|
|
end++;
|
|
}
|
|
*use_len = (size_t) (end - data);
|
|
|
|
enc = 0;
|
|
|
|
if (s2 - s1 >= 22 && memcmp(s1, "Proc-Type: 4,ENCRYPTED", 22) == 0) {
|
|
#if defined(PEM_RFC1421)
|
|
enc++;
|
|
|
|
s1 += 22;
|
|
if (*s1 == '\r') {
|
|
s1++;
|
|
}
|
|
if (*s1 == '\n') {
|
|
s1++;
|
|
} else {
|
|
return MBEDTLS_ERR_PEM_INVALID_DATA;
|
|
}
|
|
|
|
|
|
#if defined(MBEDTLS_DES_C)
|
|
if (s2 - s1 >= 23 && memcmp(s1, "DEK-Info: DES-EDE3-CBC,", 23) == 0) {
|
|
enc_alg = MBEDTLS_CIPHER_DES_EDE3_CBC;
|
|
|
|
s1 += 23;
|
|
if (s2 - s1 < 16 || pem_get_iv(s1, pem_iv, 8) != 0) {
|
|
return MBEDTLS_ERR_PEM_INVALID_ENC_IV;
|
|
}
|
|
|
|
s1 += 16;
|
|
} else if (s2 - s1 >= 18 && memcmp(s1, "DEK-Info: DES-CBC,", 18) == 0) {
|
|
enc_alg = MBEDTLS_CIPHER_DES_CBC;
|
|
|
|
s1 += 18;
|
|
if (s2 - s1 < 16 || pem_get_iv(s1, pem_iv, 8) != 0) {
|
|
return MBEDTLS_ERR_PEM_INVALID_ENC_IV;
|
|
}
|
|
|
|
s1 += 16;
|
|
}
|
|
#endif /* MBEDTLS_DES_C */
|
|
|
|
#if defined(MBEDTLS_AES_C)
|
|
if (s2 - s1 >= 14 && memcmp(s1, "DEK-Info: AES-", 14) == 0) {
|
|
if (s2 - s1 < 22) {
|
|
return MBEDTLS_ERR_PEM_UNKNOWN_ENC_ALG;
|
|
} else if (memcmp(s1, "DEK-Info: AES-128-CBC,", 22) == 0) {
|
|
enc_alg = MBEDTLS_CIPHER_AES_128_CBC;
|
|
} else if (memcmp(s1, "DEK-Info: AES-192-CBC,", 22) == 0) {
|
|
enc_alg = MBEDTLS_CIPHER_AES_192_CBC;
|
|
} else if (memcmp(s1, "DEK-Info: AES-256-CBC,", 22) == 0) {
|
|
enc_alg = MBEDTLS_CIPHER_AES_256_CBC;
|
|
} else {
|
|
return MBEDTLS_ERR_PEM_UNKNOWN_ENC_ALG;
|
|
}
|
|
|
|
s1 += 22;
|
|
if (s2 - s1 < 32 || pem_get_iv(s1, pem_iv, 16) != 0) {
|
|
return MBEDTLS_ERR_PEM_INVALID_ENC_IV;
|
|
}
|
|
|
|
s1 += 32;
|
|
}
|
|
#endif /* MBEDTLS_AES_C */
|
|
|
|
if (enc_alg == MBEDTLS_CIPHER_NONE) {
|
|
return MBEDTLS_ERR_PEM_UNKNOWN_ENC_ALG;
|
|
}
|
|
|
|
if (*s1 == '\r') {
|
|
s1++;
|
|
}
|
|
if (*s1 == '\n') {
|
|
s1++;
|
|
} else {
|
|
return MBEDTLS_ERR_PEM_INVALID_DATA;
|
|
}
|
|
#else
|
|
return MBEDTLS_ERR_PEM_FEATURE_UNAVAILABLE;
|
|
#endif /* PEM_RFC1421 */
|
|
}
|
|
|
|
if (s1 >= s2) {
|
|
return MBEDTLS_ERR_PEM_INVALID_DATA;
|
|
}
|
|
|
|
ret = mbedtls_base64_decode(NULL, 0, &len, s1, (size_t) (s2 - s1));
|
|
|
|
if (ret == MBEDTLS_ERR_BASE64_INVALID_CHARACTER) {
|
|
return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PEM_INVALID_DATA, ret);
|
|
}
|
|
|
|
if (len == 0) {
|
|
return MBEDTLS_ERR_PEM_BAD_INPUT_DATA;
|
|
}
|
|
|
|
if ((buf = mbedtls_calloc(1, len)) == NULL) {
|
|
return MBEDTLS_ERR_PEM_ALLOC_FAILED;
|
|
}
|
|
|
|
if ((ret = mbedtls_base64_decode(buf, len, &len, s1, (size_t) (s2 - s1))) != 0) {
|
|
mbedtls_zeroize_and_free(buf, len);
|
|
return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PEM_INVALID_DATA, ret);
|
|
}
|
|
|
|
if (enc != 0) {
|
|
#if defined(PEM_RFC1421)
|
|
if (pwd == NULL) {
|
|
mbedtls_zeroize_and_free(buf, len);
|
|
return MBEDTLS_ERR_PEM_PASSWORD_REQUIRED;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
#if defined(MBEDTLS_DES_C)
|
|
if (enc_alg == MBEDTLS_CIPHER_DES_EDE3_CBC) {
|
|
ret = pem_des3_decrypt(pem_iv, buf, len, pwd, pwdlen);
|
|
} else if (enc_alg == MBEDTLS_CIPHER_DES_CBC) {
|
|
ret = pem_des_decrypt(pem_iv, buf, len, pwd, pwdlen);
|
|
}
|
|
#endif /* MBEDTLS_DES_C */
|
|
|
|
#if defined(MBEDTLS_AES_C)
|
|
if (enc_alg == MBEDTLS_CIPHER_AES_128_CBC) {
|
|
ret = pem_aes_decrypt(pem_iv, 16, buf, len, pwd, pwdlen);
|
|
} else if (enc_alg == MBEDTLS_CIPHER_AES_192_CBC) {
|
|
ret = pem_aes_decrypt(pem_iv, 24, buf, len, pwd, pwdlen);
|
|
} else if (enc_alg == MBEDTLS_CIPHER_AES_256_CBC) {
|
|
ret = pem_aes_decrypt(pem_iv, 32, buf, len, pwd, pwdlen);
|
|
}
|
|
#endif /* MBEDTLS_AES_C */
|
|
|
|
if (ret != 0) {
|
|
mbedtls_free(buf);
|
|
return ret;
|
|
}
|
|
|
|
/* Check PKCS padding and update data length based on padding info.
|
|
* This can be used to detect invalid padding data and password
|
|
* mismatches. */
|
|
ret = pem_check_pkcs_padding(buf, len, &len);
|
|
if (ret != 0) {
|
|
mbedtls_zeroize_and_free(buf, len);
|
|
return ret;
|
|
}
|
|
#else
|
|
mbedtls_zeroize_and_free(buf, len);
|
|
return MBEDTLS_ERR_PEM_FEATURE_UNAVAILABLE;
|
|
#endif /* PEM_RFC1421 */
|
|
}
|
|
|
|
ctx->buf = buf;
|
|
ctx->buflen = len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mbedtls_pem_free(mbedtls_pem_context *ctx)
|
|
{
|
|
if (ctx->buf != NULL) {
|
|
mbedtls_zeroize_and_free(ctx->buf, ctx->buflen);
|
|
}
|
|
mbedtls_free(ctx->info);
|
|
|
|
mbedtls_platform_zeroize(ctx, sizeof(mbedtls_pem_context));
|
|
}
|
|
#endif /* MBEDTLS_PEM_PARSE_C */
|
|
|
|
#if defined(MBEDTLS_PEM_WRITE_C)
|
|
int mbedtls_pem_write_buffer(const char *header, const char *footer,
|
|
const unsigned char *der_data, size_t der_len,
|
|
unsigned char *buf, size_t buf_len, size_t *olen)
|
|
{
|
|
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
|
|
unsigned char *encode_buf = NULL, *c, *p = buf;
|
|
size_t len = 0, use_len, add_len = 0;
|
|
|
|
mbedtls_base64_encode(NULL, 0, &use_len, der_data, der_len);
|
|
add_len = strlen(header) + strlen(footer) + (((use_len > 2) ? (use_len - 2) : 0) / 64) + 1;
|
|
|
|
if (use_len + add_len > buf_len) {
|
|
*olen = use_len + add_len;
|
|
return MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
if (use_len != 0 &&
|
|
((encode_buf = mbedtls_calloc(1, use_len)) == NULL)) {
|
|
return MBEDTLS_ERR_PEM_ALLOC_FAILED;
|
|
}
|
|
|
|
if ((ret = mbedtls_base64_encode(encode_buf, use_len, &use_len, der_data,
|
|
der_len)) != 0) {
|
|
mbedtls_free(encode_buf);
|
|
return ret;
|
|
}
|
|
|
|
memcpy(p, header, strlen(header));
|
|
p += strlen(header);
|
|
c = encode_buf;
|
|
|
|
while (use_len) {
|
|
len = (use_len > 64) ? 64 : use_len;
|
|
memcpy(p, c, len);
|
|
use_len -= len;
|
|
p += len;
|
|
c += len;
|
|
*p++ = '\n';
|
|
}
|
|
|
|
memcpy(p, footer, strlen(footer));
|
|
p += strlen(footer);
|
|
|
|
*p++ = '\0';
|
|
*olen = (size_t) (p - buf);
|
|
|
|
/* Clean any remaining data previously written to the buffer */
|
|
memset(buf + *olen, 0, buf_len - *olen);
|
|
|
|
mbedtls_free(encode_buf);
|
|
return 0;
|
|
}
|
|
#endif /* MBEDTLS_PEM_WRITE_C */
|
|
#endif /* MBEDTLS_PEM_PARSE_C || MBEDTLS_PEM_WRITE_C */
|