Quick Secure CoAP Client Example▲
Quick Secure CoAP Client demonstrates how to create a secure CoAP client and use it in a Qt Quick application.
Qt CoAP does not provide a QML API in its current version. However, you can make the C++ classes of the module available to QML as it is shown in the example.
Running the Example▲
To run the example application, you first need to set up a secure CoAP server. You can run the example with any secure CoAP server supporting one of the pre-shared key (PSK) or certificate authentication modes. For more information about setting up a secure CoAP server, see Setting Up a Secure CoAP Server.
Creating a Client and Linking It with QML▲
QmlCoapSecureClient wraps the functionality of QCoapClient to make it available to QML. It also holds the selected security mode and security configuration parameters.
class
QmlCoapSecureClient : public
QObject
{
Q_OBJECT
public
:
QmlCoapSecureClient(QObject *
parent =
nullptr
);
~
QmlCoapSecureClient() override
;
Q_INVOKABLE void
setSecurityMode(QtCoap::
SecurityMode mode);
Q_INVOKABLE void
sendGetRequest(const
QString &
amp;host, const
QString &
amp;path, int
port);
Q_INVOKABLE void
setSecurityConfiguration(const
QString &
amp;preSharedKey, const
QString &
amp;identity);
Q_INVOKABLE void
setSecurityConfiguration(const
QString &
amp;localCertificatePath,
const
QString &
amp;caCertificatePath,
const
QString &
amp;privateKeyPath);
Q_INVOKABLE void
disconnect();
Q_SIGNALS
:
void
finished(const
QString &
amp;result);
private
:
QCoapClient *
m_coapClient;
QCoapSecurityConfiguration m_configuration;
QtCoap::
SecurityMode m_securityMode;
}
;
In the main.cpp file, we register the QmlCoapSecureClient class as a QML type:
qmlRegisterType&
lt;QmlCoapSecureClient&
gt;("CoapSecureClient"
, 1
, 0
, "CoapSecureClient"
);
We also register the QtCoap namespace, to be able to use it in QML code:
qmlRegisterUncreatableMetaObject(QtCoap::
staticMetaObject, "qtcoap.example.namespace"
, 1
, 0
,
"QtCoap"
, "Access to enums is read-only"
);
The instance of QCoapClient is created when the user selects or changes the security mode in the UI. The QmlCoapSecureClient::setSecurityMode() method is invoked from the QML code, when one of the security modes is selected:
ButtonGroup {
id
:
securityModeGroup
onClicked
:
{
if
(securityModeGroup.checkedButton ===
preSharedMode)
client.setSecurityMode(QtCoap.SecurityMode.PreSharedKey);
else
client.setSecurityMode(QtCoap.SecurityMode.Certificate);
}
}
And in the .cpp file:
void
QmlCoapSecureClient::
setSecurityMode(QtCoap::
SecurityMode mode)
{
// Create a new client, if the security mode has changed
if
(m_coapClient &
amp;&
amp; mode !=
m_securityMode) {
delete
m_coapClient;
m_coapClient =
nullptr
;
}
if
(!
m_coapClient) {
m_coapClient =
new
QCoapClient(mode);
m_securityMode =
mode;
...
Then we connect the signals of the client, to get notified when a CoAP reply is received or a request has failed:
...
connect(m_coapClient, &
amp;QCoapClient::
finished, this
,
[this
](QCoapReply *
reply) {
if
(!
reply)
emit finished("Something went wrong, received a null reply"
);
else
if
(reply-&
gt;errorReceived() !=
QtCoap::Error::
Ok)
emit finished(errorMessage(reply-&
gt;errorReceived()));
else
emit finished(reply-&
gt;message().payload());
}
);
connect(m_coapClient, &
amp;QCoapClient::
error, this
,
[this
](QCoapReply *
, QtCoap::
Error errorCode) {
emit finished(errorMessage(errorCode));
}
);
...
The QmlCoapSecureClient::finished() signal triggers the onFinished signal handler of CoapSecureClient, which handles the output:
CoapSecureClient {
id
:
client
onFinished
:
{
outputView.text =
result;
statusLabel.text =
""
;
disconnectButton.enabled =
true
;
}
}
...
Sending a Request▲
When the user clicks on the Send Request button, we set the security configuration based on the selected security mode and send a GET request:
...
Button {
id
:
requestButton
text
:
qsTr("Send Request"
)
enabled
:
securityModeGroup.checkState !==
Qt.Unchecked
onClicked
:
{
outputView.text =
""
;
if
(securityModeGroup.checkedButton ===
preSharedMode)
client.setSecurityConfiguration(pskField.text, identityField.text);
else
client.setSecurityConfiguration(localCertificatePicker.selectedFile,
caCertificatePicker.selectedFile,
privateKeyPicker.selectedFile);
client.sendGetRequest(hostComboBox.editText, resourceField.text,
...
There are two overloads for the setSecurityConfiguration method. For the PSK mode we simply set the client identity and the pre-shared key:
QmlCoapSecureClient::
setSecurityConfiguration(const
QString &
amp;preSharedKey, const
QString &
amp;identity)
{
QCoapSecurityConfiguration configuration;
configuration.setPreSharedKey(preSharedKey.toUtf8());
configuration.setPreSharedKeyIdentity(identity.toUtf8());
m_configuration =
configuration;
}
And for X.509 certificates, we read the certificate files and the private key, and set the security configuration:
void
QmlCoapSecureClient::
setSecurityConfiguration(const
QString &
amp;localCertificatePath,
const
QString &
amp;caCertificatePath,
const
QString &
amp;privateKeyPath)
{
QCoapSecurityConfiguration configuration;
const
auto
localCerts =
QSslCertificate::
fromPath(QUrl(localCertificatePath).toLocalFile(), QSsl::
Pem,
QSslCertificate::PatternSyntax::
FixedString);
if
(localCerts.isEmpty())
qCWarning(lcCoapClient, "The specified local certificate file is not valid."
);
else
configuration.setLocalCertificateChain(localCerts.toVector());
const
auto
caCerts =
QSslCertificate::
fromPath(QUrl(caCertificatePath).toLocalFile(), QSsl::
Pem,
QSslCertificate::PatternSyntax::
FixedString);
if
(caCerts.isEmpty())
qCWarning(lcCoapClient, "The specified CA certificate file is not valid."
);
else
configuration.setCaCertificates(caCerts.toVector());
QFile privateKey(QUrl(privateKeyPath).toLocalFile());
if
(privateKey.open(QIODevice::
ReadOnly)) {
QCoapPrivateKey key(privateKey.readAll(), QSsl::
Ec);
configuration.setPrivateKey(key);
}
else
{
qCWarning(lcCoapClient) &
lt;&
lt; "Unable to read the specified private key file"
&
lt;&
lt; privateKeyPath;
}
m_configuration =
configuration;
}
After setting the security configuration, the sendGetRequest method sets the request URL and sends a GET request:
void
QmlCoapSecureClient::
sendGetRequest(const
QString &
amp;host, const
QString &
amp;path, int
port)
{
if
(!
m_coapClient)
return
;
m_coapClient-&
gt;setSecurityConfiguration(m_configuration);
QUrl url;
url.setHost(host);
url.setPath(path);
url.setPort(port);
m_coapClient-&
gt;get(url);
}
When sending the first request, a handshake with the CoAP server is performed. After the handshake is successfully done, all the subsequent messages are encrypted, and changing the security configuration after a successful handshake won't have any effect. If you want to change it, or change the host, you need to disconnect first.
void
QmlCoapSecureClient::
disconnect()
{
if
(m_coapClient)
m_coapClient-&
gt;disconnect();
}
This will abort the handshake and close the open sockets.
For the authentication using X.509 certificates, we need to specify the certificate files. The FilePicker component is used for this purpose. It combines a text field and a button for opening a file dialog when the button is pressed:
Item {
id
:
filePicker
property string dialogText
property alias selectedFile: filePathField.text
height
:
addFileButton.height
FileDialog {
id
:
fileDialog
title
:
qsTr("Please Choose %1"
).arg(dialogText)
folder
:
StandardPaths.writableLocation(StandardPaths.HomeLocation)
fileMode
:
FileDialog.OpenFile
onAccepted
:
filePathField.text =
fileDialog.file
}
RowLayout {
anchors.fill: parent
TextField {
id
:
filePathField
placeholderText
:
qsTr("<%1>"
).arg(dialogText)
inputMethodHints
:
Qt.ImhUrlCharactersOnly
selectByMouse
:
true
Layout.fillWidth: true
}
Button {
id
:
addFileButton
text
:
qsTr("Add %1"
).arg(dialogText)
onClicked
:
fileDialog.open()
}
}
}
In the main.qml file FilePicker is instantiated several times for creating input fields for certificates and the private key:
...
FilePicker {
id
:
localCertificatePicker
dialogText
:
"Local Certificate"
enabled
:
securityModeGroup.checkedButton ===
certificateMode
Layout.columnSpan: 2
Layout.fillWidth: true
}
FilePicker {
id
:
caCertificatePicker
dialogText
:
"CA Certificate"
enabled
:
securityModeGroup.checkedButton ===
certificateMode
Layout.columnSpan: 2
Layout.fillWidth: true
}
FilePicker {
id
:
privateKeyPicker
dialogText
:
"Private Key"
enabled
:
securityModeGroup.checkedButton ===
certificateMode
Layout.columnSpan: 2
Layout.fillWidth: true
}
...
Setting Up a Secure CoAP Server▲
To run this example, you need to have a secure CoAP server supporting either PSK or Certificate modes (or both). You have the following options:
-
Manually build and run a secure CoAP server using, for example, libcoap, Californium, FreeCoAP, or any other CoAP library which supports DTLS.
-
Use the ready Docker images available at Docker Hub, which build and run the secure CoAP servers suitable for our example. The steps required for using the docker-based CoAP servers are described below.
Setting Up a Server For PSK Mode▲
The following command pulls the docker container for a secure CoAP server based on Californium plugtest (which is not secure by default) from the Docker Hub and starts it:
docker run --
name coap-
test-
server -
d --
rm -
p 5683
:5683
/
udp -
p 5684
:5684
/
udp sokurazy/
coap-
secure-
test-
server:californium.1.1.0
The CoAP test server will be reachable on ports 5683 (non-secure) and 5684 (secure). For instructions on retrieving the IP address see Getting The IP Address.
To run the example with this server, you need to set the pre-shared key to secretPSK and the identity to Client_identity.
Setting Up a Server For Certificate Mode▲
The docker image of the secure server using authentication with X.509 certificates is based on the time server example from the FreeCoAP library. The following command pulls the container from Docker Hub and starts it:
docker run --
name coap-
time-
server -
d --
rm -
p 5684
:5684
/
udp sokurazy/
coap-
secure-
time-
sever:freecoap