UNPKG

@trezor/connect

Version:

High-level javascript interface for Trezor hardware wallet.

302 lines 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseCertificate = exports.parseName = exports.fixSignature = void 0; const derToAsn1 = (byteArray) => { let position = 0; function getTag() { let tag = byteArray[0] & 0x1f; position += 1; if (tag === 0x1f) { tag = 0; while (byteArray[position] >= 0x80) { tag = tag * 128 + byteArray[position] - 0x80; position += 1; } tag = tag * 128 + byteArray[position] - 0x80; position += 1; } return tag; } function getLength() { let length = 0; if (byteArray[position] < 0x80) { length = byteArray[position]; position += 1; } else { const numberOfDigits = byteArray[position] & 0x7f; position += 1; length = 0; for (let i = 0; i < numberOfDigits; i++) { length = length * 256 + byteArray[position]; position += 1; } } return length; } const cls = (byteArray[0] & 0xc0) / 64; const structured = (byteArray[0] & 0x20) === 0x20; const tag = getTag(); if (byteArray[position] === 0x80) { throw new Error('Unsupported length encoding'); } const length = getLength(); const byteLength = position + length; const contents = byteArray.subarray(position, byteLength); const raw = byteArray.subarray(0, byteLength); return { cls, tag, structured, byteLength, contents, raw, }; }; const derToAsn1List = (byteArray) => { const result = []; let nextPosition = 0; while (nextPosition < byteArray.length) { const nextPiece = derToAsn1(byteArray.subarray(nextPosition)); result.push(nextPiece); nextPosition += nextPiece.byteLength; } return result; }; const derBitStringValue = (byteArray) => ({ unusedBits: byteArray[0], bytes: byteArray.subarray(1), }); const fixSignature = (byteArray) => { const asn1 = derToAsn1(byteArray); if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) { throw new Error('Bad signature. Not a SEQUENCE.'); } const items = derToAsn1List(asn1.contents); let newLength = 0; const fixedItems = items.map(chunk => { const index = chunk.contents.findIndex(value => value > 0x00); const data = chunk.contents.subarray(index); const offset = data[0] >= 0x80 ? 1 : 0; const chunkLength = data.length + offset; const newChunk = new Uint8Array(chunkLength + 2); newChunk.set([chunk.raw[0], chunkLength]); if (offset > 0) { newChunk.set([0], 2); } newChunk.set(data, 2 + offset); newLength += newChunk.length; return newChunk; }); const signature = new Uint8Array(newLength + 2); signature.set([byteArray[0], newLength]); let signatureOffset = 2; fixedItems.forEach(item => { signature.set(item, signatureOffset); signatureOffset += item.length; }); return signature; }; exports.fixSignature = fixSignature; const parseSignatureValue = (asn1) => { if (asn1.cls !== 0 || asn1.tag !== 3 || asn1.structured) { throw new Error('Bad signature value. Not a BIT STRING.'); } const { unusedBits, bytes } = derBitStringValue(asn1.contents); return { asn1, bits: { unusedBits, bytes: (0, exports.fixSignature)(bytes) }, }; }; const derObjectIdentifierValue = (byteArray) => { let oid = `${Math.floor(byteArray[0] / 40)}.${byteArray[0] % 40}`; let position = 1; while (position < byteArray.length) { let nextInteger = 0; while (byteArray[position] >= 0x80) { nextInteger = nextInteger * 0x80 + (byteArray[position] & 0x7f); position += 1; } nextInteger = nextInteger * 0x80 + byteArray[position]; position += 1; oid += `.${nextInteger}`; } return oid; }; const parseAlgorithmIdentifier = (asn1) => { if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) { throw new Error('Bad algorithm identifier. Not a SEQUENCE.'); } const pieces = derToAsn1List(asn1.contents); if (pieces.length > 2) { throw new Error('Bad algorithm identifier. Contains too many child objects.'); } const encodedAlgorithm = pieces[0]; if (encodedAlgorithm.cls !== 0 || encodedAlgorithm.tag !== 6 || encodedAlgorithm.structured) { throw new Error('Bad algorithm identifier. Does not begin with an OBJECT IDENTIFIER.'); } const algorithm = derObjectIdentifierValue(encodedAlgorithm.contents); return { asn1, algorithm, parameters: pieces.length === 2 ? { asn1: pieces[1] } : null, }; }; const parseName = (asn1) => derToAsn1List(asn1.contents).map(item => { const attrSet = derToAsn1(item.contents); return parseAlgorithmIdentifier(attrSet); }); exports.parseName = parseName; const parseSubjectPublicKeyInfo = (asn1) => { if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) { throw new Error('Bad SPKI. Not a SEQUENCE.'); } const pieces = derToAsn1List(asn1.contents); if (pieces.length !== 2) { throw new Error('Bad SubjectPublicKeyInfo. Wrong number of child objects.'); } return { asn1, algorithm: parseAlgorithmIdentifier(pieces[0]), bits: derBitStringValue(pieces[1].contents), }; }; const parseUtcTime = (time) => { let offset = 4; let yearOffset = 0; if (time.tag === 23) { offset = 2; yearOffset = 2000; } const utc = Buffer.from(time.contents).toString(); const year = yearOffset + Number(utc.substring(0, offset)); const month = Number(utc.substring(offset, offset + 2)) - 1; const day = Number(utc.substring(offset + 2, offset + 4)); const hour = Number(utc.substring(offset + 4, offset + 6)); const minute = Number(utc.substring(offset + 6, offset + 8)); const date = new Date(); date.setUTCFullYear(year, month, day); date.setUTCHours(hour, minute, 0); return date; }; const parseValidity = (asn1) => { const [from, to] = derToAsn1List(asn1.contents); return { from: parseUtcTime(from), to: parseUtcTime(to), }; }; const parseExtensions = (data) => { const asn1 = derToAsn1(data.contents); if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) { throw new Error("This can't be a Extension. Wrong data type."); } const readBoolean = (value) => { if (!value) return false; if (value.cls !== 0 || value.tag !== 1 || value.contents.length !== 1 || value.structured) { throw new Error("This can't be a boolean. Wrong data type."); } if (![0x00, 0xff].includes(value.contents[0])) { throw new Error('Invalid boolean value.'); } return value.contents[0] === 0xff; }; const readBitString = (uint8Array) => { const buffer = Buffer.from(uint8Array); const tag = buffer.readUInt8(0); if (tag !== 3) { throw new Error("This can't be a bit string. Wrong data type."); } const length = buffer.readUInt8(1); const unusedBits = buffer.readUInt8(2); const bitStringBytes = buffer.subarray(3, 3 + length - 1); const bitString = bitStringBytes.reduce((str, byte) => str + byte.toString(2).padStart(8, '0'), ''); return bitString.slice(0, bitString.length - unusedBits); }; const readInteger = (value) => { if (!value) return undefined; if (value.cls !== 0 || value.tag !== 2 || value.contents.length !== 1 || value.structured) { throw new Error("This can't be a integer. Wrong data type."); } return Buffer.from(value.contents).readInt8(); }; const extensions = []; derToAsn1List(asn1.contents).forEach(item => { const [id, ...pieces] = derToAsn1List(item.contents); if (id.cls !== 0 || id.tag !== 6 || id.structured) { throw new Error('Bad extension. Does not begin with an OBJECT IDENTIFIER.'); } const algorithm = derObjectIdentifierValue(id.contents); const critical = pieces.length > 1 ? readBoolean(pieces[0]) : false; const extnValue = pieces.length > 1 ? pieces[1] : pieces[0]; if (extnValue.cls !== 0 || extnValue.tag !== 4 || extnValue.structured) { throw new Error("This can't be a octet string. Wrong data type."); } if (algorithm === '2.5.29.15') { extensions.push({ key: 'keyUsage', critical, keyCertSign: readBitString(extnValue.contents)[5], }); } else if (algorithm === '2.5.29.19') { const fields = derToAsn1List(derToAsn1(extnValue.contents).contents); const ca = fields.length > 0 && fields[0].tag === 1 ? fields[0] : undefined; const len = fields.length > 0 && fields[0].tag === 2 ? fields[0] : fields[1]; extensions.push({ key: 'basicConstraints', critical, cA: readBoolean(ca), pathLenConstraint: readInteger(len), }); } else { extensions.push({ key: algorithm, critical, ...item, }); } }); return extensions; }; const parseTBSCertificate = (asn1) => { if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) { throw new Error("This can't be a TBSCertificate. Wrong data type."); } const pieces = derToAsn1List(asn1.contents); if (pieces.length < 7) { throw new Error('Bad TBS Certificate. There are fewer than the seven required children.'); } return { asn1, version: pieces[0], serialNumber: pieces[1], signature: parseAlgorithmIdentifier(pieces[2]), issuer: pieces[3], validity: parseValidity(pieces[4]), subject: (0, exports.parseName)(pieces[5]), subjectPublicKeyInfo: parseSubjectPublicKeyInfo(pieces[6]), extensions: parseExtensions(pieces[7]), }; }; const parseCertificate = (byteArray) => { const asn1 = derToAsn1(byteArray); if (asn1.cls !== 0 || asn1.tag !== 16 || !asn1.structured) { throw new Error("This can't be an X.509 certificate. Wrong data type."); } const pieces = derToAsn1List(asn1.contents); if (pieces.length !== 3) { throw new Error('Certificate contains more than the three specified children.'); } return { asn1, tbsCertificate: parseTBSCertificate(pieces[0]), signatureAlgorithm: parseAlgorithmIdentifier(pieces[1]), signatureValue: parseSignatureValue(pieces[2]), }; }; exports.parseCertificate = parseCertificate; //# sourceMappingURL=x509certificate.js.map