add RTSP digest authentication

This commit is contained in:
m-bene 2014-04-28 23:37:31 +02:00
parent c48fb0124d
commit 3595777d79
9 changed files with 391 additions and 66 deletions

View File

@ -51,6 +51,7 @@ zm_SOURCES = \
zm_rtp_data.cpp \
zm_rtp_source.cpp \
zm_rtsp.cpp \
zm_rtsp_auth.cpp \
zm_sdp.cpp \
zm_signal.cpp \
zm_stream.cpp \

View File

@ -56,21 +56,6 @@
#endif // ZM_MEM_MAPPED
//=============================================================================
std::string trimSpaces(std::string str)
{
// Trim Both leading and trailing spaces
size_t startpos = str.find_first_not_of(" \t"); // Find the first character position after excluding leading blank spaces
size_t endpos = str.find_last_not_of(" \t"); // Find the first character position from reverse af
// if all spaces or empty return an empty string
if(( std::string::npos == startpos ) || ( std::string::npos == endpos))
{
return std::string("");
}
else
return str.substr( startpos, endpos-startpos+1 );
}
std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> elems;
std::stringstream ss(s);

View File

@ -30,6 +30,7 @@
#include "zm_zone.h"
#include "zm_event.h"
#include "zm_camera.h"
#include "zm_utils.h"
#include "zm_image_analyser.h"

View File

@ -39,8 +39,11 @@ RtspThread::PortSet RtspThread::smAssignedPorts;
bool RtspThread::sendCommand( std::string message )
{
if ( !mAuth.empty() )
message += stringtf( "Authorization: Basic %s\r\n", mAuth64.c_str() );
if ( mNeedAuth ) {
StringVector parts = split( message, " " );
if (parts.size() > 1)
message += mAuthenticator->getAuthHeader(parts[0], parts[1]);
}
message += stringtf( "User-Agent: ZoneMinder/%s\r\n", ZM_VERSION );
message += stringtf( "CSeq: %d\r\n\r\n", ++mSeq );
Debug( 2, "Sending RTSP message: %s", message.c_str() );
@ -65,13 +68,41 @@ bool RtspThread::sendCommand( std::string message )
return( true );
}
// find WWW-Authenticate header, send to Authenticator to extract required subfields
void RtspThread::checkAuthResponse(std::string &response)
{
std::string authLine;
StringVector lines = split( response, "\r\n" );
const char* authenticate_match = "WWW-Authenticate:";
size_t authenticate_match_len = strlen(authenticate_match);
for ( size_t i = 0; i < lines.size(); i++ )
{
// stop at end of headers
if (lines[i].length()==0)
break;
if (strncasecmp(lines[i].c_str(),authenticate_match,authenticate_match_len) == 0)
{
authLine = lines[i];
Debug( 2, "Found auth line at %d", i);
break;
}
}
if (!authLine.empty())
{
Debug( 2, "Analyze auth line %s", authLine.c_str());
mAuthenticator->authHandleHeader( trimSpaces(authLine.substr(authenticate_match_len,authLine.length()-authenticate_match_len)) );
}
}
bool RtspThread::recvResponse( std::string &response )
{
if ( mRtspSocket.recv( response ) < 0 )
Error( "Recv failed; %s", strerror(errno) );
Debug( 2, "Received RTSP response: %s (%zd bytes)", response.c_str(), response.size() );
float respVer = 0;
int respCode = -1;
respCode = -1;
char respText[ZM_NETWORK_BUFSIZ];
if ( sscanf( response.c_str(), "RTSP/%f %3d %[^\r\n]\r\n", &respVer, &respCode, respText ) != 3 )
{
@ -87,7 +118,14 @@ bool RtspThread::recvResponse( std::string &response )
}
return( false );
}
if ( respCode != 200 )
if ( respCode == 401)
{
Debug( 2, "Got 401 access denied response code, check WWW-Authenticate header and retry");
checkAuthResponse(response);
mNeedAuth = true;
return( false );
}
else if ( respCode != 200 )
{
Error( "Unexpected response code %d, text is '%s'", respCode, respText );
return( false );
@ -157,14 +195,13 @@ void RtspThread::releasePorts( int port )
smAssignedPorts.erase( port );
}
RtspThread::RtspThread( int id, RtspMethod method, const std::string &protocol, const std::string &host, const std::string &port, const std::string &path, const std::string &auth ) :
RtspThread::RtspThread( int id, RtspMethod method, const std::string &protocol, const std::string &host, const std::string &port, const std::string &path, const std::string &auth) :
mId( id ),
mMethod( method ),
mProtocol( protocol ),
mHost( host ),
mPort( port ),
mPath( path ),
mAuth( auth ),
mFormatContext( 0 ),
mSeq( 0 ),
mSession( 0 ),
@ -189,8 +226,12 @@ RtspThread::RtspThread( int id, RtspMethod method, const std::string &protocol,
if ( mMethod == RTP_RTSP_HTTP )
mHttpSession = stringtf( "%d", rand() );
if ( !mAuth.empty() )
mAuth64 = base64Encode( mAuth );
mNeedAuth = false;
StringVector parts = split(auth,":");
if (parts.size() > 1)
mAuthenticator = new Authenticator(parts[0], parts[1]);
else
mAuthenticator = new Authenticator(parts[0], "");
}
RtspThread::~RtspThread()
@ -214,6 +255,8 @@ int RtspThread::run()
//Debug( 4, "Drained %d bytes from RTSP socket", response.size() );
//}
bool authTried = false;
if ( mMethod == RTP_RTSP_HTTP )
{
if ( !mRtspSocket2.connect( mHost.c_str(), strtol( mPort.c_str(), NULL, 10 ) ) )
@ -226,10 +269,16 @@ int RtspThread::run()
//Debug( 4, "Drained %d bytes from HTTP socket", response.size() );
//}
//possibly retry sending the message for authentication
int respCode = -1;
char respText[256];
do {
message = "GET "+mPath+" HTTP/1.0\r\n";
message += "X-SessionCookie: "+mHttpSession+"\r\n";
if ( !mAuth.empty() )
message += stringtf( "Authorization: Basic %s\r\n", mAuth64.c_str() );
if ( mNeedAuth ) {
message += mAuthenticator->getAuthHeader("GET", mPath);
authTried = true;
}
message += "\r\n";
Debug( 2, "Sending HTTP message: %s", message.c_str() );
if ( mRtspSocket.send( message.c_str(), message.size() ) != (int)message.length() )
@ -245,8 +294,7 @@ int RtspThread::run()
Debug( 2, "Received HTTP response: %s (%zd bytes)", response.c_str(), response.size() );
float respVer = 0;
int respCode = -1;
char respText[256];
respCode = -1;
if ( sscanf( response.c_str(), "HTTP/%f %3d %[^\r\n]\r\n", &respVer, &respCode, respText ) != 3 )
{
if ( isalnum(response[0]) )
@ -261,6 +309,14 @@ int RtspThread::run()
}
return( -1 );
}
// IF Server requests authentication, check WWW-AUthenticate header and fill required fields
// für requested authentication method
if (respCode == 401 && !authTried) {
mNeedAuth = true;
}
} while (respCode == 401 && !authTried);
if ( respCode != 200 )
{
Error( "Unexpected response code %d, text is '%s'", respCode, respText );
@ -269,8 +325,8 @@ int RtspThread::run()
message = "POST "+mPath+" HTTP/1.0\r\n";
message += "X-SessionCookie: "+mHttpSession+"\r\n";
if ( !mAuth.empty() )
message += stringtf( "Authorization: Basic %s\r\n", mAuth64.c_str() );
if ( mNeedAuth )
message += mAuthenticator->getAuthHeader("POST", mPath);
message += "Content-Length: 32767\r\n";
message += "Content-Type: application/x-rtsp-tunnelled\r\n";
message += "\r\n";
@ -290,9 +346,16 @@ int RtspThread::run()
//recvResponse( response );
message = "DESCRIBE "+mUrl+" RTSP/1.0\r\n";
bool res;
do {
if (mNeedAuth)
authTried = true;
sendCommand( message );
sleep( 1 );
recvResponse( response );
res = recvResponse( response );
if (!res && respCode==401)
mNeedAuth = true;
} while (!res && respCode==401 && !authTried);
const std::string endOfHeaders = "\r\n\r\n";
size_t sdpStart = response.find( endOfHeaders );

View File

@ -25,6 +25,7 @@
#include "zm_comms.h"
#include "zm_thread.h"
#include "zm_rtp_source.h"
#include "zm_rtsp_auth.h"
#include <set>
#include <map>
@ -54,8 +55,16 @@ private:
std::string mPort;
std::string mPath;
std::string mUrl;
std::string mAuth;
std::string mAuth64;
// Reworked authentication system
// First try without authentication, even if we have a username and password
// on receiving a 401 response, select authentication method (basic or digest)
// fill required fields and set needAuth
// subsequent requests can set the required authentication header.
bool mNeedAuth;
int respCode;
Authenticator* mAuthenticator;
std::string mHttpSession; ///< Only for RTSP over HTTP sessions
@ -81,9 +90,10 @@ private:
private:
bool sendCommand( std::string message );
bool recvResponse( std::string &response );
void checkAuthResponse(std::string &response);
public:
RtspThread( int id, RtspMethod method, const std::string &protocol, const std::string &host, const std::string &port, const std::string &path, const std::string &auth );
RtspThread( int id, RtspMethod method, const std::string &protocol, const std::string &host, const std::string &port, const std::string &path, const std::string &auth);
~RtspThread();
public:

182
src/zm_rtsp_auth.cpp Normal file
View File

@ -0,0 +1,182 @@
//
// ZoneMinder RTSP Authentication Class Implementation, $Date$, $Revision$
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "zm.h"
#include "zm_utils.h"
#include "zm_rtsp_auth.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Authenticator::Authenticator(std::string &username, std::string password) {
#ifdef HAVE_GCRYPT_H
// Special initialisation for libgcrypt
if ( !gcry_check_version( GCRYPT_VERSION ) )
{
Fatal( "Unable to initialise libgcrypt" );
}
gcry_control( GCRYCTL_DISABLE_SECMEM, 0 );
gcry_control( GCRYCTL_INITIALIZATION_FINISHED, 0 );
#endif // HAVE_GCRYPT_H
fAuthMethod = AUTH_UNDEFINED;
fUsername = username;
fPassword = password;
}
Authenticator::~Authenticator() {
reset();
}
void Authenticator::reset() {
fRealm.clear();
fNonce.clear();
fUsername.clear();
fPassword.clear();
fAuthMethod = AUTH_UNDEFINED;
}
void Authenticator::setRealmAndNonce(std::string &realm, std::string &nonce) {
fRealm = realm;
fNonce = nonce;
}
void Authenticator::authHandleHeader(std::string headerData)
{
const char* basic_match = "Basic ";
const char* digest_match = "Digest ";
size_t digest_match_len = strlen(digest_match);
// Check if basic auth
if (strncasecmp(headerData.c_str(),basic_match,strlen(basic_match)) == 0)
{
fAuthMethod = AUTH_BASIC;
Debug( 2, "Set authMethod to Basic");
}
// Check if digest auth
else if (strncasecmp( headerData.c_str(),digest_match,digest_match_len ) == 0)
{
fAuthMethod = AUTH_DIGEST;
Debug( 2, "Set authMethod to Digest");
StringVector subparts = split(headerData.substr(digest_match_len, headerData.length() - digest_match_len), ",");
// subparts are key="value"
for ( size_t i = 0; i < subparts.size(); i++ )
{
StringVector kvPair = split( trimSpaces( subparts[i] ), "=" );
std::string key = trimSpaces( kvPair[0] );
if (key == "realm") {
fRealm = trimSet( kvPair[1], "\"");
continue;
}
if (key == "nonce") {
fNonce = trimSet( kvPair[1], "\"");
continue;
}
}
Debug( 2, "Auth data completed. User: %s, realm: %s, nonce: %s", username().c_str(), fRealm.c_str(), fNonce.c_str());
}
}
std::string Authenticator::getAuthHeader(std::string method, std::string uri)
{
std::string result = "Authorization: ";
if (fAuthMethod == AUTH_BASIC)
{
result += "Basic " + base64Encode( username() + ":" + password() );
}
else if (fAuthMethod == AUTH_DIGEST)
{
result += std::string("Digest ") +
"username=\"" + username() + "\", realm=\"" + realm() + "\", " +
"nonce=\"" + nonce() + "\", uri=\"" + uri + "\", " +
"response=\"" + computeDigestResponse(method, uri) + "\"";
//Authorization: Digest username="zm",
// realm="NC-336PW-HD-1080P",
// nonce="de8859d97609a6fcc16eaba490dcfd80",
// uri="rtsp://10.192.16.8:554/live/0/h264.sdp",
// response="4092120557d3099a163bd51a0d59744d",
// algorithm=MD5,
// opaque="5ccc069c403ebaf9f0171e9517f40e41",
// qop="auth",
// cnonce="c8051140765877dc",
// nc=00000001
}
result += "\r\n";
return result;
}
std::string Authenticator::computeDigestResponse(std::string &method, std::string &uri) {
#if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT
// The "response" field is computed as:
// md5(md5(<username>:<realm>:<password>):<nonce>:md5(<cmd>:<url>))
size_t md5len = 16;
unsigned char md5buf[md5len];
char md5HexBuf[md5len*2+1];
// Step 1: md5(<username>:<realm>:<password>)
std::string ha1Data = username() + ":" + realm() + ":" + password();
#if HAVE_DECL_MD5
MD5((unsigned char*)ha1Data.c_str(), ha1Data.length(), md5buf);
#elif HAVE_DECL_GNUTLS_FINGERPRINT
gnutls_datum_t md5dataha1 = { (unsigned char*)ha1Data.c_str(), ha1Data.length() };
gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha1, md5buf, &md5len );
#endif
for ( unsigned int j = 0; j < md5len; j++ )
{
sprintf(&md5HexBuf[2*j], "%02x", md5buf[j] );
}
md5HexBuf[md5len*2]='\0';
std::string ha1Hash = md5HexBuf;
// Step 2: md5(<cmd>:<url>)
std::string ha2Data = method + ":" + uri;
#if HAVE_DECL_MD5
MD5((unsigned char*)ha2Data.c_str(), ha2Data.length(), md5buf );
#elif HAVE_DECL_GNUTLS_FINGERPRINT
gnutls_datum_t md5dataha2 = { (unsigned char*)ha2Data.c_str(), ha2Data.length() };
gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha2, md5buf, &md5len );
#endif
for ( unsigned int j = 0; j < md5len; j++ )
{
sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] );
}
md5HexBuf[md5len*2]='\0';
std::string ha2Hash = md5HexBuf;
// Step 3: md5(ha1:<nonce>:ha2)
std::string digestData = ha1Hash + ":" + nonce() + ":" + ha2Hash;
#if HAVE_DECL_MD5
MD5((unsigned char*)digestData.c_str(), digestData.length(), md5buf);
#elif HAVE_DECL_GNUTLS_FINGERPRINT
gnutls_datum_t md5datadigest = { (unsigned char*)digestData.c_str(), digestData.length() };
gnutls_fingerprint( GNUTLS_DIG_MD5, &md5datadigest, md5buf, &md5len );
#endif
for ( unsigned int j = 0; j < md5len; j++ )
{
sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] );
}
md5HexBuf[md5len*2]='\0';
return md5HexBuf;
#else // HAVE_DECL_MD5
Error( "You need to build with gnutls or openssl installed to use digest authentication" );
#endif // HAVE_DECL_MD5
return( 0 );
}

61
src/zm_rtsp_auth.h Normal file
View File

@ -0,0 +1,61 @@
//
// ZoneMinder RTSP Authentication Class Interface, $Date$, $Revision$
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef ZM_RTSP_AUTH_H
#define ZM_RTSP_AUTH_H
#if HAVE_GNUTLS_OPENSSL_H
#include <gnutls/openssl.h>
#endif
#if HAVE_GNUTLS_GNUTLS_H
#include <gnutls/gnutls.h>
#endif
#if HAVE_GCRYPT_H
#include <gcrypt.h>
#elif HAVE_LIBCRYPTO
#include <openssl/md5.h>
#endif // HAVE_GCRYPT_H || HAVE_LIBCRYPTO
class Authenticator
{
public:
typedef enum { AUTH_UNDEFINED, AUTH_BASIC, AUTH_DIGEST } RtspAuthMethod;
Authenticator(std::string &username, std::string password);
virtual ~Authenticator();
void reset();
void setRealmAndNonce(std::string &realm, std::string &nonce);
std::string realm() { return fRealm; }
std::string nonce() { return fNonce; }
std::string username() { return fUsername; }
std::string computeDigestResponse( std::string &cmd, std::string &url );
void authHandleHeader( std::string headerData );
std::string getAuthHeader( std::string method, std::string path );
private:
std::string password() { return fPassword; }
RtspAuthMethod fAuthMethod;
std::string fRealm;
std::string fNonce;
std::string fUsername;
std::string fPassword;
};
#endif // ZM_RTSP_AUTH_H

View File

@ -27,6 +27,25 @@
unsigned int sseversion = 0;
std::string trimSet(std::string str, std::string trimset) {
// Trim Both leading and trailing sets
size_t startpos = str.find_first_not_of(trimset); // Find the first character position after excluding leading blank spaces
size_t endpos = str.find_last_not_of(trimset); // Find the first character position from reverse af
// if all spaces or empty return an empty string
if(( std::string::npos == startpos ) || ( std::string::npos == endpos))
{
return std::string("");
}
else
return str.substr( startpos, endpos-startpos+1 );
}
std::string trimSpaces(std::string str)
{
return trimSet(str, " \t");
}
const std::string stringtf( const char *format, ... )
{
va_list ap;

View File

@ -27,6 +27,9 @@
typedef std::vector<std::string> StringVector;
std::string trimSpaces(std::string str);
std::string trimSet(std::string str, std::string trimset);
const std::string stringtf( const char *format, ... );
const std::string stringtf( const std::string &format, ... );