1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23
24const tls = require('tls');
25
26const {
27  ArrayPrototypePush,
28  JSONParse,
29  ObjectCreate,
30  RegExpPrototypeSymbolReplace,
31} = primordials;
32
33const {
34  codes: {
35    ERR_TLS_INVALID_PROTOCOL_VERSION,
36    ERR_TLS_PROTOCOL_VERSION_CONFLICT,
37  },
38} = require('internal/errors');
39
40const {
41  crypto: {
42    SSL_OP_CIPHER_SERVER_PREFERENCE,
43    TLS1_VERSION,
44    TLS1_1_VERSION,
45    TLS1_2_VERSION,
46    TLS1_3_VERSION,
47  },
48} = internalBinding('constants');
49
50const {
51  kEmptyObject,
52} = require('internal/util');
53
54const {
55  validateInteger,
56} = require('internal/validators');
57
58const {
59  configSecureContext,
60} = require('internal/tls/secure-context');
61
62function toV(which, v, def) {
63  if (v == null) v = def;
64  if (v === 'TLSv1') return TLS1_VERSION;
65  if (v === 'TLSv1.1') return TLS1_1_VERSION;
66  if (v === 'TLSv1.2') return TLS1_2_VERSION;
67  if (v === 'TLSv1.3') return TLS1_3_VERSION;
68  throw new ERR_TLS_INVALID_PROTOCOL_VERSION(v, which);
69}
70
71const {
72  SecureContext: NativeSecureContext,
73} = internalBinding('crypto');
74
75function SecureContext(secureProtocol, secureOptions, minVersion, maxVersion) {
76  if (!(this instanceof SecureContext)) {
77    return new SecureContext(secureProtocol, secureOptions, minVersion,
78                             maxVersion);
79  }
80
81  if (secureProtocol) {
82    if (minVersion != null)
83      throw new ERR_TLS_PROTOCOL_VERSION_CONFLICT(minVersion, secureProtocol);
84    if (maxVersion != null)
85      throw new ERR_TLS_PROTOCOL_VERSION_CONFLICT(maxVersion, secureProtocol);
86  }
87
88  this.context = new NativeSecureContext();
89  this.context.init(secureProtocol,
90                    toV('minimum', minVersion, tls.DEFAULT_MIN_VERSION),
91                    toV('maximum', maxVersion, tls.DEFAULT_MAX_VERSION));
92
93  if (secureOptions) {
94    validateInteger(secureOptions, 'secureOptions');
95    this.context.setOptions(secureOptions);
96  }
97}
98
99function createSecureContext(options) {
100  if (!options) options = kEmptyObject;
101
102  const {
103    honorCipherOrder,
104    minVersion,
105    maxVersion,
106    secureProtocol,
107  } = options;
108
109  let { secureOptions } = options;
110
111  if (honorCipherOrder)
112    secureOptions |= SSL_OP_CIPHER_SERVER_PREFERENCE;
113
114  const c = new SecureContext(secureProtocol, secureOptions,
115                              minVersion, maxVersion);
116
117  configSecureContext(c.context, options);
118
119  return c;
120}
121
122// Translate some fields from the handle's C-friendly format into more idiomatic
123// javascript object representations before passing them back to the user.  Can
124// be used on any cert object, but changing the name would be semver-major.
125function translatePeerCertificate(c) {
126  if (!c)
127    return null;
128
129  if (c.issuerCertificate != null && c.issuerCertificate !== c) {
130    c.issuerCertificate = translatePeerCertificate(c.issuerCertificate);
131  }
132  if (c.infoAccess != null) {
133    const info = c.infoAccess;
134    c.infoAccess = ObjectCreate(null);
135
136    // XXX: More key validation?
137    RegExpPrototypeSymbolReplace(/([^\n:]*):([^\n]*)(?:\n|$)/g, info,
138                                 (all, key, val) => {
139                                   if (val.charCodeAt(0) === 0x22) {
140                                     // The translatePeerCertificate function is only
141                                     // used on internally created legacy certificate
142                                     // objects, and any value that contains a quote
143                                     // will always be a valid JSON string literal,
144                                     // so this should never throw.
145                                     val = JSONParse(val);
146                                   }
147                                   if (key in c.infoAccess)
148                                     ArrayPrototypePush(c.infoAccess[key], val);
149                                   else
150                                     c.infoAccess[key] = [val];
151                                 });
152  }
153  return c;
154}
155
156module.exports = {
157  SecureContext,
158  createSecureContext,
159  translatePeerCertificate,
160};
161