• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ObjectSetPrototypeOf,
5  SafeMap,
6  Symbol,
7} = primordials;
8
9const {
10  parseX509,
11  X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
12  X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
13  X509_CHECK_FLAG_NO_WILDCARDS,
14  X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS,
15  X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS,
16  X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS,
17} = internalBinding('crypto');
18
19const {
20  PublicKeyObject,
21  isKeyObject,
22} = require('internal/crypto/keys');
23
24const {
25  customInspectSymbol: kInspect,
26  kEmptyObject,
27} = require('internal/util');
28
29const {
30  validateBoolean,
31  validateObject,
32  validateString,
33} = require('internal/validators');
34
35const { inspect } = require('internal/util/inspect');
36
37const { Buffer } = require('buffer');
38
39const {
40  isArrayBufferView,
41} = require('internal/util/types');
42
43const {
44  codes: {
45    ERR_INVALID_ARG_TYPE,
46    ERR_INVALID_ARG_VALUE,
47  },
48} = require('internal/errors');
49
50const {
51  JSTransferable,
52  kClone,
53  kDeserialize,
54} = require('internal/worker/js_transferable');
55
56const {
57  kHandle,
58} = require('internal/crypto/util');
59
60let lazyTranslatePeerCertificate;
61
62const kInternalState = Symbol('kInternalState');
63
64function isX509Certificate(value) {
65  return value[kInternalState] !== undefined;
66}
67
68function getFlags(options = kEmptyObject) {
69  validateObject(options, 'options');
70  const {
71    subject = 'default',  // Can be 'default', 'always', or 'never'
72    wildcards = true,
73    partialWildcards = true,
74    multiLabelWildcards = false,
75    singleLabelSubdomains = false,
76  } = { ...options };
77  let flags = 0;
78  validateString(subject, 'options.subject');
79  validateBoolean(wildcards, 'options.wildcards');
80  validateBoolean(partialWildcards, 'options.partialWildcards');
81  validateBoolean(multiLabelWildcards, 'options.multiLabelWildcards');
82  validateBoolean(singleLabelSubdomains, 'options.singleLabelSubdomains');
83  switch (subject) {
84    case 'default': /* Matches OpenSSL's default, no flags. */ break;
85    case 'always': flags |= X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT; break;
86    case 'never': flags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT; break;
87    default:
88      throw new ERR_INVALID_ARG_VALUE('options.subject', subject);
89  }
90  if (!wildcards) flags |= X509_CHECK_FLAG_NO_WILDCARDS;
91  if (!partialWildcards) flags |= X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
92  if (multiLabelWildcards) flags |= X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS;
93  if (singleLabelSubdomains) flags |= X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS;
94  return flags;
95}
96
97class InternalX509Certificate extends JSTransferable {
98  [kInternalState] = new SafeMap();
99
100  constructor(handle) {
101    super();
102    this[kHandle] = handle;
103  }
104}
105
106class X509Certificate extends JSTransferable {
107  [kInternalState] = new SafeMap();
108
109  constructor(buffer) {
110    if (typeof buffer === 'string')
111      buffer = Buffer.from(buffer);
112    if (!isArrayBufferView(buffer)) {
113      throw new ERR_INVALID_ARG_TYPE(
114        'buffer',
115        ['string', 'Buffer', 'TypedArray', 'DataView'],
116        buffer);
117    }
118    super();
119    this[kHandle] = parseX509(buffer);
120  }
121
122  [kInspect](depth, options) {
123    if (depth < 0)
124      return this;
125
126    const opts = {
127      ...options,
128      depth: options.depth == null ? null : options.depth - 1,
129    };
130
131    return `X509Certificate ${inspect({
132      subject: this.subject,
133      subjectAltName: this.subjectAltName,
134      issuer: this.issuer,
135      infoAccess: this.infoAccess,
136      validFrom: this.validFrom,
137      validTo: this.validTo,
138      fingerprint: this.fingerprint,
139      fingerprint256: this.fingerprint256,
140      fingerprint512: this.fingerprint512,
141      keyUsage: this.keyUsage,
142      serialNumber: this.serialNumber,
143    }, opts)}`;
144  }
145
146  [kClone]() {
147    const handle = this[kHandle];
148    return {
149      data: { handle },
150      deserializeInfo: 'internal/crypto/x509:InternalX509Certificate',
151    };
152  }
153
154  [kDeserialize]({ handle }) {
155    this[kHandle] = handle;
156  }
157
158  get subject() {
159    let value = this[kInternalState].get('subject');
160    if (value === undefined) {
161      value = this[kHandle].subject();
162      this[kInternalState].set('subject', value);
163    }
164    return value;
165  }
166
167  get subjectAltName() {
168    let value = this[kInternalState].get('subjectAltName');
169    if (value === undefined) {
170      value = this[kHandle].subjectAltName();
171      this[kInternalState].set('subjectAltName', value);
172    }
173    return value;
174  }
175
176  get issuer() {
177    let value = this[kInternalState].get('issuer');
178    if (value === undefined) {
179      value = this[kHandle].issuer();
180      this[kInternalState].set('issuer', value);
181    }
182    return value;
183  }
184
185  get issuerCertificate() {
186    let value = this[kInternalState].get('issuerCertificate');
187    if (value === undefined) {
188      const cert = this[kHandle].getIssuerCert();
189      if (cert)
190        value = new InternalX509Certificate(this[kHandle].getIssuerCert());
191      this[kInternalState].set('issuerCertificate', value);
192    }
193    return value;
194  }
195
196  get infoAccess() {
197    let value = this[kInternalState].get('infoAccess');
198    if (value === undefined) {
199      value = this[kHandle].infoAccess();
200      this[kInternalState].set('infoAccess', value);
201    }
202    return value;
203  }
204
205  get validFrom() {
206    let value = this[kInternalState].get('validFrom');
207    if (value === undefined) {
208      value = this[kHandle].validFrom();
209      this[kInternalState].set('validFrom', value);
210    }
211    return value;
212  }
213
214  get validTo() {
215    let value = this[kInternalState].get('validTo');
216    if (value === undefined) {
217      value = this[kHandle].validTo();
218      this[kInternalState].set('validTo', value);
219    }
220    return value;
221  }
222
223  get fingerprint() {
224    let value = this[kInternalState].get('fingerprint');
225    if (value === undefined) {
226      value = this[kHandle].fingerprint();
227      this[kInternalState].set('fingerprint', value);
228    }
229    return value;
230  }
231
232  get fingerprint256() {
233    let value = this[kInternalState].get('fingerprint256');
234    if (value === undefined) {
235      value = this[kHandle].fingerprint256();
236      this[kInternalState].set('fingerprint256', value);
237    }
238    return value;
239  }
240
241  get fingerprint512() {
242    let value = this[kInternalState].get('fingerprint512');
243    if (value === undefined) {
244      value = this[kHandle].fingerprint512();
245      this[kInternalState].set('fingerprint512', value);
246    }
247    return value;
248  }
249
250  get keyUsage() {
251    let value = this[kInternalState].get('keyUsage');
252    if (value === undefined) {
253      value = this[kHandle].keyUsage();
254      this[kInternalState].set('keyUsage', value);
255    }
256    return value;
257  }
258
259  get serialNumber() {
260    let value = this[kInternalState].get('serialNumber');
261    if (value === undefined) {
262      value = this[kHandle].serialNumber();
263      this[kInternalState].set('serialNumber', value);
264    }
265    return value;
266  }
267
268  get raw() {
269    let value = this[kInternalState].get('raw');
270    if (value === undefined) {
271      value = this[kHandle].raw();
272      this[kInternalState].set('raw', value);
273    }
274    return value;
275  }
276
277  get publicKey() {
278    let value = this[kInternalState].get('publicKey');
279    if (value === undefined) {
280      value = new PublicKeyObject(this[kHandle].publicKey());
281      this[kInternalState].set('publicKey', value);
282    }
283    return value;
284  }
285
286  toString() {
287    let value = this[kInternalState].get('pem');
288    if (value === undefined) {
289      value = this[kHandle].pem();
290      this[kInternalState].set('pem', value);
291    }
292    return value;
293  }
294
295  // There's no standardized JSON encoding for X509 certs so we
296  // fallback to providing the PEM encoding as a string.
297  toJSON() { return this.toString(); }
298
299  get ca() {
300    let value = this[kInternalState].get('ca');
301    if (value === undefined) {
302      value = this[kHandle].checkCA();
303      this[kInternalState].set('ca', value);
304    }
305    return value;
306  }
307
308  checkHost(name, options) {
309    validateString(name, 'name');
310    return this[kHandle].checkHost(name, getFlags(options));
311  }
312
313  checkEmail(email, options) {
314    validateString(email, 'email');
315    return this[kHandle].checkEmail(email, getFlags(options));
316  }
317
318  checkIP(ip, options) {
319    validateString(ip, 'ip');
320    // The options argument is currently undocumented since none of the options
321    // have any effect on the behavior of this function. However, we still parse
322    // the options argument in case OpenSSL adds flags in the future that do
323    // affect the behavior of X509_check_ip. This ensures that no invalid values
324    // are passed as the second argument in the meantime.
325    return this[kHandle].checkIP(ip, getFlags(options));
326  }
327
328  checkIssued(otherCert) {
329    if (!isX509Certificate(otherCert))
330      throw new ERR_INVALID_ARG_TYPE('otherCert', 'X509Certificate', otherCert);
331    return this[kHandle].checkIssued(otherCert[kHandle]);
332  }
333
334  checkPrivateKey(pkey) {
335    if (!isKeyObject(pkey))
336      throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey);
337    if (pkey.type !== 'private')
338      throw new ERR_INVALID_ARG_VALUE('pkey', pkey);
339    return this[kHandle].checkPrivateKey(pkey[kHandle]);
340  }
341
342  verify(pkey) {
343    if (!isKeyObject(pkey))
344      throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey);
345    if (pkey.type !== 'public')
346      throw new ERR_INVALID_ARG_VALUE('pkey', pkey);
347    return this[kHandle].verify(pkey[kHandle]);
348  }
349
350  toLegacyObject() {
351    // TODO(tniessen): do not depend on translatePeerCertificate here, return
352    // the correct legacy representation from the binding
353    lazyTranslatePeerCertificate ??=
354      require('_tls_common').translatePeerCertificate;
355    return lazyTranslatePeerCertificate(this[kHandle].toLegacy());
356  }
357}
358
359InternalX509Certificate.prototype.constructor = X509Certificate;
360ObjectSetPrototypeOf(
361  InternalX509Certificate.prototype,
362  X509Certificate.prototype);
363
364module.exports = {
365  X509Certificate,
366  InternalX509Certificate,
367  isX509Certificate,
368};
369