xca/lib/pki_pkcs12.cpp
Christian Hohnstaedt 53cbeea783
Some checks failed
CMake / build (build/xca-*-Linux.tar.gz, Unix Makefiles, linux, /usr, ubuntu, 5.15.2, ubuntu-latest) (push) Has been cancelled
CMake / build (build/xca-*-Linux.tar.gz, Unix Makefiles, linux, /usr, ubuntu, 6.6.2, ubuntu-latest) (push) Has been cancelled
CMake / build (build/xca-*.dmg, Unix Makefiles, mac, /opt/homebrew/opt/openssl, macos, 6.6.2, macos-latest) (push) Has been cancelled
CMake / build (build/xca-*.msi build/xca-portable-*.zip , MinGW Makefiles, windows, D:\msys2\msys64\mingw64, windows, 5.15.2, windows-2019, win64_mingw81) (push) Has been cancelled
Close #152: How can we specify the Cryptographic Service Provider
A comment line of the format "CSP: <Provider Name>"
in the private key will be used as CSP during PKCS#12 / PFX
export.
2024-09-30 19:07:12 +02:00

246 lines
6.1 KiB
C++

/* vi: set sw=4 ts=4:
*
* Copyright (C) 2001 - 2020 Christian Hohnstaedt.
*
* All rights reserved.
*/
#include "pki_pkcs12.h"
#include "pki_evp.h"
#include "pki_x509.h"
#include "pass_info.h"
#include "exception.h"
#include "func.h"
#include "PwDialogCore.h"
#include "XcaWarningCore.h"
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#include <openssl/stack.h>
pki_pkcs12::pki_pkcs12(const QString &d, pki_x509 *acert, pki_key *akey)
:pki_multi(d), cert(acert), key(akey)
{
append_item(key);
append_item(cert);
}
pki_pkcs12::pki_pkcs12(const QString &fname)
:pki_multi(fname)
{
Passwd pass;
EVP_PKEY *mykey = NULL;
X509 *mycert = NULL;
pass_info p(XCA_TITLE, tr("Please enter the password to decrypt the PKCS#12 file:\n%1").arg(compressFilename(fname)));
const X509_ALGOR *macalgid = NULL;
const ASN1_INTEGER *maciter = NULL;
const ASN1_OBJECT *macobj = NULL;
setFilename(fname);
XFile file(fname);
file.open_read();
BioByteArray b(file.readAll());
PKCS12 *pkcs12 = d2i_PKCS12_bio(b.ro(), NULL);
if (pki_ign_openssl_error()) {
if (pkcs12)
PKCS12_free(pkcs12);
throw errorEx(tr("Unable to load the PKCS#12 (pfx) file %1.")
.arg(fname));
}
PKCS12_get0_mac(NULL, &macalgid, NULL, &maciter, pkcs12);
if (macalgid)
X509_ALGOR_get0(&macobj, NULL, NULL, macalgid);
if (macobj) {
algorithm = OBJ_obj2QString(macobj);
if (maciter)
algorithm += QString(", iteration %1").arg(a1int(maciter).toDec());
}
while (!PKCS12_verify_mac(pkcs12, pass.constData(), pass.size())) {
if (pass.size() > 0)
XCA_PASSWD_ERROR();
enum open_result result = PwDialogCore::execute(&p, &pass);
if (result != pw_ok) {
/* cancel pressed */
PKCS12_free(pkcs12);
failed_files << fname;
throw result;
}
}
STACK_OF(X509) *certstack = sk_X509_new_null();
PKCS12_parse(pkcs12, pass.constData(), &mykey, &mycert, &certstack);
int error = ERR_peek_error();
if (ERR_GET_REASON(error) == PKCS12_R_MAC_VERIFY_FAILURE) {
pki_ign_openssl_error();
PKCS12_free(pkcs12);
sk_X509_free(certstack);
failed_files << fname;
throw errorEx(getClassName(),
tr("The supplied password was wrong (%1)")
.arg(ERR_reason_error_string(error)));
}
pki_ign_openssl_error();
if (mycert) {
unsigned char *str = X509_alias_get0(mycert, NULL);
if (str)
alias = QString::fromUtf8((const char *)str);
alias = QString::fromUtf8(alias.toLatin1());
cert = new pki_x509(mycert);
Q_CHECK_PTR(cert);
if (alias.isEmpty()) {
cert->autoIntName(fname);
alias = cert->getIntName();
} else {
cert->setIntName(alias);
}
cert->pkiSource = imported;
inheritFilename(cert);
append_item(cert);
}
if (mykey) {
key = new pki_evp(mykey);
key->setIntName(alias + "_key");
key->pkiSource = imported;
inheritFilename(key);
append_item(key);
}
for (int i = 0; i < sk_X509_num(certstack); ++i) {
X509 *crt = sk_X509_value(certstack, i);
if (!crt)
continue;
pki_x509 *cacert = new pki_x509(crt);
Q_CHECK_PTR(cacert);
if (alias.isEmpty()) {
cacert->autoIntName(QString());
} else {
cacert->setIntName(QString(alias + "_ca_%1").arg(i));
}
cacert->pkiSource = imported;
inheritFilename(cacert);
append_item(cacert);
}
sk_X509_free(certstack);
PKCS12_free(pkcs12);
pki_openssl_error();
}
void pki_pkcs12::writePKCS12(XFile &file, encAlgo &encAlgo) const
{
Passwd pass;
PKCS12 *pkcs12;
if (cert == NULL || key == NULL)
my_error(tr("No key or no Cert and no pkcs12"));
pass_info p(XCA_TITLE,
tr("Please enter the password to encrypt the key of certificate '%1' "
"in the PKCS#12 file:\n%2")
.arg(cert->getIntName()).arg(nativeSeparator(file.fileName())));
if (PwDialogCore::execute(&p, &pass, true) != 1)
return;
STACK_OF(X509) *certstack = sk_X509_new_null();
foreach(pki_base *pki, multi) {
pki_x509 *x = dynamic_cast<pki_x509*>(pki);
if (x && x != cert)
sk_X509_push(certstack, x->getCert());
}
int certAlgoNid, keyAlgoNid;
certAlgoNid = keyAlgoNid = encAlgo.getEncAlgoNid();
// The very ancient 40BitRC2_CBC algorithm at least can
// be combined with TripleDES_CBC for the keys.
if (keyAlgoNid == NID_pbe_WithSHA1And40BitRC2_CBC)
keyAlgoNid = NID_pbe_WithSHA1And3_Key_TripleDES_CBC;
EVP_PKEY *pkey = key->decryptKey();
for (const QString &line : key->getComment().split('\n')) {
QStringList kv = line.split(":");
qDebug() << line << kv;
if (kv.size() != 2 || kv[0] != "CSP")
continue;
QByteArray csp = kv[1].trimmed().toLatin1();
EVP_PKEY_add1_attr_by_NID(pkey, NID_ms_csp_name,
MBSTRING_ASC, (const unsigned char*)csp.constData(), csp.size());
}
pkcs12 = PKCS12_create(pass.data(), getIntName().toUtf8().data(),
pkey, cert->getCert(), certstack,
keyAlgoNid, certAlgoNid, 0, 0, 0);
pki_openssl_error();
EVP_PKEY_free(pkey);
Q_CHECK_PTR(pkcs12);
if (encAlgo.legacy())
PKCS12_set_mac(pkcs12, pass.data(), -1, NULL, 0, 1, EVP_sha1());
BioByteArray b;
i2d_PKCS12_bio(b, pkcs12);
sk_X509_free(certstack);
pki_openssl_error();
PKCS12_free(pkcs12);
file.write(b);
}
void pki_pkcs12::collect_properties(QMap<QString, QString> &prp) const
{
if (!algorithm.isEmpty())
prp["Algorithm"] = algorithm;
if (!alias.isEmpty())
prp["Friendly Name"] = alias;
}
// see https://www.rfc-editor.org/rfc/rfc8018 Appendix B.2 for possible encryption schemes
const QList<int> encAlgo::all_encAlgos(
{
NID_pbe_WithSHA1And40BitRC2_CBC,
NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
NID_aes_256_cbc
});
int encAlgo::default_encAlgo(NID_pbe_WithSHA1And3_Key_TripleDES_CBC);
encAlgo::encAlgo(int nid) : encAlgo_nid(nid)
{
}
encAlgo::encAlgo(const QString &name) : encAlgo_nid(default_encAlgo)
{
QString s(name);
encAlgo_nid = OBJ_txt2nid(CCHAR(s.remove(QChar(' '))));
ign_openssl_error();
}
QString encAlgo::name() const
{
return QString(encAlgo_nid == NID_undef ? "" : OBJ_nid2sn(encAlgo_nid));
}
QString encAlgo::displayName() const
{
QString n = name();
if (legacy())
n += QString(" (%1)").arg(QObject::tr("insecure"));
return n;
}
int encAlgo::getEncAlgoNid() const
{
return encAlgo_nid;
}
const encAlgo encAlgo::getDefault()
{
return encAlgo(default_encAlgo);
}
void encAlgo::setDefault(const QString &def)
{
default_encAlgo = encAlgo(def).encAlgo_nid;
}