From e0408ebe30e98481987a3f8befd30522053cd5f8 Mon Sep 17 00:00:00 2001 From: David Watrous Date: Wed, 21 Oct 2020 15:16:01 -0400 Subject: [PATCH 1/4] Added alternate OID 1.3.14.3.2.29 for sha1 with RSA --- lib/oids.js | 2 ++ lib/x509.js | 18 ++++++++++++------ tests/unit/x509.js | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/oids.js b/lib/oids.js index 1c86c218..0ca96e98 100644 --- a/lib/oids.js +++ b/lib/oids.js @@ -42,6 +42,8 @@ _IN('1.2.840.10040.4.3', 'dsa-with-sha1'); _IN('1.3.14.3.2.7', 'desCBC'); _IN('1.3.14.3.2.26', 'sha1'); +// Deprecated equivalent of sha1WithRSAEncryption +_IN('1.3.14.3.2.29', 'sha1WithRSASignature'); _IN('2.16.840.1.101.3.4.2.1', 'sha256'); _IN('2.16.840.1.101.3.4.2.2', 'sha384'); _IN('2.16.840.1.101.3.4.2.3', 'sha512'); diff --git a/lib/x509.js b/lib/x509.js index 65dd854e..d46e0948 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -1081,8 +1081,9 @@ pki.createCertificate = function() { var oid = oids[child.signatureOid]; switch(oid) { case 'sha1WithRSAEncryption': - md = forge.md.sha1.create(); - break; + case 'sha1WithRSASignature': + md = forge.md.sha1.create(); + break; case 'md5WithRSAEncryption': md = forge.md.md5.create(); break; @@ -1118,8 +1119,9 @@ pki.createCertificate = function() { switch(child.signatureOid) { case oids.sha1WithRSAEncryption: - scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ - break; + case oids.sha1WithRSASignature: + scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ + break; case oids['RSASSA-PSS']: var hash, mgf; @@ -1339,8 +1341,9 @@ pki.certificateFromAsn1 = function(obj, computeHash) { var oid = oids[cert.signatureOid]; switch(oid) { case 'sha1WithRSAEncryption': - cert.md = forge.md.sha1.create(); - break; + case 'sha1WithRSASignature': + cert.md = forge.md.sha1.create(); + break; case 'md5WithRSAEncryption': cert.md = forge.md.md5.create(); break; @@ -1687,6 +1690,7 @@ pki.certificationRequestFromAsn1 = function(obj, computeHash) { var oid = oids[csr.signatureOid]; switch(oid) { case 'sha1WithRSAEncryption': + case 'sha1WithRSASignature': csr.md = forge.md.sha1.create(); break; case 'md5WithRSAEncryption': @@ -1857,6 +1861,7 @@ pki.createCertificationRequest = function() { var oid = oids[csr.signatureOid]; switch(oid) { case 'sha1WithRSAEncryption': + case 'sha1WithRSASignature': md = forge.md.sha1.create(); break; case 'md5WithRSAEncryption': @@ -1896,6 +1901,7 @@ pki.createCertificationRequest = function() { switch(csr.signatureOid) { case oids.sha1WithRSAEncryption: + case oids.sha1WithRSASignature: /* use PKCS#1 v1.5 padding scheme */ break; case oids['RSASSA-PSS']: diff --git a/tests/unit/x509.js b/tests/unit/x509.js index 8ae5c469..474e97a8 100644 --- a/tests/unit/x509.js +++ b/tests/unit/x509.js @@ -1252,6 +1252,24 @@ var UTIL = require('../../lib/util'); ASSERT.strictEqual(cert.issuer.hash, 'd43b6713ab1a8679f0b70e169e9df889ed387a4b'); }); + it('should verify certificate with sha1WithRSASignature signature', function() { + var certPem = '-----BEGIN CERTIFICATE-----\r\n' + + 'MIIBwjCCAS+gAwIBAgIQj2d4hVEz0L1DYFVhA9CxCzAJBgUrDgMCHQUAMA8xDTAL\r\n' + + 'BgNVBAMTBFZQUzEwHhcNMDcwODE4MDkyODUzWhcNMDgwODE3MDkyODUzWjAPMQ0w\r\n' + + 'CwYDVQQDEwRWUFMxMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaqKn40uaU\r\n' + + 'DbFL1NXXZ8/b4ZqDJ6eSI5lysMZHfZDs60G3ocbNKofBvURIutabrFuBCB2S5f/z\r\n' + + 'ICan0LR4uFpGuZ2I/PuVaU8X5fT8gBh7L636cWzHPPScYts00OyywEq381UB7XwX\r\n' + + 'YuWpM5kUW5rkbq1JV3ystTR/4YnLl48YtQIDAQABoycwJTATBgNVHSUEDDAKBggr\r\n' + + 'BgEFBQcDATAOBgNVHQ8EBwMFALAAAAAwCQYFKw4DAh0FAAOBgQBuUrU+J2Z5WKcO\r\n' + + 'VNjJHFUKo8qpbn8jKQZDl2nvVaXCTXQZblz/qxOm4FaGGzJ/m3GybVZNVfdyHg+U\r\n' + + 'lmDpFpOITkvcyNc3xjJCf2GVBo/VvdtVt7Myq0IQtAi/CXRK22BRNhSt9uu2EcRu\r\n' + + 'HIXdFWHEzi6eD4PpNw/0X3ID6Gxk4A==\r\n' + + '-----END CERTIFICATE-----\r\n'; + var cert = PKI.certificateFromPem(certPem, true); + ASSERT.equal(cert.signatureOid, PKI.oids['sha1WithRSASignature']); + ASSERT.equal(cert.md.algorithm, 'sha1'); + }); + it('should verify certificate with sha256WithRSAEncryption signature', function() { var certPem = '-----BEGIN CERTIFICATE-----\r\n' + 'MIIDuzCCAqOgAwIBAgIEO5vZjDANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\n' + From cbf53ba99a85dddea7432b93920d0349e9ad74ad Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 22:11:48 -0500 Subject: [PATCH 2/4] Add helper to create signature digest. - Reduce duplicate code. - Fix style nit. - Update changelog. --- CHANGELOG.md | 10 +++ lib/x509.js | 189 ++++++++++++++++----------------------------------- 2 files changed, 69 insertions(+), 130 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb5ff036..8f6c22f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ Forge ChangeLog - [x509] 'Expected' and 'Actual' issuers were backwards in verification failure message. +### Added +- [oid,x509]: Added OID `1.3.14.3.2.29 / sha1WithRSASignature` for sha1 with + RSA. Considered a deprecated equivalent to `1.2.840.113549.1.1.5 / + sha1WithRSAEncryption`. See [discussion and + links](https://github.com/digitalbazaar/forge/issues/825). + +### Changed +- [x509]: Reduce duplicate code with a helper function to create a signature + digest given an signature algorithm OID. + ## 1.1.0 - 2022-01-06 ### Fixed diff --git a/lib/x509.js b/lib/x509.js index d46e0948..d7d2f892 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -689,6 +689,44 @@ var _readSignatureParameters = function(oid, obj, fillDefaults) { return params; }; +/** + * Create signature digest for OID. + * + * @param options + * signatureOid: the OID specifying the signature algorithm. + * type: a human readable type for error messages + * @return a created md instance. throws if unknown oid. + */ +var _createSignatureDigest = function(options) { + switch(oids[options.signatureOid]) { + case 'sha1WithRSAEncryption': + case 'sha1WithRSASignature': + return forge.md.sha1.create(); + break; + case 'md5WithRSAEncryption': + return forge.md.md5.create(); + break; + case 'sha256WithRSAEncryption': + return forge.md.sha256.create(); + break; + case 'sha384WithRSAEncryption': + return forge.md.sha384.create(); + break; + case 'sha512WithRSAEncryption': + return forge.md.sha512.create(); + break; + case 'RSASSA-PSS': + return forge.md.sha256.create(); + break; + default: + var error = new Error( + 'Could not compute ' + options.type + ' digest. ' + + 'Unknown signature OID.'); + error.signatureOid = options.signatureOid; + throw error; + } +}; + /** * Converts an X.509 certificate from PEM format. * @@ -1076,37 +1114,11 @@ pki.createCertificate = function() { var md = child.md; if(md === null) { - // check signature OID for supported signature types - if(child.signatureOid in oids) { - var oid = oids[child.signatureOid]; - switch(oid) { - case 'sha1WithRSAEncryption': - case 'sha1WithRSASignature': - md = forge.md.sha1.create(); - break; - case 'md5WithRSAEncryption': - md = forge.md.md5.create(); - break; - case 'sha256WithRSAEncryption': - md = forge.md.sha256.create(); - break; - case 'sha384WithRSAEncryption': - md = forge.md.sha384.create(); - break; - case 'sha512WithRSAEncryption': - md = forge.md.sha512.create(); - break; - case 'RSASSA-PSS': - md = forge.md.sha256.create(); - break; - } - } - if(md === null) { - var error = new Error('Could not compute certificate digest. ' + - 'Unknown signature OID.'); - error.signatureOid = child.signatureOid; - throw error; - } + // create digest for OID signature types + md = _createSignatureDigest({ + signatureOid: child.signatureOid, + type: 'certificate' + }); // produce DER formatted TBSCertificate and digest it var tbsCertificate = child.tbsCertificate || pki.getTBSCertificate(child); @@ -1120,8 +1132,8 @@ pki.createCertificate = function() { switch(child.signatureOid) { case oids.sha1WithRSAEncryption: case oids.sha1WithRSASignature: - scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ - break; + scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ + break; case oids['RSASSA-PSS']: var hash, mgf; @@ -1335,38 +1347,11 @@ pki.certificateFromAsn1 = function(obj, computeHash) { cert.tbsCertificate = capture.tbsCertificate; if(computeHash) { - // check signature OID for supported signature types - cert.md = null; - if(cert.signatureOid in oids) { - var oid = oids[cert.signatureOid]; - switch(oid) { - case 'sha1WithRSAEncryption': - case 'sha1WithRSASignature': - cert.md = forge.md.sha1.create(); - break; - case 'md5WithRSAEncryption': - cert.md = forge.md.md5.create(); - break; - case 'sha256WithRSAEncryption': - cert.md = forge.md.sha256.create(); - break; - case 'sha384WithRSAEncryption': - cert.md = forge.md.sha384.create(); - break; - case 'sha512WithRSAEncryption': - cert.md = forge.md.sha512.create(); - break; - case 'RSASSA-PSS': - cert.md = forge.md.sha256.create(); - break; - } - } - if(cert.md === null) { - var error = new Error('Could not compute certificate digest. ' + - 'Unknown signature OID.'); - error.signatureOid = cert.signatureOid; - throw error; - } + // create digest for OID signature type + cert.md = _createSignatureDigest({ + signatureOid: cert.signatureOid, + type: 'certificate' + }); // produce DER formatted TBSCertificate and digest it var bytes = asn1.toDer(cert.tbsCertificate); @@ -1684,38 +1669,11 @@ pki.certificationRequestFromAsn1 = function(obj, computeHash) { csr.certificationRequestInfo = capture.certificationRequestInfo; if(computeHash) { - // check signature OID for supported signature types - csr.md = null; - if(csr.signatureOid in oids) { - var oid = oids[csr.signatureOid]; - switch(oid) { - case 'sha1WithRSAEncryption': - case 'sha1WithRSASignature': - csr.md = forge.md.sha1.create(); - break; - case 'md5WithRSAEncryption': - csr.md = forge.md.md5.create(); - break; - case 'sha256WithRSAEncryption': - csr.md = forge.md.sha256.create(); - break; - case 'sha384WithRSAEncryption': - csr.md = forge.md.sha384.create(); - break; - case 'sha512WithRSAEncryption': - csr.md = forge.md.sha512.create(); - break; - case 'RSASSA-PSS': - csr.md = forge.md.sha256.create(); - break; - } - } - if(csr.md === null) { - var error = new Error('Could not compute certification request digest. ' + - 'Unknown signature OID.'); - error.signatureOid = csr.signatureOid; - throw error; - } + // create digest for OID signature type + csr.md = _createSignatureDigest({ + signatureOid: csr.signatureOid, + type: 'certification request' + }); // produce DER formatted CertificationRequestInfo and digest it var bytes = asn1.toDer(csr.certificationRequestInfo); @@ -1855,39 +1813,10 @@ pki.createCertificationRequest = function() { var md = csr.md; if(md === null) { - // check signature OID for supported signature types - if(csr.signatureOid in oids) { - // TODO: create DRY `OID to md` function - var oid = oids[csr.signatureOid]; - switch(oid) { - case 'sha1WithRSAEncryption': - case 'sha1WithRSASignature': - md = forge.md.sha1.create(); - break; - case 'md5WithRSAEncryption': - md = forge.md.md5.create(); - break; - case 'sha256WithRSAEncryption': - md = forge.md.sha256.create(); - break; - case 'sha384WithRSAEncryption': - md = forge.md.sha384.create(); - break; - case 'sha512WithRSAEncryption': - md = forge.md.sha512.create(); - break; - case 'RSASSA-PSS': - md = forge.md.sha256.create(); - break; - } - } - if(md === null) { - var error = new Error( - 'Could not compute certification request digest. ' + - 'Unknown signature OID.'); - error.signatureOid = csr.signatureOid; - throw error; - } + md = _createSignatureDigest({ + signatureOid: csr.signatureOid, + type: 'certification request' + }); // produce DER formatted CertificationRequestInfo and digest it var cri = csr.certificationRequestInfo || From 2f387588b9c2638086171ae68e2086b3554639b2 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 22:48:44 -0500 Subject: [PATCH 3/4] Add verification helper. - Reduce duplicate code. - Small cleanups. - Add note about deprecated OID alias. --- CHANGELOG.md | 5 +- lib/x509.js | 168 +++++++++++++++++++++------------------------------ 2 files changed, 72 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f6c22f5..32efeb70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,9 @@ Forge ChangeLog links](https://github.com/digitalbazaar/forge/issues/825). ### Changed -- [x509]: Reduce duplicate code with a helper function to create a signature - digest given an signature algorithm OID. +- [x509]: Reduce duplicate code. Add helper function to create a signature + digest given an signature algorithm OID. Add helper function to verify + signatures. ## 1.1.0 - 2022-01-06 diff --git a/lib/x509.js b/lib/x509.js index d7d2f892..25096c17 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -700,24 +700,19 @@ var _readSignatureParameters = function(oid, obj, fillDefaults) { var _createSignatureDigest = function(options) { switch(oids[options.signatureOid]) { case 'sha1WithRSAEncryption': + // depreacted alias case 'sha1WithRSASignature': return forge.md.sha1.create(); - break; case 'md5WithRSAEncryption': return forge.md.md5.create(); - break; case 'sha256WithRSAEncryption': return forge.md.sha256.create(); - break; case 'sha384WithRSAEncryption': return forge.md.sha384.create(); - break; case 'sha512WithRSAEncryption': return forge.md.sha512.create(); - break; case 'RSASSA-PSS': return forge.md.sha256.create(); - break; default: var error = new Error( 'Could not compute ' + options.type + ' digest. ' + @@ -727,6 +722,68 @@ var _createSignatureDigest = function(options) { } }; +/** + * Verify signature on certificate or CSR. + * + * @param options: + * certificate the certificate or CSR to verify. + * md the signature digest. + * signature the signature + * @return a created md instance. throws if unknown oid. + */ +var _verifySignature = function(options) { + var cert = options.certificate; + var scheme; + + switch(cert.signatureOid) { + case oids.sha1WithRSAEncryption: + // depreacted alias + case oids.sha1WithRSASignature: + /* use PKCS#1 v1.5 padding scheme */ + break; + case oids['RSASSA-PSS']: + var hash, mgf; + + /* initialize mgf */ + hash = oids[cert.signatureParameters.mgf.hash.algorithmOid]; + if(hash === undefined || forge.md[hash] === undefined) { + var error = new Error('Unsupported MGF hash function.'); + error.oid = cert.signatureParameters.mgf.hash.algorithmOid; + error.name = hash; + throw error; + } + + mgf = oids[cert.signatureParameters.mgf.algorithmOid]; + if(mgf === undefined || forge.mgf[mgf] === undefined) { + var error = new Error('Unsupported MGF function.'); + error.oid = cert.signatureParameters.mgf.algorithmOid; + error.name = mgf; + throw error; + } + + mgf = forge.mgf[mgf].create(forge.md[hash].create()); + + /* initialize hash function */ + hash = oids[cert.signatureParameters.hash.algorithmOid]; + if(hash === undefined || forge.md[hash] === undefined) { + var error = new Error('Unsupported RSASSA-PSS hash function.'); + error.oid = cert.signatureParameters.hash.algorithmOid; + error.name = hash; + throw error; + } + + scheme = forge.pss.create( + forge.md[hash].create(), mgf, cert.signatureParameters.saltLength + ); + break; + } + + // verify signature on cert using public key + return cert.publicKey.verify( + options.md.digest().getBytes(), options.signature, scheme + ); +}; + /** * Converts an X.509 certificate from PEM format. * @@ -1127,53 +1184,9 @@ pki.createCertificate = function() { } if(md !== null) { - var scheme; - - switch(child.signatureOid) { - case oids.sha1WithRSAEncryption: - case oids.sha1WithRSASignature: - scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ - break; - case oids['RSASSA-PSS']: - var hash, mgf; - - /* initialize mgf */ - hash = oids[child.signatureParameters.mgf.hash.algorithmOid]; - if(hash === undefined || forge.md[hash] === undefined) { - var error = new Error('Unsupported MGF hash function.'); - error.oid = child.signatureParameters.mgf.hash.algorithmOid; - error.name = hash; - throw error; - } - - mgf = oids[child.signatureParameters.mgf.algorithmOid]; - if(mgf === undefined || forge.mgf[mgf] === undefined) { - var error = new Error('Unsupported MGF function.'); - error.oid = child.signatureParameters.mgf.algorithmOid; - error.name = mgf; - throw error; - } - - mgf = forge.mgf[mgf].create(forge.md[hash].create()); - - /* initialize hash function */ - hash = oids[child.signatureParameters.hash.algorithmOid]; - if(hash === undefined || forge.md[hash] === undefined) { - throw { - message: 'Unsupported RSASSA-PSS hash function.', - oid: child.signatureParameters.hash.algorithmOid, - name: hash - }; - } - - scheme = forge.pss.create(forge.md[hash].create(), mgf, - child.signatureParameters.saltLength); - break; - } - - // verify signature on cert using public key - rval = cert.publicKey.verify( - md.digest().getBytes(), child.signature, scheme); + rval = _verifySignature({ + certificate: cert, md: md, signature: child.signature + }); } return rval; @@ -1826,52 +1839,9 @@ pki.createCertificationRequest = function() { } if(md !== null) { - var scheme; - - switch(csr.signatureOid) { - case oids.sha1WithRSAEncryption: - case oids.sha1WithRSASignature: - /* use PKCS#1 v1.5 padding scheme */ - break; - case oids['RSASSA-PSS']: - var hash, mgf; - - /* initialize mgf */ - hash = oids[csr.signatureParameters.mgf.hash.algorithmOid]; - if(hash === undefined || forge.md[hash] === undefined) { - var error = new Error('Unsupported MGF hash function.'); - error.oid = csr.signatureParameters.mgf.hash.algorithmOid; - error.name = hash; - throw error; - } - - mgf = oids[csr.signatureParameters.mgf.algorithmOid]; - if(mgf === undefined || forge.mgf[mgf] === undefined) { - var error = new Error('Unsupported MGF function.'); - error.oid = csr.signatureParameters.mgf.algorithmOid; - error.name = mgf; - throw error; - } - - mgf = forge.mgf[mgf].create(forge.md[hash].create()); - - /* initialize hash function */ - hash = oids[csr.signatureParameters.hash.algorithmOid]; - if(hash === undefined || forge.md[hash] === undefined) { - var error = new Error('Unsupported RSASSA-PSS hash function.'); - error.oid = csr.signatureParameters.hash.algorithmOid; - error.name = hash; - throw error; - } - - scheme = forge.pss.create(forge.md[hash].create(), mgf, - csr.signatureParameters.saltLength); - break; - } - - // verify signature on csr using its public key - rval = csr.publicKey.verify( - md.digest().getBytes(), csr.signature, scheme); + rval = _verifySignature({ + certificate: csr, md: md, signature: csr.signature + }); } return rval; From aaefafad8254d0d2fc6d40b294df8690fd10fbd5 Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Thu, 6 Jan 2022 22:49:48 -0500 Subject: [PATCH 4/4] Fix typos. --- lib/x509.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/x509.js b/lib/x509.js index 25096c17..2877810c 100644 --- a/lib/x509.js +++ b/lib/x509.js @@ -700,7 +700,7 @@ var _readSignatureParameters = function(oid, obj, fillDefaults) { var _createSignatureDigest = function(options) { switch(oids[options.signatureOid]) { case 'sha1WithRSAEncryption': - // depreacted alias + // deprecated alias case 'sha1WithRSASignature': return forge.md.sha1.create(); case 'md5WithRSAEncryption': @@ -737,7 +737,7 @@ var _verifySignature = function(options) { switch(cert.signatureOid) { case oids.sha1WithRSAEncryption: - // depreacted alias + // deprecated alias case oids.sha1WithRSASignature: /* use PKCS#1 v1.5 padding scheme */ break;