* This file is part of the openHiTLS project.
*
* openHiTLS is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include "app_req.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stddef.h>
#include <limits.h>
#include "app_errno.h"
#include "app_print.h"
#include "app_opt.h"
#include "app_utils.h"
#include "bsl_sal.h"
#include "bsl_errno.h"
#include "crypt_errno.h"
#include "crypt_eal_cipher.h"
#include "crypt_eal_rand.h"
#include "hitls_pki_errno.h"
#define HITLS_APP_REQ_SECTION "req"
#define HITLS_APP_REQ_EXTENSION_SECTION "req_extensions"
typedef enum {
HITLS_REQ_APP_OPT_NEW = 2,
HITLS_REQ_APP_OPT_VERIFY,
HITLS_REQ_APP_OPT_MDALG,
HITLS_REQ_APP_OPT_SUBJ,
HITLS_REQ_APP_OPT_KEY,
HITLS_REQ_APP_OPT_KEYFORM,
HITLS_REQ_APP_OPT_PASSIN,
HITLS_REQ_APP_OPT_PASSOUT,
HITLS_REQ_APP_OPT_NOOUT,
HITLS_REQ_APP_OPT_TEXT,
HITLS_REQ_APP_OPT_CONFIG,
HITLS_REQ_APP_OPT_IN,
HITLS_REQ_APP_OPT_INFORM,
HITLS_REQ_APP_OPT_OUT,
HITLS_REQ_APP_OPT_OUTFORM,
} HITLSOptType;
static const HITLS_CmdOption g_reqOpts[] = {
{"help", HITLS_APP_OPT_HELP, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "Display this function summary"},
{"new", HITLS_REQ_APP_OPT_NEW, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "New request"},
{"verify", HITLS_REQ_APP_OPT_VERIFY, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "Verify self-signature on the request"},
{"mdalg", HITLS_REQ_APP_OPT_MDALG, HITLS_APP_OPT_VALUETYPE_STRING, "Any supported digest"},
{"subj", HITLS_REQ_APP_OPT_SUBJ, HITLS_APP_OPT_VALUETYPE_STRING, "Set or modify subject of request or cert"},
{"key", HITLS_REQ_APP_OPT_KEY, HITLS_APP_OPT_VALUETYPE_STRING, "Key for signing, and to include unless -in given"},
{"keyform", HITLS_REQ_APP_OPT_KEYFORM, HITLS_APP_OPT_VALUETYPE_FMT_PEMDER, "Input format - DER or PEM"},
{"passin", HITLS_REQ_APP_OPT_PASSIN, HITLS_APP_OPT_VALUETYPE_STRING, "Private key and certificate password source"},
{"passout", HITLS_REQ_APP_OPT_PASSOUT, HITLS_APP_OPT_VALUETYPE_STRING, "Output file pass phrase source"},
{"noout", HITLS_REQ_APP_OPT_NOOUT, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "Do not output REQ"},
{"text", HITLS_REQ_APP_OPT_TEXT, HITLS_APP_OPT_VALUETYPE_NO_VALUE, "Text form of request"},
{"config", HITLS_REQ_APP_OPT_CONFIG, HITLS_APP_OPT_VALUETYPE_IN_FILE, "Request template file"},
{"in", HITLS_REQ_APP_OPT_IN, HITLS_APP_OPT_VALUETYPE_IN_FILE, "X.509 request input file (default stdin)"},
{"inform", HITLS_REQ_APP_OPT_INFORM, HITLS_APP_OPT_VALUETYPE_FMT_PEMDER, "Input format - DER or PEM"},
{"out", HITLS_REQ_APP_OPT_OUT, HITLS_APP_OPT_VALUETYPE_OUT_FILE, "Output file"},
{"outform", HITLS_REQ_APP_OPT_OUTFORM, HITLS_APP_OPT_VALUETYPE_FMT_PEMDER, "Output format - DER or PEM"},
{NULL, 0, 0, NULL},
};
typedef struct {
char *inFilePath;
BSL_ParseFormat inFormat;
bool verify;
} ReqGeneralOptions;
typedef struct {
bool new;
char *configFilePath;
bool text;
char *subj;
} ReqCertOptions;
typedef struct {
char *keyFilePath;
BSL_ParseFormat keyFormat;
char *passInArg;
char *passOutArg;
int32_t mdalgId;
} ReqKeysAndSignOptions;
typedef struct {
char *outFilePath;
BSL_ParseFormat outFormat;
bool noout;
} ReqOutputOptions;
typedef struct {
ReqGeneralOptions genOpt;
ReqCertOptions certOpt;
ReqKeysAndSignOptions keyAndSignOpt;
ReqOutputOptions outPutOpt;
char *passin;
char *passout;
HITLS_X509_Csr *csr;
CRYPT_EAL_PkeyCtx *pkey;
BSL_UIO *wUio;
BSL_Buffer encode;
HITLS_X509_Ext *ext;
BSL_CONF *conf;
} ReqOptCtx;
typedef int32_t (*ReqOptHandleFunc)(ReqOptCtx *);
typedef struct {
int optType;
ReqOptHandleFunc func;
} ReqOptHandleTable;
static int32_t ReqOptErr(ReqOptCtx *optCtx)
{
(void)optCtx;
AppPrintError("req: Use -help for summary.\n");
return HITLS_APP_OPT_UNKOWN;
}
static int32_t ReqOptHelp(ReqOptCtx *optCtx)
{
(void)optCtx;
HITLS_APP_OptHelpPrint(g_reqOpts);
return HITLS_APP_HELP;
}
static int32_t ReqOptNew(ReqOptCtx *optCtx)
{
optCtx->certOpt.new = true;
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptVerify(ReqOptCtx *optCtx)
{
optCtx->genOpt.verify = true;
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptMdAlg(ReqOptCtx *optCtx)
{
return HITLS_APP_GetAndCheckHashOpt(HITLS_APP_OptGetValueStr(), &optCtx->keyAndSignOpt.mdalgId);
}
static int32_t ReqOptSubj(ReqOptCtx *optCtx)
{
optCtx->certOpt.subj = HITLS_APP_OptGetValueStr();
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptKey(ReqOptCtx *optCtx)
{
optCtx->keyAndSignOpt.keyFilePath = HITLS_APP_OptGetValueStr();
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptKeyFormat(ReqOptCtx *optCtx)
{
return HITLS_APP_OptGetFormatType(HITLS_APP_OptGetValueStr(), HITLS_APP_OPT_VALUETYPE_FMT_ANY,
&optCtx->keyAndSignOpt.keyFormat);
}
static int32_t ReqOptPassin(ReqOptCtx *optCtx)
{
optCtx->keyAndSignOpt.passInArg = HITLS_APP_OptGetValueStr();
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptPassout(ReqOptCtx *optCtx)
{
optCtx->keyAndSignOpt.passOutArg = HITLS_APP_OptGetValueStr();
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptNoout(ReqOptCtx *optCtx)
{
optCtx->outPutOpt.noout = true;
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptText(ReqOptCtx *optCtx)
{
optCtx->certOpt.text = true;
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptConfig(ReqOptCtx *optCtx)
{
optCtx->certOpt.configFilePath = HITLS_APP_OptGetValueStr();
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptIn(ReqOptCtx *optCtx)
{
optCtx->genOpt.inFilePath = HITLS_APP_OptGetValueStr();
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptInFormat(ReqOptCtx *optCtx)
{
return HITLS_APP_OptGetFormatType(HITLS_APP_OptGetValueStr(), HITLS_APP_OPT_VALUETYPE_FMT_PEMDER,
&optCtx->genOpt.inFormat);
}
static int32_t ReqOptOut(ReqOptCtx *optCtx)
{
optCtx->outPutOpt.outFilePath = HITLS_APP_OptGetValueStr();
return HITLS_APP_SUCCESS;
}
static int32_t ReqOptOutFormat(ReqOptCtx *optCtx)
{
return HITLS_APP_OptGetFormatType(HITLS_APP_OptGetValueStr(), HITLS_APP_OPT_VALUETYPE_FMT_PEMDER,
&optCtx->outPutOpt.outFormat);
}
static const ReqOptHandleTable g_reqOptHandleTable[] = {
{HITLS_APP_OPT_ERR, ReqOptErr},
{HITLS_APP_OPT_HELP, ReqOptHelp},
{HITLS_REQ_APP_OPT_NEW, ReqOptNew},
{HITLS_REQ_APP_OPT_VERIFY, ReqOptVerify},
{HITLS_REQ_APP_OPT_MDALG, ReqOptMdAlg},
{HITLS_REQ_APP_OPT_SUBJ, ReqOptSubj},
{HITLS_REQ_APP_OPT_KEY, ReqOptKey},
{HITLS_REQ_APP_OPT_KEYFORM, ReqOptKeyFormat},
{HITLS_REQ_APP_OPT_PASSIN, ReqOptPassin},
{HITLS_REQ_APP_OPT_PASSOUT, ReqOptPassout},
{HITLS_REQ_APP_OPT_NOOUT, ReqOptNoout},
{HITLS_REQ_APP_OPT_TEXT, ReqOptText},
{HITLS_REQ_APP_OPT_CONFIG, ReqOptConfig},
{HITLS_REQ_APP_OPT_IN, ReqOptIn},
{HITLS_REQ_APP_OPT_INFORM, ReqOptInFormat},
{HITLS_REQ_APP_OPT_OUT, ReqOptOut},
{HITLS_REQ_APP_OPT_OUTFORM, ReqOptOutFormat},
};
static int32_t ParseReqOpt(ReqOptCtx *optCtx)
{
int32_t ret = HITLS_APP_SUCCESS;
int optType = HITLS_APP_OPT_ERR;
while ((ret == HITLS_APP_SUCCESS) && ((optType = HITLS_APP_OptNext()) != HITLS_APP_OPT_EOF)) {
for (size_t i = 0; i < (sizeof(g_reqOptHandleTable) / sizeof(g_reqOptHandleTable[0])); ++i) {
if (optType == g_reqOptHandleTable[i].optType) {
ret = g_reqOptHandleTable[i].func(optCtx);
break;
}
}
}
if ((ret == HITLS_APP_SUCCESS) && (HITLS_APP_GetRestOptNum() != 0)) {
AppPrintError("Extra arguments given.\n");
AppPrintError("req: Use -help for summary.\n");
ret = HITLS_APP_OPT_UNKOWN;
}
if ((HITLS_APP_ParsePasswd(optCtx->keyAndSignOpt.passInArg, &optCtx->passin) != HITLS_APP_SUCCESS) ||
(HITLS_APP_ParsePasswd(optCtx->keyAndSignOpt.passOutArg, &optCtx->passout) != HITLS_APP_SUCCESS)) {
return HITLS_APP_PASSWD_FAIL;
}
return ret;
}
static int32_t ReqLoadPrvKey(ReqOptCtx *optCtx)
{
if (optCtx->keyAndSignOpt.keyFilePath == NULL) {
optCtx->pkey = HITLS_APP_GenRsaPkeyCtx(2048);
if (optCtx->pkey == NULL) {
return HITLS_APP_CRYPTO_FAIL;
}
int32_t ret = HITLS_APP_PrintPrvKey(
optCtx->pkey, "private.pem", BSL_FORMAT_PEM, CRYPT_CIPHER_AES256_CBC, &optCtx->passout);
return ret;
}
optCtx->pkey =
HITLS_APP_LoadPrvKey(optCtx->keyAndSignOpt.keyFilePath, optCtx->keyAndSignOpt.keyFormat, &optCtx->passin);
if (optCtx->pkey == NULL) {
return HITLS_APP_LOAD_KEY_FAIL;
}
return HITLS_APP_SUCCESS;
}
static int32_t SetRequestedExt(ReqOptCtx *optCtx)
{
if (optCtx->ext == NULL) {
return HITLS_APP_SUCCESS;
}
BslList *attrList = NULL;
int32_t ret = HITLS_X509_CsrCtrl(optCtx->csr, HITLS_X509_CSR_GET_ATTRIBUTES, &attrList, sizeof(BslList *));
if (ret != HITLS_PKI_SUCCESS) {
AppPrintError("req: Failed to get attr the csr, errCode = %d.\n", ret);
return HITLS_APP_X509_FAIL;
}
HITLS_X509_Attrs *attrs = NULL;
ret = HITLS_X509_CsrCtrl(optCtx->csr, HITLS_X509_CSR_GET_ATTRIBUTES, &attrs, sizeof(HITLS_X509_Attrs *));
if (ret != HITLS_PKI_SUCCESS) {
AppPrintError("req: Failed to get attrs from the csr, errCode = %d.\n", ret);
return HITLS_APP_X509_FAIL;
}
ret = HITLS_X509_AttrCtrl(attrs, HITLS_X509_ATTR_SET_REQUESTED_EXTENSIONS, optCtx->ext, 0);
if (ret != HITLS_PKI_SUCCESS) {
AppPrintError("req: Failed to set attr the csr, errCode = %d.\n", ret);
return HITLS_APP_X509_FAIL;
}
return HITLS_APP_SUCCESS;
}
static int32_t GetSignMdId(ReqOptCtx *optCtx)
{
CRYPT_PKEY_AlgId id = CRYPT_EAL_PkeyGetId(optCtx->pkey);
int32_t mdalgId = optCtx->keyAndSignOpt.mdalgId;
if (mdalgId == CRYPT_MD_MAX) {
if (id == CRYPT_PKEY_ED25519) {
mdalgId = CRYPT_MD_SHA512;
} else if (id == CRYPT_PKEY_SM2) {
mdalgId = CRYPT_MD_SM3;
} else {
mdalgId = CRYPT_MD_SHA256;
}
}
return mdalgId;
}
static int32_t ProcSanExt(BslCid cid, void *val, void *ctx)
{
HITLS_X509_Ext *ext = ctx;
switch (cid) {
case BSL_CID_CE_SUBJECTALTNAME:
return HITLS_X509_ExtCtrl(ext, HITLS_X509_EXT_SET_SAN, val, sizeof(HITLS_X509_ExtSan));
default:
return HITLS_APP_CONF_FAIL;
}
}
static int32_t ParseConf(ReqOptCtx *optCtx)
{
if (!optCtx->certOpt.new || (optCtx->certOpt.configFilePath == NULL)) {
return HITLS_APP_SUCCESS;
}
optCtx->ext = HITLS_X509_ExtNew(HITLS_X509_EXT_TYPE_CSR);
if (optCtx->ext == NULL) {
AppPrintError("req: Failed to create the ext context.\n");
return HITLS_APP_X509_FAIL;
}
optCtx->conf = BSL_CONF_New(BSL_CONF_DefaultMethod());
if (optCtx->conf == NULL) {
AppPrintError("req: Failed to create profile context.\n");
return HITLS_APP_CONF_FAIL;
}
char extSectionStr[BSL_CONF_SEC_SIZE + 1] = {0};
uint32_t extSectionStrLen = sizeof(extSectionStr);
int32_t ret = BSL_CONF_Load(optCtx->conf, optCtx->certOpt.configFilePath);
if (ret != BSL_SUCCESS) {
AppPrintError("req: Failed to load the config file %s.\n", optCtx->certOpt.configFilePath);
return HITLS_APP_CONF_FAIL;
}
ret = BSL_CONF_GetString(optCtx->conf, HITLS_APP_REQ_SECTION, HITLS_APP_REQ_EXTENSION_SECTION,
extSectionStr, &extSectionStrLen);
if (ret == BSL_CONF_VALUE_NOT_FOUND) {
return HITLS_APP_SUCCESS;
} else if (ret != BSL_SUCCESS) {
AppPrintError("req: Failed to get req_extensions, config file %s.\n", optCtx->certOpt.configFilePath);
return HITLS_APP_CONF_FAIL;
}
ret = HITLS_APP_CONF_ProcExt(optCtx->conf, extSectionStr, ProcSanExt, optCtx->ext);
if (ret == HITLS_APP_NO_EXT) {
return HITLS_APP_SUCCESS;
} else if (ret != BSL_SUCCESS) {
AppPrintError("req: Failed to parse SAN from config file %s.\n", optCtx->certOpt.configFilePath);
return HITLS_APP_CONF_FAIL;
}
return HITLS_APP_SUCCESS;
}
static int32_t ReqGen(ReqOptCtx *optCtx)
{
if (optCtx->certOpt.subj == NULL) {
AppPrintError("req: -subj must be included when -new is used.\n");
return HITLS_APP_INVALID_ARG;
}
if (optCtx->genOpt.inFilePath != NULL) {
AppPrintError("req: ignore -in option when generating csr.\n");
}
int32_t ret = ReqLoadPrvKey(optCtx);
if (ret != HITLS_APP_SUCCESS) {
return ret;
}
optCtx->csr = HITLS_X509_CsrNew();
if (optCtx->csr == NULL) {
AppPrintError("req: Failed to create the csr context.\n");
return HITLS_APP_X509_FAIL;
}
ret = HITLS_X509_CsrCtrl(optCtx->csr, HITLS_X509_SET_PUBKEY, optCtx->pkey, sizeof(CRYPT_EAL_PkeyCtx *));
if (ret != HITLS_PKI_SUCCESS) {
AppPrintError("req: Failed to set public the csr, errCode = %d.\n", ret);
return HITLS_APP_X509_FAIL;
}
if (ParseConf(optCtx) != HITLS_APP_SUCCESS) {
return HITLS_APP_CONF_FAIL;
}
ret = HITLS_APP_CFG_ProcDnName(optCtx->certOpt.subj, HiTLS_AddSubjDnNameToCsr, optCtx->csr);
if (ret != HITLS_PKI_SUCCESS) {
AppPrintError("req: Failed to set subject name the csr, errCode = %d.\n", ret);
return HITLS_APP_X509_FAIL;
}
ret = SetRequestedExt(optCtx);
if (ret != HITLS_PKI_SUCCESS) {
return ret;
}
ret = HITLS_X509_CsrSign(GetSignMdId(optCtx), optCtx->pkey, NULL, optCtx->csr);
if (ret != HITLS_PKI_SUCCESS) {
AppPrintError("req: Failed to sign the csr, errCode = %x.\n", ret);
return HITLS_APP_X509_FAIL;
}
ret = HITLS_X509_CsrGenBuff(optCtx->outPutOpt.outFormat, optCtx->csr, &optCtx->encode);
if (ret != HITLS_PKI_SUCCESS) {
AppPrintError("req: Failed to generate the csr, errCode = %x.\n", ret);
}
return ret;
}
static int32_t ReqLoad(ReqOptCtx *optCtx)
{
optCtx->csr = HITLS_APP_LoadCsr(optCtx->genOpt.inFilePath, optCtx->genOpt.inFormat);
if (optCtx->csr == NULL) {
return HITLS_APP_X509_FAIL;
}
return HITLS_APP_SUCCESS;
}
static void ReqVerify(ReqOptCtx *optCtx)
{
int32_t ret = HITLS_X509_CsrVerify(optCtx->csr);
if (ret == HITLS_PKI_SUCCESS) {
AppPrintError("req: verify ok.\n");
} else {
AppPrintError("req: verify failure, errCode = %d.\n", ret);
}
}
static int32_t ReqOutput(ReqOptCtx *optCtx)
{
if (optCtx->outPutOpt.noout && !optCtx->certOpt.text) {
return HITLS_APP_SUCCESS;
}
optCtx->wUio = HITLS_APP_UioOpen(optCtx->outPutOpt.outFilePath, 'w',
optCtx->outPutOpt.outFilePath != NULL ? 1 : 0);
if (optCtx->wUio == NULL) {
return HITLS_APP_UIO_FAIL;
}
int32_t ret;
if (optCtx->certOpt.text) {
ret = HITLS_PKI_PrintCtrl(HITLS_PKI_PRINT_CSR, optCtx->csr, sizeof(HITLS_X509_Csr *), optCtx->wUio);
if (ret != HITLS_PKI_SUCCESS) {
AppPrintError("x509: print csr failed, errCode = %d.\n", ret);
return HITLS_APP_X509_FAIL;
}
}
if (optCtx->outPutOpt.noout) {
return HITLS_APP_SUCCESS;
}
if (optCtx->encode.data == NULL) {
ret = HITLS_X509_CsrGenBuff(optCtx->outPutOpt.outFormat, optCtx->csr, &optCtx->encode);
if (ret != 0) {
AppPrintError("x509: encode csr failed, errCode = %d.\n", ret);
return HITLS_APP_X509_FAIL;
}
}
uint32_t writeLen = 0;
ret = BSL_UIO_Write(optCtx->wUio, optCtx->encode.data, optCtx->encode.dataLen, &writeLen);
if (ret != 0 || writeLen != optCtx->encode.dataLen) {
AppPrintError("req: write csr failed, errCode = %d, writeLen = %u.\n", ret, writeLen);
return HITLS_APP_UIO_FAIL;
}
return HITLS_APP_SUCCESS;
}
static void InitReqOptCtx(ReqOptCtx *optCtx)
{
optCtx->genOpt.inFormat = BSL_FORMAT_PEM;
optCtx->keyAndSignOpt.keyFormat = BSL_FORMAT_UNKNOWN;
optCtx->outPutOpt.outFormat = BSL_FORMAT_PEM;
}
static void UnInitReqOptCtx(ReqOptCtx *optCtx)
{
if (optCtx->passin != NULL) {
BSL_SAL_ClearFree(optCtx->passin, strlen(optCtx->passin));
}
if (optCtx->passout != NULL) {
BSL_SAL_ClearFree(optCtx->passout, strlen(optCtx->passout));
}
HITLS_X509_CsrFree(optCtx->csr);
CRYPT_EAL_PkeyFreeCtx(optCtx->pkey);
BSL_UIO_Free(optCtx->wUio);
BSL_SAL_FREE(optCtx->encode.data);
HITLS_X509_ExtFree(optCtx->ext);
BSL_CONF_Free(optCtx->conf);
}
int32_t HITLS_ReqMain(int argc, char *argv[])
{
ReqOptCtx optCtx = {0};
InitReqOptCtx(&optCtx);
int32_t ret = HITLS_APP_SUCCESS;
do {
ret = HITLS_APP_OptBegin(argc, argv, g_reqOpts);
if (ret != HITLS_APP_SUCCESS) {
AppPrintError("req: error in opt begin.\n");
break;
}
if (CRYPT_EAL_ProviderRandInitCtx(NULL, CRYPT_RAND_AES128_CTR,
"provider=default", NULL, 0, NULL) != CRYPT_SUCCESS) {
AppPrintError("req: failed to init rand.\n");
ret = HITLS_APP_CRYPTO_FAIL;
break;
}
ret = ParseReqOpt(&optCtx);
if (ret != HITLS_APP_SUCCESS) {
break;
}
if (optCtx.certOpt.new) {
ret = ReqGen(&optCtx);
} else {
ret = ReqLoad(&optCtx);
}
if (ret != HITLS_APP_SUCCESS) {
break;
}
if (optCtx.genOpt.verify) {
ReqVerify(&optCtx);
}
ret = ReqOutput(&optCtx);
} while (false);
CRYPT_EAL_RandDeinitEx(NULL);
UnInitReqOptCtx(&optCtx);
HITLS_APP_OptEnd();
return ret;
}