Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsCertGen.h"
#include "ScopedNSSTypes.h"
#include "cert.h"
#include "secoid.h"
#include "certdb.h"
#include "nss.h"
#include "nsString.h"
#include "base64.h"
// Much of this code was copied from certutil.c
// Some code was copied from security/manager/ssl/nsKeygenHandler.cpp
// on old branch mozilla-esr68.
// The code was reordered to have proper cleanup without using goto.
typedef struct curveNameTagParamsStr {
const char* curveName;
SECOidTag curveOidTag;
SECOidTag hashAlgTag;
} curveNameTagParams;
static curveNameTagParams nameTagParams[] = {
{"secp256r1", SEC_OID_SECG_EC_SECP256R1, SEC_OID_SHA256},
{"secp384r1", SEC_OID_SECG_EC_SECP384R1, SEC_OID_SHA384},
{"secp521r1", SEC_OID_SECG_EC_SECP521R1, SEC_OID_SHA512},
};
mozilla::UniqueSECItem EncodeECParams(const char* curve,
SECOidTag& hashAlgTag) {
SECOidData* oidData = nullptr;
SECOidTag curveOidTag = SEC_OID_UNKNOWN; /* default */
if (curve && *curve) {
int numCurves = sizeof(nameTagParams) / sizeof(curveNameTagParams);
for (int i = 0; ((i < numCurves) && (curveOidTag == SEC_OID_UNKNOWN));
i++) {
if (PL_strcmp(curve, nameTagParams[i].curveName) == 0) {
curveOidTag = nameTagParams[i].curveOidTag;
hashAlgTag = nameTagParams[i].hashAlgTag;
}
}
}
/* Return nullptr if curve name is not recognized */
if ((curveOidTag == SEC_OID_UNKNOWN) ||
(oidData = SECOID_FindOIDByTag(curveOidTag)) == nullptr) {
return nullptr;
}
mozilla::UniqueSECItem ecparams(
SECITEM_AllocItem(nullptr, nullptr, 2 + oidData->oid.len));
if (!ecparams) {
return nullptr;
}
/*
* ecparams->data needs to contain the ASN encoding of an object ID (OID)
* representing the named curve. The actual OID is in
* oidData->oid.data so we simply prepend 0x06 and OID length
*/
ecparams->data[0] = SEC_ASN1_OBJECT_ID;
ecparams->data[1] = oidData->oid.len;
memcpy(ecparams->data + 2, oidData->oid.data, oidData->oid.len);
return ecparams;
}
#define DEFAULT_RSA_KEYGEN_PE 65537L
NS_IMPL_ISUPPORTS(nsCertGen, nsICertGen)
/* Partial copy from NSS certutil. Removed was reading of the type from
* the name string. Removed was support for types other than email.
* Parameter constNames is a comma separated list. */
static SECStatus AddEmailSubjectAltNames(PLArenaPool* arena,
CERTGeneralName** existingListp,
const char* constNames) {
CERTGeneralName* nameList = NULL;
CERTGeneralName* current = NULL;
PRCList* prev = NULL;
char *cp, *nextName = NULL;
SECStatus rv = SECSuccess;
char* names = NULL;
if (constNames) {
names = PORT_Strdup(constNames);
}
if (names == NULL) {
return SECFailure;
}
/*
* walk down the comma separated list of names. NOTE: there is
* no sanity checks to see if the email address look like
* email addresses.
*/
for (cp = names; cp; cp = nextName) {
nextName = NULL;
if (*cp == ',') {
cp++;
}
if ((*cp) == 0) {
continue;
}
current = PORT_ArenaZNew(arena, CERTGeneralName);
if (!current) {
rv = SECFailure;
break;
}
current->type = certRFC822Name;
current->name.other.data = (unsigned char*)PORT_ArenaStrdup(arena, cp);
current->name.other.len = PORT_Strlen(cp);
if (prev) {
current->l.prev = prev;
prev->next = &(current->l);
} else {
nameList = current;
}
prev = &(current->l);
}
PORT_Free(names);
/* at this point nameList points to the head of a doubly linked,
* but not yet circular, list and current points to its tail. */
if (rv == SECSuccess && nameList) {
if (*existingListp != NULL) {
PRCList* existingprev;
/* add nameList to the end of the existing list */
existingprev = (*existingListp)->l.prev;
(*existingListp)->l.prev = &(current->l);
nameList->l.prev = existingprev;
existingprev->next = &(nameList->l);
current->l.next = &((*existingListp)->l);
} else {
/* make nameList circular and set it as the new existingList */
nameList->l.prev = prev;
current->l.next = &(nameList->l);
*existingListp = nameList;
}
}
return rv;
}
NS_IMETHODIMP nsCertGen::Gen(const nsACString& keyType,
const nsACString& keyStrength,
const nsAString& email, nsACString& _retval) {
uint32_t keyGenMechanism;
SECOidTag hashAlgTag;
KeyType keytype = rsaKey;
PK11RSAGenParams rsaParams;
mozilla::UniqueSECItem ecParams;
void* params = nullptr; // Non-owning.
nsresult result = NS_ERROR_FAILURE;
_retval = "";
if (keyType == "RSA") {
keyGenMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN;
keytype = rsaKey;
if (keyStrength == "2048") {
rsaParams.keySizeInBits = 2048;
hashAlgTag = SEC_OID_SHA256;
} else if (keyStrength == "3072") {
rsaParams.keySizeInBits = 3072;
hashAlgTag = SEC_OID_SHA384;
} else if (keyStrength == "4096") {
rsaParams.keySizeInBits = 4096;
hashAlgTag = SEC_OID_SHA512;
} else {
return NS_ERROR_INVALID_ARG;
}
rsaParams.pe = DEFAULT_RSA_KEYGEN_PE;
params = &rsaParams;
} else if (keyType == "ECC") {
keyGenMechanism = CKM_EC_KEY_PAIR_GEN;
keytype = ecKey;
ecParams =
EncodeECParams(PromiseFlatCString(keyStrength).get(), hashAlgTag);
params = ecParams.get();
} else {
return NS_ERROR_INVALID_ARG;
}
// permanent and sensitive flags for keygen
PK11AttrFlags attrFlags =
PK11_ATTR_TOKEN | PK11_ATTR_SENSITIVE | PK11_ATTR_PRIVATE;
SECKEYPrivateKey* privateKey = nullptr;
SECKEYPublicKey* publicKey = nullptr;
mozilla::UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
PK11SlotInfo* slot = PK11_GetInternalKeySlot();
if (slot) {
privateKey = PK11_GenerateKeyPairWithFlags(slot, keyGenMechanism, params,
&publicKey, attrFlags, nullptr);
}
CERTSubjectPublicKeyInfo* spki = nullptr;
if (privateKey && publicKey) {
spki = SECKEY_CreateSubjectPublicKeyInfo(publicKey);
}
CERTCertificateRequest* cr = nullptr;
if (spki) {
CERTName* subject = NULL;
if (email.Length() > 0) {
nsAutoCString dn("E=");
dn += NS_LossyConvertUTF16toASCII(email);
subject = CERT_AsciiToName(dn.get());
} else {
subject = CERT_CreateName(NULL);
}
if (subject) {
cr = CERT_CreateCertificateRequest(subject, spki, NULL);
CERT_DestroyName(subject);
}
SECKEY_DestroySubjectPublicKeyInfo(spki);
}
SECItem* requestEncoding = nullptr;
if (cr) {
if (email.Length() > 0) {
void* extHandle;
extHandle = CERT_StartCertificateRequestAttributes(cr);
if (extHandle == NULL) {
CERT_DestroyCertificateRequest(cr);
return NS_ERROR_FAILURE;
}
CERTGeneralName* namelist = NULL;
SECItem item = {siBuffer, NULL, 0};
if (AddEmailSubjectAltNames(arena.get(), &namelist,
NS_ConvertUTF16toUTF8(email).get()) ==
SECSuccess) {
if (CERT_EncodeAltNameExtension(arena.get(), namelist, &item) ==
SECSuccess) {
if (CERT_AddExtension(extHandle, SEC_OID_X509_SUBJECT_ALT_NAME, &item,
PR_FALSE, PR_TRUE) != SECSuccess) {
return NS_ERROR_FAILURE;
}
}
}
CERT_FinishExtensions(extHandle);
CERT_FinishCertificateRequestAttributes(cr);
}
/* Der encode the request */
requestEncoding = SEC_ASN1EncodeItem(
arena.get(), NULL, cr, SEC_ASN1_GET(CERT_CertificateRequestTemplate));
CERT_DestroyCertificateRequest(cr);
}
if (requestEncoding) {
SECOidTag signAlgTag;
signAlgTag = SEC_GetSignatureAlgorithmOidTag(keytype, hashAlgTag);
if (signAlgTag != SEC_OID_UNKNOWN) {
SECAlgorithmID signAlg;
PORT_Memset(&signAlg, 0, sizeof(signAlg));
SECStatus rv;
rv = SECOID_SetAlgorithmID(arena.get(), &signAlg, signAlgTag, 0);
if (rv == SECSuccess) {
SECItem signedReq = {siBuffer, NULL, 0};
rv = SEC_DerSignDataWithAlgorithmID(
arena.get(), &signedReq, requestEncoding->data,
requestEncoding->len, privateKey, &signAlg);
if (rv == SECSuccess) {
char* obuf;
obuf = BTOA_ConvertItemToAscii(&signedReq);
if (obuf) {
_retval = "-----BEGIN CERTIFICATE REQUEST-----\n";
_retval += obuf;
_retval += "\n-----END CERTIFICATE REQUEST-----\n";
result = NS_OK;
PORT_Free(obuf);
}
}
}
}
}
if (slot) {
PK11_FreeSlot(slot);
}
if (privateKey) {
SECKEY_DestroyPrivateKey(privateKey);
}
if (publicKey) {
SECKEY_DestroyPublicKey(publicKey);
}
return result;
}