Coverage Report

Created: 2026-09-14 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/http_httpsig.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HTTPSIG)
27
28
#include "urldata.h"
29
#include "http_httpsig.h"
30
#include "curl_ed25519.h"
31
#include "curl_hmac.h"
32
#include "curl_sha256.h"
33
#include "http.h"
34
#include "transfer.h"
35
#include "curl_trc.h"
36
#include "slist.h"
37
#include "curlx/dynbuf.h"
38
#include "curlx/base64.h"
39
#include "curlx/strdup.h"
40
#include "curlx/strparse.h"
41
#include "strcase.h"
42
43
#include <time.h>
44
45
0
#define HTTPSIG_MAX_SIG_BASE   CURL_MAX_HTTP_HEADER
46
0
#define HTTPSIG_MAX_COMPONENTS 16
47
#define HTTPSIG_MAX_RAW_SIG    CURL_ED25519_SIGLEN
48
0
#define HTTPSIG_DEFAULT_LABEL  "sig1"
49
50
enum httpsig_alg {
51
  HTTPSIG_ALG_ED25519,
52
  HTTPSIG_ALG_HMAC_SHA256,
53
  HTTPSIG_ALG_UNKNOWN
54
};
55
56
static const char *alg_to_str(enum httpsig_alg alg)
57
0
{
58
0
  switch(alg) {
59
0
  case HTTPSIG_ALG_ED25519:
60
0
    return "ed25519";
61
0
  case HTTPSIG_ALG_HMAC_SHA256:
62
0
    return "hmac-sha256";
63
0
  default:
64
0
    break;
65
0
  }
66
0
  return NULL;
67
0
}
68
69
static enum httpsig_alg id_to_alg(uint8_t val)
70
8
{
71
8
  switch(val) {
72
0
  case CURLHTTPSIG_ED25519:
73
0
    return HTTPSIG_ALG_ED25519;
74
0
  case CURLHTTPSIG_HMAC_SHA256:
75
0
    return HTTPSIG_ALG_HMAC_SHA256;
76
8
  default:
77
8
    break;
78
8
  }
79
8
  return HTTPSIG_ALG_UNKNOWN;
80
8
}
81
82
static CURLcode decode_hex_key(struct Curl_easy *data,
83
                               const char *hexstr,
84
                               unsigned char **keyout,
85
                               size_t *keylen)
86
0
{
87
0
  size_t len, i;
88
0
  unsigned char *keybuf;
89
90
0
  *keyout = NULL;
91
0
  *keylen = 0;
92
93
0
  len = strlen(hexstr);
94
0
  while(len > 0 && ISNEWLINE(hexstr[len - 1]))
95
0
    len--;
96
97
0
  if(len == 0 || (len & 1) != 0) {
98
0
    failf(data, "httpsig: invalid hex key (length %zu)", len);
99
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
100
0
  }
101
102
0
  if(len > CURL_MAX_INPUT_LENGTH) {
103
0
    failf(data, "httpsig: hex key too long");
104
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
105
0
  }
106
107
0
  keybuf = curlx_malloc(len / 2);
108
0
  if(!keybuf)
109
0
    return CURLE_OUT_OF_MEMORY;
110
111
0
  for(i = 0; i < len; i += 2) {
112
0
    if(!ISXDIGIT(hexstr[i]) || !ISXDIGIT(hexstr[i + 1])) {
113
0
      failf(data, "httpsig: invalid hex at position %zu ('%c%c')",
114
0
            i, hexstr[i], hexstr[i + 1]);
115
0
      curlx_free(keybuf);
116
0
      return CURLE_BAD_FUNCTION_ARGUMENT;
117
0
    }
118
0
    keybuf[i / 2] = (unsigned char)((curlx_hexval(hexstr[i]) << 4) |
119
0
                                     curlx_hexval(hexstr[i + 1]));
120
0
  }
121
122
0
  *keyout = keybuf;
123
0
  *keylen = len / 2;
124
0
  return CURLE_OK;
125
0
}
126
127
/* @authority matches the Host header field-value when available (RFC 9421).
128
   data->state.http_host is produced by http_set_aptr_host() before auth. */
129
static CURLcode httpsig_authority(struct Curl_easy *data,
130
                                  struct connectdata *conn,
131
                                  struct dynbuf *authority_buf)
132
0
{
133
0
  const char *h = data->state.http_host;
134
135
0
  if(h && curl_strnequal(h, "host:", 5)) {
136
0
    const char *value = h + 5;
137
0
    const char *end;
138
139
0
    while(ISBLANK(*value))
140
0
      value++;
141
0
    if(*value) {
142
0
      CURLcode result;
143
144
0
      end = value;
145
0
      while(*end && !ISNEWLINE(*end))
146
0
        end++;
147
0
      while(end > value && ISBLANK(end[-1]))
148
0
        end--;
149
0
      result = curlx_dyn_addn(authority_buf, value, (size_t)(end - value));
150
0
      if(result)
151
0
        return result;
152
0
      return CURLE_OK;
153
0
    }
154
0
  }
155
156
0
  {
157
0
    const char *hostname = conn->origin->hostname;
158
0
    uint16_t port = conn->origin->port;
159
160
0
    if((conn->origin->scheme->defport != port) && port)
161
0
      return curlx_dyn_addf(authority_buf, "%s:%u", hostname, port);
162
0
    return curlx_dyn_add(authority_buf, hostname);
163
0
  }
164
0
}
165
166
static CURLcode sf_append_quoted(struct dynbuf *buf, const char *str)
167
0
{
168
0
  CURLcode result = curlx_dyn_addn(buf, "\"", 1);
169
0
  if(result)
170
0
    return result;
171
0
  while(*str) {
172
0
    if(ISCNTRL(*str))
173
0
      return CURLE_BAD_FUNCTION_ARGUMENT;
174
0
    if(*str == '\\' || *str == '"') {
175
0
      result = curlx_dyn_addn(buf, "\\", 1);
176
0
      if(result)
177
0
        return result;
178
0
    }
179
0
    result = curlx_dyn_addn(buf, str, 1);
180
0
    if(result)
181
0
      return result;
182
0
    str++;
183
0
  }
184
0
  return curlx_dyn_addn(buf, "\"", 1);
185
0
}
186
187
/* base64-encode raw bytes into an RFC 8941 byte sequence (:base64:) */
188
static CURLcode sf_encode_byte_seq(const unsigned char *raw, size_t rawlen,
189
                                   struct dynbuf *out)
190
0
{
191
0
  CURLcode result;
192
0
  size_t b64len;
193
0
  char *b64;
194
195
0
  result = curlx_base64_encode(raw, rawlen, &b64, &b64len);
196
0
  if(result)
197
0
    return result;
198
199
0
  result = curlx_dyn_addn(out, ":", 1);
200
0
  if(!result)
201
0
    result = curlx_dyn_addn(out, b64, b64len);
202
0
  if(!result)
203
0
    result = curlx_dyn_addn(out, ":", 1);
204
205
0
  curlx_free(b64);
206
0
  return result;
207
0
}
208
209
static CURLcode build_sig_params(struct dynbuf *params,
210
                                 const char **components, size_t count,
211
                                 time_t created, const char *keyid,
212
                                 enum httpsig_alg alg)
213
0
{
214
0
  CURLcode result;
215
0
  size_t i;
216
217
0
  result = curlx_dyn_addn(params, "(", 1);
218
0
  if(result)
219
0
    return result;
220
221
0
  for(i = 0; i < count; i++) {
222
0
    if(i > 0) {
223
0
      result = curlx_dyn_addn(params, " ", 1);
224
0
      if(result)
225
0
        return result;
226
0
    }
227
0
    result = sf_append_quoted(params, components[i]);
228
0
    if(result)
229
0
      return result;
230
0
  }
231
232
0
  result = curlx_dyn_addn(params, ")", 1);
233
0
  if(result)
234
0
    return result;
235
236
0
  result = curlx_dyn_addf(params, ";created=%lld", (long long)created);
237
0
  if(result)
238
0
    return result;
239
240
0
  if(keyid && *keyid) {
241
0
    result = curlx_dyn_add(params, ";keyid=");
242
0
    if(result)
243
0
      return result;
244
0
    result = sf_append_quoted(params, keyid);
245
0
    if(result)
246
0
      return result;
247
0
  }
248
249
0
  result = curlx_dyn_addf(params, ";alg=\"%s\"", alg_to_str(alg));
250
0
  return result;
251
0
}
252
253
/* strings defined by RFC 9421 */
254
0
#define SIG_METHOD    "@method"
255
0
#define SIG_AUTHORITY "@authority"
256
0
#define SIG_PATH      "@path"
257
0
#define SIG_QUERY     "@query"
258
259
/* Resolve a component identifier to its value.
260
 * For headers, we walk the full user-supplied header list to combine
261
 * duplicate field values with ", " per RFC 9421 Section 2.1. Each
262
 * individual value is trimmed of leading/trailing OWS and the trailing
263
 * \r\n. The combined result is written into the caller-provided buffer. */
264
static CURLcode resolve_component(const char *name,
265
                                  const char *method,
266
                                  const char *authority,
267
                                  const char *path,
268
                                  const char *query,
269
                                  struct Curl_easy *data,
270
                                  struct dynbuf *valbuf,
271
                                  const char **out)
272
0
{
273
0
  *out = NULL;
274
275
0
  if(name[0] == '@') {
276
0
    if(curl_strequal(name, SIG_METHOD))
277
0
      *out = method;
278
0
    else if(curl_strequal(name, SIG_AUTHORITY))
279
0
      *out = authority;
280
0
    else if(curl_strequal(name, SIG_PATH))
281
0
      *out = path;
282
0
    else if(curl_strequal(name, SIG_QUERY))
283
0
      *out = query;
284
0
    else {
285
0
      failf(data, "httpsig: unsupported derived component '%s'", name);
286
0
      return CURLE_BAD_FUNCTION_ARGUMENT;
287
0
    }
288
0
    if(!*out) {
289
0
      failf(data, "httpsig: derived component '%s' has no value", name);
290
0
      return CURLE_BAD_FUNCTION_ARGUMENT;
291
0
    }
292
0
    return CURLE_OK;
293
0
  }
294
0
  else {
295
    /* RFC 9421 Section 2.1: walk all user-supplied headers and combine
296
       duplicate field values with ", " per HTTP field combination rules. */
297
0
    struct curl_slist *head;
298
0
    size_t namelen = strlen(name);
299
0
    bool found = FALSE;
300
301
0
    curlx_dyn_reset(valbuf);
302
303
0
    for(head = data->set.headers; head; head = head->next) {
304
0
      if(curl_strnequal(head->data, name, namelen) &&
305
0
         Curl_headersep(head->data[namelen])) {
306
0
        const char *p = strchr(head->data, ':');
307
0
        if(p) {
308
0
          CURLcode result;
309
0
          struct Curl_str content;
310
0
          curlx_str_assign(&content, p + 1, strlen(p + 1));
311
0
          curlx_str_trimblanks(&content);
312
0
          if(found) {
313
0
            result = curlx_dyn_addn(valbuf, ", ", 2);
314
0
            if(result)
315
0
              return result;
316
0
          }
317
0
          result = curlx_dyn_addn(valbuf, curlx_str(&content),
318
0
                                  curlx_strlen(&content));
319
0
          if(result)
320
0
            return result;
321
0
          found = TRUE;
322
0
        }
323
0
      }
324
0
    }
325
326
0
    if(found) {
327
0
      *out = curlx_dyn_ptr(valbuf);
328
0
      return CURLE_OK;
329
0
    }
330
0
    failf(data, "httpsig: header '%s' not found in request", name);
331
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
332
0
  }
333
0
}
334
335
static CURLcode build_sig_base(struct dynbuf *base,
336
                               const char **components, size_t count,
337
                               const char *method,
338
                               const char *authority,
339
                               const char *path,
340
                               const char *query,
341
                               struct Curl_easy *data,
342
                               const char *sig_params)
343
0
{
344
0
  CURLcode result;
345
0
  size_t i;
346
0
  struct dynbuf hdrvalbuf;
347
348
0
  curlx_dyn_init(&hdrvalbuf, CURL_MAX_HTTP_HEADER);
349
350
0
  for(i = 0; i < count; i++) {
351
0
    const char *val = NULL;
352
0
    result = resolve_component(components[i], method,
353
0
                               authority, path, query, data,
354
0
                               &hdrvalbuf, &val);
355
0
    if(result || !val) {
356
0
      failf(data, "httpsig: cannot resolve component '%s'", components[i]);
357
0
      curlx_dyn_free(&hdrvalbuf);
358
0
      return result ? result : CURLE_BAD_FUNCTION_ARGUMENT;
359
0
    }
360
361
0
    result = curlx_dyn_addf(base, "\"%s\": %s\n", components[i], val);
362
0
    if(result) {
363
0
      curlx_dyn_free(&hdrvalbuf);
364
0
      return result;
365
0
    }
366
0
  }
367
368
0
  curlx_dyn_free(&hdrvalbuf);
369
0
  result = curlx_dyn_addf(base, "\"@signature-params\": %s", sig_params);
370
0
  return result;
371
0
}
372
373
static CURLcode parse_components(struct Curl_easy *data,
374
                                 const char *query,
375
                                 const char **components,
376
                                 size_t *ncomp_out,
377
                                 char **hdrs_copy_out)
378
0
{
379
0
  const char *hdrs = CURL_EASY_STR(data, STRING_HTTPSIG_HEADERS);
380
0
  size_t ncomp = 0;
381
382
0
  *hdrs_copy_out = NULL;
383
0
  if(hdrs && *hdrs) {
384
0
    char *p;
385
0
    char *hdrs_copy = curlx_strdup(hdrs);
386
0
    if(!hdrs_copy)
387
0
      return CURLE_OUT_OF_MEMORY;
388
0
    *hdrs_copy_out = hdrs_copy;
389
0
    p = hdrs_copy;
390
0
    while(*p && ncomp < HTTPSIG_MAX_COMPONENTS) {
391
0
      char *start;
392
0
      size_t tlen = 0;
393
0
      const char *p2 = p;
394
395
0
      curlx_str_passblanks(&p2);
396
0
      if(!*p2)
397
0
        break;
398
0
      p = start = CURL_UNCONST(p2);
399
0
      while(*p && !ISBLANK(*p)) {
400
0
        if((*p == '\"') || (*p == '\\'))
401
0
          return CURLE_BAD_FUNCTION_ARGUMENT;
402
0
        p++;
403
0
        tlen++;
404
0
      }
405
0
      if(*p)
406
0
        *p++ = '\0';
407
408
0
      if(tlen && start[tlen - 1] == ':') {
409
        /* Header field: drop the trailing ':' marker. RFC 9421 field
410
           names are canonically lowercase (Section 2.1). */
411
0
        start[--tlen] = '\0';
412
0
        if(!tlen) {
413
0
          failf(data, "httpsig: empty header component name");
414
0
          return CURLE_BAD_FUNCTION_ARGUMENT;
415
0
        }
416
0
        Curl_strntolower(start, start, tlen);
417
0
        components[ncomp++] = start;
418
0
      }
419
0
      else {
420
        /* Derived component: map the bare name to its canonical RFC 9421
421
           '@'-prefixed identifier (Section 2.2). */
422
0
        Curl_strntolower(start, start, tlen);
423
0
        if(!strcmp(start, "method"))
424
0
          components[ncomp++] = SIG_METHOD;
425
0
        else if(!strcmp(start, "authority"))
426
0
          components[ncomp++] = SIG_AUTHORITY;
427
0
        else if(!strcmp(start, "path"))
428
0
          components[ncomp++] = SIG_PATH;
429
0
        else if(!strcmp(start, "query"))
430
0
          components[ncomp++] = SIG_QUERY;
431
0
        else {
432
0
          failf(data, "httpsig: unknown component '%s'", start);
433
0
          return CURLE_BAD_FUNCTION_ARGUMENT;
434
0
        }
435
0
      }
436
0
    }
437
0
    if(!ncomp) {
438
0
      failf(data, "httpsig: no signature components specified");
439
0
      return CURLE_BAD_FUNCTION_ARGUMENT;
440
0
    }
441
0
    if(*p) {
442
0
      failf(data, "httpsig: too many signature components (max %u)",
443
0
            (unsigned int)HTTPSIG_MAX_COMPONENTS);
444
0
      return CURLE_BAD_FUNCTION_ARGUMENT;
445
0
    }
446
447
    /* RFC 9421 Section 2: each covered component MUST occur only once */
448
0
    {
449
0
      size_t i, j;
450
451
0
      for(i = 0; i < ncomp; i++) {
452
0
        for(j = i + 1; j < ncomp; j++) {
453
0
          if(!strcmp(components[i], components[j])) {
454
0
            failf(data, "httpsig: duplicate signature component '%s'",
455
0
                  components[i]);
456
0
            return CURLE_BAD_FUNCTION_ARGUMENT;
457
0
          }
458
0
        }
459
0
      }
460
0
    }
461
0
  }
462
0
  else {
463
0
    components[ncomp++] = SIG_METHOD;
464
0
    components[ncomp++] = SIG_AUTHORITY;
465
0
    components[ncomp++] = SIG_PATH;
466
0
    if(query)
467
0
      components[ncomp++] = SIG_QUERY;
468
0
  }
469
470
0
  *ncomp_out = ncomp;
471
0
  return CURLE_OK;
472
0
}
473
474
static time_t httpsig_get_created(void)
475
0
{
476
0
#ifdef DEBUGBUILD
477
0
  char *force = getenv("CURL_FORCETIME");
478
0
  if(force && *force) {
479
0
    char *sigts = getenv("CURL_HTTPSIG_CREATED");
480
0
    if(sigts && *sigts) {
481
0
      const char *p = sigts;
482
0
      curl_off_t num;
483
0
      if(!curlx_str_number(&p, &num, CURL_OFF_T_MAX))
484
0
        return (time_t)num;
485
0
    }
486
0
    return 0;
487
0
  }
488
0
#endif
489
0
  return time(NULL);
490
0
}
491
492
static CURLcode httpsig_sign_base(struct Curl_easy *data,
493
                                  enum httpsig_alg alg,
494
                                  const unsigned char *keybuf,
495
                                  size_t keylen,
496
                                  const struct dynbuf *sig_base,
497
                                  unsigned char *raw_sig,
498
                                  size_t *raw_sig_len)
499
0
{
500
0
  CURLcode result;
501
502
0
  switch(alg) {
503
0
  case HTTPSIG_ALG_ED25519:
504
0
    result = Curl_ed25519_sign(
505
0
      keybuf, keylen,
506
0
      (const unsigned char *)curlx_dyn_ptr(sig_base),
507
0
      curlx_dyn_len(sig_base),
508
0
      raw_sig, raw_sig_len);
509
0
    break;
510
0
  case HTTPSIG_ALG_HMAC_SHA256:
511
0
    result = Curl_hmacit(&Curl_HMAC_SHA256, keybuf, keylen,
512
0
                         (const unsigned char *)curlx_dyn_ptr(sig_base),
513
0
                         curlx_dyn_len(sig_base), raw_sig);
514
0
    if(!result)
515
0
      *raw_sig_len = CURL_SHA256_DIGEST_LENGTH;
516
0
    break;
517
0
  default:
518
0
    result = CURLE_BAD_FUNCTION_ARGUMENT;
519
0
    break;
520
0
  }
521
522
0
  if(result && result == CURLE_NOT_BUILT_IN) {
523
0
    failf(data, "httpsig: algorithm '%s' not supported by TLS backend",
524
0
          alg_to_str(alg));
525
0
  }
526
0
  return result;
527
0
}
528
529
CURLcode Curl_output_httpsig(struct Curl_easy *data)
530
8
{
531
8
  CURLcode result = CURLE_OUT_OF_MEMORY;
532
8
  struct connectdata *conn = data->conn;
533
8
  const char *path;
534
8
  const char *query;
535
8
  Curl_HttpReq httpreq;
536
8
  const char *method = NULL;
537
8
  const char *hexkey = CURL_EASY_STR(data, STRING_HTTPSIG_KEY);
538
8
  const char *keyid = CURL_EASY_STR(data, STRING_HTTPSIG_KEYID);
539
8
  enum httpsig_alg alg;
540
8
  time_t created;
541
8
  struct dynbuf sig_params;
542
8
  struct dynbuf sig_base;
543
8
  struct dynbuf sig_hdr;
544
8
  struct dynbuf input_hdr;
545
8
  struct dynbuf authority_buf;
546
8
  const char *authority;
547
8
  const char *components[HTTPSIG_MAX_COMPONENTS];
548
8
  size_t ncomp = 0;
549
8
  unsigned char *keybuf = NULL;
550
8
  size_t keylen = 0;
551
8
  unsigned char raw_sig[HTTPSIG_MAX_RAW_SIG];
552
8
  size_t raw_sig_len = 0;
553
8
  char *auth_headers = NULL;
554
8
  char *hdrs_copy = NULL;
555
8
  struct dynbuf query_dyn;
556
557
8
  alg = id_to_alg(data->set.httpsig_algorithm);
558
8
  if(alg == HTTPSIG_ALG_UNKNOWN) {
559
8
    failf(data, "httpsig: CURLOPT_HTTPSIG_ALGORITHM is required");
560
8
    return CURLE_BAD_FUNCTION_ARGUMENT;
561
8
  }
562
563
0
  if(!hexkey || !*hexkey) {
564
0
    failf(data, "httpsig: CURLOPT_HTTPSIG_KEY is required");
565
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
566
0
  }
567
0
  if(!keyid || !*keyid) {
568
0
    failf(data, "httpsig: CURLOPT_HTTPSIG_KEYID is required");
569
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
570
0
  }
571
572
0
  curlx_dyn_init(&sig_params, CURL_MAX_HTTP_HEADER);
573
0
  curlx_dyn_init(&sig_base, HTTPSIG_MAX_SIG_BASE);
574
0
  curlx_dyn_init(&sig_hdr, CURL_MAX_HTTP_HEADER);
575
0
  curlx_dyn_init(&input_hdr, CURL_MAX_HTTP_HEADER);
576
0
  curlx_dyn_init(&authority_buf, CURL_MAX_HTTP_HEADER);
577
0
  curlx_dyn_init(&query_dyn, CURL_MAX_HTTP_HEADER);
578
579
0
  if(Curl_checkheaders(data, STRCONST("Signature")) ||
580
0
     Curl_checkheaders(data, STRCONST("Signature-Input"))) {
581
    /* user provides their own Signature / Signature-Input headers, consider
582
       this done */
583
0
    goto done;
584
0
  }
585
586
0
  result = decode_hex_key(data, hexkey, &keybuf, &keylen);
587
0
  if(result)
588
0
    goto fail;
589
590
0
  if(alg == HTTPSIG_ALG_ED25519 && keylen != 32) {
591
0
    failf(data, "httpsig: ed25519 requires a 32-byte key (got %zu)", keylen);
592
0
    result = CURLE_BAD_FUNCTION_ARGUMENT;
593
0
    goto fail;
594
0
  }
595
596
0
  Curl_http_method(data, &method, &httpreq);
597
598
0
  path = data->state.up.path;
599
0
  if(!path || !*path)
600
0
    path = "/";
601
602
0
  query = data->state.up.query;
603
604
0
  result = httpsig_authority(data, conn, &authority_buf);
605
0
  if(result)
606
0
    goto fail;
607
0
  authority = curlx_dyn_ptr(&authority_buf);
608
609
  /* Build @query value: RFC 9421 Section 2.2.7 - always starts with "?" */
610
0
  if(query && *query)
611
0
    result = curlx_dyn_addf(&query_dyn, "?%s", query);
612
0
  else
613
0
    result = curlx_dyn_add(&query_dyn, "?");
614
0
  if(result)
615
0
    goto fail;
616
617
0
  result = parse_components(data, query, components, &ncomp, &hdrs_copy);
618
0
  if(result)
619
0
    goto fail;
620
621
0
  created = httpsig_get_created();
622
623
0
  result = build_sig_params(&sig_params, components, ncomp,
624
0
                            created, keyid, alg);
625
0
  if(result)
626
0
    goto fail;
627
628
0
  infof(data, "httpsig: Signature-Input params: %s",
629
0
        curlx_dyn_ptr(&sig_params));
630
631
0
  result = build_sig_base(&sig_base, components, ncomp,
632
0
                          method, authority, path,
633
0
                          curlx_dyn_ptr(&query_dyn),
634
0
                          data, curlx_dyn_ptr(&sig_params));
635
0
  if(result)
636
0
    goto fail;
637
638
0
  infof(data, "httpsig: Signature base: [%s]",
639
0
        curlx_dyn_ptr(&sig_base));
640
641
0
  result = httpsig_sign_base(data, alg, keybuf, keylen, &sig_base,
642
0
                             raw_sig, &raw_sig_len);
643
0
  if(result)
644
0
    goto fail;
645
646
0
  result = curlx_dyn_add(&sig_hdr, HTTPSIG_DEFAULT_LABEL "=");
647
0
  if(result)
648
0
    goto fail;
649
0
  result = sf_encode_byte_seq(raw_sig, raw_sig_len, &sig_hdr);
650
0
  if(result)
651
0
    goto fail;
652
653
0
  result = curlx_dyn_addf(&input_hdr, "%s=%s", HTTPSIG_DEFAULT_LABEL,
654
0
                          curlx_dyn_ptr(&sig_params));
655
0
  if(result)
656
0
    goto fail;
657
658
0
  auth_headers = curl_maprintf("Signature-Input: %s\r\n"
659
0
                               "Signature: %s\r\n",
660
0
                               curlx_dyn_ptr(&input_hdr),
661
0
                               curlx_dyn_ptr(&sig_hdr));
662
0
  if(!auth_headers)
663
0
    goto fail;
664
665
0
  infof(data, "httpsig: Signature-Input: %s", curlx_dyn_ptr(&input_hdr));
666
0
  infof(data, "httpsig: Signature: %s", curlx_dyn_ptr(&sig_hdr));
667
0
done:
668
0
  curlx_free(data->req.hd_auth);
669
0
  data->req.hd_auth = auth_headers;
670
0
  data->state.authhost.done = TRUE;
671
0
  result = CURLE_OK;
672
673
0
fail:
674
0
  if(keybuf) {
675
0
    memset(keybuf, 0, keylen);
676
0
    curlx_free(keybuf);
677
0
  }
678
0
  memset(raw_sig, 0, sizeof(raw_sig));
679
0
  curlx_free(hdrs_copy);
680
0
  curlx_dyn_free(&sig_params);
681
0
  curlx_dyn_free(&sig_base);
682
0
  curlx_dyn_free(&sig_hdr);
683
0
  curlx_dyn_free(&input_hdr);
684
0
  curlx_dyn_free(&authority_buf);
685
0
  curlx_dyn_free(&query_dyn);
686
0
  return result;
687
0
}
688
689
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */