OW_DigestAuthentication.cpp

Go to the documentation of this file.
00001 /*******************************************************************************
00002 * Copyright (C) 2001-2004 Vintela, Inc. All rights reserved.
00003 *
00004 * Redistribution and use in source and binary forms, with or without
00005 * modification, are permitted provided that the following conditions are met:
00006 *
00007 *  - Redistributions of source code must retain the above copyright notice,
00008 *    this list of conditions and the following disclaimer.
00009 *
00010 *  - Redistributions in binary form must reproduce the above copyright notice,
00011 *    this list of conditions and the following disclaimer in the documentation
00012 *    and/or other materials provided with the distribution.
00013 *
00014 *  - Neither the name of Vintela, Inc. nor the names of its
00015 *    contributors may be used to endorse or promote products derived from this
00016 *    software without specific prior written permission.
00017 *
00018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
00019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00020 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00021 * ARE DISCLAIMED. IN NO EVENT SHALL Vintela, Inc. OR THE CONTRIBUTORS
00022 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00023 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00024 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00025 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00026 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00027 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00028 * POSSIBILITY OF SUCH DAMAGE.
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 // PRIVATE
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    // do this 4 more times, so we get > 128 bits of randomness. Each round only yields 31.
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");  // TODO: This should be a real 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) ) // Only valid for 60 seconds.
00119       {
00120          m_asNonces.remove( 0, iNonce+1 );   // Every nonce before this one
00121          m_aDateTimes.remove( 0, iNonce+1 ); // Came before this one.
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    // TODO isn't cnonce optional?
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    String errDetails = "At end of doAuthenticate(): ";
00262    errDetails += "sHA1: >" + sHA1 + "< sNonce: >" + sNonce +
00263       "< Nonce Count >" + sNonceCount + "< sCNonce: >" +
00264       sCNonce + "< Method >" + htcon->getRequestLine()[0] +
00265       "< url >" + htcon->getRequestLine()[1] + "< testResponse >" + sTestResponse
00266       + "< client Response >" + sResponse + "<";
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 } // end namespace OW_NAMESPACE
00282 

Generated on Thu Feb 9 08:47:58 2006 for openwbem by  doxygen 1.4.6