Я пишу небольшое клиентское приложение, которое будет опрашивать IP-камеру по протоколу ONVIF, но если возникнет проблема.
Нужно использовать только C ++, без использования инструментов gSOAP.
Я использую примеры по этой ссылке Руководство для программистов Onvif
Камеры, которые не защищены паролем и поэтому не требуют генерации данных для авторизации, отвечают правильно.
Проблема, с которой я столкнулся, - это формирование данных для авторизации.
При отправке любого запроса на камеру (например, GetProfiles) камера всегда отвечает 400 ошибками.
Вот пример запроса:
POST /onvif/Media_service HTTP/1.1
Connection: keep-alive
Content-Length: 948
Content-Type: application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/media/wsdl/GetProfiles"
Host: 192.168.10.205
User-Agent: gSOAP/2.8
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<UsernameToken>
<Username>admin</Username>
<Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">M2IzYWYyOGE5OWE3ZDk3NGYzMGU1MzlkZWVhMDYyODMxMmU3NDIxMA==</Password>
<Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">MTIzNDU2Nzg5</Nonce>
<Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2019-03-16T10:43:18Z</Created>
</UsernameToken>
</Security>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<GetProfiles xmlns="http://www.onvif.org/ver10/media/wsdl"/>
</s:Body>
</s:Envelope>
Вот пример ответа камеры:
HTTP/1.1 400 Bad Request
Server: gSOAP/2.8
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 1212
Connection: close
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope > // I skipped the listing of the namespace
<SOAP-ENV:Header>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<SOAP-ENV:Code>
<SOAP-ENV:Value>SOAP-ENV:Sender</SOAP-ENV:Value>
<SOAP-ENV:Subcode>
<SOAP-ENV:Value>ter:NotAuthorized</SOAP-ENV:Value>
</SOAP-ENV:Subcode>
</SOAP-ENV:Code>
<SOAP-ENV:Reason>
<SOAP-ENV:Text xml:lang="en">Sender not Authorized</SOAP-ENV:Text>
</SOAP-ENV:Reason>
<SOAP-ENV:Detail>The action requested requires authorization and the sender is not authorized.</SOAP-ENV:Detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
В соответствии с пунктом "6.1.1.3" из документации, приведенной выше, необходимо создать "Дайджест" следующим образом:
Дайджест = B64ENCODE (SHA1 (B64DECODE (Nonce) + Дата + Пароль))
В моем коде функции B64DECODE, SHA1 и B64ENCODE работают правильно, что проверено сравнением с различными онлайн-генераторами.
Я пытался установить параметр Nonce по-другому. Пока что все безрезультатно.
Как я догадываюсь, ошибка заключается именно в работе с параметром, который я посылаю в функцию SHA1 (), но я могу ошибаться.
Кто-нибудь может сказать, что может быть причиной ошибки?
Я выкладываю не весь код, а только ту часть, которая связана с формированием необходимых данных.
Буду признателен за любую помощь.
КОД:
// hardcoded nonce
char caNonceTest[20] = { 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38 };
caNonceTest[19] = '\0';
// Random generaterd nonce
//char caNonceTest[SHA_DIGEST_LENGTH];
//int nonceInt = rand();
//memset(caNonceTest, 1, SHA_DIGEST_LENGTH);
//memcpy(caNonceTest, &nonceInt, sizeof(nonceInt));
// Get data/time
string sDataUTC = currentISO8601TimeUTC();
// non-crypted password. Make function to get password from user
string sPass = "admin";
string sName = "default";
string sNonce(caNonceTest);
// temp string for SHA1
string sTempSumBin = (sNonce) + /*string2bin*/(sDataUTC)+ /*string2bin*/(sPass); // with string2bin it is also not working
unsigned char digest[SHA_DIGEST_LENGTH];
char cstr[sTempSumBin.size() + 1];
strcpy(cstr, sTempSumBin.c_str());
SHA1((unsigned char*)&cstr, strlen(cstr), (unsigned char*)&digest);
char cSHA1Pass[SHA_DIGEST_LENGTH * 2 + 1];
for (int i = 0; i < SHA_DIGEST_LENGTH; i++)
sprintf(&cSHA1Pass[i * 2], "%02x", (unsigned int)digest[i]);
printf("SHA1 string before : %s \n", cSHA1Pass);
string strTob64(cSHA1Pass);
string strFinishedPAss = base64_encode(strTOb64); // this is the final result of the password