Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
yubihsm_winhttp.c
Go to the documentation of this file.
1/*
2 * Copyright 2015-2018 Yubico AB
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <windows.h>
18#include <winhttp.h>
19#include <winsock.h>
20#include <wchar.h>
21#include <stdbool.h>
22#include <stdint.h>
23
24#include "yubihsm.h"
25#include "internal.h"
26#include "debug_lib.h"
27
28#define MAX_STR_LEN 128
29
31 bool https;
32 wchar_t hostname[MAX_STR_LEN + 1];
33 int port;
34 wchar_t path[MAX_STR_LEN + 1];
35};
36
51
52struct context {
54 CRITICAL_SECTION mtx;
55 HINTERNET req;
57};
58
59struct state {
60 HINTERNET internet;
61 HINTERNET con;
64};
65
68
69static bool parseUrl(char *url, struct urlComponents *components) {
70 wchar_t wUrl[129];
71 size_t len = strlen(url);
72
73 if (len > 128) {
74 return false;
75 }
76 mbstowcs(wUrl, url, len);
77 URL_COMPONENTS c = {0};
78 c.dwStructSize = sizeof(c);
79 c.dwSchemeLength = -1;
80 c.dwHostNameLength = -1;
81 c.dwUrlPathLength = -1;
82
83 if (WinHttpCrackUrl(wUrl, len, 0, &c) != TRUE) {
84 return false;
85 }
86
87 if (c.nScheme == INTERNET_SCHEME_HTTPS) {
88 components->https = true;
89 } else {
90 components->https = false;
91 }
92
93 if (c.dwHostNameLength > MAX_STR_LEN || c.dwUrlPathLength > MAX_STR_LEN) {
94 return false;
95 }
96 wcsncpy_s(components->hostname,
97 sizeof(components->hostname) / sizeof(components->hostname[0]),
98 c.lpszHostName, c.dwHostNameLength);
99 wcsncpy_s(components->path,
100 sizeof(components->path) / sizeof(components->path[0]),
101 c.lpszUrlPath, c.dwUrlPathLength);
102 components->port = c.nPort;
103 return true;
104}
105
106static void CALLBACK http_callback(HINTERNET internet __attribute__((unused)),
107 DWORD_PTR context, DWORD status,
108 LPVOID statusInfo, DWORD statusInfoLen) {
109 struct context *c = (struct context *) context;
110 enum stage new_stage = NO_INIT;
111 EnterCriticalSection(&c->mtx);
112 switch (status) {
113 case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
114 DBG_INFO("sendreq complete");
115 new_stage = REQUEST_SENT;
116 break;
117 case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
118 DBG_INFO("response received");
119 new_stage = RESPONSE_RECEIVED;
120 break;
121 case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
122 DBG_INFO("data available");
123 new_stage = DATA_AVAILABLE;
124 break;
125 case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
126 DBG_INFO("read complete");
127 new_stage = READ_COMPLETE;
128 c->len = statusInfoLen;
129 break;
130 case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: {
131 WINHTTP_ASYNC_RESULT *result = (WINHTTP_ASYNC_RESULT *) statusInfo;
132 DBG_ERR("Request error: %lu %lu", (long unsigned) result->dwResult,
133 result->dwError);
134 new_stage = REQUEST_ERROR;
135 } break;
136 case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
137 DBG_INFO("handle closing");
138 new_stage = REQUEST_CLOSED;
139 break;
140 }
141 if (new_stage > c->stage) {
142 c->stage = new_stage;
143 }
144 LeaveCriticalSection(&c->mtx);
145}
146
147static void backend_set_verbosity(uint8_t verbosity, FILE *output) {
148 _yh_verbosity = verbosity;
149 _yh_output = output;
150}
151
152static yh_rc backend_init(uint8_t verbosity, FILE *output) {
153 backend_set_verbosity(verbosity, output);
154 return YHR_SUCCESS;
155}
156
157static void backend_cleanup(void) {}
158
159static yh_backend *backend_create(void) {
160 DBG_INFO("Doing backend_create");
161 yh_backend *backend = calloc(1, sizeof(yh_backend));
162 backend->internet =
163 WinHttpOpen(L"YubiHSM WinHttp/" VERSION, WINHTTP_ACCESS_TYPE_NO_PROXY,
164 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS,
165 WINHTTP_FLAG_ASYNC);
166 backend->context = calloc(1, sizeof(struct context));
167 InitializeCriticalSection(&backend->context->mtx);
168 WinHttpSetOption(backend->internet, WINHTTP_OPTION_CONTEXT_VALUE,
169 &backend->context, sizeof(backend->context));
170 WinHttpSetStatusCallback(backend->internet, http_callback,
171 WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
172 return backend;
173}
174
175static void backend_disconnect(yh_backend *connection) {
176 WinHttpCloseHandle(connection->con);
177 WinHttpCloseHandle(connection->internet);
178 Sleep(1);
179 EnterCriticalSection(&connection->context->mtx);
180 DeleteCriticalSection(&connection->context->mtx);
181 free(connection->context);
182 free(connection);
183}
184
185static yh_rc backend_connect(yh_connector *connector, int timeout) {
188
189 ZeroMemory(buf, MAX_STR_LEN + 1);
190
191 if (timeout == 0) {
192 // TODO: what does winhttp do if it gets timeout 0?
193 timeout = 300;
194 }
195
196 struct urlComponents components = {0};
197 yh_backend *backend = connector->connection;
198 backend->connector = connector;
199 DBG_INFO("setting up connection to %s", connector->status_url);
200 if (parseUrl(connector->status_url, &components) == false) {
201 DBG_INFO("URL parsing failed.");
203 }
204 backend->con =
205 WinHttpConnect(backend->internet, components.hostname, components.port, 0);
206 backend->context->stage = NO_INIT;
207 backend->context->len = 0;
208 backend->context->req =
209 WinHttpOpenRequest(backend->con, L"GET", components.path, NULL, NULL,
210 WINHTTP_DEFAULT_ACCEPT_TYPES,
211 components.https ? WINHTTP_FLAG_SECURE : 0);
212 if (timeout > 0) {
213 WinHttpSetTimeouts(backend->context->req, timeout * 1000, timeout * 1000,
214 timeout * 1000, timeout * 1000);
215 }
216 WinHttpSendRequest(backend->context->req, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
217 WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
218
219 DWORD dwStatusCode = 0;
220 DWORD dwSize = sizeof(dwStatusCode);
221 bool complete = false;
222
223 while (!complete) {
224 enum stage new_stage = 0;
225 EnterCriticalSection(&backend->context->mtx);
226 switch (backend->context->stage) {
227 case REQUEST_SENT:
228 DBG_INFO("Request sent");
229 WinHttpReceiveResponse(backend->context->req, NULL);
230 new_stage = RESPONSE_WAITING;
231 break;
233 DBG_INFO("Response received");
234 WinHttpQueryDataAvailable(backend->context->req, NULL);
235 break;
236 case DATA_AVAILABLE:
237 DBG_INFO("Data available");
238 if (WinHttpReadData(backend->context->req, buf, MAX_STR_LEN, NULL) ==
239 FALSE) {
240 DBG_ERR("Failed request for new data: %lu", GetLastError());
241 new_stage = REQUEST_ERROR;
242 } else {
243 new_stage = DATA_WAITING;
244 }
245 break;
246 case READ_COMPLETE:
247 DBG_INFO("Read complete");
248 WinHttpQueryHeaders(backend->context->req,
249 WINHTTP_QUERY_STATUS_CODE |
250 WINHTTP_QUERY_FLAG_NUMBER,
251 WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode,
252 &dwSize, WINHTTP_NO_HEADER_INDEX);
253
254 WinHttpCloseHandle(backend->context->req);
255 if (dwStatusCode != HTTP_STATUS_OK) {
256 DBG_ERR("Got HTTP error from server: %lu", dwStatusCode);
257 new_stage = REQUEST_ERROR;
259 } else {
260 parse_status_data((char *) buf, connector);
261 new_stage = REQUEST_SUCCESS;
262 res = YHR_SUCCESS;
263 }
264 break;
265 case REQUEST_ERROR:
266 DBG_INFO("Request error");
267 case REQUEST_CLOSED:
268 new_stage = REQUEST_DONE;
269 complete = true;
270 break;
271 default:
272 break;
273 }
274 if (new_stage > backend->context->stage) {
275 backend->context->stage = new_stage;
276 }
277 LeaveCriticalSection(&backend->context->mtx);
278 }
279
280 return res;
281}
282
283static yh_rc backend_send_msg(yh_backend *connection, Msg *msg, Msg *response) {
284 struct urlComponents components = {0};
285 bool complete = false;
287 uint16_t raw_len = msg->st.len + 3;
288 DWORD dwStatusCode = 0;
289 DWORD dwSize = sizeof(dwStatusCode);
290
291 DBG_INFO("sending message to %s", connection->connector->api_url);
292 if (parseUrl(connection->connector->api_url, &components) == false) {
293 return yrc;
294 }
295
296 // swap the length in the message
297 msg->st.len = htons(msg->st.len);
298
299 connection->context->stage = NO_INIT;
300 connection->context->len = 0;
301 connection->context->req =
302 WinHttpOpenRequest(connection->con, L"POST", components.path, NULL, NULL,
303 WINHTTP_DEFAULT_ACCEPT_TYPES,
304 components.https ? WINHTTP_FLAG_SECURE : 0);
305 // TODO: replace these magic numbers with something better.
306 // of note here is the 250s timeout on receive, generating rsa4096 might take
307 // some time..
308 WinHttpSetTimeouts(connection->context->req, 30 * 1000, 30 * 1000, 250 * 1000,
309 250 * 1000);
310
311 WinHttpSendRequest(connection->context->req, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
312 msg->raw, raw_len, raw_len, 0);
313
314 while (!complete) {
315 enum stage new_stage = 0;
316 EnterCriticalSection(&connection->context->mtx);
317 switch (connection->context->stage) {
318 case REQUEST_SENT:
319 DBG_INFO("Request sent");
320 WinHttpReceiveResponse(connection->context->req, NULL);
321 new_stage = RESPONSE_WAITING;
322 break;
324 DBG_INFO("Response received");
325 WinHttpQueryDataAvailable(connection->context->req, NULL);
326 break;
327 case DATA_AVAILABLE:
328 DBG_INFO("Data available");
329 if (WinHttpReadData(connection->context->req, response->raw,
330 SCP_MSG_BUF_SIZE, NULL) == FALSE) {
331 DBG_ERR("Failed request for new data: %lu", GetLastError());
332 new_stage = REQUEST_ERROR;
333 } else {
334 new_stage = DATA_WAITING;
335 }
336 break;
337 case READ_COMPLETE:
338 DBG_INFO("Read complete");
339 if (connection->context->len == 0) {
340 // NOTE: this is a hack to try to handle the case where we get 0
341 // bytes..
342 DBG_INFO(
343 "Got a 0 length response, hoping there's more on the wire for us.");
344 new_stage = connection->context->stage = RESPONSE_RECEIVED;
345 break;
346 }
347
348 WinHttpQueryHeaders(connection->context->req,
349 WINHTTP_QUERY_STATUS_CODE |
350 WINHTTP_QUERY_FLAG_NUMBER,
351 WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode,
352 &dwSize, WINHTTP_NO_HEADER_INDEX);
353
354 if (dwStatusCode != HTTP_STATUS_OK) {
355 DBG_ERR("Got HTTP error from server: %lu", dwStatusCode);
356 new_stage = REQUEST_ERROR;
358 } else {
359 WinHttpCloseHandle(connection->context->req);
360 response->st.len = ntohs(response->st.len);
361 if (response->st.len + 3 == connection->context->len) {
362 new_stage = REQUEST_SUCCESS;
364 } else {
365 DBG_ERR("Wrong length received, %d vs %d", response->st.len + 3,
366 connection->context->len);
367 new_stage = REQUEST_ERROR;
369 }
370 }
371 break;
372 case REQUEST_ERROR:
373 DBG_ERR("Request error");
375 WinHttpCloseHandle(connection->context->req);
376 case REQUEST_CLOSED:
377 complete = true;
378 new_stage = REQUEST_DONE;
379 break;
380 default:
381 break;
382 }
383 if (new_stage > connection->context->stage) {
384 connection->context->stage = new_stage;
385 }
386 LeaveCriticalSection(&connection->context->mtx);
387 }
388
389 // restore the msg len
390 msg->st.len = ntohs(msg->st.len);
391
392 return yrc;
393}
394
395static yh_rc backend_option(yh_backend *connection, yh_connector_option opt,
396 const void *val) {
397 (void) connection;
398 (void) opt;
399 (void) val;
400
401 DBG_ERR("Backend options not (yet?) supported with winhttp");
402 return YHR_CONNECTOR_ERROR;
403}
404
409
410#ifdef STATIC
411struct backend_functions *http_backend_functions(void) {
412#else
414#endif
415 return &f;
416}
#define YH_INTERNAL
Definition aes.h:58
#define DBG_ERR(...)
Definition debug_lib.h:76
#define DBG_INFO(...)
Definition debug_lib.h:63
void YH_INTERNAL parse_status_data(char *data, yh_connector *connector)
Definition lib_util.c:65
struct sysio::chain::eosvmoc::code_cache_header __attribute__((packed))
#define TRUE
Definition pkcs11.h:1209
#define FALSE
Definition pkcs11.h:1206
string url
Definition main.cpp:166
#define SCP_MSG_BUF_SIZE
Definition scp.h:52
unsigned short uint16_t
Definition stdint.h:125
unsigned char uint8_t
Definition stdint.h:124
yh_rc(* backend_init)(uint8_t verbosity, FILE *output)
Definition internal.h:66
void(* backend_set_verbosity)(uint8_t verbosity, FILE *output)
Definition internal.h:74
void(* backend_disconnect)(yh_backend *connection)
Definition internal.h:69
yh_rc(* backend_send_msg)(yh_backend *connection, Msg *msg, Msg *response)
Definition internal.h:70
yh_rc(* backend_option)(yh_backend *connection, yh_connector_option opt, const void *val)
Definition internal.h:72
yh_backend *(* backend_create)(void)
Definition internal.h:67
void(* backend_cleanup)(void)
Definition internal.h:71
yh_rc(* backend_connect)(yh_connector *connector, int timeout)
Definition internal.h:68
CRITICAL_SECTION mtx
HINTERNET req
uint16_t len
enum stage stage
HINTERNET con
yh_connector * connector
struct context * context
HINTERNET internet
wchar_t hostname[MAX_STR_LEN+1]
wchar_t path[MAX_STR_LEN+1]
char * status_url
Definition internal.h:41
char * api_url
Definition internal.h:42
yh_backend * connection
Definition internal.h:40
Definition scp.h:56
struct _Msg::@103 st
uint8_t raw[3+SCP_MSG_BUF_SIZE]
Definition scp.h:62
yh_connector_option
Definition yubihsm.h:500
yh_rc
Definition yubihsm.h:170
@ YHR_SUCCESS
Returned value when function was successful.
Definition yubihsm.h:172
@ YHR_INVALID_PARAMETERS
Returned value when an argument to a function is invalid.
Definition yubihsm.h:182
@ YHR_CONNECTOR_NOT_FOUND
Returned value when failing to find a suitable connector.
Definition yubihsm.h:180
@ YHR_CONNECTOR_ERROR
Return value when connector operation failed.
Definition yubihsm.h:232
@ YHR_WRONG_LENGTH
Definition yubihsm.h:185
yh_backend * backend_create(void)
size_t len
uint8_t buf[2048]
yh_rc yrc
#define MAX_STR_LEN
FILE YH_INTERNAL * _yh_output
@ DATA_AVAILABLE
@ DATA_WAITING
@ REQUEST_CLOSED
@ REQUEST_DONE
@ RESPONSE_RECEIVED
@ REQUEST_ERROR
@ RESPONSE_WAITING
@ NO_INIT
@ READ_COMPLETE
@ CLOSE_WAITING
@ REQUEST_SENT
@ REQUEST_SUCCESS
struct backend_functions * backend_functions(void)
uint8_t YH_INTERNAL _yh_verbosity