wolfssl-examples/pkcs7/signedData-cryptocb.c

602 lines
18 KiB
C

/* signedData-cryptocb.c
*
* Copyright (C) 2006-2024 wolfSSL Inc.
*
* This file is part of wolfSSL. (formerly known as CyaSSL)
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef WOLFSSL_USER_SETTINGS
#include <wolfssl/options.h>
#endif
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/pkcs7.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/logging.h>
#include <wolfssl/wolfcrypt/cryptocb.h>
#ifdef USE_PSA
#include <wolfssl/wolfcrypt/port/psa/psa.h>
#include <psa/crypto.h>
#endif
/* Default certificates and keys */
#define RSA_CERT_FILE "../certs/client-cert.der"
#define RSA_KEY_FILE "../certs/client-key.der"
#define RSA_KEYPUB_FILE "../certs/client-keyPub.der"
#define ECC_CERT_FILE "../certs/client-ecc-cert.der"
#define ECC_KEY_FILE "../certs/ecc-client-key.der"
#define ECC_KEYPUB_FILE "../certs/ecc-client-keyPub.der"
/* Default output file name */
#define OUTPUT_FILE "signedData_cryptocb.der"
/* Maximum temporary buffer size */
#define LARGE_TEMP_SZ 2048
#if defined(HAVE_PKCS7) && defined(WOLF_CRYPTO_CB)
/* crypto callback context */
typedef struct {
const char* keyFilePriv;
#ifdef USE_PSA
psa_key_id_t key_id;
#endif
} myCryptoCbCtx;
/* test data to sign (could be CSR for example) */
static const byte data[] = { /* Hello World */
0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64
};
/* reads file size, allocates buffer, reads into buffer, returns buffer */
static int load_file(const char* fname, byte** buf, size_t* bufLen)
{
int ret;
long int fileSz;
XFILE lFile;
if (fname == NULL || buf == NULL || bufLen == NULL)
return BAD_FUNC_ARG;
/* set defaults */
*buf = NULL;
*bufLen = 0;
/* open file (read-only binary) */
lFile = XFOPEN(fname, "rb");
if (!lFile) {
printf("Error loading %s\n", fname);
return BAD_PATH_ERROR;
}
fseek(lFile, 0, SEEK_END);
fileSz = (int)ftell(lFile);
rewind(lFile);
if (fileSz > 0) {
*bufLen = (size_t)fileSz;
*buf = (byte*)malloc(*bufLen);
if (*buf == NULL) {
ret = MEMORY_E;
printf("Error allocating %lu bytes\n", (unsigned long)*bufLen);
}
else {
size_t readLen = fread(*buf, *bufLen, 1, lFile);
/* check response code */
ret = (readLen > 0) ? 0 : -1;
}
}
else {
ret = BUFFER_E;
}
fclose(lFile);
return ret;
}
static int write_file(const char* fileName, byte* in, word32 inSz)
{
int ret;
FILE* file;
file = fopen(fileName, "wb");
if (file == NULL) {
printf("ERROR: opening file for writing: %s\n", fileName);
return -1;
}
ret = (int)fwrite(in, 1, inSz, file);
if (ret == 0) {
printf("ERROR: writing buffer to output file\n");
return -1;
}
fclose(file);
return 0;
}
#ifdef USE_PSA
static int psa_map_hash_alg(int hash_len)
{
switch (hash_len) {
case 20:
return PSA_ALG_SHA_1;
case 28:
return PSA_ALG_SHA_224;
case 32:
return PSA_ALG_SHA_256;
case 48:
return PSA_ALG_SHA_384;
case 64:
return PSA_ALG_SHA_512;
default:
return PSA_ALG_NONE;
}
}
/* import private key helper */
static int import_ecc_key(byte* keyBuf, word32 keySz, psa_key_id_t* id)
{
int ret;
psa_key_attributes_t key_attr = { 0 };
psa_key_type_t key_type;
psa_key_id_t key_id;
psa_status_t status;
ecc_key eccPriv;
byte d[MAX_ECC_BYTES]; /* private d */
word32 dSz = (word32)sizeof(d);
memset(d, 0, sizeof(d));
ret = wc_ecc_init_ex(&eccPriv, NULL, INVALID_DEVID);
if (ret == 0) {
word32 idx = 0;
ret = wc_EccPrivateKeyDecode(keyBuf, &idx, &eccPriv, keySz);
if (ret == 0) {
ret = wc_ecc_export_private_only(&eccPriv, d, &dSz);
}
wc_ecc_free(&eccPriv);
}
if (ret == 0) {
psa_set_key_usage_flags(&key_attr, PSA_KEY_USAGE_SIGN_HASH);
psa_set_key_lifetime(&key_attr, PSA_KEY_LIFETIME_VOLATILE);
psa_set_key_algorithm(&key_attr, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
key_type = PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1);
psa_set_key_type(&key_attr, key_type);
psa_set_key_bits(&key_attr, (dSz * 8));
status = psa_import_key(&key_attr, d, dSz, &key_id);
if (status != PSA_SUCCESS) {
fprintf(stderr,
"ERROR: provisioning of private key failed: [%d] \n", status);
ret = WC_HW_E;
}
else {
ret = 0;
*id = key_id;
}
}
return ret;
}
#endif
/* Example crypto dev callback function that calls software versions, could
* be set up to call down to hardware module for crypto operations if
* desired by user. If an algorithm is not supported by hardware, or user
* callback, the crypto callback can return CRYPTOCB_UNAVAILABLE to default
* back to using software crypto implementation. */
static int myCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx)
{
int ret = CRYPTOCB_UNAVAILABLE; /* return this to bypass HW and use SW */
myCryptoCbCtx* myCtx = (myCryptoCbCtx*)ctx;
if (info == NULL)
return BAD_FUNC_ARG;
#ifdef DEBUG_CRYPTOCB
wc_CryptoCb_InfoString(info);
#endif
if (info->algo_type == WC_ALGO_TYPE_PK) {
#ifndef NO_RSA
if (info->pk.type == WC_PK_TYPE_RSA) {
switch (info->pk.rsa.type) {
case RSA_PUBLIC_ENCRYPT:
case RSA_PUBLIC_DECRYPT:
/* set devId to invalid, so software is used */
info->pk.rsa.key->devId = INVALID_DEVID;
/* perform software based RSA public op */
ret = wc_RsaFunction(
info->pk.rsa.in, info->pk.rsa.inLen,
info->pk.rsa.out, info->pk.rsa.outLen,
info->pk.rsa.type, info->pk.rsa.key, info->pk.rsa.rng);
info->pk.rsa.key->devId = devIdArg; /* reset devId */
break;
case RSA_PRIVATE_ENCRYPT:
case RSA_PRIVATE_DECRYPT:
{
RsaKey rsaPriv;
byte* der = NULL;
size_t derSz = 0;
word32 idx = 0;
ret = load_file(myCtx->keyFilePriv, &der, &derSz);
if (ret != 0) {
printf("Error %d loading %s\n", ret, myCtx->keyFilePriv);
return ret;
}
ret = wc_InitRsaKey_ex(&rsaPriv, NULL, INVALID_DEVID);
if (ret != 0) {
return ret;
}
ret = wc_RsaPrivateKeyDecode(der, &idx, &rsaPriv, derSz);
if (ret == 0) {
/* perform software based RSA private op */
ret = wc_RsaFunction(
info->pk.rsa.in, info->pk.rsa.inLen,
info->pk.rsa.out, info->pk.rsa.outLen,
info->pk.rsa.type, &rsaPriv, info->pk.rsa.rng);
}
wc_FreeRsaKey(&rsaPriv);
if (der != NULL)
free(der);
break;
}
}
}
#endif /* !NO_RSA */
#ifdef HAVE_ECC
if (info->pk.type == WC_PK_TYPE_ECDSA_SIGN) {
#ifdef USE_PSA
uint8_t rs[MAX_ECC_BYTES * 2];
size_t rs_length;
psa_status_t status;
psa_algorithm_t hash_algo;
/* get the desired key ID into your callback context */
psa_key_id_t id = myCtx->key_id;
/* Get correct hash algorithm that matches input hash length */
hash_algo = psa_map_hash_alg(info->pk.eccsign.inlen);
status = psa_sign_hash(
id,
PSA_ALG_ECDSA(hash_algo),
info->pk.eccsign.in, info->pk.eccsign.inlen,
rs, sizeof(rs),
&rs_length);
if (status == PSA_SUCCESS) {
word32 point_len = (word32)(rs_length / 2);
ret = wc_ecc_rs_raw_to_sig(
rs, point_len,
rs + point_len, point_len,
info->pk.eccsign.out, info->pk.eccsign.outlen);
}
else {
ret = WC_HW_E;
}
#else
ecc_key eccPriv;
byte* der = NULL;
size_t derSz = 0;
word32 idx = 0;
ret = load_file(myCtx->keyFilePriv, &der, &derSz);
if (ret != 0) {
printf("Error %d loading %s\n", ret, myCtx->keyFilePriv);
return ret;
}
ret = wc_ecc_init_ex(&eccPriv, NULL, INVALID_DEVID);
if (ret != 0) {
return ret;
}
ret = wc_EccPrivateKeyDecode(der, &idx, &eccPriv, derSz);
if (ret == 0) {
ret = wc_ecc_sign_hash(
info->pk.eccsign.in, info->pk.eccsign.inlen,
info->pk.eccsign.out, info->pk.eccsign.outlen,
info->pk.eccsign.rng, &eccPriv);
}
wc_ecc_free(&eccPriv);
if (der != NULL)
free(der);
#endif
}
else if (info->pk.type == WC_PK_TYPE_ECDSA_VERIFY) {
/* set devId to invalid, so software is used */
info->pk.eccverify.key->devId = INVALID_DEVID;
ret = wc_ecc_verify_hash(
info->pk.eccverify.sig, info->pk.eccverify.siglen,
info->pk.eccverify.hash, info->pk.eccverify.hashlen,
info->pk.eccverify.res, info->pk.eccverify.key);
/* reset devId */
info->pk.eccverify.key->devId = devIdArg;
}
#endif /* HAVE_ECC */
}
(void)devIdArg;
(void)myCtx;
return ret;
}
static int signedData_sign(byte* cert, word32 certSz, byte* key,
word32 keySz, byte* out, word32 outSz,
int devId, int algOid, int hashOid)
{
int ret;
PKCS7* pkcs7;
WC_RNG rng;
/* init rng */
ret = wc_InitRng(&rng);
if (ret != 0) {
printf("ERROR: wc_InitRng() failed, ret = %d\n", ret);
return -1;
}
/* init PKCS7 */
pkcs7 = wc_PKCS7_New(NULL, devId);
if (pkcs7 == NULL) {
wc_FreeRng(&rng);
return -1;
}
ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz);
if (ret != 0) {
printf("ERROR: wc_PKCS7_InitWithCert() failed, ret = %d\n", ret);
wc_PKCS7_Free(pkcs7);
wc_FreeRng(&rng);
return -1;
}
pkcs7->rng = &rng;
pkcs7->content = (byte*)data;
pkcs7->contentSz = sizeof(data);
pkcs7->contentOID = DATA;
pkcs7->hashOID = hashOid;
pkcs7->encryptOID = algOid;
pkcs7->privateKey = key;
pkcs7->privateKeySz = keySz;
/* encode signedData, returns size */
ret = wc_PKCS7_EncodeSignedData(pkcs7, out, outSz);
if (ret <= 0) {
printf("ERROR: wc_PKCS7_EncodeSignedData() failed, ret = %d\n", ret);
wc_PKCS7_Free(pkcs7);
wc_FreeRng(&rng);
return -1;
}
else {
printf("Successfully encoded SignedData bundle\n");
#ifdef DEBUG_WOLFSSL
printf("Encoded DER (%d bytes):\n", ret);
WOLFSSL_BUFFER(out, ret);
#endif
}
wc_PKCS7_Free(pkcs7);
wc_FreeRng(&rng);
return ret;
}
static int signedData_verify(byte* in, word32 inSz, byte* cert,
word32 certSz, byte* key, word32 keySz,
byte* out, word32 outSz, int devId)
{
int ret;
PKCS7* pkcs7;
pkcs7 = wc_PKCS7_New(NULL, devId);
if (pkcs7 == NULL)
return -1;
/* decode signedData, returns size */
ret = wc_PKCS7_VerifySignedData(pkcs7, in, inSz);
if (ret < 0 || (pkcs7->contentSz != sizeof(data)) ||
(XMEMCMP(pkcs7->content, data, pkcs7->contentSz) != 0)) {
printf("ERROR: Failed to verify SignedData bundle, ret = %d\n", ret);
wc_PKCS7_Free(pkcs7);
return -1;
} else {
printf("Successfully verified SignedData bundle.\n");
#ifdef DEBUG_WOLFSSL
printf("Decoded content (%d bytes):\n", pkcs7->contentSz);
WOLFSSL_BUFFER(pkcs7->content, pkcs7->contentSz);
#endif
}
wc_PKCS7_Free(pkcs7);
return ret;
}
static void usage(void)
{
printf("Expected usage:\n");
printf("./signData-cryptocb [-ecc/-rsa] [-out=]\n");
printf("* -ecc/-rsa: Use ECC or RSA key (default is ECC)\n");
printf("* -key=file: DER formatted private key\n");
printf("* -keyPub=file: DER formatted public key\n");
printf("* -cert=file: Certificate for signing key\n");
printf("* -out=file: Generated PKCS7 file containing signed data and certificate (default %s)\n", OUTPUT_FILE);
}
int main(int argc, char** argv)
{
int ret, devId;
int encryptedSz, decryptedSz;
size_t certSz, keySz;
myCryptoCbCtx myCtx;
const char* certFile = NULL;
const char* keyFile = NULL;
const char* keyFilePub = NULL;
const char* outFile = OUTPUT_FILE;
int algOid = ECDSAk;
int hashOid = SHA256h;
byte* cert = NULL;
byte* key = NULL;
byte encrypted[LARGE_TEMP_SZ];
byte decrypted[LARGE_TEMP_SZ];
if (argc >= 2) {
if (XSTRCMP(argv[1], "-?") == 0 ||
XSTRCMP(argv[1], "-h") == 0 ||
XSTRCMP(argv[1], "--help") == 0) {
usage();
return 0;
}
}
while (argc > 1) {
if (XSTRCMP(argv[argc-1], "-ecc") == 0) {
algOid = ECDSAk;
}
else if (XSTRCMP(argv[argc-1], "-rsa") == 0) {
algOid = RSAk;
}
else if (XSTRNCMP(argv[argc-1], "-cert=",
XSTRLEN("-cert=")) == 0) {
certFile = argv[argc-1] + XSTRLEN("-cert=");
}
else if (XSTRNCMP(argv[argc-1], "-key=",
XSTRLEN("-key=")) == 0) {
keyFile = argv[argc-1] + XSTRLEN("-key=");
}
else if (XSTRNCMP(argv[argc-1], "-keyPub=",
XSTRLEN("-keyPub=")) == 0) {
keyFilePub = argv[argc-1] + XSTRLEN("-keyPub=");
}
else if (XSTRNCMP(argv[argc-1], "-out=",
XSTRLEN("-out=")) == 0) {
outFile = argv[argc-1] + XSTRLEN("-out=");
}
else {
printf("Warning: Unrecognized option: %s\n", argv[argc-1]);
}
argc--;
}
if (certFile == NULL) {
if (algOid == RSAk) {
certFile = RSA_CERT_FILE;
keyFile = RSA_KEY_FILE;
keyFilePub = RSA_KEYPUB_FILE;
}
else {
certFile = ECC_CERT_FILE;
keyFile = ECC_KEY_FILE;
keyFilePub = ECC_KEYPUB_FILE;
}
}
#ifdef DEBUG_WOLFSSL
wolfSSL_Debugging_ON();
#endif
ret = wolfCrypt_Init();
if (ret != 0) {
printf("wolfCrypt initialization failed\n");
return -1;
}
/* provide private key to crypto callback */
myCtx.keyFilePriv = keyFile;
#ifdef USE_PSA
ret = load_file(keyFile, &key, &keySz);
if (ret == 0) {
ret = import_ecc_key(key, keySz, &myCtx.key_id);
}
free(key); key = NULL;
if (ret != 0)
goto exit;
#endif
/* Load certificate and public key */
ret = load_file(certFile, &cert, &certSz);
if (ret == 0)
ret = load_file(keyFilePub, &key, &keySz);
if (ret != 0)
goto exit;
/* setting devId to something other than INVALID_DEVID, enables
* crypto callback to be used internally by wolfCrypt */
devId = 1;
ret = wc_CryptoCb_RegisterDevice(devId, myCryptoCb, &myCtx);
if (ret != 0) {
printf("Failed to register crypto dev device, ret = %d\n", ret);
goto exit;
}
/* Sign bundle */
encryptedSz = signedData_sign(cert, certSz, key, keySz,
encrypted, sizeof(encrypted), devId, algOid, hashOid);
if (encryptedSz >= 0) {
if (write_file(outFile, encrypted, encryptedSz) != 0) {
printf("ERROR: error writing encoded to output file %s\n", outFile);
ret =-1;
}
else {
printf("Wrote encoded PKCS7 bundle to file %s\n", outFile);
ret = 0;
}
}
else {
ret = -1; goto exit;
}
/* Verify bundle */
decryptedSz = signedData_verify(encrypted, encryptedSz,
cert, certSz, key, keySz,
decrypted, sizeof(decrypted), devId);
if (decryptedSz < 0) {
ret = -1; goto exit;
}
exit:
if (cert != NULL) free(cert);
if (key != NULL) free(key);
wolfCrypt_Cleanup();
return ret;
}
#else
int main(int argc, char** argv)
{
printf("Must build wolfSSL using ./configure --enable-pkcs7 --enable-cryptocb\n");
return 0;
}
#endif /* HAVE_PKCS7 & WOLF_CRYPTO_DEV */