Coverage Report

Created: 2026-10-28 07:17

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