Coverage Report

Created: 2026-10-19 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/boringssl/crypto/x509/x_x509.cc
Line
Count
Source
1
// Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include <assert.h>
16
#include <limits.h>
17
#include <stdio.h>
18
19
#include <openssl/asn1.h>
20
#include <openssl/asn1t.h>
21
#include <openssl/bytestring.h>
22
#include <openssl/evp.h>
23
#include <openssl/mem.h>
24
#include <openssl/obj.h>
25
#include <openssl/pool.h>
26
#include <openssl/x509.h>
27
28
#include "../asn1/internal.h"
29
#include "../bytestring/internal.h"
30
#include "../evp/internal.h"
31
#include "../internal.h"
32
#include "../mem_internal.h"
33
#include "internal.h"
34
35
36
using namespace bssl;
37
38
static ExDataClass g_ex_data_class;
39
40
static constexpr CBS_ASN1_TAG kVersionTag =
41
    CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0;
42
static constexpr CBS_ASN1_TAG kIssuerUIDTag = CBS_ASN1_CONTEXT_SPECIFIC | 1;
43
static constexpr CBS_ASN1_TAG kSubjectUIDTag = CBS_ASN1_CONTEXT_SPECIFIC | 2;
44
static constexpr CBS_ASN1_TAG kExtensionsTag =
45
    CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 3;
46
47
235k
X509Impl::X509Impl() : RefCounted(CheckSubClass()) {
48
235k
  CRYPTO_new_ex_data(&ex_data);
49
235k
}
50
51
235k
X509 *X509_new() { return New<X509Impl>(); }
52
53
235k
X509Impl::~X509Impl() {
54
235k
  CRYPTO_free_ex_data(&g_ex_data_class, &ex_data);
55
56
235k
  sk_X509_EXTENSION_pop_free(extensions, X509_EXTENSION_free);
57
235k
  X509_CERT_AUX_free(aux);
58
235k
}
59
60
839k
void X509_free(X509 *x509) {
61
839k
  if (x509 == nullptr) {
62
391k
    return;
63
391k
  }
64
447k
  auto *impl = FromOpaque(x509);
65
447k
  impl->DecRefInternal();
66
447k
}
67
68
X509 *X509_parse_with_algorithms(const CRYPTO_BUFFER *buf,
69
                                 const EVP_PKEY_ALG *const *algs,
70
211k
                                 size_t num_algs) {
71
211k
  UniquePtr<X509Impl> ret(FromOpaque(X509_new()));
72
211k
  if (ret == nullptr) {
73
0
    return nullptr;
74
0
  }
75
76
  // Save the buffer to cache the original encoding.
77
211k
  ret->buf = UpRef(buf);
78
79
  // Parse the Certificate.
80
211k
  CBS cbs, cert, tbs;
81
211k
  CRYPTO_BUFFER_init_CBS(buf, &cbs);
82
211k
  if (!CBS_get_asn1(&cbs, &cert, CBS_ASN1_SEQUENCE) ||  //
83
211k
      CBS_len(&cbs) != 0 ||
84
      // Bound the length to comfortably fit in an int. Lengths in this
85
      // module often omit overflow checks.
86
211k
      CBS_len(&cert) > INT_MAX / 2 ||
87
211k
      !CBS_get_asn1(&cert, &tbs, CBS_ASN1_SEQUENCE) ||
88
211k
      !x509_parse_algorithm(&cert, ret->sig_alg.get()) ||
89
      // For just the signature field, we accept non-minimal BER lengths, though
90
      // not indefinite-length encoding. See b/18228011.
91
      //
92
      // TODO(crbug.com/boringssl/354): Switch the affected callers to convert
93
      // the certificate before parsing and then remove this workaround.
94
206k
      !asn1_parse_bit_string_with_bad_length(&cert, ret->signature.get()) ||
95
205k
      CBS_len(&cert) != 0) {
96
6.38k
    OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
97
6.38k
    return nullptr;
98
6.38k
  }
99
100
  // Parse the TBSCertificate.
101
205k
  if (CBS_peek_asn1_tag(&tbs, kVersionTag)) {
102
199k
    CBS wrapper;
103
199k
    uint64_t version;
104
199k
    if (!CBS_get_asn1(&tbs, &wrapper, kVersionTag) ||
105
199k
        !CBS_get_asn1_uint64(&wrapper, &version) ||  //
106
199k
        CBS_len(&wrapper) != 0) {
107
309
      OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
108
309
      return nullptr;
109
309
    }
110
    // The version must be one of v1(0), v2(1), or v3(2).
111
    // TODO(https://crbug.com/42290225): Also reject `X509_VERSION_1`. v1 is
112
    // DEFAULT, so DER requires it be omitted.
113
199k
    if (version != X509_VERSION_1 && version != X509_VERSION_2 &&
114
193k
        version != X509_VERSION_3) {
115
807
      OPENSSL_PUT_ERROR(X509, X509_R_INVALID_VERSION);
116
807
      return nullptr;
117
807
    }
118
198k
    ret->version = static_cast<uint8_t>(version);
119
198k
  } else {
120
5.86k
    ret->version = X509_VERSION_1;
121
5.86k
  }
122
204k
  CBS validity;
123
204k
  if (!asn1_parse_integer(&tbs, ret->serialNumber.get(), /*tag=*/0) ||
124
203k
      !x509_parse_algorithm(&tbs, ret->tbs_sig_alg.get()) ||
125
203k
      !x509_parse_name(&tbs, &ret->issuer) ||
126
203k
      !CBS_get_asn1(&tbs, &validity, CBS_ASN1_SEQUENCE) ||
127
199k
      !asn1_parse_time(&validity, ret->notBefore.get(),
128
199k
                       /*allow_utc_timezone_offset=*/1) ||
129
199k
      !asn1_parse_time(&validity, ret->notAfter.get(),
130
199k
                       /*allow_utc_timezone_offset=*/1) ||
131
199k
      CBS_len(&validity) != 0 ||  //
132
199k
      !x509_parse_name(&tbs, &ret->subject) ||
133
198k
      !x509_parse_public_key(&tbs, &ret->key, Span(algs, num_algs))) {
134
6.26k
    OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
135
6.26k
    return nullptr;
136
6.26k
  }
137
  // Per RFC 5280, section 4.1.2.8, these fields require v2 or v3:
138
198k
  if (ret->version >= X509_VERSION_2 &&
139
196k
      CBS_peek_asn1_tag(&tbs, kIssuerUIDTag)) {
140
6.69k
    ret->issuerUID.reset(ASN1_BIT_STRING_new());
141
6.69k
    if (ret->issuerUID == nullptr ||
142
6.69k
        !asn1_parse_bit_string(&tbs, ret->issuerUID.get(), kIssuerUIDTag)) {
143
39
      return nullptr;
144
39
    }
145
6.69k
  }
146
198k
  if (ret->version >= X509_VERSION_2 &&
147
196k
      CBS_peek_asn1_tag(&tbs, kSubjectUIDTag)) {
148
426
    ret->subjectUID.reset(ASN1_BIT_STRING_new());
149
426
    if (ret->subjectUID == nullptr ||
150
426
        !asn1_parse_bit_string(&tbs, ret->subjectUID.get(), kSubjectUIDTag)) {
151
64
      return nullptr;
152
64
    }
153
426
  }
154
  // Per RFC 5280, section 4.1.2.9, extensions require v3:
155
197k
  if (ret->version >= X509_VERSION_3 &&
156
190k
      CBS_peek_asn1_tag(&tbs, kExtensionsTag)) {
157
188k
    CBS wrapper;
158
188k
    if (!CBS_get_asn1(&tbs, &wrapper, kExtensionsTag)) {
159
42
      OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
160
42
      return nullptr;
161
42
    }
162
    // TODO(crbug.com/442221114, crbug.com/42290219): Empty extension lists
163
    // should be rejected. Extensions is a SEQUENCE SIZE (1..MAX), so it cannot
164
    // be empty. An empty extensions list is encoded by omitting the OPTIONAL
165
    // field. libpki already rejects this.
166
188k
    const uint8_t *p = CBS_data(&wrapper);
167
188k
    ret->extensions = d2i_X509_EXTENSIONS(nullptr, &p, CBS_len(&wrapper));
168
188k
    if (ret->extensions == nullptr ||
169
186k
        p != CBS_data(&wrapper) + CBS_len(&wrapper)) {
170
1.39k
      OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
171
1.39k
      return nullptr;
172
1.39k
    }
173
188k
  }
174
196k
  if (CBS_len(&tbs) != 0) {
175
1.01k
    OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
176
1.01k
    return nullptr;
177
1.01k
  }
178
179
195k
  return ret.release();
180
196k
}
181
182
211k
X509 *X509_parse_from_buffer(const CRYPTO_BUFFER *buf) {
183
211k
  auto algs = GetDefaultEVPAlgorithms();
184
211k
  return X509_parse_with_algorithms(buf, algs.data(), algs.size());
185
211k
}
186
187
13.5k
static UniquePtr<X509> x509_parse(CBS *cbs) {
188
13.5k
  CBS cert;
189
13.5k
  if (!CBS_get_asn1_element(cbs, &cert, CBS_ASN1_SEQUENCE)) {
190
1.23k
    OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
191
1.23k
    return nullptr;
192
1.23k
  }
193
194
12.3k
  UniquePtr<CRYPTO_BUFFER> buf(CRYPTO_BUFFER_new_from_CBS(&cert, nullptr));
195
12.3k
  if (buf == nullptr) {
196
0
    return nullptr;
197
0
  }
198
12.3k
  return UniquePtr<X509>(X509_parse_from_buffer(buf.get()));
199
12.3k
}
200
201
32.7k
int bssl::x509_marshal_tbs_cert(CBB *cbb, const X509 *x509) {
202
32.7k
  auto *impl = FromOpaque(x509);
203
32.7k
  if (impl->buf != nullptr) {
204
    // Replay the saved TBSCertificate from the `CRYPTO_BUFFER`, to verify
205
    // exactly what we parsed. The `CRYPTO_BUFFER` contains the full
206
    // Certificate, so we need to find the TBSCertificate portion.
207
29.8k
    CBS cbs, cert, tbs;
208
29.8k
    CRYPTO_BUFFER_init_CBS(impl->buf.get(), &cbs);
209
29.8k
    if (!CBS_get_asn1(&cbs, &cert, CBS_ASN1_SEQUENCE) ||
210
29.8k
        !CBS_get_asn1_element(&cert, &tbs, CBS_ASN1_SEQUENCE)) {
211
      // This should be impossible.
212
0
      OPENSSL_PUT_ERROR(X509, ERR_R_INTERNAL_ERROR);
213
0
      return 0;
214
0
    }
215
29.8k
    return CBB_add_bytes(cbb, CBS_data(&tbs), CBS_len(&tbs));
216
29.8k
  }
217
218
  // No saved TBSCertificate encoding. Encode it anew.
219
2.88k
  CBB tbs, version, validity, extensions;
220
2.88k
  if (!CBB_add_asn1(cbb, &tbs, CBS_ASN1_SEQUENCE)) {
221
0
    return 0;
222
0
  }
223
2.88k
  if (impl->version != X509_VERSION_1) {
224
2.80k
    if (!CBB_add_asn1(&tbs, &version, kVersionTag) ||
225
2.80k
        !CBB_add_asn1_uint64(&version, impl->version)) {
226
0
      return 0;
227
0
    }
228
2.80k
  }
229
2.88k
  if (!asn1_marshal_integer(&tbs, impl->serialNumber.get(), /*tag=*/0) ||
230
2.88k
      !x509_marshal_algorithm(&tbs, impl->tbs_sig_alg.get()) ||
231
2.88k
      !x509_marshal_name(&tbs, &impl->issuer) ||
232
2.88k
      !CBB_add_asn1(&tbs, &validity, CBS_ASN1_SEQUENCE) ||
233
2.88k
      !asn1_marshal_time(&validity, impl->notBefore.get()) ||
234
2.88k
      !asn1_marshal_time(&validity, impl->notAfter.get()) ||
235
2.88k
      !x509_marshal_name(&tbs, &impl->subject) ||
236
2.88k
      !x509_marshal_public_key(&tbs, &impl->key) ||
237
2.88k
      (impl->issuerUID != nullptr &&
238
64
       !asn1_marshal_bit_string(&tbs, impl->issuerUID.get(), kIssuerUIDTag)) ||
239
2.88k
      (impl->subjectUID != nullptr &&
240
41
       !asn1_marshal_bit_string(&tbs, impl->subjectUID.get(),
241
41
                                kSubjectUIDTag))) {
242
0
    return 0;
243
0
  }
244
2.88k
  if (impl->extensions != nullptr) {
245
2.62k
    int len = i2d_X509_EXTENSIONS(impl->extensions, nullptr);
246
2.62k
    uint8_t *out;
247
2.62k
    if (len <= 0 ||  //
248
2.62k
        !CBB_add_asn1(&tbs, &extensions, kExtensionsTag) ||
249
2.62k
        !CBB_add_space(&extensions, &out, len) ||
250
2.62k
        i2d_X509_EXTENSIONS(impl->extensions, &out) != len) {
251
0
      return 0;
252
0
    }
253
2.62k
  }
254
2.88k
  return CBB_flush(cbb);
255
2.88k
}
256
257
int bssl::x509_get_or_marshal_tbs_cert(CBS *out, Array<uint8_t> *scratch,
258
0
                                       const X509 *x509) {
259
0
  auto *impl = FromOpaque(x509);
260
0
  if (impl->buf != nullptr) {
261
    // If there is a cached TBSCertificate encoding, just use it and avoid
262
    // making a copy.
263
0
    CBS cbs, cert;
264
0
    CRYPTO_BUFFER_init_CBS(impl->buf.get(), &cbs);
265
0
    if (!CBS_get_asn1(&cbs, &cert, CBS_ASN1_SEQUENCE) ||
266
0
        !CBS_get_asn1_element(&cert, out, CBS_ASN1_SEQUENCE)) {
267
      // This should be impossible.
268
0
      OPENSSL_PUT_ERROR(X509, ERR_R_INTERNAL_ERROR);
269
0
      return 0;
270
0
    }
271
0
  } else {
272
0
    ScopedCBB cbb;
273
0
    if (!CBB_init(cbb.get(), 128) || !x509_marshal_tbs_cert(cbb.get(), x509) ||
274
0
        !CBBFinishArray(cbb.get(), scratch)) {
275
0
      OPENSSL_PUT_ERROR(X509, ERR_R_INTERNAL_ERROR);
276
0
      return 0;
277
0
    }
278
0
    CBS_init(out, scratch->data(), scratch->size());
279
0
  }
280
0
  return 1;
281
0
}
282
283
29.8k
static int x509_marshal(CBB *cbb, const X509 *x509) {
284
29.8k
  CBB cert;
285
29.8k
  auto *impl = FromOpaque(x509);
286
29.8k
  return CBB_add_asn1(cbb, &cert, CBS_ASN1_SEQUENCE) &&
287
29.8k
         x509_marshal_tbs_cert(&cert, x509) &&
288
29.8k
         x509_marshal_algorithm(&cert, impl->sig_alg.get()) &&
289
29.8k
         asn1_marshal_bit_string(&cert, impl->signature.get(), /*tag=*/0) &&
290
29.8k
         CBB_flush(cbb);
291
29.8k
}
292
293
13.5k
X509 *d2i_X509(X509 **out, const uint8_t **inp, long len) {
294
13.5k
  return D2IFromCBS(out, inp, len, x509_parse);
295
13.5k
}
296
297
29.8k
int i2d_X509(const X509 *x509, uint8_t **outp) {
298
29.8k
  if (x509 == nullptr) {
299
0
    OPENSSL_PUT_ERROR(ASN1, ASN1_R_MISSING_VALUE);
300
0
    return -1;
301
0
  }
302
303
29.8k
  return I2DFromCBB(
304
29.8k
      /*initial_capacity=*/256, outp,
305
29.8k
      [&](CBB *cbb) -> bool { return x509_marshal(cbb, x509); });
306
29.8k
}
307
308
IMPLEMENT_EXTERN_ASN1_PARSE_NEW(X509, X509_new, X509_free, CBS_ASN1_SEQUENCE,
309
                                x509_parse, x509_marshal)
310
311
0
X509 *X509_dup(const X509 *x509) {
312
0
  uint8_t *der = nullptr;
313
0
  int len = i2d_X509(x509, &der);
314
0
  if (len < 0) {
315
0
    return nullptr;
316
0
  }
317
318
0
  const uint8_t *inp = der;
319
0
  X509 *ret = d2i_X509(nullptr, &inp, len);
320
0
  OPENSSL_free(der);
321
0
  return ret;
322
0
}
323
324
0
int X509_up_ref(X509 *x) {
325
0
  auto *impl = FromOpaque(x);
326
0
  impl->UpRefInternal();
327
0
  return 1;
328
0
}
329
330
229k
X509 *X509_dup_ref(const X509 *x) {
331
229k
  if (x == nullptr) {
332
1.62k
    return nullptr;
333
1.62k
  }
334
228k
  return FromOpaque(x)->DupRefInternal();
335
229k
}
336
337
int X509_get_ex_new_index(long argl, void *argp, CRYPTO_EX_unused *unused,
338
                          CRYPTO_EX_dup *dup_unused,
339
0
                          CRYPTO_EX_free *free_func) {
340
0
  return CRYPTO_get_ex_new_index_ex(&g_ex_data_class, argl, argp, free_func);
341
0
}
342
343
0
int X509_set_ex_data(X509 *r, int idx, void *arg) {
344
0
  auto *impl = FromOpaque(r);
345
0
  return (CRYPTO_set_ex_data(&impl->ex_data, idx, arg));
346
0
}
347
348
0
void *X509_get_ex_data(X509 *r, int idx) {
349
0
  auto *impl = FromOpaque(r);
350
0
  return (CRYPTO_get_ex_data(&impl->ex_data, idx));
351
0
}
352
353
// X509_AUX ASN1 routines. X509_AUX is the name given to a certificate with
354
// extra info tagged on the end. Since these functions set how a certificate
355
// is trusted they should only be used when the certificate comes from a
356
// reliable source such as local storage.
357
358
0
X509 *d2i_X509_AUX(X509 **a, const unsigned char **pp, long length) {
359
0
  const unsigned char *q = *pp;
360
0
  X509 *ret;
361
0
  int freeret = 0;
362
363
0
  if (!a || *a == nullptr) {
364
0
    freeret = 1;
365
0
  }
366
0
  ret = d2i_X509(a, &q, length);
367
  // If certificate unreadable then forget it
368
0
  if (!ret) {
369
0
    return nullptr;
370
0
  }
371
0
  auto *impl = FromOpaque(ret);
372
  // update length
373
0
  length -= q - *pp;
374
  // Parse auxiliary information if there is any.
375
0
  if (length > 0 && !d2i_X509_CERT_AUX(&impl->aux, &q, length)) {
376
0
    goto err;
377
0
  }
378
0
  *pp = q;
379
0
  return ret;
380
0
err:
381
0
  if (freeret) {
382
0
    X509_free(ret);
383
0
    if (a) {
384
0
      *a = nullptr;
385
0
    }
386
0
  }
387
0
  return nullptr;
388
0
}
389
390
// Serialize trusted certificate to *pp or just return the required buffer
391
// length if pp == NULL.  We ultimately want to avoid modifying *pp in the
392
// error path, but that depends on similar hygiene in lower-level functions.
393
// Here we avoid compounding the problem.
394
0
static int i2d_x509_aux_internal(const X509 *a, unsigned char **pp) {
395
0
  int length, tmplen;
396
0
  unsigned char *start = pp != nullptr ? *pp : nullptr;
397
398
0
  assert(pp == nullptr || *pp != nullptr);
399
400
  // This might perturb *pp on error, but fixing that belongs in i2d_X509()
401
  // not here.  It should be that if a == NULL length is zero, but we check
402
  // both just in case.
403
0
  length = i2d_X509(a, pp);
404
0
  if (length <= 0 || a == nullptr) {
405
0
    return length;
406
0
  }
407
408
0
  auto *impl = FromOpaque(a);
409
0
  if (impl->aux != nullptr) {
410
0
    tmplen = i2d_X509_CERT_AUX(impl->aux, pp);
411
0
    if (tmplen < 0) {
412
0
      if (start != nullptr) {
413
0
        *pp = start;
414
0
      }
415
0
      return tmplen;
416
0
    }
417
0
    length += tmplen;
418
0
  }
419
420
0
  return length;
421
0
}
422
423
// Serialize trusted certificate to *pp, or just return the required buffer
424
// length if pp == NULL.
425
//
426
// When pp is not NULL, but *pp == NULL, we allocate the buffer, but since
427
// we're writing two ASN.1 objects back to back, we can't have i2d_X509() do
428
// the allocation, nor can we allow i2d_X509_CERT_AUX() to increment the
429
// allocated buffer.
430
0
int i2d_X509_AUX(const X509 *a, unsigned char **pp) {
431
0
  int length;
432
0
  unsigned char *tmp;
433
434
  // Buffer provided by caller
435
0
  if (pp == nullptr || *pp != nullptr) {
436
0
    return i2d_x509_aux_internal(a, pp);
437
0
  }
438
439
  // Obtain the combined length
440
0
  if ((length = i2d_x509_aux_internal(a, nullptr)) <= 0) {
441
0
    return length;
442
0
  }
443
444
  // Allocate requisite combined storage
445
0
  *pp = tmp = reinterpret_cast<uint8_t *>(OPENSSL_malloc(length));
446
0
  if (tmp == nullptr) {
447
0
    return -1;  // Push error onto error stack?
448
0
  }
449
450
  // Encode, but keep *pp at the originally malloced pointer
451
0
  length = i2d_x509_aux_internal(a, &tmp);
452
0
  if (length <= 0) {
453
0
    OPENSSL_free(*pp);
454
0
    *pp = nullptr;
455
0
  }
456
0
  return length;
457
0
}
458
459
2.88k
int i2d_re_X509_tbs(X509 *x509, uint8_t **outp) {
460
2.88k
  auto *impl = FromOpaque(x509);
461
2.88k
  impl->buf = nullptr;
462
2.88k
  return i2d_X509_tbs(x509, outp);
463
2.88k
}
464
465
2.88k
int i2d_X509_tbs(const X509 *x509, uint8_t **outp) {
466
2.88k
  return I2DFromCBB(/*initial_capacity=*/128, outp, [&](CBB *cbb) -> bool {
467
2.88k
    return x509_marshal_tbs_cert(cbb, x509);
468
2.88k
  });
469
2.88k
}
470
471
0
int X509_set1_signature_algo(X509 *x509, const X509_ALGOR *algo) {
472
0
  auto *impl = FromOpaque(x509);
473
0
  return X509_ALGOR_copy(impl->sig_alg.get(), algo) &&
474
0
         X509_ALGOR_copy(impl->tbs_sig_alg.get(), algo);
475
0
}
476
477
0
int X509_set1_signature_value(X509 *x509, const uint8_t *sig, size_t sig_len) {
478
0
  return ASN1_STRING_set(FromOpaque(x509)->signature.get(), sig, sig_len);
479
0
}
480
481
void X509_get0_signature(const ASN1_BIT_STRING **psig, const X509_ALGOR **palg,
482
0
                         const X509 *x) {
483
0
  const auto *impl = FromOpaque(x);
484
0
  if (psig) {
485
0
    *psig = impl->signature.get();
486
0
  }
487
0
  if (palg) {
488
0
    *palg = impl->sig_alg.get();
489
0
  }
490
0
}
491
492
0
int X509_get_signature_nid(const X509 *x) {
493
0
  const auto *impl = FromOpaque(x);
494
0
  return OBJ_obj2nid(impl->sig_alg->algorithm);
495
0
}