CyberChef/src/core/vendor/gost/gostCoding.mjs

1160 lines
47 KiB
JavaScript
Raw Normal View History

/**
* Coding algorithms: Base64, Hex, Int16, Chars, BER and PEM
* version 1.76
* 2014-2016, Rudolf Nickolaev. All rights reserved.
*
* Exported for CyberChef by mshwed [m@ttshwed.com]
*/
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* THIS SOfTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES Of MERCHANTABILITY AND fITNESS fOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* fOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT Of SUBSTITUTE GOODS OR
* SERVICES; LOSS Of USE, DATA, OR PROfITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY Of LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT Of THE USE
* Of THIS SOfTWARE, EVEN If ADVISED Of THE POSSIBILITY OF SUCH DAMAGE.
*
*/
import gostCrypto from './gostCrypto';
/**
* The Coding interface provides string converting methods: Base64, Hex,
* Int16, Chars, BER and PEM
* @class GostCoding
*
*/ // <editor-fold defaultstate="collapsed">
var root = {};
var DataError = Error;
var CryptoOperationData = ArrayBuffer;
var Date = Date;
function buffer(d) {
if (d instanceof CryptoOperationData)
return d;
else if (d && d.buffer && d.buffer instanceof CryptoOperationData)
return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ?
d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer;
else
throw new DataError('CryptoOperationData required');
} // </editor-fold>
function GostCoding() {
}
/**
* BASE64 conversion
*
* @class GostCoding.Base64
*/
var Base64 = {// <editor-fold defaultstate="collapsed">
/**
* Base64.decode convert BASE64 string s to CryptoOperationData
*
* @memberOf GostCoding.Base64
* @param {String} s BASE64 encoded string value
* @returns {CryptoOperationData} Binary decoded data
*/
decode: function (s) {
s = s.replace(/[^A-Za-z0-9\+\/]/g, '');
var n = s.length,
k = n * 3 + 1 >> 2, r = new Uint8Array(k);
for (var m3, m4, u24 = 0, j = 0, i = 0; i < n; i++) {
m4 = i & 3;
var c = s.charCodeAt(i);
c = c > 64 && c < 91 ?
c - 65 : c > 96 && c < 123 ?
c - 71 : c > 47 && c < 58 ?
c + 4 : c === 43 ?
62 : c === 47 ?
63 : 0;
u24 |= c << 18 - 6 * m4;
if (m4 === 3 || n - i === 1) {
for (m3 = 0; m3 < 3 && j < k; m3++, j++) {
r[j] = u24 >>> (16 >>> m3 & 24) & 255;
}
u24 = 0;
}
}
return r.buffer;
},
/**
* Base64.encode(data) convert CryptoOperationData data to BASE64 string
*
* @memberOf GostCoding.Base64
* @param {CryptoOperationData} data Bynary data for encoding
* @returns {String} BASE64 encoded data
*/
encode: function (data) {
var slen = 8, d = new Uint8Array(buffer(data));
var m3 = 2, s = '';
for (var n = d.length, u24 = 0, i = 0; i < n; i++) {
m3 = i % 3;
if (i > 0 && (i * 4 / 3) % (12 * slen) === 0)
s += '\r\n';
u24 |= d[i] << (16 >>> m3 & 24);
if (m3 === 2 || n - i === 1) {
for (var j = 18; j >= 0; j -= 6) {
var c = u24 >>> j & 63;
c = c < 26 ? c + 65 : c < 52 ? c + 71 : c < 62 ? c - 4 :
c === 62 ? 43 : c === 63 ? 47 : 65;
s += String.fromCharCode(c);
}
u24 = 0;
}
}
return s.substr(0, s.length - 2 + m3) + (m3 === 2 ? '' : m3 === 1 ? '=' : '==');
} // </editor-fold>
};
/**
* BASE64 conversion
*
* @memberOf GostCoding
* @insnance
* @type GostCoding.Base64
*/
GostCoding.prototype.Base64 = Base64;
/**
* Text string conversion <br>
* Methods support charsets: ascii, win1251, utf8, utf16 (ucs2, unicode), utf32 (ucs4)
*
* @class GostCoding.Chars
*/
var Chars = (function () { // <editor-fold defaultstate="collapsed">
var _win1251_ = {
0x402: 0x80, 0x403: 0x81, 0x201A: 0x82, 0x453: 0x83, 0x201E: 0x84, 0x2026: 0x85, 0x2020: 0x86, 0x2021: 0x87,
0x20AC: 0x88, 0x2030: 0x89, 0x409: 0x8A, 0x2039: 0x8B, 0x40A: 0x8C, 0x40C: 0x8D, 0x40B: 0x8E, 0x40f: 0x8f,
0x452: 0x90, 0x2018: 0x91, 0x2019: 0x92, 0x201C: 0x93, 0x201D: 0x94, 0x2022: 0x95, 0x2013: 0x96, 0x2014: 0x97,
0x2122: 0x99, 0x459: 0x9A, 0x203A: 0x9B, 0x45A: 0x9C, 0x45C: 0x9D, 0x45B: 0x9E, 0x45f: 0x9f,
0xA0: 0xA0, 0x40E: 0xA1, 0x45E: 0xA2, 0x408: 0xA3, 0xA4: 0xA4, 0x490: 0xA5, 0xA6: 0xA6, 0xA7: 0xA7,
0x401: 0xA8, 0xA9: 0xA9, 0x404: 0xAA, 0xAB: 0xAB, 0xAC: 0xAC, 0xAD: 0xAD, 0xAE: 0xAE, 0x407: 0xAf,
0xB0: 0xB0, 0xB1: 0xB1, 0x406: 0xB2, 0x456: 0xB3, 0x491: 0xB4, 0xB5: 0xB5, 0xB6: 0xB6, 0xB7: 0xB7,
0x451: 0xB8, 0x2116: 0xB9, 0x454: 0xBA, 0xBB: 0xBB, 0x458: 0xBC, 0x405: 0xBD, 0x455: 0xBE, 0x457: 0xBf
};
var _win1251back_ = {};
for (var from in _win1251_) {
var to = _win1251_[from];
_win1251back_[to] = from;
}
return {
/**
* Chars.decode(s, charset) convert string s with defined charset to CryptoOperationData
*
* @memberOf GostCoding.Chars
* @param {string} s Javascript string
* @param {string} charset Charset, default 'win1251'
* @returns {CryptoOperationData} Decoded binary data
*/
decode: function (s, charset) {
charset = (charset || 'win1251').toLowerCase().replace('-', '');
var r = [];
for (var i = 0, j = s.length; i < j; i++) {
var c = s.charCodeAt(i);
if (charset === 'utf8') {
if (c < 0x80) {
r.push(c);
} else if (c < 0x800) {
r.push(0xc0 + (c >>> 6));
r.push(0x80 + (c & 63));
} else if (c < 0x10000) {
r.push(0xe0 + (c >>> 12));
r.push(0x80 + (c >>> 6 & 63));
r.push(0x80 + (c & 63));
} else if (c < 0x200000) {
r.push(0xf0 + (c >>> 18));
r.push(0x80 + (c >>> 12 & 63));
r.push(0x80 + (c >>> 6 & 63));
r.push(0x80 + (c & 63));
} else if (c < 0x4000000) {
r.push(0xf8 + (c >>> 24));
r.push(0x80 + (c >>> 18 & 63));
r.push(0x80 + (c >>> 12 & 63));
r.push(0x80 + (c >>> 6 & 63));
r.push(0x80 + (c & 63));
} else {
r.push(0xfc + (c >>> 30));
r.push(0x80 + (c >>> 24 & 63));
r.push(0x80 + (c >>> 18 & 63));
r.push(0x80 + (c >>> 12 & 63));
r.push(0x80 + (c >>> 6 & 63));
r.push(0x80 + (c & 63));
}
} else if (charset === 'unicode' || charset === 'ucs2' || charset === 'utf16') {
if (c < 0xD800 || (c >= 0xE000 && c <= 0x10000)) {
r.push(c >>> 8);
r.push(c & 0xff);
} else if (c >= 0x10000 && c < 0x110000) {
c -= 0x10000;
var first = ((0xffc00 & c) >> 10) + 0xD800;
var second = (0x3ff & c) + 0xDC00;
r.push(first >>> 8);
r.push(first & 0xff);
r.push(second >>> 8);
r.push(second & 0xff);
}
} else if (charset === 'utf32' || charset === 'ucs4') {
r.push(c >>> 24 & 0xff);
r.push(c >>> 16 & 0xff);
r.push(c >>> 8 & 0xff);
r.push(c & 0xff);
} else if (charset === 'win1251') {
if (c >= 0x80) {
if (c >= 0x410 && c < 0x450) // А..Яа..я
c -= 0x350;
else
c = _win1251_[c] || 0;
}
r.push(c);
} else
r.push(c & 0xff);
}
return new Uint8Array(r).buffer;
},
/**
* Chars.encode(data, charset) convert CryptoOperationData data to string with defined charset
*
* @memberOf GostCoding.Chars
* @param {CryptoOperationData} data Binary data
* @param {string} charset Charset, default win1251
* @returns {string} Encoded javascript string
*/
encode: function (data, charset) {
charset = (charset || 'win1251').toLowerCase().replace('-', '');
var r = [], d = new Uint8Array(buffer(data));
for (var i = 0, n = d.length; i < n; i++) {
var c = d[i];
if (charset === 'utf8') {
c = c >= 0xfc && c < 0xfe && i + 5 < n ? // six bytes
(c - 0xfc) * 1073741824 + (d[++i] - 0x80 << 24) + (d[++i] - 0x80 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80
: c >> 0xf8 && c < 0xfc && i + 4 < n ? // five bytes
(c - 0xf8 << 24) + (d[++i] - 0x80 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80
: c >> 0xf0 && c < 0xf8 && i + 3 < n ? // four bytes
(c - 0xf0 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80
: c >= 0xe0 && c < 0xf0 && i + 2 < n ? // three bytes
(c - 0xe0 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80
: c >= 0xc0 && c < 0xe0 && i + 1 < n ? // two bytes
(c - 0xc0 << 6) + d[++i] - 0x80
: c; // one byte
} else if (charset === 'unicode' || charset === 'ucs2' || charset === 'utf16') {
c = (c << 8) + d[++i];
if (c >= 0xD800 && c < 0xE000) {
var first = (c - 0xD800) << 10;
c = d[++i];
c = (c << 8) + d[++i];
var second = c - 0xDC00;
c = first + second + 0x10000;
}
} else if (charset === 'utf32' || charset === 'ucs4') {
c = (c << 8) + d[++i];
c = (c << 8) + d[++i];
c = (c << 8) + d[++i];
} else if (charset === 'win1251') {
if (c >= 0x80) {
if (c >= 0xC0 && c < 0x100)
c += 0x350; // А..Яа..я
else
c = _win1251back_[c] || 0;
}
}
r.push(String.fromCharCode(c));
}
return r.join('');
}
}; // </editor-fold>
})();
/**
* Text string conversion
*
* @memberOf GostCoding
* @insnance
* @type GostCoding.Chars
*/
GostCoding.prototype.Chars = Chars;
/**
* HEX conversion
*
* @class GostCoding.Hex
*/
var Hex = {// <editor-fold defaultstate="collapsed">
/**
* Hex.decode(s, endean) convert HEX string s to CryptoOperationData in endean mode
*
* @memberOf GostCoding.Hex
* @param {string} s Hex encoded string
* @param {boolean} endean Little or Big Endean, default Little
* @returns {CryptoOperationData} Decoded binary data
*/
decode: function (s, endean) {
s = s.replace(/[^A-fa-f0-9]/g, '');
var n = Math.ceil(s.length / 2), r = new Uint8Array(n);
s = (s.length % 2 > 0 ? '0' : '') + s;
if (endean && ((typeof endean !== 'string') ||
(endean.toLowerCase().indexOf('little') < 0)))
for (var i = 0; i < n; i++)
r[i] = parseInt(s.substr((n - i - 1) * 2, 2), 16);
else
for (var i = 0; i < n; i++)
r[i] = parseInt(s.substr(i * 2, 2), 16);
return r.buffer;
},
/**
* Hex.encode(data, endean) convert CryptoOperationData data to HEX string in endean mode
*
* @memberOf GostCoding.Hex
* @param {CryptoOperationData} data Binary data
* @param {boolean} endean Little/Big Endean, default Little
* @returns {string} Hex decoded string
*/
encode: function (data, endean) {
var s = [], d = new Uint8Array(buffer(data)), n = d.length;
if (endean && ((typeof endean !== 'string') ||
(endean.toLowerCase().indexOf('little') < 0)))
for (var i = 0; i < n; i++) {
var j = n - i - 1;
s[j] = (j > 0 && j % 32 === 0 ? '\r\n' : '') +
('00' + d[i].toString(16)).slice(-2);
}
else
for (var i = 0; i < n; i++)
s[i] = (i > 0 && i % 32 === 0 ? '\r\n' : '') +
('00' + d[i].toString(16)).slice(-2);
return s.join('');
} // </editor-fold>
};
/**
* HEX conversion
* @memberOf GostCoding
* @insnance
* @type GostCoding.Hex
*/
GostCoding.prototype.Hex = Hex;
/**
* String hex-encoded integer conversion
*
* @class GostCoding.Int16
*/
var Int16 = {// <editor-fold defaultstate="collapsed">
/**
* Int16.decode(s) convert hex big insteger s to CryptoOperationData
*
* @memberOf GostCoding.Int16
* @param {string} s Int16 string
* @returns {CryptoOperationData} Decoded binary data
*/
decode: function (s) {
s = (s || '').replace(/[^\-A-fa-f0-9]/g, '');
if (s.length === 0)
s = '0';
// Signature
var neg = false;
if (s.charAt(0) === '-') {
neg = true;
s = s.substring(1);
}
// Align 2 chars
while (s.charAt(0) === '0' && s.length > 1)
s = s.substring(1);
s = (s.length % 2 > 0 ? '0' : '') + s;
// Padding for singanuture
// '800000' - 'ffffff' - for positive
// '800001' - 'ffffff' - for negative
if ((!neg && !/^[0-7]/.test(s)) ||
(neg && !/^[0-7]|8[0]+$/.test(s)))
s = '00' + s;
// Convert hex
var n = s.length / 2, r = new Uint8Array(n), t = 0;
for (var i = n - 1; i >= 0; --i) {
var c = parseInt(s.substr(i * 2, 2), 16);
if (neg && (c + t > 0)) {
c = 256 - c - t;
t = 1;
}
r[i] = c;
}
return r.buffer;
},
/**
* Int16.encode(data) convert CryptoOperationData data to big integer hex string
*
* @memberOf GostCoding.Int16
* @param {CryptoOperationData} data Binary data
* @returns {string} Int16 encoded string
*/
encode: function (data) {
var d = new Uint8Array(buffer(data)), n = d.length;
if (d.length === 0)
return '0x00';
var s = [], neg = d[0] > 0x7f, t = 0;
for (var i = n - 1; i >= 0; --i) {
var v = d[i];
if (neg && (v + t > 0)) {
v = 256 - v - t;
t = 1;
}
s[i] = ('00' + v.toString(16)).slice(-2);
}
s = s.join('');
while (s.charAt(0) === '0')
s = s.substring(1);
return (neg ? '-' : '') + '0x' + s;
} // </editor-fold>
};
/**
* String hex-encoded integer conversion
* @memberOf GostCoding
* @insnance
* @type GostCoding.Int16
*/
GostCoding.prototype.Int16 = Int16;
/**
* BER, DER, CER conversion
*
* @class GostCoding.BER
*/
var BER = (function () { // <editor-fold defaultstate="collapsed">
// Predefenition block
function encodeBER(source, format, onlyContent) {
// Correct primitive type
var object = source.object;
if (object === undefined)
object = source;
// Determinate tagClass
var tagClass = source.tagClass = source.tagClass || 0; // Universial default
// Determinate tagNumber. Use only for Universal class
if (tagClass === 0) {
var tagNumber = source.tagNumber;
if (typeof tagNumber === 'undefined') {
if (typeof object === 'string') {
if (object === '') // NULL
tagNumber = 0x05;
else if (/^\-?0x[0-9a-fA-F]+$/.test(object)) // INTEGER
tagNumber = 0x02;
else if (/^(\d+\.)+\d+$/.test(object)) // OID
tagNumber = 0x06;
else if (/^[01]+$/.test(object)) // BIT STRING
tagNumber = 0x03;
else if (/^(true|false)$/.test(object)) // BOOLEAN
tagNumber = 0x01;
else if (/^[0-9a-fA-F]+$/.test(object)) // OCTET STRING
tagNumber = 0x04;
else
tagNumber = 0x13; // Printable string (later can be changed to UTF8String)
} else if (typeof object === 'number') { // INTEGER
tagNumber = 0x02;
} else if (typeof object === 'boolean') { // BOOLEAN
tagNumber = 0x01;
} else if (object instanceof Array) { // SEQUENCE
tagNumber = 0x10;
} else if (object instanceof Date) { // GeneralizedTime
tagNumber = 0x18;
} else if (object instanceof CryptoOperationData || (object && object.buffer instanceof CryptoOperationData)) {
tagNumber = 0x04;
} else
throw new DataError('Unrecognized type for ' + object);
}
}
// Determinate constructed
var tagConstructed = source.tagConstructed;
if (typeof tagConstructed === 'undefined')
tagConstructed = source.tagConstructed = object instanceof Array;
// Create content
var content;
if (object instanceof CryptoOperationData || (object && object.buffer instanceof CryptoOperationData)) { // Direct
content = new Uint8Array(buffer(object));
if (tagNumber === 0x03) { // BITSTRING
// Set unused bits
var a = new Uint8Array(buffer(content));
content = new Uint8Array(a.length + 1);
content[0] = 0; // No unused bits
content.set(a, 1);
}
} else if (tagConstructed) { // Sub items coding
if (object instanceof Array) {
var bytelen = 0, ba = [], offset = 0;
for (var i = 0, n = object.length; i < n; i++) {
ba[i] = encodeBER(object[i], format);
bytelen += ba[i].length;
}
if (tagNumber === 0x11)
ba.sort(function (a, b) { // Sort order for SET components
for (var i = 0, n = Math.min(a.length, b.length); i < n; i++) {
var r = a[i] - b[i];
if (r !== 0)
return r;
}
return a.length - b.length;
});
if (format === 'CER') { // final for CER 00 00
ba[n] = new Uint8Array(2);
bytelen += 2;
}
content = new Uint8Array(bytelen);
for (var i = 0, n = ba.length; i < n; i++) {
content.set(ba[i], offset);
offset = offset + ba[i].length;
}
} else
throw new DataError('Constracted block can\'t be primitive');
} else {
switch (tagNumber) {
// 0x00: // EOC
case 0x01: // BOOLEAN
content = new Uint8Array(1);
content[0] = object ? 0xff : 0;
break;
case 0x02: // INTEGER
case 0x0a: // ENUMIRATED
content = Int16.decode(
typeof object === 'number' ? object.toString(16) : object);
break;
case 0x03: // BIT STRING
if (typeof object === 'string') {
var unusedBits = 7 - (object.length + 7) % 8;
var n = Math.ceil(object.length / 8);
content = new Uint8Array(n + 1);
content[0] = unusedBits;
for (var i = 0; i < n; i++) {
var c = 0;
for (var j = 0; j < 8; j++) {
var k = i * 8 + j;
c = (c << 1) + (k < object.length ? (object.charAt(k) === '1' ? 1 : 0) : 0);
}
content[i + 1] = c;
}
}
break;
case 0x04:
content = Hex.decode(
typeof object === 'number' ? object.toString(16) : object);
break;
// case 0x05: // NULL
case 0x06: // OBJECT IDENTIFIER
var a = object.match(/\d+/g), r = [];
for (var i = 1; i < a.length; i++) {
var n = +a[i], r1 = [];
if (i === 1)
n = n + a[0] * 40;
do {
r1.push(n & 0x7F);
n = n >>> 7;
} while (n);
// reverse order
for (j = r1.length - 1; j >= 0; --j)
r.push(r1[j] + (j === 0 ? 0x00 : 0x80));
}
content = new Uint8Array(r);
break;
// case 0x07: // ObjectDescriptor
// case 0x08: // EXTERNAL
// case 0x09: // REAL
// case 0x0A: // ENUMERATED
// case 0x0B: // EMBEDDED PDV
case 0x0C: // UTF8String
content = Chars.decode(object, 'utf8');
break;
// case 0x10: // SEQUENCE
// case 0x11: // SET
case 0x12: // NumericString
case 0x16: // IA5String // ASCII
case 0x13: // PrintableString // ASCII subset
case 0x14: // TeletexString // aka T61String
case 0x15: // VideotexString
case 0x19: // GraphicString
case 0x1A: // VisibleString // ASCII subset
case 0x1B: // GeneralString
// Reflect on character encoding
for (var i = 0, n = object.length; i < n; i++)
if (object.charCodeAt(i) > 255)
tagNumber = 0x0C;
if (tagNumber === 0x0C)
content = Chars.decode(object, 'utf8');
else
content = Chars.decode(object, 'ascii');
break;
case 0x17: // UTCTime
case 0x18: // GeneralizedTime
var result = object.original;
if (!result) {
var date = new Date(object);
date.setMinutes(date.getMinutes() + date.getTimezoneOffset()); // to UTC
var ms = tagNumber === 0x18 ? date.getMilliseconds().toString() : ''; // Milliseconds, remove trailing zeros
while (ms.length > 0 && ms.charAt(ms.length - 1) === '0')
ms = ms.substring(0, ms.length - 1);
if (ms.length > 0)
ms = '.' + ms;
result = (tagNumber === 0x17 ? date.getYear().toString().slice(-2) : date.getFullYear().toString()) +
('00' + (date.getMonth() + 1)).slice(-2) +
('00' + date.getDate()).slice(-2) +
('00' + date.getHours()).slice(-2) +
('00' + date.getMinutes()).slice(-2) +
('00' + date.getSeconds()).slice(-2) + ms + 'Z';
}
content = Chars.decode(result, 'ascii');
break;
case 0x1C: // UniversalString
content = Chars.decode(object, 'utf32');
break;
case 0x1E: // BMPString
content = Chars.decode(object, 'utf16');
break;
}
}
if (!content)
content = new Uint8Array(0);
if (content instanceof CryptoOperationData)
content = new Uint8Array(content);
if (!tagConstructed && format === 'CER') {
// Encoding CER-form for string types
var k;
switch (tagNumber) {
case 0x03: // BIT_STRING
k = 1; // ingnore unused bit for bit string
case 0x04: // OCTET_STRING
case 0x0C: // UTF8String
case 0x12: // NumericString
case 0x13: // PrintableString
case 0x14: // TeletexString
case 0x15: // VideotexString
case 0x16: // IA5String
case 0x19: // GraphicString
case 0x1A: // VisibleString
case 0x1B: // GeneralString
case 0x1C: // UniversalString
case 0x1E: // BMPString
k = k || 0;
// Split content on 1000 octet len parts
var size = 1000;
var bytelen = 0, ba = [], offset = 0;
for (var i = k, n = content.length; i < n; i += size - k) {
ba[i] = encodeBER({
object: new Unit8Array(content.buffer, i, Math.min(size - k, n - i)),
tagNumber: tagNumber,
tagClass: 0,
tagConstructed: false
}, format);
bytelen += ba[i].length;
}
ba[n] = new Uint8Array(2); // final for CER 00 00
bytelen += 2;
content = new Uint8Array(bytelen);
for (var i = 0, n = ba.length; i < n; i++) {
content.set(ba[i], offset);
offset = offset + ba[i].length;
}
}
}
// Restore tagNumber for all classes
if (tagClass === 0)
source.tagNumber = tagNumber;
else
source.tagNumber = tagNumber = source.tagNumber || 0;
source.content = content;
if (onlyContent)
return content;
// Create header
// tagNumber
var ha = [], first = tagClass === 3 ? 0xC0 : tagClass === 2 ? 0x80 :
tagClass === 1 ? 0x40 : 0x00;
if (tagConstructed)
first |= 0x20;
if (tagNumber < 0x1F) {
first |= tagNumber & 0x1F;
ha.push(first);
} else {
first |= 0x1F;
ha.push(first);
var n = tagNumber, ha1 = [];
do {
ha1.push(n & 0x7F);
n = n >>> 7;
} while (n)
// reverse order
for (var j = ha1.length - 1; j >= 0; --j)
ha.push(ha1[j] + (j === 0 ? 0x00 : 0x80));
}
// Length
if (tagConstructed && format === 'CER') {
ha.push(0x80);
} else {
var len = content.length;
if (len > 0x7F) {
var l2 = len, ha2 = [];
do {
ha2.push(l2 & 0xff);
l2 = l2 >>> 8;
} while (l2);
ha.push(ha2.length + 0x80); // reverse order
for (var j = ha2.length - 1; j >= 0; --j)
ha.push(ha2[j]);
} else {
// simple len
ha.push(len);
}
}
var header = source.header = new Uint8Array(ha);
// Result - complete buffer
var block = new Uint8Array(header.length + content.length);
block.set(header, 0);
block.set(content, header.length);
return block;
}
function decodeBER(source, offset) {
// start pos
var pos = offset || 0, start = pos;
var tagNumber, tagClass, tagConstructed,
content, header, buffer, sub, len;
if (source.object) {
// Ready from source
tagNumber = source.tagNumber;
tagClass = source.tagClass;
tagConstructed = source.tagConstructed;
content = source.content;
header = source.header;
buffer = source.object instanceof CryptoOperationData ?
new Uint8Array(source.object) : null;
sub = source.object instanceof Array ? source.object : null;
len = buffer && buffer.length || null;
} else {
// Decode header
var d = source;
// Read tag
var buf = d[pos++];
tagNumber = buf & 0x1f;
tagClass = buf >> 6;
tagConstructed = (buf & 0x20) !== 0;
if (tagNumber === 0x1f) { // long tag
tagNumber = 0;
do {
if (tagNumber > 0x1fffffffffff80)
throw new DataError('Convertor not supported tag number more then (2^53 - 1) at position ' + offset);
buf = d[pos++];
tagNumber = (tagNumber << 7) + (buf & 0x7f);
} while (buf & 0x80);
}
// Read len
buf = d[pos++];
len = buf & 0x7f;
if (len !== buf) {
if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
throw new DataError('Length over 48 bits not supported at position ' + offset);
if (len === 0)
len = null; // undefined
else {
buf = 0;
for (var i = 0; i < len; ++i)
buf = (buf << 8) + d[pos++];
len = buf;
}
}
start = pos;
sub = null;
if (tagConstructed) {
// must have valid content
sub = [];
if (len !== null) {
// definite length
var end = start + len;
while (pos < end) {
var s = decodeBER(d, pos);
sub.push(s);
pos += s.header.length + s.content.length;
}
if (pos !== end)
throw new DataError('Content size is not correct for container starting at offset ' + start);
} else {
// undefined length
try {
for (; ; ) {
var s = decodeBER(d, pos);
pos += s.header.length + s.content.length;
if (s.tagClass === 0x00 && s.tagNumber === 0x00)
break;
sub.push(s);
}
len = pos - start;
} catch (e) {
throw new DataError('Exception ' + e + ' while decoding undefined length content at offset ' + start);
}
}
}
// Header and content
header = new Uint8Array(d.buffer, offset, start - offset);
content = new Uint8Array(d.buffer, start, len);
buffer = content;
}
// Constructed types - check for string concationation
if (sub !== null && tagClass === 0) {
var k;
switch (tagNumber) {
case 0x03: // BIT_STRING
k = 1; // ingnore unused bit for bit string
case 0x04: // OCTET_STRING
case 0x0C: // UTF8String
case 0x12: // NumericString
case 0x13: // PrintableString
case 0x14: // TeletexString
case 0x15: // VideotexString
case 0x16: // IA5String
case 0x19: // GraphicString
case 0x1A: // VisibleString
case 0x1B: // GeneralString
case 0x1C: // UniversalString
case 0x1E: // BMPString
k = k || 0;
// Concatination
if (sub.length === 0)
throw new DataError('No constructed encoding content of string type at offset ' + start);
len = k;
for (var i = 0, n = sub.length; i < n; i++) {
var s = sub[i];
if (s.tagClass !== tagClass || s.tagNumber !== tagNumber || s.tagConstructed)
throw new DataError('Invalid constructed encoding of string type at offset ' + start);
len += s.content.length - k;
}
buffer = new Uint8Array(len);
for (var i = 0, n = sub.length, j = k; i < n; i++) {
var s = sub[i];
if (k > 0)
buffer.set(s.content.subarray(1), j);
else
buffer.set(s.content, j);
j += s.content.length - k;
}
tagConstructed = false; // follow not required
sub = null;
break;
}
}
// Primitive types
var object = '';
if (sub === null) {
if (len === null)
throw new DataError('Invalid tag with undefined length at offset ' + start);
if (tagClass === 0) {
switch (tagNumber) {
case 0x01: // BOOLEAN
object = buffer[0] !== 0;
break;
case 0x02: // INTEGER
case 0x0a: // ENUMIRATED
if (len > 6) {
object = Int16.encode(buffer);
} else {
var v = buffer[0];
if (buffer[0] > 0x7f)
v = v - 256;
for (var i = 1; i < len; i++)
v = v * 256 + buffer[i];
object = v;
}
break;
case 0x03: // BIT_STRING
if (len > 5) { // Content buffer
object = new Uint8Array(buffer.subarray(1)).buffer;
} else { // Max bit mask only for 32 bit
var unusedBit = buffer[0],
skip = unusedBit, s = [];
for (var i = len - 1; i >= 1; --i) {
var b = buffer[i];
for (var j = skip; j < 8; ++j)
s.push((b >> j) & 1 ? '1' : '0');
skip = 0;
}
object = s.reverse().join('');
}
break;
case 0x04: // OCTET_STRING
object = new Uint8Array(buffer).buffer;
break;
// case 0x05: // NULL
case 0x06: // OBJECT_IDENTIFIER
var s = '',
n = 0,
bits = 0;
for (var i = 0; i < len; ++i) {
var v = buffer[i];
n = (n << 7) + (v & 0x7F);
bits += 7;
if (!(v & 0x80)) { // finished
if (s === '') {
var m = n < 80 ? n < 40 ? 0 : 1 : 2;
s = m + "." + (n - m * 40);
} else
s += "." + n.toString();
n = 0;
bits = 0;
}
}
if (bits > 0)
throw new DataError('Incompleted OID at offset ' + start);
object = s;
break;
//case 0x07: // ObjectDescriptor
//case 0x08: // EXTERNAL
//case 0x09: // REAL
//case 0x0A: // ENUMERATED
//case 0x0B: // EMBEDDED_PDV
case 0x10: // SEQUENCE
case 0x11: // SET
object = [];
break;
case 0x0C: // UTF8String
object = Chars.encode(buffer, 'utf8');
break;
case 0x12: // NumericString
case 0x13: // PrintableString
case 0x14: // TeletexString
case 0x15: // VideotexString
case 0x16: // IA5String
case 0x19: // GraphicString
case 0x1A: // VisibleString
case 0x1B: // GeneralString
object = Chars.encode(buffer, 'ascii');
break;
case 0x1C: // UniversalString
object = Chars.encode(buffer, 'utf32');
break;
case 0x1E: // BMPString
object = Chars.encode(buffer, 'utf16');
break;
case 0x17: // UTCTime
case 0x18: // GeneralizedTime
var shortYear = tagNumber === 0x17;
var s = Chars.encode(buffer, 'ascii'),
m = (shortYear ?
/^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/ :
/^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/).exec(s);
if (!m)
throw new DataError('Unrecognized time format "' + s + '" at offset ' + start);
if (shortYear) {
// Where YY is greater than or equal to 50, the year SHALL be interpreted as 19YY; and
// Where YY is less than 50, the year SHALL be interpreted as 20YY
m[1] = +m[1];
m[1] += (m[1] < 50) ? 2000 : 1900;
}
var dt = new Date(m[1], +m[2] - 1, +m[3], +(m[4] || '0'), +(m[5] || '0'), +(m[6] || '0'), +(m[7] || '0')),
tz = dt.getTimezoneOffset();
if (m[8] || tagNumber === 0x17) {
if (m[8].toUpperCase() !== 'Z' && m[9]) {
tz = tz + parseInt(m[9]);
}
dt.setMinutes(dt.getMinutes() - tz);
}
dt.original = s;
object = dt;
break;
}
} else // OCTET_STRING
object = new Uint8Array(buffer).buffer;
} else
object = sub;
// result
return {
tagConstructed: tagConstructed,
tagClass: tagClass,
tagNumber: tagNumber,
header: header,
content: content,
object: object
};
}
return {
/**
* BER.decode(object, format) convert javascript object to ASN.1 format CryptoOperationData<br><br>
* If object has members tagNumber, tagClass and tagConstructed
* it is clear define encoding rules. Else method use defaul rules:
* <ul>
* <li>Empty string or null - NULL</li>
* <li>String starts with '0x' and has 0-9 and a-f characters - INTEGER</li>
* <li>String like d.d.d.d (d - set of digits) - OBJECT IDENTIFIER</li>
* <li>String with characters 0 and 1 - BIT STRING</li>
* <li>Strings 'true' or 'false' - BOOLEAN</li>
* <li>String has only 0-9 and a-f characters - OCTET STRING</li>
* <li>String has only characters with code 0-255 - PrintableString</li>
* <li>Other strings - UTF8String</li>
* <li>Number - INTEGER</li>
* <li>Date - GeneralizedTime</li>
* <li>Boolean - SEQUENCE</li>
* <li>CryptoOperationData - OCTET STRING</li>
* </ul>
* SEQUENCE or SET arrays recursively encoded for each item.<br>
* OCTET STRING and BIT STRING can presents as array with one item.
* It means encapsulates encoding for child element.<br>
*
* If CONTEXT or APPLICATION classes item presents as array with one
* item we use EXPLICIT encoding for element, else IMPLICIT encoding.<br>
*
* @memberOf GostCoding.BER
* @param {Object} object Object to encoding
* @param {string} format Encoding rule: 'DER' or 'CER', default 'DER'
* @param {boolean} onlyContent Encode content only, without header
* @returns {CryptoOperationData} BER encoded data
*/
encode: function (object, format, onlyContent) {
return encodeBER(object, format, onlyContent).buffer;
},
/**
* BER.encode(data) convert ASN.1 format CryptoOperationData data to javascript object<br><br>
*
* Conversion rules to javascript object:
* <ul>
* <li>BOOLEAN - Boolean object</li>
* <li>INTEGER, ENUMIRATED - Integer object if len <= 6 (48 bits) else Int16 encoded string</li>
* <li>BIT STRING - Integer object if len <= 5 (w/o unsedBit octet - 32 bits) else String like '10111100' or Array with one item in case of incapsulates encoding</li>
* <li>OCTET STRING - Hex encoded string or Array with one item in case of incapsulates encoding</li>
* <li>OBJECT IDENTIFIER - String with object identifier</li>
* <li>SEQUENCE, SET - Array of encoded items</li>
* <li>UTF8String, NumericString, PrintableString, TeletexString, VideotexString,
* IA5String, GraphicString, VisibleString, GeneralString, UniversalString,
* BMPString - encoded String</li>
* <li>UTCTime, GeneralizedTime - Date</li>
* </ul>
* @memberOf GostCoding.BER
* @param {(CryptoOperationData|GostCoding.BER)} data Binary data to decode
* @returns {Object} Javascript object with result of decoding
*/
decode: function (data) {
return decodeBER(data.object ? data : new Uint8Array(buffer(data)), 0);
}
}; // </editor-fold>
})();
/**
* BER, DER, CER conversion
* @memberOf GostCoding
* @insnance
* @type GostCoding.BER
*/
GostCoding.prototype.BER = BER;
/**
* PEM conversion
* @class GostCoding.PEM
*/
var PEM = {// <editor-fold defaultstate="collapsed">
/**
* PEM.encode(data, name) encode CryptoOperationData to PEM format with name label
*
* @memberOf GostCoding.PEM
* @param {(Object|CryptoOperationData)} data Java script object or BER-encoded binary data
* @param {string} name Name of PEM object: 'certificate', 'private key' etc.
* @returns {string} Encoded object
*/
encode: function (data, name) {
return (name ? '-----BEGIN ' + name.toUpperCase() + '-----\r\n' : '') +
Base64.encode(data instanceof CryptoOperationData ? data : BER.encode(data)) +
(name ? '\r\n-----END ' + name.toUpperCase() + '-----' : '');
},
/**
* PEM.decode(s, name, deep) decode PEM format s labeled name to CryptoOperationData or javascript object in according to deep parameter
*
* @memberOf GostCoding.PEM
* @param {string} s PEM encoded string
* @param {string} name Name of PEM object: 'certificate', 'private key' etc.
* @param {boolean} deep If true method do BER-decoding, else only BASE64 decoding
* @param {integer} index Index of decoded value
* @returns {(Object|CryptoOperationData)} Decoded javascript object if deep=true, else CryptoOperationData for father BER decoding
*/
decode: function (s, name, deep, index) {
// Try clear base64
var re1 = /([A-Za-z0-9\+\/\s\=]+)/g,
valid = re1.exec(s);
if (valid[1].length !== s.length)
valid = false;
if (!valid && name) {
// Try with the name
var re2 = new RegExp(
'-----\\s?BEGIN ' + name.toUpperCase() +
'-----([A-Za-z0-9\\+\\/\\s\\=]+)-----\\s?END ' +
name.toUpperCase() + '-----', 'g');
valid = re2.exec(s);
}
if (!valid) {
// Try with some name
var re3 = new RegExp(
'-----\\s?BEGIN [A-Z0-9\\s]+' +
'-----([A-Za-z0-9\\+\\/\\s\\=]+)-----\\s?END ' +
'[A-Z0-9\\s]+-----', 'g');
valid = re3.exec(s);
}
var r = valid && valid[1 + (index || 0)];
if (!r)
throw new DataError('Not valid PEM format');
var out = Base64.decode(r);
if (deep)
out = BER.decode(out);
return out;
} // </editor-fold>
};
/**
* PEM conversion
* @memberOf GostCoding
* @insnance
* @type GostCoding.PEM
*/
GostCoding.prototype.PEM = PEM;
if (gostCrypto)
/**
* Coding algorithms: Base64, Hex, Int16, Chars, BER and PEM
*
* @memberOf gostCrypto
* @type GostCoding
*/
gostCrypto.coding = new GostCoding();
export default GostCoding;