1 /***************************************************************************
2     qgsauthcertutils.cpp
3     ---------------------
4     begin                : May 1, 2015
5     copyright            : (C) 2015 by Boundless Spatial, Inc. USA
6     author               : Larry Shaffer
7     email                : lshaffer at boundlessgeo dot com
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "qgsauthcertutils.h"
18 
19 #include <QColor>
20 #include <QDir>
21 #include <QFile>
22 #include <QObject>
23 #include <QSslCertificate>
24 #include <QUuid>
25 
26 #include "qgsapplication.h"
27 #include "qgsauthmanager.h"
28 #include "qgslogger.h"
29 
30 #ifdef Q_OS_MAC
31 #include <string.h>
32 #include "libtasn1.h"
33 #endif
34 
35 
getSslProtocolName(QSsl::SslProtocol protocol)36 QString QgsAuthCertUtils::getSslProtocolName( QSsl::SslProtocol protocol )
37 {
38   switch ( protocol )
39   {
40     case QSsl::SecureProtocols:
41       return QObject::tr( "SecureProtocols" );
42 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
43     case QSsl::TlsV1SslV3:
44       return QObject::tr( "TlsV1SslV3" );
45 #endif
46     case QSsl::TlsV1_0:
47       return QObject::tr( "TlsV1" );
48 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
49     // not supported by Qt 5.15+
50     case QSsl::SslV3:
51       return QObject::tr( "SslV3" );
52     case QSsl::SslV2:
53       return QObject::tr( "SslV2" );
54 #endif
55     default:
56       return QString();
57   }
58 }
59 
mapDigestToCerts(const QList<QSslCertificate> & certs)60 QMap<QString, QSslCertificate> QgsAuthCertUtils::mapDigestToCerts( const QList<QSslCertificate> &certs )
61 {
62   QMap<QString, QSslCertificate> digestmap;
63   for ( const auto &cert : certs )
64   {
65     digestmap.insert( shaHexForCert( cert ), cert );
66   }
67   return digestmap;
68 }
69 
certsGroupedByOrg(const QList<QSslCertificate> & certs)70 QMap<QString, QList<QSslCertificate> > QgsAuthCertUtils::certsGroupedByOrg( const QList<QSslCertificate> &certs )
71 {
72   QMap< QString, QList<QSslCertificate> > orgcerts;
73   for ( const auto &cert : certs )
74   {
75     QString org( SSL_SUBJECT_INFO( cert, QSslCertificate::Organization ) );
76     if ( org.isEmpty() )
77       org = QStringLiteral( "(Organization not defined)" );
78     QList<QSslCertificate> valist = orgcerts.contains( org ) ? orgcerts.value( org ) : QList<QSslCertificate>();
79     orgcerts.insert( org, valist << cert );
80   }
81   return orgcerts;
82 }
83 
mapDigestToSslConfigs(const QList<QgsAuthConfigSslServer> & configs)84 QMap<QString, QgsAuthConfigSslServer> QgsAuthCertUtils::mapDigestToSslConfigs( const QList<QgsAuthConfigSslServer> &configs )
85 {
86   QMap<QString, QgsAuthConfigSslServer> digestmap;
87   for ( const auto &config : configs )
88   {
89     digestmap.insert( shaHexForCert( config.sslCertificate() ), config );
90   }
91   return digestmap;
92 }
93 
sslConfigsGroupedByOrg(const QList<QgsAuthConfigSslServer> & configs)94 QMap<QString, QList<QgsAuthConfigSslServer> > QgsAuthCertUtils::sslConfigsGroupedByOrg( const QList<QgsAuthConfigSslServer> &configs )
95 {
96   QMap< QString, QList<QgsAuthConfigSslServer> > orgconfigs;
97   for ( const auto &config : configs )
98   {
99     QString org( SSL_SUBJECT_INFO( config.sslCertificate(), QSslCertificate::Organization ) );
100 
101     if ( org.isEmpty() )
102       org = QObject::tr( "(Organization not defined)" );
103     QList<QgsAuthConfigSslServer> valist = orgconfigs.contains( org ) ? orgconfigs.value( org ) : QList<QgsAuthConfigSslServer>();
104     orgconfigs.insert( org, valist << config );
105   }
106   return orgconfigs;
107 }
108 
fileData(const QString & path)109 QByteArray QgsAuthCertUtils::fileData( const QString &path )
110 {
111   QByteArray data;
112   QFile file( path );
113   if ( !file.exists() )
114   {
115     QgsDebugMsg( QStringLiteral( "Read file error, file not found: %1" ).arg( path ) );
116     return data;
117   }
118   // TODO: add checks for locked file, etc., to ensure it can be read
119   const QFile::OpenMode openflags( QIODevice::ReadOnly );
120   const bool ret = file.open( openflags );
121   if ( ret )
122   {
123     data = file.readAll();
124   }
125   file.close();
126 
127   return data;
128 }
129 
certsFromFile(const QString & certspath)130 QList<QSslCertificate> QgsAuthCertUtils::certsFromFile( const QString &certspath )
131 {
132   QList<QSslCertificate> certs;
133   const QByteArray payload( QgsAuthCertUtils::fileData( certspath ) );
134   certs = QSslCertificate::fromData( payload, sniffEncoding( payload ) );
135   if ( certs.isEmpty() )
136   {
137     QgsDebugMsg( QStringLiteral( "Parsed cert(s) EMPTY for path: %1" ).arg( certspath ) );
138   }
139   return certs;
140 }
141 
casFromFile(const QString & certspath)142 QList<QSslCertificate> QgsAuthCertUtils::casFromFile( const QString &certspath )
143 {
144   QList<QSslCertificate> cas;
145   const QList<QSslCertificate> certs( certsFromFile( certspath ) );
146   for ( const auto &cert : certs )
147   {
148     if ( certificateIsAuthority( cert ) )
149     {
150       cas.append( cert );
151     }
152   }
153   return cas;
154 }
155 
casMerge(const QList<QSslCertificate> & bundle1,const QList<QSslCertificate> & bundle2)156 QList<QSslCertificate> QgsAuthCertUtils::casMerge( const QList<QSslCertificate> &bundle1, const QList<QSslCertificate> &bundle2 )
157 {
158   QStringList shas;
159   QList<QSslCertificate> result( bundle1 );
160   const QList<QSslCertificate> c_bundle1( bundle1 );
161   for ( const auto &cert : c_bundle1 )
162   {
163     shas.append( shaHexForCert( cert ) );
164   }
165   const QList<QSslCertificate> c_bundle2( bundle2 );
166   for ( const auto &cert : c_bundle2 )
167   {
168     if ( ! shas.contains( shaHexForCert( cert ) ) )
169     {
170       result.append( cert );
171     }
172   }
173   return result;
174 }
175 
176 
177 
certFromFile(const QString & certpath)178 QSslCertificate QgsAuthCertUtils::certFromFile( const QString &certpath )
179 {
180   QSslCertificate cert;
181   QList<QSslCertificate> certs( QgsAuthCertUtils::certsFromFile( certpath ) );
182   if ( !certs.isEmpty() )
183   {
184     cert = certs.first();
185   }
186   if ( cert.isNull() )
187   {
188     QgsDebugMsg( QStringLiteral( "Parsed cert is NULL for path: %1" ).arg( certpath ) );
189   }
190   return cert;
191 }
192 
keyFromFile(const QString & keypath,const QString & keypass,QString * algtype)193 QSslKey QgsAuthCertUtils::keyFromFile( const QString &keypath,
194                                        const QString &keypass,
195                                        QString *algtype )
196 {
197   // The approach here is to try all possible encodings and algorithms
198   const QByteArray keydata( QgsAuthCertUtils::fileData( keypath ) );
199   QSslKey clientkey;
200 
201   const QSsl::EncodingFormat keyEncoding( sniffEncoding( keydata ) );
202 
203   const std::vector<QSsl::KeyAlgorithm> algs
204   {
205     QSsl::KeyAlgorithm::Rsa,
206     QSsl::KeyAlgorithm::Dsa,
207     QSsl::KeyAlgorithm::Ec,
208     QSsl::KeyAlgorithm::Opaque
209   };
210 
211   for ( const auto &alg : algs )
212   {
213     clientkey = QSslKey( keydata,
214                          alg,
215                          keyEncoding,
216                          QSsl::PrivateKey,
217                          !keypass.isEmpty() ? keypass.toUtf8() : QByteArray() );
218     if ( ! clientkey.isNull() )
219     {
220       if ( algtype )
221       {
222         switch ( alg )
223         {
224           case QSsl::KeyAlgorithm::Rsa:
225             *algtype = QStringLiteral( "rsa" );
226             break;
227           case QSsl::KeyAlgorithm::Dsa:
228             *algtype = QStringLiteral( "dsa" );
229             break;
230           case QSsl::KeyAlgorithm::Ec:
231             *algtype = QStringLiteral( "ec" );
232             break;
233           case QSsl::KeyAlgorithm::Opaque:
234             *algtype = QStringLiteral( "opaque" );
235             break;
236 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
237           case QSsl::KeyAlgorithm::Dh:
238             *algtype = QStringLiteral( "dh" );
239             break;
240 #endif
241         }
242       }
243       return clientkey;
244     }
245   }
246   return QSslKey();
247 }
248 
certsFromString(const QString & pemtext)249 QList<QSslCertificate> QgsAuthCertUtils::certsFromString( const QString &pemtext )
250 {
251   QList<QSslCertificate> certs;
252   certs = QSslCertificate::fromData( pemtext.toLatin1(), QSsl::Pem );
253   if ( certs.isEmpty() )
254   {
255     QgsDebugMsg( QStringLiteral( "Parsed cert(s) EMPTY" ) );
256   }
257   return certs;
258 }
259 
casRemoveSelfSigned(const QList<QSslCertificate> & caList)260 QList<QSslCertificate> QgsAuthCertUtils::casRemoveSelfSigned( const QList<QSslCertificate> &caList )
261 {
262   QList<QSslCertificate> certs;
263   for ( const auto &cert : caList )
264   {
265     if ( ! cert.isSelfSigned( ) )
266     {
267       certs.append( cert );
268     }
269   }
270   return certs;
271 }
272 
certKeyBundleToPem(const QString & certpath,const QString & keypath,const QString & keypass,bool reencrypt)273 QStringList QgsAuthCertUtils::certKeyBundleToPem( const QString &certpath,
274     const QString &keypath,
275     const QString &keypass,
276     bool reencrypt )
277 {
278   QString certpem;
279   const QSslCertificate clientcert = QgsAuthCertUtils::certFromFile( certpath );
280   if ( !clientcert.isNull() )
281   {
282     certpem = QString( clientcert.toPem() );
283   }
284 
285   QString keypem;
286   QString algtype;
287   const QSslKey clientkey = QgsAuthCertUtils::keyFromFile( keypath, keypass, &algtype );
288 
289   // reapply passphrase if protection is requested and passphrase exists
290   if ( !clientkey.isNull() )
291   {
292     keypem = QString( clientkey.toPem( ( reencrypt && !keypass.isEmpty() ) ? keypass.toUtf8() : QByteArray() ) );
293   }
294 
295   return QStringList() << certpem << keypem << algtype;
296 }
297 
pemIsPkcs8(const QString & keyPemTxt)298 bool QgsAuthCertUtils::pemIsPkcs8( const QString &keyPemTxt )
299 {
300   const QString pkcs8Header = QStringLiteral( "-----BEGIN PRIVATE KEY-----" );
301   const QString pkcs8Footer = QStringLiteral( "-----END PRIVATE KEY-----" );
302   return keyPemTxt.contains( pkcs8Header ) && keyPemTxt.contains( pkcs8Footer );
303 }
304 
305 #ifdef Q_OS_MAC
pkcs8PrivateKey(QByteArray & pkcs8Der)306 QByteArray QgsAuthCertUtils::pkcs8PrivateKey( QByteArray &pkcs8Der )
307 {
308   QByteArray pkcs1;
309 
310   if ( pkcs8Der.isEmpty() )
311   {
312     QgsDebugMsg( QStringLiteral( "ERROR, passed DER is empty" ) );
313     return pkcs1;
314   }
315   // Dump as unarmored PEM format, e.g. missing '-----BEGIN|END...' wrapper
316   //QgsDebugMsg ( QStringLiteral( "pkcs8Der: %1" ).arg( QString( pkcs8Der.toBase64() ) ) );
317 
318   QFileInfo asnDefsRsrc( QgsApplication::pkgDataPath() + QStringLiteral( "/resources/pkcs8.asn" ) );
319   if ( ! asnDefsRsrc.exists() )
320   {
321     QgsDebugMsg( QStringLiteral( "ERROR, pkcs.asn resource file not found: %1" ).arg( asnDefsRsrc.filePath() ) );
322     return pkcs1;
323   }
324   const char *asnDefsFile = asnDefsRsrc.absoluteFilePath().toLocal8Bit().constData();
325 
326   int asn1_result = ASN1_SUCCESS, der_len = 0, oct_len = 0;
327   asn1_node definitions = NULL, structure = NULL;
328   char errorDescription[ASN1_MAX_ERROR_DESCRIPTION_SIZE], oct_data[1024];
329   unsigned char *der = NULL;
330   unsigned int flags = 0; //TODO: see if any or all ASN1_DECODE_FLAG_* flags can be set
331   unsigned oct_etype;
332 
333   // Base PKCS#8 element to decode
334   QString typeName( QStringLiteral( "PKCS-8.PrivateKeyInfo" ) );
335 
336   asn1_result = asn1_parser2tree( asnDefsFile, &definitions, errorDescription );
337 
338   switch ( asn1_result )
339   {
340     case ASN1_SUCCESS:
341       QgsDebugMsgLevel( QStringLiteral( "Parse: done.\n" ), 4 );
342       break;
343     case ASN1_FILE_NOT_FOUND:
344       QgsDebugMsg( QStringLiteral( "ERROR, file not found: %1" ).arg( asnDefsFile ) );
345       return pkcs1;
346     case ASN1_SYNTAX_ERROR:
347     case ASN1_IDENTIFIER_NOT_FOUND:
348     case ASN1_NAME_TOO_LONG:
349       QgsDebugMsg( QStringLiteral( "ERROR, asn1 parsing: %1" ).arg( errorDescription ) );
350       return pkcs1;
351     default:
352       QgsDebugMsg( QStringLiteral( "ERROR, libtasn1: %1" ).arg( asn1_strerror( asn1_result ) ) );
353       return pkcs1;
354   }
355 
356   // Generate the ASN.1 structure
357   asn1_result = asn1_create_element( definitions, typeName.toLatin1().constData(), &structure );
358 
359   //asn1_print_structure( stdout, structure, "", ASN1_PRINT_ALL);
360 
361   if ( asn1_result != ASN1_SUCCESS )
362   {
363     QgsDebugMsg( QStringLiteral( "ERROR, structure creation: %1" ).arg( asn1_strerror( asn1_result ) ) );
364     goto PKCS1DONE;
365   }
366 
367   // Populate the ASN.1 structure with decoded DER data
368   der = reinterpret_cast<unsigned char *>( pkcs8Der.data() );
369   der_len = pkcs8Der.size();
370 
371   if ( flags != 0 )
372   {
373     asn1_result = asn1_der_decoding2( &structure, der, &der_len, flags, errorDescription );
374   }
375   else
376   {
377     asn1_result = asn1_der_decoding( &structure, der, der_len, errorDescription );
378   }
379 
380   if ( asn1_result != ASN1_SUCCESS )
381   {
382     QgsDebugMsg( QStringLiteral( "ERROR, decoding: %1" ).arg( errorDescription ) );
383     goto PKCS1DONE;
384   }
385   else
386   {
387     QgsDebugMsgLevel( QStringLiteral( "Decoding: %1" ).arg( asn1_strerror( asn1_result ) ), 4 );
388   }
389 
390   if ( QgsLogger::debugLevel() >= 4 )
391   {
392     QgsDebugMsg( QStringLiteral( "DECODING RESULT:" ) );
393     asn1_print_structure( stdout, structure, "", ASN1_PRINT_NAME_TYPE_VALUE );
394   }
395 
396   // Validate and extract privateKey value
397   QgsDebugMsgLevel( QStringLiteral( "Validating privateKey type..." ), 4 );
398   typeName.append( QStringLiteral( ".privateKey" ) );
399   QgsDebugMsgLevel( QStringLiteral( "privateKey element name: %1" ).arg( typeName ), 4 );
400 
401   asn1_result = asn1_read_value_type( structure, "privateKey", NULL, &oct_len, &oct_etype );
402 
403   if ( asn1_result != ASN1_MEM_ERROR ) // not sure why ASN1_MEM_ERROR = success, but it does
404   {
405     QgsDebugMsg( QStringLiteral( "ERROR, asn1 read privateKey value type: %1" ).arg( asn1_strerror( asn1_result ) ) );
406     goto PKCS1DONE;
407   }
408 
409   if ( oct_etype != ASN1_ETYPE_OCTET_STRING )
410   {
411     QgsDebugMsg( QStringLiteral( "ERROR, asn1 privateKey value not octet string, but type: %1" ).arg( static_cast<int>( oct_etype ) ) );
412     goto PKCS1DONE;
413   }
414 
415   if ( oct_len == 0 )
416   {
417     QgsDebugMsg( QStringLiteral( "ERROR, asn1 privateKey octet string empty" ) );
418     goto PKCS1DONE;
419   }
420 
421   QgsDebugMsgLevel( QStringLiteral( "Reading privateKey value..." ), 4 );
422   asn1_result = asn1_read_value( structure, "privateKey", oct_data, &oct_len );
423 
424   if ( asn1_result != ASN1_SUCCESS )
425   {
426     QgsDebugMsg( QStringLiteral( "ERROR, asn1 read privateKey value: %1" ).arg( asn1_strerror( asn1_result ) ) );
427     goto PKCS1DONE;
428   }
429 
430   if ( oct_len == 0 )
431   {
432     QgsDebugMsg( QStringLiteral( "ERROR, asn1 privateKey value octet string empty" ) );
433     goto PKCS1DONE;
434   }
435 
436   pkcs1 = QByteArray( oct_data, oct_len );
437 
438   // !!! SENSITIVE DATA - DO NOT LEAVE UNCOMMENTED !!!
439   //QgsDebugMsgLevel( QStringLiteral( "privateKey octet data as PEM: %1" ).arg( QString( pkcs1.toBase64() ) ), 4 );
440 
441 PKCS1DONE:
442 
443   asn1_delete_structure( &structure );
444   return pkcs1;
445 }
446 #endif
447 
pkcs12BundleToPem(const QString & bundlepath,const QString & bundlepass,bool reencrypt)448 QStringList QgsAuthCertUtils::pkcs12BundleToPem( const QString &bundlepath,
449     const QString &bundlepass,
450     bool reencrypt )
451 {
452   QStringList empty;
453   if ( !QCA::isSupported( "pkcs12" ) )
454   {
455     QgsDebugMsg( QStringLiteral( "QCA does not support PKCS#12" ) );
456     return empty;
457   }
458 
459   const QCA::KeyBundle bundle( QgsAuthCertUtils::qcaKeyBundle( bundlepath, bundlepass ) );
460   if ( bundle.isNull() )
461   {
462     QgsDebugMsg( QStringLiteral( "FAILED to convert PKCS#12 file to QCA key bundle: %1" ).arg( bundlepath ) );
463     return empty;
464   }
465 
466   QCA::SecureArray passarray;
467   if ( reencrypt && !bundlepass.isEmpty() )
468   {
469     passarray = QCA::SecureArray( bundlepass.toUtf8() );
470   }
471 
472   QString algtype;
473   QSsl::KeyAlgorithm keyalg = QSsl::Opaque;
474   if ( bundle.privateKey().isRSA() )
475   {
476     algtype = QStringLiteral( "rsa" );
477     keyalg = QSsl::Rsa;
478   }
479   else if ( bundle.privateKey().isDSA() )
480   {
481     algtype = QStringLiteral( "dsa" );
482     keyalg = QSsl::Dsa;
483   }
484   else if ( bundle.privateKey().isDH() )
485   {
486     algtype = QStringLiteral( "dh" );
487   }
488   // TODO: add support for EC keys, once QCA supports them
489 
490   // can currently only support RSA and DSA between QCA and Qt
491   if ( keyalg == QSsl::Opaque )
492   {
493     QgsDebugMsg( QStringLiteral( "FAILED to read PKCS#12 key (only RSA and DSA algorithms supported): %1" ).arg( bundlepath ) );
494     return empty;
495   }
496 
497   QString keyPem;
498 #ifdef Q_OS_MAC
499   if ( keyalg == QSsl::Rsa && QgsAuthCertUtils::pemIsPkcs8( bundle.privateKey().toPEM() ) )
500   {
501     QgsDebugMsgLevel( QStringLiteral( "Private key is PKCS#8: attempting conversion to PKCS#1..." ), 4 );
502     // if RSA, convert from PKCS#8 key to 'traditional' OpenSSL RSA format, which Qt prefers
503     // note: QCA uses OpenSSL, regardless of the Qt SSL backend, and 1.0.2+ OpenSSL versions return
504     //       RSA private keys as PKCS#8, which choke Qt upon QSslKey creation
505 
506     QByteArray pkcs8Der = bundle.privateKey().toDER().toByteArray();
507     if ( pkcs8Der.isEmpty() )
508     {
509       QgsDebugMsg( QStringLiteral( "FAILED to convert PKCS#12 key to DER-encoded format: %1" ).arg( bundlepath ) );
510       return empty;
511     }
512 
513     QByteArray pkcs1Der = QgsAuthCertUtils::pkcs8PrivateKey( pkcs8Der );
514     if ( pkcs1Der.isEmpty() )
515     {
516       QgsDebugMsg( QStringLiteral( "FAILED to convert PKCS#12 key from PKCS#8 to PKCS#1: %1" ).arg( bundlepath ) );
517       return empty;
518     }
519 
520     QSslKey pkcs1Key( pkcs1Der, QSsl::Rsa, QSsl::Der, QSsl::PrivateKey );
521     if ( pkcs1Key.isNull() )
522     {
523       QgsDebugMsg( QStringLiteral( "FAILED to convert PKCS#12 key from PKCS#8 to PKCS#1 QSslKey: %1" ).arg( bundlepath ) );
524       return empty;
525     }
526     keyPem = QString( pkcs1Key.toPem( passarray.toByteArray() ) );
527   }
528   else
529   {
530     keyPem = bundle.privateKey().toPEM( passarray );
531   }
532 #else
533   keyPem = bundle.privateKey().toPEM( passarray );
534 #endif
535 
536   QgsDebugMsgLevel( QStringLiteral( "PKCS#12 cert as PEM:\n%1" ).arg( QString( bundle.certificateChain().primary().toPEM() ) ), 4 );
537   // !!! SENSITIVE DATA - DO NOT LEAVE UNCOMMENTED !!!
538   //QgsDebugMsgLevel( QStringLiteral( "PKCS#12 key as PEM:\n%1" ).arg( QString( keyPem ) ), 4 );
539 
540   return QStringList() << bundle.certificateChain().primary().toPEM() << keyPem << algtype;
541 }
542 
pkcs12BundleCas(const QString & bundlepath,const QString & bundlepass)543 QList<QSslCertificate> QgsAuthCertUtils::pkcs12BundleCas( const QString &bundlepath, const QString &bundlepass )
544 {
545   QList<QSslCertificate> result;
546   if ( !QCA::isSupported( "pkcs12" ) )
547     return result;
548 
549   const QCA::KeyBundle bundle( QgsAuthCertUtils::qcaKeyBundle( bundlepath, bundlepass ) );
550   if ( bundle.isNull() )
551     return result;
552 
553   const QCA::CertificateChain chain( bundle.certificateChain() );
554   for ( const auto &cert : chain )
555   {
556     if ( cert.isCA( ) )
557     {
558       result.append( QSslCertificate::fromData( cert.toPEM().toLatin1() ) );
559     }
560   }
561   return result;
562 }
563 
certsToPemText(const QList<QSslCertificate> & certs)564 QByteArray QgsAuthCertUtils::certsToPemText( const QList<QSslCertificate> &certs )
565 {
566   QByteArray capem;
567   if ( !certs.isEmpty() )
568   {
569     QStringList certslist;
570     for ( const auto &cert : certs )
571     {
572       certslist << cert.toPem();
573     }
574     capem = certslist.join( QLatin1Char( '\n' ) ).toLatin1(); //+ "\n";
575   }
576   return capem;
577 }
578 
pemTextToTempFile(const QString & name,const QByteArray & pemtext)579 QString QgsAuthCertUtils::pemTextToTempFile( const QString &name, const QByteArray &pemtext )
580 {
581   QFile pemFile( QDir::tempPath() + QDir::separator() + name );
582   QString pemFilePath( pemFile.fileName() );
583 
584   if ( pemFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
585   {
586     const qint64 bytesWritten = pemFile.write( pemtext );
587     if ( bytesWritten == -1 )
588     {
589       QgsDebugMsg( QStringLiteral( "FAILED to write to temp PEM file: %1" ).arg( pemFilePath ) );
590       pemFilePath.clear();
591     }
592   }
593   else
594   {
595     QgsDebugMsg( QStringLiteral( "FAILED to open writing for temp PEM file: %1" ).arg( pemFilePath ) );
596     pemFilePath.clear();
597   }
598 
599   if ( !pemFile.setPermissions( QFile::ReadUser ) )
600   {
601     QgsDebugMsg( QStringLiteral( "FAILED to set permissions on temp PEM file: %1" ).arg( pemFilePath ) );
602     pemFilePath.clear();
603   }
604 
605   return pemFilePath;
606 }
607 
getCaSourceName(QgsAuthCertUtils::CaCertSource source,bool single)608 QString QgsAuthCertUtils::getCaSourceName( QgsAuthCertUtils::CaCertSource source, bool single )
609 {
610   switch ( source )
611   {
612     case SystemRoot:
613       return single ? QObject::tr( "System Root CA" ) : QObject::tr( "System Root Authorities" );
614     case FromFile:
615       return single ? QObject::tr( "File CA" ) : QObject::tr( "Authorities from File" );
616     case InDatabase:
617       return single ? QObject::tr( "Database CA" ) : QObject::tr( "Authorities in Database" );
618     case Connection:
619       return single ? QObject::tr( "Connection CA" ) : QObject::tr( "Authorities from connection" );
620     default:
621       return QString();
622   }
623 }
624 
resolvedCertName(const QSslCertificate & cert,bool issuer)625 QString QgsAuthCertUtils::resolvedCertName( const QSslCertificate &cert, bool issuer )
626 {
627   QString name( issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::CommonName )
628                 : SSL_SUBJECT_INFO( cert, QSslCertificate::CommonName ) );
629 
630   if ( name.isEmpty() )
631     name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::OrganizationalUnitName )
632            : SSL_SUBJECT_INFO( cert, QSslCertificate::OrganizationalUnitName );
633 
634   if ( name.isEmpty() )
635     name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::Organization )
636            : SSL_SUBJECT_INFO( cert, QSslCertificate::Organization );
637 
638   if ( name.isEmpty() )
639     name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::LocalityName )
640            : SSL_SUBJECT_INFO( cert, QSslCertificate::LocalityName );
641 
642   if ( name.isEmpty() )
643     name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::StateOrProvinceName )
644            : SSL_SUBJECT_INFO( cert, QSslCertificate::StateOrProvinceName );
645 
646   if ( name.isEmpty() )
647     name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::CountryName )
648            : SSL_SUBJECT_INFO( cert, QSslCertificate::CountryName );
649 
650   return name;
651 }
652 
653 // private
appendDirSegment_(QStringList & dirname,const QString & segment,QString value)654 void QgsAuthCertUtils::appendDirSegment_( QStringList &dirname,
655     const QString &segment, QString value )
656 {
657   if ( !value.isEmpty() )
658   {
659     dirname.append( segment + '=' + value.replace( ',', QLatin1String( "\\," ) ) );
660   }
661 }
662 
sniffEncoding(const QByteArray & payload)663 QSsl::EncodingFormat QgsAuthCertUtils::sniffEncoding( const QByteArray &payload )
664 {
665   return payload.contains( QByteArrayLiteral( "-----BEGIN " ) ) ?
666          QSsl::Pem :
667          QSsl::Der;
668 }
669 
getCertDistinguishedName(const QSslCertificate & qcert,const QCA::Certificate & acert,bool issuer)670 QString QgsAuthCertUtils::getCertDistinguishedName( const QSslCertificate &qcert,
671     const QCA::Certificate &acert,
672     bool issuer )
673 {
674   if ( QgsApplication::authManager()->isDisabled() )
675     return QString();
676 
677   if ( acert.isNull() )
678   {
679     QCA::ConvertResult res;
680     const QCA::Certificate acert( QCA::Certificate::fromPEM( qcert.toPem(), &res, QStringLiteral( "qca-ossl" ) ) );
681     if ( res != QCA::ConvertGood || acert.isNull() )
682     {
683       QgsDebugMsg( QStringLiteral( "Certificate could not be converted to QCA cert" ) );
684       return QString();
685     }
686   }
687   //  E=testcert@boundlessgeo.com,
688   //  CN=Boundless Test Root CA,
689   //  OU=Certificate Authority,
690   //  O=Boundless Test CA,
691   //  L=District of Columbia,
692   //  ST=Washington\, DC,
693   //  C=US
694   QStringList dirname;
695   QgsAuthCertUtils::appendDirSegment_(
696     dirname, QStringLiteral( "E" ), issuer ? acert.issuerInfo().value( QCA::Email )
697     : acert.subjectInfo().value( QCA::Email ) );
698   QgsAuthCertUtils::appendDirSegment_(
699     dirname, QStringLiteral( "CN" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::CommonName )
700     : SSL_SUBJECT_INFO( qcert, QSslCertificate::CommonName ) );
701   QgsAuthCertUtils::appendDirSegment_(
702     dirname, QStringLiteral( "OU" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::OrganizationalUnitName )
703     : SSL_SUBJECT_INFO( qcert, QSslCertificate::OrganizationalUnitName ) );
704   QgsAuthCertUtils::appendDirSegment_(
705     dirname, QStringLiteral( "O" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::Organization )
706     : SSL_SUBJECT_INFO( qcert, QSslCertificate::Organization ) );
707   QgsAuthCertUtils::appendDirSegment_(
708     dirname, QStringLiteral( "L" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::LocalityName )
709     : SSL_SUBJECT_INFO( qcert, QSslCertificate::LocalityName ) );
710   QgsAuthCertUtils::appendDirSegment_(
711     dirname, QStringLiteral( "ST" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::StateOrProvinceName )
712     : SSL_SUBJECT_INFO( qcert, QSslCertificate::StateOrProvinceName ) );
713   QgsAuthCertUtils::appendDirSegment_(
714     dirname, QStringLiteral( "C" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::CountryName )
715     : SSL_SUBJECT_INFO( qcert, QSslCertificate::CountryName ) );
716 
717   return dirname.join( QLatin1Char( ',' ) );
718 }
719 
getCertTrustName(QgsAuthCertUtils::CertTrustPolicy trust)720 QString QgsAuthCertUtils::getCertTrustName( QgsAuthCertUtils::CertTrustPolicy trust )
721 {
722   switch ( trust )
723   {
724     case DefaultTrust:
725       return QObject::tr( "Default" );
726     case Trusted:
727       return QObject::tr( "Trusted" );
728     case Untrusted:
729       return QObject::tr( "Untrusted" );
730     default:
731       return QString();
732   }
733 }
734 
getColonDelimited(const QString & txt)735 QString QgsAuthCertUtils::getColonDelimited( const QString &txt )
736 {
737   // 64321c05b0ebab8e2b67ec0d7d9e2b6d4bc3c303
738   //   -> 64:32:1c:05:b0:eb:ab:8e:2b:67:ec:0d:7d:9e:2b:6d:4b:c3:c3:03
739   QStringList sl;
740   sl.reserve( txt.size() );
741   for ( int i = 0; i < txt.size(); i += 2 )
742   {
743     sl << txt.mid( i, ( i + 2 > txt.size() ) ? -1 : 2 );
744   }
745   return sl.join( QLatin1Char( ':' ) );
746 }
747 
shaHexForCert(const QSslCertificate & cert,bool formatted)748 QString QgsAuthCertUtils::shaHexForCert( const QSslCertificate &cert, bool formatted )
749 {
750   QString sha( cert.digest( QCryptographicHash::Sha1 ).toHex() );
751   if ( formatted )
752   {
753     return QgsAuthCertUtils::getColonDelimited( sha );
754   }
755   return sha;
756 }
757 
qtCertToQcaCert(const QSslCertificate & cert)758 QCA::Certificate QgsAuthCertUtils::qtCertToQcaCert( const QSslCertificate &cert )
759 {
760   if ( QgsApplication::authManager()->isDisabled() )
761     return QCA::Certificate();
762 
763   QCA::ConvertResult res;
764   QCA::Certificate qcacert( QCA::Certificate::fromPEM( cert.toPem(), &res, QStringLiteral( "qca-ossl" ) ) );
765   if ( res != QCA::ConvertGood || qcacert.isNull() )
766   {
767     QgsDebugMsg( QStringLiteral( "Certificate could not be converted to QCA cert" ) );
768     qcacert = QCA::Certificate();
769   }
770   return qcacert;
771 }
772 
qtCertsToQcaCollection(const QList<QSslCertificate> & certs)773 QCA::CertificateCollection QgsAuthCertUtils::qtCertsToQcaCollection( const QList<QSslCertificate> &certs )
774 {
775   QCA::CertificateCollection qcacoll;
776   if ( QgsApplication::authManager()->isDisabled() )
777     return qcacoll;
778 
779   for ( const auto &cert : certs )
780   {
781     const QCA::Certificate qcacert( qtCertToQcaCert( cert ) );
782     if ( !qcacert.isNull() )
783     {
784       qcacoll.addCertificate( qcacert );
785     }
786   }
787   return qcacoll;
788 }
789 
qcaKeyBundle(const QString & path,const QString & pass)790 QCA::KeyBundle QgsAuthCertUtils::qcaKeyBundle( const QString &path, const QString &pass )
791 {
792   QCA::SecureArray passarray;
793   if ( !pass.isEmpty() )
794     passarray = QCA::SecureArray( pass.toUtf8() );
795 
796   QCA::ConvertResult res;
797   const QCA::KeyBundle bundle( QCA::KeyBundle::fromFile( path, passarray, &res, QStringLiteral( "qca-ossl" ) ) );
798 
799   return ( res == QCA::ConvertGood ? bundle : QCA::KeyBundle() );
800 }
801 
qcaValidityMessage(QCA::Validity validity)802 QString QgsAuthCertUtils::qcaValidityMessage( QCA::Validity validity )
803 {
804   switch ( validity )
805   {
806     case QCA::ValidityGood:
807       return QObject::tr( "Certificate is valid." );
808     case QCA::ErrorRejected:
809       return QObject::tr( "Root CA rejected the certificate purpose." );
810     case QCA::ErrorUntrusted:
811       return QObject::tr( "Certificate is not trusted." );
812     case QCA::ErrorSignatureFailed:
813       return QObject::tr( "Signature does not match." );
814     case QCA::ErrorInvalidCA:
815       return QObject::tr( "Certificate Authority is invalid or not found." );
816     case QCA::ErrorInvalidPurpose:
817       return QObject::tr( "Purpose does not match the intended usage." );
818     case QCA::ErrorSelfSigned:
819       return QObject::tr( "Certificate is self-signed, and is not found in the list of trusted certificates." );
820     case QCA::ErrorRevoked:
821       return QObject::tr( "Certificate has been revoked." );
822     case QCA::ErrorPathLengthExceeded:
823       return QObject::tr( "Path length from the root CA to this certificate is too long." );
824     case QCA::ErrorExpired:
825       return QObject::tr( "Certificate has expired or is not yet valid." );
826     case QCA::ErrorExpiredCA:
827       return QObject::tr( "Certificate Authority has expired." );
828     case QCA::ErrorValidityUnknown:
829       return QObject::tr( "Validity is unknown." );
830     default:
831       return QString();
832   }
833 }
834 
qcaSignatureAlgorithm(QCA::SignatureAlgorithm algorithm)835 QString QgsAuthCertUtils::qcaSignatureAlgorithm( QCA::SignatureAlgorithm algorithm )
836 {
837   switch ( algorithm )
838   {
839     case QCA::EMSA1_SHA1:
840       return QObject::tr( "SHA1, with EMSA1" );
841     case QCA::EMSA3_SHA1:
842       return QObject::tr( "SHA1, with EMSA3" );
843     case QCA::EMSA3_MD5:
844       return QObject::tr( "MD5, with EMSA3" );
845     case QCA::EMSA3_MD2:
846       return QObject::tr( "MD2, with EMSA3" );
847     case QCA::EMSA3_RIPEMD160:
848       return QObject::tr( "RIPEMD160, with EMSA3" );
849     case QCA::EMSA3_Raw:
850       return QObject::tr( "EMSA3, without digest" );
851 #if QCA_VERSION >= 0x020100
852     case QCA::EMSA3_SHA224:
853       return QObject::tr( "SHA224, with EMSA3" );
854     case QCA::EMSA3_SHA256:
855       return QObject::tr( "SHA256, with EMSA3" );
856     case QCA::EMSA3_SHA384:
857       return QObject::tr( "SHA384, with EMSA3" );
858     case QCA::EMSA3_SHA512:
859       return QObject::tr( "SHA512, with EMSA3" );
860 #endif
861     default:
862       return QObject::tr( "Unknown (possibly Elliptic Curve)" );
863   }
864 }
865 
qcaKnownConstraint(QCA::ConstraintTypeKnown constraint)866 QString QgsAuthCertUtils::qcaKnownConstraint( QCA::ConstraintTypeKnown constraint )
867 {
868   switch ( constraint )
869   {
870     case QCA::DigitalSignature:
871       return QObject::tr( "Digital Signature" );
872     case QCA::NonRepudiation:
873       return QObject::tr( "Non-repudiation" );
874     case QCA::KeyEncipherment:
875       return QObject::tr( "Key Encipherment" );
876     case QCA::DataEncipherment:
877       return QObject::tr( "Data Encipherment" );
878     case QCA::KeyAgreement:
879       return QObject::tr( "Key Agreement" );
880     case QCA::KeyCertificateSign:
881       return QObject::tr( "Key Certificate Sign" );
882     case QCA::CRLSign:
883       return QObject::tr( "CRL Sign" );
884     case QCA::EncipherOnly:
885       return QObject::tr( "Encipher Only" );
886     case QCA::DecipherOnly:
887       return QObject::tr( "Decipher Only" );
888     case QCA::ServerAuth:
889       return QObject::tr( "Server Authentication" );
890     case QCA::ClientAuth:
891       return QObject::tr( "Client Authentication" );
892     case QCA::CodeSigning:
893       return QObject::tr( "Code Signing" );
894     case QCA::EmailProtection:
895       return QObject::tr( "Email Protection" );
896     case QCA::IPSecEndSystem:
897       return QObject::tr( "IPSec Endpoint" );
898     case QCA::IPSecTunnel:
899       return QObject::tr( "IPSec Tunnel" );
900     case QCA::IPSecUser:
901       return QObject::tr( "IPSec User" );
902     case QCA::TimeStamping:
903       return QObject::tr( "Time Stamping" );
904     case QCA::OCSPSigning:
905       return QObject::tr( "OCSP Signing" );
906     default:
907       return QString();
908   }
909 }
910 
certificateUsageTypeString(QgsAuthCertUtils::CertUsageType usagetype)911 QString QgsAuthCertUtils::certificateUsageTypeString( QgsAuthCertUtils::CertUsageType usagetype )
912 {
913   switch ( usagetype )
914   {
915     case QgsAuthCertUtils::AnyOrUnspecifiedUsage:
916       return QObject::tr( "Any or unspecified" );
917     case QgsAuthCertUtils::CertAuthorityUsage:
918       return QObject::tr( "Certificate Authority" );
919     case QgsAuthCertUtils::CertIssuerUsage:
920       return QObject::tr( "Certificate Issuer" );
921     case QgsAuthCertUtils::TlsServerUsage:
922       return QObject::tr( "TLS/SSL Server" );
923     case QgsAuthCertUtils::TlsServerEvUsage:
924       return QObject::tr( "TLS/SSL Server EV" );
925     case QgsAuthCertUtils::TlsClientUsage:
926       return QObject::tr( "TLS/SSL Client" );
927     case QgsAuthCertUtils::CodeSigningUsage:
928       return QObject::tr( "Code Signing" );
929     case QgsAuthCertUtils::EmailProtectionUsage:
930       return QObject::tr( "Email Protection" );
931     case QgsAuthCertUtils::TimeStampingUsage:
932       return QObject::tr( "Time Stamping" );
933     case QgsAuthCertUtils::CRLSigningUsage:
934       return QObject::tr( "CRL Signing" );
935     case QgsAuthCertUtils::UndeterminedUsage:
936     default:
937       return QObject::tr( "Undetermined usage" );
938   }
939 }
940 
certificateUsageTypes(const QSslCertificate & cert)941 QList<QgsAuthCertUtils::CertUsageType> QgsAuthCertUtils::certificateUsageTypes( const QSslCertificate &cert )
942 {
943   QList<QgsAuthCertUtils::CertUsageType> usages;
944 
945   if ( QgsApplication::authManager()->isDisabled() )
946     return usages;
947 
948   QCA::ConvertResult res;
949   const QCA::Certificate qcacert( QCA::Certificate::fromPEM( cert.toPem(), &res, QStringLiteral( "qca-ossl" ) ) );
950   if ( res != QCA::ConvertGood || qcacert.isNull() )
951   {
952     QgsDebugMsg( QStringLiteral( "Certificate could not be converted to QCA cert" ) );
953     return usages;
954   }
955 
956   if ( qcacert.isCA() )
957   {
958     QgsDebugMsg( QStringLiteral( "Certificate has 'CA:TRUE' basic constraint" ) );
959     usages << QgsAuthCertUtils::CertAuthorityUsage;
960   }
961 
962   const QList<QCA::ConstraintType> certconsts = qcacert.constraints();
963   for ( const auto &certconst : certconsts )
964   {
965     if ( certconst.known() == QCA::KeyCertificateSign )
966     {
967       QgsDebugMsg( QStringLiteral( "Certificate has 'Certificate Sign' key usage" ) );
968       usages << QgsAuthCertUtils::CertIssuerUsage;
969     }
970     else if ( certconst.known() == QCA::ServerAuth )
971     {
972       QgsDebugMsg( QStringLiteral( "Certificate has 'server authentication' extended key usage" ) );
973       usages << QgsAuthCertUtils::TlsServerUsage;
974     }
975   }
976 
977   // ask QCA what it thinks about potential usages
978   const QCA::CertificateCollection trustedCAs(
979     qtCertsToQcaCollection( QgsApplication::authManager()->trustedCaCertsCache() ) );
980   const QCA::CertificateCollection untrustedCAs(
981     qtCertsToQcaCollection( QgsApplication::authManager()->untrustedCaCerts() ) );
982 
983   QCA::Validity v_any;
984   v_any = qcacert.validate( trustedCAs, untrustedCAs, QCA::UsageAny, QCA::ValidateAll );
985   if ( v_any == QCA::ValidityGood )
986   {
987     usages << QgsAuthCertUtils::AnyOrUnspecifiedUsage;
988   }
989 
990   QCA::Validity v_tlsserver;
991   v_tlsserver = qcacert.validate( trustedCAs, untrustedCAs, QCA::UsageTLSServer, QCA::ValidateAll );
992   if ( v_tlsserver == QCA::ValidityGood )
993   {
994     if ( !usages.contains( QgsAuthCertUtils::TlsServerUsage ) )
995     {
996       usages << QgsAuthCertUtils::TlsServerUsage;
997     }
998   }
999 
1000   // TODO: why doesn't this tag client certs?
1001   //       always seems to return QCA::ErrorInvalidPurpose (enum #5)
1002   QCA::Validity v_tlsclient;
1003   v_tlsclient = qcacert.validate( trustedCAs, untrustedCAs, QCA::UsageTLSClient, QCA::ValidateAll );
1004   //QgsDebugMsg( QStringLiteral( "QCA::UsageTLSClient validity: %1" ).arg( static_cast<int>(v_tlsclient) ) );
1005   if ( v_tlsclient == QCA::ValidityGood )
1006   {
1007     usages << QgsAuthCertUtils::TlsClientUsage;
1008   }
1009 
1010   // TODO: add TlsServerEvUsage, CodeSigningUsage, EmailProtectionUsage, TimeStampingUsage, CRLSigningUsage
1011   //       as they become necessary, since we do not want the overhead of checking just yet.
1012 
1013   return usages;
1014 }
1015 
certificateIsAuthority(const QSslCertificate & cert)1016 bool QgsAuthCertUtils::certificateIsAuthority( const QSslCertificate &cert )
1017 {
1018   return QgsAuthCertUtils::certificateUsageTypes( cert ).contains( QgsAuthCertUtils::CertAuthorityUsage );
1019 }
1020 
certificateIsIssuer(const QSslCertificate & cert)1021 bool QgsAuthCertUtils::certificateIsIssuer( const QSslCertificate &cert )
1022 {
1023   return QgsAuthCertUtils::certificateUsageTypes( cert ).contains( QgsAuthCertUtils::CertIssuerUsage );
1024 }
1025 
certificateIsAuthorityOrIssuer(const QSslCertificate & cert)1026 bool QgsAuthCertUtils::certificateIsAuthorityOrIssuer( const QSslCertificate &cert )
1027 {
1028   return ( QgsAuthCertUtils::certificateIsAuthority( cert )
1029            || QgsAuthCertUtils::certificateIsIssuer( cert ) );
1030 }
1031 
certificateIsSslServer(const QSslCertificate & cert)1032 bool QgsAuthCertUtils::certificateIsSslServer( const QSslCertificate &cert )
1033 {
1034   return ( QgsAuthCertUtils::certificateUsageTypes( cert ).contains( QgsAuthCertUtils::TlsServerUsage )
1035            || QgsAuthCertUtils::certificateUsageTypes( cert ).contains( QgsAuthCertUtils::TlsServerEvUsage ) );
1036 }
1037 
1038 #if 0
1039 bool QgsAuthCertUtils::certificateIsSslServer( const QSslCertificate &cert )
1040 {
1041   // TODO: There is no difinitive method for strictly enforcing what determines an SSL server cert;
1042   //       only what it should not be able to do (cert sign, etc.). The logic here may need refined
1043   // see: http://security.stackexchange.com/a/26650
1044 
1045   if ( QgsApplication::authManager()->isDisabled() )
1046     return false;
1047 
1048   QCA::ConvertResult res;
1049   QCA::Certificate qcacert( QCA::Certificate::fromPEM( cert.toPem(), &res, QString( "qca-ossl" ) ) );
1050   if ( res != QCA::ConvertGood || qcacert.isNull() )
1051   {
1052     QgsDebugMsg( QStringLiteral( "Certificate could not be converted to QCA cert" ) );
1053     return false;
1054   }
1055 
1056   if ( qcacert.isCA() )
1057   {
1058     QgsDebugMsg( QStringLiteral( "SSL server certificate has 'CA:TRUE' basic constraint (and should not)" ) );
1059     return false;
1060   }
1061 
1062   const QList<QCA::ConstraintType> certconsts = qcacert.constraints();
1063   for ( const auto & certconst, certconsts )
1064   {
1065     if ( certconst.known() == QCA::KeyCertificateSign )
1066     {
1067       QgsDebugMsg( QStringLiteral( "SSL server certificate has 'Certificate Sign' key usage (and should not)" ) );
1068       return false;
1069     }
1070   }
1071 
1072   // check for common key usage and extended key usage constraints
1073   // see: https://www.ietf.org/rfc/rfc3280.txt  4.2.1.3(Key Usage) and  4.2.1.13(Extended Key Usage)
1074   bool serverauth = false;
1075   bool dsignature = false;
1076   bool keyencrypt = false;
1077   for ( const auto &certconst : certconsts )
1078   {
1079     if ( certconst.known() == QCA::DigitalSignature )
1080     {
1081       QgsDebugMsg( QStringLiteral( "SSL server certificate has 'digital signature' key usage" ) );
1082       dsignature = true;
1083     }
1084     else if ( certconst.known() == QCA::KeyEncipherment )
1085     {
1086       QgsDebugMsg( QStringLiteral( "SSL server certificate has 'key encipherment' key usage" ) );
1087       keyencrypt = true;
1088     }
1089     else if ( certconst.known() == QCA::KeyAgreement )
1090     {
1091       QgsDebugMsg( QStringLiteral( "SSL server certificate has 'key agreement' key usage" ) );
1092       keyencrypt = true;
1093     }
1094     else if ( certconst.known() == QCA::ServerAuth )
1095     {
1096       QgsDebugMsg( QStringLiteral( "SSL server certificate has 'server authentication' extended key usage" ) );
1097       serverauth = true;
1098     }
1099   }
1100   // From 4.2.1.13(Extended Key Usage):
1101   //   "If a certificate contains both a key usage extension and an extended
1102   //   key usage extension, then both extensions MUST be processed
1103   //   independently and the certificate MUST only be used for a purpose
1104   //   consistent with both extensions.  If there is no purpose consistent
1105   //   with both extensions, then the certificate MUST NOT be used for any
1106   //   purpose."
1107 
1108   if ( serverauth && dsignature && keyencrypt )
1109   {
1110     return true;
1111   }
1112   if ( dsignature && keyencrypt )
1113   {
1114     return true;
1115   }
1116 
1117   // lastly, check for DH key and key agreement
1118   bool keyagree = false;
1119   bool encipheronly = false;
1120   bool decipheronly = false;
1121 
1122   QCA::PublicKey pubkey( qcacert.subjectPublicKey() );
1123   // key size may be 0 for eliptical curve-based keys, in which case isDH() crashes QCA
1124   if ( pubkey.bitSize() > 0 && pubkey.isDH() )
1125   {
1126     keyagree = pubkey.canKeyAgree();
1127     if ( !keyagree )
1128     {
1129       return false;
1130     }
1131     for ( const auto &certconst : certconsts )
1132     {
1133       if ( certconst.known() == QCA::EncipherOnly )
1134       {
1135         QgsDebugMsg( QStringLiteral( "SSL server public key has 'encipher only' key usage" ) );
1136         encipheronly = true;
1137       }
1138       else if ( certconst.known() == QCA::DecipherOnly )
1139       {
1140         QgsDebugMsg( QStringLiteral( "SSL server public key has 'decipher only' key usage" ) );
1141         decipheronly = true;
1142       }
1143     }
1144     if ( !encipheronly && !decipheronly )
1145     {
1146       return true;
1147     }
1148   }
1149   return false;
1150 }
1151 #endif
1152 
certificateIsSslClient(const QSslCertificate & cert)1153 bool QgsAuthCertUtils::certificateIsSslClient( const QSslCertificate &cert )
1154 {
1155   return QgsAuthCertUtils::certificateUsageTypes( cert ).contains( QgsAuthCertUtils::TlsClientUsage );
1156 }
1157 
sslErrorEnumString(QSslError::SslError errenum)1158 QString QgsAuthCertUtils::sslErrorEnumString( QSslError::SslError errenum )
1159 {
1160   switch ( errenum )
1161   {
1162     case QSslError::UnableToGetIssuerCertificate:
1163       return QObject::tr( "Unable to Get Issuer Certificate" );
1164     case QSslError::UnableToDecryptCertificateSignature:
1165       return QObject::tr( "Unable to Decrypt Certificate Signature" );
1166     case QSslError::UnableToDecodeIssuerPublicKey:
1167       return QObject::tr( "Unable to Decode Issuer Public Key" );
1168     case QSslError::CertificateSignatureFailed:
1169       return QObject::tr( "Certificate Signature Failed" );
1170     case QSslError::CertificateNotYetValid:
1171       return QObject::tr( "Certificate Not Yet Valid" );
1172     case QSslError::CertificateExpired:
1173       return QObject::tr( "Certificate Expired" );
1174     case QSslError::InvalidNotBeforeField:
1175       return QObject::tr( "Invalid Not Before Field" );
1176     case QSslError::InvalidNotAfterField:
1177       return QObject::tr( "Invalid Not After Field" );
1178     case QSslError::SelfSignedCertificate:
1179       return QObject::tr( "Self-signed Certificate" );
1180     case QSslError::SelfSignedCertificateInChain:
1181       return QObject::tr( "Self-signed Certificate In Chain" );
1182     case QSslError::UnableToGetLocalIssuerCertificate:
1183       return QObject::tr( "Unable to Get Local Issuer Certificate" );
1184     case QSslError::UnableToVerifyFirstCertificate:
1185       return QObject::tr( "Unable to Verify First Certificate" );
1186     case QSslError::CertificateRevoked:
1187       return QObject::tr( "Certificate Revoked" );
1188     case QSslError::InvalidCaCertificate:
1189       return QObject::tr( "Invalid CA Certificate" );
1190     case QSslError::PathLengthExceeded:
1191       return QObject::tr( "Path Length Exceeded" );
1192     case QSslError::InvalidPurpose:
1193       return QObject::tr( "Invalid Purpose" );
1194     case QSslError::CertificateUntrusted:
1195       return QObject::tr( "Certificate Untrusted" );
1196     case QSslError::CertificateRejected:
1197       return QObject::tr( "Certificate Rejected" );
1198     case QSslError::SubjectIssuerMismatch:
1199       return QObject::tr( "Subject Issuer Mismatch" );
1200     case QSslError::AuthorityIssuerSerialNumberMismatch:
1201       return QObject::tr( "Authority Issuer Serial Number Mismatch" );
1202     case QSslError::NoPeerCertificate:
1203       return QObject::tr( "No Peer Certificate" );
1204     case QSslError::HostNameMismatch:
1205       return QObject::tr( "Host Name Mismatch" );
1206     case QSslError::UnspecifiedError:
1207       return QObject::tr( "Unspecified Error" );
1208     case QSslError::CertificateBlacklisted:
1209       return QObject::tr( "Certificate Blacklisted" );
1210     case QSslError::NoError:
1211       return QObject::tr( "No Error" );
1212     case QSslError::NoSslSupport:
1213       return QObject::tr( "No SSL Support" );
1214     default:
1215       return QString();
1216   }
1217 }
1218 
sslErrorEnumStrings()1219 QList<QPair<QSslError::SslError, QString> > QgsAuthCertUtils::sslErrorEnumStrings()
1220 {
1221   QList<QPair<QSslError::SslError, QString> > errenums;
1222   errenums << qMakePair( QSslError::UnableToGetIssuerCertificate,
1223                          QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToGetIssuerCertificate ) );
1224   errenums << qMakePair( QSslError::UnableToDecryptCertificateSignature,
1225                          QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToDecryptCertificateSignature ) );
1226   errenums << qMakePair( QSslError::UnableToDecodeIssuerPublicKey,
1227                          QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToDecodeIssuerPublicKey ) );
1228   errenums << qMakePair( QSslError::CertificateSignatureFailed,
1229                          QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateSignatureFailed ) );
1230   errenums << qMakePair( QSslError::CertificateNotYetValid,
1231                          QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateNotYetValid ) );
1232   errenums << qMakePair( QSslError::CertificateExpired,
1233                          QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateExpired ) );
1234   errenums << qMakePair( QSslError::InvalidNotBeforeField,
1235                          QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidNotBeforeField ) );
1236   errenums << qMakePair( QSslError::InvalidNotAfterField,
1237                          QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidNotAfterField ) );
1238   errenums << qMakePair( QSslError::SelfSignedCertificate,
1239                          QgsAuthCertUtils::sslErrorEnumString( QSslError::SelfSignedCertificate ) );
1240   errenums << qMakePair( QSslError::SelfSignedCertificateInChain,
1241                          QgsAuthCertUtils::sslErrorEnumString( QSslError::SelfSignedCertificateInChain ) );
1242   errenums << qMakePair( QSslError::UnableToGetLocalIssuerCertificate,
1243                          QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToGetLocalIssuerCertificate ) );
1244   errenums << qMakePair( QSslError::UnableToVerifyFirstCertificate,
1245                          QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToVerifyFirstCertificate ) );
1246   errenums << qMakePair( QSslError::CertificateRevoked,
1247                          QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateRevoked ) );
1248   errenums << qMakePair( QSslError::InvalidCaCertificate,
1249                          QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidCaCertificate ) );
1250   errenums << qMakePair( QSslError::PathLengthExceeded,
1251                          QgsAuthCertUtils::sslErrorEnumString( QSslError::PathLengthExceeded ) );
1252   errenums << qMakePair( QSslError::InvalidPurpose,
1253                          QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidPurpose ) );
1254   errenums << qMakePair( QSslError::CertificateUntrusted,
1255                          QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateUntrusted ) );
1256   errenums << qMakePair( QSslError::CertificateRejected,
1257                          QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateRejected ) );
1258   errenums << qMakePair( QSslError::SubjectIssuerMismatch,
1259                          QgsAuthCertUtils::sslErrorEnumString( QSslError::SubjectIssuerMismatch ) );
1260   errenums << qMakePair( QSslError::AuthorityIssuerSerialNumberMismatch,
1261                          QgsAuthCertUtils::sslErrorEnumString( QSslError::AuthorityIssuerSerialNumberMismatch ) );
1262   errenums << qMakePair( QSslError::NoPeerCertificate,
1263                          QgsAuthCertUtils::sslErrorEnumString( QSslError::NoPeerCertificate ) );
1264   errenums << qMakePair( QSslError::HostNameMismatch,
1265                          QgsAuthCertUtils::sslErrorEnumString( QSslError::HostNameMismatch ) );
1266   errenums << qMakePair( QSslError::UnspecifiedError,
1267                          QgsAuthCertUtils::sslErrorEnumString( QSslError::UnspecifiedError ) );
1268   errenums << qMakePair( QSslError::CertificateBlacklisted,
1269                          QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateBlacklisted ) );
1270   return errenums;
1271 }
1272 
certIsCurrent(const QSslCertificate & cert)1273 bool QgsAuthCertUtils::certIsCurrent( const QSslCertificate &cert )
1274 {
1275   if ( cert.isNull() )
1276     return false;
1277   const QDateTime currentTime = QDateTime::currentDateTime();
1278   return cert.effectiveDate() <= currentTime && cert.expiryDate() >= currentTime;
1279 }
1280 
certViabilityErrors(const QSslCertificate & cert)1281 QList<QSslError> QgsAuthCertUtils::certViabilityErrors( const QSslCertificate &cert )
1282 {
1283   QList<QSslError> sslErrors;
1284 
1285   if ( cert.isNull() )
1286     return sslErrors;
1287 
1288   const QDateTime currentTime = QDateTime::currentDateTime();
1289   if ( cert.expiryDate() <= currentTime )
1290   {
1291     sslErrors << QSslError( QSslError::SslError::CertificateExpired, cert );
1292   }
1293   if ( cert.effectiveDate() >= QDateTime::currentDateTime() )
1294   {
1295     sslErrors << QSslError( QSslError::SslError::CertificateNotYetValid, cert );
1296   }
1297   if ( cert.isBlacklisted() )
1298   {
1299     sslErrors << QSslError( QSslError::SslError::CertificateBlacklisted, cert );
1300   }
1301 
1302   return sslErrors;
1303 }
1304 
certIsViable(const QSslCertificate & cert)1305 bool QgsAuthCertUtils::certIsViable( const QSslCertificate &cert )
1306 {
1307   return !cert.isNull() && QgsAuthCertUtils::certViabilityErrors( cert ).isEmpty();
1308 }
1309 
validateCertChain(const QList<QSslCertificate> & certificateChain,const QString & hostName,bool trustRootCa)1310 QList<QSslError> QgsAuthCertUtils::validateCertChain( const QList<QSslCertificate> &certificateChain,
1311     const QString &hostName,
1312     bool trustRootCa )
1313 {
1314   QList<QSslError> sslErrors;
1315   QList<QSslCertificate> trustedChain;
1316   // Filter out all CAs that are not trusted from QgsAuthManager
1317   for ( const auto &cert : certificateChain )
1318   {
1319     bool untrusted = false;
1320     for ( const auto &untrustedCert : QgsApplication::authManager()->untrustedCaCerts() )
1321     {
1322       if ( cert.digest( ) == untrustedCert.digest( ) )
1323       {
1324         untrusted = true;
1325         break;
1326       }
1327     }
1328     if ( ! untrusted )
1329     {
1330       trustedChain << cert;
1331     }
1332   }
1333 
1334   // Check that no certs in the chain are expired or not yet valid or blocklisted
1335   const QList<QSslCertificate> constTrustedChain( trustedChain );
1336   for ( const auto &cert : constTrustedChain )
1337   {
1338     sslErrors << QgsAuthCertUtils::certViabilityErrors( cert );
1339   }
1340 
1341   // Merge in the root CA if present and asked for
1342   if ( trustRootCa && trustedChain.count() > 1 && trustedChain.last().isSelfSigned() )
1343   {
1344     static QMutex sMutex;
1345     const QMutexLocker lock( &sMutex );
1346     const QSslConfiguration oldSslConfig( QSslConfiguration::defaultConfiguration() );
1347     QSslConfiguration sslConfig( oldSslConfig );
1348     sslConfig.setCaCertificates( casMerge( sslConfig.caCertificates(), QList<QSslCertificate>() << trustedChain.last() ) );
1349     QSslConfiguration::setDefaultConfiguration( sslConfig );
1350     sslErrors = QSslCertificate::verify( trustedChain, hostName );
1351     QSslConfiguration::setDefaultConfiguration( oldSslConfig );
1352   }
1353   else
1354   {
1355     sslErrors = QSslCertificate::verify( trustedChain, hostName );
1356   }
1357   return sslErrors;
1358 }
1359 
validatePKIBundle(QgsPkiBundle & bundle,bool useIntermediates,bool trustRootCa)1360 QStringList QgsAuthCertUtils::validatePKIBundle( QgsPkiBundle &bundle, bool useIntermediates, bool trustRootCa )
1361 {
1362   QStringList errors;
1363   if ( bundle.clientCert().isNull() )
1364     errors << QObject::tr( "Client certificate is NULL." );
1365 
1366   if ( bundle.clientKey().isNull() )
1367     errors << QObject::tr( "Client certificate key is NULL." );
1368 
1369   // immediately bail out if cert or key is NULL
1370   if ( !errors.isEmpty() )
1371     return errors;
1372 
1373   QList<QSslError> sslErrors;
1374   if ( useIntermediates )
1375   {
1376     QList<QSslCertificate> certsList( bundle.caChain() );
1377     certsList.insert( 0, bundle.clientCert( ) );
1378     sslErrors = QgsAuthCertUtils::validateCertChain( certsList, QString(), trustRootCa );
1379   }
1380   else
1381   {
1382     sslErrors = QSslCertificate::verify( QList<QSslCertificate>() << bundle.clientCert() );
1383   }
1384   const QList<QSslError> constSslErrors( sslErrors );
1385   for ( const auto &sslError : constSslErrors )
1386   {
1387     if ( sslError.error() != QSslError::NoError )
1388     {
1389       errors << sslError.errorString();
1390     }
1391   }
1392   // Now check the key with QCA!
1393   const QCA::PrivateKey pvtKey( QCA::PrivateKey::fromPEM( bundle.clientKey().toPem() ) );
1394   const QCA::PublicKey pubKey( QCA::PublicKey::fromPEM( bundle.clientCert().publicKey().toPem( ) ) );
1395   bool keyValid( ! pvtKey.isNull() );
1396   if ( keyValid && !( pubKey.toRSA().isNull( ) || pvtKey.toRSA().isNull( ) ) )
1397   {
1398     keyValid = pubKey.toRSA().n() == pvtKey.toRSA().n();
1399   }
1400   else if ( keyValid && !( pubKey.toDSA().isNull( ) || pvtKey.toDSA().isNull( ) ) )
1401   {
1402     keyValid = pubKey == QCA::DSAPublicKey( pvtKey.toDSA() );
1403   }
1404   else
1405   {
1406     QgsDebugMsg( QStringLiteral( "Key is not DSA, RSA: validation is not supported by QCA" ) );
1407   }
1408   if ( ! keyValid )
1409   {
1410     errors << QObject::tr( "Private key does not match client certificate public key." );
1411   }
1412   return errors;
1413 }
1414