00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00036 #include "OW_config.h"
00037 #include "OW_DigestAuthentication.hpp"
00038 #include "OW_AuthenticationException.hpp"
00039 #include "OW_Array.hpp"
00040 #include "OW_Map.hpp"
00041 #include "OW_HTTPUtils.hpp"
00042 #include "OW_DateTime.hpp"
00043 #include "OW_Format.hpp"
00044 #include "OW_MD5.hpp"
00045 #include "OW_HTTPSvrConnection.hpp"
00046 #include "OW_ConfigOpts.hpp"
00047 #include "OW_CryptographicRandomNumber.hpp"
00048 #include <fstream>
00049
00050 namespace OW_NAMESPACE
00051 {
00052
00053 using std::endl;
00054 using std::ifstream;
00056 DigestAuthentication::DigestAuthentication(const String& passwdFile)
00057 : m_asNonces()
00058 , m_aDateTimes()
00059 , m_passwdMap()
00060 {
00061 if (passwdFile.empty())
00062 {
00063 OW_THROW(AuthenticationException, "No password file given for "
00064 "digest authentication.");
00065 }
00066 ifstream infile(passwdFile.c_str());
00067 if (!infile)
00068 {
00069 OW_THROW(AuthenticationException, Format("Unable to open password file %1",
00070 passwdFile).c_str());
00071 }
00072 while (infile)
00073 {
00074 String line;
00075 line = String::getLine(infile);
00076 size_t idx = line.lastIndexOf(':');
00077 m_passwdMap[line.substring(0, idx)] = line.substring(idx + 1);
00078 }
00079 }
00081
00082 String
00083 DigestAuthentication::getHash( const String &sUserName,
00084 const String &sRealm )
00085 {
00086 String key = sUserName + ":" + sRealm;
00087 return m_passwdMap[key];
00088 }
00090 String
00091 DigestAuthentication::generateNewNonce( void )
00092 {
00093 DateTime DateTime;
00094 DateTime.setToCurrent();
00095 String sDateTime( static_cast<Int64>(DateTime.get()) );
00096 String sPrivateData;
00097 CryptographicRandomNumber rn(0, 0x7FFFFFFF);
00098 sPrivateData.format( "%08x", rn.getNextNumber() );
00099
00100 for (size_t i = 0; i < 4; ++i)
00101 {
00102 String randomData;
00103 randomData.format("%08x", rn.getNextNumber());
00104 sPrivateData += randomData;
00105 }
00106
00107 MD5 md5;
00108 md5.update(sDateTime);
00109 md5.update(":");
00110 md5.update("ETag");
00111 md5.update(":");
00112 md5.update(sPrivateData);
00113 String sNonce = md5.toString();
00114 sNonce = sDateTime + sNonce;
00115 sNonce = HTTPUtils::base64Encode( sNonce );
00116 for ( Int16 iNonce=m_asNonces.size()-1; iNonce>=0; iNonce-- )
00117 {
00118 if ( m_aDateTimes[ iNonce ] < (DateTime.get()-60) )
00119 {
00120 m_asNonces.remove( 0, iNonce+1 );
00121 m_aDateTimes.remove( 0, iNonce+1 );
00122 break;
00123 }
00124 }
00125 m_asNonces.append( sNonce );
00126 m_aDateTimes.append( DateTime.get() );
00127 return sNonce;
00128 }
00130 static void
00131 parseInfo(const String& pinfo, Map<String, String>& infoMap)
00132 {
00133 size_t idx = pinfo.indexOf("Digest");
00134 String info;
00135 if (idx != String::npos)
00136 {
00137 info = pinfo.substring(7);
00138 }
00139 else
00140 {
00141 OW_THROW(AuthenticationException, "Error parsing Digest Response");
00142 }
00143 Array<String> infoAr = info.tokenize(",");
00144 for (size_t i = 0; i < infoAr.size(); ++i)
00145 {
00146 String lhs, rhs;
00147 idx = infoAr[i].indexOf('=');
00148 if (idx != String::npos)
00149 {
00150 lhs = infoAr[i].substring(0, idx);
00151 lhs.trim();
00152 if (idx + 1 < infoAr[i].length())
00153 {
00154 rhs = infoAr[i].substring(idx + 1);
00155 rhs.trim();
00156 if (rhs[0] == '\"')
00157 {
00158 rhs = rhs.substring(1);
00159 rhs = rhs.substring(0, rhs.length() - 1);
00160 }
00161 infoMap[lhs] = rhs;
00162 continue;
00163 }
00164 }
00165 OW_THROW(AuthenticationException, "Error parsing Digest Response");
00166 }
00167 }
00169 bool
00170 DigestAuthentication::authenticate(String& userName,
00171 const String& info, HTTPSvrConnection* htcon)
00172 {
00173 String hostname = htcon->getHostName();
00174 if (info.empty())
00175 {
00176 htcon->setErrorDetails("You must authenticate to access this resource");
00177 htcon->addHeader("WWW-Authenticate", getChallenge(hostname));
00178 return false;
00179 }
00180 Map<String, String> infoMap;
00181 parseInfo(info, infoMap);
00182 String sNonce = infoMap["nonce"];
00183 bool nonceFound = false;
00184 if ( !sNonce.empty())
00185 {
00186 for (size_t iNonce = 0; iNonce < m_asNonces.size(); ++iNonce)
00187 {
00188 if ( sNonce == m_asNonces[ iNonce ] )
00189 {
00190 m_asNonces.remove( iNonce );
00191 m_aDateTimes.remove( iNonce );
00192 nonceFound = true;
00193 break;
00194 }
00195 }
00196 }
00197 else
00198 {
00199 htcon->setErrorDetails("No nonce value was provided");
00200 htcon->addHeader("WWW-Authenticate", getChallenge(hostname));
00201 return false;
00202 }
00203 userName = infoMap["username"];
00204 if ( userName.empty() )
00205 {
00206 htcon->setErrorDetails("No user name was provided");
00207 htcon->addHeader("WWW-Authenticate", getChallenge(hostname));
00208 return false;
00209 }
00210 String sRealm = infoMap["realm"];
00211 if ( sRealm.empty() )
00212 {
00213 htcon->setErrorDetails("No realm was provided");
00214 htcon->addHeader("WWW-Authenticate", getChallenge(hostname));
00215 return false;
00216 }
00217 String sNonceCount = infoMap["nc"];
00218 if ( sNonceCount.empty() )
00219 {
00220 htcon->setErrorDetails("No Nonce Count was provided");
00221 htcon->addHeader("WWW-Authenticate", getChallenge(hostname));
00222 return false;
00223 }
00224
00225 String sCNonce = infoMap["cnonce"];
00226 if ( sCNonce.empty() )
00227 {
00228 htcon->setErrorDetails("No cnonce value provided");
00229 htcon->addHeader("WWW-Authenticate", getChallenge(hostname));
00230 return false;
00231 }
00232 String sResponse = infoMap["response"];
00233 if ( sResponse.empty() )
00234 {
00235 htcon->setErrorDetails("The response was zero length");
00236 htcon->addHeader("WWW-Authenticate", getChallenge(hostname));
00237 return false;
00238 }
00239 String sHA1 = getHash( userName, sRealm );
00240 String sTestResponse;
00241 HTTPUtils::DigestCalcResponse( sHA1, sNonce, sNonceCount, sCNonce,
00242 "auth", htcon->getRequestLine()[ 0 ], htcon->getRequestLine()[1],
00243 "", sTestResponse );
00244 if ( sTestResponse == sResponse )
00245 {
00246 if (nonceFound)
00247 {
00248 htcon->addHeader("Authentication-Info",
00249 "qop=\"auth\", nextnonce=\"" + generateNewNonce() + "\"");
00250 return true;
00251 }
00252 else
00253 {
00254 htcon->setErrorDetails("Nonce not found.");
00255 htcon->addHeader("WWW-Authenticate", getChallenge(hostname)
00256 + ", stale=true");
00257 return false;
00258 }
00259 }
00260
00261
00262
00263
00264
00265
00266
00267
00268 String errDetails = "User name or password not valid";
00269 htcon->setErrorDetails(errDetails);
00270 htcon->addHeader("WWW-Authenticate", getChallenge(hostname));
00271 return false;
00272 }
00274 String
00275 DigestAuthentication::getChallenge(const String& hostname)
00276 {
00277 return String("Digest realm=\"" + hostname + "\", "
00278 "qop=\"auth\", nonce=\"" + generateNewNonce() + "\"");
00279 }
00280
00281 }
00282