Initial commit
[yaffs-website] / node_modules / sshpk / lib / identity.js
1 // Copyright 2016 Joyent, Inc.
2
3 module.exports = Identity;
4
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');
14
15 /*JSSTYLED*/
16 var DNS_NAME_RE = /^([*]|[a-z0-9][a-z0-9\-]{0,62})(?:\.([*]|[a-z0-9][a-z0-9\-]{0,62}))*$/i;
17
18 var oids = {};
19 oids.cn = '2.5.4.3';
20 oids.o = '2.5.4.10';
21 oids.ou = '2.5.4.11';
22 oids.l = '2.5.4.7';
23 oids.s = '2.5.4.8';
24 oids.c = '2.5.4.6';
25 oids.sn = '2.5.4.4';
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';
29
30 var unoids = {};
31 Object.keys(oids).forEach(function (k) {
32         unoids[oids[k]] = k;
33 });
34
35 function Identity(opts) {
36         var self = this;
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) {
42                 if (c.name && !c.oid)
43                         c.oid = oids[c.name];
44                 if (c.oid && !c.name)
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);
49         });
50         if (this.componentLookup.cn && this.componentLookup.cn.length > 0) {
51                 this.cn = this.componentLookup.cn[0].value;
52         }
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)) {
59                         this.type = 'host';
60                         this.hostname = this.componentLookup.cn[0].value;
61
62                 } else if (this.componentLookup.dc &&
63                     this.components.length === this.componentLookup.dc.length) {
64                         this.type = 'host';
65                         this.hostname = this.componentLookup.dc.map(
66                             function (c) {
67                                 return (c.value);
68                         }).join('.');
69
70                 } else if (this.componentLookup.uid &&
71                     this.components.length ===
72                     this.componentLookup.uid.length) {
73                         this.type = 'user';
74                         this.uid = this.componentLookup.uid[0].value;
75
76                 } else if (this.componentLookup.cn &&
77                     this.componentLookup.cn.length === 1 &&
78                     this.componentLookup.cn[0].value.match(DNS_NAME_RE)) {
79                         this.type = 'host';
80                         this.hostname = this.componentLookup.cn[0].value;
81
82                 } else if (this.componentLookup.uid &&
83                     this.componentLookup.uid.length === 1) {
84                         this.type = 'user';
85                         this.uid = this.componentLookup.uid[0].value;
86
87                 } else if (this.componentLookup.mail &&
88                     this.componentLookup.mail.length === 1) {
89                         this.type = 'email';
90                         this.email = this.componentLookup.mail[0].value;
91
92                 } else if (this.componentLookup.cn &&
93                     this.componentLookup.cn.length === 1) {
94                         this.type = 'user';
95                         this.uid = this.componentLookup.cn[0].value;
96
97                 } else {
98                         this.type = 'unknown';
99                 }
100         } else {
101                 this.type = opts.type;
102                 if (this.type === 'host')
103                         this.hostname = opts.hostname;
104                 else if (this.type === 'user')
105                         this.uid = opts.uid;
106                 else if (this.type === 'email')
107                         this.email = opts.email;
108                 else
109                         throw (new Error('Unknown type ' + this.type));
110         }
111 }
112
113 Identity.prototype.toString = function () {
114         return (this.components.map(function (c) {
115                 return (c.name.toUpperCase() + '=' + c.value);
116         }).join(', '));
117 };
118
119 /*
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).
124  */
125 /* JSSTYLED */
126 var NOT_PRINTABLE = /[^a-zA-Z0-9 '(),+.\/:=?-]/;
127 /* JSSTYLED */
128 var NOT_IA5 = /[^\x00-\x7f]/;
129
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);
134                 der.startSequence();
135                 der.writeOID(c.oid);
136                 /*
137                  * If we fit in a PrintableString, use that. Otherwise use an
138                  * IA5String or UTF8String.
139                  */
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);
145                 } else {
146                         der.writeString(c.value, asn1.Ber.PrintableString);
147                 }
148                 der.endSequence();
149                 der.endSequence();
150         });
151         der.endSequence();
152 };
153
154 function globMatch(a, b) {
155         if (a === '**' || b === '**')
156                 return (true);
157         var aParts = a.split('.');
158         var bParts = b.split('.');
159         if (aParts.length !== bParts.length)
160                 return (false);
161         for (var i = 0; i < aParts.length; ++i) {
162                 if (aParts[i] === '*' || bParts[i] === '*')
163                         continue;
164                 if (aParts[i] !== bParts[i])
165                         return (false);
166         }
167         return (true);
168 }
169
170 Identity.prototype.equals = function (other) {
171         if (!Identity.isIdentity(other, [1, 0]))
172                 return (false);
173         if (other.components.length !== this.components.length)
174                 return (false);
175         for (var i = 0; i < this.components.length; ++i) {
176                 if (this.components[i].oid !== other.components[i].oid)
177                         return (false);
178                 if (!globMatch(this.components[i].value,
179                     other.components[i].value)) {
180                         return (false);
181                 }
182         }
183         return (true);
184 };
185
186 Identity.forHost = function (hostname) {
187         assert.string(hostname, 'hostname');
188         return (new Identity({
189                 type: 'host',
190                 hostname: hostname,
191                 components: [ { name: 'cn', value: hostname } ]
192         }));
193 };
194
195 Identity.forUser = function (uid) {
196         assert.string(uid, 'uid');
197         return (new Identity({
198                 type: 'user',
199                 uid: uid,
200                 components: [ { name: 'uid', value: uid } ]
201         }));
202 };
203
204 Identity.forEmail = function (email) {
205         assert.string(email, 'email');
206         return (new Identity({
207                 type: 'email',
208                 email: email,
209                 components: [ { name: 'mail', value: email } ]
210         }));
211 };
212
213 Identity.parseDN = function (dn) {
214         assert.string(dn, 'dn');
215         var parts = dn.split(',');
216         var cmps = parts.map(function (c) {
217                 c = c.trim();
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 });
222         });
223         return (new Identity({ components: cmps }));
224 };
225
226 Identity.parseAsn1 = function (der, top) {
227         var components = [];
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;
233                 der.readSequence();
234                 var oid = der.readOID();
235                 var type = der.peek();
236                 var value;
237                 switch (type) {
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);
243                         break;
244                 case asn1.Ber.Utf8String:
245                         value = der.readString(type, true);
246                         value = value.toString('utf8');
247                         break;
248                 case asn1.Ber.CharacterString:
249                 case asn1.Ber.BMPString:
250                         value = der.readString(type, true);
251                         value = value.toString('utf16le');
252                         break;
253                 default:
254                         throw (new Error('Unknown asn1 type ' + type));
255                 }
256                 components.push({ oid: oid, value: value });
257                 der._offset = after;
258         }
259         der._offset = end;
260         return (new Identity({
261                 components: components
262         }));
263 };
264
265 Identity.isIdentity = function (obj, ver) {
266         return (utils.isCompatible(obj, Identity, ver));
267 };
268
269 /*
270  * API versions for Identity:
271  * [1,0] -- initial ver
272  */
273 Identity.prototype._sshpkApiVersion = [1, 0];
274
275 Identity._oldVersionDetect = function (obj) {
276         return ([1, 0]);
277 };