identity.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. // Copyright 2016 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. Identity.prototype.toAsn1 = function (der, tag) {
  105. der.startSequence(tag);
  106. this.components.forEach(function (c) {
  107. der.startSequence(asn1.Ber.Constructor | asn1.Ber.Set);
  108. der.startSequence();
  109. der.writeOID(c.oid);
  110. der.writeString(c.value, asn1.Ber.PrintableString);
  111. der.endSequence();
  112. der.endSequence();
  113. });
  114. der.endSequence();
  115. };
  116. function globMatch(a, b) {
  117. if (a === '**' || b === '**')
  118. return (true);
  119. var aParts = a.split('.');
  120. var bParts = b.split('.');
  121. if (aParts.length !== bParts.length)
  122. return (false);
  123. for (var i = 0; i < aParts.length; ++i) {
  124. if (aParts[i] === '*' || bParts[i] === '*')
  125. continue;
  126. if (aParts[i] !== bParts[i])
  127. return (false);
  128. }
  129. return (true);
  130. }
  131. Identity.prototype.equals = function (other) {
  132. if (!Identity.isIdentity(other, [1, 0]))
  133. return (false);
  134. if (other.components.length !== this.components.length)
  135. return (false);
  136. for (var i = 0; i < this.components.length; ++i) {
  137. if (this.components[i].oid !== other.components[i].oid)
  138. return (false);
  139. if (!globMatch(this.components[i].value,
  140. other.components[i].value)) {
  141. return (false);
  142. }
  143. }
  144. return (true);
  145. };
  146. Identity.forHost = function (hostname) {
  147. assert.string(hostname, 'hostname');
  148. return (new Identity({
  149. type: 'host',
  150. hostname: hostname,
  151. components: [ { name: 'cn', value: hostname } ]
  152. }));
  153. };
  154. Identity.forUser = function (uid) {
  155. assert.string(uid, 'uid');
  156. return (new Identity({
  157. type: 'user',
  158. uid: uid,
  159. components: [ { name: 'uid', value: uid } ]
  160. }));
  161. };
  162. Identity.forEmail = function (email) {
  163. assert.string(email, 'email');
  164. return (new Identity({
  165. type: 'email',
  166. email: email,
  167. components: [ { name: 'mail', value: email } ]
  168. }));
  169. };
  170. Identity.parseDN = function (dn) {
  171. assert.string(dn, 'dn');
  172. var parts = dn.split(',');
  173. var cmps = parts.map(function (c) {
  174. c = c.trim();
  175. var eqPos = c.indexOf('=');
  176. var name = c.slice(0, eqPos).toLowerCase();
  177. var value = c.slice(eqPos + 1);
  178. return ({ name: name, value: value });
  179. });
  180. return (new Identity({ components: cmps }));
  181. };
  182. Identity.parseAsn1 = function (der, top) {
  183. var components = [];
  184. der.readSequence(top);
  185. var end = der.offset + der.length;
  186. while (der.offset < end) {
  187. der.readSequence(asn1.Ber.Constructor | asn1.Ber.Set);
  188. var after = der.offset + der.length;
  189. der.readSequence();
  190. var oid = der.readOID();
  191. var type = der.peek();
  192. var value;
  193. switch (type) {
  194. case asn1.Ber.PrintableString:
  195. case asn1.Ber.IA5String:
  196. case asn1.Ber.OctetString:
  197. case asn1.Ber.T61String:
  198. value = der.readString(type);
  199. break;
  200. case asn1.Ber.Utf8String:
  201. value = der.readString(type, true);
  202. value = value.toString('utf8');
  203. break;
  204. case asn1.Ber.CharacterString:
  205. case asn1.Ber.BMPString:
  206. value = der.readString(type, true);
  207. value = value.toString('utf16le');
  208. break;
  209. default:
  210. throw (new Error('Unknown asn1 type ' + type));
  211. }
  212. components.push({ oid: oid, value: value });
  213. der._offset = after;
  214. }
  215. der._offset = end;
  216. return (new Identity({
  217. components: components
  218. }));
  219. };
  220. Identity.isIdentity = function (obj, ver) {
  221. return (utils.isCompatible(obj, Identity, ver));
  222. };
  223. /*
  224. * API versions for Identity:
  225. * [1,0] -- initial ver
  226. */
  227. Identity.prototype._sshpkApiVersion = [1, 0];
  228. Identity._oldVersionDetect = function (obj) {
  229. return ([1, 0]);
  230. };