VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/rest/RTCRestClientApiBaseOci.cpp@ 74052

Last change on this file since 74052 was 74052, checked in by vboxsync, 6 years ago

IPRT/rest: Started implementing http-signatures for oci. bugref:9167

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 13.3 KB
Line 
1/* $Id: RTCRestClientApiBaseOci.cpp 74052 2018-09-03 20:09:45Z vboxsync $ */
2/** @file
3 * IPRT - C++ REST, RTCRestClientApiBase implementation, OCI specific bits.
4 */
5
6/*
7 * Copyright (C) 2018 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#define LOG_GROUP RTLOGGROUP_REST
32#include <iprt/cpp/restclient.h>
33
34#include <iprt/base64.h>
35#include <iprt/err.h>
36#include <iprt/http.h>
37#include <iprt/log.h>
38#include <iprt/uri.h>
39#include <iprt/sha.h>
40#include <iprt/crypto/digest.h>
41#include <iprt/crypto/pkix.h>
42
43
44/**
45 * Worker for ociSignRequestAddAllFields().
46 */
47static int ociSignRequestAddField(RTCRDIGEST hDigest, RTCString *pStrAuth, const char *pszField, size_t cchField,
48 const char *pszValue, size_t cchValue)
49{
50 /* First? */
51 bool const fFirst = pStrAuth->endsWith("\"");
52
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);
64
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;
85}
86
87
88/**
89 * Worker for ociSignRequest().
90 */
91static 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)
94{
95 char szTmp[256];
96
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. */
100
101 /*
102 * (request-target) and host.
103 */
104 RTURIPARSED ParsedUrl;
105 int rc = RTUriParse(a_rStrFullUrl.c_str(), &ParsedUrl);
106 AssertRCReturn(rc, rc);
107
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));
130
131 rc = ociSignRequestAddField(hDigest,pStrAuth, RT_STR_TUPLE("host"),
132 a_rStrFullUrl.c_str() + ParsedUrl.offAuthorityHost, ParsedUrl.cchAuthorityHost);
133 AssertRCReturn(rc, rc);
134
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 int rc = RTHttpAppendHeader(a_hHttp, s_szContentLength, szTmp, 0);
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 int rc = RTHttpAppendHeader(a_hHttp, s_szXContentSha256, szTmp, 0);
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 int rc = RTHttpAppendHeader(a_hHttp, s_szXDate, szTmp, 0);
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;
241}
242
243
244int RTCRestClientApiBase::ociSignRequest(RTHTTP a_hHttp, RTCString const &a_rStrFullUrl, RTHTTPMETHOD a_enmHttpMethod,
245 RTCString const &a_rStrXmitBody, uint32_t a_fFlags,
246 RTCRKEY a_hKey, RTCString const &a_rStrKeyId)
247{
248 /*
249 * Start the signature.
250 */
251 RTCString strAuth;
252 int rc = strAuth.printfNoThrow("Signature version=\"1\",keyId=\"%s\",algorithm=\"rsa-sha256\",headers=\"",
253 a_rStrKeyId.c_str());
254 if (RT_SUCCESS(rc))
255 {
256 RTCRDIGEST hDigest;
257 rc = RTCrDigestCreateByType(&hDigest, RTDIGESTTYPE_SHA256);
258 if (RT_SUCCESS(rc))
259 {
260 /*
261 * Call worker for adding the fields. Simpler to clean up hDigest this way.
262 */
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 = RTHttpAppendHeader(a_hHttp, "Authorization", strAuth.c_str(), 0);
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);
321 }
322 }
323 return rc;
324}
325
Note: See TracBrowser for help on using the repository browser.

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