qtsslapple-push-notificationsssl-certificateqsslsocket

Qt 5.8 Apple Notification Server Handshake failed


i tried it some ours now but i could not get it to work. I want to push some notifications from a Qt Application. Tried to get it to work on macOS Sierra with Qt 5.8 installation and on Pi3 also with Qt 5.8.

I created my push certificate with "fastlane pem" and i tested it with "Pusher" and it works correct. But i cannot get it to work in Qt....

At first is the code i use to initialize and connect the QSslSocket:

QSslSocket * ssl = new QSslSocket;

connect(ssl, &QSslSocket::encrypted, this, &IOSPusher::encrypted );
connect(ssl, static_cast<void(QSslSocket::*)(const QList<QSslError> &)>(&QSslSocket::sslErrors),this,&IOSPusher::sslErrors);
connect(ssl, static_cast<void(QSslSocket::*)(QAbstractSocket::SocketError)>(&QSslSocket::error),this, &IOSPusher::error );
connect(ssl,&QSslSocket::stateChanged,this,&IOSPusher::stateChanged );

Loading the certificate

QString path = QStandardPaths::writableLocation(certificateLocation) + "/apns.pem";

const auto certs =  QSslCertificate::fromPath(path);

if(certs.count() > 0){
    qDebug() << "IOSPusher: Certificate loaded successfully";
}else{
    qDebug() << "Could not load certificate : " + path;
}

QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setCaCertificates(certs);

ssl->setSslConfiguration(config);
ssl->connectToHostEncrypted( gateway.sandbox.push.apple.com,2195 );

And thats the output i get:

IOSPusher: Certificate loaded successfully
IOSPusher::stateChanged  QAbstractSocket::HostLookupState
IOSPusher::stateChanged  QAbstractSocket::ConnectingState
IOSPusher::stateChanged  QAbstractSocket::ConnectedState
IOSPusher::error  QAbstractSocket::SocketError(13)
IOSPusher::stateChanged  QAbstractSocket::ClosingState
IOSPusher::stateChanged  QAbstractSocket::UnconnectedState

So according to the Qt documentation the error:

QAbstractSocket::SocketError(13)

means the following:

SslHandshakeFailedError

And

> the SSL/TLS handshake failed and the encrypted channel could not be established. The sslErrors() signal should have been emitted.

But the sslErrors() signal will not be emitted in my case....

The SSL/TLS handshake failed, so the connection was closed (only used in QSslSocket)

Any ideas or samples how i can establish a encrypted connection to apple?

Thanks in advance!


Solution

  • Okay like so often when i try to get help from anyone i got it now :D

    The solution which worked for me now is:

    Create the *.pem cert with fastlane pem with a password (maybe it works without password too but that was the last i tried now and....never change a running system haha)

     fastlane pem --development -p <private_key_password> -a <your_app_identifier>
    

    Then to connect with the QSslSocket do the following... as before in my question...

    QSslSocket * ssl = new QSslSocket;
    QString path = QStandardPaths::writableLocation(certificateLocation) + "/apns.pem";
    
    connect(ssl, &QSslSocket::encrypted, this, &IOSPusher::encrypted );
    connect(ssl, static_cast<void(QSslSocket::*)(const QList<QSslError> &)>(&QSslSocket::sslErrors),this,&IOSPusher::sslErrors);
    connect(ssl, static_cast<void(QSslSocket::*)(QAbstractSocket::SocketError)>(&QSslSocket::error),this, &IOSPusher::error );
    connect(ssl,&QSslSocket::stateChanged,this,&IOSPusher::stateChanged );
    
    QSslCertificate cert;
    const auto certs =  QSslCertificate::fromPath(path);
    
    if(certs.count() > 0){
        cert = certs.at(0);
        qDebug() << "IOSPusher: Certificate loaded successfully";
    }else{
        qDebug() << "Could not load certificate : " + path;
        return false;
    }
    

    Now here comes the magic what it did for me

    Use the private_key (.pkey) file which will also be created with fastlane pem

    //Use the path to the .pkey file from fastlane
    QFile keyfile(path + "/apns.pkey");
    keyfile.open(QFile::ReadOnly);
    
    //Create the QSslKey as private key
    QSslKey privateKey(&keyfile,QSsl::Rsa,QSsl::Pem,QSsl::PrivateKey,QByteArray("<private_key_password_from_fastlane>"));
    //Close the file
    keyfile.close();
    

    And add the private key and the certificate to the ssl config

    QSslConfiguration config = QSslConfiguration::defaultConfiguration();
    
    config.setLocalCertificate(cert);
    config.setPrivateKey(privateKey);
    

    as you can see here this time i not use the config.setCaCertificates method but instead the config.setLocalCertificate method. That was a mistake on my side...

    At least add the config to the ssl socket and FIRE!

    ssl->setSslConfiguration(config);
    ssl->connectToHostEncrypted( "gateway.sandbox.push.apple.com",2195 );
    

    Thats all

    Now the encrypted() signal gets emitted! Yeah..