Coverage Report

Created: 2026-10-22 06:50

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
1.58k
#define HTTPSIG_MAX_SIG_BASE   CURL_MAX_HTTP_HEADER
46
1.88k
#define HTTPSIG_MAX_COMPONENTS 16
47
#define HTTPSIG_MAX_RAW_SIG    CURL_ED25519_SIGLEN
48
2.88k
#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
1.47k
{
58
1.47k
  switch(alg) {
59
1.34k
  case HTTPSIG_ALG_ED25519:
60
1.34k
    return "ed25519";
61
134
  case HTTPSIG_ALG_HMAC_SHA256:
62
134
    return "hmac-sha256";
63
0
  default:
64
0
    break;
65
1.47k
  }
66
0
  return NULL;
67
1.47k
}
68
69
static enum httpsig_alg id_to_alg(uint8_t val)
70
1.60k
{
71
1.60k
  switch(val) {
72
1.44k
  case CURLHTTPSIG_ED25519:
73
1.44k
    return HTTPSIG_ALG_ED25519;
74
163
  case CURLHTTPSIG_HMAC_SHA256:
75
163
    return HTTPSIG_ALG_HMAC_SHA256;
76
1
  default:
77
1
    break;
78
1.60k
  }
79
1
  return HTTPSIG_ALG_UNKNOWN;
80
1.60k
}
81
82
static CURLcode decode_hex_key(struct Curl_easy *data,
83
                               const char *hexstr,
84
                               unsigned char **keyout,
85
                               size_t *keylen)
86
1.58k
{
87
1.58k
  size_t len, i;
88
1.58k
  unsigned char *keybuf;
89
90
1.58k
  *keyout = NULL;
91
1.58k
  *keylen = 0;
92
93
1.58k
  len = strlen(hexstr);
94
1.74k
  while(len > 0 && ISNEWLINE(hexstr[len - 1]))
95
159
    len--;
96
97
1.58k
  if(len == 0 || (len & 1) != 0) {
98
14
    failf(data, "httpsig: invalid hex key (length %zu)", len);
99
14
    return CURLE_BAD_FUNCTION_ARGUMENT;
100
14
  }
101
102
1.57k
  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
1.57k
  keybuf = curlx_malloc(len / 2);
108
1.57k
  if(!keybuf)
109
1
    return CURLE_OUT_OF_MEMORY;
110
111
50.1k
  for(i = 0; i < len; i += 2) {
112
48.5k
    if(!ISXDIGIT(hexstr[i]) || !ISXDIGIT(hexstr[i + 1])) {
113
22
      failf(data, "httpsig: invalid hex at position %zu ('%c%c')",
114
22
            i, hexstr[i], hexstr[i + 1]);
115
22
      curlx_free(keybuf);
116
22
      return CURLE_BAD_FUNCTION_ARGUMENT;
117
22
    }
118
48.5k
    keybuf[i / 2] = (unsigned char)((curlx_hexval(hexstr[i]) << 4) |
119
48.5k
                                     curlx_hexval(hexstr[i + 1]));
120
48.5k
  }
121
122
1.55k
  *keyout = keybuf;
123
1.55k
  *keylen = len / 2;
124
1.55k
  return CURLE_OK;
125
1.57k
}
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
1.53k
{
133
1.53k
  const char *h = data->state.http_host;
134
135
1.53k
  if(h && curl_strnequal(h, "host:", 5)) {
136
1.50k
    const char *value = h + 5;
137
1.50k
    const char *end;
138
139
2.86k
    while(ISBLANK(*value))
140
1.36k
      value++;
141
1.50k
    if(*value) {
142
1.42k
      CURLcode result;
143
144
1.42k
      end = value;
145
6.12k
      while(*end && !ISNEWLINE(*end))
146
4.70k
        end++;
147
1.42k
      while(end > value && ISBLANK(end[-1]))
148
0
        end--;
149
1.42k
      result = curlx_dyn_addn(authority_buf, value, (size_t)(end - value));
150
1.42k
      if(result)
151
1
        return result;
152
1.41k
      return CURLE_OK;
153
1.42k
    }
154
1.50k
  }
155
156
119
  {
157
119
    const char *hostname = conn->origin->hostname;
158
119
    uint16_t port = conn->origin->port;
159
160
119
    if((conn->origin->scheme->defport != port) && port)
161
35
      return curlx_dyn_addf(authority_buf, "%s:%u", hostname, port);
162
84
    return curlx_dyn_add(authority_buf, hostname);
163
119
  }
164
119
}
165
166
static CURLcode sf_append_quoted(struct dynbuf *buf, const char *str)
167
7.03k
{
168
7.03k
  CURLcode result = curlx_dyn_addn(buf, "\"", 1);
169
7.03k
  if(result)
170
1
    return result;
171
69.8k
  while(*str) {
172
62.8k
    if(ISCNTRL(*str))
173
7
      return CURLE_BAD_FUNCTION_ARGUMENT;
174
62.8k
    if(*str == '\\' || *str == '"') {
175
821
      result = curlx_dyn_addn(buf, "\\", 1);
176
821
      if(result)
177
1
        return result;
178
821
    }
179
62.8k
    result = curlx_dyn_addn(buf, str, 1);
180
62.8k
    if(result)
181
1
      return result;
182
62.8k
    str++;
183
62.8k
  }
184
7.02k
  return curlx_dyn_addn(buf, "\"", 1);
185
7.03k
}
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
1.44k
{
191
1.44k
  CURLcode result;
192
1.44k
  size_t b64len;
193
1.44k
  char *b64;
194
195
1.44k
  result = curlx_base64_encode(raw, rawlen, &b64, &b64len);
196
1.44k
  if(result)
197
1
    return result;
198
199
1.44k
  result = curlx_dyn_addn(out, ":", 1);
200
1.44k
  if(!result)
201
1.44k
    result = curlx_dyn_addn(out, b64, b64len);
202
1.44k
  if(!result)
203
1.44k
    result = curlx_dyn_addn(out, ":", 1);
204
205
1.44k
  curlx_free(b64);
206
1.44k
  return result;
207
1.44k
}
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
1.49k
{
214
1.49k
  CURLcode result;
215
1.49k
  size_t i;
216
217
1.49k
  result = curlx_dyn_addn(params, "(", 1);
218
1.49k
  if(result)
219
1
    return result;
220
221
7.03k
  for(i = 0; i < count; i++) {
222
5.54k
    if(i > 0) {
223
4.05k
      result = curlx_dyn_addn(params, " ", 1);
224
4.05k
      if(result)
225
1
        return result;
226
4.05k
    }
227
5.54k
    result = sf_append_quoted(params, components[i]);
228
5.54k
    if(result)
229
2
      return result;
230
5.54k
  }
231
232
1.48k
  result = curlx_dyn_addn(params, ")", 1);
233
1.48k
  if(result)
234
1
    return result;
235
236
1.48k
  result = curlx_dyn_addf(params, ";created=%lld", (long long)created);
237
1.48k
  if(result)
238
1
    return result;
239
240
1.48k
  if(keyid && *keyid) {
241
1.48k
    result = curlx_dyn_add(params, ";keyid=");
242
1.48k
    if(result)
243
1
      return result;
244
1.48k
    result = sf_append_quoted(params, keyid);
245
1.48k
    if(result)
246
8
      return result;
247
1.48k
  }
248
249
1.47k
  result = curlx_dyn_addf(params, ";alg=\"%s\"", alg_to_str(alg));
250
1.47k
  return result;
251
1.48k
}
252
253
/* strings defined by RFC 9421 */
254
6.22k
#define SIG_METHOD    "@method"
255
4.75k
#define SIG_AUTHORITY "@authority"
256
3.32k
#define SIG_PATH      "@path"
257
812
#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
5.47k
{
273
5.47k
  *out = NULL;
274
275
5.47k
  if(name[0] == '@') {
276
4.73k
    if(curl_strequal(name, SIG_METHOD))
277
1.44k
      *out = method;
278
3.28k
    else if(curl_strequal(name, SIG_AUTHORITY))
279
1.44k
      *out = authority;
280
1.84k
    else if(curl_strequal(name, SIG_PATH))
281
1.44k
      *out = path;
282
401
    else if(curl_strequal(name, SIG_QUERY))
283
400
      *out = query;
284
1
    else {
285
1
      failf(data, "httpsig: unsupported derived component '%s'", name);
286
1
      return CURLE_BAD_FUNCTION_ARGUMENT;
287
1
    }
288
4.73k
    if(!*out) {
289
0
      failf(data, "httpsig: derived component '%s' has no value", name);
290
0
      return CURLE_BAD_FUNCTION_ARGUMENT;
291
0
    }
292
4.73k
    return CURLE_OK;
293
4.73k
  }
294
741
  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
741
    struct curl_slist *head;
298
741
    size_t namelen = strlen(name);
299
741
    bool found = FALSE;
300
301
741
    curlx_dyn_reset(valbuf);
302
303
5.69k
    for(head = data->set.headers; head; head = head->next) {
304
4.95k
      if(curl_strnequal(head->data, name, namelen) &&
305
1.30k
         Curl_headersep(head->data[namelen])) {
306
1.09k
        const char *p = strchr(head->data, ':');
307
1.09k
        if(p) {
308
1.07k
          CURLcode result;
309
1.07k
          struct Curl_str content;
310
1.07k
          curlx_str_assign(&content, p + 1, strlen(p + 1));
311
1.07k
          curlx_str_trimblanks(&content);
312
1.07k
          if(found) {
313
363
            result = curlx_dyn_addn(valbuf, ", ", 2);
314
363
            if(result)
315
0
              return result;
316
363
          }
317
1.07k
          result = curlx_dyn_addn(valbuf, curlx_str(&content),
318
1.07k
                                  curlx_strlen(&content));
319
1.07k
          if(result)
320
1
            return result;
321
1.07k
          found = TRUE;
322
1.07k
        }
323
1.09k
      }
324
4.95k
    }
325
326
740
    if(found) {
327
714
      *out = curlx_dyn_ptr(valbuf);
328
714
      return CURLE_OK;
329
714
    }
330
26
    failf(data, "httpsig: header '%s' not found in request", name);
331
26
    return CURLE_BAD_FUNCTION_ARGUMENT;
332
740
  }
333
5.47k
}
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
1.47k
{
344
1.47k
  CURLcode result;
345
1.47k
  size_t i;
346
1.47k
  struct dynbuf hdrvalbuf;
347
348
1.47k
  curlx_dyn_init(&hdrvalbuf, CURL_MAX_HTTP_HEADER);
349
350
6.92k
  for(i = 0; i < count; i++) {
351
5.47k
    const char *val = NULL;
352
5.47k
    result = resolve_component(components[i], method,
353
5.47k
                               authority, path, query, data,
354
5.47k
                               &hdrvalbuf, &val);
355
5.47k
    if(result || !val) {
356
28
      failf(data, "httpsig: cannot resolve component '%s'", components[i]);
357
28
      curlx_dyn_free(&hdrvalbuf);
358
28
      return result ? result : CURLE_BAD_FUNCTION_ARGUMENT;
359
28
    }
360
361
5.44k
    result = curlx_dyn_addf(base, "\"%s\": %s\n", components[i], val);
362
5.44k
    if(result) {
363
3
      curlx_dyn_free(&hdrvalbuf);
364
3
      return result;
365
3
    }
366
5.44k
  }
367
368
1.44k
  curlx_dyn_free(&hdrvalbuf);
369
1.44k
  result = curlx_dyn_addf(base, "\"@signature-params\": %s", sig_params);
370
1.44k
  return result;
371
1.47k
}
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
1.53k
{
379
1.53k
  const char *hdrs = CURL_EASY_STR(data, STRING_HTTPSIG_HEADERS);
380
1.53k
  size_t ncomp = 0;
381
382
1.53k
  *hdrs_copy_out = NULL;
383
1.53k
  if(hdrs && *hdrs) {
384
320
    char *p;
385
320
    char *hdrs_copy = curlx_strdup(hdrs);
386
320
    if(!hdrs_copy)
387
1
      return CURLE_OUT_OF_MEMORY;
388
319
    *hdrs_copy_out = hdrs_copy;
389
319
    p = hdrs_copy;
390
2.15k
    while(*p && ncomp < HTTPSIG_MAX_COMPONENTS) {
391
1.88k
      char *start;
392
1.88k
      size_t tlen = 0;
393
1.88k
      const char *p2 = p;
394
395
1.88k
      curlx_str_passblanks(&p2);
396
1.88k
      if(!*p2)
397
2
        break;
398
1.87k
      p = start = CURL_UNCONST(p2);
399
16.2k
      while(*p && !ISBLANK(*p)) {
400
14.4k
        if((*p == '\"') || (*p == '\\'))
401
3
          return CURLE_BAD_FUNCTION_ARGUMENT;
402
14.4k
        p++;
403
14.4k
        tlen++;
404
14.4k
      }
405
1.87k
      if(*p)
406
1.58k
        *p++ = '\0';
407
408
1.87k
      if(tlen && start[tlen - 1] == ':') {
409
        /* Header field: drop the trailing ':' marker. RFC 9421 field
410
           names are canonically lowercase (Section 2.1). */
411
806
        start[--tlen] = '\0';
412
806
        if(!tlen) {
413
1
          failf(data, "httpsig: empty header component name");
414
1
          return CURLE_BAD_FUNCTION_ARGUMENT;
415
1
        }
416
805
        Curl_strntolower(start, start, tlen);
417
805
        components[ncomp++] = start;
418
805
      }
419
1.06k
      else {
420
        /* Derived component: map the bare name to its canonical RFC 9421
421
           '@'-prefixed identifier (Section 2.2). */
422
1.06k
        Curl_strntolower(start, start, tlen);
423
1.06k
        if(!strcmp(start, "method"))
424
276
          components[ncomp++] = SIG_METHOD;
425
793
        else if(!strcmp(start, "authority"))
426
250
          components[ncomp++] = SIG_AUTHORITY;
427
543
        else if(!strcmp(start, "path"))
428
263
          components[ncomp++] = SIG_PATH;
429
280
        else if(!strcmp(start, "query"))
430
246
          components[ncomp++] = SIG_QUERY;
431
34
        else {
432
34
          failf(data, "httpsig: unknown component '%s'", start);
433
34
          return CURLE_BAD_FUNCTION_ARGUMENT;
434
34
        }
435
1.06k
      }
436
1.87k
    }
437
281
    if(!ncomp) {
438
1
      failf(data, "httpsig: no signature components specified");
439
1
      return CURLE_BAD_FUNCTION_ARGUMENT;
440
1
    }
441
280
    if(*p) {
442
2
      failf(data, "httpsig: too many signature components (max %u)",
443
2
            (unsigned int)HTTPSIG_MAX_COMPONENTS);
444
2
      return CURLE_BAD_FUNCTION_ARGUMENT;
445
2
    }
446
447
    /* RFC 9421 Section 2: each covered component MUST occur only once */
448
278
    {
449
278
      size_t i, j;
450
451
2.01k
      for(i = 0; i < ncomp; i++) {
452
6.84k
        for(j = i + 1; j < ncomp; j++) {
453
5.10k
          if(!strcmp(components[i], components[j])) {
454
3
            failf(data, "httpsig: duplicate signature component '%s'",
455
3
                  components[i]);
456
3
            return CURLE_BAD_FUNCTION_ARGUMENT;
457
3
          }
458
5.10k
        }
459
1.74k
      }
460
278
    }
461
278
  }
462
1.21k
  else {
463
1.21k
    components[ncomp++] = SIG_METHOD;
464
1.21k
    components[ncomp++] = SIG_AUTHORITY;
465
1.21k
    components[ncomp++] = SIG_PATH;
466
1.21k
    if(query)
467
165
      components[ncomp++] = SIG_QUERY;
468
1.21k
  }
469
470
1.49k
  *ncomp_out = ncomp;
471
1.49k
  return CURLE_OK;
472
1.53k
}
473
474
static time_t httpsig_get_created(void)
475
1.49k
{
476
1.49k
#ifdef DEBUGBUILD
477
1.49k
  char *force = getenv("CURL_FORCETIME");
478
1.49k
  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
1.49k
#endif
489
1.49k
  return time(NULL);
490
1.49k
}
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
1.44k
{
500
1.44k
  CURLcode result;
501
502
1.44k
  switch(alg) {
503
1.31k
  case HTTPSIG_ALG_ED25519:
504
1.31k
    result = Curl_ed25519_sign(
505
1.31k
      keybuf, keylen,
506
1.31k
      (const unsigned char *)curlx_dyn_ptr(sig_base),
507
1.31k
      curlx_dyn_len(sig_base),
508
1.31k
      raw_sig, raw_sig_len);
509
1.31k
    break;
510
134
  case HTTPSIG_ALG_HMAC_SHA256:
511
134
    result = Curl_hmacit(&Curl_HMAC_SHA256, keybuf, keylen,
512
134
                         (const unsigned char *)curlx_dyn_ptr(sig_base),
513
134
                         curlx_dyn_len(sig_base), raw_sig);
514
134
    if(!result)
515
133
      *raw_sig_len = CURL_SHA256_DIGEST_LENGTH;
516
134
    break;
517
0
  default:
518
0
    result = CURLE_BAD_FUNCTION_ARGUMENT;
519
0
    break;
520
1.44k
  }
521
522
1.44k
  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
1.44k
  return result;
527
1.44k
}
528
529
CURLcode Curl_output_httpsig(struct Curl_easy *data)
530
1.60k
{
531
1.60k
  CURLcode result = CURLE_OUT_OF_MEMORY;
532
1.60k
  struct connectdata *conn = data->conn;
533
1.60k
  const char *path;
534
1.60k
  const char *query;
535
1.60k
  Curl_HttpReq httpreq;
536
1.60k
  const char *method = NULL;
537
1.60k
  const char *hexkey = CURL_EASY_STR(data, STRING_HTTPSIG_KEY);
538
1.60k
  const char *keyid = CURL_EASY_STR(data, STRING_HTTPSIG_KEYID);
539
1.60k
  enum httpsig_alg alg;
540
1.60k
  time_t created;
541
1.60k
  struct dynbuf sig_params;
542
1.60k
  struct dynbuf sig_base;
543
1.60k
  struct dynbuf sig_hdr;
544
1.60k
  struct dynbuf input_hdr;
545
1.60k
  struct dynbuf authority_buf;
546
1.60k
  const char *authority;
547
1.60k
  const char *components[HTTPSIG_MAX_COMPONENTS];
548
1.60k
  size_t ncomp = 0;
549
1.60k
  unsigned char *keybuf = NULL;
550
1.60k
  size_t keylen = 0;
551
1.60k
  unsigned char raw_sig[HTTPSIG_MAX_RAW_SIG];
552
1.60k
  size_t raw_sig_len = 0;
553
1.60k
  char *auth_headers = NULL;
554
1.60k
  char *hdrs_copy = NULL;
555
1.60k
  struct dynbuf query_dyn;
556
557
1.60k
  alg = id_to_alg(data->set.httpsig_algorithm);
558
1.60k
  if(alg == HTTPSIG_ALG_UNKNOWN) {
559
1
    failf(data, "httpsig: CURLOPT_HTTPSIG_ALGORITHM is required");
560
1
    return CURLE_BAD_FUNCTION_ARGUMENT;
561
1
  }
562
563
1.60k
  if(!hexkey || !*hexkey) {
564
17
    failf(data, "httpsig: CURLOPT_HTTPSIG_KEY is required");
565
17
    return CURLE_BAD_FUNCTION_ARGUMENT;
566
17
  }
567
1.59k
  if(!keyid || !*keyid) {
568
3
    failf(data, "httpsig: CURLOPT_HTTPSIG_KEYID is required");
569
3
    return CURLE_BAD_FUNCTION_ARGUMENT;
570
3
  }
571
572
1.58k
  curlx_dyn_init(&sig_params, CURL_MAX_HTTP_HEADER);
573
1.58k
  curlx_dyn_init(&sig_base, HTTPSIG_MAX_SIG_BASE);
574
1.58k
  curlx_dyn_init(&sig_hdr, CURL_MAX_HTTP_HEADER);
575
1.58k
  curlx_dyn_init(&input_hdr, CURL_MAX_HTTP_HEADER);
576
1.58k
  curlx_dyn_init(&authority_buf, CURL_MAX_HTTP_HEADER);
577
1.58k
  curlx_dyn_init(&query_dyn, CURL_MAX_HTTP_HEADER);
578
579
1.58k
  if(Curl_checkheaders(data, STRCONST("Signature")) ||
580
1.58k
     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
1.58k
  result = decode_hex_key(data, hexkey, &keybuf, &keylen);
587
1.58k
  if(result)
588
37
    goto fail;
589
590
1.55k
  if(alg == HTTPSIG_ALG_ED25519 && keylen != 32) {
591
11
    failf(data, "httpsig: ed25519 requires a 32-byte key (got %zu)", keylen);
592
11
    result = CURLE_BAD_FUNCTION_ARGUMENT;
593
11
    goto fail;
594
11
  }
595
596
1.53k
  Curl_http_method(data, &method, &httpreq);
597
598
1.53k
  path = data->state.up.path;
599
1.53k
  if(!path || !*path)
600
0
    path = "/";
601
602
1.53k
  query = data->state.up.query;
603
604
1.53k
  result = httpsig_authority(data, conn, &authority_buf);
605
1.53k
  if(result)
606
1
    goto fail;
607
1.53k
  authority = curlx_dyn_ptr(&authority_buf);
608
609
  /* Build @query value: RFC 9421 Section 2.2.7 - always starts with "?" */
610
1.53k
  if(query && *query)
611
170
    result = curlx_dyn_addf(&query_dyn, "?%s", query);
612
1.36k
  else
613
1.36k
    result = curlx_dyn_add(&query_dyn, "?");
614
1.53k
  if(result)
615
1
    goto fail;
616
617
1.53k
  result = parse_components(data, query, components, &ncomp, &hdrs_copy);
618
1.53k
  if(result)
619
45
    goto fail;
620
621
1.49k
  created = httpsig_get_created();
622
623
1.49k
  result = build_sig_params(&sig_params, components, ncomp,
624
1.49k
                            created, keyid, alg);
625
1.49k
  if(result)
626
15
    goto fail;
627
628
1.47k
  infof(data, "httpsig: Signature-Input params: %s",
629
1.47k
        curlx_dyn_ptr(&sig_params));
630
631
1.47k
  result = build_sig_base(&sig_base, components, ncomp,
632
1.47k
                          method, authority, path,
633
1.47k
                          curlx_dyn_ptr(&query_dyn),
634
1.47k
                          data, curlx_dyn_ptr(&sig_params));
635
1.47k
  if(result)
636
31
    goto fail;
637
638
1.44k
  infof(data, "httpsig: Signature base: [%s]",
639
1.44k
        curlx_dyn_ptr(&sig_base));
640
641
1.44k
  result = httpsig_sign_base(data, alg, keybuf, keylen, &sig_base,
642
1.44k
                             raw_sig, &raw_sig_len);
643
1.44k
  if(result)
644
1
    goto fail;
645
646
1.44k
  result = curlx_dyn_add(&sig_hdr, HTTPSIG_DEFAULT_LABEL "=");
647
1.44k
  if(result)
648
1
    goto fail;
649
1.44k
  result = sf_encode_byte_seq(raw_sig, raw_sig_len, &sig_hdr);
650
1.44k
  if(result)
651
2
    goto fail;
652
653
1.44k
  result = curlx_dyn_addf(&input_hdr, "%s=%s", HTTPSIG_DEFAULT_LABEL,
654
1.44k
                          curlx_dyn_ptr(&sig_params));
655
1.44k
  if(result)
656
1
    goto fail;
657
658
1.44k
  auth_headers = curl_maprintf("Signature-Input: %s\r\n"
659
1.44k
                               "Signature: %s\r\n",
660
1.44k
                               curlx_dyn_ptr(&input_hdr),
661
1.44k
                               curlx_dyn_ptr(&sig_hdr));
662
1.44k
  if(!auth_headers)
663
2
    goto fail;
664
665
1.43k
  infof(data, "httpsig: Signature-Input: %s", curlx_dyn_ptr(&input_hdr));
666
1.43k
  infof(data, "httpsig: Signature: %s", curlx_dyn_ptr(&sig_hdr));
667
1.43k
done:
668
1.43k
  curlx_free(data->req.hd_auth);
669
1.43k
  data->req.hd_auth = auth_headers;
670
1.43k
  data->state.authhost.done = TRUE;
671
1.43k
  result = CURLE_OK;
672
673
1.58k
fail:
674
1.58k
  if(keybuf) {
675
1.55k
    memset(keybuf, 0, keylen);
676
1.55k
    curlx_free(keybuf);
677
1.55k
  }
678
1.58k
  memset(raw_sig, 0, sizeof(raw_sig));
679
1.58k
  curlx_free(hdrs_copy);
680
1.58k
  curlx_dyn_free(&sig_params);
681
1.58k
  curlx_dyn_free(&sig_base);
682
1.58k
  curlx_dyn_free(&sig_hdr);
683
1.58k
  curlx_dyn_free(&input_hdr);
684
1.58k
  curlx_dyn_free(&authority_buf);
685
1.58k
  curlx_dyn_free(&query_dyn);
686
1.58k
  return result;
687
1.43k
}
688
689
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */