UNPKG

@microsoft/dev-tunnels-ssh

Version:
298 lines 10.8 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // Object.defineProperty(exports, "__esModule", { value: true }); exports.DerWriter = exports.DerReader = void 0; const buffer_1 = require("buffer"); const bigInt_1 = require("./bigInt"); /** * Reads data in DER (Distinguished Encoding Rules) format. * * Enables importing and exporting key files, which are commonly DER-encoded. */ class DerReader { constructor(buffer, dataType = 32 /* DerType.Constructed */ | 16 /* DerType.Sequence */) { this.buffer = buffer; this.position = 0; this.readType(dataType); const length = this.readLength(); if (length > this.buffer.length - this.position) { throw new Error('Read out of bounds.'); } this.buffer = this.buffer.slice(0, this.position + length); } get available() { return this.buffer.length - this.position; } readNull() { this.readType(5 /* DerType.Null */); if (this.readByte() !== 0) { throw new Error('Expected a 0 after Null type.'); } } readInteger() { this.readType(2 /* DerType.Integer */); const length = this.readLength(); const bytes = this.readBytes(length); const result = new bigInt_1.BigInt(bytes); return result; } readOctetString() { this.readType(4 /* DerType.OctetString */); const length = this.readLength(); const result = this.readBytes(length); return result; } readBitString() { this.readType(3 /* DerType.BitString */); const length = this.readLength(); const padding = this.readByte(); if (padding !== 0) { throw new Error('Padded bit strings are not supported.'); } const result = this.readBytes(length - 1); return result; } readObjectIdentifier(expected) { this.readType(6 /* DerType.ObjectIdentifier */); const length = this.readLength(); const end = this.position + length; const values = []; const first = this.readByte(); values.push(Math.trunc(first / 40)); values.push(first % 40); let next = 0; while (this.position < end) { const b = this.readByte(); if ((b & 0x80) !== 0) { next = next * 128 + (b & 0x7f); } else { next = next * 128 + b; values.push(next); next = 0; } } if (next !== 0) { throw new Error('Invalid OID format.'); } const result = values.join('.'); if (expected && result !== expected) { throw new Error(`Expected OID ${expected}, found: ${result}`); } return result; } readSequence() { const start = this.position; this.readType(32 /* DerType.Constructed */ | 16 /* DerType.Sequence */); const length = this.readLength(); this.position += length; return new DerReader(this.buffer.slice(start, this.position)); } tryReadTagged(tagId) { if (this.position >= this.buffer.length) { return null; } const type = this.buffer[this.position]; if ((type & 160 /* DerType.Tagged */) === 0 || (type & ~160 /* DerType.Tagged */) !== tagId) { return null; } const start = this.position; this.position++; const length = this.readLength(); this.position += length; const taggedData = new DerReader(this.buffer.slice(start, this.position), type); return taggedData; } /** Reads the type of the next value in the sequence WITHOUT advancing the reader position. */ peek() { if (this.position >= this.buffer.length) { throw new Error('Read out of bounds.'); } return this.buffer[this.position]; } readLength() { let length = this.readByte(); if (length === 0x80) { throw new Error('Indefinite-length encoding is not supported.'); } if (length > 127) { const size = length & 0x7f; if (size > 4) { throw new Error(`DER length size is ${size} and cannot be more than 4 bytes.`); } length = 0; for (let i = 0; i < size; i++) { const next = this.readByte(); length = (length << 8) + next; } if (length < 0) { throw new Error('Corrupted data - negative length found'); } } return length; } readByte() { if (this.position >= this.buffer.length) { throw new Error('Read out of bounds.'); } return this.buffer[this.position++]; } readBytes(length) { if (this.position + length > this.buffer.length) { throw new Error('Read out of bounds.'); } const result = this.buffer.slice(this.position, this.position + length); this.position += length; return result; } readType(expectedType) { const type = this.readByte(); if (type !== expectedType) { throw new Error(`Expected ${expectedType} data type, found : ${type}`); } } } exports.DerReader = DerReader; /** * Writes data in DER (Distinguished Encoding Rules) format. * * Enables importing and exporting key files, which are commonly DER-encoded. */ class DerWriter { constructor(buffer, dataType = 32 /* DerType.Constructed */ | 16 /* DerType.Sequence */) { this.buffer = buffer; this.dataType = dataType; this.position = 0; this.buffer = buffer; } toBuffer() { // Move the data over to make space for the type + length prefix. const length = this.position; const lengthBytes = DerWriter.getLength(length); this.ensureCapacity(1 + lengthBytes.length + length); const result = this.buffer.slice(0, 1 + lengthBytes.length + length); this.buffer.copy(result, 1 + lengthBytes.length); // Write the type + length prefix. result[0] = this.dataType; lengthBytes.copy(result, 1, 0); // Restore the writer buffer to its previous state (without the type + length prefix). this.buffer = this.buffer.slice(1 + lengthBytes.length, result.length); this.position = length; return result; } writeSequence(data) { this.writeBytes(data.toBuffer()); } writeTagged(tagId, data) { if (tagId > 0xf) throw new Error('Invalid DER tag.'); this.writeByte(160 /* DerType.Tagged */ | tagId); const lengthBytes = DerWriter.getLength(data.position); this.writeBytes(lengthBytes); this.writeBytes(data.buffer.slice(0, data.position)); } writeNull() { this.writeByte(5 /* DerType.Null */); this.writeByte(0); } writeInteger(value) { this.writeByte(2 /* DerType.Integer */); const integerBytes = value.toBytes(); const lengthBytes = DerWriter.getLength(integerBytes.length); this.writeBytes(lengthBytes); this.writeBytes(integerBytes); } writeOctetString(data) { this.writeByte(4 /* DerType.OctetString */); const lengthBytes = DerWriter.getLength(data.length); this.writeBytes(lengthBytes); this.writeBytes(data); } writeBitString(data) { this.writeByte(3 /* DerType.BitString */); const lengthBytes = DerWriter.getLength(1 + data.length); this.writeBytes(lengthBytes); this.writeByte(0); this.writeBytes(data); } writeObjectIdentifier(oid) { if (!oid) throw new TypeError('OID value is null or empty.'); const values = oid.split('.').map(Number); if (values.length < 2 || values[0] > 3 || values[1] >= 40) { throw new Error(`Invalid OID: ${oid}`); } this.writeByte(6 /* DerType.ObjectIdentifier */); let length = values.length - 1; for (let i = 2; i < values.length; i++) { let value = values[i]; while (value > 128) { length++; value /= 128; } } const lengthBytes = DerWriter.getLength(length); this.writeBytes(lengthBytes); this.writeByte(values[0] * 40 + values[1]); for (let i = 2; i < values.length; i++) { let value = values[i]; if (value >= 128) { const bytes = []; bytes.push(value & 0x7f); while (value >= 128) { value /= 128; bytes.push(0x80 | (value & 0x7f)); } while (bytes.length > 0) { this.writeByte(bytes.pop()); } } else { this.writeByte(value); } } } static getLength(length) { if (length > 127) { let size = 1; for (let val = length >> 8; val !== 0; val >>= 8) { size++; } const lengthBytes = DerWriter.lengthBuffer.slice(0, size + 1); lengthBytes[0] = size | 0x80; for (let i = (size - 1) * 8, j = 1; i >= 0; i -= 8, j++) { lengthBytes[j] = length >> i; } return lengthBytes; } else { const lengthBytes = DerWriter.lengthBuffer.slice(0, 1); lengthBytes[0] = length; return lengthBytes; } } writeByte(value) { this.ensureCapacity(this.position + 1); this.buffer[this.position++] = value; } writeBytes(value) { this.ensureCapacity(this.position + value.length); value.copy(this.buffer, this.position); this.position += value.length; } ensureCapacity(capacity) { if (this.buffer.length < capacity) { let newLength = Math.max(512, this.buffer.length * 2); while (newLength < capacity) newLength *= 2; const newBuffer = buffer_1.Buffer.alloc(newLength); this.buffer.copy(newBuffer, 0, 0, this.position); this.buffer = newBuffer; } } } exports.DerWriter = DerWriter; DerWriter.lengthBuffer = buffer_1.Buffer.alloc(10); //# sourceMappingURL=derData.js.map