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);
119 Identity.prototype.toAsn1 = function (der, tag) {
120 der.startSequence(tag);
121 this.components.forEach(function (c) {
122 der.startSequence(asn1.Ber.Constructor | asn1.Ber.Set);
125 der.writeString(c.value, asn1.Ber.PrintableString);
132 function globMatch(a, b) {
133 if (a === '**' || b === '**')
135 var aParts = a.split('.');
136 var bParts = b.split('.');
137 if (aParts.length !== bParts.length)
139 for (var i = 0; i < aParts.length; ++i) {
140 if (aParts[i] === '*' || bParts[i] === '*')
142 if (aParts[i] !== bParts[i])
148 Identity.prototype.equals = function (other) {
149 if (!Identity.isIdentity(other, [1, 0]))
151 if (other.components.length !== this.components.length)
153 for (var i = 0; i < this.components.length; ++i) {
154 if (this.components[i].oid !== other.components[i].oid)
156 if (!globMatch(this.components[i].value,
157 other.components[i].value)) {
164 Identity.forHost = function (hostname) {
165 assert.string(hostname, 'hostname');
166 return (new Identity({
169 components: [ { name: 'cn', value: hostname } ]
173 Identity.forUser = function (uid) {
174 assert.string(uid, 'uid');
175 return (new Identity({
178 components: [ { name: 'uid', value: uid } ]
182 Identity.forEmail = function (email) {
183 assert.string(email, 'email');
184 return (new Identity({
187 components: [ { name: 'mail', value: email } ]
191 Identity.parseDN = function (dn) {
192 assert.string(dn, 'dn');
193 var parts = dn.split(',');
194 var cmps = parts.map(function (c) {
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 });
201 return (new Identity({ components: cmps }));
204 Identity.parseAsn1 = function (der, top) {
206 der.readSequence(top);
207 var end = der.offset + der.length;
208 while (der.offset < end) {
209 der.readSequence(asn1.Ber.Constructor | asn1.Ber.Set);
210 var after = der.offset + der.length;
212 var oid = der.readOID();
213 var type = der.peek();
216 case asn1.Ber.PrintableString:
217 case asn1.Ber.IA5String:
218 case asn1.Ber.OctetString:
219 case asn1.Ber.T61String:
220 value = der.readString(type);
222 case asn1.Ber.Utf8String:
223 value = der.readString(type, true);
224 value = value.toString('utf8');
226 case asn1.Ber.CharacterString:
227 case asn1.Ber.BMPString:
228 value = der.readString(type, true);
229 value = value.toString('utf16le');
232 throw (new Error('Unknown asn1 type ' + type));
234 components.push({ oid: oid, value: value });
238 return (new Identity({
239 components: components
243 Identity.isIdentity = function (obj, ver) {
244 return (utils.isCompatible(obj, Identity, ver));
248 * API versions for Identity:
249 * [1,0] -- initial ver
251 Identity.prototype._sshpkApiVersion = [1, 0];
253 Identity._oldVersionDetect = function (obj) {