Issues (18)

lib/x509/validation.jsrsasign.js (6 issues)

1 1
var jsrsasign = require('jsrsasign');
2 1
var ProtoBuf = require('../protobuf');
3 1
var PKIType = require('./pkitype');
4 1
var X509Certificates = ProtoBuf.X509Certificates;
5
6
/**
7
 * Checks if certificate serial numbers match.
8
 * @param certA
9
 * @param certB
10
 * @returns {boolean}
11
 */
12
function hasEqualSerialNumber(certA, certB) {
13 10
    return certA.getSerialNumberHex() === certB.getSerialNumberHex();
14
}
15
16
/**
17
 * Checks if certificate subjects match
18
 * @param certA
19
 * @param certB
20
 * @returns {boolean}
21
 */
22
function hasEqualSubject(certA, certB) {
23
    return certA.getSubjectHex() === certB.getSubjectHex();
24
}
25
26
/**
27
 * Checks if certificate public keys match
28
 * @param certA
29
 * @param certB
30
 * @returns {boolean}
31
 */
32
function hasEqualPublicKey(certA, certB) {
33
    return certA.getPublicKeyHex() === certB.getPublicKeyHex();
34
}
35
36
/**
37
 * Checks if certificates are equal
38
 * @param certA
39
 * @param certB
40
 * @returns {boolean}
41
 */
42
function checkCertsEqual(certA, certB) {
43 10
    return hasEqualSerialNumber(certA, certB) &&
44
        hasEqualSubject(certA, certB) &&
45
        hasEqualPublicKey(certA, certB);
46
}
47
48
/**
49
 * Checks if the certificate is self issued
50
 * @param certificate
51
 * @returns {boolean}
52
 */
53
function isSelfSigned(certificate) {
54 11
    return certificate.getSubjectString() === certificate.getIssuerString();
55
}
56
57
/**
58
 * Returns a callback that filters by the
59
 * provided subject key identifier.
60
 * @param subjectKeyId
61
 * @returns {Function}
62
 */
63
function makeFilterBySubjectKey(subjectKeyId) {
64 46
    return function(certificate) {
65 43
        try {
66 43
            return certificate.getExtSubjectKeyIdentifier() === subjectKeyId;
67
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
68
69
        return false;
70
    };
71
}
72
73
/**
74
 * Look in bundle for certificates for the
75
 * issuer of `target`
76
 * @param target
77
 * @param bundle
78
 * @returns {Array}
79
 */
80
function findIssuers(target, bundle) {
81 46
    try {
82 46
        var authorityKeyIdentifier = target.getExtAuthorityKeyIdentifier();
83
84 46
        if (typeof authorityKeyIdentifier.kid === "string") {
85 46
            var issuerName = target.getIssuerString();
86 46
            return bundle
87
                .filter(makeFilterBySubjectKey(authorityKeyIdentifier.kid))
88
                .filter(function(issuerCert) {
89 21
                    return issuerName === issuerCert.getSubjectString();
90
                });
91
        }
92
    } catch (e) { }
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
93
94
    return [];
95
}
96
97
/**
98
 * ChainPathBuilder builds an unverified chain
99
 * of certificates by linking issuer<>subject
100
 * @param trustStore
101
 * @constructor
102
 */
103
function ChainPathBuilder(trustStore) {
104 12
    if (typeof trustStore === "undefined") {
105
        trustStore = [];
106
    }
107
108 12
    this.trustStore = trustStore;
109
}
110
111
/**
112
 * Returns all paths to the target. First checks if target
113
 * was issued by a trustStore certificate, then attempts
114
 * against issuers (if any)
115
 * @param target
116
 * @param intermediates
117
 * @returns {Array}
118
 * @private
119
 */
120 1
ChainPathBuilder.prototype._pathsToTarget = function(target, intermediates) {
121 23
    var paths = [];
122 23
    findIssuers(target, this.trustStore).map(function(issuer) {
123 10
        if (checkCertsEqual(target, issuer)) {
124
            paths.push(target);
125
        } else {
126 10
            paths.push([].concat(issuer, target));
127
        }
128
    });
129
130 23
    if (Array.isArray(intermediates)) {
131 23
        var self = this;
132 23
        findIssuers(target, intermediates)
133
            .map(function(issuer) {
134 11
                if (isSelfSigned(issuer)) {
135
                    return;
136
                }
137
138 11
                var subpaths = self._pathsToTarget(issuer, intermediates);
139 11
                subpaths.map(function(path) {
140 10
                    paths.push([].concat(path, target));
141
                });
142
            });
143
    }
144
145 23
    return paths;
146
};
147
148
/**
149
 * Searches for paths which qualify `target`,
150
 * and returns the shortest.
151
 * @param target
152
 * @param intermediates
153
 * @returns {*}
154
 */
155 1
ChainPathBuilder.prototype.shortestPathToTarget = function(target, intermediates) {
156 12
    var paths = this._pathsToTarget(target, intermediates);
157 12
    if (paths.length === 0) {
158 2
        throw new Error("No certificate paths found");
159
    }
160
161 10
    paths.sort(function(a, b) {
162 2
        if (a < b) {
163
            return -1;
164
        }
165 2
        if (b > a) {
166
            return 1;
167
        }
168
169
        return 0;
170
    });
171
172 10
    return paths[0];
173
};
174
175
/**
176
 * Encapsulation for some validation state
177
 * @param pubKey
178
 * @param issuerName
179
 * @constructor
180
 */
181
function WorkingData(pubKey, issuerName) {
182 21
    this.pubKey = pubKey;
183 21
    this.issuerName = issuerName;
184 21
    this.getPublicKey = function() {
185 21
        return this.pubKey;
186
    };
187 21
    this.getIssuerName = function() {
188 15
        return this.issuerName;
189
    };
190
}
191
192
/**
193
 *
194
 * @param {jsrsasign.Certificate} rootCert
195
 * @param {number} chainLength
196
 * @param {object} opts
197
 * @constructor
198
 */
199
function ChainValidationState(rootCert, chainLength, opts) {
200 10
    this.workingData = new WorkingData(rootCert.getPublicKey(), rootCert.getIssuerString());
201 10
    this.index = 1;
202 10
    this.chainLength = chainLength;
203
204 10
    if (opts.currentTime) {
205 10
        this.currentTime = opts.currentTime;
206
    } else {
207
        this.currentTime = Date.now();
208
    }
209
210 10
    var self = this;
211 10
    this.updateState = function(cert) {
212 11
        self.workingData = new WorkingData(cert.getPublicKey(), cert.getSubjectString());
213
    };
214 10
    this.isFinal = function() {
215 15
        return this.chainLength === self.index;
216
    };
217
}
218
219
/**
220
 * Constructor for ChainPathValidator. Takes a list of
221
 * certificates, starting from the root, ending with
222
 * the entity certificate.
223
 *
224
 * @param {object} config
225
 * @param {jsrsasign.Certificate[]} certificates
226
 * @constructor
227
 */
228
function ChainPathValidator(config, certificates) {
229 10
    this._certificates = certificates;
230 10
    this._config = config;
231 10
    this._trustAnchor = certificates[0];
232
}
233
234
/**
235
 *
236
 * @param {ChainValidationState} state
237
 * @param {KJUR.asn1.x509.Certificate} cert
238
 */
239
function checkSignature(state, cert) {
240 21
    if (!cert.verifySignature(state.workingData.getPublicKey())) {
241
        throw new Error("Failed to verify signature");
242
    }
243
}
244
245
/**
246
 *
247
 * @param {ChainValidationState} state
248
 * @param {KJUR.asn1.x509.Certificate} cert
249
 */
250
function checkValidity(state, cert) {
251 21
    var notBefore = jsrsasign.zulutodate(cert.getNotBefore());
252 21
    var notAfter = jsrsasign.zulutodate(cert.getNotAfter());
253
254 21
    if (notBefore > Date.parse(state.currentTime) || notAfter < Date.parse(state.currentTime)) {
255 6
        throw new Error("Certificate is not valid");
256
    }
257
}
258
function checkRevocation(cert) {
0 ignored issues
show
The parameter cert is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
259
260
}
261
function checkIssuer(state, cert) {
262 15
    if (state.workingData.getIssuerName() !== cert.getIssuerString()) {
263
        throw new Error("Certificate issuer doesn't match");
264
    }
265
}
266
function processCertificate(state, cert) {
267 21
    checkSignature(state, cert);
268 21
    checkValidity(state, cert);
269
    // crl handling
270 15
    checkIssuer(state, cert);
271
}
272
273 1
ChainPathValidator.prototype.validate = function() {
274 10
    var state = new ChainValidationState(this._trustAnchor, this._certificates.length, this._config);
275 10
    for (var i = 0; i < this._certificates.length; i++) {
276 21
        state.index = i + 1;
277 21
        var cert = this._certificates[i];
278 21
        processCertificate(state, cert);
279 15
        if (!state.isFinal()) {
280 11
            state.updateState(cert);
281
        }
282
    }
283
};
284
285 1
var RequestValidator = function(opts) {
286 2
    if (typeof opts === "undefined") {
287
        opts = {};
288
    }
289 2
    opts.trustStore = opts.trustStore ? opts.trustStore : [];
290 2
    this.opts = opts;
291
};
292
293 1
RequestValidator.prototype.verifyX509Details = function(paymentRequest) {
294 2
    var x509 = X509Certificates.decode(paymentRequest.pkiData);
295
296
    function certFromDER(derBuf) {
297 4
        var cert = new jsrsasign.X509();
298 4
        cert.readCertHex(new Buffer(derBuf).toString('hex'));
0 ignored issues
show
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
299 4
        return cert;
300
    }
301
302 2
    var entityCert = certFromDER(x509.certificate[0]);
303 2
    var intermediates = x509.certificate.slice(1).map(certFromDER);
304 2
    var path = this.validateCertificateChain(entityCert, intermediates);
305
306 2
    if (!this.validateSignature(paymentRequest, entityCert)) {
307 1
        throw new Error("Invalid signature on request");
308
    }
309
310 1
    return path;
311
};
312
313 1
RequestValidator.prototype.validateCertificateChain = function(entityCert, intermediates) {
314 3
    var builder = new ChainPathBuilder(this.opts.trustStore);
315 3
    var path = builder.shortestPathToTarget(entityCert, intermediates);
316 3
    var validator = new ChainPathValidator(this.opts, path);
317 3
    validator.validate();
318 3
    return path;
319
};
320
321 1
RequestValidator.prototype.validateSignature = function(request, entityCert) {
322 4
    var sig = new jsrsasign.Signature({alg: getSignatureAlgorithm(entityCert, request.pkiType)});
323 4
    sig.init(entityCert.getPublicKey());
324 4
    sig.updateHex(getDataToSign(request).toString('hex'));
325 4
    return sig.verify(Buffer.from(request.signature).toString('hex'));
0 ignored issues
show
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
326
};
327
328
function getDataToSign(request) {
329 4
    var tmp = request.signature;
330 4
    request.signature = '';
331 4
    var encoded = new Buffer(ProtoBuf.PaymentRequest.encode(request).finish());
0 ignored issues
show
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
332 4
    request.signature = tmp;
333 4
    return encoded;
334
}
335
336
function getSignatureAlgorithm(entityCert, pkiType) {
337 11
    var publicKey = entityCert.getPublicKey();
338
339
    var keyType;
340 11
    if (publicKey.type === "ECDSA") {
341 4
        keyType = "ECDSA";
342 7
    } else if (publicKey.type === "RSA") {
343 6
        keyType = "RSA";
344
    } else {
345 1
        throw new Error("Unknown public key type");
346
    }
347
348
    var hashAlg;
349 10
    if (pkiType === PKIType.X509_SHA1) {
350 2
        hashAlg = "SHA1";
351 8
    } else if (pkiType === PKIType.X509_SHA256) {
352 6
        hashAlg = "SHA256";
353
    } else {
354 2
        throw new Error("Unknown PKI type or no signature algorithm specified.");
355
    }
356
357 8
    return hashAlg + "with" + keyType;
358
}
359
360 1
module.exports = {
361
    ChainPathBuilder: ChainPathBuilder,
362
    ChainPathValidator: ChainPathValidator,
363
    RequestValidator: RequestValidator,
364
    GetSignatureAlgorithm: getSignatureAlgorithm
365
};
366