priore / SOAPEngine

This generic SOAP client allows you to access web services using a your iOS app, Mac OS X app and AppleTV app.
http://www.prioregroup.com
Other
482 stars 75 forks source link

Digest authent #180

Closed HEYGUL closed 6 years ago

HEYGUL commented 6 years ago

Hi,

I am trying to use SOAPEngine (entreprise version) to connect to an Axis IP Camera requiring Digest Authent.

Thus, I did try the following:

    let soap = SOAPEngine()
    soap.licenseKey = soapEngineLicenseKey
    soap.version = SOAPVersion.VERSION_1_2
    soap.authorizationMethod = SOAPAuthorization.AUTH_WSSECURITY
    soap.username = credential.login
    soap.password = credential.password
    soap.responseHeader = true

    let soapRequestURL = "http://" + ipAddress + pathFor(request: request)

    soap.requestURL(soapRequestURL,
                    soapAction: request.soapAction)

But I always get a 400 - Bad Request error

Any clue on how to make it work ?

priore commented 6 years ago

Hi, reading around, trying with google, about the 400 error for this Axis cam's: "The problem was that the time on the camera was not correct. So, this caused the authentication to fail."

other links: https://github.com/quatanium/python-onvif/issues/19

but you can ask Axis support by also sending the xml generated by the framework, printing the contents of the data, converting them in string, through the delegate didBeforeSendingURLRequest, because we do not have an Axis cam and we can not help you better.

Sorry.

HEYGUL commented 6 years ago

Thank you for your quick reply. I can provide you by email with an accessible and working Axis cam if you want to test. I contacted Axis support and they provided me with working payloads.

<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
   <s:Header>
      <a:Action s:mustUnderstand="1">http://www.onvif.org/ver10/device/wsdl/GetServices</a:Action>
      <a:MessageID>urn:uuid:e1e32ae0-afc7-4d68-b935-470f6f5bdb05</a:MessageID>
      <a:ReplyTo>
         <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
      </a:ReplyTo>
      <a:To s:mustUnderstand="1">http://ip_address/onvif/device_service</a:To>
   </s:Header>
   <s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <GetServices xmlns="http://www.onvif.org/ver10/device/wsdl">
         <IncludeCapability>false</IncludeCapability>
      </GetServices>
   </s:Body>
</s:Envelope>

(I removed the actual ip address of the camera.)

When I log what SoapEngine is generating, I see wwse login and password, which is what cause the '401' error. It seems the framework does not properly manage digest authentication at least not as required in the ONVIF documentation.

Here is what I log:

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <soap:Header>
      <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
         <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Id-3948BDC3A9A844C4AF9F1F9C27B1C527">
            <wsse:Username>operator</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">nFFSfTJVP4FaQIHzrZuwgTNzYrM=</wsse:Password>
            <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">QUY5RjFGOUMyN0IxQzUyNw==</wsse:Nonce>
            <wsu:Created>2018-04-03T11:12:35.148Z</wsu:Created>
         </wsse:UsernameToken>
      </wsse:Security>
   </soap:Header>
   <soap:Body>
      <GetServices xmlns="http://www.onvif.org/ver10/device/wsdl">
         <IncludeCapability>false</IncludeCapability>
      </GetServices>
   </soap:Body>
</soap:Envelope>
priore commented 6 years ago

to create the header equal to the xml provided by the Axis support :


soap.version = SOAPVersion.VERSION_1_2
//soap.authorizationMethod = SOAPAuthorization.AUTH_WSSECURITY
//soap.username = credential.login
//soap.password = credential.password
soap.responseHeader = true

soap.envelope = "xmlns:a=\"http://www.w3.org/2005/08/addressing\""
soap.authorizationMethod = SOAPAuthorization.AUTH_CUSTOM

let attr =  ["mustUnderstand" : "1"]
var header: [String: Any] = ["a:Action": ["value": "http://www.onvif.org/ver10/device/wsdl/GetServices", "attributes": attr]]
header["a:MessageID"] = ["a:Address" : "urn:uuid:e1e32ae0-afc7-4d68-b935-470f6f5bdb05"]
header["a:ReplyTo"] = ["a:Address" : "http://www.w3.org/2005/08/addressing/anonymous"]
header["a:To"] = ["value": "http://ip_address/onvif/device_service", "attributes": attr]
soap.header = header
HEYGUL commented 6 years ago

Thank you, it worked as expected and I now receive a '401' response. I then need to extract Digest Realm and Nonce from the response headers and use it in next requests. It seems the Digest as per implemented in SOAPEngine is not the same as Digest as per implemented in the Axis camera. Am I wrong ?

priore commented 6 years ago

Digest is a standard, for all types of communications, perhaps Asis makes other checks that require a custom digest ?

The our digest is :

date = current date (UTC)
nonce = last 16 dgt from CFUUIDCreate api
s = nonce + date + password (like: B3DA04C6F1BB6DC82018-04-03T13:19:05.624Zmypassword)
digest = BASE64(SHA1(s))

you could ask to Axis which digest format used and we extend our framework for you.

HEYGUL commented 6 years ago

I did not have answer from Axis yet, but digging deeper in Digest authentication let met think that there may be a confusion between Digest Password (as implemented in SOAPEngine) and Digest Authentication (as implemented in Axis Camera). What let me think is this wikipedia page

As you can see, the Digest realm, nonce and opaque values are extracted from WWW-Authenticate header sent by server in 401 - Unauthorized response.

Do you confirm my guess ?

Edit: Just saw this comment. This is exactly what it is needed to connect to Axis camera. I thus tried what you said it the thread (AuthNone but providing username and password) but I still get 400 - Bad Request error.

Any idea ?

priore commented 6 years ago

reading some documents for Axis maybe we understood that it does not need a digest authorization in the soap message, but the digest authorization must be in the header of the request, like below :

Authorization: Digest
           username="<username>",
           realm="<realm>",
           nonce="<nonce>", 
           uri="<requested-uri>", 
           response="<digest>"

we can implement this type of authorization for you, but we need a link to do the test, you can send a link and access data to our support email?

if you prefer to do it yourself, you can use the delegate didBeforeSendingURLRequest to modify the request before it is sent.

The digest is calculate like this :

H( H(A1) + ":" + N + ":" + H(A2) )

where: 

    A1 = U + ':' + R + ':' + P
    A2 = M + ':' + URI

    N -- nonce value
    U -- username
    R -- realm
    P -- password
    M -- GET or POST
    URI -- request uri
    H -- hash algorithm (maybe MD5)

thanks

HEYGUL commented 6 years ago

Thank you very much for your quick reply. You are right, the digest authorization must be in header exactly as you wrote it. I send you a link to do the test.

priore commented 6 years ago

Hi, now we release a new version that supports Digest authentication in the header of requests.

        soap.version = .VERSION_1_2
        soap.authorizationMethod = .AUTH_DIGEST
        soap.username = "your-username"
        soap.password = "your-password"
        soap.realm = "realm-server"
        soap.responseHeader = true
        soap.actionNamespaceSlash = false
        soap.envelope = "xmlns:wsdl=\"http://www.onvif.org/ver10/device/wsdl\""
        soap.requestURL("http://IP-cam/onvif/device_service",
                        soapAction: "wsdl:GetDeviceInformation",
                        completeWithDictionary: { (statusCode, dict) in
                            print(statusCode)
                            print(dict)
        }) { (error) in
            print(error)
        }
HEYGUL commented 6 years ago

Thank you very much. I will give it a try. One question looking your code sample. Why should I specify the realm? This one is received from server when trying to authenticate.

priore commented 6 years ago

realm is a code that identifies your server, is necessary for the generation of the digest, and you can recover it from any response of server, like CURL, for you it should be AXIS_WS_ACCC8E90705A.

HEYGUL commented 6 years ago

Many thanks for this release. It is working nice with Axis and almost every other camera. I still have one camera model for which it it not working. I will analyze further and let you know soon.

Edit: I have issue connecting to Hikvision model.

As said by email, the following error shows up in Xcode console: In the Xcode console I have following error:

CredStore - performQuery - Error copying matching creds.  Error=-25300, query={
    class = inet;
    "m_Limit" = "m_LimitAll";
    ptcl = http;
    "r_Attributes" = 1;
    srvr = "123.157.208.28";
    sync = syna;
}
priore commented 6 years ago

Hello, we have tried and it works properly.

Body =     {
        GetDeviceInformationResponse =         {
            FirmwareVersion = "V5.5.11 build 180403";
            HardwareId = 88;
            Manufacturer = HIKVISION;
            Model = "DS-2DE2204IW-DE3/W";
            SerialNumber = "DS-2DE2204IW-DE3/W20160611CCCH611488922W";
        };
    };
}

But you need to set the right parameters.

To retrieve the realm value to use, try to make a curl call, the server will respond with an error but also with the realm value to use for your calls, so in your case use :

realm = IP Camera(61148)

and for the requestURL use the IP-cam with port number like IP-cam:port-num

(check that your network allows the use of port 81, for example in our offices are allowed only port 80 for telecommunications, all other ports can not be used).

HEYGUL commented 6 years ago

Thank you for your time and your great answers. Everything is working like a charm, thus I am closing the issue.

Long life to SOAPEngine !