crypto: expose signatureAlgorithm on X509Certificate

Adds the `signatureAlgorithm` property to a X509Certificate allowing
users to retrieve a string representing the algorithm used to sign the
certificate. This string is defined by the OpenSSL library.

Fixes: https://github.com/nodejs/node/issues/59103
PR-URL: https://github.com/nodejs/node/pull/59235
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
Patrick Costa 2025-09-16 16:27:04 -03:00 committed by GitHub
parent f1a8f447d7
commit 64a8b4b1ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 134 additions and 0 deletions

View File

@ -8,7 +8,9 @@
#include <openssl/rand.h>
#include <openssl/x509v3.h>
#include <algorithm>
#include <array>
#include <cstring>
#include <string_view>
#if OPENSSL_VERSION_MAJOR >= 3
#include <openssl/core_names.h>
#include <openssl/params.h>
@ -1094,6 +1096,29 @@ BIOPointer X509View::getValidTo() const {
return bio;
}
std::optional<std::string_view> X509View::getSignatureAlgorithm() const {
if (cert_ == nullptr) return std::nullopt;
int nid = X509_get_signature_nid(cert_);
if (nid == NID_undef) return std::nullopt;
const char* ln = OBJ_nid2ln(nid);
if (ln == nullptr) return std::nullopt;
return std::string_view(ln);
}
std::optional<std::string> X509View::getSignatureAlgorithmOID() const {
if (cert_ == nullptr) return std::nullopt;
const X509_ALGOR* alg = nullptr;
X509_get0_signature(nullptr, &alg, cert_);
if (alg == nullptr) return std::nullopt;
const ASN1_OBJECT* obj = nullptr;
X509_ALGOR_get0(&obj, nullptr, nullptr, alg);
if (obj == nullptr) return std::nullopt;
std::array<char, 128> buf{};
int len = OBJ_obj2txt(buf.data(), buf.size(), obj, 1);
if (len < 0 || static_cast<size_t>(len) >= buf.size()) return std::nullopt;
return std::string(buf.data(), static_cast<size_t>(len));
}
int64_t X509View::getValidToTime() const {
#ifdef OPENSSL_IS_BORINGSSL
// Boringssl does not implement ASN1_TIME_to_tm in a public way,

View File

@ -1191,6 +1191,8 @@ class X509View final {
BIOPointer getInfoAccess() const;
BIOPointer getValidFrom() const;
BIOPointer getValidTo() const;
std::optional<std::string_view> getSignatureAlgorithm() const;
std::optional<std::string> getSignatureAlgorithmOID() const;
int64_t getValidFromTime() const;
int64_t getValidToTime() const;
DataPointer getSerialNumber() const;

View File

@ -2971,6 +2971,26 @@ added:
The date/time until which this certificate is valid, encapsulated in a `Date` object.
### `x509.signatureAlgorithm`
<!-- YAML
added: REPLACEME
-->
* Type: {string|undefined}
The algorithm used to sign the certificate or `undefined` if the signature algorithm is unknown by OpenSSL.
### `x509.signatureAlgorithmOid`
<!-- YAML
added: REPLACEME
-->
* Type: {string}
The OID of the algorithm used to sign the certificate.
### `x509.verify(publicKey)`
<!-- YAML

View File

@ -142,6 +142,8 @@ class X509Certificate {
fingerprint512: this.fingerprint512,
keyUsage: this.keyUsage,
serialNumber: this.serialNumber,
signatureAlgorithm: this.signatureAlgorithm,
signatureAlgorithmOid: this.signatureAlgorithmOid,
}, opts)}`;
}
@ -285,6 +287,24 @@ class X509Certificate {
return value;
}
get signatureAlgorithm() {
let value = this[kInternalState].get('signatureAlgorithm');
if (value === undefined) {
value = this[kHandle].signatureAlgorithm();
this[kInternalState].set('signatureAlgorithm', value);
}
return value;
}
get signatureAlgorithmOid() {
let value = this[kInternalState].get('signatureAlgorithmOid');
if (value === undefined) {
value = this[kHandle].signatureAlgorithmOid();
this[kInternalState].set('signatureAlgorithmOid', value);
}
return value;
}
get raw() {
let value = this[kInternalState].get('raw');
if (value === undefined) {

View File

@ -225,6 +225,30 @@ MaybeLocal<Value> GetValidToDate(Environment* env, const X509View& view) {
return Date::New(env->context(), validToTime * 1000.);
}
MaybeLocal<Value> GetSignatureAlgorithm(Environment* env,
const X509View& view) {
auto algo = view.getSignatureAlgorithm();
if (!algo.has_value()) [[unlikely]]
return Undefined(env->isolate());
Local<Value> ret;
if (!ToV8Value(env, algo.value()).ToLocal(&ret)) {
return {};
}
return ret;
}
MaybeLocal<Value> GetSignatureAlgorithmOID(Environment* env,
const X509View& view) {
auto oid = view.getSignatureAlgorithmOID();
if (!oid.has_value()) [[unlikely]]
return Undefined(env->isolate());
Local<Value> ret;
if (!ToV8Value(env, oid.value()).ToLocal(&ret)) {
return {};
}
return ret;
}
MaybeLocal<Value> GetSerialNumber(Environment* env, const X509View& view) {
if (auto serial = view.getSerialNumber()) {
return OneByteString(env->isolate(),
@ -342,6 +366,26 @@ void ValidToDate(const FunctionCallbackInfo<Value>& args) {
}
}
void SignatureAlgorithm(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (GetSignatureAlgorithm(env, cert->view()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void SignatureAlgorithmOID(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (GetSignatureAlgorithmOID(env, cert->view()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void SerialNumber(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
@ -822,6 +866,10 @@ Local<FunctionTemplate> X509Certificate::GetConstructorTemplate(
SetProtoMethodNoSideEffect(isolate, tmpl, "validFrom", ValidFrom);
SetProtoMethodNoSideEffect(isolate, tmpl, "validToDate", ValidToDate);
SetProtoMethodNoSideEffect(isolate, tmpl, "validFromDate", ValidFromDate);
SetProtoMethodNoSideEffect(
isolate, tmpl, "signatureAlgorithm", SignatureAlgorithm);
SetProtoMethodNoSideEffect(
isolate, tmpl, "signatureAlgorithmOid", SignatureAlgorithmOID);
SetProtoMethodNoSideEffect(
isolate, tmpl, "fingerprint", Fingerprint<Digest::SHA1>);
SetProtoMethodNoSideEffect(
@ -996,6 +1044,8 @@ void X509Certificate::RegisterExternalReferences(
registry->Register(ValidFrom);
registry->Register(ValidToDate);
registry->Register(ValidFromDate);
registry->Register(SignatureAlgorithm);
registry->Register(SignatureAlgorithmOID);
registry->Register(Fingerprint<Digest::SHA1>);
registry->Register(Fingerprint<Digest::SHA256>);
registry->Register(Fingerprint<Digest::SHA512>);

View File

@ -114,6 +114,9 @@ const der = Buffer.from(
assert.strictEqual(x509.keyUsage, undefined);
assert.strictEqual(x509.serialNumber.toUpperCase(), '147D36C1C2F74206DE9FAB5F2226D78ADB00A426');
assert.strictEqual(x509.signatureAlgorithm, 'sha256WithRSAEncryption');
assert.strictEqual(x509.signatureAlgorithmOid, '1.2.840.113549.1.1.11');
assert.deepStrictEqual(x509.raw, der);
if (!process.features.openssl_is_boringssl) {
@ -448,3 +451,17 @@ CWwQO8JZjJqFtqtuzy2n+gLCvqePgG/gmSqHOPm2ZbLW
assert.deepStrictEqual(c2.validToDate, new Date('2050-01-02T00:00:01Z'));
}
}
{
const certPem = `-----BEGIN CERTIFICATE-----
MIGXMHugAwIBAgIBATANBgkrBgEEAYaNHwEFADASMRAwDgYDVQQDEwdVbmtub3du
MB4XDTI0MDEwMTAwMDAwMFoXDTM0MDEwMTAwMDAwMFowEjEQMA4GA1UEAxMHVW5r
bm93bjAaMA0GCSqGSIb3DQEBAQUAAwkAAAAAAAAAAAAwDQYJKwYBBAGGjR8BBQAD
CQAAAAAAAAAAAA==
-----END CERTIFICATE-----`;
const cert = new X509Certificate(certPem);
assert.strictEqual(cert.signatureAlgorithm, undefined);
assert.strictEqual(cert.signatureAlgorithmOid, '1.3.6.1.4.1.99999.1');
}