trouble-in-terror-town/node_modules/sshpk/lib/identity.js
Mikolaj 2bbacbea09 did some more work on networking and removed EOS in favor of LRM
did some more work on networking and removed EOS in favor of Light Reflective Mirror
2022-05-31 15:04:31 +02:00

373 lines
9.8 KiB
JavaScript

// Copyright 2017 Joyent, Inc.
module.exports = Identity;
var assert = require('assert-plus');
var algs = require('./algs');
var crypto = require('crypto');
var Fingerprint = require('./fingerprint');
var Signature = require('./signature');
var errs = require('./errors');
var util = require('util');
var utils = require('./utils');
var asn1 = require('asn1');
var Buffer = require('safer-buffer').Buffer;
/*JSSTYLED*/
var DNS_NAME_RE = /^([*]|[a-z0-9][a-z0-9\-]{0,62})(?:\.([*]|[a-z0-9][a-z0-9\-]{0,62}))*$/i;
var oids = {};
oids.cn = '2.5.4.3';
oids.o = '2.5.4.10';
oids.ou = '2.5.4.11';
oids.l = '2.5.4.7';
oids.s = '2.5.4.8';
oids.c = '2.5.4.6';
oids.sn = '2.5.4.4';
oids.postalCode = '2.5.4.17';
oids.serialNumber = '2.5.4.5';
oids.street = '2.5.4.9';
oids.x500UniqueIdentifier = '2.5.4.45';
oids.role = '2.5.4.72';
oids.telephoneNumber = '2.5.4.20';
oids.description = '2.5.4.13';
oids.dc = '0.9.2342.19200300.100.1.25';
oids.uid = '0.9.2342.19200300.100.1.1';
oids.mail = '0.9.2342.19200300.100.1.3';
oids.title = '2.5.4.12';
oids.gn = '2.5.4.42';
oids.initials = '2.5.4.43';
oids.pseudonym = '2.5.4.65';
oids.emailAddress = '1.2.840.113549.1.9.1';
var unoids = {};
Object.keys(oids).forEach(function (k) {
unoids[oids[k]] = k;
});
function Identity(opts) {
var self = this;
assert.object(opts, 'options');
assert.arrayOfObject(opts.components, 'options.components');
this.components = opts.components;
this.componentLookup = {};
this.components.forEach(function (c) {
if (c.name && !c.oid)
c.oid = oids[c.name];
if (c.oid && !c.name)
c.name = unoids[c.oid];
if (self.componentLookup[c.name] === undefined)
self.componentLookup[c.name] = [];
self.componentLookup[c.name].push(c);
});
if (this.componentLookup.cn && this.componentLookup.cn.length > 0) {
this.cn = this.componentLookup.cn[0].value;
}
assert.optionalString(opts.type, 'options.type');
if (opts.type === undefined) {
if (this.components.length === 1 &&
this.componentLookup.cn &&
this.componentLookup.cn.length === 1 &&
this.componentLookup.cn[0].value.match(DNS_NAME_RE)) {
this.type = 'host';
this.hostname = this.componentLookup.cn[0].value;
} else if (this.componentLookup.dc &&
this.components.length === this.componentLookup.dc.length) {
this.type = 'host';
this.hostname = this.componentLookup.dc.map(
function (c) {
return (c.value);
}).join('.');
} else if (this.componentLookup.uid &&
this.components.length ===
this.componentLookup.uid.length) {
this.type = 'user';
this.uid = this.componentLookup.uid[0].value;
} else if (this.componentLookup.cn &&
this.componentLookup.cn.length === 1 &&
this.componentLookup.cn[0].value.match(DNS_NAME_RE)) {
this.type = 'host';
this.hostname = this.componentLookup.cn[0].value;
} else if (this.componentLookup.uid &&
this.componentLookup.uid.length === 1) {
this.type = 'user';
this.uid = this.componentLookup.uid[0].value;
} else if (this.componentLookup.mail &&
this.componentLookup.mail.length === 1) {
this.type = 'email';
this.email = this.componentLookup.mail[0].value;
} else if (this.componentLookup.cn &&
this.componentLookup.cn.length === 1) {
this.type = 'user';
this.uid = this.componentLookup.cn[0].value;
} else {
this.type = 'unknown';
}
} else {
this.type = opts.type;
if (this.type === 'host')
this.hostname = opts.hostname;
else if (this.type === 'user')
this.uid = opts.uid;
else if (this.type === 'email')
this.email = opts.email;
else
throw (new Error('Unknown type ' + this.type));
}
}
Identity.prototype.toString = function () {
return (this.components.map(function (c) {
var n = c.name.toUpperCase();
/*JSSTYLED*/
n = n.replace(/=/g, '\\=');
var v = c.value;
/*JSSTYLED*/
v = v.replace(/,/g, '\\,');
return (n + '=' + v);
}).join(', '));
};
Identity.prototype.get = function (name, asArray) {
assert.string(name, 'name');
var arr = this.componentLookup[name];
if (arr === undefined || arr.length === 0)
return (undefined);
if (!asArray && arr.length > 1)
throw (new Error('Multiple values for attribute ' + name));
if (!asArray)
return (arr[0].value);
return (arr.map(function (c) {
return (c.value);
}));
};
Identity.prototype.toArray = function (idx) {
return (this.components.map(function (c) {
return ({
name: c.name,
value: c.value
});
}));
};
/*
* These are from X.680 -- PrintableString allowed chars are in section 37.4
* table 8. Spec for IA5Strings is "1,6 + SPACE + DEL" where 1 refers to
* ISO IR #001 (standard ASCII control characters) and 6 refers to ISO IR #006
* (the basic ASCII character set).
*/
/* JSSTYLED */
var NOT_PRINTABLE = /[^a-zA-Z0-9 '(),+.\/:=?-]/;
/* JSSTYLED */
var NOT_IA5 = /[^\x00-\x7f]/;
Identity.prototype.toAsn1 = function (der, tag) {
der.startSequence(tag);
this.components.forEach(function (c) {
der.startSequence(asn1.Ber.Constructor | asn1.Ber.Set);
der.startSequence();
der.writeOID(c.oid);
/*
* If we fit in a PrintableString, use that. Otherwise use an
* IA5String or UTF8String.
*
* If this identity was parsed from a DN, use the ASN.1 types
* from the original representation (otherwise this might not
* be a full match for the original in some validators).
*/
if (c.asn1type === asn1.Ber.Utf8String ||
c.value.match(NOT_IA5)) {
var v = Buffer.from(c.value, 'utf8');
der.writeBuffer(v, asn1.Ber.Utf8String);
} else if (c.asn1type === asn1.Ber.IA5String ||
c.value.match(NOT_PRINTABLE)) {
der.writeString(c.value, asn1.Ber.IA5String);
} else {
var type = asn1.Ber.PrintableString;
if (c.asn1type !== undefined)
type = c.asn1type;
der.writeString(c.value, type);
}
der.endSequence();
der.endSequence();
});
der.endSequence();
};
function globMatch(a, b) {
if (a === '**' || b === '**')
return (true);
var aParts = a.split('.');
var bParts = b.split('.');
if (aParts.length !== bParts.length)
return (false);
for (var i = 0; i < aParts.length; ++i) {
if (aParts[i] === '*' || bParts[i] === '*')
continue;
if (aParts[i] !== bParts[i])
return (false);
}
return (true);
}
Identity.prototype.equals = function (other) {
if (!Identity.isIdentity(other, [1, 0]))
return (false);
if (other.components.length !== this.components.length)
return (false);
for (var i = 0; i < this.components.length; ++i) {
if (this.components[i].oid !== other.components[i].oid)
return (false);
if (!globMatch(this.components[i].value,
other.components[i].value)) {
return (false);
}
}
return (true);
};
Identity.forHost = function (hostname) {
assert.string(hostname, 'hostname');
return (new Identity({
type: 'host',
hostname: hostname,
components: [ { name: 'cn', value: hostname } ]
}));
};
Identity.forUser = function (uid) {
assert.string(uid, 'uid');
return (new Identity({
type: 'user',
uid: uid,
components: [ { name: 'uid', value: uid } ]
}));
};
Identity.forEmail = function (email) {
assert.string(email, 'email');
return (new Identity({
type: 'email',
email: email,
components: [ { name: 'mail', value: email } ]
}));
};
Identity.parseDN = function (dn) {
assert.string(dn, 'dn');
var parts = [''];
var idx = 0;
var rem = dn;
while (rem.length > 0) {
var m;
/*JSSTYLED*/
if ((m = /^,/.exec(rem)) !== null) {
parts[++idx] = '';
rem = rem.slice(m[0].length);
/*JSSTYLED*/
} else if ((m = /^\\,/.exec(rem)) !== null) {
parts[idx] += ',';
rem = rem.slice(m[0].length);
/*JSSTYLED*/
} else if ((m = /^\\./.exec(rem)) !== null) {
parts[idx] += m[0];
rem = rem.slice(m[0].length);
/*JSSTYLED*/
} else if ((m = /^[^\\,]+/.exec(rem)) !== null) {
parts[idx] += m[0];
rem = rem.slice(m[0].length);
} else {
throw (new Error('Failed to parse DN'));
}
}
var cmps = parts.map(function (c) {
c = c.trim();
var eqPos = c.indexOf('=');
while (eqPos > 0 && c.charAt(eqPos - 1) === '\\')
eqPos = c.indexOf('=', eqPos + 1);
if (eqPos === -1) {
throw (new Error('Failed to parse DN'));
}
/*JSSTYLED*/
var name = c.slice(0, eqPos).toLowerCase().replace(/\\=/g, '=');
var value = c.slice(eqPos + 1);
return ({ name: name, value: value });
});
return (new Identity({ components: cmps }));
};
Identity.fromArray = function (components) {
assert.arrayOfObject(components, 'components');
components.forEach(function (cmp) {
assert.object(cmp, 'component');
assert.string(cmp.name, 'component.name');
if (!Buffer.isBuffer(cmp.value) &&
!(typeof (cmp.value) === 'string')) {
throw (new Error('Invalid component value'));
}
});
return (new Identity({ components: components }));
};
Identity.parseAsn1 = function (der, top) {
var components = [];
der.readSequence(top);
var end = der.offset + der.length;
while (der.offset < end) {
der.readSequence(asn1.Ber.Constructor | asn1.Ber.Set);
var after = der.offset + der.length;
der.readSequence();
var oid = der.readOID();
var type = der.peek();
var value;
switch (type) {
case asn1.Ber.PrintableString:
case asn1.Ber.IA5String:
case asn1.Ber.OctetString:
case asn1.Ber.T61String:
value = der.readString(type);
break;
case asn1.Ber.Utf8String:
value = der.readString(type, true);
value = value.toString('utf8');
break;
case asn1.Ber.CharacterString:
case asn1.Ber.BMPString:
value = der.readString(type, true);
value = value.toString('utf16le');
break;
default:
throw (new Error('Unknown asn1 type ' + type));
}
components.push({ oid: oid, asn1type: type, value: value });
der._offset = after;
}
der._offset = end;
return (new Identity({
components: components
}));
};
Identity.isIdentity = function (obj, ver) {
return (utils.isCompatible(obj, Identity, ver));
};
/*
* API versions for Identity:
* [1,0] -- initial ver
*/
Identity.prototype._sshpkApiVersion = [1, 0];
Identity._oldVersionDetect = function (obj) {
return ([1, 0]);
};