mirror of
https://github.com/chris2511/xca.git
synced 2026-09-12 19:51:05 +05:00
Close #89: ta.key for OpenVPN tls-auth
The ta-key will be generated on-demand and assigned to the issuing CA. All issued certificates of this CA will use the same ta-key. It can be exported as single file and will also be part of the exported openvpn configuration file. Extend export tests to validate the output of ta-keys and adapt the OpenVPN conf file export test to also check the ta-key.
This commit is contained in:
parent
0dd587a3f2
commit
1199d776d2
@ -72,8 +72,8 @@ const QString &database_model::detect_provider()
|
|||||||
|
|
||||||
QSqlError database_model::initSqlDB()
|
QSqlError database_model::initSqlDB()
|
||||||
{
|
{
|
||||||
#define MAX_SCHEMAS 7
|
#define MAX_SCHEMAS 8
|
||||||
#define SCHEMA_VERSION "7"
|
#define SCHEMA_VERSION "8"
|
||||||
|
|
||||||
QStringList schemas[MAX_SCHEMAS];
|
QStringList schemas[MAX_SCHEMAS];
|
||||||
|
|
||||||
|
|||||||
@ -305,6 +305,16 @@
|
|||||||
<< "UPDATE settings SET value='7' WHERE key_='schema'"
|
<< "UPDATE settings SET value='7' WHERE key_='schema'"
|
||||||
;
|
;
|
||||||
|
|
||||||
|
schemas[7]
|
||||||
|
// OpenVPN TA (tls-auth) keys associated to the CA to be
|
||||||
|
// the same for all issued certificates
|
||||||
|
<< "CREATE TABLE takeys ("
|
||||||
|
"item INTEGER UNIQUE, " // reference to items(id) of the CA
|
||||||
|
"value " B64_BLOB ", " // The base64 encoded 2048 bit key
|
||||||
|
"FOREIGN KEY (item) REFERENCES items (id))"
|
||||||
|
<< "UPDATE settings SET value='8' WHERE key_='schema'"
|
||||||
|
;
|
||||||
|
|
||||||
/* When adding new tables or views, also add them to the list
|
/* When adding new tables or views, also add them to the list
|
||||||
* in XSqlQuery::rewriteQuery(QString) in lib/sql.cpp
|
* in XSqlQuery::rewriteQuery(QString) in lib/sql.cpp
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -566,11 +566,11 @@ int db_x509::exportFlags(const QModelIndex &idx) const
|
|||||||
return disable_flags;
|
return disable_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
void db_x509::writeTaggedPEM(BioByteArray &b, const QString &tag, XFile &file)
|
void db_x509::writeTaggedPEM(const BioByteArray &b, const QString &tag, XFile &file)
|
||||||
{
|
{
|
||||||
if (b.size() > 0) {
|
if (b.size() > 0) {
|
||||||
file.write(QString("<%1>\n").arg(tag).toLatin1());
|
file.write(QString("<%1>\n").arg(tag).toLatin1());
|
||||||
file.write(b);
|
file.write(b.byteArray());
|
||||||
file.write(QString("</%1>\n").arg(tag).toLatin1());
|
file.write(QString("</%1>\n").arg(tag).toLatin1());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -609,6 +609,7 @@ void db_x509::exportItems(const QModelIndexList &list,
|
|||||||
writeTaggedPEM(extra, "extra-certs", file);
|
writeTaggedPEM(extra, "extra-certs", file);
|
||||||
writeTaggedPEM(cert, "cert", file);
|
writeTaggedPEM(cert, "cert", file);
|
||||||
writeTaggedPEM(key, "key", file);
|
writeTaggedPEM(key, "key", file);
|
||||||
|
writeTaggedPEM(crt->getTaKey().toLatin1(), "tls-auth", file);
|
||||||
} else if (xport->match_all(F_CHAIN)) {
|
} else if (xport->match_all(F_CHAIN)) {
|
||||||
for (; crt && crt != oldcrt; oldcrt = crt, crt = crt->getSigner())
|
for (; crt && crt != oldcrt; oldcrt = crt, crt = crt->getSigner())
|
||||||
crt->writeCert(file, true);
|
crt->writeCert(file, true);
|
||||||
@ -655,6 +656,8 @@ void db_x509::exportItems(const QModelIndexList &list,
|
|||||||
writeVcalendar(file, vcal);
|
writeVcalendar(file, vcal);
|
||||||
} else if (xport->match_all(F_CONFIG)) {
|
} else if (xport->match_all(F_CONFIG)) {
|
||||||
crt->opensslConf(file);
|
crt->opensslConf(file);
|
||||||
|
} else if (xport->match_all(F_TAKEY)) {
|
||||||
|
file.write(crt->getTaKey().toLatin1());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ class db_x509: public db_x509super
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
static void writeTaggedPEM(BioByteArray &b,
|
static void writeTaggedPEM(const BioByteArray &b,
|
||||||
const QString &tag, XFile &file);
|
const QString &tag, XFile &file);
|
||||||
protected:
|
protected:
|
||||||
dbheaderList getHeaders();
|
dbheaderList getHeaders();
|
||||||
|
|||||||
@ -77,6 +77,7 @@ new pki_export(16, x509, "txt", tr("Certificate Index file"), F_INDEX | F_CA,
|
|||||||
new pki_export(17, x509, "ics", tr("vCalendar"), F_CAL, tr("vCalendar expiry reminder for the selected items")),
|
new pki_export(17, x509, "ics", tr("vCalendar"), F_CAL, tr("vCalendar expiry reminder for the selected items")),
|
||||||
new pki_export(18, x509, "ics", tr("CA vCalendar"), F_CAL | F_CA, tr("vCalendar expiry reminder containing all issued, valid certificates, the CA itself and the latest CRL")),
|
new pki_export(18, x509, "ics", tr("CA vCalendar"), F_CAL | F_CA, tr("vCalendar expiry reminder containing all issued, valid certificates, the CA itself and the latest CRL")),
|
||||||
new pki_export(38, x509, "conf", tr("OpenSSL config"), F_SINGLE | F_CONFIG, tr("OpenSSL configuration file to create a certificate or request with the openssl commandline tool")),
|
new pki_export(38, x509, "conf", tr("OpenSSL config"), F_SINGLE | F_CONFIG, tr("OpenSSL configuration file to create a certificate or request with the openssl commandline tool")),
|
||||||
|
new pki_export(39, x509, "key", tr("OpenVPN tls-auth key"), F_SINGLE | F_TAKEY, tr("The OpenVPN tls-auth key is a secret key shared between endpoints")),
|
||||||
|
|
||||||
new pki_export(19, asym_key, "pem", tr("PEM public"), F_PEM | F_CLIPBOARD, tr("Text format of the public key in one PEM file")),
|
new pki_export(19, asym_key, "pem", tr("PEM public"), F_PEM | F_CLIPBOARD, tr("Text format of the public key in one PEM file")),
|
||||||
new pki_export(20, asym_key, "pem", tr("PEM private"), F_PEM | F_PRIVATE | F_USUAL | F_CLIPBOARD, tr("Unencrypted private key in text format")),
|
new pki_export(20, asym_key, "pem", tr("PEM private"), F_PEM | F_PRIVATE | F_USUAL | F_CLIPBOARD, tr("Unencrypted private key in text format")),
|
||||||
|
|||||||
@ -49,6 +49,7 @@ enum {
|
|||||||
F_DER = BIT(19),/* DER format */
|
F_DER = BIT(19),/* DER format */
|
||||||
F_OVPN = BIT(20),/* OpenVPN format */
|
F_OVPN = BIT(20),/* OpenVPN format */
|
||||||
F_CONFIG = BIT(21),/* OpenSSL config format */
|
F_CONFIG = BIT(21),/* OpenSSL config format */
|
||||||
|
F_TAKEY = BIT(22),/* OpenVPN TLS-Auth key */
|
||||||
};
|
};
|
||||||
|
|
||||||
class pki_export : public QObject {
|
class pki_export : public QObject {
|
||||||
|
|||||||
@ -17,6 +17,8 @@
|
|||||||
#include "exception.h"
|
#include "exception.h"
|
||||||
#include "pass_info.h"
|
#include "pass_info.h"
|
||||||
|
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
|
||||||
pki_x509::pki_x509(X509 *c)
|
pki_x509::pki_x509(X509 *c)
|
||||||
:pki_x509super(), cert(c)
|
:pki_x509super(), cert(c)
|
||||||
{
|
{
|
||||||
@ -143,6 +145,7 @@ QSqlError pki_x509::deleteSqlData()
|
|||||||
<< "UPDATE crls SET issuer=NULL WHERE issuer=?"
|
<< "UPDATE crls SET issuer=NULL WHERE issuer=?"
|
||||||
<< "UPDATE certs SET issuer=NULL WHERE issuer=?"
|
<< "UPDATE certs SET issuer=NULL WHERE issuer=?"
|
||||||
<< "DELETE FROM revocations WHERE caId=?"
|
<< "DELETE FROM revocations WHERE caId=?"
|
||||||
|
<< "DELETE FROM takeys WHERE item=?"
|
||||||
;
|
;
|
||||||
foreach(QString task, tasks) {
|
foreach(QString task, tasks) {
|
||||||
SQL_PREPARE(q, task);
|
SQL_PREPARE(q, task);
|
||||||
@ -273,6 +276,42 @@ pki_x509 *pki_x509::getBySerial(const a1int &a) const
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString pki_x509::getTaKey()
|
||||||
|
{
|
||||||
|
XSqlQuery q;
|
||||||
|
QByteArray b;
|
||||||
|
pki_x509 *issuer = getSigner();
|
||||||
|
if (!isCA() && issuer && issuer != this)
|
||||||
|
return issuer->getTaKey();
|
||||||
|
|
||||||
|
Transaction;
|
||||||
|
if (!TransBegin())
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
SQL_PREPARE(q, "SELECT value FROM takeys WHERE item = ?");
|
||||||
|
q.bindValue(0, sqlItemId);
|
||||||
|
q.exec();
|
||||||
|
if (q.next()) {
|
||||||
|
b = QByteArray::fromBase64(q.value(0).toByteArray());
|
||||||
|
qDebug() << "Loaded TA key" << this << b.size() << QString::fromLatin1(b.toHex()).left(6);
|
||||||
|
} else {
|
||||||
|
b.resize(2048/8);
|
||||||
|
RAND_bytes((unsigned char*)b.data(), 2048/8);
|
||||||
|
SQL_PREPARE(q, "INSERT INTO takeys (item, value) VALUES ( ?, ? )");
|
||||||
|
q.bindValue(0, sqlItemId);
|
||||||
|
q.bindValue(1, b.toBase64());
|
||||||
|
q.exec();
|
||||||
|
qDebug() << "Generated TA key" << this << b.size() << QString::fromLatin1(b.toHex()).left(6);
|
||||||
|
}
|
||||||
|
TransCommit();
|
||||||
|
QString takey("-----BEGIN OpenVPN Static key V1-----\n");
|
||||||
|
QString hex(QString::fromLatin1(b.toHex()));
|
||||||
|
for (int i=0; i<16; i++)
|
||||||
|
takey += hex.mid(32*i, 32) + "\n";
|
||||||
|
takey += "-----END OpenVPN Static key V1-----\n";
|
||||||
|
return takey;
|
||||||
|
}
|
||||||
|
|
||||||
a1int pki_x509::hashInfo(const EVP_MD *md) const
|
a1int pki_x509::hashInfo(const EVP_MD *md) const
|
||||||
{
|
{
|
||||||
unsigned char digest[EVP_MAX_MD_SIZE];
|
unsigned char digest[EVP_MAX_MD_SIZE];
|
||||||
|
|||||||
@ -163,6 +163,7 @@ class pki_x509 : public pki_x509super
|
|||||||
void restoreSql(const QSqlRecord &rec);
|
void restoreSql(const QSqlRecord &rec);
|
||||||
QStringList icsVEVENT() const;
|
QStringList icsVEVENT() const;
|
||||||
QStringList icsVEVENT_ca() const;
|
QStringList icsVEVENT_ca() const;
|
||||||
|
QString getTaKey();
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(pki_x509 *);
|
Q_DECLARE_METATYPE(pki_x509 *);
|
||||||
|
|||||||
@ -125,7 +125,7 @@ QString XSqlQuery::rewriteQuery(QString _q)
|
|||||||
"items" , "crls" , "private_keys" , "public_keys" ,
|
"items" , "crls" , "private_keys" , "public_keys" ,
|
||||||
"tokens" , "token_mechanism" , "templates" , "certs" ,
|
"tokens" , "token_mechanism" , "templates" , "certs" ,
|
||||||
"authority" , "revocations" , "requests" , "x509super" ,
|
"authority" , "revocations" , "requests" , "x509super" ,
|
||||||
"settings" ,
|
"settings" , "takeys",
|
||||||
|
|
||||||
"view_public_keys" , "view_certs" , "view_requests" ,
|
"view_public_keys" , "view_certs" , "view_requests" ,
|
||||||
"view_crls" , "view_templates" , "view_private",
|
"view_crls" , "view_templates" , "view_private",
|
||||||
|
|||||||
@ -24,14 +24,14 @@
|
|||||||
void check_pems(const QString &name, int n, QStringList matches = QStringList())
|
void check_pems(const QString &name, int n, QStringList matches = QStringList())
|
||||||
{
|
{
|
||||||
int begin = 0, end = 0;
|
int begin = 0, end = 0;
|
||||||
qWarning() << "Expecting" << n << "PEMs in" << name;
|
qDebug() << "Expecting" << n << "PEMs in" << name;
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
// This is an endless loop: open_read() succeeds,
|
// This is an endless loop: open_read() succeeds,
|
||||||
// but isOpen returns false. Stop investigating, use POSIX open()
|
// but isOpen returns false. Stop investigating, use POSIX open()
|
||||||
XFile F(name);
|
XFile F(name);
|
||||||
while (!F.isOpen()) {
|
while (!F.isOpen()) {
|
||||||
qWarning() << "OPEN" << name;
|
qDebug() << "OPEN" << name;
|
||||||
F.close();
|
F.close();
|
||||||
Q_ASSERT(F.open_read());
|
Q_ASSERT(F.open_read());
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ void check_pems(const QString &name, int n, QStringList matches = QStringList())
|
|||||||
QByteArray all(buf, ret);
|
QByteArray all(buf, ret);
|
||||||
close(fd);
|
close(fd);
|
||||||
#endif
|
#endif
|
||||||
qWarning() << "ALL" << name << all.size();
|
qDebug() << "ALL" << name << all.size();
|
||||||
|
|
||||||
foreach(QByteArray b, all.split('\n')) {
|
foreach(QByteArray b, all.split('\n')) {
|
||||||
if (b.indexOf("-----BEGIN ") == 0)
|
if (b.indexOf("-----BEGIN ") == 0)
|
||||||
@ -63,7 +63,7 @@ void check_pems(const QString &name, int n, QStringList matches = QStringList())
|
|||||||
QCOMPARE(begin, n);
|
QCOMPARE(begin, n);
|
||||||
QCOMPARE(end, n);
|
QCOMPARE(end, n);
|
||||||
foreach(QString m, matches) {
|
foreach(QString m, matches) {
|
||||||
qWarning() << QString("Pattern %1 not found in %2").arg(m).arg(name);
|
qDebug() << QString("Pattern %1 not found in %2").arg(m).arg(name);
|
||||||
}
|
}
|
||||||
QCOMPARE(matches.size(), 0);
|
QCOMPARE(matches.size(), 0);
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ void verify_key(const QString &name, QList<unsigned> hashes, bool priv)
|
|||||||
QCOMPARE(pems->get().size(), hashes.size());
|
QCOMPARE(pems->get().size(), hashes.size());
|
||||||
foreach (pki_base *pki, pems->get()) {
|
foreach (pki_base *pki, pems->get()) {
|
||||||
unsigned hash = pki->hash();
|
unsigned hash = pki->hash();
|
||||||
qWarning() << pki->getIntName() << hash;
|
qDebug() << pki->getIntName() << hash;
|
||||||
QVERIFY2(hashes.contains(hash),
|
QVERIFY2(hashes.contains(hash),
|
||||||
qPrintable(QString("%1 not expected in %2")
|
qPrintable(QString("%1 not expected in %2")
|
||||||
.arg(pki->getIntName())
|
.arg(pki->getIntName())
|
||||||
@ -220,10 +220,11 @@ void test_main::exportFormat()
|
|||||||
export_by_id(4, file, list, certs);
|
export_by_id(4, file, list, certs);
|
||||||
verify_key(file, QList<unsigned> {
|
verify_key(file, QList<unsigned> {
|
||||||
ROOT_HASH, INTER_HASH, END_HASH, ENDKEY_HASH }, true);
|
ROOT_HASH, INTER_HASH, END_HASH, ENDKEY_HASH }, true);
|
||||||
check_pems(file, 4, QStringList { " RSA PRIVATE KEY-",
|
check_pems(file, 5, QStringList { " RSA PRIVATE KEY-",
|
||||||
" CERTIFICATE-", " CERTIFICATE-"," CERTIFICATE-",
|
" CERTIFICATE-", " CERTIFICATE-"," CERTIFICATE-",
|
||||||
"<ca>", "</ca>", "<extra-certs>", "</extra-certs>",
|
"<ca>", "</ca>", "<extra-certs>", "</extra-certs>",
|
||||||
"<cert>", "</cert>", "<key>", "</key>" });
|
"<cert>", "</cert>", "<key>", "</key>",
|
||||||
|
"<tls-auth>", "</tls-auth>" });
|
||||||
// Export Endentity as PKCS#7
|
// Export Endentity as PKCS#7
|
||||||
file = AUTOFILE(CERTP7)
|
file = AUTOFILE(CERTP7)
|
||||||
export_by_id(8, file, list, certs);
|
export_by_id(8, file, list, certs);
|
||||||
@ -235,9 +236,15 @@ void test_main::exportFormat()
|
|||||||
verify_file(file, QList<unsigned> { ROOT_HASH, INTER_HASH, END_HASH });
|
verify_file(file, QList<unsigned> { ROOT_HASH, INTER_HASH, END_HASH });
|
||||||
check_pems(file, 0);
|
check_pems(file, 0);
|
||||||
// Export Endentity as DER certificate
|
// Export Endentity as DER certificate
|
||||||
|
file = AUTOFILE(CERTDER)
|
||||||
export_by_id(13, file, list, certs);
|
export_by_id(13, file, list, certs);
|
||||||
verify_file(file, QList<unsigned> { END_HASH });
|
verify_file(file, QList<unsigned> { END_HASH });
|
||||||
check_pems(file, 0);
|
check_pems(file, 0);
|
||||||
|
// Export Endentity as OpenVPN config file
|
||||||
|
file = AUTOFILE(OPENVPNTA)
|
||||||
|
export_by_id(39, file, list, certs);
|
||||||
|
check_pems(file, 1, QStringList {
|
||||||
|
"BEGIN OpenVPN Static key V1", "END OpenVPN Static key V1" });
|
||||||
|
|
||||||
// Export Endentity key
|
// Export Endentity key
|
||||||
list.clear();
|
list.clear();
|
||||||
|
|||||||
@ -68,6 +68,7 @@ void test_main::openDB()
|
|||||||
new pw_expect("testdbpass", pw_ok),
|
new pw_expect("testdbpass", pw_ok),
|
||||||
});
|
});
|
||||||
mainwin->close_database();
|
mainwin->close_database();
|
||||||
|
QFile::remove("testdb.xdb");
|
||||||
Database.open("testdb.xdb");
|
Database.open("testdb.xdb");
|
||||||
Settings["pkcs12_keep_legacy"] = true;
|
Settings["pkcs12_keep_legacy"] = true;
|
||||||
mainwin->setup_open_database();
|
mainwin->setup_open_database();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user