1 | // Copyright (C) 2019 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qopcuagdsclient_p.h" |
5 | #include <QOpcUaProvider> |
6 | #include <QOpcUaExtensionObject> |
7 | #include <QOpcUaBinaryDataEncoding> |
8 | #include <QOpcUaErrorState> |
9 | #include <QOpcUaKeyPair> |
10 | #include <QOpcUaX509ExtensionSubjectAlternativeName> |
11 | #include <QOpcUaX509ExtensionBasicConstraints> |
12 | #include <QOpcUaX509ExtensionKeyUsage> |
13 | #include <QFile> |
14 | #include <QTimer> |
15 | #include <QTemporaryFile> |
16 | #include <QSettings> |
17 | #include <QSslCertificate> |
18 | #include <QtCore/qloggingcategory.h> |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | Q_LOGGING_CATEGORY(QT_OPCUA_GDSCLIENT, "qt.opcua.gdsclient") |
23 | |
24 | static const QStringList elementsToResolve { |
25 | QLatin1String("GetApplication"), |
26 | QLatin1String("FindApplications"), |
27 | QLatin1String("RegisterApplication"), |
28 | QLatin1String("UnregisterApplication"), |
29 | QLatin1String("GetCertificateGroups"), |
30 | QLatin1String("GetCertificateStatus"), |
31 | QLatin1String("StartSigningRequest"), |
32 | QLatin1String("FinishRequest"), |
33 | QLatin1String("GetTrustList"), |
34 | }; |
35 | |
36 | /*! |
37 | \class QOpcUaGdsClient |
38 | \inmodule QtOpcUa |
39 | \since 5.14 |
40 | |
41 | \brief Handles communication with the GDS Server. |
42 | |
43 | This class is currently available as a Technology Preview, and therefore the API |
44 | and functionality provided by the class may be subject to change at any time without |
45 | prior notice. |
46 | |
47 | This class handles all steps needed for communication with a GDS server. Provided with |
48 | information about the application it does registering with the server and managing |
49 | key/certificates. |
50 | |
51 | Only few details need to be known in order to take part in a secured network. |
52 | |
53 | First time registration requires administrative privileges using username and password |
54 | for authentication. All further authentications are application based, using the certificate |
55 | which was received first. |
56 | |
57 | Expecting the whole process to succeed, you have to wait for the applicationRegistered signal. |
58 | |
59 | Most of the setup structs have to be the same as for the connection with QOpcUaClient afterwards |
60 | and can be shared. |
61 | |
62 | Setting up a GDS client: |
63 | |
64 | \code |
65 | QOpcUaGdsClient c; |
66 | |
67 | // In case the credentials are needed |
68 | QObject::connect(&c, &QOpcUaGdsClient::authenticationRequired, [&](QOpcUaAuthenticationInformation &authInfo) { |
69 | authInfo.setUsernameAuthentication("root", "secret"); |
70 | }); |
71 | |
72 | // Await success |
73 | QObject::connect(&c, &QOpcUaGdsClient::applicationRegistered, [&]() { |
74 | qDebug() << "Application" << c.applicationId() << "registered"; |
75 | }); |
76 | |
77 | c.setBackend(...); |
78 | c.setEndpoint(...); |
79 | c.setApplicationIdentity(...); |
80 | c.setPkiConfiguration(...); |
81 | c.setApplicationRecord(...); |
82 | c.setCertificateSigningRequestPresets(...); |
83 | c.start(); |
84 | \endcode |
85 | */ |
86 | |
87 | /*! |
88 | \enum QOpcUaGdsClient::Error |
89 | |
90 | This enum is used to specify errors, that could happen during the registration |
91 | process. |
92 | |
93 | \value NoError |
94 | Everying is fine |
95 | \value InvalidBackend |
96 | The backend could not be instantiated. |
97 | The backend string given, does not match any backend or loading the plugin failed. |
98 | \value InvalidEndpoint |
99 | The given endpoint is not valid. |
100 | \value ConnectionError |
101 | The connection to the server endpoint failed. |
102 | \value DirectoryNodeNotFound |
103 | The directory node on the server could not be resolved |
104 | \value FailedToRegisterApplication |
105 | The registration of the application was not successful. |
106 | \value FailedToUnregisterApplication |
107 | The unregistration of the application was not successful. |
108 | \value FailedToGetCertificateStatus |
109 | The status of the current certificate could not be retrieved. |
110 | \value FailedToGetCertificate |
111 | A certificate could not be retrieved from the server. |
112 | */ |
113 | |
114 | /*! |
115 | \enum QOpcUaGdsClient::State |
116 | |
117 | This enum is used to specify the current state of the registration of the GDS |
118 | client. |
119 | |
120 | \value Idle |
121 | The client was not started yet. |
122 | \value BackendInstantiated |
123 | The backend was instantiated |
124 | \value Connecting |
125 | A connecting to the server is being made |
126 | \value Connected |
127 | The connection to the server endpoint was successful. |
128 | \value RegisteringApplication |
129 | The application is being registered with the server. |
130 | \value ApplicationRegistered |
131 | Registering the application with the server was successful. |
132 | \value Error |
133 | An error happened. See the return value of error() and the terminal output |
134 | for more details. |
135 | */ |
136 | |
137 | /*! |
138 | \fn QOpcUaGdsClient::stateChanged(State state) |
139 | |
140 | This signal is emitted when the internal state of the client changes. |
141 | The \a state indicates the new state. |
142 | */ |
143 | |
144 | /*! |
145 | \fn QOpcUaGdsClient::errorChanged(Error error) |
146 | |
147 | This signal is emitted when an \a error occurred. |
148 | */ |
149 | |
150 | /*! |
151 | \fn QOpcUaGdsClient::applicationRegistered() |
152 | |
153 | This signal is emitted when an application was registered successfully. |
154 | */ |
155 | |
156 | /*! |
157 | \fn QOpcUaGdsClient::certificateGroupsReceived(QStringList certificateGroups) |
158 | |
159 | This signal is emitted when the GDS client receives a new list of \a certificateGroups |
160 | for this application. |
161 | */ |
162 | |
163 | /*! |
164 | \fn QOpcUaGdsClient::certificateUpdateRequired() |
165 | |
166 | This signal is emitted when the GDS client detects that an update of the currently |
167 | used certificate is necessary. |
168 | |
169 | This could be caused by the server, requesting the client to update the certificate, |
170 | when the certificate's due date is met or if the certificate is self-signed. |
171 | |
172 | The certificate update is handled automatically. This signal is only for informational |
173 | purpose that an update is going to happen. |
174 | */ |
175 | |
176 | /*! |
177 | \fn QOpcUaGdsClient::certificateUpdated() |
178 | |
179 | This signal is emitted when the GDS client received a new certificate that was |
180 | stored on disk. |
181 | */ |
182 | |
183 | /*! |
184 | \fn QOpcUaGdsClient::unregistered() |
185 | |
186 | This signal is emitted when the GDS client has unregistered the application. |
187 | */ |
188 | |
189 | /*! |
190 | \fn QOpcUaGdsClient::trustListUpdated() |
191 | |
192 | This signal is emitted when the GDS client has received a new trust list from the |
193 | server and stored to disk. |
194 | */ |
195 | |
196 | /*! |
197 | \fn QOpcUaGdsClient::authenticationRequired(QOpcUaAuthenticationInformation &authInfo) |
198 | |
199 | This signal is emitted when the GDS client tries to do a first time authentication |
200 | with a server, that requires administrative privileges. |
201 | |
202 | \a authInfo has to be filled with valid authentication information. |
203 | This slot must not be used crossing thread boundaries. |
204 | */ |
205 | |
206 | /* |
207 | Step 1 |
208 | Create a self-signed certificate for the application. |
209 | Previously created key and certificate are reused if present. |
210 | |
211 | Step 2 |
212 | Connect to the given endpoint and wait for the namespace array to be updated. |
213 | |
214 | Step 3 |
215 | Find the root node of the GDS API |
216 | |
217 | Step 4 |
218 | From the root node find the "Directory" node where the method nodes are located. |
219 | - resolveDirectoryNode() |
220 | - lambda |
221 | |
222 | Step 5 |
223 | Resolve the method nodes that are being used. |
224 | - resolveMethodNodes() |
225 | - _q_handleResolveBrowsePathFinished() |
226 | |
227 | Step 6a |
228 | In case a previous appliation id is known, check if the application registration is still valid. |
229 | In case it is still valid, reuse it. |
230 | - getApplication() |
231 | - handleGetApplicationFinished() |
232 | |
233 | Step 6b |
234 | In case there is no valid application id known, try to find a previous registration by the application URI. |
235 | - findRegisteredApplication() |
236 | - handleFindApplicationsFinished() |
237 | |
238 | Step 6c |
239 | In case an application id was not found by the application URI, register the application. |
240 | - registerApplication() |
241 | - handleRegisterApplicationFinished() |
242 | |
243 | Step 7 |
244 | Get the certificate groups of the application |
245 | - getCertificateGroups() |
246 | - handleGetCertificateGroupsFinished() |
247 | - emit signal certificateGroupsReceived |
248 | |
249 | Step 8 |
250 | Resolve the certificate types from the certificate groups (CertificateGroup->CertificateTypes) |
251 | - resolveCertificateTypes() |
252 | - lambda |
253 | |
254 | Step 9 |
255 | Read certificate type value from the resolved nodes |
256 | - getCertificateTypes() |
257 | - lambda |
258 | |
259 | Step 10 |
260 | The application now is using a application id from 6a, 6b or 6c, resolved the certificates and is ready to interact with the GDS. |
261 | - registrationDone() |
262 | - emit signal applicationRegistered |
263 | -> Start a certificate check |
264 | -> Start a trust list update |
265 | |
266 | Depending on the certificate state, the certificate needs to be updated by the client. |
267 | This may happen due to |
268 | - the server requires it (GetCertificateStatus) |
269 | - the expiry date is due |
270 | - the certificate is self-signed from the initial connection |
271 | |
272 | Step 1 |
273 | A timer will trigger the certificate check regularly by triggering |
274 | - _q_certificateCheckTimeout() |
275 | - emit signal certificateUpdateRequired |
276 | |
277 | Step 2a |
278 | Check locally first, if a renew condition is met. |
279 | Otherwise ask the server. |
280 | |
281 | Step 2b |
282 | Ask the server if the certificate needs renewal |
283 | - getCertificateStatus() |
284 | - handleGetCertificateStatusFinished() |
285 | - emit signal certificateUpdateRequired |
286 | |
287 | Step 3 |
288 | Start a certificate signing request |
289 | - startCertificateRequest() |
290 | - handleStartSigningRequestFinished() |
291 | |
292 | Step 4 |
293 | Regularly ask the server for the certificate signing result |
294 | - finishCertificateRequest() |
295 | - handleFinishRequestFinished() |
296 | - emit signal certificateUpdated |
297 | This will return a new certificate. |
298 | */ |
299 | |
300 | /*! |
301 | Constructs a GDS client with \a parent as the parent object. |
302 | */ |
303 | QOpcUaGdsClient::QOpcUaGdsClient(QObject *parent) |
304 | : QObject(*(new QOpcUaGdsClientPrivate()), parent) |
305 | { |
306 | Q_D(QOpcUaGdsClient); |
307 | d->initializePrivateConnections(); |
308 | } |
309 | |
310 | /*! |
311 | Destructs a GDS client. |
312 | */ |
313 | QOpcUaGdsClient::~QOpcUaGdsClient() |
314 | { |
315 | |
316 | } |
317 | |
318 | /*! |
319 | Sets the backend to be used by the client to communicate with the server to \a backend. |
320 | |
321 | This function has to be called before starting the GDS client. |
322 | Changing this setting afterwards has no effect. |
323 | |
324 | \sa QOpcUaProvider::availableBackends() start() |
325 | */ |
326 | void QOpcUaGdsClient::setBackend(const QString &backend) |
327 | { |
328 | Q_D(QOpcUaGdsClient); |
329 | d->setBackend(backend); |
330 | } |
331 | |
332 | /*! |
333 | Returns the current backend setting. |
334 | |
335 | If the backend was changed after starting the client, it will return |
336 | the changed setting, but not the actually used instance. |
337 | */ |
338 | const QString &QOpcUaGdsClient::backend() const |
339 | { |
340 | Q_D(const QOpcUaGdsClient); |
341 | return d->backend(); |
342 | } |
343 | |
344 | /*! |
345 | Sets the endpoint to be used by the client to communicate with the server to \a endpoint. |
346 | |
347 | This function has to be called before starting the GDS client. |
348 | Changing this setting afterwards has no effect. |
349 | |
350 | Communication to a GDS server is only possible through an encrypted endpoint. |
351 | Using an unencrypted endpoint will fail. |
352 | */ |
353 | void QOpcUaGdsClient::setEndpoint(const QOpcUaEndpointDescription &endpoint) |
354 | { |
355 | Q_D(QOpcUaGdsClient); |
356 | d->setEndpoint(endpoint); |
357 | } |
358 | |
359 | /*! |
360 | Returns the current endpoint setting. |
361 | |
362 | If the endpoint was changed after starting the client, it will return |
363 | the changed setting, but not the actually used endpoint. |
364 | */ |
365 | const QOpcUaEndpointDescription &QOpcUaGdsClient::endpoint() const |
366 | { |
367 | Q_D(const QOpcUaGdsClient); |
368 | return d->endpoint(); |
369 | } |
370 | |
371 | /*! |
372 | Sets the PKI configuration \a pkiConfig to be used by the client. |
373 | |
374 | All certificates, keys and trust lists will be used from or stored to |
375 | the locations given. In order to use the certificate received from |
376 | the GDS, the same configuration has to be used with QOpcUaClient. |
377 | |
378 | This function has to be called before starting the GDS client. |
379 | Changing this setting afterwards has no effect. |
380 | */ |
381 | void QOpcUaGdsClient::setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig) |
382 | { |
383 | Q_D(QOpcUaGdsClient); |
384 | d->setPkiConfiguration(pkiConfig); |
385 | } |
386 | |
387 | /*! |
388 | Returns the current pkiConfiguration. |
389 | */ |
390 | const QOpcUaPkiConfiguration &QOpcUaGdsClient::pkiConfiguration() const |
391 | { |
392 | Q_D(const QOpcUaGdsClient); |
393 | return d->pkiConfiguration(); |
394 | } |
395 | |
396 | /*! |
397 | Sets the application identity \a appIdentity to be used by the client. |
398 | |
399 | This identity is used to register with the GDS server. |
400 | This function has to be called before starting the GDS client. |
401 | Changing this setting afterwards has no effect. |
402 | */ |
403 | void QOpcUaGdsClient::setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity) |
404 | { |
405 | Q_D(QOpcUaGdsClient); |
406 | d->setApplicationIdentity(appIdentity); |
407 | } |
408 | |
409 | /*! |
410 | Returns the current applicationIdentity. |
411 | */ |
412 | const QOpcUaApplicationIdentity &QOpcUaGdsClient::applicationIdentity() const |
413 | { |
414 | Q_D(const QOpcUaGdsClient); |
415 | return d->applicationIdentity(); |
416 | } |
417 | |
418 | /*! |
419 | Sets the application record data \a appRecord to be used by the client. |
420 | |
421 | This data is used to register with the GDS server. |
422 | This function has to be called before starting the GDS client. |
423 | |
424 | Most of the data is the same as in the application identity. |
425 | After registration the assigned application id can be retrieved. |
426 | |
427 | \sa setApplicationIdentity |
428 | */ |
429 | void QOpcUaGdsClient::setApplicationRecord(const QOpcUaApplicationRecordDataType &appRecord) |
430 | { |
431 | Q_D(QOpcUaGdsClient); |
432 | d->setApplicationRecord(appRecord); |
433 | } |
434 | |
435 | /*! |
436 | Returns the application record data that is used by the client. |
437 | */ |
438 | const QOpcUaApplicationRecordDataType &QOpcUaGdsClient::applicationRecord() const |
439 | { |
440 | Q_D(const QOpcUaGdsClient); |
441 | return d->applicationRecord(); |
442 | } |
443 | |
444 | /*! |
445 | Returns the application id assigned by the server. |
446 | |
447 | Is is a shortcut to receive the application id from the application record data. |
448 | |
449 | \sa applicationRecord() |
450 | */ |
451 | QString QOpcUaGdsClient::applicationId() const |
452 | { |
453 | Q_D(const QOpcUaGdsClient); |
454 | return d->applicationId(); |
455 | } |
456 | |
457 | /*! |
458 | Sets the presets for certificate siging requests; the distinguished name \a dn and |
459 | the DNS string \a dns. |
460 | |
461 | When creating a certificate signing request some additional information is needed, |
462 | that is not provided by the application identity. |
463 | |
464 | This function has to be called before starting the GDS client. |
465 | |
466 | \sa setApplicationIdentity() |
467 | */ |
468 | void QOpcUaGdsClient::setCertificateSigningRequestPresets(const QOpcUaX509DistinguishedName &dn, const QString &dns) |
469 | { |
470 | Q_D(QOpcUaGdsClient); |
471 | d->setCertificateSigningRequestPresets(dn, dns); |
472 | } |
473 | |
474 | /*! |
475 | Returns the distinguished name preset for certificate siging requests. |
476 | */ |
477 | const QOpcUaX509DistinguishedName &QOpcUaGdsClient::distinguishedNameCertificateSigningRequestPreset() const |
478 | { |
479 | Q_D(const QOpcUaGdsClient); |
480 | return d->distinguishedNameCertificateSigningRequestPreset(); |
481 | } |
482 | |
483 | /*! |
484 | Returns the DNS preset for certificate siging requests. |
485 | */ |
486 | const QString &QOpcUaGdsClient::dnsCertificateSigningRequestPreset() const |
487 | { |
488 | Q_D(const QOpcUaGdsClient); |
489 | return d->dnsCertificateSigningRequestPreset(); |
490 | } |
491 | |
492 | /*! |
493 | Sets the interval in milliseconds for checking the validity of the client certificate |
494 | to \a interval. |
495 | */ |
496 | void QOpcUaGdsClient::setCertificateCheckInterval(int interval) |
497 | { |
498 | Q_D(QOpcUaGdsClient); |
499 | d->setCertificateCheckInterval(interval); |
500 | } |
501 | |
502 | /*! |
503 | Returns the interval in milliseconds for checking the validity of the client certificate. |
504 | */ |
505 | int QOpcUaGdsClient::certificateCheckInterval() const |
506 | { |
507 | Q_D(const QOpcUaGdsClient); |
508 | return d->certificateCheckInterval(); |
509 | } |
510 | |
511 | /*! |
512 | Sets the interval in milliseconds for updating the trust list from the server |
513 | to \a interval. |
514 | */ |
515 | void QOpcUaGdsClient::setTrustListUpdateInterval(int interval) |
516 | { |
517 | Q_D(QOpcUaGdsClient); |
518 | d->setTrustListUpdateInterval(interval); |
519 | } |
520 | |
521 | /*! |
522 | Returns the interval in milliseconds for updating the trust list from the server. |
523 | */ |
524 | int QOpcUaGdsClient::trustListUpdateInterval() const |
525 | { |
526 | Q_D(const QOpcUaGdsClient); |
527 | return d->trustListUpdateInterval(); |
528 | } |
529 | |
530 | /*! |
531 | Returns the current error state. |
532 | */ |
533 | QOpcUaGdsClient::Error QOpcUaGdsClient::error() const |
534 | { |
535 | Q_D(const QOpcUaGdsClient); |
536 | return d->error(); |
537 | } |
538 | |
539 | /*! |
540 | Returns the current client state. |
541 | */ |
542 | QOpcUaGdsClient::State QOpcUaGdsClient::state() const |
543 | { |
544 | Q_D(const QOpcUaGdsClient); |
545 | return d->state(); |
546 | } |
547 | |
548 | /*! |
549 | Starts the client process. |
550 | |
551 | After setting up all information, |
552 | the client can be started. |
553 | |
554 | \list |
555 | \li setBackend |
556 | \li setEndpoing |
557 | \li setApplicationIdentity |
558 | \li setPkiConfiguration |
559 | \li setApplicationRecord |
560 | \li setCertificateSigingRequestPresets |
561 | \endlist |
562 | */ |
563 | void QOpcUaGdsClient::start() |
564 | { |
565 | Q_D(QOpcUaGdsClient); |
566 | return d->start(); |
567 | } |
568 | |
569 | /*! |
570 | Unregisters an application from the server. |
571 | |
572 | This function can be used when an application has to be removed permanently from |
573 | the network. It does not need to be called when rebooting or shutting down. |
574 | */ |
575 | void QOpcUaGdsClient::unregisterApplication() |
576 | { |
577 | Q_D(QOpcUaGdsClient); |
578 | return d->unregisterApplication(); |
579 | } |
580 | |
581 | QOpcUaGdsClientPrivate::QOpcUaGdsClientPrivate() |
582 | : QObjectPrivate() |
583 | , m_certificateCheckTimer(new QTimer) |
584 | , m_trustListUpdateTimer(new QTimer) |
585 | { |
586 | m_certificateCheckTimer->setInterval(60*60*1000); |
587 | m_trustListUpdateTimer->setInterval(60*60*1000); |
588 | } |
589 | |
590 | QOpcUaGdsClientPrivate::~QOpcUaGdsClientPrivate() |
591 | { |
592 | delete m_client; |
593 | delete m_directoryNode; |
594 | delete m_certificateGroupNode; |
595 | delete m_certificateTypesNode; |
596 | delete m_certificateFinishTimer; |
597 | delete m_certificateCheckTimer; |
598 | delete m_trustListUpdateTimer; |
599 | delete m_trustListNode; |
600 | } |
601 | |
602 | void QOpcUaGdsClientPrivate::initializePrivateConnections() |
603 | { |
604 | Q_Q(QOpcUaGdsClient); |
605 | QObject::connect(sender: m_certificateCheckTimer, SIGNAL(timeout()), receiver: q, SLOT(_q_certificateCheckTimeout())); |
606 | QObject::connect(sender: m_trustListUpdateTimer, SIGNAL(timeout()), receiver: q, SLOT(_q_updateTrustList())); |
607 | } |
608 | |
609 | void QOpcUaGdsClientPrivate::setEndpoint(const QOpcUaEndpointDescription &endpoint) |
610 | { |
611 | m_endpoint = endpoint; |
612 | } |
613 | |
614 | const QOpcUaEndpointDescription &QOpcUaGdsClientPrivate::endpoint() const |
615 | { |
616 | return m_endpoint; |
617 | } |
618 | |
619 | void QOpcUaGdsClientPrivate::setBackend(const QString &backend) |
620 | { |
621 | m_backend = backend; |
622 | } |
623 | |
624 | const QString &QOpcUaGdsClientPrivate::backend() const |
625 | { |
626 | return m_backend; |
627 | } |
628 | |
629 | QString QOpcUaGdsClientPrivate::applicationId() const |
630 | { |
631 | return m_appRecord.applicationId(); |
632 | } |
633 | |
634 | void QOpcUaGdsClientPrivate::setError(QOpcUaGdsClient::Error error) |
635 | { |
636 | Q_Q(QOpcUaGdsClient); |
637 | m_error = error; |
638 | setState(QOpcUaGdsClient::State::Error); |
639 | emit q->errorChanged(error: m_error); |
640 | } |
641 | |
642 | void QOpcUaGdsClientPrivate::setState(QOpcUaGdsClient::State state) |
643 | { |
644 | Q_Q(QOpcUaGdsClient); |
645 | m_state = state; |
646 | emit q->stateChanged(state); |
647 | } |
648 | |
649 | void QOpcUaGdsClientPrivate::start() |
650 | { |
651 | Q_Q(QOpcUaGdsClient); |
652 | |
653 | if (m_backend.isEmpty()) { |
654 | qCWarning(QT_OPCUA_GDSCLIENT) << "Backend name not set"; |
655 | setError(QOpcUaGdsClient::Error::InvalidBackend); |
656 | return; |
657 | } |
658 | |
659 | if (!m_client) { |
660 | QOpcUaProvider provider; |
661 | setState(QOpcUaGdsClient::State::BackendInstantiated); |
662 | m_client = provider.createClient(backend: m_backend); |
663 | if (!m_client) { |
664 | setError(QOpcUaGdsClient::Error::InvalidBackend); |
665 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid backend"; |
666 | return; |
667 | } |
668 | |
669 | QObject::connect(sender: m_client, signal: &QOpcUaClient::namespaceArrayUpdated, context: q, slot: [this]() { |
670 | setState(QOpcUaGdsClient::State::Connected); |
671 | this->resolveDirectoryNode(); |
672 | }); |
673 | |
674 | QObject::connect(sender: m_client, signal: &QOpcUaClient::errorChanged, context: q, slot: [this](QOpcUaClient::ClientError error) { |
675 | if (error == QOpcUaClient::InvalidUrl) { |
676 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid URL"; |
677 | setError(QOpcUaGdsClient::Error::InvalidEndpoint); |
678 | } else { |
679 | qCWarning(QT_OPCUA_GDSCLIENT) << "Connection error"; |
680 | setError(QOpcUaGdsClient::Error::ConnectionError); |
681 | } |
682 | }); |
683 | |
684 | QObject::connect(sender: m_client, signal: &QOpcUaClient::disconnected, context: q, slot: [this]() { |
685 | delete m_directoryNode; |
686 | m_directoryNode = nullptr; |
687 | m_directoryNodes.clear(); |
688 | setState(QOpcUaGdsClient::State::Idle); |
689 | if (m_restartRequired) { |
690 | m_restartRequired = false; |
691 | this->start(); |
692 | } |
693 | }); |
694 | |
695 | QObject::connect(sender: m_client, signal: &QOpcUaClient::connectError, context: q, slot: [](QOpcUaErrorState *errorState) { |
696 | // Ignore all client side errors and continue |
697 | if (errorState->isClientSideError()) |
698 | errorState->setIgnoreError(); |
699 | }); |
700 | } |
701 | |
702 | setState(QOpcUaGdsClient::State::Connecting); |
703 | if (m_endpoint.endpointUrl().isEmpty()) { |
704 | setError(QOpcUaGdsClient::Error::InvalidEndpoint); |
705 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid endpoint"; |
706 | return; |
707 | } |
708 | |
709 | QOpcUaKeyPair keyPair; |
710 | QFile keyFile(m_pkiConfig.privateKeyFile()); |
711 | |
712 | if (!keyFile.exists()) { |
713 | // File does not exist: Create a key |
714 | qCDebug(QT_OPCUA_GDSCLIENT) << "Creating a key"; |
715 | keyPair.generateRsaKey(strength: QOpcUaKeyPair::RsaKeyStrength::Bits1024); |
716 | |
717 | if (!keyFile.open(flags: QFile::WriteOnly | QFile::NewOnly)) { |
718 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file"<< keyFile.fileName() << "for writing:"<< keyFile.errorString(); |
719 | setError(QOpcUaGdsClient::Error::ConnectionError); |
720 | return; |
721 | } |
722 | |
723 | auto data = keyPair.privateKeyToByteArray(cipher: QOpcUaKeyPair::Cipher::Unencrypted, password: QString()); |
724 | |
725 | if (!keyFile.resize(sz: data.size())) { |
726 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set file size"; |
727 | setError(QOpcUaGdsClient::Error::ConnectionError); |
728 | return; |
729 | } |
730 | |
731 | if (!keyFile.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner)) { |
732 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set permissions"; |
733 | setError(QOpcUaGdsClient::Error::ConnectionError); |
734 | return; |
735 | } |
736 | |
737 | keyFile.write(data); |
738 | keyFile.close(); |
739 | } else { |
740 | qCDebug(QT_OPCUA_GDSCLIENT) << "Using private key"<< keyFile.fileName(); |
741 | |
742 | if (!keyFile.open(flags: QFile::ReadOnly)) { |
743 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file"<< keyFile.fileName() << "for reading:"<< keyFile.errorString(); |
744 | setError(QOpcUaGdsClient::Error::ConnectionError); |
745 | return; |
746 | } |
747 | |
748 | auto data = keyFile.readAll(); |
749 | keyFile.close(); |
750 | |
751 | if (!keyPair.loadFromPemData(data)) { |
752 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load private key"; |
753 | setError(QOpcUaGdsClient::Error::ConnectionError); |
754 | return; |
755 | } |
756 | |
757 | if (!keyPair.hasPrivateKey()) { |
758 | qCWarning(QT_OPCUA_GDSCLIENT) << "Private key does not contain a private key"; |
759 | setError(QOpcUaGdsClient::Error::ConnectionError); |
760 | return; |
761 | } |
762 | } |
763 | |
764 | QFile certFile(m_pkiConfig.clientCertificateFile()); |
765 | if (!certFile.exists()) { |
766 | qCDebug(QT_OPCUA_GDSCLIENT) << "Creating self-signed certificate in"<< certFile.fileName(); |
767 | |
768 | auto csr = createSigningRequest(); |
769 | csr.setEncoding(QOpcUaX509CertificateSigningRequest::Encoding::DER); |
770 | auto selfSigned = csr.createSelfSignedCertificate(privateKey: keyPair); |
771 | |
772 | if (!certFile.open(flags: QFile::WriteOnly | QFile::NewOnly)) { |
773 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open certificate file"<< certFile.fileName() << "for writing:"<< certFile.errorString(); |
774 | setError(QOpcUaGdsClient::Error::ConnectionError); |
775 | return; |
776 | } |
777 | |
778 | if (!certFile.resize(sz: selfSigned.size())) { |
779 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set file size 2"; |
780 | setError(QOpcUaGdsClient::Error::ConnectionError); |
781 | return; |
782 | } |
783 | |
784 | certFile.write(data: selfSigned); |
785 | certFile.close(); |
786 | } |
787 | |
788 | // Load persistent data |
789 | QSettings settings; |
790 | qCDebug(QT_OPCUA_GDSCLIENT) << "Using settings from"<< settings.fileName(); |
791 | const auto applicationId = settings.value(key: QLatin1String("gds/applicationId"), defaultValue: QString()).toString(); |
792 | if (applicationId.isEmpty()) |
793 | qCInfo(QT_OPCUA_GDSCLIENT) << "No application ID in persistent storage"; |
794 | else |
795 | m_appRecord.setApplicationId(applicationId); |
796 | |
797 | m_client->setApplicationIdentity(m_appIdentitiy); |
798 | m_client->setPkiConfiguration(m_pkiConfig); |
799 | m_client->connectToEndpoint(endpoint: m_endpoint); |
800 | } |
801 | |
802 | void QOpcUaGdsClientPrivate::resolveDirectoryNode() |
803 | { |
804 | Q_Q(QOpcUaGdsClient); |
805 | |
806 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
807 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
808 | return; |
809 | } |
810 | |
811 | delete m_directoryNode; |
812 | m_directoryNode = m_client->node(nodeId: QOpcUa::namespace0Id(id: QOpcUa::NodeIds::Namespace0::ObjectsFolder)); // ns=0;i=85 |
813 | if (!m_directoryNode) { |
814 | qCWarning(QT_OPCUA_GDSCLIENT) << "Root node not found"; |
815 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
816 | return; |
817 | } |
818 | |
819 | m_gdsNamespaceIndex = m_client->namespaceArray().indexOf(needle: QLatin1String("http://opcfoundation.org/UA/GDS/")); |
820 | if (m_gdsNamespaceIndex < 0) { |
821 | qCWarning(QT_OPCUA_GDSCLIENT) << "Namespace not found"; |
822 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
823 | return; |
824 | } |
825 | |
826 | QObject::connect(sender: m_directoryNode, signal: &QOpcUaNode::resolveBrowsePathFinished, context: q, |
827 | slot: [this, q](QList<QOpcUaBrowsePathTarget> targets, |
828 | QList<QOpcUaRelativePathElement> path, |
829 | QOpcUa::UaStatusCode statusCode) { |
830 | m_directoryNode->deleteLater(); |
831 | m_directoryNode = nullptr; |
832 | |
833 | if (path.size() != 1) { |
834 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size"; |
835 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
836 | return; |
837 | } |
838 | |
839 | if (path[0].targetName().name() != QLatin1String("Directory")) { |
840 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name"<< path[0].targetName().name(); |
841 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
842 | return; |
843 | } |
844 | |
845 | if (statusCode != QOpcUa::Good) { |
846 | qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving directory failed"<< statusCode; |
847 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
848 | return; |
849 | } |
850 | |
851 | if (targets.size() != 1) { |
852 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results"; |
853 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
854 | return; |
855 | } |
856 | |
857 | if (!targets[0].isFullyResolved()) { |
858 | qCWarning(QT_OPCUA_GDSCLIENT) << "Directory not fully resolved"; |
859 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
860 | return; |
861 | } |
862 | |
863 | m_directoryNode = m_client->node(expandedNodeId: targets[0].targetId()); |
864 | if (!m_directoryNode) { |
865 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid directory node"; |
866 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
867 | return; |
868 | } |
869 | |
870 | auto handleResolve = [this](QList<QOpcUaBrowsePathTarget> targets, |
871 | QList<QOpcUaRelativePathElement> path, |
872 | QOpcUa::UaStatusCode statusCode) { |
873 | _q_handleResolveBrowsePathFinished(targets, path, statusCode); |
874 | }; |
875 | QObject::connect(sender: m_directoryNode, signal: &QOpcUaNode::resolveBrowsePathFinished, |
876 | context: q, slot: std::move(handleResolve)); |
877 | |
878 | auto handleDirNode = [this](QString methodNodeId, QVariant result, |
879 | QOpcUa::UaStatusCode statusCode) { |
880 | _q_handleDirectoryNodeMethodCallFinished(methodNodeId, result, statusCode); |
881 | }; |
882 | QObject::connect(sender: m_directoryNode, signal: &QOpcUaNode::methodCallFinished, |
883 | context: q, slot: std::move(handleDirNode)); |
884 | |
885 | qCDebug(QT_OPCUA_GDSCLIENT) << "Directory node resolved:"<< m_directoryNode->nodeId(); |
886 | this->resolveMethodNodes(); |
887 | }); |
888 | |
889 | QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(m_gdsNamespaceIndex, QLatin1String("Directory")), |
890 | QOpcUa::ReferenceTypeId::Organizes); |
891 | QList<QOpcUaRelativePathElement> browsePath { pathElement }; |
892 | |
893 | if (!m_directoryNode->resolveBrowsePath(path: browsePath)) { |
894 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to resolve directory node"; |
895 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
896 | delete m_directoryNode; |
897 | m_directoryNode = nullptr; |
898 | return; |
899 | } |
900 | } |
901 | |
902 | void QOpcUaGdsClientPrivate::resolveMethodNodes() |
903 | { |
904 | // See OPC UA Specification 1.05 part 12 6.6.2 "Directory" |
905 | |
906 | QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(m_gdsNamespaceIndex, QLatin1String()), |
907 | QOpcUa::ReferenceTypeId::HasComponent); |
908 | QList<QOpcUaRelativePathElement> browsePath { pathElement }; |
909 | |
910 | |
911 | // Resolve all needed nodes from the directory |
912 | for (const auto &key : std::as_const(t: elementsToResolve)) { |
913 | if (!m_directoryNodes.value(key).isEmpty()) |
914 | continue; // Already resolved |
915 | |
916 | auto target = browsePath[0].targetName(); |
917 | target.setName(key); |
918 | browsePath[0].setTargetName(target); |
919 | |
920 | if (!m_directoryNode->resolveBrowsePath(path: browsePath)) { |
921 | qCWarning(QT_OPCUA_GDSCLIENT) << "Could not resolve Directory node"; |
922 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
923 | return; |
924 | } |
925 | } |
926 | } |
927 | |
928 | void QOpcUaGdsClientPrivate::_q_handleResolveBrowsePathFinished(QList<QOpcUaBrowsePathTarget> targets, QList<QOpcUaRelativePathElement> path, QOpcUa::UaStatusCode statusCode) { |
929 | if (path.size() != 1) { |
930 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size"; |
931 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
932 | return; |
933 | } |
934 | |
935 | if (m_directoryNodes.contains(key: path[0].targetName().name()) || !elementsToResolve.contains(str: path[0].targetName().name())) { |
936 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name"<< path[0].targetName().name(); |
937 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
938 | return; |
939 | } |
940 | |
941 | if (statusCode != QOpcUa::Good) { |
942 | qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving directory failed"<< statusCode; |
943 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
944 | return; |
945 | } |
946 | |
947 | if (targets.size() != 1) { |
948 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results"; |
949 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
950 | return; |
951 | } |
952 | |
953 | if (!targets[0].isFullyResolved()) { |
954 | qCWarning(QT_OPCUA_GDSCLIENT) << "Directory not fully resolved"; |
955 | setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); |
956 | return; |
957 | } |
958 | |
959 | m_directoryNodes[path[0].targetName().name()] = targets[0].targetId().nodeId(); |
960 | |
961 | if (elementsToResolve.size() == m_directoryNodes.size()) { |
962 | qCDebug(QT_OPCUA_GDSCLIENT) << "All symbols resolved"; |
963 | |
964 | if (m_appRecord.applicationId().isEmpty() || m_appRecord.applicationId() == QLatin1String("ns=0;i=0")) |
965 | this->findRegisteredApplication(); |
966 | else |
967 | this->getApplication(); |
968 | } |
969 | } |
970 | |
971 | void QOpcUaGdsClientPrivate::getApplication() |
972 | { |
973 | // See OPC UA Specification 1.05 part 12 6.6.9 "GetApplication" |
974 | |
975 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
976 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
977 | return; |
978 | } |
979 | |
980 | if (!m_directoryNode) { |
981 | qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; |
982 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
983 | return; |
984 | } |
985 | |
986 | if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetApplication")], |
987 | args: QList<QPair<QVariant, QOpcUa::Types>> { qMakePair(value1: QVariant(m_appRecord.applicationId()), value2: QOpcUa::Types::NodeId) })) { |
988 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method GetApplication"; |
989 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
990 | return; |
991 | } |
992 | } |
993 | |
994 | void QOpcUaGdsClientPrivate::handleGetApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) |
995 | { |
996 | if (statusCode == QOpcUa::BadNotFound) { |
997 | qCWarning(QT_OPCUA_GDSCLIENT) << "No application with ID"<< m_appRecord.applicationId() << "registered"; |
998 | // Clear application ID and register |
999 | m_appRecord.setApplicationId(QString()); |
1000 | |
1001 | // Remove invalid id from settings |
1002 | QSettings settings; |
1003 | settings.remove(key: QLatin1String("gds/applicationId")); |
1004 | settings.sync(); |
1005 | |
1006 | restartWithCredentials(); |
1007 | return; |
1008 | } |
1009 | |
1010 | if (statusCode != QOpcUa::Good) { |
1011 | qCWarning(QT_OPCUA_GDSCLIENT) << "Get application failed"<< statusCode; |
1012 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1013 | return; |
1014 | } |
1015 | |
1016 | auto extensionObject = result.value<QOpcUaExtensionObject>(); |
1017 | if (!extensionObject.encodingTypeId().isEmpty()) { |
1018 | if (extensionObject.encodingTypeId() != QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)) { |
1019 | qCWarning(QT_OPCUA_GDSCLIENT) << "Unexpected return type:"<< extensionObject.encodingTypeId(); |
1020 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1021 | return; |
1022 | } |
1023 | |
1024 | auto buffer = extensionObject.encodedBody(); |
1025 | QOpcUaBinaryDataEncoding decoder(&buffer); |
1026 | bool ok; |
1027 | QOpcUaApplicationRecordDataType appRecord = decoder.decode<QOpcUaApplicationRecordDataType>(success&: ok); |
1028 | |
1029 | if (!ok) { |
1030 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to decode data"; |
1031 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1032 | return; |
1033 | } |
1034 | |
1035 | if (m_appRecord.applicationId() != appRecord.applicationId()) |
1036 | qCWarning(QT_OPCUA_GDSCLIENT) << "Returned application record contains a different application ID"<< m_appRecord.applicationId() << appRecord.applicationId(); |
1037 | |
1038 | // FIXME: To be removed |
1039 | appRecord.setApplicationId(m_appRecord.applicationId()); |
1040 | |
1041 | m_appRecord = appRecord; |
1042 | qCInfo(QT_OPCUA_GDSCLIENT) << "Reusing application ID"<< m_appRecord.applicationId() << "which is already registered at the server"; |
1043 | } |
1044 | getCertificateGroups(); |
1045 | } |
1046 | |
1047 | void QOpcUaGdsClientPrivate::findRegisteredApplication() |
1048 | { |
1049 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
1050 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
1051 | return; |
1052 | } |
1053 | |
1054 | if (!m_directoryNode) { |
1055 | qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; |
1056 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1057 | return; |
1058 | } |
1059 | |
1060 | if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("FindApplications")], |
1061 | args: QList<QPair<QVariant, QOpcUa::Types>> { qMakePair(value1: QVariant(m_appRecord.applicationUri()), value2: QOpcUa::Types::String) })) { |
1062 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method"; |
1063 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1064 | return; |
1065 | } |
1066 | } |
1067 | |
1068 | void QOpcUaGdsClientPrivate::handleFindApplicationsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) |
1069 | { |
1070 | if (statusCode != QOpcUa::Good) { |
1071 | qCWarning(QT_OPCUA_GDSCLIENT) << "Find application failed"<< statusCode; |
1072 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1073 | return; |
1074 | } |
1075 | |
1076 | auto extensionObject = result.value<QOpcUaExtensionObject>(); |
1077 | |
1078 | if (extensionObject.encodingTypeId().isEmpty()) { |
1079 | qCInfo(QT_OPCUA_GDSCLIENT) << "No application with URI"<< m_appIdentitiy.applicationUri() << "registered"; |
1080 | this->registerApplication(); |
1081 | return; |
1082 | } |
1083 | |
1084 | if (extensionObject.encodingTypeId() != QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)) { |
1085 | qCWarning(QT_OPCUA_GDSCLIENT) << "Unexpected return type:"<< extensionObject.encodingTypeId(); |
1086 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1087 | return; |
1088 | } |
1089 | |
1090 | auto buffer = extensionObject.encodedBody(); |
1091 | QOpcUaBinaryDataEncoding decoder(&buffer); |
1092 | bool ok; |
1093 | QOpcUaApplicationRecordDataType appRecord = decoder.decode<QOpcUaApplicationRecordDataType>(success&: ok); |
1094 | |
1095 | if (!ok) { |
1096 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to decode data"; |
1097 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1098 | return; |
1099 | } |
1100 | |
1101 | m_appRecord = appRecord; |
1102 | qCInfo(QT_OPCUA_GDSCLIENT) << "Reusing application ID"<< appRecord.applicationId() << "registered for URI"<< appRecord.applicationUri(); |
1103 | getCertificateGroups(); |
1104 | } |
1105 | |
1106 | void QOpcUaGdsClientPrivate::registerApplication() |
1107 | { |
1108 | if (!m_appRecord.applicationId().isEmpty() && m_appRecord.applicationId() != QLatin1String("ns=0;i=0")) |
1109 | return; |
1110 | |
1111 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
1112 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
1113 | return; |
1114 | } |
1115 | |
1116 | if (!m_directoryNode) { |
1117 | qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; |
1118 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1119 | return; |
1120 | } |
1121 | |
1122 | QByteArray buffer; |
1123 | QOpcUaBinaryDataEncoding encoder(&buffer); |
1124 | if (!encoder.encode(src: m_appRecord)) { |
1125 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to encode body"; |
1126 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1127 | return; |
1128 | } |
1129 | |
1130 | QOpcUaExtensionObject parameter; |
1131 | parameter.setBinaryEncodedBody(encodedBody: buffer, typeId: QOpcUa::nodeIdFromInteger(ns: m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)); |
1132 | |
1133 | if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("RegisterApplication")], |
1134 | args: QList<QOpcUa::TypedVariant> { QOpcUa::TypedVariant(parameter, QOpcUa::ExtensionObject) })) { |
1135 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method RegisterApplication"; |
1136 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1137 | } |
1138 | } |
1139 | |
1140 | void QOpcUaGdsClientPrivate::handleRegisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) |
1141 | { |
1142 | if (statusCode != QOpcUa::Good) { |
1143 | qCDebug(QT_OPCUA_GDSCLIENT) << "Requesting user credentials"; |
1144 | if (statusCode == QOpcUa::BadUserAccessDenied && m_client->authenticationInformation().authenticationType() == QOpcUaUserTokenPolicy::Anonymous) { |
1145 | restartWithCredentials(); |
1146 | return; |
1147 | } |
1148 | qCWarning(QT_OPCUA_GDSCLIENT) << "Register application failed with"<< statusCode; |
1149 | setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); |
1150 | return; |
1151 | } |
1152 | |
1153 | m_appRecord.setApplicationId(result.toString()); |
1154 | |
1155 | QSettings settings; |
1156 | settings.setValue(key: QLatin1String("gds/applicationId"), value: m_appRecord.applicationId()); |
1157 | settings.sync(); |
1158 | |
1159 | qCInfo(QT_OPCUA_GDSCLIENT) << "Registered application with id"<< m_appRecord.applicationId(); |
1160 | getCertificateGroups(); |
1161 | } |
1162 | |
1163 | void QOpcUaGdsClientPrivate::getCertificateGroups() |
1164 | { |
1165 | // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.7 GetCertificateGroups |
1166 | |
1167 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
1168 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
1169 | return; |
1170 | } |
1171 | |
1172 | if (!m_directoryNode) { |
1173 | qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; |
1174 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1175 | return; |
1176 | } |
1177 | |
1178 | QList<QOpcUa::TypedVariant> arguments; |
1179 | arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); |
1180 | |
1181 | if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetCertificateGroups")], args: arguments)) { |
1182 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetCertificateGroups"; |
1183 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1184 | } |
1185 | } |
1186 | |
1187 | void QOpcUaGdsClientPrivate::handleGetCertificateGroupsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) |
1188 | { |
1189 | Q_Q(QOpcUaGdsClient); |
1190 | |
1191 | if (statusCode != QOpcUa::Good) { |
1192 | qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate groups failed"<< statusCode; |
1193 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1194 | return; |
1195 | } |
1196 | |
1197 | m_certificateGroups = result.value<QStringList>(); |
1198 | qCDebug(QT_OPCUA_GDSCLIENT) << "Certificate groups:"<< m_certificateGroups; |
1199 | |
1200 | emit q->certificateGroupsReceived(certificateGroups: m_certificateGroups); |
1201 | resolveCertificateTypes(); |
1202 | } |
1203 | |
1204 | void QOpcUaGdsClientPrivate::resolveCertificateTypes() |
1205 | { |
1206 | Q_Q(QOpcUaGdsClient); |
1207 | |
1208 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
1209 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
1210 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1211 | return; |
1212 | } |
1213 | |
1214 | if (m_certificateGroups.isEmpty()) { |
1215 | qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate groups is empty"; |
1216 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1217 | return; |
1218 | } |
1219 | |
1220 | delete m_certificateGroupNode; |
1221 | m_certificateGroupNode = m_client->node(nodeId: m_certificateGroups[0]); |
1222 | |
1223 | if (!m_certificateGroupNode) { |
1224 | qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate node failed"; |
1225 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1226 | return; |
1227 | } |
1228 | |
1229 | QObject::connect(sender: m_certificateGroupNode, signal: &QOpcUaNode::resolveBrowsePathFinished, |
1230 | context: q, slot: [this](QList<QOpcUaBrowsePathTarget> targets, |
1231 | QList<QOpcUaRelativePathElement> path, |
1232 | QOpcUa::UaStatusCode statusCode) { |
1233 | if (path.size() != 1) { |
1234 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size"; |
1235 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1236 | return; |
1237 | } |
1238 | |
1239 | if (path[0].targetName().name() != QLatin1String("CertificateTypes")) { |
1240 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name"<< path[0].targetName().name(); |
1241 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1242 | return; |
1243 | } |
1244 | |
1245 | if (statusCode != QOpcUa::Good) { |
1246 | qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving certificate types failed"<< statusCode; |
1247 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1248 | return; |
1249 | } |
1250 | |
1251 | if (targets.size() != 1) { |
1252 | qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results"; |
1253 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1254 | return; |
1255 | } |
1256 | |
1257 | if (!targets[0].isFullyResolved()) { |
1258 | qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types not fully resolved"; |
1259 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1260 | return; |
1261 | } |
1262 | |
1263 | m_certificateTypesNodeId = targets[0].targetId().nodeId(); |
1264 | this->getCertificateTypes(); |
1265 | }); |
1266 | |
1267 | QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(0, QLatin1String("CertificateTypes")), |
1268 | QOpcUa::ReferenceTypeId::Unspecified); |
1269 | QList<QOpcUaRelativePathElement> browsePath { pathElement }; |
1270 | |
1271 | |
1272 | if (!m_certificateGroupNode->resolveBrowsePath(path: browsePath)) { |
1273 | qCWarning(QT_OPCUA_GDSCLIENT) << "Could not resolve certificate type"; |
1274 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1275 | return; |
1276 | } |
1277 | } |
1278 | |
1279 | void QOpcUaGdsClientPrivate::getCertificateTypes() |
1280 | { |
1281 | Q_Q(QOpcUaGdsClient); |
1282 | |
1283 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
1284 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
1285 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1286 | return; |
1287 | } |
1288 | |
1289 | if (m_certificateTypesNodeId.isEmpty()) { |
1290 | qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types node id is empty"; |
1291 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1292 | return; |
1293 | } |
1294 | |
1295 | delete m_certificateTypesNode; |
1296 | m_certificateTypesNode = m_client->node(nodeId: m_certificateTypesNodeId); |
1297 | |
1298 | if (!m_certificateTypesNode) { |
1299 | qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types node failed"; |
1300 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1301 | return; |
1302 | } |
1303 | |
1304 | QObject::connect(sender: m_certificateTypesNode, signal: &QOpcUaNode::attributeUpdated, |
1305 | context: q, slot: [this](QOpcUa::NodeAttribute attr, QVariant value) { |
1306 | if (attr == QOpcUa::NodeAttribute::Value) |
1307 | qCWarning(QT_OPCUA_GDSCLIENT) << "possible certificate types"<< value; |
1308 | registrationDone(); |
1309 | }); |
1310 | |
1311 | if (!m_certificateTypesNode->readValueAttribute()) { |
1312 | qCWarning(QT_OPCUA_GDSCLIENT) << "Could not read certificate types"; |
1313 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1314 | return; |
1315 | } |
1316 | } |
1317 | |
1318 | void QOpcUaGdsClientPrivate::registrationDone() |
1319 | { |
1320 | Q_Q(QOpcUaGdsClient); |
1321 | |
1322 | setState(QOpcUaGdsClient::State::ApplicationRegistered); |
1323 | emit q->applicationRegistered(); |
1324 | |
1325 | m_certificateCheckTimer->start(); |
1326 | _q_certificateCheckTimeout(); // Force a check now |
1327 | |
1328 | m_trustListUpdateTimer->start(); |
1329 | _q_updateTrustList(); // Force a check now |
1330 | } |
1331 | |
1332 | void QOpcUaGdsClientPrivate::getCertificateStatus() |
1333 | { |
1334 | // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.10 GetCertificateStatus |
1335 | |
1336 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
1337 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
1338 | return; |
1339 | } |
1340 | |
1341 | if (!m_directoryNode) { |
1342 | qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; |
1343 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1344 | return; |
1345 | } |
1346 | |
1347 | if (m_certificateGroups.isEmpty()) { |
1348 | qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate groups received"; |
1349 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1350 | return; |
1351 | } |
1352 | |
1353 | QString certificateType = QLatin1String("ns=0;i=0"); // null node |
1354 | |
1355 | QList<QOpcUa::TypedVariant> arguments; |
1356 | arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); |
1357 | // Let the server choose the certificate group id |
1358 | arguments.push_back(t: QOpcUa::TypedVariant(m_certificateGroups[0], QOpcUa::NodeId)); |
1359 | // Let the server choose the certificate type id |
1360 | arguments.push_back(t: QOpcUa::TypedVariant(certificateType, QOpcUa::NodeId)); |
1361 | |
1362 | if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetCertificateStatus")], args: arguments)) { |
1363 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetCertificateStatus"; |
1364 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1365 | } |
1366 | } |
1367 | |
1368 | void QOpcUaGdsClientPrivate::handleGetCertificateStatusFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) |
1369 | { |
1370 | Q_Q(QOpcUaGdsClient); |
1371 | |
1372 | if (statusCode != QOpcUa::Good) { |
1373 | qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate status failed"<< statusCode; |
1374 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1375 | return; |
1376 | } |
1377 | |
1378 | if (result.toBool()) { |
1379 | qInfo(catFunc: QT_OPCUA_GDSCLIENT) << "Certificate needs update"; |
1380 | emit q->certificateUpdateRequired(); |
1381 | startCertificateRequest(); |
1382 | } |
1383 | } |
1384 | |
1385 | void QOpcUaGdsClientPrivate::startCertificateRequest() |
1386 | { |
1387 | // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.3 StartSigningRequest |
1388 | |
1389 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
1390 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
1391 | return; |
1392 | } |
1393 | |
1394 | if (!m_directoryNode) { |
1395 | qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; |
1396 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1397 | return; |
1398 | } |
1399 | |
1400 | QOpcUaKeyPair keyPair; |
1401 | QFile keyFile(m_pkiConfig.privateKeyFile()); |
1402 | |
1403 | qCDebug(QT_OPCUA_GDSCLIENT) << "Using private key"<< keyFile.fileName(); |
1404 | |
1405 | if (!keyFile.open(flags: QFile::ReadOnly)) { |
1406 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file"<< keyFile.fileName() << "for reading:"<< keyFile.errorString(); |
1407 | setError(QOpcUaGdsClient::Error::ConnectionError); |
1408 | return; |
1409 | } |
1410 | |
1411 | auto data = keyFile.readAll(); |
1412 | keyFile.close(); |
1413 | |
1414 | if (!keyPair.loadFromPemData(data)) { |
1415 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load private key"; |
1416 | setError(QOpcUaGdsClient::Error::ConnectionError); |
1417 | return; |
1418 | } |
1419 | |
1420 | if (!keyPair.hasPrivateKey()) { |
1421 | qCWarning(QT_OPCUA_GDSCLIENT) << "Private key does not contain a private key"; |
1422 | setError(QOpcUaGdsClient::Error::ConnectionError); |
1423 | return; |
1424 | } |
1425 | |
1426 | auto csr = createSigningRequest(); |
1427 | csr.setEncoding(QOpcUaX509CertificateSigningRequest::Encoding::DER); |
1428 | const auto csrData = csr.createRequest(privateKey: keyPair); |
1429 | |
1430 | |
1431 | QList<QOpcUa::TypedVariant> arguments; |
1432 | arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); |
1433 | // Let the server choose the certificate group id |
1434 | arguments.push_back(t: QOpcUa::TypedVariant(QLatin1String("ns=0;i=0"), QOpcUa::NodeId)); |
1435 | // Let the server choose the certificate type id |
1436 | arguments.push_back(t: QOpcUa::TypedVariant(QLatin1String("ns=0;i=0"), QOpcUa::NodeId)); |
1437 | |
1438 | arguments.push_back(t: QOpcUa::TypedVariant(csrData, QOpcUa::ByteString)); |
1439 | |
1440 | if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("StartSigningRequest")], args: arguments)) { |
1441 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call StartSigningRequest"; |
1442 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1443 | } |
1444 | } |
1445 | |
1446 | void QOpcUaGdsClientPrivate::handleStartSigningRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) |
1447 | { |
1448 | Q_Q(QOpcUaGdsClient); |
1449 | |
1450 | if (statusCode != QOpcUa::Good) { |
1451 | qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate failed"<< statusCode; |
1452 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1453 | return; |
1454 | } |
1455 | |
1456 | m_certificateRequestId = result.toString(); |
1457 | |
1458 | if (!m_certificateFinishTimer) |
1459 | m_certificateFinishTimer = new QTimer; |
1460 | |
1461 | m_certificateFinishTimer->setInterval(2000); |
1462 | m_certificateFinishTimer->setSingleShot(true); |
1463 | QObject::connect(sender: m_certificateFinishTimer, signal: &QTimer::timeout, context: q, slot: [this]() { |
1464 | this->finishCertificateRequest(); |
1465 | }); |
1466 | |
1467 | m_certificateFinishTimer->start(); |
1468 | } |
1469 | |
1470 | void QOpcUaGdsClientPrivate::finishCertificateRequest() |
1471 | { |
1472 | // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.5 FinishRequest |
1473 | |
1474 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
1475 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
1476 | return; |
1477 | } |
1478 | |
1479 | if (m_certificateRequestId.isEmpty()) { |
1480 | qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate request id"; |
1481 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1482 | return; |
1483 | } |
1484 | |
1485 | QList<QOpcUa::TypedVariant> arguments; |
1486 | arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); |
1487 | arguments.push_back(t: QOpcUa::TypedVariant(m_certificateRequestId, QOpcUa::NodeId)); |
1488 | |
1489 | if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("FinishRequest")], args: arguments)) { |
1490 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call FinishRequest"; |
1491 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1492 | } |
1493 | } |
1494 | |
1495 | void QOpcUaGdsClientPrivate::handleFinishRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) |
1496 | { |
1497 | Q_Q(QOpcUaGdsClient); |
1498 | |
1499 | if (statusCode == QOpcUa::BadNothingToDo) { |
1500 | // Server not finished yet: Try again later |
1501 | m_certificateFinishTimer->setInterval(m_certificateFinishTimer->interval() * 2); |
1502 | m_certificateFinishTimer->start(); |
1503 | qCWarning(QT_OPCUA_GDSCLIENT) << "Server not finished yet: Trying again in"<< m_certificateFinishTimer->interval() / 1000 << "s"; |
1504 | return; |
1505 | } |
1506 | if (statusCode != QOpcUa::Good) { |
1507 | qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate failed"<< statusCode; |
1508 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1509 | return; |
1510 | } |
1511 | |
1512 | m_certificateRequestId.clear(); |
1513 | const auto resultList = result.toList(); |
1514 | |
1515 | if (resultList.size() != 3) { |
1516 | qCWarning(QT_OPCUA_GDSCLIENT) << "Expected list of 3 results but got"<< resultList.size(); |
1517 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1518 | return; |
1519 | } |
1520 | |
1521 | const auto certificate = resultList[0].toByteArray(); |
1522 | //const auto privateKey = resultList[1].toByteArray(); |
1523 | const auto issuerList = resultList[2].toByteArray(); |
1524 | |
1525 | if (certificate.isEmpty() || issuerList.isEmpty()) { |
1526 | qCWarning(QT_OPCUA_GDSCLIENT) << "Certificates are empty"; |
1527 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1528 | return; |
1529 | } |
1530 | |
1531 | qCInfo(QT_OPCUA_GDSCLIENT) << "Received new certificate"<< certificate; |
1532 | |
1533 | QFile certificateFile(m_pkiConfig.clientCertificateFile()); |
1534 | if (!certificateFile.open(flags: QFile::WriteOnly)) { |
1535 | qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate"; |
1536 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1537 | return; |
1538 | } |
1539 | |
1540 | if (certificateFile.write(data: certificate) != certificate.size()) { |
1541 | qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate data"; |
1542 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1543 | return; |
1544 | } |
1545 | |
1546 | certificateFile.close(); |
1547 | |
1548 | // FIMXE: How to store this? |
1549 | QTemporaryFile issuerFile(m_pkiConfig.issuerListDirectory() + QLatin1String("/XXXXXX.der")); |
1550 | if (!issuerFile.open()) { |
1551 | qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store issuer certificates to"<< m_pkiConfig.issuerListDirectory(); |
1552 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1553 | return; |
1554 | } |
1555 | |
1556 | if (issuerFile.write(data: issuerList) != issuerList.size()) { |
1557 | qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate data"; |
1558 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1559 | return; |
1560 | } |
1561 | |
1562 | qCDebug(QT_OPCUA_GDSCLIENT) << "issuer list stored to"<< issuerFile.fileName(); |
1563 | issuerFile.close(); |
1564 | issuerFile.setAutoRemove(false); |
1565 | |
1566 | emit q->certificateUpdated(); |
1567 | } |
1568 | |
1569 | void QOpcUaGdsClientPrivate::unregisterApplication() |
1570 | { |
1571 | Q_Q(QOpcUaGdsClient); |
1572 | |
1573 | if (m_appRecord.applicationId().isEmpty() || m_appRecord.applicationId() == QLatin1String("ns=0;i=0")) { |
1574 | emit q->unregistered(); |
1575 | return; |
1576 | } |
1577 | |
1578 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
1579 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
1580 | setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); |
1581 | return; |
1582 | } |
1583 | |
1584 | if (!m_directoryNode) { |
1585 | qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; |
1586 | setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); |
1587 | return; |
1588 | } |
1589 | |
1590 | if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("UnregisterApplication")], |
1591 | args: QList<QOpcUa::TypedVariant> { QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId) })) { |
1592 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method UnregisterApplication"; |
1593 | setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); |
1594 | } |
1595 | } |
1596 | |
1597 | void QOpcUaGdsClientPrivate::handleUnregisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) |
1598 | { |
1599 | Q_Q(QOpcUaGdsClient); |
1600 | Q_UNUSED(result); |
1601 | |
1602 | if (statusCode != QOpcUa::Good) { |
1603 | qCWarning(QT_OPCUA_GDSCLIENT) << "Unregister application failed"<< statusCode; |
1604 | setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); |
1605 | return; |
1606 | } |
1607 | |
1608 | m_client->disconnectFromEndpoint(); |
1609 | emit q->unregistered(); |
1610 | } |
1611 | |
1612 | void QOpcUaGdsClientPrivate::setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig) |
1613 | { |
1614 | m_pkiConfig = pkiConfig; |
1615 | } |
1616 | |
1617 | const QOpcUaPkiConfiguration &QOpcUaGdsClientPrivate::pkiConfiguration() const |
1618 | { |
1619 | return m_pkiConfig; |
1620 | } |
1621 | |
1622 | void QOpcUaGdsClientPrivate::setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity) |
1623 | { |
1624 | m_appIdentitiy = appIdentity; |
1625 | } |
1626 | |
1627 | const QOpcUaApplicationIdentity &QOpcUaGdsClientPrivate::applicationIdentity() const |
1628 | { |
1629 | return m_appIdentitiy; |
1630 | } |
1631 | |
1632 | void QOpcUaGdsClientPrivate::setApplicationRecord(const QOpcUaApplicationRecordDataType &appRecord) |
1633 | { |
1634 | m_appRecord = appRecord; |
1635 | } |
1636 | |
1637 | const QOpcUaApplicationRecordDataType &QOpcUaGdsClientPrivate::applicationRecord() const |
1638 | { |
1639 | return m_appRecord; |
1640 | } |
1641 | |
1642 | void QOpcUaGdsClientPrivate::setCertificateSigningRequestPresets(const QOpcUaX509DistinguishedName &dn, const QString &dns) |
1643 | { |
1644 | csrPresets.dn = dn; |
1645 | csrPresets.dns = dns; |
1646 | } |
1647 | |
1648 | const QOpcUaX509DistinguishedName &QOpcUaGdsClientPrivate::distinguishedNameCertificateSigningRequestPreset() const |
1649 | { |
1650 | return csrPresets.dn; |
1651 | } |
1652 | |
1653 | const QString &QOpcUaGdsClientPrivate::dnsCertificateSigningRequestPreset() const |
1654 | { |
1655 | return csrPresets.dns; |
1656 | } |
1657 | |
1658 | void QOpcUaGdsClientPrivate::setCertificateCheckInterval(int interval) |
1659 | { |
1660 | m_certificateCheckTimer->setInterval(interval); |
1661 | } |
1662 | |
1663 | int QOpcUaGdsClientPrivate::certificateCheckInterval() const |
1664 | { |
1665 | return m_certificateCheckTimer->interval(); |
1666 | } |
1667 | |
1668 | void QOpcUaGdsClientPrivate::setTrustListUpdateInterval(int interval) |
1669 | { |
1670 | m_trustListUpdateTimer->setInterval(interval); |
1671 | } |
1672 | |
1673 | int QOpcUaGdsClientPrivate::trustListUpdateInterval() const |
1674 | { |
1675 | return m_trustListUpdateTimer->interval(); |
1676 | } |
1677 | |
1678 | QOpcUaGdsClient::Error QOpcUaGdsClientPrivate::error() const |
1679 | { |
1680 | return m_error; |
1681 | } |
1682 | |
1683 | QOpcUaGdsClient::State QOpcUaGdsClientPrivate::state() const |
1684 | { |
1685 | return m_state; |
1686 | } |
1687 | |
1688 | QOpcUaX509CertificateSigningRequest QOpcUaGdsClientPrivate::createSigningRequest() const |
1689 | { |
1690 | QOpcUaX509CertificateSigningRequest csr; |
1691 | QOpcUaX509DistinguishedName dn = csrPresets.dn; |
1692 | // Overwrite the application name because it has to match the identity |
1693 | dn.setEntry(type: QOpcUaX509DistinguishedName::Type::CommonName, value: m_appIdentitiy.applicationName()); |
1694 | csr.setSubject(dn); |
1695 | |
1696 | QOpcUaX509ExtensionSubjectAlternativeName *san = new QOpcUaX509ExtensionSubjectAlternativeName; |
1697 | san->addEntry(type: QOpcUaX509ExtensionSubjectAlternativeName::Type::URI, value: m_appIdentitiy.applicationUri()); |
1698 | if (!csrPresets.dns.isEmpty()) |
1699 | san->addEntry(type: QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS, value: csrPresets.dns); |
1700 | san->setCritical(true); |
1701 | csr.addExtension(extension: san); |
1702 | |
1703 | QOpcUaX509ExtensionBasicConstraints *bc = new QOpcUaX509ExtensionBasicConstraints; |
1704 | bc->setCa(false); |
1705 | bc->setCritical(true); |
1706 | csr.addExtension(extension: bc); |
1707 | |
1708 | QOpcUaX509ExtensionKeyUsage *ku = new QOpcUaX509ExtensionKeyUsage; |
1709 | ku->setCritical(true); |
1710 | ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::DigitalSignature); |
1711 | ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::NonRepudiation); |
1712 | ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyEncipherment); |
1713 | ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::DataEncipherment); |
1714 | ku->setKeyUsage(keyUsage: QOpcUaX509ExtensionKeyUsage::KeyUsage::CertificateSigning); |
1715 | csr.addExtension(extension: ku); |
1716 | |
1717 | return csr; |
1718 | } |
1719 | |
1720 | void QOpcUaGdsClientPrivate::_q_handleDirectoryNodeMethodCallFinished(QString methodNodeId, QVariant result, QOpcUa::UaStatusCode statusCode) |
1721 | { |
1722 | if (methodNodeId == m_directoryNodes[QLatin1String("UnregisterApplication")]) { |
1723 | this->handleUnregisterApplicationFinished(result, statusCode); |
1724 | } else if ( methodNodeId == m_directoryNodes[QLatin1String("FinishRequest")]) { |
1725 | this->handleFinishRequestFinished(result, statusCode); |
1726 | } else if (methodNodeId == m_directoryNodes[QLatin1String("StartSigningRequest")]) { |
1727 | this->handleStartSigningRequestFinished(result, statusCode); |
1728 | } else if (methodNodeId == m_directoryNodes[QLatin1String("GetCertificateStatus")]) { |
1729 | this->handleGetCertificateStatusFinished(result, statusCode); |
1730 | } else if (methodNodeId == m_directoryNodes[QLatin1String("GetCertificateGroups")]) { |
1731 | this->handleGetCertificateGroupsFinished(result, statusCode); |
1732 | } else if (methodNodeId == m_directoryNodes[QLatin1String("RegisterApplication")]) { |
1733 | this->handleRegisterApplicationFinished(result, statusCode); |
1734 | } else if (methodNodeId == m_directoryNodes[QLatin1String("FindApplications")]) { |
1735 | this->handleFindApplicationsFinished(result, statusCode); |
1736 | } else if (methodNodeId == m_directoryNodes[QLatin1String("GetApplication")]) { |
1737 | this->handleGetApplicationFinished(result, statusCode); |
1738 | } else if (methodNodeId == m_directoryNodes[QLatin1String("GetTrustList")]) { |
1739 | this->handleGetTrustListFinished(result, statusCode); |
1740 | } else { |
1741 | qCWarning(QT_OPCUA_GDSCLIENT) << "Result unexpeced method call received"<< methodNodeId; |
1742 | } |
1743 | } |
1744 | |
1745 | void QOpcUaGdsClientPrivate::_q_certificateCheckTimeout() |
1746 | { |
1747 | Q_Q(QOpcUaGdsClient); |
1748 | |
1749 | QFile file(m_pkiConfig.clientCertificateFile()); |
1750 | if (!file.open(flags: QFile::ReadOnly)) { |
1751 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load certificate from file"<< m_pkiConfig.clientCertificateFile(); |
1752 | } |
1753 | |
1754 | QSslCertificate cert(&file, QSsl::Der); |
1755 | if (cert.isNull()) { |
1756 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load certificate from file"<< m_pkiConfig.clientCertificateFile(); |
1757 | return; |
1758 | } |
1759 | |
1760 | bool needsUpdate = false; |
1761 | |
1762 | if (cert.isSelfSigned()) { |
1763 | needsUpdate = true; |
1764 | qCInfo(QT_OPCUA_GDSCLIENT) << "Certificate is self-signed: requesting update"; |
1765 | } |
1766 | |
1767 | if (QDateTime::currentDateTime().addSecs(secs: 24*60*60) > cert.expiryDate()) { |
1768 | needsUpdate = true; |
1769 | qCInfo(QT_OPCUA_GDSCLIENT) << "Certificate expiry date is due: requesting update"; |
1770 | } |
1771 | |
1772 | if (!needsUpdate) |
1773 | getCertificateStatus(); |
1774 | |
1775 | if (needsUpdate) { |
1776 | emit q->certificateUpdateRequired(); |
1777 | startCertificateRequest(); |
1778 | } |
1779 | } |
1780 | |
1781 | void QOpcUaGdsClientPrivate::_q_updateTrustList() |
1782 | { |
1783 | // OPC UA Specification Version 1.05 Part 12 Chapter 7.9.9 GetTrustList |
1784 | |
1785 | if (!m_client || m_client->state() != QOpcUaClient::Connected) { |
1786 | qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; |
1787 | return; |
1788 | } |
1789 | |
1790 | if (!m_certificateGroupNode) { |
1791 | qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate group node"; |
1792 | return; |
1793 | } |
1794 | |
1795 | QList<QOpcUa::TypedVariant> arguments; |
1796 | arguments.push_back(t: QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); |
1797 | arguments.push_back(t: QOpcUa::TypedVariant(m_certificateGroupNode->nodeId(), QOpcUa::NodeId)); |
1798 | |
1799 | if (!m_directoryNode->callMethod(methodNodeId: m_directoryNodes[QLatin1String("GetTrustList")], args: arguments)) { |
1800 | qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetTrustList"; |
1801 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1802 | } |
1803 | } |
1804 | |
1805 | void QOpcUaGdsClientPrivate::handleGetTrustListFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) |
1806 | { |
1807 | Q_Q(QOpcUaGdsClient); |
1808 | |
1809 | if (statusCode != QOpcUa::Good) { |
1810 | qCWarning(QT_OPCUA_GDSCLIENT) << "Getting trust list node failed"<< statusCode; |
1811 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1812 | return; |
1813 | } |
1814 | |
1815 | const auto trustListNodeId = result.toString(); |
1816 | |
1817 | if (trustListNodeId.isEmpty()) { |
1818 | qCWarning(QT_OPCUA_GDSCLIENT) << "Trust list node id is empty"; |
1819 | setError(QOpcUaGdsClient::Error::FailedToGetCertificate); |
1820 | return; |
1821 | } |
1822 | |
1823 | delete m_trustListNode; |
1824 | m_trustListNode = m_client->node(nodeId: trustListNodeId); |
1825 | |
1826 | if (!m_trustListNode) { |
1827 | qCWarning(QT_OPCUA_GDSCLIENT) << "Trust list node failed"; |
1828 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1829 | return; |
1830 | } |
1831 | |
1832 | QObject::connect(sender: m_trustListNode, signal: &QOpcUaNode::attributeUpdated, |
1833 | context: q, slot: [q](QOpcUa::NodeAttribute attr, QVariant value) { |
1834 | Q_UNUSED(value); |
1835 | if (attr == QOpcUa::NodeAttribute::Value) |
1836 | emit q->trustListUpdated(); |
1837 | }); |
1838 | |
1839 | if (!m_trustListNode->readValueAttribute()) { |
1840 | qCWarning(QT_OPCUA_GDSCLIENT) << "Could not read trust list"; |
1841 | setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); |
1842 | return; |
1843 | } |
1844 | } |
1845 | |
1846 | // When it is detected that authentication credentials are required this function |
1847 | // is used to re-connect to the server after requesting credentials |
1848 | void QOpcUaGdsClientPrivate::restartWithCredentials() |
1849 | { |
1850 | Q_Q(QOpcUaGdsClient); |
1851 | |
1852 | QOpcUaAuthenticationInformation authInfo; |
1853 | emit q->authenticationRequired(authInfo); |
1854 | m_client->setAuthenticationInformation(authInfo); |
1855 | |
1856 | qCInfo(QT_OPCUA_GDSCLIENT) << "Restarting connection with credentials"; |
1857 | m_restartRequired = true; |
1858 | m_client->disconnectFromEndpoint(); |
1859 | } |
1860 | |
1861 | QT_END_NAMESPACE |
1862 | |
1863 | #include "moc_qopcuagdsclient.cpp" |
1864 |
Definitions
- QT_OPCUA_GDSCLIENT
- elementsToResolve
- QOpcUaGdsClient
- ~QOpcUaGdsClient
- setBackend
- backend
- setEndpoint
- endpoint
- setPkiConfiguration
- pkiConfiguration
- setApplicationIdentity
- applicationIdentity
- setApplicationRecord
- applicationRecord
- applicationId
- setCertificateSigningRequestPresets
- distinguishedNameCertificateSigningRequestPreset
- dnsCertificateSigningRequestPreset
- setCertificateCheckInterval
- certificateCheckInterval
- setTrustListUpdateInterval
- trustListUpdateInterval
- error
- state
- start
- unregisterApplication
- QOpcUaGdsClientPrivate
- ~QOpcUaGdsClientPrivate
- initializePrivateConnections
- setEndpoint
- endpoint
- setBackend
- backend
- applicationId
- setError
- setState
- start
- resolveDirectoryNode
- resolveMethodNodes
- _q_handleResolveBrowsePathFinished
- getApplication
- handleGetApplicationFinished
- findRegisteredApplication
- handleFindApplicationsFinished
- registerApplication
- handleRegisterApplicationFinished
- getCertificateGroups
- handleGetCertificateGroupsFinished
- resolveCertificateTypes
- getCertificateTypes
- registrationDone
- getCertificateStatus
- handleGetCertificateStatusFinished
- startCertificateRequest
- handleStartSigningRequestFinished
- finishCertificateRequest
- handleFinishRequestFinished
- unregisterApplication
- handleUnregisterApplicationFinished
- setPkiConfiguration
- pkiConfiguration
- setApplicationIdentity
- applicationIdentity
- setApplicationRecord
- applicationRecord
- setCertificateSigningRequestPresets
- distinguishedNameCertificateSigningRequestPreset
- dnsCertificateSigningRequestPreset
- setCertificateCheckInterval
- certificateCheckInterval
- setTrustListUpdateInterval
- trustListUpdateInterval
- error
- state
- createSigningRequest
- _q_handleDirectoryNodeMethodCallFinished
- _q_certificateCheckTimeout
- _q_updateTrustList
- handleGetTrustListFinished
Learn to use CMake with our Intro Training
Find out more