I am in the process of writing a new Betfair bot in C++ using Qt but I am falling at the first hurdle with the login process. After a lot of reading around I decided to use QNetworkAccessManager
and this is what I currently have:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
BetfairManager bet_man;
QNetworkAccessManager *manager = new QNetworkAccessManager();
QObject::connect(manager, SIGNAL(finished(QNetworkReply*)),
&bet_man,SLOT(replyFinished(QNetworkReply*)));
QObject::connect(manager,SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)),
&bet_man,SLOT(replySSLErrors(QNetworkReply*,QList<QSslError>)));
QObject::connect(manager,SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
&bet_man,SLOT(replyAuthenticationRequired(QNetworkReply*,QAuthenticator*)));
QObject::connect(manager,SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&,QAuthenticator*)),
&bet_man,SLOT(replyProxyAuthenticationRequired(const QNetworkProxy&,QAuthenticator*)));
QNetworkRequest request;
QByteArray payload("username=myusername&password=mypassword");
QString url("https://identitysso.betfair.com/api/certlogin");
QSslConfiguration config;
QString pemfile("../../client-2048.pem");
QFile file(pemfile);
if(!file.exists()) {
qWarning("Filename %s doesn't exists.", qPrintable(pemfile));
}
if(!file.open(QIODevice::ReadOnly)) {
qWarning("Cannot open filename %s.", qPrintable(pemfile));
}
QByteArray data = file.readAll();
QSslCertificate sslcert(data, QSsl::Pem);
if(sslcert.isNull()) {
qWarning("The certificate has no content.");
}
config.setLocalCertificate(sslcert);
request.setSslConfiguration(config);
request.setUrl(url);
request.setRawHeader("X-Application","mykey");
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
manager->post(request,payload);
return a.exec();
}
The BetfairManager
class is just a dummy class that I have created to have slots for the QNetworkAccessManager
signals - they just dump some debug info to the screen to see what signals have been emitted.
When I run the above, I see that the QNetworkAccessManager::finished()
signal has fired, but I have an empty response - there are not even any HTTP headers.
When I stop setting the certificate by commenting out the line
request.setSslConfiguration(config);
I get the response
"{"loginStatus":"CERT_AUTH_REQUIRED"}"
which is unsurprising but shows that I am passing the correct parameters in my request, so I am convinced that the problem lies with my certificate passing mechanism/creation with the Qt classes.
I have created the .pem file according to the betfair documentation (which says to create it from appending the generated .crt and the .key files) and I know they are fine as I also have a Python bot that works perfectly using the same files.
I am new to Qt and not a SSL expert and I get the impression that I am missing something fundamental here.
In case it is useful here is my Python equivalent code which works perfectly using urllib2
package
un = "myusername";
pw = "mypassword";
app_key = 'mykey'
payload = 'username=' + un + '&password=' + pw
login_headers = {'X-Application': app_key, 'Content-Type': 'application/x-www-form-urlencoded'}
resp = requests.post('https://identitysso.betfair.com/api/certlogin', data=payload, cert=('client-2048.crt', 'client-2048.key'), headers=login_headers)
I am developing this with Qt 5.2.1 on Ubuntu 14.04
When you want to login over an SSL connection using a client certificate, you need to additionally specify the client's private key. A certificate is basically only a public key, that someone else checked. The access is granted when you can proof to the server, that you are also having the private key to the certificate.
You can do that using the following methods:
// get certData and keyData from files
QSslConfiguration config;
QSslCertificate sslcert(certData, QSsl::Der);
config.setLocalCertificate(sslcert);
QSslKey privkey(keyData, QSsl::Rsa, QSsl::Der, QSsl::PrivateKey, "myKeyPassword")
config.​setPrivateKey(privkey);
When both of your original files client-2048.crt
and client-2048.key
are binary, it is likely that they are stored in the DER format. In this case you can specify the input format as above and don't need to convert anything.