Tereius / libONVIF

Yet another ONVIF library
GNU General Public License v3.0
164 stars 47 forks source link

Authentication #6

Closed fiz1962 closed 3 years ago

fiz1962 commented 3 years ago

I can get Discovery to work. I can get GetSystemDateandTime to work. But anything requiring authentication fails. It seems to insist on using Basic authentication which my cameras do not support. If I turn authentication off in the cameras, this code works. When I snoop the traffic with wireshark, I do not see any soap headers.

Tereius commented 3 years ago

As far as I can see the ONVIF standard provides only two authentication methods: Digest Access Authentication and WS-UsernameToken (legacy devices only). Both hopefully work in libONVIF (Digest Access Authentication only works if you link libONVIF against OpenSSL). If your camera doesn't support one of these authentication methods it is not ONVIF compliant and you have to implement the authentication mechanism yourself in libONVIF. Anyways, please check if your camera supports Digest Access Authentication and that you linked against OpenSSL. If both are given and the request still fails then it could be a bug in libONVIF. Make sure to set the correct credentials in Client.h:66 and force a specific auth. method like HTTP_DIGEST instead of AUTO.

fiz1962 commented 3 years ago

Thanks for the reply. This is what I can say. And I do suspect it is some non-conformity in the cameras ONVIF implentation.

I am linking with openssl.

I have written code with other ONVIF libs that do work, until Qt6.0 dropped xmlpatterns. I have 2 cameras of different models, but from the same manufacturer. To get them to work with my previous code, I had to use the username token (user,nonce,date and hash) in the ONVIF soap/xml "header". They do not work if passing the user/passwd as plain text in the xml header. When I sniff the traffic with wireshark using the libONVIF code I only see the soap/xml Envelope and Body sections. I do not see the "Header" section. I do see "Authentication:Basic " in the http header.

For some reason the soap "header" section is not being generated. I will continue to track this down. So far it may have something to do with either authrealm or ctx not being set.

fiz1962 commented 3 years ago

Hi, finally got some time to look into this. The main problem was in

define CheckIfWsTokenAuthFault(pSoap) \

(pSoap->error == SOAP_CLI_FAULT && QString::compare(QString::fromLocal8Bit(*soap_faultsubcode(pSoap)), \
                                                    QString("\"http://www.onvif.org/ver10/error\":Not Aauthorized)")) != 0)

To get it to work with my cameras (3 models from 2 manufacturers), I had to change SOAP_CLI_FAULT to SOAP_FAULT and 'Not Authorized' to 'Unauthorized'.

Then a 2nd problem was void Client::RestoreAuth(soap pCtx) was not called after a 2nd pass of bool Client::ProcessAuthFaultAndRetry(soap pCtx) . Adding RestoreAuth() in ProcessAuthFaultAndRetry got it working.

Tereius commented 3 years ago

Hi, thanks for investigating. I'm not sure if the soap error response "Unauthorized" is compliant with ONVIF specs page 32. Can you please check if the following patch works.

diff --git a/src/Client.cpp b/src/Client.cpp
index 8af23a7..c8a48dc 100644
--- a/src/Client.cpp
+++ b/src/Client.cpp
@@ -24,9 +24,12 @@

 #define CheckIfDigestAuthFault(pSoap) (pSoap->error == HTTP_UNAUTHORIZED)
-#define CheckIfWsTokenAuthFault(pSoap)                                                                  \
-       (pSoap->error == SOAP_CLI_FAULT && QString::compare(QString::fromLocal8Bit(*soap_faultsubcode(pSoap)), \
-                                                           QString("\"http://www.onvif.org/ver10/error\":NotAuthorized)")) != 0)
+#define CheckIfWsTokenAuthFault(pSoap)                                                                                                     \
+       ((pSoap->error == SOAP_CLI_FAULT || pSoap->error == SOAP_FAULT) &&                                                                        \
+        ((QString::compare(QString::fromLocal8Bit(*soap_faultsubcode(pSoap)), QString("\"http://www.onvif.org/ver10/error\":NotAuthorized)")) != \
+          0) ||                                                                                                                                  \
+         (QString::compare(QString::fromLocal8Bit(*soap_faultsubcode(pSoap)), QString("\"http://www.onvif.org/ver10/error\":Unauthorized)")) !=  \
+          0)))
 #define CheckIfAuthFault(pSoap) (CheckIfWsTokenAuthFault(pSoap) || CheckIfDigestAuthFault(pSoap))

 struct ClientPrivate {
@@ -58,8 +61,7 @@ struct ClientPrivate {
 };

 Client::Client(const QUrl &rEndpoint, QSharedPointer<SoapCtx> sharedCtx, QObject *pParent) :
- QObject(pParent),
- mpD(new ClientPrivate(this, rEndpoint, sharedCtx)) {}
+ QObject(pParent), mpD(new ClientPrivate(this, rEndpoint, sharedCtx)) {}

 Client::~Client() {

@@ -205,5 +207,7 @@ QSharedPointer<SoapCtx> Client::GetCtx() const {

 int Client::Retry(soap *pCtx) {

-       return ProcessAuthFaultAndRetry(pCtx);
+       auto retry = ProcessAuthFaultAndRetry(pCtx);
+       if(retry) RestoreAuth(pCtx);
+       return retry;
 }
fiz1962 commented 3 years ago

I don't know if the response is compliant either, but I get it from 3 different model cameras from 2 manufacturers (1 IdeaNext ~6 yrs old and 2 Wansview ~2-3 yrs old) . Interestingly, the web interface for both manufacturers is very similar so I suspect the code for both manufacturers may be similar.

And after some brief testing, your patch worked :) Thanks very much.

If you are interested, I forked your code and

Tereius commented 3 years ago

Hi, I have revised the authorization handling and added WSS token digest. Now you can override the default authorization handler by writing your own SoapAuthHandler. Furthermore libONVIF now compiles with Qt6. Currently the code changes are on the dev branch.