LCOV - code coverage report
Current view: top level - port - cpl_aws.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 907 965 94.0 %
Date: 2024-11-21 22:18:42 Functions: 46 46 100.0 %

          Line data    Source code
       1             : /**********************************************************************
       2             :  *
       3             :  * Name:     cpl_aws.cpp
       4             :  * Project:  CPL - Common Portability Library
       5             :  * Purpose:  Amazon Web Services routines
       6             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       7             :  *
       8             :  **********************************************************************
       9             :  * Copyright (c) 2015, Even Rouault <even.rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : //! @cond Doxygen_Suppress
      15             : 
      16             : #include "cpl_aws.h"
      17             : #include "cpl_json.h"
      18             : #include "cpl_vsi_error.h"
      19             : #include "cpl_sha256.h"
      20             : #include "cpl_time.h"
      21             : #include "cpl_minixml.h"
      22             : #include "cpl_multiproc.h"
      23             : #include "cpl_http.h"
      24             : #include <algorithm>
      25             : 
      26             : // #define DEBUG_VERBOSE 1
      27             : 
      28             : #ifdef _WIN32
      29             : #if defined(HAVE_ATLBASE_H)
      30             : bool CPLFetchWindowsProductUUID(
      31             :     std::string &osStr);  // defined in cpl_aws_win32.cpp
      32             : #endif
      33             : const char *CPLGetWineVersion();  // defined in cpl_vsil_win32.cpp
      34             : #endif
      35             : 
      36             : #ifdef HAVE_CURL
      37             : static CPLMutex *ghMutex = nullptr;
      38             : static std::string gosIAMRole;
      39             : static std::string gosGlobalAccessKeyId;
      40             : static std::string gosGlobalSecretAccessKey;
      41             : static std::string gosGlobalSessionToken;
      42             : static GIntBig gnGlobalExpiration = 0;
      43             : static std::string gosRegion;
      44             : 
      45             : // The below variables are used for credentials retrieved through a STS
      46             : // AssumedRole operation
      47             : static std::string gosRoleArn;
      48             : static std::string gosExternalId;
      49             : static std::string gosMFASerial;
      50             : static std::string gosRoleSessionName;
      51             : static std::string gosSourceProfileAccessKeyId;
      52             : static std::string gosSourceProfileSecretAccessKey;
      53             : static std::string gosSourceProfileSessionToken;
      54             : 
      55             : // The below variables are used for web identity settings in aws/config
      56             : static std::string gosRoleArnWebIdentity;
      57             : static std::string gosWebIdentityTokenFile;
      58             : 
      59             : /************************************************************************/
      60             : /*                         CPLGetLowerCaseHex()                         */
      61             : /************************************************************************/
      62             : 
      63        1065 : static std::string CPLGetLowerCaseHex(const GByte *pabyData, size_t nBytes)
      64             : 
      65             : {
      66        1065 :     std::string osRet;
      67        1065 :     osRet.resize(nBytes * 2);
      68             : 
      69        1065 :     constexpr char achHex[] = "0123456789abcdef";
      70             : 
      71       35145 :     for (size_t i = 0; i < nBytes; ++i)
      72             :     {
      73       34080 :         const int nLow = pabyData[i] & 0x0f;
      74       34080 :         const int nHigh = (pabyData[i] & 0xf0) >> 4;
      75             : 
      76       34080 :         osRet[i * 2] = achHex[nHigh];
      77       34080 :         osRet[i * 2 + 1] = achHex[nLow];
      78             :     }
      79             : 
      80        2130 :     return osRet;
      81             : }
      82             : 
      83             : /************************************************************************/
      84             : /*                       CPLGetLowerCaseHexSHA256()                     */
      85             : /************************************************************************/
      86             : 
      87         713 : std::string CPLGetLowerCaseHexSHA256(const void *pabyData, size_t nBytes)
      88             : {
      89         713 :     GByte hash[CPL_SHA256_HASH_SIZE] = {};
      90         713 :     CPL_SHA256(static_cast<const GByte *>(pabyData), nBytes, hash);
      91        1426 :     return CPLGetLowerCaseHex(hash, CPL_SHA256_HASH_SIZE);
      92             : }
      93             : 
      94             : /************************************************************************/
      95             : /*                       CPLGetLowerCaseHexSHA256()                     */
      96             : /************************************************************************/
      97             : 
      98         357 : std::string CPLGetLowerCaseHexSHA256(const std::string &osStr)
      99             : {
     100         357 :     return CPLGetLowerCaseHexSHA256(osStr.c_str(), osStr.size());
     101             : }
     102             : 
     103             : /************************************************************************/
     104             : /*                       CPLAWSURLEncode()                              */
     105             : /************************************************************************/
     106             : 
     107        5917 : std::string CPLAWSURLEncode(const std::string &osURL, bool bEncodeSlash)
     108             : {
     109        5917 :     std::string osRet;
     110    12098800 :     for (size_t i = 0; i < osURL.size(); i++)
     111             :     {
     112    12092900 :         char ch = osURL[i];
     113    12092900 :         if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
     114       40833 :             (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' ||
     115             :             ch == '.')
     116             :         {
     117    12089200 :             osRet += ch;
     118             :         }
     119        3699 :         else if (ch == '/')
     120             :         {
     121        3474 :             if (bEncodeSlash)
     122         573 :                 osRet += "%2F";
     123             :             else
     124        2901 :                 osRet += ch;
     125             :         }
     126             :         else
     127             :         {
     128         225 :             osRet += CPLSPrintf("%%%02X", static_cast<unsigned char>(ch));
     129             :         }
     130             :     }
     131        5917 :     return osRet;
     132             : }
     133             : 
     134             : /************************************************************************/
     135             : /*                         CPLAWSGetHeaderVal()                         */
     136             : /************************************************************************/
     137             : 
     138        2413 : std::string CPLAWSGetHeaderVal(const struct curl_slist *psExistingHeaders,
     139             :                                const char *pszKey)
     140             : {
     141        4826 :     std::string osKey(pszKey);
     142        2413 :     osKey += ":";
     143        2413 :     const struct curl_slist *psIter = psExistingHeaders;
     144        3772 :     for (; psIter != nullptr; psIter = psIter->next)
     145             :     {
     146        1445 :         if (STARTS_WITH(psIter->data, osKey.c_str()))
     147         172 :             return CPLString(psIter->data + osKey.size()).Trim();
     148             :     }
     149        2327 :     return std::string();
     150             : }
     151             : 
     152             : /************************************************************************/
     153             : /*                 CPLGetAWS_SIGN4_Signature()                          */
     154             : /************************************************************************/
     155             : 
     156             : // See:
     157             : // http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
     158         352 : std::string CPLGetAWS_SIGN4_Signature(
     159             :     const std::string &osSecretAccessKey, const std::string &osAccessToken,
     160             :     const std::string &osRegion, const std::string &osRequestPayer,
     161             :     const std::string &osService, const std::string &osVerb,
     162             :     const struct curl_slist *psExistingHeaders, const std::string &osHost,
     163             :     const std::string &osCanonicalURI,
     164             :     const std::string &osCanonicalQueryString,
     165             :     const std::string &osXAMZContentSHA256, bool bAddHeaderAMZContentSHA256,
     166             :     const std::string &osTimestamp, std::string &osSignedHeaders)
     167             : {
     168             :     /* -------------------------------------------------------------------- */
     169             :     /*      Compute canonical request string.                               */
     170             :     /* -------------------------------------------------------------------- */
     171         704 :     std::string osCanonicalRequest = osVerb + "\n";
     172             : 
     173         352 :     osCanonicalRequest += osCanonicalURI + "\n";
     174             : 
     175         352 :     osCanonicalRequest += osCanonicalQueryString + "\n";
     176             : 
     177         704 :     std::map<std::string, std::string> oSortedMapHeaders;
     178         352 :     oSortedMapHeaders["host"] = osHost;
     179         352 :     if (osXAMZContentSHA256 != "UNSIGNED-PAYLOAD" && bAddHeaderAMZContentSHA256)
     180             :     {
     181         342 :         oSortedMapHeaders["x-amz-content-sha256"] = osXAMZContentSHA256;
     182         342 :         oSortedMapHeaders["x-amz-date"] = osTimestamp;
     183             :     }
     184         352 :     if (!osRequestPayer.empty())
     185           2 :         oSortedMapHeaders["x-amz-request-payer"] = osRequestPayer;
     186         352 :     if (!osAccessToken.empty())
     187          11 :         oSortedMapHeaders["x-amz-security-token"] = osAccessToken;
     188             :     std::string osCanonicalizedHeaders(
     189             :         IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
     190         704 :             oSortedMapHeaders, psExistingHeaders, "x-amz-"));
     191             : 
     192         352 :     osCanonicalRequest += osCanonicalizedHeaders + "\n";
     193             : 
     194         352 :     osSignedHeaders.clear();
     195             :     std::map<std::string, std::string>::const_iterator oIter =
     196         352 :         oSortedMapHeaders.begin();
     197        1419 :     for (; oIter != oSortedMapHeaders.end(); ++oIter)
     198             :     {
     199        1067 :         if (!osSignedHeaders.empty())
     200         715 :             osSignedHeaders += ";";
     201        1067 :         osSignedHeaders += oIter->first;
     202             :     }
     203             : 
     204         352 :     osCanonicalRequest += osSignedHeaders + "\n";
     205             : 
     206         352 :     osCanonicalRequest += osXAMZContentSHA256;
     207             : 
     208             : #ifdef DEBUG_VERBOSE
     209             :     CPLDebug("S3", "osCanonicalRequest='%s'", osCanonicalRequest.c_str());
     210             : #endif
     211             : 
     212             :     /* -------------------------------------------------------------------- */
     213             :     /*      Compute StringToSign .                                          */
     214             :     /* -------------------------------------------------------------------- */
     215         704 :     std::string osStringToSign = "AWS4-HMAC-SHA256\n";
     216         352 :     osStringToSign += osTimestamp + "\n";
     217             : 
     218         704 :     std::string osYYMMDD(osTimestamp);
     219         352 :     osYYMMDD.resize(8);
     220             : 
     221         704 :     std::string osScope = osYYMMDD + "/";
     222         352 :     osScope += osRegion;
     223         352 :     osScope += "/";
     224         352 :     osScope += osService;
     225         352 :     osScope += "/aws4_request";
     226         352 :     osStringToSign += osScope + "\n";
     227         352 :     osStringToSign += CPLGetLowerCaseHexSHA256(osCanonicalRequest);
     228             : 
     229             : #ifdef DEBUG_VERBOSE
     230             :     CPLDebug("S3", "osStringToSign='%s'", osStringToSign.c_str());
     231             : #endif
     232             : 
     233             :     /* -------------------------------------------------------------------- */
     234             :     /*      Compute signing key.                                            */
     235             :     /* -------------------------------------------------------------------- */
     236         352 :     GByte abySigningKeyIn[CPL_SHA256_HASH_SIZE] = {};
     237         352 :     GByte abySigningKeyOut[CPL_SHA256_HASH_SIZE] = {};
     238             : 
     239        1056 :     std::string osFirstKey(std::string("AWS4") + osSecretAccessKey);
     240         352 :     CPL_HMAC_SHA256(osFirstKey.c_str(), osFirstKey.size(), osYYMMDD.c_str(),
     241             :                     osYYMMDD.size(), abySigningKeyOut);
     242         352 :     memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
     243             : 
     244         352 :     CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, osRegion.c_str(),
     245             :                     osRegion.size(), abySigningKeyOut);
     246         352 :     memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
     247             : 
     248         352 :     CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, osService.c_str(),
     249             :                     osService.size(), abySigningKeyOut);
     250         352 :     memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
     251             : 
     252         352 :     CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, "aws4_request",
     253             :                     strlen("aws4_request"), abySigningKeyOut);
     254         352 :     memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
     255             : 
     256             : #ifdef DEBUG_VERBOSE
     257             :     std::string osSigningKey(
     258             :         CPLGetLowerCaseHex(abySigningKeyIn, CPL_SHA256_HASH_SIZE));
     259             :     CPLDebug("S3", "osSigningKey='%s'", osSigningKey.c_str());
     260             : #endif
     261             : 
     262             :     /* -------------------------------------------------------------------- */
     263             :     /*      Compute signature.                                              */
     264             :     /* -------------------------------------------------------------------- */
     265         352 :     GByte abySignature[CPL_SHA256_HASH_SIZE] = {};
     266         704 :     CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE,
     267         352 :                     osStringToSign.c_str(), osStringToSign.size(),
     268             :                     abySignature);
     269             :     std::string osSignature(
     270         352 :         CPLGetLowerCaseHex(abySignature, CPL_SHA256_HASH_SIZE));
     271             : 
     272             : #ifdef DEBUG_VERBOSE
     273             :     CPLDebug("S3", "osSignature='%s'", osSignature.c_str());
     274             : #endif
     275             : 
     276         704 :     return osSignature;
     277             : }
     278             : 
     279             : /************************************************************************/
     280             : /*                CPLGetAWS_SIGN4_Authorization()                       */
     281             : /************************************************************************/
     282             : 
     283         347 : std::string CPLGetAWS_SIGN4_Authorization(
     284             :     const std::string &osSecretAccessKey, const std::string &osAccessKeyId,
     285             :     const std::string &osAccessToken, const std::string &osRegion,
     286             :     const std::string &osRequestPayer, const std::string &osService,
     287             :     const std::string &osVerb, const struct curl_slist *psExistingHeaders,
     288             :     const std::string &osHost, const std::string &osCanonicalURI,
     289             :     const std::string &osCanonicalQueryString,
     290             :     const std::string &osXAMZContentSHA256, bool bAddHeaderAMZContentSHA256,
     291             :     const std::string &osTimestamp)
     292             : {
     293         694 :     std::string osSignedHeaders;
     294             :     std::string osSignature(CPLGetAWS_SIGN4_Signature(
     295             :         osSecretAccessKey, osAccessToken, osRegion, osRequestPayer, osService,
     296             :         osVerb, psExistingHeaders, osHost, osCanonicalURI,
     297             :         osCanonicalQueryString, osXAMZContentSHA256, bAddHeaderAMZContentSHA256,
     298         694 :         osTimestamp, osSignedHeaders));
     299             : 
     300         694 :     std::string osYYMMDD(osTimestamp);
     301         347 :     osYYMMDD.resize(8);
     302             : 
     303             :     /* -------------------------------------------------------------------- */
     304             :     /*      Build authorization header.                                     */
     305             :     /* -------------------------------------------------------------------- */
     306         347 :     std::string osAuthorization;
     307         347 :     osAuthorization = "AWS4-HMAC-SHA256 Credential=";
     308         347 :     osAuthorization += osAccessKeyId;
     309         347 :     osAuthorization += "/";
     310         347 :     osAuthorization += osYYMMDD;
     311         347 :     osAuthorization += "/";
     312         347 :     osAuthorization += osRegion;
     313         347 :     osAuthorization += "/";
     314         347 :     osAuthorization += osService;
     315         347 :     osAuthorization += "/";
     316         347 :     osAuthorization += "aws4_request";
     317         347 :     osAuthorization += ",";
     318         347 :     osAuthorization += "SignedHeaders=";
     319         347 :     osAuthorization += osSignedHeaders;
     320         347 :     osAuthorization += ",";
     321         347 :     osAuthorization += "Signature=";
     322         347 :     osAuthorization += osSignature;
     323             : 
     324             : #ifdef DEBUG_VERBOSE
     325             :     CPLDebug("S3", "osAuthorization='%s'", osAuthorization.c_str());
     326             : #endif
     327             : 
     328         694 :     return osAuthorization;
     329             : }
     330             : 
     331             : /************************************************************************/
     332             : /*                        CPLGetAWS_SIGN4_Timestamp()                   */
     333             : /************************************************************************/
     334             : 
     335           4 : std::string CPLGetAWS_SIGN4_Timestamp(GIntBig timestamp)
     336             : {
     337             :     struct tm brokenDown;
     338           4 :     CPLUnixTimeToYMDHMS(timestamp, &brokenDown);
     339             : 
     340           4 :     char szTimeStamp[80] = {};
     341           4 :     snprintf(szTimeStamp, sizeof(szTimeStamp), "%04d%02d%02dT%02d%02d%02dZ",
     342           4 :              brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
     343             :              brokenDown.tm_mday, brokenDown.tm_hour, brokenDown.tm_min,
     344             :              brokenDown.tm_sec);
     345           4 :     return szTimeStamp;
     346             : }
     347             : 
     348             : /************************************************************************/
     349             : /*                         VSIS3HandleHelper()                          */
     350             : /************************************************************************/
     351         462 : VSIS3HandleHelper::VSIS3HandleHelper(
     352             :     const std::string &osSecretAccessKey, const std::string &osAccessKeyId,
     353             :     const std::string &osSessionToken, const std::string &osEndpoint,
     354             :     const std::string &osRegion, const std::string &osRequestPayer,
     355             :     const std::string &osBucket, const std::string &osObjectKey, bool bUseHTTPS,
     356         462 :     bool bUseVirtualHosting, AWSCredentialsSource eCredentialsSource)
     357             :     : m_osURL(BuildURL(osEndpoint, osBucket, osObjectKey, bUseHTTPS,
     358             :                        bUseVirtualHosting)),
     359             :       m_osSecretAccessKey(osSecretAccessKey), m_osAccessKeyId(osAccessKeyId),
     360             :       m_osSessionToken(osSessionToken), m_osEndpoint(osEndpoint),
     361             :       m_osRegion(osRegion), m_osRequestPayer(osRequestPayer),
     362             :       m_osBucket(osBucket), m_osObjectKey(osObjectKey), m_bUseHTTPS(bUseHTTPS),
     363             :       m_bUseVirtualHosting(bUseVirtualHosting),
     364         462 :       m_eCredentialsSource(eCredentialsSource)
     365             : {
     366         462 :     VSIS3UpdateParams::UpdateHandleFromMap(this);
     367         462 : }
     368             : 
     369             : /************************************************************************/
     370             : /*                        ~VSIS3HandleHelper()                          */
     371             : /************************************************************************/
     372             : 
     373         924 : VSIS3HandleHelper::~VSIS3HandleHelper()
     374             : {
     375        9598 :     for (size_t i = 0; i < m_osSecretAccessKey.size(); i++)
     376        9136 :         m_osSecretAccessKey[i] = 0;
     377         924 : }
     378             : 
     379             : /************************************************************************/
     380             : /*                           BuildURL()                                 */
     381             : /************************************************************************/
     382             : 
     383        1102 : std::string VSIS3HandleHelper::BuildURL(const std::string &osEndpoint,
     384             :                                         const std::string &osBucket,
     385             :                                         const std::string &osObjectKey,
     386             :                                         bool bUseHTTPS, bool bUseVirtualHosting)
     387             : {
     388        1102 :     const char *pszProtocol = (bUseHTTPS) ? "https" : "http";
     389        1102 :     if (osBucket.empty())
     390           9 :         return CPLSPrintf("%s://%s", pszProtocol, osEndpoint.c_str());
     391        1093 :     else if (bUseVirtualHosting)
     392             :         return CPLSPrintf("%s://%s.%s/%s", pszProtocol, osBucket.c_str(),
     393             :                           osEndpoint.c_str(),
     394          36 :                           CPLAWSURLEncode(osObjectKey, false).c_str());
     395             :     else
     396             :         return CPLSPrintf("%s://%s/%s/%s", pszProtocol, osEndpoint.c_str(),
     397             :                           osBucket.c_str(),
     398        2151 :                           CPLAWSURLEncode(osObjectKey, false).c_str());
     399             : }
     400             : 
     401             : /************************************************************************/
     402             : /*                           RebuildURL()                               */
     403             : /************************************************************************/
     404             : 
     405         640 : void VSIS3HandleHelper::RebuildURL()
     406             : {
     407         640 :     m_osURL = BuildURL(m_osEndpoint, m_osBucket, m_osObjectKey, m_bUseHTTPS,
     408         640 :                        m_bUseVirtualHosting);
     409         640 :     m_osURL += GetQueryString(false);
     410         640 : }
     411             : 
     412             : /************************************************************************/
     413             : /*                        GetBucketAndObjectKey()                       */
     414             : /************************************************************************/
     415             : 
     416         543 : bool IVSIS3LikeHandleHelper::GetBucketAndObjectKey(const char *pszURI,
     417             :                                                    const char *pszFSPrefix,
     418             :                                                    bool bAllowNoObject,
     419             :                                                    std::string &osBucket,
     420             :                                                    std::string &osObjectKey)
     421             : {
     422         543 :     osBucket = pszURI;
     423         543 :     if (osBucket.empty())
     424             :     {
     425           0 :         return false;
     426             :     }
     427         543 :     size_t nPos = osBucket.find('/');
     428         543 :     if (nPos == std::string::npos)
     429             :     {
     430         107 :         if (bAllowNoObject)
     431             :         {
     432         105 :             osObjectKey = "";
     433         105 :             return true;
     434             :         }
     435           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     436             :                  "Filename should be of the form %sbucket/key", pszFSPrefix);
     437           2 :         return false;
     438             :     }
     439         436 :     osBucket.resize(nPos);
     440         435 :     osObjectKey = pszURI + nPos + 1;
     441         435 :     return true;
     442             : }
     443             : 
     444             : /************************************************************************/
     445             : /*                      BuildCanonicalizedHeaders()                    */
     446             : /************************************************************************/
     447             : 
     448         654 : std::string IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
     449             :     std::map<std::string, std::string> &oSortedMapHeaders,
     450             :     const struct curl_slist *psExistingHeaders, const char *pszHeaderPrefix)
     451             : {
     452         654 :     const struct curl_slist *psIter = psExistingHeaders;
     453        1030 :     for (; psIter != nullptr; psIter = psIter->next)
     454             :     {
     455         376 :         if (STARTS_WITH_CI(psIter->data, pszHeaderPrefix) ||
     456         332 :             STARTS_WITH_CI(psIter->data, "Content-MD5"))
     457             :         {
     458          50 :             const char *pszColumn = strstr(psIter->data, ":");
     459          50 :             if (pszColumn)
     460             :             {
     461          50 :                 CPLString osKey(psIter->data);
     462          50 :                 osKey.resize(pszColumn - psIter->data);
     463          50 :                 oSortedMapHeaders[osKey.tolower()] =
     464         100 :                     CPLString(pszColumn + strlen(":")).Trim();
     465             :             }
     466             :         }
     467             :     }
     468             : 
     469         654 :     std::string osCanonicalizedHeaders;
     470             :     std::map<std::string, std::string>::const_iterator oIter =
     471         654 :         oSortedMapHeaders.begin();
     472        2150 :     for (; oIter != oSortedMapHeaders.end(); ++oIter)
     473             :     {
     474        1496 :         osCanonicalizedHeaders += oIter->first + ":" + oIter->second + "\n";
     475             :     }
     476        1308 :     return osCanonicalizedHeaders;
     477             : }
     478             : 
     479             : /************************************************************************/
     480             : /*                         GetRFC822DateTime()                          */
     481             : /************************************************************************/
     482             : 
     483          29 : std::string IVSIS3LikeHandleHelper::GetRFC822DateTime()
     484             : {
     485             :     char szDate[64];
     486          29 :     time_t nNow = time(nullptr);
     487             :     struct tm tm;
     488          29 :     CPLUnixTimeToYMDHMS(nNow, &tm);
     489          29 :     int nRet = CPLPrintTime(szDate, sizeof(szDate) - 1,
     490             :                             "%a, %d %b %Y %H:%M:%S GMT", &tm, "C");
     491          29 :     szDate[nRet] = 0;
     492          29 :     return szDate;
     493             : }
     494             : 
     495             : /************************************************************************/
     496             : /*                        Iso8601ToUnixTime()                           */
     497             : /************************************************************************/
     498             : 
     499          16 : static bool Iso8601ToUnixTime(const char *pszDT, GIntBig *pnUnixTime)
     500             : {
     501             :     int nYear;
     502             :     int nMonth;
     503             :     int nDay;
     504             :     int nHour;
     505             :     int nMinute;
     506             :     int nSecond;
     507          16 :     if (sscanf(pszDT, "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth, &nDay,
     508          16 :                &nHour, &nMinute, &nSecond) == 6)
     509             :     {
     510             :         struct tm brokendowntime;
     511          16 :         brokendowntime.tm_year = nYear - 1900;
     512          16 :         brokendowntime.tm_mon = nMonth - 1;
     513          16 :         brokendowntime.tm_mday = nDay;
     514          16 :         brokendowntime.tm_hour = nHour;
     515          16 :         brokendowntime.tm_min = nMinute;
     516          16 :         brokendowntime.tm_sec = nSecond;
     517          16 :         *pnUnixTime = CPLYMDHMSToUnixTime(&brokendowntime);
     518          16 :         return true;
     519             :     }
     520           0 :     return false;
     521             : }
     522             : 
     523             : /************************************************************************/
     524             : /*                  IsMachinePotentiallyEC2Instance()                   */
     525             : /************************************************************************/
     526             : 
     527             : enum class EC2InstanceCertainty
     528             : {
     529             :     YES,
     530             :     NO,
     531             :     MAYBE
     532             : };
     533             : 
     534          13 : static EC2InstanceCertainty IsMachinePotentiallyEC2Instance()
     535             : {
     536             : #if defined(__linux) || defined(_WIN32)
     537           7 :     const auto IsMachinePotentiallyEC2InstanceFromLinuxHost = []()
     538             :     {
     539             :         // On the newer Nitro Hypervisor (C5, M5, H1, T3), use
     540             :         // /sys/devices/virtual/dmi/id/sys_vendor = 'Amazon EC2' instead.
     541             : 
     542             :         // On older Xen hypervisor EC2 instances, a /sys/hypervisor/uuid file
     543             :         // will exist with a string beginning with 'ec2'.
     544             : 
     545             :         // If the files exist but don't contain the correct content, then we're
     546             :         // not EC2 and do not attempt any network access
     547             : 
     548             :         // Check for Xen Hypervisor instances
     549             :         // This file doesn't exist on Nitro instances
     550           7 :         VSILFILE *fp = VSIFOpenL("/sys/hypervisor/uuid", "rb");
     551           7 :         if (fp != nullptr)
     552             :         {
     553           0 :             char uuid[36 + 1] = {0};
     554           0 :             VSIFReadL(uuid, 1, sizeof(uuid) - 1, fp);
     555           0 :             VSIFCloseL(fp);
     556           0 :             return EQUALN(uuid, "ec2", 3) ? EC2InstanceCertainty::YES
     557           0 :                                           : EC2InstanceCertainty::NO;
     558             :         }
     559             : 
     560             :         // Check for Nitro Hypervisor instances
     561             :         // This file may exist on Xen instances with a value of 'Xen'
     562             :         // (but that doesn't mean we're on EC2)
     563           7 :         fp = VSIFOpenL("/sys/devices/virtual/dmi/id/sys_vendor", "rb");
     564           7 :         if (fp != nullptr)
     565             :         {
     566           7 :             char buf[10 + 1] = {0};
     567           7 :             VSIFReadL(buf, 1, sizeof(buf) - 1, fp);
     568           7 :             VSIFCloseL(fp);
     569           7 :             return EQUALN(buf, "Amazon EC2", 10) ? EC2InstanceCertainty::YES
     570           7 :                                                  : EC2InstanceCertainty::NO;
     571             :         }
     572             : 
     573             :         // Fallback: Check via the network
     574           0 :         return EC2InstanceCertainty::MAYBE;
     575             :     };
     576             : #endif
     577             : 
     578             : #ifdef __linux
     579             :     // Optimization on Linux to avoid the network request
     580             :     // See
     581             :     // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html
     582             :     // Skip if either:
     583             :     // - CPL_AWS_AUTODETECT_EC2=NO
     584             :     // - CPL_AWS_CHECK_HYPERVISOR_UUID=NO (deprecated)
     585             : 
     586          13 :     if (!CPLTestBool(CPLGetConfigOption("CPL_AWS_AUTODETECT_EC2", "YES")))
     587             :     {
     588           6 :         return EC2InstanceCertainty::MAYBE;
     589             :     }
     590             :     else
     591             :     {
     592             :         const char *opt =
     593           7 :             CPLGetConfigOption("CPL_AWS_CHECK_HYPERVISOR_UUID", "");
     594           7 :         if (opt[0])
     595             :         {
     596           0 :             CPLDebug("AWS", "CPL_AWS_CHECK_HYPERVISOR_UUID is deprecated. Use "
     597             :                             "CPL_AWS_AUTODETECT_EC2 instead");
     598           0 :             if (!CPLTestBool(opt))
     599             :             {
     600           0 :                 return EC2InstanceCertainty::MAYBE;
     601             :             }
     602             :         }
     603             :     }
     604             : 
     605           7 :     return IsMachinePotentiallyEC2InstanceFromLinuxHost();
     606             : 
     607             : #elif defined(_WIN32)
     608             :     if (!CPLTestBool(CPLGetConfigOption("CPL_AWS_AUTODETECT_EC2", "YES")))
     609             :     {
     610             :         return EC2InstanceCertainty::MAYBE;
     611             :     }
     612             : 
     613             :     // Regular UUID is not valid for WINE, fetch from sysfs instead.
     614             :     if (CPLGetWineVersion() != nullptr)
     615             :     {
     616             :         return IsMachinePotentiallyEC2InstanceFromLinuxHost();
     617             :     }
     618             :     else
     619             :     {
     620             : #if defined(HAVE_ATLBASE_H)
     621             :         std::string osMachineUUID;
     622             :         if (CPLFetchWindowsProductUUID(osMachineUUID))
     623             :         {
     624             :             if (osMachineUUID.length() >= 3 &&
     625             :                 EQUALN(osMachineUUID.c_str(), "EC2", 3))
     626             :             {
     627             :                 return EC2InstanceCertainty::YES;
     628             :             }
     629             :             else if (osMachineUUID.length() >= 8 && osMachineUUID[4] == '2' &&
     630             :                      osMachineUUID[6] == 'E' && osMachineUUID[7] == 'C')
     631             :             {
     632             :                 return EC2InstanceCertainty::YES;
     633             :             }
     634             :             else
     635             :             {
     636             :                 return EC2InstanceCertainty::NO;
     637             :             }
     638             :         }
     639             : #endif
     640             :     }
     641             : 
     642             :     // Fallback: Check via the network
     643             :     return EC2InstanceCertainty::MAYBE;
     644             : #else
     645             :     // At time of writing EC2 instances can be only Linux or Windows
     646             :     return EC2InstanceCertainty::NO;
     647             : #endif
     648             : }
     649             : 
     650             : /************************************************************************/
     651             : /*                   ReadAWSTokenFile()                                 */
     652             : /************************************************************************/
     653             : 
     654           5 : static bool ReadAWSTokenFile(const std::string &osAWSTokenFile,
     655             :                              std::string &awsToken)
     656             : {
     657           5 :     GByte *pabyOut = nullptr;
     658           5 :     if (!VSIIngestFile(nullptr, osAWSTokenFile.c_str(), &pabyOut, nullptr, -1))
     659           0 :         return false;
     660             : 
     661           5 :     awsToken = reinterpret_cast<char *>(pabyOut);
     662           5 :     VSIFree(pabyOut);
     663             :     // Remove trailing end-of-line character
     664           5 :     if (!awsToken.empty() && awsToken.back() == '\n')
     665           4 :         awsToken.pop_back();
     666           5 :     return !awsToken.empty();
     667             : }
     668             : 
     669             : /************************************************************************/
     670             : /*          GetConfigurationFromAssumeRoleWithWebIdentity()             */
     671             : /************************************************************************/
     672             : 
     673          16 : bool VSIS3HandleHelper::GetConfigurationFromAssumeRoleWithWebIdentity(
     674             :     bool bForceRefresh, const std::string &osPathForOption,
     675             :     const std::string &osRoleArnIn, const std::string &osWebIdentityTokenFileIn,
     676             :     std::string &osSecretAccessKey, std::string &osAccessKeyId,
     677             :     std::string &osSessionToken)
     678             : {
     679          32 :     CPLMutexHolder oHolder(&ghMutex);
     680          16 :     if (!bForceRefresh)
     681             :     {
     682             :         time_t nCurTime;
     683          16 :         time(&nCurTime);
     684             :         // Try to reuse credentials if they are still valid, but
     685             :         // keep one minute of margin...
     686          16 :         if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
     687             :         {
     688           1 :             osAccessKeyId = gosGlobalAccessKeyId;
     689           1 :             osSecretAccessKey = gosGlobalSecretAccessKey;
     690           1 :             osSessionToken = gosGlobalSessionToken;
     691           1 :             return true;
     692             :         }
     693             :     }
     694             : 
     695             :     const std::string roleArn =
     696          15 :         !osRoleArnIn.empty() ? osRoleArnIn
     697             :                              : VSIGetPathSpecificOption(osPathForOption.c_str(),
     698          30 :                                                         "AWS_ROLE_ARN", "");
     699          15 :     if (roleArn.empty())
     700             :     {
     701          11 :         CPLDebug("AWS", "AWS_ROLE_ARN configuration option not defined");
     702          11 :         return false;
     703             :     }
     704             : 
     705             :     const std::string webIdentityTokenFile =
     706           4 :         !osWebIdentityTokenFileIn.empty()
     707             :             ? osWebIdentityTokenFileIn
     708             :             : VSIGetPathSpecificOption(osPathForOption.c_str(),
     709           8 :                                        "AWS_WEB_IDENTITY_TOKEN_FILE", "");
     710           4 :     if (webIdentityTokenFile.empty())
     711             :     {
     712           0 :         CPLDebug(
     713             :             "AWS",
     714             :             "AWS_WEB_IDENTITY_TOKEN_FILE configuration option not defined");
     715           0 :         return false;
     716             :     }
     717             : 
     718             :     const std::string stsRegionalEndpoints = VSIGetPathSpecificOption(
     719           8 :         osPathForOption.c_str(), "AWS_STS_REGIONAL_ENDPOINTS", "regional");
     720             : 
     721           8 :     std::string osStsDefaultUrl;
     722           4 :     if (stsRegionalEndpoints == "regional")
     723             :     {
     724             :         const std::string osRegion = VSIGetPathSpecificOption(
     725           4 :             osPathForOption.c_str(), "AWS_REGION", "us-east-1");
     726           4 :         osStsDefaultUrl = "https://sts." + osRegion + ".amazonaws.com";
     727             :     }
     728             :     else
     729             :     {
     730           0 :         osStsDefaultUrl = "https://sts.amazonaws.com";
     731             :     }
     732             :     const std::string osStsRootUrl(VSIGetPathSpecificOption(
     733             :         osPathForOption.c_str(), "CPL_AWS_STS_ROOT_URL",
     734           8 :         osStsDefaultUrl.c_str()));
     735             : 
     736             :     // Get token from web identity token file
     737           8 :     std::string webIdentityToken;
     738           4 :     if (!ReadAWSTokenFile(webIdentityTokenFile, webIdentityToken))
     739             :     {
     740           0 :         CPLDebug("AWS", "%s is empty", webIdentityTokenFile.c_str());
     741           0 :         return false;
     742             :     }
     743             : 
     744             :     // Get credentials from sts AssumeRoleWithWebIdentity
     745           4 :     std::string osExpiration;
     746             :     {
     747             :         const std::string osSTS_asuume_role_with_web_identity_URL =
     748           8 :             osStsRootUrl +
     749             :             "/?Action=AssumeRoleWithWebIdentity&RoleSessionName=gdal"
     750           8 :             "&Version=2011-06-15&RoleArn=" +
     751          16 :             CPLAWSURLEncode(roleArn) +
     752          12 :             "&WebIdentityToken=" + CPLAWSURLEncode(webIdentityToken);
     753             : 
     754           4 :         CPLPushErrorHandler(CPLQuietErrorHandler);
     755             : 
     756           4 :         CPLHTTPResult *psResult = CPLHTTPFetch(
     757             :             osSTS_asuume_role_with_web_identity_URL.c_str(), nullptr);
     758           4 :         CPLPopErrorHandler();
     759           4 :         if (psResult)
     760             :         {
     761           4 :             if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
     762             :             {
     763             :                 CPLXMLTreeCloser oTree(CPLParseXMLString(
     764           8 :                     reinterpret_cast<char *>(psResult->pabyData)));
     765           4 :                 if (oTree)
     766             :                 {
     767           4 :                     const auto psCredentials = CPLGetXMLNode(
     768             :                         oTree.get(),
     769             :                         "=AssumeRoleWithWebIdentityResponse."
     770             :                         "AssumeRoleWithWebIdentityResult.Credentials");
     771           4 :                     if (psCredentials)
     772             :                     {
     773             :                         osAccessKeyId =
     774           3 :                             CPLGetXMLValue(psCredentials, "AccessKeyId", "");
     775             :                         osSecretAccessKey = CPLGetXMLValue(
     776           3 :                             psCredentials, "SecretAccessKey", "");
     777             :                         osSessionToken =
     778           3 :                             CPLGetXMLValue(psCredentials, "SessionToken", "");
     779             :                         osExpiration =
     780           3 :                             CPLGetXMLValue(psCredentials, "Expiration", "");
     781             :                     }
     782             :                 }
     783             :             }
     784           4 :             CPLHTTPDestroyResult(psResult);
     785             :         }
     786             :     }
     787             : 
     788           4 :     GIntBig nExpirationUnix = 0;
     789           7 :     if (!osAccessKeyId.empty() && !osSecretAccessKey.empty() &&
     790          10 :         !osSessionToken.empty() &&
     791           3 :         Iso8601ToUnixTime(osExpiration.c_str(), &nExpirationUnix))
     792             :     {
     793           3 :         gosGlobalAccessKeyId = osAccessKeyId;
     794           3 :         gosGlobalSecretAccessKey = osSecretAccessKey;
     795           3 :         gosGlobalSessionToken = osSessionToken;
     796           3 :         gnGlobalExpiration = nExpirationUnix;
     797           3 :         CPLDebug("AWS", "Storing AIM credentials until %s",
     798             :                  osExpiration.c_str());
     799             :     }
     800           7 :     return !osAccessKeyId.empty() && !osSecretAccessKey.empty() &&
     801           7 :            !osSessionToken.empty();
     802             : }
     803             : 
     804             : /************************************************************************/
     805             : /*                      GetConfigurationFromEC2()                       */
     806             : /************************************************************************/
     807             : 
     808          25 : bool VSIS3HandleHelper::GetConfigurationFromEC2(
     809             :     bool bForceRefresh, const std::string &osPathForOption,
     810             :     std::string &osSecretAccessKey, std::string &osAccessKeyId,
     811             :     std::string &osSessionToken)
     812             : {
     813          50 :     CPLMutexHolder oHolder(&ghMutex);
     814          25 :     if (!bForceRefresh)
     815             :     {
     816             :         time_t nCurTime;
     817          24 :         time(&nCurTime);
     818             :         // Try to reuse credentials if they are still valid, but
     819             :         // keep one minute of margin...
     820          24 :         if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
     821             :         {
     822           9 :             osAccessKeyId = gosGlobalAccessKeyId;
     823           9 :             osSecretAccessKey = gosGlobalSecretAccessKey;
     824           9 :             osSessionToken = gosGlobalSessionToken;
     825           9 :             return true;
     826             :         }
     827             :     }
     828             : 
     829          32 :     std::string osURLRefreshCredentials;
     830          32 :     const std::string osEC2DefaultURL("http://169.254.169.254");
     831             :     // coverity[tainted_data]
     832             :     const std::string osEC2RootURL(VSIGetPathSpecificOption(
     833             :         osPathForOption.c_str(), "CPL_AWS_EC2_API_ROOT_URL",
     834          32 :         osEC2DefaultURL.c_str()));
     835             :     // coverity[tainted_data]
     836             :     const std::string osECSFullURI(VSIGetPathSpecificOption(
     837          32 :         osPathForOption.c_str(), "AWS_CONTAINER_CREDENTIALS_FULL_URI", ""));
     838             :     // coverity[tainted_data]
     839             :     const std::string osECSRelativeURI(
     840          16 :         osECSFullURI.empty() ? VSIGetPathSpecificOption(
     841             :                                    osPathForOption.c_str(),
     842             :                                    "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "")
     843          32 :                              : std::string());
     844             :     // coverity[tainted_data]
     845             :     const std::string osECSTokenFile(
     846          13 :         (osECSFullURI.empty() && osECSRelativeURI.empty())
     847          16 :             ? std::string()
     848             :             : VSIGetPathSpecificOption(osPathForOption.c_str(),
     849             :                                        "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE",
     850          48 :                                        ""));
     851             : 
     852             :     // coverity[tainted_data]
     853             :     const std::string osECSTokenValue(
     854          13 :         (osECSFullURI.empty() && osECSRelativeURI.empty() &&
     855          13 :          !osECSTokenFile.empty())
     856          16 :             ? std::string()
     857             :             : VSIGetPathSpecificOption(osPathForOption.c_str(),
     858             :                                        "AWS_CONTAINER_AUTHORIZATION_TOKEN",
     859          48 :                                        ""));
     860             : 
     861          32 :     std::string osECSToken;
     862          16 :     if (!osECSTokenFile.empty())
     863             :     {
     864           1 :         if (!ReadAWSTokenFile(osECSTokenFile, osECSToken))
     865             :         {
     866           0 :             CPLDebug("AWS", "%s is empty", osECSTokenFile.c_str());
     867             :         }
     868             :     }
     869          15 :     else if (!osECSTokenValue.empty())
     870             :     {
     871           1 :         osECSToken = osECSTokenValue;
     872             :     }
     873             : 
     874          32 :     std::string osToken;
     875          16 :     if (!osECSFullURI.empty())
     876             :     {
     877             :         // Cf https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html
     878           3 :         osURLRefreshCredentials = osECSFullURI;
     879             :     }
     880          13 :     else if (osEC2RootURL == osEC2DefaultURL && !osECSRelativeURI.empty())
     881             :     {
     882             :         // See
     883             :         // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
     884           0 :         osURLRefreshCredentials = "http://169.254.170.2" + osECSRelativeURI;
     885             :     }
     886             :     else
     887             :     {
     888          13 :         const auto eIsEC2 = IsMachinePotentiallyEC2Instance();
     889          13 :         if (eIsEC2 == EC2InstanceCertainty::NO)
     890           7 :             return false;
     891             : 
     892             :         // Use IMDSv2 protocol:
     893             :         // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
     894             : 
     895             :         // Retrieve IMDSv2 token
     896             :         {
     897             :             const std::string osEC2_IMDSv2_api_token_URL =
     898          12 :                 osEC2RootURL + "/latest/api/token";
     899          12 :             CPLStringList aosOptions;
     900           6 :             aosOptions.SetNameValue("TIMEOUT", "1");
     901           6 :             aosOptions.SetNameValue("CUSTOMREQUEST", "PUT");
     902             :             aosOptions.SetNameValue("HEADERS",
     903           6 :                                     "X-aws-ec2-metadata-token-ttl-seconds: 10");
     904           6 :             CPLPushErrorHandler(CPLQuietErrorHandler);
     905           6 :             CPLHTTPResult *psResult = CPLHTTPFetch(
     906           6 :                 osEC2_IMDSv2_api_token_URL.c_str(), aosOptions.List());
     907           6 :             CPLPopErrorHandler();
     908           6 :             if (psResult)
     909             :             {
     910           6 :                 if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
     911             :                 {
     912           4 :                     osToken = reinterpret_cast<char *>(psResult->pabyData);
     913             :                 }
     914             :                 else
     915             :                 {
     916             :                     // Failure: either we are not running on EC2 (or something
     917             :                     // emulating it) or this doesn't implement yet IMDSv2.
     918             :                     // Fallback to IMDSv1
     919             : 
     920             :                     // /latest/api/token doesn't work inside a Docker container
     921             :                     // that has no host networking. Cf
     922             :                     // https://community.grafana.com/t/imdsv2-is-not-working-from-docker/65944
     923           2 :                     if (psResult->pszErrBuf != nullptr &&
     924           2 :                         strstr(psResult->pszErrBuf,
     925             :                                "Operation timed out after") != nullptr)
     926             :                     {
     927           0 :                         aosOptions.Clear();
     928           0 :                         aosOptions.SetNameValue("TIMEOUT", "1");
     929           0 :                         CPLPushErrorHandler(CPLQuietErrorHandler);
     930           0 :                         CPLHTTPResult *psResult2 = CPLHTTPFetch(
     931           0 :                             (osEC2RootURL + "/latest/meta-data").c_str(),
     932           0 :                             aosOptions.List());
     933           0 :                         CPLPopErrorHandler();
     934           0 :                         if (psResult2)
     935             :                         {
     936           0 :                             if (psResult2->nStatus == 0 &&
     937           0 :                                 psResult2->pabyData != nullptr)
     938             :                             {
     939           0 :                                 CPLDebug("AWS",
     940             :                                          "/latest/api/token EC2 IMDSv2 request "
     941             :                                          "timed out, but /latest/metadata "
     942             :                                          "succeeded. "
     943             :                                          "Trying with IMDSv1. "
     944             :                                          "Consult "
     945             :                                          "https://gdal.org/user/"
     946             :                                          "virtual_file_systems.html#vsis3_imds "
     947             :                                          "for IMDS related issues.");
     948             :                             }
     949           0 :                             CPLHTTPDestroyResult(psResult2);
     950             :                         }
     951             :                     }
     952             :                 }
     953           6 :                 CPLHTTPDestroyResult(psResult);
     954             :             }
     955           6 :             CPLErrorReset();
     956             :         }
     957             : 
     958             :         // If we don't know yet the IAM role, fetch it
     959             :         const std::string osEC2CredentialsURL =
     960           6 :             osEC2RootURL + "/latest/meta-data/iam/security-credentials/";
     961           6 :         if (gosIAMRole.empty())
     962             :         {
     963           3 :             CPLStringList aosOptions;
     964           3 :             aosOptions.SetNameValue("TIMEOUT", "1");
     965           3 :             if (!osToken.empty())
     966             :             {
     967             :                 aosOptions.SetNameValue(
     968             :                     "HEADERS",
     969           2 :                     ("X-aws-ec2-metadata-token: " + osToken).c_str());
     970             :             }
     971           3 :             CPLPushErrorHandler(CPLQuietErrorHandler);
     972             :             CPLHTTPResult *psResult =
     973           3 :                 CPLHTTPFetch(osEC2CredentialsURL.c_str(), aosOptions.List());
     974           3 :             CPLPopErrorHandler();
     975           3 :             if (psResult)
     976             :             {
     977           3 :                 if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
     978             :                 {
     979           3 :                     gosIAMRole = reinterpret_cast<char *>(psResult->pabyData);
     980             :                 }
     981           3 :                 CPLHTTPDestroyResult(psResult);
     982             :             }
     983           3 :             CPLErrorReset();
     984           3 :             if (gosIAMRole.empty())
     985             :             {
     986             :                 // We didn't get the IAM role. We are definitely not running
     987             :                 // on (a correctly configured) EC2 or an emulation of it.
     988             : 
     989           0 :                 if (eIsEC2 == EC2InstanceCertainty::YES)
     990             :                 {
     991           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     992             :                              "EC2 IMDSv2 and IMDSv1 requests failed. Consult "
     993             :                              "https://gdal.org/user/"
     994             :                              "virtual_file_systems.html#vsis3_imds "
     995             :                              "for IMDS related issues.");
     996             :                 }
     997             : 
     998           0 :                 return false;
     999             :             }
    1000             :         }
    1001           6 :         osURLRefreshCredentials = osEC2CredentialsURL + gosIAMRole;
    1002             :     }
    1003             : 
    1004             :     // Now fetch the refreshed credentials
    1005          18 :     CPLStringList oResponse;
    1006          18 :     CPLStringList aosOptions;
    1007           9 :     if (!osToken.empty())
    1008             :     {
    1009             :         aosOptions.SetNameValue(
    1010           4 :             "HEADERS", ("X-aws-ec2-metadata-token: " + osToken).c_str());
    1011             :     }
    1012           5 :     else if (!osECSToken.empty())
    1013             :     {
    1014             :         aosOptions.SetNameValue("HEADERS",
    1015           2 :                                 ("Authorization: " + osECSToken).c_str());
    1016             :     }
    1017             :     CPLHTTPResult *psResult =
    1018           9 :         CPLHTTPFetch(osURLRefreshCredentials.c_str(), aosOptions.List());
    1019           9 :     if (psResult)
    1020             :     {
    1021           9 :         if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
    1022             :         {
    1023             :             const std::string osJSon =
    1024           8 :                 reinterpret_cast<char *>(psResult->pabyData);
    1025           8 :             oResponse = CPLParseKeyValueJson(osJSon.c_str());
    1026             :         }
    1027           9 :         CPLHTTPDestroyResult(psResult);
    1028             :     }
    1029           9 :     CPLErrorReset();
    1030           9 :     osAccessKeyId = oResponse.FetchNameValueDef("AccessKeyId", "");
    1031           9 :     osSecretAccessKey = oResponse.FetchNameValueDef("SecretAccessKey", "");
    1032           9 :     osSessionToken = oResponse.FetchNameValueDef("Token", "");
    1033             :     const std::string osExpiration =
    1034           9 :         oResponse.FetchNameValueDef("Expiration", "");
    1035           9 :     GIntBig nExpirationUnix = 0;
    1036          17 :     if (!osAccessKeyId.empty() && !osSecretAccessKey.empty() &&
    1037           8 :         Iso8601ToUnixTime(osExpiration.c_str(), &nExpirationUnix))
    1038             :     {
    1039           8 :         gosGlobalAccessKeyId = osAccessKeyId;
    1040           8 :         gosGlobalSecretAccessKey = osSecretAccessKey;
    1041           8 :         gosGlobalSessionToken = osSessionToken;
    1042           8 :         gnGlobalExpiration = nExpirationUnix;
    1043           8 :         CPLDebug("AWS", "Storing AIM credentials until %s",
    1044             :                  osExpiration.c_str());
    1045             :     }
    1046           9 :     return !osAccessKeyId.empty() && !osSecretAccessKey.empty();
    1047             : }
    1048             : 
    1049             : /************************************************************************/
    1050             : /*                      UpdateAndWarnIfInconsistent()                   */
    1051             : /************************************************************************/
    1052             : 
    1053           6 : static void UpdateAndWarnIfInconsistent(const char *pszKeyword,
    1054             :                                         std::string &osVal,
    1055             :                                         const std::string &osNewVal,
    1056             :                                         const std::string &osCredentials,
    1057             :                                         const std::string &osConfig)
    1058             : {
    1059             :     // nominally defined in ~/.aws/credentials but can
    1060             :     // be set here too. If both values exist, credentials
    1061             :     // has the priority
    1062           6 :     if (osVal.empty())
    1063             :     {
    1064           2 :         osVal = osNewVal;
    1065             :     }
    1066           4 :     else if (osVal != osNewVal)
    1067             :     {
    1068           2 :         CPLError(CE_Warning, CPLE_AppDefined,
    1069             :                  "%s defined in both %s "
    1070             :                  "and %s. The one of %s will be used",
    1071             :                  pszKeyword, osCredentials.c_str(), osConfig.c_str(),
    1072             :                  osCredentials.c_str());
    1073             :     }
    1074           6 : }
    1075             : 
    1076             : /************************************************************************/
    1077             : /*                         ReadAWSCredentials()                         */
    1078             : /************************************************************************/
    1079             : 
    1080          28 : static bool ReadAWSCredentials(const std::string &osProfile,
    1081             :                                const std::string &osCredentials,
    1082             :                                std::string &osSecretAccessKey,
    1083             :                                std::string &osAccessKeyId,
    1084             :                                std::string &osSessionToken)
    1085             : {
    1086          28 :     osSecretAccessKey.clear();
    1087          28 :     osAccessKeyId.clear();
    1088          28 :     osSessionToken.clear();
    1089             : 
    1090          28 :     VSILFILE *fp = VSIFOpenL(osCredentials.c_str(), "rb");
    1091          28 :     if (fp != nullptr)
    1092             :     {
    1093             :         const char *pszLine;
    1094           9 :         bool bInProfile = false;
    1095          27 :         const std::string osBracketedProfile("[" + osProfile + "]");
    1096          49 :         while ((pszLine = CPLReadLineL(fp)) != nullptr)
    1097             :         {
    1098          44 :             if (pszLine[0] == '[')
    1099             :             {
    1100          15 :                 if (bInProfile)
    1101           4 :                     break;
    1102          11 :                 if (std::string(pszLine) == osBracketedProfile)
    1103           6 :                     bInProfile = true;
    1104             :             }
    1105          29 :             else if (bInProfile)
    1106             :             {
    1107          12 :                 char *pszKey = nullptr;
    1108          12 :                 const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
    1109          12 :                 if (pszKey && pszValue)
    1110             :                 {
    1111          12 :                     if (EQUAL(pszKey, "aws_access_key_id"))
    1112           6 :                         osAccessKeyId = pszValue;
    1113           6 :                     else if (EQUAL(pszKey, "aws_secret_access_key"))
    1114           6 :                         osSecretAccessKey = pszValue;
    1115           0 :                     else if (EQUAL(pszKey, "aws_session_token"))
    1116           0 :                         osSessionToken = pszValue;
    1117             :                 }
    1118          12 :                 CPLFree(pszKey);
    1119             :             }
    1120             :         }
    1121           9 :         VSIFCloseL(fp);
    1122             :     }
    1123             : 
    1124          28 :     return !osSecretAccessKey.empty() && !osAccessKeyId.empty();
    1125             : }
    1126             : 
    1127             : /************************************************************************/
    1128             : /*                GetConfigurationFromAWSConfigFiles()                  */
    1129             : /************************************************************************/
    1130             : 
    1131          27 : bool VSIS3HandleHelper::GetConfigurationFromAWSConfigFiles(
    1132             :     const std::string &osPathForOption, const char *pszProfile,
    1133             :     std::string &osSecretAccessKey, std::string &osAccessKeyId,
    1134             :     std::string &osSessionToken, std::string &osRegion,
    1135             :     std::string &osCredentials, std::string &osRoleArn,
    1136             :     std::string &osSourceProfile, std::string &osExternalId,
    1137             :     std::string &osMFASerial, std::string &osRoleSessionName,
    1138             :     std::string &osWebIdentityTokenFile)
    1139             : {
    1140             :     // See http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html
    1141             :     // If AWS_DEFAULT_PROFILE is set (obsolete, no longer documented), use it in
    1142             :     // priority Otherwise use AWS_PROFILE Otherwise fallback to "default"
    1143          27 :     const char *pszProfileOri = pszProfile;
    1144          27 :     if (pszProfile == nullptr)
    1145             :     {
    1146          25 :         pszProfile = VSIGetPathSpecificOption(osPathForOption.c_str(),
    1147             :                                               "AWS_DEFAULT_PROFILE", "");
    1148          25 :         if (pszProfile[0] == '\0')
    1149          25 :             pszProfile = VSIGetPathSpecificOption(osPathForOption.c_str(),
    1150             :                                                   "AWS_PROFILE", "");
    1151             :     }
    1152          54 :     const std::string osProfile(pszProfile[0] != '\0' ? pszProfile : "default");
    1153             : 
    1154             : #ifdef _WIN32
    1155             :     const char *pszHome = CPLGetConfigOption("USERPROFILE", nullptr);
    1156             :     constexpr char SEP_STRING[] = "\\";
    1157             : #else
    1158          27 :     const char *pszHome = CPLGetConfigOption("HOME", nullptr);
    1159          27 :     constexpr char SEP_STRING[] = "/";
    1160             : #endif
    1161             : 
    1162             :     const std::string osDotAws(
    1163          81 :         std::string(pszHome ? pszHome : "").append(SEP_STRING).append(".aws"));
    1164             : 
    1165             :     // Read first ~/.aws/credential file
    1166             : 
    1167             :     // GDAL specific config option (mostly for testing purpose, but also
    1168             :     // used in production in some cases)
    1169          27 :     const char *pszCredentials = VSIGetPathSpecificOption(
    1170             :         osPathForOption.c_str(), "CPL_AWS_CREDENTIALS_FILE", nullptr);
    1171          27 :     if (pszCredentials)
    1172             :     {
    1173          22 :         osCredentials = pszCredentials;
    1174             :     }
    1175             :     else
    1176             :     {
    1177           5 :         osCredentials = osDotAws;
    1178           5 :         osCredentials += SEP_STRING;
    1179           5 :         osCredentials += "credentials";
    1180             :     }
    1181             : 
    1182          27 :     ReadAWSCredentials(osProfile, osCredentials, osSecretAccessKey,
    1183             :                        osAccessKeyId, osSessionToken);
    1184             : 
    1185             :     // And then ~/.aws/config file (unless AWS_CONFIG_FILE is defined)
    1186          27 :     const char *pszAWSConfigFileEnv = VSIGetPathSpecificOption(
    1187             :         osPathForOption.c_str(), "AWS_CONFIG_FILE", nullptr);
    1188          27 :     std::string osConfig;
    1189          27 :     if (pszAWSConfigFileEnv)
    1190             :     {
    1191          22 :         osConfig = pszAWSConfigFileEnv;
    1192             :     }
    1193             :     else
    1194             :     {
    1195           5 :         osConfig = osDotAws;
    1196           5 :         osConfig += SEP_STRING;
    1197           5 :         osConfig += "config";
    1198             :     }
    1199          27 :     VSILFILE *fp = VSIFOpenL(osConfig.c_str(), "rb");
    1200          27 :     if (fp != nullptr)
    1201             :     {
    1202             :         const char *pszLine;
    1203           8 :         bool bInProfile = false;
    1204          16 :         const std::string osBracketedProfile("[" + osProfile + "]");
    1205          16 :         const std::string osBracketedProfileProfile("[profile " + osProfile +
    1206          16 :                                                     "]");
    1207          65 :         while ((pszLine = CPLReadLineL(fp)) != nullptr)
    1208             :         {
    1209          62 :             if (pszLine[0] == '[')
    1210             :             {
    1211          18 :                 if (bInProfile)
    1212           5 :                     break;
    1213             :                 // In config file, the section name is nominally [profile foo]
    1214             :                 // for the non default profile.
    1215          36 :                 if (std::string(pszLine) == osBracketedProfile ||
    1216          23 :                     std::string(pszLine) == osBracketedProfileProfile)
    1217             :                 {
    1218           7 :                     bInProfile = true;
    1219             :                 }
    1220             :             }
    1221          44 :             else if (bInProfile)
    1222             :             {
    1223          20 :                 char *pszKey = nullptr;
    1224          20 :                 const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
    1225          20 :                 if (pszKey && pszValue)
    1226             :                 {
    1227          19 :                     if (EQUAL(pszKey, "aws_access_key_id"))
    1228             :                     {
    1229           3 :                         UpdateAndWarnIfInconsistent(pszKey, osAccessKeyId,
    1230             :                                                     pszValue, osCredentials,
    1231             :                                                     osConfig);
    1232             :                     }
    1233          16 :                     else if (EQUAL(pszKey, "aws_secret_access_key"))
    1234             :                     {
    1235           3 :                         UpdateAndWarnIfInconsistent(pszKey, osSecretAccessKey,
    1236             :                                                     pszValue, osCredentials,
    1237             :                                                     osConfig);
    1238             :                     }
    1239          13 :                     else if (EQUAL(pszKey, "aws_session_token"))
    1240             :                     {
    1241           0 :                         UpdateAndWarnIfInconsistent(pszKey, osSessionToken,
    1242             :                                                     pszValue, osCredentials,
    1243             :                                                     osConfig);
    1244             :                     }
    1245          13 :                     else if (EQUAL(pszKey, "region"))
    1246             :                     {
    1247           4 :                         osRegion = pszValue;
    1248             :                     }
    1249           9 :                     else if (strcmp(pszKey, "role_arn") == 0)
    1250             :                     {
    1251           3 :                         osRoleArn = pszValue;
    1252             :                     }
    1253           6 :                     else if (strcmp(pszKey, "source_profile") == 0)
    1254             :                     {
    1255           2 :                         osSourceProfile = pszValue;
    1256             :                     }
    1257           4 :                     else if (strcmp(pszKey, "external_id") == 0)
    1258             :                     {
    1259           1 :                         osExternalId = pszValue;
    1260             :                     }
    1261           3 :                     else if (strcmp(pszKey, "mfa_serial") == 0)
    1262             :                     {
    1263           1 :                         osMFASerial = pszValue;
    1264             :                     }
    1265           2 :                     else if (strcmp(pszKey, "role_session_name") == 0)
    1266             :                     {
    1267           1 :                         osRoleSessionName = pszValue;
    1268             :                     }
    1269           1 :                     else if (strcmp(pszKey, "web_identity_token_file") == 0)
    1270             :                     {
    1271           1 :                         osWebIdentityTokenFile = pszValue;
    1272             :                     }
    1273             :                 }
    1274          20 :                 CPLFree(pszKey);
    1275             :             }
    1276             :         }
    1277           8 :         VSIFCloseL(fp);
    1278             :     }
    1279          19 :     else if (pszAWSConfigFileEnv != nullptr)
    1280             :     {
    1281          15 :         if (pszAWSConfigFileEnv[0] != '\0')
    1282             :         {
    1283           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1284             :                      "%s does not exist or cannot be open",
    1285             :                      pszAWSConfigFileEnv);
    1286             :         }
    1287             :     }
    1288             : 
    1289          33 :     return (!osAccessKeyId.empty() && !osSecretAccessKey.empty()) ||
    1290          34 :            (!osRoleArn.empty() && !osSourceProfile.empty()) ||
    1291           1 :            (pszProfileOri != nullptr && !osRoleArn.empty() &&
    1292          55 :             !osWebIdentityTokenFile.empty());
    1293             : }
    1294             : 
    1295             : /************************************************************************/
    1296             : /*                     GetTemporaryCredentialsForRole()                 */
    1297             : /************************************************************************/
    1298             : 
    1299             : // Issue a STS AssumedRole operation to get temporary credentials for an assumed
    1300             : // role.
    1301           5 : static bool GetTemporaryCredentialsForRole(
    1302             :     const std::string &osRoleArn, const std::string &osExternalId,
    1303             :     const std::string &osMFASerial, const std::string &osRoleSessionName,
    1304             :     const std::string &osSecretAccessKey, const std::string &osAccessKeyId,
    1305             :     const std::string &osSessionToken, std::string &osTempSecretAccessKey,
    1306             :     std::string &osTempAccessKeyId, std::string &osTempSessionToken,
    1307             :     std::string &osExpiration)
    1308             : {
    1309          10 :     std::string osXAMZDate = CPLGetConfigOption("AWS_TIMESTAMP", "");
    1310           5 :     if (osXAMZDate.empty())
    1311           0 :         osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
    1312          10 :     std::string osDate(osXAMZDate);
    1313           5 :     osDate.resize(8);
    1314             : 
    1315          10 :     const std::string osVerb("GET");
    1316          10 :     const std::string osService("sts");
    1317             :     const std::string osRegion(
    1318          10 :         CPLGetConfigOption("AWS_STS_REGION", "us-east-1"));
    1319             :     const std::string osHost(
    1320          10 :         CPLGetConfigOption("AWS_STS_ENDPOINT", "sts.amazonaws.com"));
    1321             : 
    1322          10 :     std::map<std::string, std::string> oMap;
    1323           5 :     oMap["Version"] = "2011-06-15";
    1324           5 :     oMap["Action"] = "AssumeRole";
    1325           5 :     oMap["RoleArn"] = osRoleArn;
    1326          10 :     oMap["RoleSessionName"] =
    1327           5 :         !osRoleSessionName.empty()
    1328           3 :             ? osRoleSessionName.c_str()
    1329          10 :             : CPLGetConfigOption("AWS_ROLE_SESSION_NAME", "GDAL-session");
    1330           5 :     if (!osExternalId.empty())
    1331           3 :         oMap["ExternalId"] = osExternalId;
    1332           5 :     if (!osMFASerial.empty())
    1333           3 :         oMap["SerialNumber"] = osMFASerial;
    1334             : 
    1335          10 :     std::string osQueryString;
    1336          31 :     for (const auto &kv : oMap)
    1337             :     {
    1338          26 :         if (osQueryString.empty())
    1339           5 :             osQueryString += "?";
    1340             :         else
    1341          21 :             osQueryString += "&";
    1342          26 :         osQueryString += kv.first;
    1343          26 :         osQueryString += "=";
    1344          26 :         osQueryString += CPLAWSURLEncode(kv.second);
    1345             :     }
    1346          10 :     std::string osCanonicalQueryString(osQueryString.substr(1));
    1347             : 
    1348             :     const std::string osAuthorization = CPLGetAWS_SIGN4_Authorization(
    1349             :         osSecretAccessKey, osAccessKeyId, osSessionToken, osRegion,
    1350          10 :         std::string(),  // m_osRequestPayer,
    1351             :         osService, osVerb,
    1352             :         nullptr,  // psExistingHeaders,
    1353             :         osHost, "/", osCanonicalQueryString,
    1354          10 :         CPLGetLowerCaseHexSHA256(std::string()),
    1355             :         false,  // bAddHeaderAMZContentSHA256
    1356          20 :         osXAMZDate);
    1357             : 
    1358           5 :     bool bRet = false;
    1359           5 :     const bool bUseHTTPS = CPLTestBool(CPLGetConfigOption("AWS_HTTPS", "YES"));
    1360             : 
    1361          10 :     CPLStringList aosOptions;
    1362          10 :     std::string headers;
    1363           5 :     if (!osSessionToken.empty())
    1364           2 :         headers += "X-Amz-Security-Token: " + osSessionToken + "\r\n";
    1365           5 :     headers += "X-Amz-Date: " + osXAMZDate + "\r\n";
    1366           5 :     headers += "Authorization: " + osAuthorization;
    1367           5 :     aosOptions.AddNameValue("HEADERS", headers.c_str());
    1368             : 
    1369             :     const std::string osURL =
    1370          10 :         (bUseHTTPS ? "https://" : "http://") + osHost + "/" + osQueryString;
    1371           5 :     CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), aosOptions.List());
    1372           5 :     if (psResult)
    1373             :     {
    1374           5 :         if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
    1375             :         {
    1376             :             CPLXMLTreeCloser oTree(CPLParseXMLString(
    1377          10 :                 reinterpret_cast<char *>(psResult->pabyData)));
    1378           5 :             if (oTree)
    1379             :             {
    1380           5 :                 const auto psCredentials = CPLGetXMLNode(
    1381             :                     oTree.get(),
    1382             :                     "=AssumeRoleResponse.AssumeRoleResult.Credentials");
    1383           5 :                 if (psCredentials)
    1384             :                 {
    1385             :                     osTempAccessKeyId =
    1386           5 :                         CPLGetXMLValue(psCredentials, "AccessKeyId", "");
    1387             :                     osTempSecretAccessKey =
    1388           5 :                         CPLGetXMLValue(psCredentials, "SecretAccessKey", "");
    1389             :                     osTempSessionToken =
    1390           5 :                         CPLGetXMLValue(psCredentials, "SessionToken", "");
    1391             :                     osExpiration =
    1392           5 :                         CPLGetXMLValue(psCredentials, "Expiration", "");
    1393           5 :                     bRet = true;
    1394             :                 }
    1395             :                 else
    1396             :                 {
    1397           0 :                     CPLDebug("S3", "%s",
    1398           0 :                              reinterpret_cast<char *>(psResult->pabyData));
    1399             :                 }
    1400             :             }
    1401             :         }
    1402           5 :         CPLHTTPDestroyResult(psResult);
    1403             :     }
    1404          10 :     return bRet;
    1405             : }
    1406             : 
    1407             : /************************************************************************/
    1408             : /*               GetOrRefreshTemporaryCredentialsForRole()              */
    1409             : /************************************************************************/
    1410             : 
    1411          10 : bool VSIS3HandleHelper::GetOrRefreshTemporaryCredentialsForRole(
    1412             :     bool bForceRefresh, std::string &osSecretAccessKey,
    1413             :     std::string &osAccessKeyId, std::string &osSessionToken,
    1414             :     std::string &osRegion)
    1415             : {
    1416          20 :     CPLMutexHolder oHolder(&ghMutex);
    1417          10 :     if (!bForceRefresh)
    1418             :     {
    1419             :         time_t nCurTime;
    1420          10 :         time(&nCurTime);
    1421             :         // Try to reuse credentials if they are still valid, but
    1422             :         // keep one minute of margin...
    1423          10 :         if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
    1424             :         {
    1425           6 :             osAccessKeyId = gosGlobalAccessKeyId;
    1426           6 :             osSecretAccessKey = gosGlobalSecretAccessKey;
    1427           6 :             osSessionToken = gosGlobalSessionToken;
    1428           6 :             osRegion = gosRegion;
    1429           6 :             return true;
    1430             :         }
    1431             :     }
    1432             : 
    1433           4 :     if (!gosRoleArnWebIdentity.empty())
    1434             :     {
    1435           2 :         if (GetConfigurationFromAssumeRoleWithWebIdentity(
    1436           4 :                 bForceRefresh, std::string(), gosRoleArnWebIdentity,
    1437             :                 gosWebIdentityTokenFile, osSecretAccessKey, osAccessKeyId,
    1438             :                 osSessionToken))
    1439             :         {
    1440           1 :             gosSourceProfileSecretAccessKey = osSecretAccessKey;
    1441           1 :             gosSourceProfileAccessKeyId = osAccessKeyId;
    1442           1 :             gosSourceProfileSessionToken = osSessionToken;
    1443             :         }
    1444             :         else
    1445             :         {
    1446           1 :             return false;
    1447             :         }
    1448             :     }
    1449             : 
    1450           6 :     std::string osExpiration;
    1451           3 :     gosGlobalSecretAccessKey.clear();
    1452           3 :     gosGlobalAccessKeyId.clear();
    1453           3 :     gosGlobalSessionToken.clear();
    1454           3 :     if (GetTemporaryCredentialsForRole(
    1455             :             gosRoleArn, gosExternalId, gosMFASerial, gosRoleSessionName,
    1456             :             gosSourceProfileSecretAccessKey, gosSourceProfileAccessKeyId,
    1457             :             gosSourceProfileSessionToken, gosGlobalSecretAccessKey,
    1458             :             gosGlobalAccessKeyId, gosGlobalSessionToken, osExpiration))
    1459             :     {
    1460           3 :         Iso8601ToUnixTime(osExpiration.c_str(), &gnGlobalExpiration);
    1461           3 :         osAccessKeyId = gosGlobalAccessKeyId;
    1462           3 :         osSecretAccessKey = gosGlobalSecretAccessKey;
    1463           3 :         osSessionToken = gosGlobalSessionToken;
    1464           3 :         osRegion = gosRegion;
    1465           3 :         return true;
    1466             :     }
    1467           0 :     return false;
    1468             : }
    1469             : 
    1470             : /************************************************************************/
    1471             : /*                        GetConfiguration()                            */
    1472             : /************************************************************************/
    1473             : 
    1474         472 : bool VSIS3HandleHelper::GetConfiguration(
    1475             :     const std::string &osPathForOption, CSLConstList papszOptions,
    1476             :     std::string &osSecretAccessKey, std::string &osAccessKeyId,
    1477             :     std::string &osSessionToken, std::string &osRegion,
    1478             :     AWSCredentialsSource &eCredentialsSource)
    1479             : {
    1480         472 :     eCredentialsSource = AWSCredentialsSource::REGULAR;
    1481             : 
    1482             :     // AWS_REGION is GDAL specific. Later overloaded by standard
    1483             :     // AWS_DEFAULT_REGION
    1484             :     osRegion = CSLFetchNameValueDef(
    1485             :         papszOptions, "AWS_REGION",
    1486             :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_REGION",
    1487         472 :                                  "us-east-1"));
    1488             : 
    1489         472 :     if (CPLTestBool(VSIGetPathSpecificOption(osPathForOption.c_str(),
    1490             :                                              "AWS_NO_SIGN_REQUEST", "NO")))
    1491             :     {
    1492          28 :         osSecretAccessKey.clear();
    1493          28 :         osAccessKeyId.clear();
    1494          28 :         osSessionToken.clear();
    1495          28 :         return true;
    1496             :     }
    1497             : 
    1498             :     osSecretAccessKey = CSLFetchNameValueDef(
    1499             :         papszOptions, "AWS_SECRET_ACCESS_KEY",
    1500             :         VSIGetPathSpecificOption(osPathForOption.c_str(),
    1501         444 :                                  "AWS_SECRET_ACCESS_KEY", ""));
    1502         444 :     if (!osSecretAccessKey.empty())
    1503             :     {
    1504             :         osAccessKeyId = CSLFetchNameValueDef(
    1505             :             papszOptions, "AWS_ACCESS_KEY_ID",
    1506             :             VSIGetPathSpecificOption(osPathForOption.c_str(),
    1507         415 :                                      "AWS_ACCESS_KEY_ID", ""));
    1508         415 :         if (osAccessKeyId.empty())
    1509             :         {
    1510           1 :             VSIError(VSIE_AWSInvalidCredentials,
    1511             :                      "AWS_ACCESS_KEY_ID configuration option not defined");
    1512           1 :             return false;
    1513             :         }
    1514             : 
    1515             :         osSessionToken = CSLFetchNameValueDef(
    1516             :             papszOptions, "AWS_SESSION_TOKEN",
    1517             :             VSIGetPathSpecificOption(osPathForOption.c_str(),
    1518         414 :                                      "AWS_SESSION_TOKEN", ""));
    1519         414 :         return true;
    1520             :     }
    1521             : 
    1522             :     // Next try to see if we have a current assumed role
    1523          29 :     bool bAssumedRole = false;
    1524             :     {
    1525          29 :         CPLMutexHolder oHolder(&ghMutex);
    1526          29 :         bAssumedRole = !gosRoleArn.empty();
    1527             :     }
    1528          29 :     if (bAssumedRole && GetOrRefreshTemporaryCredentialsForRole(
    1529             :                             /* bForceRefresh = */ false, osSecretAccessKey,
    1530             :                             osAccessKeyId, osSessionToken, osRegion))
    1531             :     {
    1532           4 :         eCredentialsSource = AWSCredentialsSource::ASSUMED_ROLE;
    1533           4 :         return true;
    1534             :     }
    1535             : 
    1536             :     // Next try reading from ~/.aws/credentials and ~/.aws/config
    1537          50 :     std::string osCredentials;
    1538          50 :     std::string osRoleArn;
    1539          50 :     std::string osSourceProfile;
    1540          50 :     std::string osExternalId;
    1541          50 :     std::string osMFASerial;
    1542          50 :     std::string osRoleSessionName;
    1543          50 :     std::string osWebIdentityTokenFile;
    1544             :     // coverity[tainted_data]
    1545          25 :     if (GetConfigurationFromAWSConfigFiles(
    1546             :             osPathForOption,
    1547             :             /* pszProfile = */ nullptr, osSecretAccessKey, osAccessKeyId,
    1548             :             osSessionToken, osRegion, osCredentials, osRoleArn, osSourceProfile,
    1549             :             osExternalId, osMFASerial, osRoleSessionName,
    1550             :             osWebIdentityTokenFile))
    1551             :     {
    1552           7 :         if (osSecretAccessKey.empty() && !osRoleArn.empty())
    1553             :         {
    1554             :             // Check if the default profile is pointing to another profile
    1555             :             // that has a role_arn and web_identity_token_file settings.
    1556           2 :             if (!osSourceProfile.empty())
    1557             :             {
    1558           4 :                 std::string osSecretAccessKeySP;
    1559           4 :                 std::string osAccessKeyIdSP;
    1560           4 :                 std::string osSessionTokenSP;
    1561           4 :                 std::string osRegionSP;
    1562           4 :                 std::string osCredentialsSP;
    1563           4 :                 std::string osRoleArnSP;
    1564           4 :                 std::string osSourceProfileSP;
    1565           4 :                 std::string osExternalIdSP;
    1566           4 :                 std::string osMFASerialSP;
    1567           4 :                 std::string osRoleSessionNameSP;
    1568           2 :                 if (GetConfigurationFromAWSConfigFiles(
    1569             :                         osPathForOption, osSourceProfile.c_str(),
    1570             :                         osSecretAccessKeySP, osAccessKeyIdSP, osSessionTokenSP,
    1571             :                         osRegionSP, osCredentialsSP, osRoleArnSP,
    1572             :                         osSourceProfileSP, osExternalIdSP, osMFASerialSP,
    1573             :                         osRoleSessionNameSP, osWebIdentityTokenFile))
    1574             :                 {
    1575           2 :                     if (GetConfigurationFromAssumeRoleWithWebIdentity(
    1576             :                             /* bForceRefresh = */ false, osPathForOption,
    1577             :                             osRoleArnSP, osWebIdentityTokenFile,
    1578             :                             osSecretAccessKey, osAccessKeyId, osSessionToken))
    1579             :                     {
    1580           2 :                         CPLMutexHolder oHolder(&ghMutex);
    1581           1 :                         gosRoleArnWebIdentity = std::move(osRoleArnSP);
    1582             :                         gosWebIdentityTokenFile =
    1583           1 :                             std::move(osWebIdentityTokenFile);
    1584             :                     }
    1585             :                 }
    1586             :             }
    1587             : 
    1588           2 :             if (gosRoleArnWebIdentity.empty())
    1589             :             {
    1590             :                 // Get the credentials for the source profile, that will be
    1591             :                 // used to sign the STS AssumedRole request.
    1592           1 :                 if (!ReadAWSCredentials(osSourceProfile, osCredentials,
    1593             :                                         osSecretAccessKey, osAccessKeyId,
    1594             :                                         osSessionToken))
    1595             :                 {
    1596           0 :                     VSIError(
    1597             :                         VSIE_AWSInvalidCredentials,
    1598             :                         "Cannot retrieve credentials for source profile %s",
    1599             :                         osSourceProfile.c_str());
    1600           0 :                     return false;
    1601             :                 }
    1602             :             }
    1603             : 
    1604           4 :             std::string osTempSecretAccessKey;
    1605           4 :             std::string osTempAccessKeyId;
    1606           4 :             std::string osTempSessionToken;
    1607           4 :             std::string osExpiration;
    1608           2 :             if (GetTemporaryCredentialsForRole(
    1609             :                     osRoleArn, osExternalId, osMFASerial, osRoleSessionName,
    1610             :                     osSecretAccessKey, osAccessKeyId, osSessionToken,
    1611             :                     osTempSecretAccessKey, osTempAccessKeyId,
    1612             :                     osTempSessionToken, osExpiration))
    1613             :             {
    1614           2 :                 CPLDebug("S3", "Using assumed role %s", osRoleArn.c_str());
    1615             :                 {
    1616             :                     // Store global variables to be able to reuse the
    1617             :                     // temporary credentials
    1618           4 :                     CPLMutexHolder oHolder(&ghMutex);
    1619           2 :                     Iso8601ToUnixTime(osExpiration.c_str(),
    1620             :                                       &gnGlobalExpiration);
    1621           2 :                     gosRoleArn = std::move(osRoleArn);
    1622           2 :                     gosExternalId = std::move(osExternalId);
    1623           2 :                     gosMFASerial = std::move(osMFASerial);
    1624           2 :                     gosRoleSessionName = std::move(osRoleSessionName);
    1625             :                     gosSourceProfileSecretAccessKey =
    1626           2 :                         std::move(osSecretAccessKey);
    1627           2 :                     gosSourceProfileAccessKeyId = std::move(osAccessKeyId);
    1628           2 :                     gosSourceProfileSessionToken = std::move(osSessionToken);
    1629           2 :                     gosGlobalAccessKeyId = osTempAccessKeyId;
    1630           2 :                     gosGlobalSecretAccessKey = osTempSecretAccessKey;
    1631           2 :                     gosGlobalSessionToken = osTempSessionToken;
    1632           2 :                     gosRegion = osRegion;
    1633             :                 }
    1634           2 :                 osSecretAccessKey = std::move(osTempSecretAccessKey);
    1635           2 :                 osAccessKeyId = std::move(osTempAccessKeyId);
    1636           2 :                 osSessionToken = std::move(osTempSessionToken);
    1637           2 :                 eCredentialsSource = AWSCredentialsSource::ASSUMED_ROLE;
    1638           2 :                 return true;
    1639             :             }
    1640           0 :             return false;
    1641             :         }
    1642             : 
    1643           5 :         return true;
    1644             :     }
    1645             : 
    1646          18 :     if (CPLTestBool(CPLGetConfigOption("CPL_AWS_WEB_IDENTITY_ENABLE", "YES")))
    1647             :     {
    1648             :         // WebIdentity method: use Web Identity Token
    1649          11 :         if (GetConfigurationFromAssumeRoleWithWebIdentity(
    1650             :                 /* bForceRefresh = */ false, osPathForOption,
    1651          22 :                 /* osRoleArnIn = */ std::string(),
    1652          22 :                 /* osWebIdentityTokenFileIn = */ std::string(),
    1653             :                 osSecretAccessKey, osAccessKeyId, osSessionToken))
    1654             :         {
    1655           1 :             eCredentialsSource = AWSCredentialsSource::WEB_IDENTITY;
    1656           1 :             return true;
    1657             :         }
    1658             :     }
    1659             : 
    1660             :     // Last method: use IAM role security credentials on EC2 instances
    1661          17 :     if (GetConfigurationFromEC2(/* bForceRefresh = */ false, osPathForOption,
    1662             :                                 osSecretAccessKey, osAccessKeyId,
    1663             :                                 osSessionToken))
    1664             :     {
    1665           9 :         eCredentialsSource = AWSCredentialsSource::EC2;
    1666           9 :         return true;
    1667             :     }
    1668             : 
    1669           8 :     VSIError(VSIE_AWSInvalidCredentials,
    1670             :              "AWS_SECRET_ACCESS_KEY and AWS_NO_SIGN_REQUEST configuration "
    1671             :              "options not defined, and %s not filled",
    1672             :              osCredentials.c_str());
    1673           8 :     return false;
    1674             : }
    1675             : 
    1676             : /************************************************************************/
    1677             : /*                          CleanMutex()                                */
    1678             : /************************************************************************/
    1679             : 
    1680         933 : void VSIS3HandleHelper::CleanMutex()
    1681             : {
    1682         933 :     if (ghMutex != nullptr)
    1683         933 :         CPLDestroyMutex(ghMutex);
    1684         933 :     ghMutex = nullptr;
    1685         933 : }
    1686             : 
    1687             : /************************************************************************/
    1688             : /*                          ClearCache()                                */
    1689             : /************************************************************************/
    1690             : 
    1691        1232 : void VSIS3HandleHelper::ClearCache()
    1692             : {
    1693        2464 :     CPLMutexHolder oHolder(&ghMutex);
    1694             : 
    1695        1232 :     gosIAMRole.clear();
    1696        1232 :     gosGlobalAccessKeyId.clear();
    1697        1232 :     gosGlobalSecretAccessKey.clear();
    1698        1232 :     gosGlobalSessionToken.clear();
    1699        1232 :     gnGlobalExpiration = 0;
    1700        1232 :     gosRoleArn.clear();
    1701        1232 :     gosExternalId.clear();
    1702        1232 :     gosMFASerial.clear();
    1703        1232 :     gosRoleSessionName.clear();
    1704        1232 :     gosSourceProfileAccessKeyId.clear();
    1705        1232 :     gosSourceProfileSecretAccessKey.clear();
    1706        1232 :     gosSourceProfileSessionToken.clear();
    1707        1232 :     gosRegion.clear();
    1708        1232 :     gosRoleArnWebIdentity.clear();
    1709        1232 :     gosWebIdentityTokenFile.clear();
    1710        1232 : }
    1711             : 
    1712             : /************************************************************************/
    1713             : /*                          BuildFromURI()                              */
    1714             : /************************************************************************/
    1715             : 
    1716         472 : VSIS3HandleHelper *VSIS3HandleHelper::BuildFromURI(const char *pszURI,
    1717             :                                                    const char *pszFSPrefix,
    1718             :                                                    bool bAllowNoObject,
    1719             :                                                    CSLConstList papszOptions)
    1720             : {
    1721         944 :     std::string osPathForOption("/vsis3/");
    1722         472 :     if (pszURI)
    1723         472 :         osPathForOption += pszURI;
    1724             : 
    1725         944 :     std::string osSecretAccessKey;
    1726         944 :     std::string osAccessKeyId;
    1727         944 :     std::string osSessionToken;
    1728         944 :     std::string osRegion;
    1729         472 :     AWSCredentialsSource eCredentialsSource = AWSCredentialsSource::REGULAR;
    1730         472 :     if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey,
    1731             :                           osAccessKeyId, osSessionToken, osRegion,
    1732             :                           eCredentialsSource))
    1733             :     {
    1734           9 :         return nullptr;
    1735             :     }
    1736             : 
    1737             :     // According to
    1738             :     // http://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html "
    1739             :     // This variable overrides the default region of the in-use profile, if
    1740             :     // set."
    1741             :     const std::string osDefaultRegion = CSLFetchNameValueDef(
    1742             :         papszOptions, "AWS_DEFAULT_REGION",
    1743             :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_DEFAULT_REGION",
    1744         926 :                                  ""));
    1745         463 :     if (!osDefaultRegion.empty())
    1746             :     {
    1747         463 :         osRegion = osDefaultRegion;
    1748             :     }
    1749             : 
    1750             :     std::string osEndpoint = VSIGetPathSpecificOption(
    1751         926 :         osPathForOption.c_str(), "AWS_S3_ENDPOINT", "s3.amazonaws.com");
    1752         463 :     if (!osRegion.empty() && osEndpoint == "s3.amazonaws.com")
    1753             :     {
    1754          40 :         osEndpoint = "s3." + osRegion + ".amazonaws.com";
    1755             :     }
    1756             :     const std::string osRequestPayer = VSIGetPathSpecificOption(
    1757         926 :         osPathForOption.c_str(), "AWS_REQUEST_PAYER", "");
    1758         926 :     std::string osBucket;
    1759         926 :     std::string osObjectKey;
    1760         921 :     if (pszURI != nullptr && pszURI[0] != '\0' &&
    1761         459 :         !GetBucketAndObjectKey(pszURI, pszFSPrefix, bAllowNoObject, osBucket,
    1762             :                                osObjectKey))
    1763             :     {
    1764           1 :         return nullptr;
    1765             :     }
    1766         461 :     const bool bUseHTTPS = CPLTestBool(
    1767             :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_HTTPS", "YES"));
    1768             :     const bool bIsValidNameForVirtualHosting =
    1769         462 :         osBucket.find('.') == std::string::npos;
    1770         462 :     const bool bUseVirtualHosting = CPLTestBool(CSLFetchNameValueDef(
    1771             :         papszOptions, "AWS_VIRTUAL_HOSTING",
    1772             :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_VIRTUAL_HOSTING",
    1773             :                                  bIsValidNameForVirtualHosting ? "TRUE"
    1774             :                                                                : "FALSE")));
    1775             :     return new VSIS3HandleHelper(
    1776             :         osSecretAccessKey, osAccessKeyId, osSessionToken, osEndpoint, osRegion,
    1777             :         osRequestPayer, osBucket, osObjectKey, bUseHTTPS, bUseVirtualHosting,
    1778         462 :         eCredentialsSource);
    1779             : }
    1780             : 
    1781             : /************************************************************************/
    1782             : /*                          GetQueryString()                            */
    1783             : /************************************************************************/
    1784             : 
    1785             : std::string
    1786        1792 : IVSIS3LikeHandleHelper::GetQueryString(bool bAddEmptyValueAfterEqual) const
    1787             : {
    1788        1792 :     std::string osQueryString;
    1789             :     std::map<std::string, std::string>::const_iterator oIter =
    1790        1792 :         m_oMapQueryParameters.begin();
    1791        3352 :     for (; oIter != m_oMapQueryParameters.end(); ++oIter)
    1792             :     {
    1793        1560 :         if (oIter == m_oMapQueryParameters.begin())
    1794         806 :             osQueryString += "?";
    1795             :         else
    1796         754 :             osQueryString += "&";
    1797        1560 :         osQueryString += oIter->first;
    1798        1560 :         if (!oIter->second.empty() || bAddEmptyValueAfterEqual)
    1799             :         {
    1800        1515 :             osQueryString += "=";
    1801        1515 :             osQueryString += CPLAWSURLEncode(oIter->second);
    1802             :         }
    1803             :     }
    1804        3584 :     return osQueryString;
    1805             : }
    1806             : 
    1807             : /************************************************************************/
    1808             : /*                       ResetQueryParameters()                         */
    1809             : /************************************************************************/
    1810             : 
    1811         569 : void IVSIS3LikeHandleHelper::ResetQueryParameters()
    1812             : {
    1813         569 :     m_oMapQueryParameters.clear();
    1814         569 :     RebuildURL();
    1815         569 : }
    1816             : 
    1817             : /************************************************************************/
    1818             : /*                         AddQueryParameter()                          */
    1819             : /************************************************************************/
    1820             : 
    1821         652 : void IVSIS3LikeHandleHelper::AddQueryParameter(const std::string &osKey,
    1822             :                                                const std::string &osValue)
    1823             : {
    1824         652 :     m_oMapQueryParameters[osKey] = osValue;
    1825         652 :     RebuildURL();
    1826         652 : }
    1827             : 
    1828             : /************************************************************************/
    1829             : /*                           GetURLNoKVP()                              */
    1830             : /************************************************************************/
    1831             : 
    1832         388 : std::string IVSIS3LikeHandleHelper::GetURLNoKVP() const
    1833             : {
    1834         388 :     std::string osURL(GetURL());
    1835         388 :     const auto nPos = osURL.find('?');
    1836         388 :     if (nPos != std::string::npos)
    1837           8 :         osURL.resize(nPos);
    1838         388 :     return osURL;
    1839             : }
    1840             : 
    1841             : /************************************************************************/
    1842             : /*                          RefreshCredentials()                        */
    1843             : /************************************************************************/
    1844             : 
    1845         357 : void VSIS3HandleHelper::RefreshCredentials(const std::string &osPathForOption,
    1846             :                                            bool bForceRefresh) const
    1847             : {
    1848         357 :     if (m_eCredentialsSource == AWSCredentialsSource::EC2)
    1849             :     {
    1850          16 :         std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
    1851           8 :         if (GetConfigurationFromEC2(bForceRefresh, osPathForOption.c_str(),
    1852             :                                     osSecretAccessKey, osAccessKeyId,
    1853             :                                     osSessionToken))
    1854             :         {
    1855           8 :             m_osSecretAccessKey = std::move(osSecretAccessKey);
    1856           8 :             m_osAccessKeyId = std::move(osAccessKeyId);
    1857           8 :             m_osSessionToken = std::move(osSessionToken);
    1858             :         }
    1859             :     }
    1860         349 :     else if (m_eCredentialsSource == AWSCredentialsSource::ASSUMED_ROLE)
    1861             :     {
    1862          12 :         std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
    1863          12 :         std::string osRegion;
    1864           6 :         if (GetOrRefreshTemporaryCredentialsForRole(
    1865             :                 bForceRefresh, osSecretAccessKey, osAccessKeyId, osSessionToken,
    1866             :                 osRegion))
    1867             :         {
    1868           5 :             m_osSecretAccessKey = std::move(osSecretAccessKey);
    1869           5 :             m_osAccessKeyId = std::move(osAccessKeyId);
    1870           5 :             m_osSessionToken = std::move(osSessionToken);
    1871             :         }
    1872             :     }
    1873         343 :     else if (m_eCredentialsSource == AWSCredentialsSource::WEB_IDENTITY)
    1874             :     {
    1875           2 :         std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
    1876           1 :         if (GetConfigurationFromAssumeRoleWithWebIdentity(
    1877           2 :                 bForceRefresh, osPathForOption.c_str(), std::string(),
    1878           2 :                 std::string(), osSecretAccessKey, osAccessKeyId,
    1879             :                 osSessionToken))
    1880             :         {
    1881           1 :             m_osSecretAccessKey = std::move(osSecretAccessKey);
    1882           1 :             m_osAccessKeyId = std::move(osAccessKeyId);
    1883           1 :             m_osSessionToken = std::move(osSessionToken);
    1884             :         }
    1885             :     }
    1886         357 : }
    1887             : 
    1888             : /************************************************************************/
    1889             : /*                           GetCurlHeaders()                           */
    1890             : /************************************************************************/
    1891             : 
    1892         356 : struct curl_slist *VSIS3HandleHelper::GetCurlHeaders(
    1893             :     const std::string &osVerb, const struct curl_slist *psExistingHeaders,
    1894             :     const void *pabyDataContent, size_t nBytesContent) const
    1895             : {
    1896         712 :     std::string osPathForOption("/vsis3/");
    1897         356 :     osPathForOption += m_osBucket;
    1898         356 :     osPathForOption += '/';
    1899         356 :     osPathForOption += m_osObjectKey;
    1900             : 
    1901         356 :     RefreshCredentials(osPathForOption, /* bForceRefresh = */ false);
    1902             : 
    1903             :     std::string osXAMZDate =
    1904         712 :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_TIMESTAMP", "");
    1905         356 :     if (osXAMZDate.empty())
    1906           0 :         osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
    1907             : 
    1908             :     const std::string osXAMZContentSHA256 =
    1909         712 :         CPLGetLowerCaseHexSHA256(pabyDataContent, nBytesContent);
    1910             : 
    1911         712 :     std::string osCanonicalQueryString(GetQueryString(true));
    1912         356 :     if (!osCanonicalQueryString.empty())
    1913         145 :         osCanonicalQueryString = osCanonicalQueryString.substr(1);
    1914             : 
    1915           2 :     const std::string osHost(m_bUseVirtualHosting && !m_osBucket.empty()
    1916         358 :                                  ? std::string(m_osBucket + "." + m_osEndpoint)
    1917         714 :                                  : m_osEndpoint);
    1918             :     const std::string osAuthorization =
    1919         356 :         m_osSecretAccessKey.empty()
    1920             :             ? std::string()
    1921             :             : CPLGetAWS_SIGN4_Authorization(
    1922         342 :                   m_osSecretAccessKey, m_osAccessKeyId, m_osSessionToken,
    1923         342 :                   m_osRegion, m_osRequestPayer, "s3", osVerb, psExistingHeaders,
    1924             :                   osHost,
    1925         342 :                   m_bUseVirtualHosting
    1926         356 :                       ? CPLAWSURLEncode("/" + m_osObjectKey, false).c_str()
    1927        1724 :                       : CPLAWSURLEncode("/" + m_osBucket + "/" + m_osObjectKey,
    1928             :                                         false)
    1929         342 :                             .c_str(),
    1930             :                   osCanonicalQueryString, osXAMZContentSHA256,
    1931             :                   true,  // bAddHeaderAMZContentSHA256
    1932        2080 :                   osXAMZDate);
    1933             : 
    1934         356 :     struct curl_slist *headers = nullptr;
    1935         356 :     headers = curl_slist_append(
    1936             :         headers, CPLSPrintf("x-amz-date: %s", osXAMZDate.c_str()));
    1937             :     headers =
    1938         356 :         curl_slist_append(headers, CPLSPrintf("x-amz-content-sha256: %s",
    1939             :                                               osXAMZContentSHA256.c_str()));
    1940         356 :     if (!m_osSessionToken.empty())
    1941             :         headers =
    1942           9 :             curl_slist_append(headers, CPLSPrintf("X-Amz-Security-Token: %s",
    1943             :                                                   m_osSessionToken.c_str()));
    1944         356 :     if (!m_osRequestPayer.empty())
    1945             :         headers =
    1946           2 :             curl_slist_append(headers, CPLSPrintf("x-amz-request-payer: %s",
    1947             :                                                   m_osRequestPayer.c_str()));
    1948         356 :     if (!osAuthorization.empty())
    1949             :     {
    1950         342 :         headers = curl_slist_append(
    1951             :             headers, CPLSPrintf("Authorization: %s", osAuthorization.c_str()));
    1952             :     }
    1953         712 :     return headers;
    1954             : }
    1955             : 
    1956             : /************************************************************************/
    1957             : /*                          CanRestartOnError()                         */
    1958             : /************************************************************************/
    1959             : 
    1960          43 : bool VSIS3HandleHelper::CanRestartOnError(const char *pszErrorMsg,
    1961             :                                           const char *pszHeaders,
    1962             :                                           bool bSetError)
    1963             : {
    1964             : #ifdef DEBUG_VERBOSE
    1965             :     CPLDebug("S3", "%s", pszErrorMsg);
    1966             :     CPLDebug("S3", "%s", pszHeaders ? pszHeaders : "");
    1967             : #endif
    1968             : 
    1969          43 :     if (!STARTS_WITH(pszErrorMsg, "<?xml") &&
    1970           9 :         !STARTS_WITH(pszErrorMsg, "<Error>"))
    1971             :     {
    1972           9 :         if (bSetError)
    1973             :         {
    1974           2 :             VSIError(VSIE_AWSError, "Invalid AWS response: %s", pszErrorMsg);
    1975             :         }
    1976           9 :         return false;
    1977             :     }
    1978             : 
    1979          34 :     CPLXMLNode *psTree = CPLParseXMLString(pszErrorMsg);
    1980          34 :     if (psTree == nullptr)
    1981             :     {
    1982           2 :         if (bSetError)
    1983             :         {
    1984           2 :             VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
    1985             :                      pszErrorMsg);
    1986             :         }
    1987           2 :         return false;
    1988             :     }
    1989             : 
    1990          32 :     const char *pszCode = CPLGetXMLValue(psTree, "=Error.Code", nullptr);
    1991          32 :     if (pszCode == nullptr)
    1992             :     {
    1993           2 :         CPLDestroyXMLNode(psTree);
    1994           2 :         if (bSetError)
    1995             :         {
    1996           2 :             VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
    1997             :                      pszErrorMsg);
    1998             :         }
    1999           2 :         return false;
    2000             :     }
    2001             : 
    2002          30 :     if (EQUAL(pszCode, "AuthorizationHeaderMalformed"))
    2003             :     {
    2004             :         const char *pszRegion =
    2005          10 :             CPLGetXMLValue(psTree, "=Error.Region", nullptr);
    2006          10 :         if (pszRegion == nullptr)
    2007             :         {
    2008           2 :             CPLDestroyXMLNode(psTree);
    2009           2 :             if (bSetError)
    2010             :             {
    2011           2 :                 VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
    2012             :                          pszErrorMsg);
    2013             :             }
    2014           2 :             return false;
    2015             :         }
    2016           8 :         SetRegion(pszRegion);
    2017           8 :         CPLDebug("S3", "Switching to region %s", m_osRegion.c_str());
    2018           8 :         CPLDestroyXMLNode(psTree);
    2019             : 
    2020           8 :         VSIS3UpdateParams::UpdateMapFromHandle(this);
    2021             : 
    2022           8 :         return true;
    2023             :     }
    2024             : 
    2025          20 :     if (EQUAL(pszCode, "PermanentRedirect") ||
    2026          12 :         EQUAL(pszCode, "TemporaryRedirect"))
    2027             :     {
    2028          14 :         const bool bIsTemporaryRedirect = EQUAL(pszCode, "TemporaryRedirect");
    2029             :         const char *pszEndpoint =
    2030          14 :             CPLGetXMLValue(psTree, "=Error.Endpoint", nullptr);
    2031          26 :         if (pszEndpoint == nullptr ||
    2032          12 :             (m_bUseVirtualHosting && (strncmp(pszEndpoint, m_osBucket.c_str(),
    2033           0 :                                               m_osBucket.size()) != 0 ||
    2034           0 :                                       pszEndpoint[m_osBucket.size()] != '.')))
    2035             :         {
    2036           2 :             CPLDestroyXMLNode(psTree);
    2037           2 :             if (bSetError)
    2038             :             {
    2039           2 :                 VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
    2040             :                          pszErrorMsg);
    2041             :             }
    2042           2 :             return false;
    2043             :         }
    2044          36 :         if (!m_bUseVirtualHosting &&
    2045          13 :             strncmp(pszEndpoint, m_osBucket.c_str(), m_osBucket.size()) == 0 &&
    2046           1 :             pszEndpoint[m_osBucket.size()] == '.')
    2047             :         {
    2048             :             /* If we have a body with
    2049             :             <Error><Code>PermanentRedirect</Code><Message>The bucket you are
    2050             :             attempting to access must be addressed using the specified endpoint.
    2051             :             Please send all future requests to this
    2052             :             endpoint.</Message><Bucket>bucket.with.dot</Bucket><Endpoint>bucket.with.dot.s3.amazonaws.com</Endpoint></Error>
    2053             :             and headers like
    2054             :             x-amz-bucket-region: eu-west-1
    2055             :             and the bucket name has dot in it,
    2056             :             then we must use s3.$(x-amz-bucket-region).amazon.com as endpoint.
    2057             :             See #7154 */
    2058           1 :             const char *pszRegionPtr =
    2059             :                 (pszHeaders != nullptr)
    2060           1 :                     ? strstr(pszHeaders, "x-amz-bucket-region: ")
    2061             :                     : nullptr;
    2062           1 :             if (strchr(m_osBucket.c_str(), '.') != nullptr &&
    2063             :                 pszRegionPtr != nullptr)
    2064             :             {
    2065             :                 std::string osRegion(pszRegionPtr +
    2066           1 :                                      strlen("x-amz-bucket-region: "));
    2067           1 :                 size_t nPos = osRegion.find('\r');
    2068           1 :                 if (nPos != std::string::npos)
    2069           1 :                     osRegion.resize(nPos);
    2070           1 :                 SetEndpoint(
    2071             :                     CPLSPrintf("s3.%s.amazonaws.com", osRegion.c_str()));
    2072           1 :                 SetRegion(osRegion.c_str());
    2073           1 :                 CPLDebug("S3", "Switching to endpoint %s",
    2074             :                          m_osEndpoint.c_str());
    2075           1 :                 CPLDebug("S3", "Switching to region %s", m_osRegion.c_str());
    2076           1 :                 CPLDestroyXMLNode(psTree);
    2077           1 :                 if (!bIsTemporaryRedirect)
    2078           1 :                     VSIS3UpdateParams::UpdateMapFromHandle(this);
    2079           1 :                 return true;
    2080             :             }
    2081             : 
    2082           0 :             m_bUseVirtualHosting = true;
    2083           0 :             CPLDebug("S3", "Switching to virtual hosting");
    2084             :         }
    2085          11 :         SetEndpoint(m_bUseVirtualHosting ? pszEndpoint + m_osBucket.size() + 1
    2086             :                                          : pszEndpoint);
    2087          11 :         CPLDebug("S3", "Switching to endpoint %s", m_osEndpoint.c_str());
    2088          11 :         CPLDestroyXMLNode(psTree);
    2089             : 
    2090          11 :         if (!bIsTemporaryRedirect)
    2091           5 :             VSIS3UpdateParams::UpdateMapFromHandle(this);
    2092             : 
    2093          11 :         return true;
    2094             :     }
    2095             : 
    2096           6 :     if (bSetError)
    2097             :     {
    2098             :         // Translate AWS errors into VSI errors.
    2099             :         const char *pszMessage =
    2100           4 :             CPLGetXMLValue(psTree, "=Error.Message", nullptr);
    2101             : 
    2102           4 :         if (pszMessage == nullptr)
    2103             :         {
    2104           2 :             VSIError(VSIE_AWSError, "%s", pszErrorMsg);
    2105             :         }
    2106           2 :         else if (EQUAL(pszCode, "AccessDenied"))
    2107             :         {
    2108           0 :             VSIError(VSIE_AWSAccessDenied, "%s", pszMessage);
    2109             :         }
    2110           2 :         else if (EQUAL(pszCode, "NoSuchBucket"))
    2111             :         {
    2112           0 :             VSIError(VSIE_AWSBucketNotFound, "%s", pszMessage);
    2113             :         }
    2114           2 :         else if (EQUAL(pszCode, "NoSuchKey"))
    2115             :         {
    2116           0 :             VSIError(VSIE_AWSObjectNotFound, "%s", pszMessage);
    2117             :         }
    2118           2 :         else if (EQUAL(pszCode, "SignatureDoesNotMatch"))
    2119             :         {
    2120           0 :             VSIError(VSIE_AWSSignatureDoesNotMatch, "%s", pszMessage);
    2121             :         }
    2122             :         else
    2123             :         {
    2124           2 :             VSIError(VSIE_AWSError, "%s", pszMessage);
    2125             :         }
    2126             :     }
    2127             : 
    2128           6 :     CPLDestroyXMLNode(psTree);
    2129             : 
    2130           6 :     return false;
    2131             : }
    2132             : 
    2133             : /************************************************************************/
    2134             : /*                          SetEndpoint()                          */
    2135             : /************************************************************************/
    2136             : 
    2137          64 : void VSIS3HandleHelper::SetEndpoint(const std::string &osStr)
    2138             : {
    2139          64 :     m_osEndpoint = osStr;
    2140          64 :     RebuildURL();
    2141          64 : }
    2142             : 
    2143             : /************************************************************************/
    2144             : /*                           SetRegion()                             */
    2145             : /************************************************************************/
    2146             : 
    2147          61 : void VSIS3HandleHelper::SetRegion(const std::string &osStr)
    2148             : {
    2149          61 :     m_osRegion = osStr;
    2150          61 : }
    2151             : 
    2152             : /************************************************************************/
    2153             : /*                           SetRequestPayer()                          */
    2154             : /************************************************************************/
    2155             : 
    2156          52 : void VSIS3HandleHelper::SetRequestPayer(const std::string &osStr)
    2157             : {
    2158          52 :     m_osRequestPayer = osStr;
    2159          52 : }
    2160             : 
    2161             : /************************************************************************/
    2162             : /*                         SetVirtualHosting()                          */
    2163             : /************************************************************************/
    2164             : 
    2165          52 : void VSIS3HandleHelper::SetVirtualHosting(bool b)
    2166             : {
    2167          52 :     m_bUseVirtualHosting = b;
    2168          52 :     RebuildURL();
    2169          52 : }
    2170             : 
    2171             : /************************************************************************/
    2172             : /*                           GetSignedURL()                             */
    2173             : /************************************************************************/
    2174             : 
    2175           5 : std::string VSIS3HandleHelper::GetSignedURL(CSLConstList papszOptions)
    2176             : {
    2177          10 :     std::string osPathForOption("/vsis3/");
    2178           5 :     osPathForOption += m_osBucket;
    2179           5 :     osPathForOption += '/';
    2180           5 :     osPathForOption += m_osObjectKey;
    2181             : 
    2182             :     std::string osXAMZDate = CSLFetchNameValueDef(
    2183             :         papszOptions, "START_DATE",
    2184          10 :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_TIMESTAMP", ""));
    2185           5 :     if (osXAMZDate.empty())
    2186           0 :         osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
    2187          10 :     std::string osDate(osXAMZDate);
    2188           5 :     osDate.resize(8);
    2189             : 
    2190             :     std::string osXAMZExpires =
    2191          10 :         CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600");
    2192             : 
    2193           5 :     if (m_eCredentialsSource != AWSCredentialsSource::REGULAR)
    2194             :     {
    2195             :         // For credentials that have an expiration, we must check their
    2196             :         // expiration compared to the expiration of the signed URL, since
    2197             :         // if the effective expiration is min(desired_expiration,
    2198             :         // credential_expiration) Cf
    2199             :         // https://aws.amazon.com/premiumsupport/knowledge-center/presigned-url-s3-bucket-expiration
    2200           2 :         int nYear, nMonth, nDay, nHour = 0, nMin = 0, nSec = 0;
    2201           2 :         if (sscanf(osXAMZDate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &nYear,
    2202           2 :                    &nMonth, &nDay, &nHour, &nMin, &nSec) < 3)
    2203             :         {
    2204           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Bad format for START_DATE");
    2205           0 :             return std::string();
    2206             :         }
    2207             :         struct tm brokendowntime;
    2208           2 :         brokendowntime.tm_year = nYear - 1900;
    2209           2 :         brokendowntime.tm_mon = nMonth - 1;
    2210           2 :         brokendowntime.tm_mday = nDay;
    2211           2 :         brokendowntime.tm_hour = nHour;
    2212           2 :         brokendowntime.tm_min = nMin;
    2213           2 :         brokendowntime.tm_sec = nSec;
    2214           2 :         const GIntBig nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
    2215             : 
    2216             :         {
    2217           4 :             CPLMutexHolder oHolder(&ghMutex);
    2218             : 
    2219             :             // Try to reuse credentials if they will still be valid after the
    2220             :             // desired end of the validity of the signed URL,
    2221             :             // with one minute of margin
    2222           2 :             if (nStartDate + CPLAtoGIntBig(osXAMZExpires.c_str()) >=
    2223           2 :                 gnGlobalExpiration - 60)
    2224             :             {
    2225           1 :                 RefreshCredentials(osPathForOption, /* bForceRefresh = */ true);
    2226             :             }
    2227             :         }
    2228             :     }
    2229             : 
    2230          10 :     std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
    2231             : 
    2232           5 :     ResetQueryParameters();
    2233           5 :     AddQueryParameter("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
    2234          15 :     AddQueryParameter("X-Amz-Credential", m_osAccessKeyId + "/" + osDate + "/" +
    2235          15 :                                               m_osRegion + "/s3/aws4_request");
    2236           5 :     AddQueryParameter("X-Amz-Date", osXAMZDate);
    2237           5 :     AddQueryParameter("X-Amz-Expires", osXAMZExpires);
    2238           5 :     if (!m_osSessionToken.empty())
    2239           1 :         AddQueryParameter("X-Amz-Security-Token", m_osSessionToken);
    2240           5 :     AddQueryParameter("X-Amz-SignedHeaders", "host");
    2241             : 
    2242          10 :     std::string osCanonicalQueryString(GetQueryString(true).substr(1));
    2243             : 
    2244           0 :     const std::string osHost(m_bUseVirtualHosting && !m_osBucket.empty()
    2245           5 :                                  ? std::string(m_osBucket + "." + m_osEndpoint)
    2246          10 :                                  : m_osEndpoint);
    2247          10 :     std::string osSignedHeaders;
    2248             :     const std::string osSignature = CPLGetAWS_SIGN4_Signature(
    2249           5 :         m_osSecretAccessKey,
    2250           5 :         std::string(),  // sessionToken set to empty as we include it in query
    2251             :                         // parameters
    2252           5 :         m_osRegion, m_osRequestPayer, "s3", osVerb,
    2253             :         nullptr, /* existing headers */
    2254             :         osHost,
    2255           5 :         m_bUseVirtualHosting
    2256           5 :             ? CPLAWSURLEncode("/" + m_osObjectKey, false).c_str()
    2257          25 :             : CPLAWSURLEncode("/" + m_osBucket + "/" + m_osObjectKey, false)
    2258           5 :                   .c_str(),
    2259             :         osCanonicalQueryString, "UNSIGNED-PAYLOAD",
    2260             :         false,  // bAddHeaderAMZContentSHA256
    2261          30 :         osXAMZDate, osSignedHeaders);
    2262             : 
    2263           5 :     AddQueryParameter("X-Amz-Signature", osSignature);
    2264           5 :     return m_osURL;
    2265             : }
    2266             : 
    2267             : /************************************************************************/
    2268             : /*                        UpdateMapFromHandle()                         */
    2269             : /************************************************************************/
    2270             : 
    2271             : std::mutex VSIS3UpdateParams::gsMutex{};
    2272             : 
    2273             : std::map<std::string, VSIS3UpdateParams>
    2274             :     VSIS3UpdateParams::goMapBucketsToS3Params{};
    2275             : 
    2276          14 : void VSIS3UpdateParams::UpdateMapFromHandle(VSIS3HandleHelper *poS3HandleHelper)
    2277             : {
    2278          14 :     std::lock_guard<std::mutex> guard(gsMutex);
    2279             : 
    2280          14 :     goMapBucketsToS3Params[poS3HandleHelper->GetBucket()] =
    2281          28 :         VSIS3UpdateParams(poS3HandleHelper);
    2282          14 : }
    2283             : 
    2284             : /************************************************************************/
    2285             : /*                         UpdateHandleFromMap()                        */
    2286             : /************************************************************************/
    2287             : 
    2288         461 : void VSIS3UpdateParams::UpdateHandleFromMap(VSIS3HandleHelper *poS3HandleHelper)
    2289             : {
    2290         923 :     std::lock_guard<std::mutex> guard(gsMutex);
    2291             : 
    2292             :     std::map<std::string, VSIS3UpdateParams>::iterator oIter =
    2293         462 :         goMapBucketsToS3Params.find(poS3HandleHelper->GetBucket());
    2294         462 :     if (oIter != goMapBucketsToS3Params.end())
    2295             :     {
    2296          52 :         oIter->second.UpdateHandlerHelper(poS3HandleHelper);
    2297             :     }
    2298         462 : }
    2299             : 
    2300             : /************************************************************************/
    2301             : /*                            ClearCache()                              */
    2302             : /************************************************************************/
    2303             : 
    2304        1531 : void VSIS3UpdateParams::ClearCache()
    2305             : {
    2306        3062 :     std::lock_guard<std::mutex> guard(gsMutex);
    2307             : 
    2308        1531 :     goMapBucketsToS3Params.clear();
    2309        1531 : }
    2310             : 
    2311             : #endif
    2312             : 
    2313             : //! @endcond

Generated by: LCOV version 1.14