VirtualBox

Changeset 74070 in vbox for trunk/src/VBox/Runtime


Ignore:
Timestamp:
Sep 4, 2018 3:17:01 PM (6 years ago)
Author:
vboxsync
Message:

IPRT/rest: Redid the OCI-style signing code. Added a value length to RTHttpAddHeader so it's possible to deal efficiently with the 'host' field given an URL. bugref:9167

Location:
trunk/src/VBox/Runtime
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Runtime/common/rest/RTCRestClientApiBaseOci.cpp

    r74060 r74070  
    3232#include <iprt/cpp/restclient.h>
    3333
     34#include <iprt/assert.h>
    3435#include <iprt/base64.h>
    3536#include <iprt/err.h>
    3637#include <iprt/http.h>
    3738#include <iprt/log.h>
     39#include <iprt/sha.h>
     40#include <iprt/time.h>
    3841#include <iprt/uri.h>
    39 #include <iprt/sha.h>
    40 #include <iprt/crypto/digest.h>
    41 #include <iprt/crypto/pkix.h>
     42
    4243
    4344
    4445/**
    45  * Worker for ociSignRequestAddAllFields().
     46 * Ensures that we've got a 'Content-Length' header.
     47 *
     48 * @returns IPRT status code.
     49 * @param   hHttp       The HTTP client handle.
     50 * @param   pvContent
    4651 */
    47 static int ociSignRequestAddField(RTCRDIGEST hDigest, RTCString *pStrAuth, const char *pszField, size_t cchField,
    48                                   const char *pszValue, size_t cchValue)
     52static int ociSignRequestEnsureDateOrXDate(RTHTTP hHttp)
    4953{
    50     /* First? */
    51     bool const fFirst = pStrAuth->endsWith("\"");
     54    if (RTHttpGetHeader(hHttp, RT_STR_TUPLE("x-date")))
     55        return VINF_SUCCESS;
     56    if (RTHttpGetHeader(hHttp, RT_STR_TUPLE("date")))
     57        return VINF_SUCCESS;
    5258
    53     /* Append the field to the list, in lowercased form: */
    54     int rc = VINF_SUCCESS;
    55     if (!fFirst)
    56         rc = pStrAuth->appendNoThrow(' ');
    57     if (RT_SUCCESS(rc))
    58     {
    59         size_t offStart = pStrAuth->length();
    60         rc = pStrAuth->appendNoThrow(pszField, cchField);
    61         if (RT_SUCCESS(rc))
    62         {
    63             RTStrToLower(pStrAuth->mutableRaw() + offStart);
     59    RTTIMESPEC NowSpec;
     60    RTTIME     Now;
     61    char       szDate[RTTIME_RTC2822_LEN];
     62    ssize_t cch = RTTimeToRfc2822(RTTimeExplode(&Now, RTTimeNow(&NowSpec)), szDate, sizeof(szDate));
     63    AssertRCReturn((int)cch, (int)cch);
    6464
    65             /* 2.3 (3) If not the first field, add newline separator. */
    66             rc = RTCrDigestUpdate(hDigest, "\n", 1);
    67 
    68 
    69             /* Update the digest with the field name followed by ': ' */
    70             rc = RTCrDigestUpdate(hDigest, pStrAuth->c_str() + offStart, pStrAuth->length() - offStart);
    71             if (RT_SUCCESS(rc))
    72             {
    73                 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(": "));
    74                 if (RT_SUCCESS(rc))
    75                 {
    76                     /* Update the digest with the field value: */
    77                     if (cchValue == RTSTR_MAX)
    78                         cchValue = strlen(pszValue);
    79                     rc = RTCrDigestUpdate(hDigest, pszValue, cchValue);
    80                 }
    81             }
    82         }
    83     }
    84     return rc;
     65    return RTHttpAddHeader(hHttp, "x-date", szDate, cch, RTHTTPADDHDR_F_BACK);
    8566}
    8667
    8768
    8869/**
    89  * Worker for ociSignRequest().
     70 * Ensures that we've got a 'x-content-sha256' header.
     71 *
     72 * @returns IPRT status code.
     73 * @param   hHttp       The HTTP client handle.
     74 * @param   pvContent
    9075 */
    91 static int ociSignRequestAddAllFields(RTHTTP a_hHttp, RTCString const &a_rStrFullUrl, RTHTTPMETHOD a_enmHttpMethod,
    92                                       RTCString const &a_rStrXmitBody, uint32_t a_fFlags,
    93                                       RTCRDIGEST hDigest, RTCString *pStrAuth)
     76static int ociSignRequestEnsureXContentSha256(RTHTTP hHttp, void const *pvContent, size_t cbContent)
    9477{
    95     char szTmp[256];
     78    if (RTHttpGetHeader(hHttp, RT_STR_TUPLE("x-content-sha256")))
     79        return VINF_SUCCESS;
    9680
    97     /** @todo This is a little ugly.  I think that instead of this uglyness, we should just
    98      *  ensure the presence of required fields and sign all headers.  That way all parameters
    99      *  will be covered by the signature. */
     81#ifdef RT_STRICT
     82    const char *pszContentLength = RTHttpGetHeader(hHttp, RT_STR_TUPLE("Content-Length"));
     83    Assert(pszContentLength);
     84    AssertMsg(!pszContentLength || RTStrToUInt64(pszContentLength) == cbContent, ("'%s' vs %RU64\n", pszContentLength, cbContent));
     85#endif
    10086
    101     /*
    102      * (request-target) and host.
    103      */
    104     RTURIPARSED ParsedUrl;
    105     int rc = RTUriParse(a_rStrFullUrl.c_str(), &ParsedUrl);
     87    uint8_t abHash[RTSHA256_HASH_SIZE];
     88    RTSha256(pvContent, cbContent, abHash);
     89
     90    char szBase64[RTSHA256_DIGEST_LEN + 1]; /* (base64 should be shorter) */
     91    int rc = RTBase64EncodeEx(abHash, sizeof(abHash), RTBASE64_FLAGS_NO_LINE_BREAKS, szBase64, sizeof(szBase64), NULL);
    10692    AssertRCReturn(rc, rc);
    10793
    108     const char *pszMethod = NULL;
    109     switch (a_enmHttpMethod)
    110     {
    111         case RTHTTPMETHOD_GET:      pszMethod = "get "; break;
    112         case RTHTTPMETHOD_PUT:      pszMethod = "put "; break;
    113         case RTHTTPMETHOD_POST:     pszMethod = "post "; break;
    114         case RTHTTPMETHOD_PATCH:    pszMethod = "patch "; break;
    115         case RTHTTPMETHOD_DELETE:   pszMethod = "delete "; break;
    116         case RTHTTPMETHOD_HEAD:     pszMethod = "head "; break;
    117         case RTHTTPMETHOD_OPTIONS:  pszMethod = "options "; break;
    118         case RTHTTPMETHOD_TRACE:    pszMethod = "trace "; break;
    119         case RTHTTPMETHOD_END:
    120         case RTHTTPMETHOD_INVALID:
    121         case RTHTTPMETHOD_32BIT_HACK:
    122             break;
    123     }
    124     AssertReturn(pszMethod, VERR_REST_INTERAL_ERROR_6);
    125     rc = ociSignRequestAddField(hDigest, pStrAuth, RT_STR_TUPLE("(request-target)"),
    126                                 pszMethod, strlen(pszMethod));
    127     AssertRCReturn(rc, rc);
    128     const char *pszValue = a_rStrFullUrl.c_str() + ParsedUrl.offPath; /* Add the path. */
    129     rc = RTCrDigestUpdate(hDigest, pszValue, strlen(pszValue));
     94    return RTHttpAddHeader(hHttp, "x-content-sha256", szBase64, RTSTR_MAX, RTHTTPADDHDR_F_BACK);
     95}
    13096
    131     rc = ociSignRequestAddField(hDigest,pStrAuth, RT_STR_TUPLE("host"),
    132                                 a_rStrFullUrl.c_str() + ParsedUrl.offAuthorityHost, ParsedUrl.cchAuthorityHost);
     97
     98/**
     99 * Ensures that we've got a 'Content-Length' header.
     100 *
     101 * @returns IPRT status code.
     102 * @param   hHttp       The HTTP client handle.
     103 * @param   cbContent   The content length.
     104 */
     105static int ociSignRequestEnsureContentLength(RTHTTP hHttp, uint64_t cbContent)
     106{
     107    if (RTHttpGetHeader(hHttp, RT_STR_TUPLE("Content-Length")))
     108        return VINF_SUCCESS;
     109    char    szValue[64];
     110    ssize_t cchValue = RTStrFormatU64(szValue, sizeof(szValue), cbContent, 10, 0, 0, 0);
     111    AssertRCReturn((int)cchValue, (int)cchValue);
     112    return RTHttpAddHeader(hHttp, "Content-Length", szValue, cchValue, RTHTTPADDHDR_F_BACK);
     113}
     114
     115
     116/**
     117 * Ensures that we've got a host header.
     118 *
     119 * @returns IPRT status code.
     120 * @param   hHttp       The HTTP client handle.
     121 * @param   pszUrl      The URL.
     122 */
     123static int ociSignRequestEnsureHost(RTHTTP hHttp, const char *pszUrl)
     124{
     125    if (RTHttpGetHeader(hHttp, RT_STR_TUPLE("host")))
     126        return VINF_SUCCESS;
     127
     128    RTURIPARSED ParsedUrl;
     129    int rc = RTUriParse(pszUrl, &ParsedUrl);
    133130    AssertRCReturn(rc, rc);
    134131
    135     /*
    136      * Content-Length - required for POST and PUT.
    137      * Note! We add it to the digest a little bit later to preserve documented order..
    138      */
    139     static char s_szContentLength[] = "content-length";
    140     const char *pszContentLength = RTHttpGetHeader(a_hHttp, RT_STR_TUPLE(s_szContentLength));
    141     if (   !pszContentLength
    142         && (   a_rStrXmitBody.isNotEmpty()
    143             || a_enmHttpMethod == RTHTTPMETHOD_POST
    144             || a_enmHttpMethod == RTHTTPMETHOD_PUT))
    145     {
    146         RTStrPrintf(szTmp, sizeof(szTmp), "%zu", a_rStrXmitBody.length());
    147 
    148         rc = RTHttpAddHeader(a_hHttp, s_szContentLength, szTmp, RTHTTPADDHDR_F_BACK);
    149         AssertRCReturn(rc, rc);
    150         pszContentLength = RTHttpGetHeader(a_hHttp, RT_STR_TUPLE(s_szContentLength));
    151         AssertPtrReturn(pszContentLength, VERR_REST_INTERAL_ERROR_4);
    152     }
    153     if (pszContentLength)
    154     {
    155         rc = ociSignRequestAddField(hDigest, pStrAuth, RT_STR_TUPLE(s_szContentLength), pszContentLength, RTSTR_MAX);
    156         AssertRCReturn(rc, rc);
    157     }
    158 
    159     /*
    160      * x-content-sha256 - required when there is a body.
    161      */
    162     static char s_szXContentSha256[] = "x-content-sha256";
    163     pszValue = RTHttpGetHeader(a_hHttp, RT_STR_TUPLE(s_szXContentSha256));
    164     if (   !pszValue
    165         && pszContentLength
    166         && strcmp(pszContentLength, "0") != 0
    167         && !(a_fFlags & RTCRestClientApiBase::kDoCall_OciReqSignExcludeBody) )
    168     {
    169 #ifdef RT_STRICT
    170         RTStrPrintf(szTmp, sizeof(szTmp), "%zu", a_rStrXmitBody.length());
    171         AssertMsgReturn(strcmp(szTmp, pszContentLength) == 0, ("szTmp=%s; pszContentLength=%s\n", szTmp, pszContentLength),
    172                         VERR_REST_INTERAL_ERROR_5);
    173 #endif
    174 
    175         uint8_t abHash[RTSHA256_HASH_SIZE];
    176         RTSha256(a_rStrXmitBody.c_str(), a_rStrXmitBody.length(), abHash);
    177         rc = RTBase64EncodeEx(abHash, sizeof(abHash), RTBASE64_FLAGS_NO_LINE_BREAKS, szTmp, sizeof(szTmp), NULL);
    178 
    179         rc = RTHttpAddHeader(a_hHttp, s_szXContentSha256, szTmp, RTHTTPADDHDR_F_BACK);
    180         AssertRCReturn(rc, rc);
    181         pszValue = RTHttpGetHeader(a_hHttp, RT_STR_TUPLE(s_szXContentSha256));
    182         AssertPtrReturn(pszValue, VERR_REST_INTERAL_ERROR_4);
    183     }
    184     if (pszValue)
    185     {
    186         rc = ociSignRequestAddField(hDigest, pStrAuth, RT_STR_TUPLE(s_szXContentSha256), pszValue, RTSTR_MAX);
    187         AssertRCReturn(rc, rc);
    188     }
    189 
    190     /*
    191      * Content-Type
    192      */
    193     pszValue = RTHttpGetHeader(a_hHttp, RT_STR_TUPLE("content-type"));
    194     Assert(   pszValue
    195            || !pszContentLength
    196            || strcmp(pszContentLength, "0") == 0);
    197     if (pszValue)
    198     {
    199         rc = ociSignRequestAddField(hDigest, pStrAuth, RT_STR_TUPLE("content-type"), pszValue, RTSTR_MAX);
    200         AssertRCReturn(rc, rc);
    201     }
    202 
    203     /*
    204      * x-date or/and date.
    205      */
    206     static char const s_szDate[] = "date";
    207     pszValue = RTHttpGetHeader(a_hHttp, RT_STR_TUPLE(s_szDate));
    208     static char const s_szXDate[] = "x-date";
    209     const char *pszXDate = RTHttpGetHeader(a_hHttp, RT_STR_TUPLE(s_szXDate));
    210     if (!pszValue && !pszXDate)
    211     {
    212         RTTIMESPEC TimeSpec;
    213         RTTIME     Time;
    214         RT_ZERO(Time); /* paranoia */
    215         RTTimeExplode(&Time, RTTimeNow(&TimeSpec));
    216 
    217         /* Date format: Tue, 16 Nov 2018 12:15:00 GMT */
    218         /** @todo make RTTimeXxx api that does exactly this (RFC-1123). */
    219         static const char * const s_apszWeekDays[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
    220         static const char * const s_apszMonths[] = { "000", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
    221         RTStrPrintf(szTmp, sizeof(szTmp), "%s, %u %s %u %u02:%u02:%u02 GMT",
    222                     s_apszWeekDays[Time.u8WeekDay], Time.u8MonthDay, s_apszMonths[Time.u8Month], Time.i32Year,
    223                     Time.u8Hour, Time.u8Minute, Time.u8Second);
    224 
    225         rc = RTHttpAddHeader(a_hHttp, s_szXDate, szTmp, RTHTTPADDHDR_F_BACK);
    226         AssertRCReturn(rc, rc);
    227         pszXDate = RTHttpGetHeader(a_hHttp, RT_STR_TUPLE(s_szXDate));
    228         AssertPtrReturn(pszXDate, VERR_REST_INTERAL_ERROR_4);
    229     }
    230     if (pszXDate)
    231         rc = ociSignRequestAddField(hDigest, pStrAuth, RT_STR_TUPLE(s_szXDate), pszXDate, RTSTR_MAX);
    232     else
    233         rc = ociSignRequestAddField(hDigest, pStrAuth, RT_STR_TUPLE(s_szDate), pszValue, RTSTR_MAX);
    234     AssertRCReturn(rc, rc);
    235 
    236     /*
    237      * We probably should add all parameter fields ...
    238      */
    239     /** @todo sign more header fields */
    240     return VINF_SUCCESS;
     132    return RTHttpAddHeader(hHttp, "host", &pszUrl[ParsedUrl.offAuthorityHost], ParsedUrl.cchAuthorityHost, RTHTTPADDHDR_F_BACK);
    241133}
    242134
     
    247139{
    248140    /*
    249      * Start the signature.
     141     * First make sure required headers are present, adding them as needed.
    250142     */
    251     RTCString strAuth;
    252     int rc = strAuth.printfNoThrow("Signature version=\"1\",keyId=\"%s\",algorithm=\"rsa-sha256\",headers=\"",
    253                                    a_rStrKeyId.c_str());
     143    int rc = ociSignRequestEnsureHost(a_hHttp, a_rStrFullUrl.c_str());
    254144    if (RT_SUCCESS(rc))
    255145    {
    256         RTCRDIGEST hDigest;
    257         rc = RTCrDigestCreateByType(&hDigest, RTDIGESTTYPE_SHA256);
     146        if (   a_rStrXmitBody.isNotEmpty()
     147            || a_enmHttpMethod == RTHTTPMETHOD_POST
     148            || a_enmHttpMethod == RTHTTPMETHOD_PUT)
     149            rc = ociSignRequestEnsureContentLength(a_hHttp, a_rStrXmitBody.length());
     150        if (   RT_SUCCESS(rc)
     151            && a_rStrXmitBody.isNotEmpty())
     152            rc = ociSignRequestEnsureXContentSha256(a_hHttp, a_rStrXmitBody.c_str(), a_rStrXmitBody.length());
     153        if (RT_SUCCESS(rc))
     154            rc = ociSignRequestEnsureDateOrXDate(a_hHttp);
    258155        if (RT_SUCCESS(rc))
    259156        {
    260157            /*
    261              * Call worker for adding the fields.  Simpler to clean up hDigest this way.
     158             * Do the signing.
    262159             */
    263             rc = ociSignRequestAddAllFields(a_hHttp, a_rStrFullUrl, a_enmHttpMethod, a_rStrXmitBody, a_fFlags, hDigest, &strAuth);
    264             if (RT_SUCCESS(rc))
    265             {
    266                 /*
    267                  * Do the signing.
    268                  */
    269                 RTCRPKIXSIGNATURE hSigner;
    270                 rc = RTCrPkixSignatureCreateByObjIdString(&hSigner, RTCR_PKCS1_SHA256_WITH_RSA_OID, a_hKey,
    271                                                           NULL, true /*fSigning*/);
    272                 AssertRC(rc);
    273                 if (RT_SUCCESS(rc))
    274                 {
    275                     /* Figure the signature size first. */
    276                     size_t cbSignature = 0;
    277                     RTCrPkixSignatureSign(hSigner, hDigest, NULL, &cbSignature);
    278                     if (cbSignature == 0)
    279                         cbSignature = _32K;
    280                     size_t cbBase64Sign = RTBase64EncodedLengthEx(cbSignature, RTBASE64_FLAGS_NO_LINE_BREAKS) + 2;
    281 
    282                     /* Allocate temporary heap buffer and calc the signature. */
    283                     uint8_t *pbSignature = (uint8_t *)RTMemTmpAllocZ(cbSignature + cbBase64Sign);
    284                     if (pbSignature)
    285                     {
    286                         size_t cbActual = cbSignature;
    287                         rc = RTCrPkixSignatureSign(hSigner, hDigest, pbSignature, &cbActual);
    288                         AssertRC(rc);
    289                         if (RT_SUCCESS(rc))
    290                         {
    291                             /*
    292                              * Convert the signature to Base64 and add it to the auth value.
    293                              */
    294                             char *pszBase64 = (char *)(pbSignature + cbSignature);
    295                             rc = RTBase64EncodeEx(pbSignature, cbActual, RTBASE64_FLAGS_NO_LINE_BREAKS,
    296                                                   pszBase64, cbBase64Sign, NULL);
    297                             AssertRC(rc);
    298                             if (RT_SUCCESS(rc))
    299                             {
    300                                 rc = strAuth.appendPrintfNoThrow("\",signature=\"Base64(RSA-SHA256(%s))\"", pszBase64);
    301                                 if (RT_SUCCESS(rc))
    302                                 {
    303                                     /*
    304                                      * Finally, add the authorization header.
    305                                      */
    306                                     rc = RTHttpAddHeader(a_hHttp, "Authorization", strAuth.c_str(), RTHTTPADDHDR_F_FRONT);
    307                                     AssertRC(rc);
    308                                 }
    309                             }
    310                         }
    311                         RT_BZERO(pbSignature, cbSignature + cbBase64Sign);
    312                         RTMemTmpFree(pbSignature);
    313                     }
    314                     else
    315                         rc = VERR_NO_TMP_MEMORY;
    316                     uint32_t cRefs = RTCrPkixSignatureRelease(hSigner);
    317                     Assert(cRefs == 0); NOREF(cRefs);
    318                 }
    319             }
    320             RTCrDigestRelease(hDigest);
     160            rc = RTHttpSignHeaders(a_hHttp, a_enmHttpMethod, a_rStrFullUrl.c_str(), a_hKey, a_rStrKeyId.c_str(), 0 /*fFlags*/);
    321161        }
    322162    }
     163    RT_NOREF_PV(a_fFlags); /* We don't need to use the kDoCall_OciReqSignExcludeBody flag it turns out. */
    323164    return rc;
    324165}
  • trunk/src/VBox/Runtime/common/rest/RTCRestClientRequestBase.cpp

    r74060 r74070  
    193193                AssertRCReturn(rc, rc);
    194194
    195                 rc = RTHttpAddHeader(a_hHttp, a_paHeaderParams[i].pszName, strTmpVal.c_str(), RTHTTPADDHDR_F_BACK);
     195                rc = RTHttpAddHeader(a_hHttp, a_paHeaderParams[i].pszName, strTmpVal.c_str(), strTmpVal.length(),
     196                                     RTHTTPADDHDR_F_BACK);
    196197                AssertRCReturn(rc, rc);
    197198            }
     
    217218                    AssertRCReturn(rc, rc);
    218219
    219                     rc = RTHttpAddHeader(a_hHttp, strTmpName.c_str(), strTmpVal.c_str(), RTHTTPADDHDR_F_BACK);
     220                    rc = RTHttpAddHeader(a_hHttp, strTmpName.c_str(), strTmpVal.c_str(), strTmpVal.length(),
     221                                         RTHTTPADDHDR_F_BACK);
    220222                    AssertRCReturn(rc, rc);
    221223                }
  • trunk/src/VBox/Runtime/generic/http-curl.cpp

    r74064 r74070  
    21532153
    21542154
    2155 RTR3DECL(int) RTHttpAddHeader(RTHTTP hHttp, const char *pszField, const char *pszValue, uint32_t fFlags)
     2155RTR3DECL(int) RTHttpAddHeader(RTHTTP hHttp, const char *pszField, const char *pszValue, size_t cchValue, uint32_t fFlags)
    21562156{
    21572157    /*
     
    21752175
    21762176    AssertPtr(pszValue);
    2177     size_t const cchValue = strlen(pszValue);
     2177    if (cchValue == RTSTR_MAX)
     2178        cchValue = strlen(pszValue);
    21782179
    21792180    /*
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette