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

Generated by: LCOV version 1.14