From 1aece0d1df155e60e4cb0ae42cb3ed7aa043e174 Mon Sep 17 00:00:00 2001 From: Alex Wilson Date: Tue, 22 Jan 2019 15:00:06 -0800 Subject: [PATCH] joyent/node-sshpk#60 certs should generate GeneralizedTime values for dates >2050 Reviewed by: Robert Mustacchi --- lib/formats/x509.js | 30 ++++++++-- test/openssl-cmd.js | 130 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 4 deletions(-) diff --git a/lib/formats/x509.js b/lib/formats/x509.js index cc0d2b3..0144c44 100644 --- a/lib/formats/x509.js +++ b/lib/formats/x509.js @@ -203,6 +203,14 @@ function readDate(der) { } } +function writeDate(der, date) { + if (date.getUTCFullYear() >= 2050 || date.getUTCFullYear() < 1950) { + der.writeString(dateToGTime(date), asn1.Ber.GeneralizedTime); + } else { + der.writeString(dateToUTCTime(date), asn1.Ber.UTCTime); + } +} + /* RFC5280, section 4.2.1.6 (GeneralName type) */ var ALTNAME = { OtherName: Local(0), @@ -415,9 +423,11 @@ function gTimeToDate(t) { return (d); } -function zeroPad(n) { +function zeroPad(n, m) { + if (m === undefined) + m = 2; var s = '' + n; - while (s.length < 2) + while (s.length < m) s = '0' + s; return (s); } @@ -434,6 +444,18 @@ function dateToUTCTime(d) { return (s); } +function dateToGTime(d) { + var s = ''; + s += zeroPad(d.getUTCFullYear(), 4); + s += zeroPad(d.getUTCMonth() + 1); + s += zeroPad(d.getUTCDate()); + s += zeroPad(d.getUTCHours()); + s += zeroPad(d.getUTCMinutes()); + s += zeroPad(d.getUTCSeconds()); + s += 'Z'; + return (s); +} + function sign(cert, key) { if (cert.signatures.x509 === undefined) cert.signatures.x509 = {}; @@ -532,8 +554,8 @@ function writeTBSCert(cert, der) { cert.issuer.toAsn1(der); der.startSequence(); - der.writeString(dateToUTCTime(cert.validFrom), asn1.Ber.UTCTime); - der.writeString(dateToUTCTime(cert.validUntil), asn1.Ber.UTCTime); + writeDate(der, cert.validFrom); + writeDate(der, cert.validUntil); der.endSequence(); var subject = cert.subjects[0]; diff --git a/test/openssl-cmd.js b/test/openssl-cmd.js index dfcbfbb..af1a967 100644 --- a/test/openssl-cmd.js +++ b/test/openssl-cmd.js @@ -541,6 +541,136 @@ test('utf8string in issuer DN (#40)', function (t) { kid.stdin.end(); }); +test('certs with <2050 dates should use UTCTime', function (t) { + var pem = fs.readFileSync(path.join(testDir, 'id_rsa')); + var key = sshpk.parsePrivateKey(pem, 'pkcs1'); + + var id = sshpk.identityFromDN('cn=foobar'); + var opts = {}; + opts.validFrom = new Date('1990-01-02T03:04:05Z'); + opts.validUntil = new Date('2010-01-02T03:04:05Z'); + var cert = sshpk.createSelfSignedCertificate(id, key, opts); + var certPem = cert.toBuffer('pem'); + + var kid = spawn('openssl', ['asn1parse']); + var bufs = []; + kid.stdout.on('data', bufs.push.bind(bufs)); + kid.on('close', function (rc) { + t.equal(rc, 0); + var output = Buffer.concat(bufs).toString('utf8'); + var lines = output.split('\n'); + var found = 0; + for (var i = 0; i < lines.length; ++i) { + if (!lines[i]) + continue; + var line = asn1parse_line2obj(lines[i]); + if (line.tag === 'UTCTIME') { + if (line.value === '900102030405Z' || + line.value === '100102030405Z') { + ++found; + } else { + t.fail('unexpected utctime: ' + + line.value); + } + } + } + t.equal(found, 2); + t.end(); + }); + kid.stdin.write(certPem); + kid.stdin.end(); +}); + +test('certs with >=2050 dates should use GeneralizedTime', function (t) { + var pem = fs.readFileSync(path.join(testDir, 'id_rsa')); + var key = sshpk.parsePrivateKey(pem, 'pkcs1'); + + var id = sshpk.identityFromDN('cn=foobar'); + var opts = {}; + opts.validFrom = new Date('2050-01-02T03:04:05Z'); + opts.validUntil = new Date('2051-01-02T03:04:05Z'); + var cert = sshpk.createSelfSignedCertificate(id, key, opts); + var certPem = cert.toBuffer('pem'); + + var kid = spawn('openssl', ['asn1parse']); + var bufs = []; + kid.stdout.on('data', bufs.push.bind(bufs)); + kid.on('close', function (rc) { + t.equal(rc, 0); + var output = Buffer.concat(bufs).toString('utf8'); + var lines = output.split('\n'); + var found = 0; + for (var i = 0; i < lines.length; ++i) { + if (!lines[i]) + continue; + var line = asn1parse_line2obj(lines[i]); + if (line.tag === 'UTCTIME') { + t.fail('unexpected utctime: ' + line.value); + } + if (line.tag === 'GENERALIZEDTIME') { + if (line.value === '20500102030405Z') { + ++found; + } else if (line.value === '20510102030405Z') { + ++found; + } else { + t.fail('bad gentime: ' + line.value); + } + } + } + t.equal(found, 2); + t.end(); + }); + kid.stdin.write(certPem); + kid.stdin.end(); +}); + +test('certs with <1950 dates should use GeneralizedTime', function (t) { + var pem = fs.readFileSync(path.join(testDir, 'id_rsa')); + var key = sshpk.parsePrivateKey(pem, 'pkcs1'); + + var id = sshpk.identityFromDN('cn=foobar'); + var opts = {}; + opts.validFrom = new Date('1949-01-02T03:04:05Z'); + opts.validUntil = new Date('1950-01-02T03:04:05Z'); + var cert = sshpk.createSelfSignedCertificate(id, key, opts); + var certPem = cert.toBuffer('pem'); + + var kid = spawn('openssl', ['asn1parse']); + var bufs = []; + kid.stdout.on('data', bufs.push.bind(bufs)); + kid.on('close', function (rc) { + t.equal(rc, 0); + var output = Buffer.concat(bufs).toString('utf8'); + var lines = output.split('\n'); + var found = 0; + for (var i = 0; i < lines.length; ++i) { + if (!lines[i]) + continue; + var line = asn1parse_line2obj(lines[i]); + if (line.tag === 'UTCTIME') { + if (line.value === '500102030405Z') { + ++found; + } else { + t.fail('unexpected utctime: ' + + line.value); + } + } + if (line.tag === 'GENERALIZEDTIME') { + if (line.value === '19490102030405Z') { + ++found; + } else { + t.fail('unexpected gentime: ' + + line.value); + } + } + } + t.equal(found, 2); + t.end(); + }); + kid.stdin.write(certPem); + kid.stdin.end(); +}); + test('teardown', function (t) { temp.cleanup(function () { t.end();