mirror of
https://github.com/chris2511/xca.git
synced 2026-09-13 18:46:25 +05:00
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
Conversion of name constraints from certificate to Template (Advanced Tab) and using them from there was working since long. Now there is an additional input line, like (and next to) SubjectAlternativeName with live validation and edit button with guided input. The conversion from cert to template now puts the extension into the correct LineEdit and not on the Advanced Tab anymore. However, XCA does NOT follow the constraints, yet. It is still possible to issue certificates with arbitrary names, independent of any "Name Constraints" in the CA. This is what issue #239 asks for.
1150 lines
27 KiB
C++
1150 lines
27 KiB
C++
/* vi: set sw=4 ts=4:
|
|
*
|
|
* Copyright (C) 2001 - 2011 Christian Hohnstaedt.
|
|
*
|
|
* All rights reserved.
|
|
*/
|
|
|
|
#include "x509v3ext.h"
|
|
#include "x509name.h"
|
|
#include "asn1int.h"
|
|
#include "func.h"
|
|
#include "exception.h"
|
|
#include <openssl/x509v3.h>
|
|
#include <openssl/stack.h>
|
|
#include <QStringList>
|
|
#include <QRegularExpression>
|
|
#include <QDebug>
|
|
#include "base.h"
|
|
#include "BioByteArray.h"
|
|
|
|
x509v3ext::x509v3ext()
|
|
{
|
|
}
|
|
|
|
x509v3ext::x509v3ext(const X509_EXTENSION *n)
|
|
{
|
|
if (n) {
|
|
ext = X509_EXTENSION_dup((X509_EXTENSION *)n);
|
|
Q_CHECK_PTR(ext);
|
|
}
|
|
}
|
|
|
|
x509v3ext::x509v3ext(const x509v3ext &n)
|
|
{
|
|
set(n.ext);
|
|
}
|
|
|
|
x509v3ext::x509v3ext(int nid, const QString &et, X509V3_CTX *ctx)
|
|
{
|
|
create(nid, et, ctx);
|
|
}
|
|
|
|
x509v3ext::~x509v3ext()
|
|
{
|
|
if (ext)
|
|
X509_EXTENSION_free(ext);
|
|
}
|
|
|
|
x509v3ext &x509v3ext::set(const X509_EXTENSION *n)
|
|
{
|
|
if (n) {
|
|
ASN1_OCTET_STRING *str = X509_EXTENSION_get_data((X509_EXTENSION *)n);
|
|
if (!str || !str->length)
|
|
n = nullptr;
|
|
}
|
|
if (ext != nullptr)
|
|
X509_EXTENSION_free(ext);
|
|
ext = n ? X509_EXTENSION_dup((X509_EXTENSION *)n) : nullptr;
|
|
openssl_error();
|
|
return *this;
|
|
}
|
|
|
|
x509v3ext &x509v3ext::create(int nid, const QString &et, X509V3_CTX *ctx)
|
|
{
|
|
if (ext) {
|
|
X509_EXTENSION_free(ext);
|
|
ext = nullptr;
|
|
}
|
|
openssl_error();
|
|
if (!et.isEmpty()) {
|
|
QString etext = et;
|
|
if (et.contains("DNS:copycn") && ctx && ctx->subject_cert &&
|
|
nid == NID_subject_alt_name)
|
|
{
|
|
x509name xn(X509_get_subject_name(ctx->subject_cert));
|
|
QStringList new_san;
|
|
int i, max = xn.entryCount();
|
|
for (i=0; i < max; i++) {
|
|
if (xn.nid(i) == NID_commonName)
|
|
new_san << QString("DNS:%1").arg(xn.getEntry(i));
|
|
}
|
|
qDebug() << "COUNT" << new_san.size() << new_san.join(",");
|
|
if (new_san.size() > 0)
|
|
etext.replace(QString("DNS:copycn"), new_san.join(","));
|
|
}
|
|
if (nid == NID_subject_alt_name || nid == NID_issuer_alt_name) {
|
|
QStringList sl = etext.split(",");
|
|
for (int i=0; i<sl.size(); i++) {
|
|
QString s = sl[i].trimmed();
|
|
if (s.startsWith("UPN:"))
|
|
sl[i] = s.replace("UPN:", "otherName:msUPN;UTF8:");
|
|
}
|
|
etext = sl.join(",");
|
|
}
|
|
QByteArray ba = etext.toLocal8Bit();
|
|
ext = X509V3_EXT_conf_nid(NULL, ctx, nid, ba.data());
|
|
}
|
|
if (ext) {
|
|
if (ctx && ctx->subject_cert) {
|
|
X509_add_ext(ctx->subject_cert, ext, -1);
|
|
}
|
|
}
|
|
ign_openssl_error();
|
|
return *this;
|
|
}
|
|
|
|
x509v3ext &x509v3ext::create_ia5(int nid, const QString &et, X509V3_CTX *ctx)
|
|
{
|
|
QByteArray ba = et.toLocal8Bit();
|
|
for (int i=0; i<ba.size(); i++) {
|
|
if (ba[i] & 0x80)
|
|
throw errorEx(QObject::tr("String '%1' for '%2' contains invalid characters").arg(et).arg(OBJ_nid2ln(nid)));
|
|
}
|
|
return create(nid, et, ctx);
|
|
}
|
|
|
|
const ASN1_OBJECT *x509v3ext::object() const
|
|
{
|
|
ASN1_OBJECT *obj = nullptr;
|
|
if (ext) {
|
|
obj = X509_EXTENSION_get_object(ext);
|
|
ign_openssl_error();
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
int x509v3ext::nid() const
|
|
{
|
|
const ASN1_OBJECT *obj = object();
|
|
if (!obj)
|
|
return NID_undef;
|
|
int nid = OBJ_obj2nid(obj);
|
|
if (nid == NID_undef) {
|
|
QString o = OBJ_obj2QString(obj, 1);
|
|
if (!o.isEmpty())
|
|
nid = OBJ_create(CCHAR(o), CCHAR(o), CCHAR(o));
|
|
openssl_error();
|
|
}
|
|
return nid;
|
|
}
|
|
|
|
void *x509v3ext::d2i() const
|
|
{
|
|
if (!ext)
|
|
return nullptr;
|
|
|
|
void *r = X509V3_EXT_d2i(ext);
|
|
ign_openssl_error();
|
|
return r;
|
|
}
|
|
|
|
x509v3ext &x509v3ext::operator = (const x509v3ext &x)
|
|
{
|
|
set(x.ext);
|
|
return *this;
|
|
}
|
|
|
|
QString x509v3ext::getObject() const
|
|
{
|
|
const ASN1_OBJECT *obj = object();
|
|
return obj ? OBJ_obj2QString(obj) : QString("INVALID");
|
|
}
|
|
|
|
int x509v3ext::getCritical() const
|
|
{
|
|
return ext ? X509_EXTENSION_get_critical(ext) : 0;
|
|
}
|
|
|
|
ASN1_OCTET_STRING *x509v3ext::getData() const
|
|
{
|
|
return ext ? X509_EXTENSION_get_data(ext) : nullptr;;
|
|
}
|
|
|
|
QString x509v3ext::getValue() const
|
|
{
|
|
BioByteArray bba;
|
|
if (!isValid())
|
|
return QString();
|
|
int ret = X509V3_EXT_print(bba, ext, X509V3_EXT_DEFAULT, 0);
|
|
if (ign_openssl_error() || !ret)
|
|
ret = ASN1_STRING_print(bba, (ASN1_STRING *)getData());
|
|
if (ign_openssl_error() || !ret)
|
|
return QString();
|
|
return bba.qstring().trimmed();
|
|
}
|
|
|
|
QString x509v3ext::getHtmlValue() const
|
|
{
|
|
QString text = getValue();
|
|
text.replace(QRegularExpression("&"), "&");
|
|
text.replace(QRegularExpression("<"), "<");
|
|
text.replace(QRegularExpression(">"), ">");
|
|
text.replace(QRegularExpression("\n"), "<br>\n");
|
|
return text;
|
|
}
|
|
|
|
QString x509v3ext::getConsoleValue(const QString &indent) const
|
|
{
|
|
QString text = getValue();
|
|
text.replace(QRegularExpression("\n"), QString("\n") + indent);
|
|
return text;
|
|
}
|
|
|
|
static QString vlist2Section(QStringList vlist, QString tag, QString *sect)
|
|
{
|
|
/* Check for commas in the text */
|
|
if (!vlist.join("").contains(","))
|
|
return vlist.join(", ");
|
|
|
|
*sect += QString("\n[%1_sect]\n").arg(tag);
|
|
|
|
for (int i=0; i<vlist.count(); i++) {
|
|
QString s = vlist[i];
|
|
int eq = s.indexOf(":");
|
|
*sect += QString("%1.%2=%3\n").arg(s.left(eq)).
|
|
arg(i).arg(s.mid(eq+1));
|
|
}
|
|
return QString("@%1_sect\n").arg(tag);
|
|
}
|
|
|
|
static QString obj2SnOid(const ASN1_OBJECT *a)
|
|
{
|
|
QString obj;
|
|
int nid = OBJ_obj2nid(a);
|
|
|
|
if (nid == NID_undef)
|
|
obj = OBJ_obj2QString(a);
|
|
else
|
|
obj = OBJ_nid2sn(nid);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static const char *asn1Type2Name(int type)
|
|
{
|
|
#define ASN1_GEN_STR(x,y) { x,y }
|
|
struct {
|
|
const char *strnam;
|
|
int tag;
|
|
} tags[] = {
|
|
ASN1_GEN_STR("BOOL", V_ASN1_BOOLEAN),
|
|
ASN1_GEN_STR("NULL", V_ASN1_NULL),
|
|
ASN1_GEN_STR("INT", V_ASN1_INTEGER),
|
|
ASN1_GEN_STR("ENUM", V_ASN1_ENUMERATED),
|
|
ASN1_GEN_STR("OID", V_ASN1_OBJECT),
|
|
ASN1_GEN_STR("UTC", V_ASN1_UTCTIME),
|
|
ASN1_GEN_STR("GENTIME", V_ASN1_GENERALIZEDTIME),
|
|
ASN1_GEN_STR("OCT", V_ASN1_OCTET_STRING),
|
|
ASN1_GEN_STR("BITSTR", V_ASN1_BIT_STRING),
|
|
ASN1_GEN_STR("UNIV", V_ASN1_UNIVERSALSTRING),
|
|
ASN1_GEN_STR("IA5", V_ASN1_IA5STRING),
|
|
ASN1_GEN_STR("UTF8", V_ASN1_UTF8STRING),
|
|
ASN1_GEN_STR("BMP", V_ASN1_BMPSTRING),
|
|
ASN1_GEN_STR("VISIBLE", V_ASN1_VISIBLESTRING),
|
|
ASN1_GEN_STR("PRINTABLE", V_ASN1_PRINTABLESTRING),
|
|
ASN1_GEN_STR("T61", V_ASN1_T61STRING),
|
|
ASN1_GEN_STR("GENSTR", V_ASN1_GENERALSTRING),
|
|
ASN1_GEN_STR("NUMERIC", V_ASN1_NUMERICSTRING),
|
|
};
|
|
for (unsigned i=0; i< ARRAY_SIZE(tags); i++) {
|
|
if (tags[i].tag == type)
|
|
return tags[i].strnam;
|
|
}
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
static bool asn1TypePrintable(int type)
|
|
{
|
|
switch (type) {
|
|
case V_ASN1_IA5STRING:
|
|
case V_ASN1_UTF8STRING:
|
|
case V_ASN1_BMPSTRING:
|
|
case V_ASN1_VISIBLESTRING:
|
|
case V_ASN1_PRINTABLESTRING:
|
|
case V_ASN1_T61STRING:
|
|
case V_ASN1_GENERALSTRING:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static QString ipv6_from_binary(const unsigned char *p)
|
|
{
|
|
QString ip;
|
|
int i, skip =0, skiplen = 0, skippos =0;
|
|
|
|
/* find largest gap */
|
|
for (i = 0; i < 17; i += 2) {
|
|
if (i==16 || (p[i] | p[i +1])) {
|
|
if (skiplen < skip) {
|
|
skiplen = skip;
|
|
skippos = i - skip;
|
|
}
|
|
skip = 0;
|
|
} else {
|
|
skip += 2;
|
|
}
|
|
}
|
|
for (i = 0, skip = 0; i < 16; i += 2) {
|
|
int x = p[i] << 8 | p[i+1];
|
|
skip += skippos == i;
|
|
switch (!x*4 + skip) {
|
|
case 5: // skip first 0
|
|
skip = 2;
|
|
ip += ":";
|
|
case 6: // skip next 0
|
|
break;
|
|
default: // no reduction
|
|
skip = 0;
|
|
ip += QString("%1%2").arg(i? ":" : "").arg(x,0,16);
|
|
}
|
|
}
|
|
if (skip == 2)
|
|
ip += ":";
|
|
return ip;
|
|
}
|
|
|
|
static bool
|
|
genName2conf(GENERAL_NAME *gen, QString tag, QString *single, QString *sect)
|
|
{
|
|
unsigned char *p;
|
|
QString ret;
|
|
|
|
switch (gen->type) {
|
|
case GEN_EMAIL: ret = "email:%1"; break;
|
|
case GEN_DNS: ret = "DNS:%1"; break;
|
|
case GEN_URI: ret = "URI:%1"; break;
|
|
|
|
case GEN_DIRNAME: {
|
|
tag += "_dirname";
|
|
x509name xn(gen->d.dirn);
|
|
*sect += QString("\n[%1]\n"). arg(tag);
|
|
*sect += xn.taggedValues();
|
|
*single = QString("dirName:") + tag;
|
|
return true;
|
|
}
|
|
case GEN_IPADD:
|
|
p = gen->d.ip->data;
|
|
if (gen->d.ip->length == 4) {
|
|
*single = QString("IP:%1.%2.%3.%4").
|
|
arg(p[0]).arg(p[1]).arg(p[2]).arg(p[3]);
|
|
return true;
|
|
} else if(gen->d.ip->length == 8) {
|
|
*single = QString("IP:%1.%2.%3.%4/%5.%6.%7.%8").
|
|
arg(p[0]).arg(p[1]).arg(p[2]).arg(p[3]).
|
|
arg(p[4]).arg(p[5]).arg(p[6]).arg(p[7]);
|
|
return true;
|
|
} else if(gen->d.ip->length == 16) {
|
|
*single = "IP:" + ipv6_from_binary(gen->d.ip->data);
|
|
return true;
|
|
} else if(gen->d.ip->length == 32) {
|
|
*single = "IP:" + ipv6_from_binary(gen->d.ip->data) +
|
|
"/" + ipv6_from_binary(gen->d.ip->data +16);
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case GEN_RID:
|
|
*single = QString("RID:%1").
|
|
arg(obj2SnOid(gen->d.rid));
|
|
return true;
|
|
case GEN_OTHERNAME: {
|
|
int type = gen->d.otherName->value->type;
|
|
ASN1_STRING *a;
|
|
a = gen->d.otherName->value->value.asn1_string;
|
|
if (asn1TypePrintable(type)) {
|
|
*single = QString("otherName:%1;%2:%3").
|
|
arg(obj2SnOid(gen->d.otherName->type_id)).
|
|
arg(asn1Type2Name(type)).
|
|
arg(asn1ToQString(a, true));
|
|
} else {
|
|
*single = QString("otherName:%1;FORMAT:HEX,%2").
|
|
arg(obj2SnOid(gen->d.otherName->type_id)).
|
|
arg(asn1Type2Name(type));
|
|
for (int i=0; i<a->length; i++) {
|
|
*single += QString(":%1").
|
|
arg((int)(a->data[i]), 2, 16, QChar('0'));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
if (!ret.isEmpty())
|
|
*single = ret.arg(asn1ToQString(gen->d.ia5, true));
|
|
return true;
|
|
}
|
|
|
|
static bool genNameStack2conf(STACK_OF(GENERAL_NAME) *gens, QString tag,
|
|
QString *single, QString *sect)
|
|
{
|
|
int i;
|
|
QStringList sl;
|
|
for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) {
|
|
QString one;
|
|
if (!genName2conf(sk_GENERAL_NAME_value(gens, i),
|
|
QString("%1_%2").arg(tag).arg(i), &one, sect))
|
|
{
|
|
return false;
|
|
}
|
|
sl << one;
|
|
}
|
|
*single = vlist2Section(sl, tag, sect);
|
|
return true;
|
|
}
|
|
|
|
QString x509v3ext::parse_critical() const
|
|
{
|
|
return QString(getCritical() ? "critical," : "");
|
|
}
|
|
|
|
#define TEXTS (\
|
|
B_ASN1_TIME | B_ASN1_DIRECTORYSTRING | B_ASN1_DISPLAYTEXT | \
|
|
B_ASN1_NUMERICSTRING | B_ASN1_T61STRING | B_ASN1_UNIVERSALSTRING)
|
|
|
|
bool x509v3ext::parse_ia5(QString *single, QString *adv) const
|
|
{
|
|
ASN1_STRING *str = (ASN1_STRING *)d2i();
|
|
QString ret;
|
|
|
|
if (!isValid())
|
|
return false;
|
|
|
|
if (!str) {
|
|
const unsigned char *p = getData()->data;
|
|
str = d2i_ASN1_OCTET_STRING(NULL, &p, getData()->length);
|
|
if (ign_openssl_error() || !str)
|
|
return false;
|
|
ret = QString("<ERROR: NOT IA5 but %1>%2").
|
|
arg(asn1Type2Name(str->type)).
|
|
arg(QString(asn1ToQString(str)));
|
|
} else {
|
|
ret = QString(asn1ToQString(str));
|
|
}
|
|
if (single)
|
|
*single = ret;
|
|
else if (adv)
|
|
*adv = QString("%1=%2\n").arg(OBJ_nid2sn(nid())).arg(ret) +*adv;
|
|
|
|
ASN1_STRING_free(str);
|
|
return true;
|
|
}
|
|
|
|
bool x509v3ext::parse_generalName(QString *single, QString *adv) const
|
|
{
|
|
bool retval = true;
|
|
QString sect, ret;
|
|
STACK_OF(GENERAL_NAME) *gens = (STACK_OF(GENERAL_NAME) *)d2i();
|
|
|
|
if (!gens)
|
|
return false;
|
|
|
|
QString tag = OBJ_nid2sn(nid());
|
|
|
|
if (!genNameStack2conf(gens, tag, &ret, §))
|
|
retval = false;
|
|
else if (sect.isEmpty() && single) {
|
|
*single = parse_critical() + ret;
|
|
} else if (adv) {
|
|
*adv = QString("%1=%2\n").arg(tag).
|
|
arg(parse_critical() +ret) + *adv + sect;
|
|
}
|
|
sk_GENERAL_NAME_free(gens);
|
|
return retval;
|
|
}
|
|
|
|
bool x509v3ext::parse_eku(QString *single, QString *adv) const
|
|
{
|
|
EXTENDED_KEY_USAGE *eku = (EXTENDED_KEY_USAGE *)d2i();
|
|
QStringList sl;
|
|
int i;
|
|
|
|
if (!eku)
|
|
return false;
|
|
|
|
for (i = 0; i < sk_ASN1_OBJECT_num(eku); i++) {
|
|
sl << QString(OBJ_obj2sn(sk_ASN1_OBJECT_value(eku, i)));
|
|
}
|
|
QString r = parse_critical() + sl.join(", ");
|
|
if (single)
|
|
*single = r;
|
|
else if (adv)
|
|
*adv = QString("%1=%2\n").arg(OBJ_nid2sn(nid())).arg(r) + *adv;
|
|
|
|
EXTENDED_KEY_USAGE_free(eku);
|
|
return true;
|
|
}
|
|
|
|
bool x509v3ext::parse_ainfo(QString *single, QString *adv) const
|
|
{
|
|
bool retval = true;
|
|
QString sect, ret;
|
|
QString tag = OBJ_nid2sn(nid());
|
|
QStringList sl;
|
|
int i;
|
|
|
|
AUTHORITY_INFO_ACCESS *ainfo = (AUTHORITY_INFO_ACCESS *)d2i();
|
|
|
|
if (!ainfo)
|
|
return false;
|
|
|
|
for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ainfo); i++) {
|
|
QString one;
|
|
ACCESS_DESCRIPTION *desc = sk_ACCESS_DESCRIPTION_value(ainfo, i);
|
|
if (!genName2conf(desc->location,
|
|
QString("%1_%2").arg(tag).arg(i), &one, §))
|
|
{
|
|
retval = false;
|
|
break;
|
|
}
|
|
sl << QString("%1;%2").arg(OBJ_obj2sn(desc->method)).arg(one);
|
|
}
|
|
if (retval) {
|
|
ret = vlist2Section(sl, tag, §);
|
|
if (sect.isEmpty() && sk_ACCESS_DESCRIPTION_num(ainfo) == 1 && single) {
|
|
*single = parse_critical() + ret;
|
|
} else if (adv) {
|
|
*adv = QString("%1=%2\n").arg(tag).
|
|
arg(parse_critical() + ret) + *adv + sect;
|
|
}
|
|
}
|
|
AUTHORITY_INFO_ACCESS_free(ainfo);
|
|
return retval;
|
|
}
|
|
|
|
static const BIT_STRING_BITNAME reason_flags[] = {
|
|
{0, "", "unused"},
|
|
{1, "", "keyCompromise"},
|
|
{2, "", "CACompromise"},
|
|
{3, "", "affiliationChanged"},
|
|
{4, "", "superseded"},
|
|
{5, "", "cessationOfOperation"},
|
|
{6, "", "certificateHold"},
|
|
{7, "", "privilegeWithdrawn"},
|
|
{8, "", "AACompromise"},
|
|
{-1, NULL, NULL}
|
|
};
|
|
|
|
static QString parse_bits(const BIT_STRING_BITNAME *flags,
|
|
ASN1_BIT_STRING *str)
|
|
{
|
|
const BIT_STRING_BITNAME *pbn;
|
|
QStringList r;
|
|
for (pbn = flags; pbn->sname; pbn++) {
|
|
if (ASN1_BIT_STRING_get_bit(str, pbn->bitnum))
|
|
r << QString(pbn->sname);
|
|
}
|
|
return r.join(", ");
|
|
}
|
|
|
|
bool x509v3ext::parse_Crldp(QString *single, QString *adv) const
|
|
{
|
|
QString othersect;
|
|
QStringList crldps;
|
|
const char *sn = OBJ_nid2sn(nid());
|
|
|
|
STACK_OF(DIST_POINT) *crld = (STACK_OF(DIST_POINT)*)d2i();
|
|
|
|
if (!crld)
|
|
return false;
|
|
|
|
if (sk_DIST_POINT_num(crld) == 1 && single) {
|
|
DIST_POINT *point = sk_DIST_POINT_value(crld, 0);
|
|
if (point->distpoint && !point->reasons && !point->CRLissuer &&
|
|
!point->distpoint->type)
|
|
{
|
|
QString sect, ret;
|
|
if (!genNameStack2conf(point->distpoint->name.fullname,
|
|
"", &ret, §))
|
|
goto could_not_parse;
|
|
|
|
if (sect.isEmpty()) {
|
|
if (single)
|
|
*single = parse_critical() +ret;
|
|
else if (adv)
|
|
*adv = QString("%1=%2\n").arg(sn).
|
|
arg(parse_critical() +ret) +*adv;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
for(int i = 0; i < sk_DIST_POINT_num(crld); i++) {
|
|
DIST_POINT *point = sk_DIST_POINT_value(crld, i);
|
|
QString tag = QString("crlDistributionPoint%1_sect").arg(i);
|
|
QString crldpsect = QString("\n[%1]\n").arg(tag);
|
|
if (point->distpoint) {
|
|
if (!point->distpoint->type) {
|
|
QString ret;
|
|
if (!genNameStack2conf(point->distpoint->name.fullname,
|
|
tag + "_fullname", &ret, &othersect))
|
|
goto could_not_parse;
|
|
|
|
crldpsect += "fullname=" + ret +"\n";
|
|
} else {
|
|
QString mysect = tag + "_relativename";
|
|
x509name xn(point->distpoint->name.relativename);
|
|
crldpsect += "relativename=" + mysect + "\n";
|
|
othersect += QString("\n[%1]\n").arg(mysect) +
|
|
xn.taggedValues();
|
|
}
|
|
}
|
|
if (point->reasons) {
|
|
crldpsect += QString("reasons=%1\n").
|
|
arg(parse_bits(reason_flags, point->reasons));
|
|
}
|
|
if (point->CRLissuer) {
|
|
QString ret;
|
|
if (genNameStack2conf(point->CRLissuer,
|
|
tag +"_crlissuer", &ret, &othersect))
|
|
goto could_not_parse;
|
|
crldpsect += "CRLissuer=" + ret + "\n";
|
|
}
|
|
crldps << tag;
|
|
othersect = crldpsect + othersect;
|
|
}
|
|
sk_DIST_POINT_free(crld);
|
|
if (crldps.size() == 0)
|
|
return true;
|
|
if (adv) {
|
|
*adv = QString("%1=%2\n").arg(sn).
|
|
arg(parse_critical() + crldps.join(", ")) +
|
|
*adv + othersect;
|
|
}
|
|
return true;
|
|
|
|
could_not_parse:
|
|
sk_DIST_POINT_free(crld);
|
|
return false;
|
|
}
|
|
|
|
static void gen_cpol_notice(QString tag, USERNOTICE *notice, QString *adv)
|
|
{
|
|
*adv += QString("\n[%1]\n").arg(tag);
|
|
if (notice->exptext) {
|
|
*adv += QString("explicitText=%1\n").
|
|
arg(asn1ToQString(notice->exptext, true));
|
|
}
|
|
if (notice->noticeref) {
|
|
NOTICEREF *ref = notice->noticeref;
|
|
QStringList sl;
|
|
int i;
|
|
*adv += QString("organization=%1\n").
|
|
arg(asn1ToQString(ref->organization, true));
|
|
for (i = 0; i < sk_ASN1_INTEGER_num(ref->noticenos); i++) {
|
|
a1int num(sk_ASN1_INTEGER_value(ref->noticenos, i));
|
|
sl << num.toDec();
|
|
}
|
|
if (sl.size())
|
|
*adv += QString("noticeNumbers=%1\n").
|
|
arg(sl.join(", "));
|
|
}
|
|
}
|
|
|
|
static bool gen_cpol_qual_sect(QString tag, POLICYINFO *pinfo, QString *adv)
|
|
{
|
|
QString polsect = QString("\n[%1]\n").arg(tag);
|
|
QString noticetag, _adv;
|
|
STACK_OF(POLICYQUALINFO) *quals = pinfo->qualifiers;
|
|
int i;
|
|
|
|
if (!quals)
|
|
return false;
|
|
|
|
if (!adv)
|
|
adv = &_adv;
|
|
|
|
polsect += QString("policyIdentifier=%1\n").
|
|
arg(obj2SnOid(pinfo->policyid));
|
|
|
|
for (i = 0; i < sk_POLICYQUALINFO_num(quals); i++) {
|
|
POLICYQUALINFO *qualinfo = sk_POLICYQUALINFO_value(quals, i);
|
|
switch (OBJ_obj2nid(qualinfo->pqualid)) {
|
|
case NID_id_qt_cps:
|
|
polsect += QString("CPS.%1=%2\n").arg(i).
|
|
arg(asn1ToQString(qualinfo->d.cpsuri, true));
|
|
break;
|
|
case NID_id_qt_unotice:
|
|
noticetag = QString("%1_notice%2_sect").arg(tag).arg(i);
|
|
polsect += QString("userNotice.%1=@%2\n").arg(i).
|
|
arg(noticetag);
|
|
gen_cpol_notice(noticetag, qualinfo->d.usernotice, adv);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
*adv = polsect + *adv;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool x509v3ext::parse_certpol(QString *, QString *adv) const
|
|
{
|
|
bool retval = true;
|
|
QStringList pols;
|
|
QString myadv;
|
|
int i;
|
|
STACK_OF(POLICYINFO) *pol = (STACK_OF(POLICYINFO) *)d2i();
|
|
|
|
if (!pol)
|
|
return false;
|
|
|
|
for (i = 0; i < sk_POLICYINFO_num(pol); i++) {
|
|
POLICYINFO *pinfo = sk_POLICYINFO_value(pol, i);
|
|
if (!pinfo->qualifiers) {
|
|
pols << obj2SnOid(pinfo->policyid);
|
|
continue;
|
|
}
|
|
QString tag = QString("certpol%1_sect").arg(i);
|
|
pols << QString("@") + tag;
|
|
if (!gen_cpol_qual_sect(tag, pinfo, &myadv)) {
|
|
retval = false;
|
|
break;
|
|
}
|
|
}
|
|
if (retval && adv)
|
|
*adv = QString("certificatePolicies=%1ia5org,%2\n").
|
|
arg(parse_critical()).arg(pols.join(", ")) + *adv + myadv;
|
|
sk_POLICYINFO_free(pol);
|
|
return retval;
|
|
}
|
|
|
|
bool x509v3ext::parse_bc(QString *single, QString *adv) const
|
|
{
|
|
BASIC_CONSTRAINTS *bc = (BASIC_CONSTRAINTS *)d2i();
|
|
|
|
if (!bc)
|
|
return false;
|
|
|
|
QString ret = a1int(bc->pathlen).toDec();
|
|
if (!ret.isEmpty())
|
|
ret = ",pathlen:" + ret;
|
|
ret = parse_critical() + (bc->ca ? "CA:TRUE" : "CA:FALSE") + ret;
|
|
if (single)
|
|
*single = ret;
|
|
else if (adv)
|
|
*adv = QString("%1=%2\n").arg(OBJ_nid2sn(nid())).arg(ret) +*adv;
|
|
BASIC_CONSTRAINTS_free(bc);
|
|
return true;
|
|
}
|
|
|
|
static const BIT_STRING_BITNAME key_usage_type_table[] = {
|
|
{0, "Digital Signature", "digitalSignature"},
|
|
{1, "Non Repudiation", "nonRepudiation"},
|
|
{2, "Key Encipherment", "keyEncipherment"},
|
|
{3, "Data Encipherment", "dataEncipherment"},
|
|
{4, "Key Agreement", "keyAgreement"},
|
|
{5, "Certificate Sign", "keyCertSign"},
|
|
{6, "CRL Sign", "cRLSign"},
|
|
{7, "Encipher Only", "encipherOnly"},
|
|
{8, "Decipher Only", "decipherOnly"},
|
|
{-1, NULL, NULL}
|
|
};
|
|
|
|
static const BIT_STRING_BITNAME ns_cert_type_table[] = {
|
|
{0, "SSL Client", "client"},
|
|
{1, "SSL Server", "server"},
|
|
{2, "S/MIME", "email"},
|
|
{3, "Object Signing", "objsign"},
|
|
{4, "Unused", "reserved"},
|
|
{5, "SSL CA", "sslCA"},
|
|
{6, "S/MIME CA", "emailCA"},
|
|
{7, "Object Signing CA", "objCA"},
|
|
{-1, NULL, NULL}
|
|
};
|
|
|
|
bool x509v3ext::parse_bitstring(QString *single, QString *adv) const
|
|
{
|
|
ASN1_BIT_STRING *bs;
|
|
const BIT_STRING_BITNAME *bnames;
|
|
int n = nid();
|
|
|
|
switch (n) {
|
|
case NID_key_usage: bnames = key_usage_type_table; break;
|
|
case NID_netscape_cert_type: bnames = ns_cert_type_table; break;
|
|
default: return false;
|
|
}
|
|
bs = (ASN1_BIT_STRING *)d2i();
|
|
|
|
if (!bs)
|
|
return false;
|
|
|
|
QString ret = parse_critical() + parse_bits(bnames, bs);
|
|
if (single)
|
|
*single = ret;
|
|
else if (adv)
|
|
*adv = QString("%1=%2\n").arg(OBJ_nid2sn(nid())).arg(ret) +*adv;
|
|
ASN1_BIT_STRING_free(bs);
|
|
return true;
|
|
}
|
|
|
|
bool x509v3ext::parse_sKeyId(QString *, QString *adv) const
|
|
{
|
|
if (adv)
|
|
*adv = QString("%1=hash\n").arg(OBJ_nid2sn(nid())) + *adv;
|
|
return true;
|
|
}
|
|
|
|
bool x509v3ext::parse_aKeyId(QString *, QString *adv) const
|
|
{
|
|
QStringList ret;
|
|
AUTHORITY_KEYID *akeyid = (AUTHORITY_KEYID *)d2i();
|
|
ign_openssl_error();
|
|
|
|
if (!akeyid)
|
|
return false;
|
|
|
|
if (akeyid->keyid)
|
|
ret << "keyid";
|
|
if (akeyid->issuer)
|
|
ret << "issuer:always";
|
|
if (adv)
|
|
*adv = QString("%1=%2\n").arg(OBJ_nid2sn(nid())).
|
|
arg(ret.join(", ")) + *adv;
|
|
AUTHORITY_KEYID_free(akeyid);
|
|
return true;
|
|
}
|
|
|
|
bool x509v3ext::parse_generic(QString *, QString *adv) const
|
|
{
|
|
if (!isValid())
|
|
return false;
|
|
|
|
const ASN1_OBJECT *o = object();
|
|
QString der, obj = o ? obj2SnOid(o) : QString("INVALID");
|
|
ASN1_OCTET_STRING *v = getData();
|
|
|
|
for (int i=0; v && i < v->length; i++)
|
|
der += QString(":%1").arg((int)(v->data[i]), 2, 16, QChar('0'));
|
|
|
|
if (adv)
|
|
*adv = QString("%1=%2DER%3\n").arg(obj).
|
|
arg(parse_critical()).arg(der) + *adv;
|
|
return true;
|
|
}
|
|
|
|
bool x509v3ext::parse_inhibitAnyPolicy(QString *, QString *adv) const
|
|
{
|
|
ASN1_INTEGER *a = (ASN1_INTEGER *)d2i();
|
|
ign_openssl_error();
|
|
|
|
if (!a)
|
|
return false;
|
|
|
|
a1int val(a);
|
|
if (adv) {
|
|
*adv = QString("%1=%2%3\n").arg(OBJ_nid2sn(nid())).
|
|
arg(parse_critical()).arg(val.toDec()) + *adv;
|
|
}
|
|
ASN1_INTEGER_free(a);
|
|
return true;
|
|
}
|
|
|
|
bool x509v3ext::parse_policyConstraints(QString *, QString *adv) const
|
|
{
|
|
QStringList v;
|
|
a1int a1null(0L), a;
|
|
POLICY_CONSTRAINTS *pol = (POLICY_CONSTRAINTS *)d2i();
|
|
ign_openssl_error();
|
|
|
|
if (!pol)
|
|
return false;
|
|
|
|
a = a1int(pol->requireExplicitPolicy);
|
|
if (a != a1null)
|
|
v << QString("requireExplicitPolicy:%1").arg(a.toDec());
|
|
|
|
a = a1int(pol->inhibitPolicyMapping);
|
|
if (a != a1null)
|
|
v << QString("inhibitPolicyMapping:%1").arg(a.toDec());
|
|
|
|
if (adv)
|
|
*adv = QString("%1=%2%3\n").arg(OBJ_nid2sn(nid())).
|
|
arg(parse_critical()).arg(v.join(", ")) + *adv;
|
|
POLICY_CONSTRAINTS_free(pol);
|
|
return true;
|
|
|
|
}
|
|
|
|
bool x509v3ext::parse_policyMappings(QString *, QString *adv) const
|
|
{
|
|
bool retval = true;
|
|
QStringList polMaps;
|
|
QString myadv;
|
|
POLICY_MAPPINGS *pmaps = (POLICY_MAPPINGS *)d2i();
|
|
ign_openssl_error();
|
|
|
|
if (!pmaps)
|
|
return false;
|
|
|
|
for (int i = 0; i < sk_POLICY_MAPPING_num(pmaps); i++) {
|
|
POLICY_MAPPING *pmap = sk_POLICY_MAPPING_value(pmaps, i);
|
|
polMaps << QString("%1 = %2").
|
|
arg(obj2SnOid(pmap->issuerDomainPolicy)).
|
|
arg(obj2SnOid(pmap->subjectDomainPolicy));
|
|
}
|
|
if (polMaps.size() > 0 && adv) {
|
|
*adv = QString("policyMappings=%1@policyMappings_sect\n").
|
|
arg(parse_critical()) + *adv +
|
|
QString("[policyMappings_sect]\n") + polMaps.join("\n");
|
|
}
|
|
sk_POLICY_MAPPING_free(pmaps);
|
|
return retval;
|
|
}
|
|
|
|
static bool nameConstraint(STACK_OF(GENERAL_SUBTREE) *trees,
|
|
QString prefix, QString tag, QString *single, QString *sect)
|
|
{
|
|
QStringList sl;
|
|
for (int i = 0; i < sk_GENERAL_SUBTREE_num(trees); i++) {
|
|
QString one;
|
|
GENERAL_SUBTREE *tree = sk_GENERAL_SUBTREE_value(trees, i);
|
|
if (!genName2conf(tree->base,
|
|
QString("%1_%2").arg(tag).arg(i), &one, sect))
|
|
{
|
|
return false;
|
|
}
|
|
qDebug("%s: %d '%s'\n", __func__, i, CCHAR(one));
|
|
sl << prefix + ";" + one;
|
|
}
|
|
*single = vlist2Section(sl, tag+prefix, sect);
|
|
qDebug("Single: '%s'\n", CCHAR(*single));
|
|
return true;
|
|
}
|
|
|
|
bool x509v3ext::parse_nameConstraints(QString *single, QString *adv) const
|
|
{
|
|
bool retval = true;
|
|
QString sect, ret;
|
|
QStringList permEx;
|
|
QString tag = OBJ_nid2sn(nid());
|
|
NAME_CONSTRAINTS *cons = (NAME_CONSTRAINTS *)d2i();
|
|
ign_openssl_error();
|
|
|
|
if (!cons)
|
|
return false;
|
|
|
|
if (!nameConstraint(cons->permittedSubtrees, "permitted",
|
|
tag, &ret, §))
|
|
retval = false;
|
|
if (ret.size() > 0)
|
|
permEx << ret;
|
|
if (!nameConstraint(cons->excludedSubtrees, "excluded",
|
|
tag, &ret, §))
|
|
retval = false;
|
|
if (ret.size() > 0)
|
|
permEx << ret;
|
|
|
|
if (retval && permEx.size() > 0) {
|
|
ret = permEx.join(", ");
|
|
qDebug() << retval << ret;
|
|
if (single)
|
|
*single = ret;
|
|
else if (adv)
|
|
*adv = QString("%1=%2\n").arg(tag).
|
|
arg(parse_critical() +ret) + *adv + sect;
|
|
}
|
|
NAME_CONSTRAINTS_free(cons);
|
|
return retval;
|
|
}
|
|
|
|
bool x509v3ext::genConf(QString *single, QString *adv) const
|
|
{
|
|
int n = nid();
|
|
switch (n) {
|
|
case NID_crl_distribution_points:
|
|
return parse_Crldp(single, adv);
|
|
case NID_subject_alt_name:
|
|
case NID_issuer_alt_name:
|
|
return parse_generalName(single, adv);
|
|
case NID_info_access:
|
|
return parse_ainfo(single, adv);
|
|
case NID_ext_key_usage:
|
|
return parse_eku(single, adv);
|
|
case NID_certificate_policies:
|
|
return parse_certpol(single, adv);
|
|
case NID_netscape_comment:
|
|
case NID_netscape_base_url:
|
|
case NID_netscape_revocation_url:
|
|
case NID_netscape_ca_revocation_url:
|
|
case NID_netscape_renewal_url:
|
|
case NID_netscape_ca_policy_url:
|
|
case NID_netscape_ssl_server_name:
|
|
return parse_ia5(single, adv);
|
|
case NID_basic_constraints:
|
|
return parse_bc(single, adv);
|
|
case NID_key_usage:
|
|
case NID_netscape_cert_type:
|
|
return parse_bitstring(single, adv);
|
|
case NID_subject_key_identifier:
|
|
return parse_sKeyId(single, adv);
|
|
case NID_authority_key_identifier:
|
|
return parse_aKeyId(single, adv);
|
|
case NID_inhibit_any_policy:
|
|
return parse_inhibitAnyPolicy(single, adv);
|
|
case NID_policy_constraints:
|
|
return parse_policyConstraints(single, adv);
|
|
case NID_policy_mappings:
|
|
return parse_policyMappings(single, adv);
|
|
case NID_name_constraints:
|
|
return parse_nameConstraints(single, adv);
|
|
case NID_id_pkix_OCSP_noCheck:
|
|
if (adv)
|
|
*adv = "noCheck = ignored\n" + *adv;
|
|
return true;
|
|
default:
|
|
return parse_generic(single, adv);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QString x509v3ext::getHtml() const
|
|
{
|
|
QString html;
|
|
html = "<b><u>" + getObject();
|
|
if (getCritical() != 0)
|
|
html += " <font color=\"red\">critical</font>";
|
|
html += ":</u></b><br><tt>" + getHtmlValue() + "</tt>";
|
|
return html;
|
|
}
|
|
|
|
QString x509v3ext::getConsole(const QString &indent) const
|
|
{
|
|
QString text, twoind = indent + indent;
|
|
text = indent + COL_BOLD COL_UNDER + getObject();
|
|
if (getCritical() != 0)
|
|
text += " " COL_RED COL_UNDER "[critical]";
|
|
text += COL_RESET "\n" + twoind + getConsoleValue(twoind);
|
|
return text;
|
|
}
|
|
|
|
X509_EXTENSION *x509v3ext::get() const
|
|
{
|
|
return ext ? X509_EXTENSION_dup(ext) : nullptr;
|
|
}
|
|
|
|
bool x509v3ext::isValid() const
|
|
{
|
|
return ext && getData() && getData()->length > 0 &&
|
|
OBJ_obj2nid(X509_EXTENSION_get_object(ext)) != NID_undef;
|
|
}
|
|
|
|
/*************************************************************/
|
|
|
|
bool extList::genConf(int nid, QString *single, QString *adv)
|
|
{
|
|
int i = idxByNid(nid);
|
|
if (i != -1) {
|
|
if (at(i).genConf(single, adv))
|
|
removeAt(i);
|
|
ign_openssl_error();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void extList::genGenericConf(QString *adv)
|
|
{
|
|
for (int i=0; i< size();) {
|
|
if (at(i).genConf(NULL, adv) || at(i).parse_generic(NULL, adv))
|
|
removeAt(i);
|
|
else
|
|
i++;
|
|
ign_openssl_error();
|
|
}
|
|
}
|
|
|
|
void extList::setStack(const STACK_OF(X509_EXTENSION) *st, int start)
|
|
{
|
|
clear();
|
|
int cnt = sk_X509_EXTENSION_num(st);
|
|
x509v3ext e;
|
|
for (int i=start; i<cnt; i++) {
|
|
e.set(sk_X509_EXTENSION_value(st,i));
|
|
append(e);
|
|
}
|
|
}
|
|
|
|
STACK_OF(X509_EXTENSION) *extList::getStack() const
|
|
{
|
|
STACK_OF(X509_EXTENSION) *sk = sk_X509_EXTENSION_new_null();
|
|
Q_CHECK_PTR(sk);
|
|
foreach(const x509v3ext &e, *this)
|
|
sk_X509_EXTENSION_push(sk, e.get());
|
|
return sk;
|
|
}
|
|
|
|
bool extList::search(const QRegularExpression &pattern) const
|
|
{
|
|
foreach(const x509v3ext &e, *this)
|
|
if (e.getValue().contains(pattern))
|
|
return true;
|
|
return false;
|
|
}
|
|
QString extList::getHtml(const QString &sep) const
|
|
{
|
|
x509v3ext e;
|
|
QStringList s;
|
|
foreach(const x509v3ext &e, *this)
|
|
s << e.getHtml();
|
|
QString a = s.join(sep);
|
|
return a;
|
|
}
|
|
|
|
QString extList::getConsole(const QString &indent) const
|
|
{
|
|
x509v3ext e;
|
|
QStringList s;
|
|
foreach(const x509v3ext &e, *this)
|
|
s << e.getConsole(indent);
|
|
QString a = "\n" + s.join("\n");
|
|
return a;
|
|
}
|
|
|
|
|
|
bool extList::delByNid(int nid)
|
|
{
|
|
for(int i = 0; i< size(); i++) {
|
|
if (at(i).nid() == nid) {
|
|
removeAt(i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int extList::idxByNid(int nid) const
|
|
{
|
|
for(int i = 0; i< size(); i++) {
|
|
if (at(i).nid() == nid) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int extList::delInvalid(void)
|
|
{
|
|
int removed=0;
|
|
QMutableListIterator<x509v3ext> i(*this);
|
|
while (i.hasNext()) {
|
|
if (!i.next().isValid()) {
|
|
i.remove();
|
|
removed=1;
|
|
}
|
|
}
|
|
return removed;
|
|
}
|