Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: add validFromDate and validToDate fields to X509Certificate #54159

Merged
merged 1 commit into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,35 @@ int PasswordCallback(char* buf, int size, int rwflag, void* u) {
return -1;
}

// Algorithm: http://howardhinnant.github.io/date_algorithms.html
constexpr int days_from_epoch(int y, unsigned m, unsigned d)
{
y -= m <= 2;
const int era = (y >= 0 ? y : y - 399) / 400;
const unsigned yoe = static_cast<unsigned>(y - era * 400); // [0, 399]
const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365]
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
return era * 146097 + static_cast<int>(doe) - 719468;
}

// tm must be in UTC
// using time_t causes problems on 32-bit systems and windows x64.
int64_t PortableTimeGM(struct tm* t) {
int year = t->tm_year + 1900;
int month = t->tm_mon;
if (month > 11) {
year += month / 12;
month %= 12;
} else if (month < 0) {
int years_diff = (11 - month) / 12;
year -= years_diff;
month += 12 * years_diff;
}
int days_since_epoch = days_from_epoch(year, month + 1, t->tm_mday);

return 60 * (60 * (24LL * static_cast<int64_t>(days_since_epoch) + t->tm_hour) + t->tm_min) + t->tm_sec;
}

// ============================================================================
// SPKAC

Expand Down Expand Up @@ -816,6 +845,18 @@ BIOPointer X509View::getValidTo() const {
return bio;
}

int64_t X509View::getValidToTime() const {
struct tm tp;
ASN1_TIME_to_tm(X509_get0_notAfter(cert_), &tp);
return PortableTimeGM(&tp);
}

int64_t X509View::getValidFromTime() const {
struct tm tp;
ASN1_TIME_to_tm(X509_get0_notBefore(cert_), &tp);
return PortableTimeGM(&tp);
}

DataPointer X509View::getSerialNumber() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
Expand Down
2 changes: 2 additions & 0 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ class X509View final {
BIOPointer getInfoAccess() const;
BIOPointer getValidFrom() const;
BIOPointer getValidTo() const;
int64_t getValidFromTime() const;
int64_t getValidToTime() const;
DataPointer getSerialNumber() const;
Result<EVPKeyPointer, int> getPublicKey() const;
StackOfASN1 getKeyUsage() const;
Expand Down
20 changes: 20 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -2865,6 +2865,16 @@ added: v15.6.0

The date/time from which this certificate is valid.

### `x509.validFromDate`

<!-- YAML
added: REPLACEME
-->

* Type: {Date}

The date/time from which this certificate is valid, encapsulated in a `Date` object.

### `x509.validTo`

<!-- YAML
Expand All @@ -2875,6 +2885,16 @@ added: v15.6.0

The date/time until which this certificate is valid.

### `x509.validToDate`

<!-- YAML
added: REPLACEME
-->

* Type: {Date}

The date/time until which this certificate is valid, encapsulated in a `Date` object.

### `x509.verify(publicKey)`

<!-- YAML
Expand Down
20 changes: 20 additions & 0 deletions lib/internal/crypto/x509.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ class X509Certificate {
infoAccess: this.infoAccess,
validFrom: this.validFrom,
validTo: this.validTo,
validFromDate: this.validFromDate,
validToDate: this.validToDate,
fingerprint: this.fingerprint,
fingerprint256: this.fingerprint256,
fingerprint512: this.fingerprint512,
Expand Down Expand Up @@ -220,6 +222,24 @@ class X509Certificate {
return value;
}

get validFromDate() {
let value = this[kInternalState].get('validFromDate');
if (value === undefined) {
value = this[kHandle].validFromDate();
this[kInternalState].set('validFromDate', value);
}
return value;
}

get validToDate() {
let value = this[kInternalState].get('validToDate');
if (value === undefined) {
value = this[kHandle].validToDate();
this[kInternalState].set('validToDate', value);
}
return value;
}

get fingerprint() {
let value = this[kInternalState].get('fingerprint');
if (value === undefined) {
Expand Down
37 changes: 37 additions & 0 deletions src/crypto/crypto_x509.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ using v8::ArrayBufferView;
using v8::BackingStore;
using v8::Boolean;
using v8::Context;
using v8::Date;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
Expand Down Expand Up @@ -241,6 +242,18 @@ MaybeLocal<Value> GetValidTo(Environment* env, const ncrypto::X509View& view) {
return ret;
}

MaybeLocal<Value> GetValidFromDate(Environment* env,
const ncrypto::X509View& view) {
int64_t validFromTime = view.getValidFromTime();
return Date::New(env->context(), validFromTime * 1000.);
}

MaybeLocal<Value> GetValidToDate(Environment* env,
const ncrypto::X509View& view) {
int64_t validToTime = view.getValidToTime();
return Date::New(env->context(), validToTime * 1000.);
}

MaybeLocal<Value> GetSerialNumber(Environment* env,
const ncrypto::X509View& view) {
if (auto serial = view.getSerialNumber()) {
Expand Down Expand Up @@ -352,6 +365,26 @@ void ValidTo(const FunctionCallbackInfo<Value>& args) {
}
}

void ValidFromDate(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (GetValidFromDate(env, cert->view()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}

void ValidToDate(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (GetValidToDate(env, cert->view()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}

void SerialNumber(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
Expand Down Expand Up @@ -834,6 +867,8 @@ Local<FunctionTemplate> X509Certificate::GetConstructorTemplate(
SetProtoMethodNoSideEffect(isolate, tmpl, "issuer", Issuer);
SetProtoMethodNoSideEffect(isolate, tmpl, "validTo", ValidTo);
SetProtoMethodNoSideEffect(isolate, tmpl, "validFrom", ValidFrom);
SetProtoMethodNoSideEffect(isolate, tmpl, "validToDate", ValidToDate);
SetProtoMethodNoSideEffect(isolate, tmpl, "validFromDate", ValidFromDate);
SetProtoMethodNoSideEffect(
isolate, tmpl, "fingerprint", Fingerprint<EVP_sha1>);
SetProtoMethodNoSideEffect(
Expand Down Expand Up @@ -1001,6 +1036,8 @@ void X509Certificate::RegisterExternalReferences(
registry->Register(Issuer);
registry->Register(ValidTo);
registry->Register(ValidFrom);
registry->Register(ValidToDate);
registry->Register(ValidFromDate);
registry->Register(Fingerprint<EVP_sha1>);
registry->Register(Fingerprint<EVP_sha256>);
registry->Register(Fingerprint<EVP_sha512>);
Expand Down
77 changes: 77 additions & 0 deletions test/parallel/test-crypto-x509.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ const der = Buffer.from(
assert.strictEqual(x509.infoAccess, infoAccessCheck);
assert.strictEqual(x509.validFrom, 'Sep 3 21:40:37 2022 GMT');
assert.strictEqual(x509.validTo, 'Jun 17 21:40:37 2296 GMT');
assert.deepStrictEqual(x509.validFromDate, new Date('2022-09-03T21:40:37Z'));
assert.deepStrictEqual(x509.validToDate, new Date('2296-06-17T21:40:37Z'));
assert.strictEqual(
x509.fingerprint,
'8B:89:16:C4:99:87:D2:13:1A:64:94:36:38:A5:32:01:F0:95:3B:53');
Expand Down Expand Up @@ -359,3 +361,78 @@ UcXd/5qu2GhokrKU2cPttU+XAN2Om6a0

assert.strictEqual(cert.checkIssued(cert), false);
}

{
// Test date parsing of `validFromDate` and `validToDate` fields, according to RFC 5280.

// Validity dates up until the year 2049 are encoded as UTCTime.
// The fomatting of UTCTime changes from the year ~1949 to 1950~.
const certPemUTCTime = `-----BEGIN CERTIFICATE-----
MIIE/TCCAuWgAwIBAgIUHbXPaFnjeBehMvdHkXZ+E3a78QswDQYJKoZIhvcNAQEL
BQAwDTELMAkGA1UEBhMCS1IwIBgPMTk0OTEyMjUyMzU5NThaFw01MDAxMDEyMzU5
NThaMA0xCzAJBgNVBAYTAktSMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEAtFfV2DB2dZFFaR1PPZMmyo0mSDAxGReoixxlhQTFZZymU71emWV/6gR8MxAE
L5+uzpgBvOZWgEbELWeV/gzZGU/x1Cki0dSJ0B8Qwr5HvKX6oOZrJ8t+wn4SRceq
r6MRPskDpTjnvelt+VURGmawtKKHll5fSqfjRWkQC8WQHdogXylRjd3oIh9p1D5P
hphK/jKddxsRkLhJKQWqTjAy2v8hsJAxvpCPnlqMCXxjbQV41UTY8+kY3RPG3d6c
yHBGM7dzM7XWVc79V9z/rjdRcxE2eBqrJT/yR3Cok8wWVVfQEgBfpolHUZxA8K4N
tubTez9zsJy7xUG7udf91wXWVHMBHXg6m/u5nIW0fAXGMtnG/H6FMyyBDbJoUlqm
VRTG71DzvBXpd/qx2P5LkU1JjWY3U8HSn6Q1DJzMIrbOmWpdlFYXxzLlXU2vG8Q3
PmdAHDDYW3M2YBVCdKqOtsuL2dMDuqRWdi3iCCPSR2UCm4HzAVYSe2FP8SPcY3xs
1NX+oDSpTxXruJYHGUp10/pXoqMrGT1IBgv2Dhsm3jcfRLSXkaBDJIKLO6dXmLBt
rlxM0DphiKnP6lDjpv7EDMdwsakz0zib3JrTmSLSbwZXR4abITmtbYbTpY3XAq7c
adO8YCMTCtb50ZbYEpGDAjOcWFHUlQQMsgZM2zc8ZHPY4EkCAwEAAaNTMFEwHQYD
VR0OBBYEFExDmZyzdo8ccjX7iFIwU7JYMV+qMB8GA1UdIwQYMBaAFExDmZyzdo8c
cjX7iFIwU7JYMV+qMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB
ADEF/JIH+Ku9NqrO47Q/CEn9qpIgmqX10d1joDjchPY3OHIIyt8Xpo845mPBTM7L
dnMJSlkzJEk0ep9qAGGdKpBnLq8B/1mgCWQ81jwrwdYSsY+4xark+7+y0fij6qAt
L4T6aA37nbV5q5/DMOwZucFwRTf9ZI1IjC+MaQmnV01vGCogqqfLQ9v26bVBRE1K
UIixH0r3f/LWtuo0KaebZbb+oq6Zb8ljKJaUlt5OB8Zy5NrcP69r29QJUR57ukT6
rt7fk5mOj2NBLMCErLHa7E6+GAUG94QEgdKzZ4yr2aduhMAfnOnK/HfuXO8TVa8/
+oYENr47M8x139+yu92C8Be1MRk0VHteBaScUL+IaY3HgGbYR1lT0azvIyBN/DCN
bYczI7JQGYVitLuaUYFw/RtK7Qg1957/ZmGeGa+86aTLXbqsGjI951D81EIzdqod
1QW/Jn3yMNeVIzF9eYVEy2DIJjGgM2A8NWbqfWGUAUMRgyTxH1j42tnWG3eRnMsX
UnQfpY8i3v6gYoNNgEZktrqgpmukTWgl08TlDtBCjXTBkcBt4dxDApeoy7XWKq+/
qBY/+uIsG30BRgJhAwApjdnCs7l5xpwtqluXFwOxyTWNV5IfChO7QFqWPlSVIHML
UidvpWWipVLZgK+oDks+bKTobcoXGW9oXobiIYqslXPy
-----END CERTIFICATE-----`.trim();
const c1 = new X509Certificate(certPemUTCTime);

assert.deepStrictEqual(c1.validFromDate, new Date('1949-12-25T23:59:58Z'));
assert.deepStrictEqual(c1.validToDate, new Date('1950-01-01T23:59:58Z'));

// The GeneralizedTime format is used for dates in 2050 or later.
const certPemGeneralizedTime = `-----BEGIN CERTIFICATE-----
MIIE/TCCAuWgAwIBAgIUYHPUNd6S5xlNMjrWSaekgCBrbDQwDQYJKoZIhvcNAQEL
BQAwDTELMAkGA1UEBhMCS1IwIBcNNDkxMjI2MDAwMDAxWhgPMjA1MDAxMDIwMDAw
MDFaMA0xCzAJBgNVBAYTAktSMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEAlBPjQXHTzQWflvq6Lc01E0gVSSUQd5XnfK9K8TEN8ic/6iJVBWK8OwTmwh6u
KdSO+DrTpoTA3Wo4T7oSL89xsyJN5JHiIT2VdZvgcXkv+ZL+rZ2INzYSSXbPQ8V+
Md5A7tNWGJOvneD1Pb+AKrVXn6N1+xiKuv08U+d6ZCcv8P2cGUJCQr5BSg6eXPm2
ZIoFhNLDaqleci0P/Bs7uMwKjVr2IP99bCMwTS2STxexEmYf4J3wgNXBOHxspLcS
p7Yt3JgezvzRn5kijQi7ceS24q/fsGCCwB706mOKdYLCfEL1DhhEr27+XICw7zOF
Q8tSe33IfSdxejEVV+lf/jGW5zFH5m+lDTJC0VAUCBG5E7q57yFaoQ44CQWtbMHZ
+dtodKx4B0lzWXJs8xkGo0rl9/1CuY2iPX3lB6xxlX50ruj8stccMwarRzUvfkjw
AhnbUs9X1ooFyVXmVYXWzR0gP1/q05Zob03khX1NipGbMf0RBI4WlItkiRsrEl9x
08YPbrUyd7JnFkgG0O5TcmTzHr9cTJHg5BzclQA9/V0HuslSVOkKMMlKHky2zcqY
dDBmWtfTrvowaB7hTGD6YK4R9JCDUy7oeeK4ZUxRNCnJY698HodE9lQu+F0cJpbY
uZExFapE/AWA8ftlw2/fXoK0L3DhYsOVQkHd2YbrvzZEHVMCAwEAAaNTMFEwHQYD
VR0OBBYEFNptaIzozylFlD0+JKULue+5gvfZMB8GA1UdIwQYMBaAFNptaIzozylF
lD0+JKULue+5gvfZMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB
AFXP4SCP6VFMINaKE/rLJOaFiVVuS4qDfpGRjuBryNxey4ErBFsXcPi/D0LIsNkb
c3qsex1cJadZdg3sqdkyliyYgzjJn1fSsiPT8GMiMnckw9awIwqtucGf+6SPdL6o
9jp0xg6KsRHWjN0GetCz89hy9HwSiSwiLpTxVYOMLjQ+ey8KXPk0LNaXve/++hrr
gN+cvcPKkspAE5SMTSKqHwVUD4MRJgdQqYDqB6demCq9Yl+kyQg9gVnuzkpKeNBT
qNVeeA6gczCpYV4rUMqT0UVVPbPOcygwZP2o7tUyNk6fmYzyLpi5R+FYD/PoowFp
LOrIaG426QaXhLr4U0i+HD/LhHZ4AWWt0OYAvbkk/xrhmagUcyeOxUrcYl6tA3NQ
sjPV2FNGitX+zOyxfMxcjf0RpaBbyMsO6DSfQidDchFvPR9VFX4THs/0mP02IK27
MpsZj8AG2/jjPz6ytnWBJGuLeIt2sWnluZyldX+V9QEEhEmrEweUolacKF5ESODG
SHyZZVSUCK0bJfDfk5rXCQokWCIe+jHbW3CSWWmBRz6blZDeO/wI8nN4TWHDMCu6
lawls1QdAwfP4CWIq4T7gsn/YqxMs74zDCXIF0tfuPmw5FMeCYVgnXQ7et8HBfeE
CWwQO8JZjJqFtqtuzy2n+gLCvqePgG/gmSqHOPm2ZbLW
-----END CERTIFICATE-----`.trim();
const c2 = new X509Certificate(certPemGeneralizedTime);

assert.deepStrictEqual(c2.validFromDate, new Date('2049-12-26T00:00:01Z'));
assert.deepStrictEqual(c2.validToDate, new Date('2050-01-02T00:00:01Z'));
}
Loading