identity.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. // Copyright 2017 Joyent, Inc.
  2. module.exports = Identity;
  3. var assert = require('assert-plus');
  4. var algs = require('./algs');
  5. var crypto = require('crypto');
  6. var Fingerprint = require('./fingerprint');
  7. var Signature = require('./signature');
  8. var errs = require('./errors');
  9. var util = require('util');
  10. var utils = require('./utils');
  11. var asn1 = require('asn1');
  12. /*JSSTYLED*/
  13. var DNS_NAME_RE = /^([*]|[a-z0-9][a-z0-9\-]{0,62})(?:\.([*]|[a-z0-9][a-z0-9\-]{0,62}))*$/i;
  14. var oids = {};
  15. oids.cn = '2.5.4.3';
  16. oids.o = '2.5.4.10';
  17. oids.ou = '2.5.4.11';
  18. oids.l = '2.5.4.7';
  19. oids.s = '2.5.4.8';
  20. oids.c = '2.5.4.6';
  21. oids.sn = '2.5.4.4';
  22. oids.dc = '0.9.2342.19200300.100.1.25';
  23. oids.uid = '0.9.2342.19200300.100.1.1';
  24. oids.mail = '0.9.2342.19200300.100.1.3';
  25. var unoids = {};
  26. Object.keys(oids).forEach(function (k) {
  27. unoids[oids[k]] = k;
  28. });
  29. function Identity(opts) {
  30. var self = this;
  31. assert.object(opts, 'options');
  32. assert.arrayOfObject(opts.components, 'options.components');
  33. this.components = opts.components;
  34. this.componentLookup = {};
  35. this.components.forEach(function (c) {
  36. if (c.name && !c.oid)
  37. c.oid = oids[c.name];
  38. if (c.oid && !c.name)
  39. c.name = unoids[c.oid];
  40. if (self.componentLookup[c.name] === undefined)
  41. self.componentLookup[c.name] = [];
  42. self.componentLookup[c.name].push(c);
  43. });
  44. if (this.componentLookup.cn && this.componentLookup.cn.length > 0) {
  45. this.cn = this.componentLookup.cn[0].value;
  46. }
  47. assert.optionalString(opts.type, 'options.type');
  48. if (opts.type === undefined) {
  49. if (this.components.length === 1 &&
  50. this.componentLookup.cn &&
  51. this.componentLookup.cn.length === 1 &&
  52. this.componentLookup.cn[0].value.match(DNS_NAME_RE)) {
  53. this.type = 'host';
  54. this.hostname = this.componentLookup.cn[0].value;
  55. } else if (this.componentLookup.dc &&
  56. this.components.length === this.componentLookup.dc.length) {
  57. this.type = 'host';
  58. this.hostname = this.componentLookup.dc.map(
  59. function (c) {
  60. return (c.value);
  61. }).join('.');
  62. } else if (this.componentLookup.uid &&
  63. this.components.length ===
  64. this.componentLookup.uid.length) {
  65. this.type = 'user';
  66. this.uid = this.componentLookup.uid[0].value;
  67. } else if (this.componentLookup.cn &&
  68. this.componentLookup.cn.length === 1 &&
  69. this.componentLookup.cn[0].value.match(DNS_NAME_RE)) {
  70. this.type = 'host';
  71. this.hostname = this.componentLookup.cn[0].value;
  72. } else if (this.componentLookup.uid &&
  73. this.componentLookup.uid.length === 1) {
  74. this.type = 'user';
  75. this.uid = this.componentLookup.uid[0].value;
  76. } else if (this.componentLookup.mail &&
  77. this.componentLookup.mail.length === 1) {
  78. this.type = 'email';
  79. this.email = this.componentLookup.mail[0].value;
  80. } else if (this.componentLookup.cn &&
  81. this.componentLookup.cn.length === 1) {
  82. this.type = 'user';
  83. this.uid = this.componentLookup.cn[0].value;
  84. } else {
  85. this.type = 'unknown';
  86. }
  87. } else {
  88. this.type = opts.type;
  89. if (this.type === 'host')
  90. this.hostname = opts.hostname;
  91. else if (this.type === 'user')
  92. this.uid = opts.uid;
  93. else if (this.type === 'email')
  94. this.email = opts.email;
  95. else
  96. throw (new Error('Unknown type ' + this.type));
  97. }
  98. }
  99. Identity.prototype.toString = function () {
  100. return (this.components.map(function (c) {
  101. return (c.name.toUpperCase() + '=' + c.value);
  102. }).join(', '));
  103. };
  104. /*
  105. * These are from X.680 -- PrintableString allowed chars are in section 37.4
  106. * table 8. Spec for IA5Strings is "1,6 + SPACE + DEL" where 1 refers to
  107. * ISO IR #001 (standard ASCII control characters) and 6 refers to ISO IR #006
  108. * (the basic ASCII character set).
  109. */
  110. /* JSSTYLED */
  111. var NOT_PRINTABLE = /[^a-zA-Z0-9 '(),+.\/:=?-]/;
  112. /* JSSTYLED */
  113. var NOT_IA5 = /[^\x00-\x7f]/;
  114. Identity.prototype.toAsn1 = function (der, tag) {
  115. der.startSequence(tag);
  116. this.components.forEach(function (c) {
  117. der.startSequence(asn1.Ber.Constructor | asn1.Ber.Set);
  118. der.startSequence();
  119. der.writeOID(c.oid);
  120. /*
  121. * If we fit in a PrintableString, use that. Otherwise use an
  122. * IA5String or UTF8String.
  123. */
  124. if (c.value.match(NOT_IA5)) {
  125. var v = new Buffer(c.value, 'utf8');
  126. der.writeBuffer(v, asn1.Ber.Utf8String);
  127. } else if (c.value.match(NOT_PRINTABLE)) {
  128. der.writeString(c.value, asn1.Ber.IA5String);
  129. } else {
  130. der.writeString(c.value, asn1.Ber.PrintableString);
  131. }
  132. der.endSequence();
  133. der.endSequence();
  134. });
  135. der.endSequence();
  136. };
  137. function globMatch(a, b) {
  138. if (a === '**' || b === '**')
  139. return (true);
  140. var aParts = a.split('.');
  141. var bParts = b.split('.');
  142. if (aParts.length !== bParts.length)
  143. return (false);
  144. for (var i = 0; i < aParts.length; ++i) {
  145. if (aParts[i] === '*' || bParts[i] === '*')
  146. continue;
  147. if (aParts[i] !== bParts[i])
  148. return (false);
  149. }
  150. return (true);
  151. }
  152. Identity.prototype.equals = function (other) {
  153. if (!Identity.isIdentity(other, [1, 0]))
  154. return (false);
  155. if (other.components.length !== this.components.length)
  156. return (false);
  157. for (var i = 0; i < this.components.length; ++i) {
  158. if (this.components[i].oid !== other.components[i].oid)
  159. return (false);
  160. if (!globMatch(this.components[i].value,
  161. other.components[i].value)) {
  162. return (false);
  163. }
  164. }
  165. return (true);
  166. };
  167. Identity.forHost = function (hostname) {
  168. assert.string(hostname, 'hostname');
  169. return (new Identity({
  170. type: 'host',
  171. hostname: hostname,
  172. components: [ { name: 'cn', value: hostname } ]
  173. }));
  174. };
  175. Identity.forUser = function (uid) {
  176. assert.string(uid, 'uid');
  177. return (new Identity({
  178. type: 'user',
  179. uid: uid,
  180. components: [ { name: 'uid', value: uid } ]
  181. }));
  182. };
  183. Identity.forEmail = function (email) {
  184. assert.string(email, 'email');
  185. return (new Identity({
  186. type: 'email',
  187. email: email,
  188. components: [ { name: 'mail', value: email } ]
  189. }));
  190. };
  191. Identity.parseDN = function (dn) {
  192. assert.string(dn, 'dn');
  193. var parts = dn.split(',');
  194. var cmps = parts.map(function (c) {
  195. c = c.trim();
  196. var eqPos = c.indexOf('=');
  197. var name = c.slice(0, eqPos).toLowerCase();
  198. var value = c.slice(eqPos + 1);
  199. return ({ name: name, value: value });
  200. });
  201. return (new Identity({ components: cmps }));
  202. };
  203. Identity.parseAsn1 = function (der, top) {
  204. var components = [];
  205. der.readSequence(top);
  206. var end = der.offset + der.length;
  207. while (der.offset < end) {
  208. der.readSequence(asn1.Ber.Constructor | asn1.Ber.Set);
  209. var after = der.offset + der.length;
  210. der.readSequence();
  211. var oid = der.readOID();
  212. var type = der.peek();
  213. var value;
  214. switch (type) {
  215. case asn1.Ber.PrintableString:
  216. case asn1.Ber.IA5String:
  217. case asn1.Ber.OctetString:
  218. case asn1.Ber.T61String:
  219. value = der.readString(type);
  220. break;
  221. case asn1.Ber.Utf8String:
  222. value = der.readString(type, true);
  223. value = value.toString('utf8');
  224. break;
  225. case asn1.Ber.CharacterString:
  226. case asn1.Ber.BMPString:
  227. value = der.readString(type, true);
  228. value = value.toString('utf16le');
  229. break;
  230. default:
  231. throw (new Error('Unknown asn1 type ' + type));
  232. }
  233. components.push({ oid: oid, value: value });
  234. der._offset = after;
  235. }
  236. der._offset = end;
  237. return (new Identity({
  238. components: components
  239. }));
  240. };
  241. Identity.isIdentity = function (obj, ver) {
  242. return (utils.isCompatible(obj, Identity, ver));
  243. };
  244. /*
  245. * API versions for Identity:
  246. * [1,0] -- initial ver
  247. */
  248. Identity.prototype._sshpkApiVersion = [1, 0];
  249. Identity._oldVersionDetect = function (obj) {
  250. return ([1, 0]);
  251. };