1 // Copyright 2016 Joyent, Inc.
3 module.exports = Identity;
5 var assert = require('assert-plus');
6 var algs = require('./algs');
7 var crypto = require('crypto');
8 var Fingerprint = require('./fingerprint');
9 var Signature = require('./signature');
10 var errs = require('./errors');
11 var util = require('util');
12 var utils = require('./utils');
13 var asn1 = require('asn1');
16 var DNS_NAME_RE = /^([*]|[a-z0-9][a-z0-9\-]{0,62})(?:\.([*]|[a-z0-9][a-z0-9\-]{0,62}))*$/i;
26 oids.dc = '0.9.2342.19200300.100.1.25';
27 oids.uid = '0.9.2342.19200300.100.1.1';
28 oids.mail = '0.9.2342.19200300.100.1.3';
31 Object.keys(oids).forEach(function (k) {
35 function Identity(opts) {
37 assert.object(opts, 'options');
38 assert.arrayOfObject(opts.components, 'options.components');
39 this.components = opts.components;
40 this.componentLookup = {};
41 this.components.forEach(function (c) {
45 c.name = unoids[c.oid];
46 if (self.componentLookup[c.name] === undefined)
47 self.componentLookup[c.name] = [];
48 self.componentLookup[c.name].push(c);
50 if (this.componentLookup.cn && this.componentLookup.cn.length > 0) {
51 this.cn = this.componentLookup.cn[0].value;
53 assert.optionalString(opts.type, 'options.type');
54 if (opts.type === undefined) {
55 if (this.components.length === 1 &&
56 this.componentLookup.cn &&
57 this.componentLookup.cn.length === 1 &&
58 this.componentLookup.cn[0].value.match(DNS_NAME_RE)) {
60 this.hostname = this.componentLookup.cn[0].value;
62 } else if (this.componentLookup.dc &&
63 this.components.length === this.componentLookup.dc.length) {
65 this.hostname = this.componentLookup.dc.map(
70 } else if (this.componentLookup.uid &&
71 this.components.length ===
72 this.componentLookup.uid.length) {
74 this.uid = this.componentLookup.uid[0].value;
76 } else if (this.componentLookup.cn &&
77 this.componentLookup.cn.length === 1 &&
78 this.componentLookup.cn[0].value.match(DNS_NAME_RE)) {
80 this.hostname = this.componentLookup.cn[0].value;
82 } else if (this.componentLookup.uid &&
83 this.componentLookup.uid.length === 1) {
85 this.uid = this.componentLookup.uid[0].value;
87 } else if (this.componentLookup.mail &&
88 this.componentLookup.mail.length === 1) {
90 this.email = this.componentLookup.mail[0].value;
92 } else if (this.componentLookup.cn &&
93 this.componentLookup.cn.length === 1) {
95 this.uid = this.componentLookup.cn[0].value;
98 this.type = 'unknown';
101 this.type = opts.type;
102 if (this.type === 'host')
103 this.hostname = opts.hostname;
104 else if (this.type === 'user')
106 else if (this.type === 'email')
107 this.email = opts.email;
109 throw (new Error('Unknown type ' + this.type));
113 Identity.prototype.toString = function () {
114 return (this.components.map(function (c) {
115 return (c.name.toUpperCase() + '=' + c.value);
120 * These are from X.680 -- PrintableString allowed chars are in section 37.4
121 * table 8. Spec for IA5Strings is "1,6 + SPACE + DEL" where 1 refers to
122 * ISO IR #001 (standard ASCII control characters) and 6 refers to ISO IR #006
123 * (the basic ASCII character set).
126 var NOT_PRINTABLE = /[^a-zA-Z0-9 '(),+.\/:=?-]/;
128 var NOT_IA5 = /[^\x00-\x7f]/;
130 Identity.prototype.toAsn1 = function (der, tag) {
131 der.startSequence(tag);
132 this.components.forEach(function (c) {
133 der.startSequence(asn1.Ber.Constructor | asn1.Ber.Set);
137 * If we fit in a PrintableString, use that. Otherwise use an
138 * IA5String or UTF8String.
140 if (c.value.match(NOT_IA5)) {
141 var v = new Buffer(c.value, 'utf8');
142 der.writeBuffer(v, asn1.Ber.Utf8String);
143 } else if (c.value.match(NOT_PRINTABLE)) {
144 der.writeString(c.value, asn1.Ber.IA5String);
146 der.writeString(c.value, asn1.Ber.PrintableString);
154 function globMatch(a, b) {
155 if (a === '**' || b === '**')
157 var aParts = a.split('.');
158 var bParts = b.split('.');
159 if (aParts.length !== bParts.length)
161 for (var i = 0; i < aParts.length; ++i) {
162 if (aParts[i] === '*' || bParts[i] === '*')
164 if (aParts[i] !== bParts[i])
170 Identity.prototype.equals = function (other) {
171 if (!Identity.isIdentity(other, [1, 0]))
173 if (other.components.length !== this.components.length)
175 for (var i = 0; i < this.components.length; ++i) {
176 if (this.components[i].oid !== other.components[i].oid)
178 if (!globMatch(this.components[i].value,
179 other.components[i].value)) {
186 Identity.forHost = function (hostname) {
187 assert.string(hostname, 'hostname');
188 return (new Identity({
191 components: [ { name: 'cn', value: hostname } ]
195 Identity.forUser = function (uid) {
196 assert.string(uid, 'uid');
197 return (new Identity({
200 components: [ { name: 'uid', value: uid } ]
204 Identity.forEmail = function (email) {
205 assert.string(email, 'email');
206 return (new Identity({
209 components: [ { name: 'mail', value: email } ]
213 Identity.parseDN = function (dn) {
214 assert.string(dn, 'dn');
215 var parts = dn.split(',');
216 var cmps = parts.map(function (c) {
218 var eqPos = c.indexOf('=');
219 var name = c.slice(0, eqPos).toLowerCase();
220 var value = c.slice(eqPos + 1);
221 return ({ name: name, value: value });
223 return (new Identity({ components: cmps }));
226 Identity.parseAsn1 = function (der, top) {
228 der.readSequence(top);
229 var end = der.offset + der.length;
230 while (der.offset < end) {
231 der.readSequence(asn1.Ber.Constructor | asn1.Ber.Set);
232 var after = der.offset + der.length;
234 var oid = der.readOID();
235 var type = der.peek();
238 case asn1.Ber.PrintableString:
239 case asn1.Ber.IA5String:
240 case asn1.Ber.OctetString:
241 case asn1.Ber.T61String:
242 value = der.readString(type);
244 case asn1.Ber.Utf8String:
245 value = der.readString(type, true);
246 value = value.toString('utf8');
248 case asn1.Ber.CharacterString:
249 case asn1.Ber.BMPString:
250 value = der.readString(type, true);
251 value = value.toString('utf16le');
254 throw (new Error('Unknown asn1 type ' + type));
256 components.push({ oid: oid, value: value });
260 return (new Identity({
261 components: components
265 Identity.isIdentity = function (obj, ver) {
266 return (utils.isCompatible(obj, Identity, ver));
270 * API versions for Identity:
271 * [1,0] -- initial ver
273 Identity.prototype._sshpkApiVersion = [1, 0];
275 Identity._oldVersionDetect = function (obj) {