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