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

Generated by: LCOV version 1.14