Reworked the identity system and changed some stuff
parent
c5e7029c81
commit
cd74e94925
|
@ -1,4 +1,8 @@
|
|||
# Changelog:
|
||||
* **26.01.19**
|
||||
- Improved TeaSpeak identities (now generates automatic and are saveable)
|
||||
- Fixed `connect_profile` parameter within URL
|
||||
|
||||
* **20.01.19**
|
||||
- Added the possibility to change the remote volume of a bot
|
||||
- Added a playlist management system
|
||||
|
|
Binary file not shown.
10
files.php
10
files.php
|
@ -21,7 +21,7 @@
|
|||
],
|
||||
[ /* shared generated worker codec */
|
||||
"type" => "js",
|
||||
"search-pattern" => "/WorkerCodec.js$/",
|
||||
"search-pattern" => "/(WorkerCodec.js|WorkerPOW.js)$/",
|
||||
"build-target" => "dev|rel",
|
||||
|
||||
"path" => "js/workers/",
|
||||
|
@ -75,6 +75,14 @@
|
|||
"path" => "wasm/",
|
||||
"local-path" => "./asm/generated/"
|
||||
],
|
||||
[ /* own webassembly files */
|
||||
"type" => "wasm",
|
||||
"search-pattern" => "/.*\.(wasm)/",
|
||||
"build-target" => "dev|rel",
|
||||
|
||||
"path" => "wat/",
|
||||
"local-path" => "./shared/wat/"
|
||||
],
|
||||
[ /* translations */
|
||||
"type" => "i18n",
|
||||
"search-pattern" => "/.*\.(translation|json)/",
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"build-worker": "tsc -p shared/js/workers/tsconfig_worker_codec.json",
|
||||
"dtsgen": "node tools/dtsgen/index.js",
|
||||
"trgen": "node tools/trgen/index.js",
|
||||
"ttsc": "ttsc"
|
||||
"ttsc": "ttsc",
|
||||
"rebuild-structure-web-dev": "php files.php generate web dev"
|
||||
},
|
||||
"author": "TeaSpeak (WolverinDEV)",
|
||||
"license": "ISC",
|
||||
|
@ -26,7 +27,8 @@
|
|||
"sass": "^1.14.1",
|
||||
"sha256": "^0.2.0",
|
||||
"ttypescript": "^1.5.5",
|
||||
"typescript": "^3.1.1"
|
||||
"typescript": "^3.1.1",
|
||||
"wat2wasm": "^1.0.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -295,6 +295,7 @@ interface HandshakeIdentityHandler {
|
|||
class HandshakeHandler {
|
||||
private connection: ServerConnection;
|
||||
private handshake_handler: HandshakeIdentityHandler;
|
||||
private failed = false;
|
||||
|
||||
readonly profile: profiles.ConnectionProfile;
|
||||
readonly name: string;
|
||||
|
@ -328,6 +329,9 @@ class HandshakeHandler {
|
|||
}
|
||||
|
||||
private handshake_failed(message: string) {
|
||||
if(this.failed) return;
|
||||
|
||||
this.failed = true;
|
||||
this.connection._client.handleDisconnect(DisconnectReason.HANDSHAKE_FAILED, message);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,547 @@
|
|||
// ASN.1 JavaScript decoder
|
||||
// Copyright (c) 2008-2018 Lapo Luchini <lapo@lapo.it>
|
||||
// Copyright (c) 2019-2019 Markus Hadenfeldt <git@teaspeak.de>
|
||||
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
namespace asn1 {
|
||||
declare class Int10 {
|
||||
constructor(value?: any);
|
||||
|
||||
sub(sub: number);
|
||||
mulAdd(mul: number, add: number);
|
||||
simplify();
|
||||
}
|
||||
|
||||
const ellipsis = "\u2026";
|
||||
|
||||
function string_cut(str, len) {
|
||||
if (str.length > len)
|
||||
str = str.substring(0, len) + ellipsis;
|
||||
return str;
|
||||
}
|
||||
|
||||
export class Stream {
|
||||
private static HEX_DIGITS = "0123456789ABCDEF";
|
||||
private static reTimeS = /^(\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)?)?$/;
|
||||
private static reTimeL = /^(\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)?)?$/;
|
||||
|
||||
position: number;
|
||||
data: string | ArrayBuffer;
|
||||
|
||||
constructor(data: string | Stream | ArrayBuffer, position: number) {
|
||||
if (data instanceof Stream)
|
||||
this.data = data.data;
|
||||
else
|
||||
this.data = data;
|
||||
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
length() : number {
|
||||
if (this.data instanceof ArrayBuffer)
|
||||
return this.data.byteLength;
|
||||
return this.data.length;
|
||||
}
|
||||
|
||||
get(position?: number) {
|
||||
if (position === undefined)
|
||||
position = this.position++;
|
||||
|
||||
if (position >= this.length())
|
||||
throw 'Requesting byte offset ' + this.position + ' on a stream of length ' + this.length();
|
||||
|
||||
return (typeof(this.data) === "string") ? this.data.charCodeAt(position) : this.data[position];
|
||||
}
|
||||
|
||||
hexByte(byte: number) {
|
||||
return Stream.HEX_DIGITS.charAt((byte >> 4) & 0xF) + Stream.HEX_DIGITS.charAt(byte & 0xF);
|
||||
}
|
||||
|
||||
parseStringISO(start, end) {
|
||||
let s = "";
|
||||
for (let i = start; i < end; ++i)
|
||||
s += String.fromCharCode(this.get(i));
|
||||
return s;
|
||||
}
|
||||
|
||||
parseStringUTF(start, end) {
|
||||
let s = "";
|
||||
for (let i = start; i < end;) {
|
||||
let c = this.get(i++);
|
||||
if (c < 128)
|
||||
s += String.fromCharCode(c);
|
||||
else if ((c > 191) && (c < 224))
|
||||
s += String.fromCharCode(((c & 0x1F) << 6) | (this.get(i++) & 0x3F));
|
||||
else
|
||||
s += String.fromCharCode(((c & 0x0F) << 12) | ((this.get(i++) & 0x3F) << 6) | (this.get(i++) & 0x3F));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
parseStringBMP(start, end) {
|
||||
let str = "", hi, lo;
|
||||
for (let i = start; i < end;) {
|
||||
hi = this.get(i++);
|
||||
lo = this.get(i++);
|
||||
str += String.fromCharCode((hi << 8) | lo);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
parseTime(start, end, shortYear) {
|
||||
let s = this.parseStringISO(start, end),
|
||||
m = (shortYear ? Stream.reTimeS : Stream.reTimeL).exec(s);
|
||||
if (!m)
|
||||
return "Unrecognized time: " + s;
|
||||
if (shortYear) {
|
||||
// to avoid querying the timer, use the fixed range [1970, 2069]
|
||||
// it will conform with ITU X.400 [-10, +40] sliding window until 2030
|
||||
//m[1] = +m[1];
|
||||
//m[1] += (parseInt(m[1]) < 70) ? 2000 : 1900;
|
||||
throw "fixme!";
|
||||
}
|
||||
s = m[1] + "-" + m[2] + "-" + m[3] + " " + m[4];
|
||||
if (m[5]) {
|
||||
s += ":" + m[5];
|
||||
if (m[6]) {
|
||||
s += ":" + m[6];
|
||||
if (m[7])
|
||||
s += "." + m[7];
|
||||
}
|
||||
}
|
||||
if (m[8]) {
|
||||
s += " UTC";
|
||||
if (m[8] != 'Z') {
|
||||
s += m[8];
|
||||
if (m[9])
|
||||
s += ":" + m[9];
|
||||
}
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
parseInteger(start, end) {
|
||||
let current: number = this.get(start);
|
||||
|
||||
let negative = (current > 127);
|
||||
let padding = negative ? 255 : 0;
|
||||
let length;
|
||||
let descriptor: number | string;
|
||||
|
||||
// skip unuseful bits (not allowed in DER)
|
||||
while (current == padding && ++start < end)
|
||||
current = this.get(start);
|
||||
|
||||
length = end - start;
|
||||
if (length === 0)
|
||||
return negative ? '-1' : '0';
|
||||
|
||||
// show bit length of huge integers
|
||||
if (length > 4) {
|
||||
descriptor = current;
|
||||
length <<= 3; /* calculate bit length */
|
||||
|
||||
while (((descriptor ^ padding) & 0x80) == 0) {
|
||||
descriptor <<= 1;
|
||||
--length;
|
||||
}
|
||||
descriptor = "(" + length + " bit)\n";
|
||||
}
|
||||
// decode the integer
|
||||
if (negative) current = current - 256;
|
||||
|
||||
let number = "";
|
||||
if(typeof(Int10) !== "undefined") {
|
||||
let n = new Int10(current);
|
||||
for (let i = start + 1; i < end; ++i)
|
||||
n.mulAdd(256, this.get(i));
|
||||
number = n.toString();
|
||||
} else {
|
||||
let n = 0;
|
||||
for (let i = start + 1; i < end; ++i) {
|
||||
n <<= 8;
|
||||
n += this.get(i);
|
||||
}
|
||||
number = n.toString();
|
||||
}
|
||||
return descriptor + number;
|
||||
};
|
||||
|
||||
isASCII(start: number, end: number) {
|
||||
for (let i = start; i < end; ++i) {
|
||||
const c = this.get(i);
|
||||
if (c < 32 || c > 176)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
parseBitString(start, end, maxLength) {
|
||||
let unusedBit = this.get(start),
|
||||
lenBit = ((end - start - 1) << 3) - unusedBit,
|
||||
intro = "(" + lenBit + " bit)\n",
|
||||
s = "";
|
||||
for (let i = start + 1; i < end; ++i) {
|
||||
let b = this.get(i),
|
||||
skip = (i == end - 1) ? unusedBit : 0;
|
||||
for (let j = 7; j >= skip; --j)
|
||||
s += (b >> j) & 1 ? "1" : "0";
|
||||
if (s.length > maxLength)
|
||||
return intro + string_cut(s, maxLength);
|
||||
}
|
||||
return intro + s;
|
||||
};
|
||||
|
||||
parseOctetString(start, end, maxLength) {
|
||||
if (this.isASCII(start, end))
|
||||
return string_cut(this.parseStringISO(start, end), maxLength);
|
||||
let len = end - start,
|
||||
s = "(" + len + " byte)\n";
|
||||
maxLength /= 2; // we work in bytes
|
||||
if (len > maxLength)
|
||||
end = start + maxLength;
|
||||
for (let i = start; i < end; ++i)
|
||||
s += this.hexByte(this.get(i));
|
||||
if (len > maxLength)
|
||||
s += ellipsis;
|
||||
return s;
|
||||
};
|
||||
|
||||
parseOID(start, end, maxLength) {
|
||||
let s = '',
|
||||
n = new Int10(),
|
||||
bits = 0;
|
||||
for (let i = start; i < end; ++i) {
|
||||
let v = this.get(i);
|
||||
n.mulAdd(128, v & 0x7F);
|
||||
bits += 7;
|
||||
if (!(v & 0x80)) { // finished
|
||||
if (s === '') {
|
||||
n = n.simplify();
|
||||
if (n instanceof Int10) {
|
||||
n.sub(80);
|
||||
s = "2." + n.toString();
|
||||
} else {
|
||||
let m = n < 80 ? n < 40 ? 0 : 1 : 2;
|
||||
s = m + "." + (n - m * 40);
|
||||
}
|
||||
} else
|
||||
s += "." + n.toString();
|
||||
if (s.length > maxLength)
|
||||
return string_cut(s, maxLength);
|
||||
n = new Int10();
|
||||
bits = 0;
|
||||
}
|
||||
}
|
||||
if (bits > 0)
|
||||
s += ".incomplete";
|
||||
/* FIXME
|
||||
if (typeof oids === 'object') {
|
||||
let oid = oids[s];
|
||||
if (oid) {
|
||||
if (oid.d) s += "\n" + oid.d;
|
||||
if (oid.c) s += "\n" + oid.c;
|
||||
if (oid.w) s += "\n(warning!)";
|
||||
}
|
||||
}
|
||||
*/
|
||||
return s;
|
||||
};
|
||||
}
|
||||
|
||||
export enum TagClass {
|
||||
UNIVERSAL = 0x00,
|
||||
APPLICATION = 0x01,
|
||||
CONTEXT = 0x02,
|
||||
PRIVATE = 0x03
|
||||
}
|
||||
|
||||
export enum TagType {
|
||||
EOC = 0x00,
|
||||
BOOLEAN = 0x01,
|
||||
INTEGER = 0x02,
|
||||
BIT_STRING = 0x03,
|
||||
OCTET_STRING = 0x04,
|
||||
NULL = 0x05,
|
||||
OBJECT_IDENTIFIER = 0x06,
|
||||
ObjectDescriptor = 0x07,
|
||||
EXTERNAL = 0x08,
|
||||
REAL = 0x09,
|
||||
ENUMERATED = 0x0A,
|
||||
EMBEDDED_PDV = 0x0B,
|
||||
UTF8String = 0x0C,
|
||||
SEQUENCE = 0x10,
|
||||
SET = 0x11,
|
||||
NumericString = 0x12,
|
||||
PrintableString = 0x13, // ASCII subset
|
||||
TeletextString = 0x14, // aka T61String
|
||||
VideotexString = 0x15,
|
||||
IA5String = 0x16, // ASCII
|
||||
UTCTime = 0x17,
|
||||
GeneralizedTime = 0x18,
|
||||
GraphicString = 0x19,
|
||||
VisibleString = 0x1A, // ASCII subset
|
||||
GeneralString = 0x1B,
|
||||
UniversalString = 0x1C,
|
||||
BMPString = 0x1E
|
||||
}
|
||||
|
||||
class ASN1Tag {
|
||||
tagClass: TagClass;
|
||||
type: TagType;
|
||||
tagConstructed: boolean;
|
||||
tagNumber: number;
|
||||
|
||||
constructor(stream: Stream) {
|
||||
let buf = stream.get();
|
||||
this.tagClass = buf >> 6;
|
||||
this.tagConstructed = ((buf & 0x20) !== 0);
|
||||
this.tagNumber = buf & 0x1F;
|
||||
if (this.tagNumber == 0x1F) { // long tag
|
||||
let n = new Int10();
|
||||
do {
|
||||
buf = stream.get();
|
||||
n.mulAdd(128, buf & 0x7F);
|
||||
} while (buf & 0x80);
|
||||
this.tagNumber = n.simplify();
|
||||
}
|
||||
}
|
||||
|
||||
isUniversal() {
|
||||
return this.tagClass === 0x00;
|
||||
};
|
||||
|
||||
isEOC() {
|
||||
return this.tagClass === 0x00 && this.tagNumber === 0x00;
|
||||
};
|
||||
}
|
||||
|
||||
export class ASN1 {
|
||||
stream: Stream;
|
||||
header: number;
|
||||
length: number;
|
||||
tag: ASN1Tag;
|
||||
children: ASN1[];
|
||||
|
||||
constructor(stream: Stream, header: number, length: number, tag: ASN1Tag, children: ASN1[]) {
|
||||
this.stream = stream;
|
||||
this.header = header;
|
||||
this.length = length;
|
||||
this.tag = tag;
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
content(max_length?: number, type?: TagType) { // a preview of the content (intended for humans)
|
||||
if (this.tag === undefined) return null;
|
||||
if (max_length === undefined)
|
||||
max_length = Infinity;
|
||||
|
||||
let content = this.posContent(),
|
||||
len = Math.abs(this.length);
|
||||
|
||||
if (!this.tag.isUniversal()) {
|
||||
if (this.children !== null)
|
||||
return "(" + this.children.length + " elem)";
|
||||
return this.stream.parseOctetString(content, content + len, max_length);
|
||||
}
|
||||
switch (type || this.tag.tagNumber) {
|
||||
case 0x01: // BOOLEAN
|
||||
return (this.stream.get(content) === 0) ? "false" : "true";
|
||||
case 0x02: // INTEGER
|
||||
return this.stream.parseInteger(content, content + len);
|
||||
case 0x03: // BIT_STRING
|
||||
return this.children ? "(" + this.children.length + " elem)" :
|
||||
this.stream.parseBitString(content, content + len, max_length);
|
||||
case 0x04: // OCTET_STRING
|
||||
return this.children ? "(" + this.children.length + " elem)" :
|
||||
this.stream.parseOctetString(content, content + len, max_length);
|
||||
//case 0x05: // NULL
|
||||
case 0x06: // OBJECT_IDENTIFIER
|
||||
return this.stream.parseOID(content, content + len, max_length);
|
||||
//case 0x07: // ObjectDescriptor
|
||||
//case 0x08: // EXTERNAL
|
||||
//case 0x09: // REAL
|
||||
//case 0x0A: // ENUMERATED
|
||||
//case 0x0B: // EMBEDDED_PDV
|
||||
case 0x10: // SEQUENCE
|
||||
case 0x11: // SET
|
||||
if (this.children !== null)
|
||||
return "(" + this.children.length + " elem)";
|
||||
else
|
||||
return "(no elem)";
|
||||
case 0x0C: // UTF8String
|
||||
return string_cut(this.stream.parseStringUTF(content, content + len), max_length);
|
||||
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
|
||||
return string_cut(this.stream.parseStringISO(content, content + len), max_length);
|
||||
case 0x1E: // BMPString
|
||||
return string_cut(this.stream.parseStringBMP(content, content + len), max_length);
|
||||
case 0x17: // UTCTime
|
||||
case 0x18: // GeneralizedTime
|
||||
return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17));
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
typeName(): string {
|
||||
switch (this.tag.tagClass) {
|
||||
case 0: // universal
|
||||
return TagType[this.tag.tagNumber] || ("Universal_" + this.tag.tagNumber.toString());
|
||||
case 1:
|
||||
return "Application_" + this.tag.tagNumber.toString();
|
||||
case 2:
|
||||
return "[" + this.tag.tagNumber.toString() + "]"; // Context
|
||||
case 3:
|
||||
return "Private_" + this.tag.tagNumber.toString();
|
||||
}
|
||||
};
|
||||
|
||||
toString() {
|
||||
return this.typeName() + "@" + this.stream.position + "[header:" + this.header + ",length:" + this.length + ",sub:" + ((this.children === null) ? 'null' : this.children.length) + "]";
|
||||
}
|
||||
|
||||
toPrettyString(indent) {
|
||||
if (indent === undefined) indent = '';
|
||||
let s = indent + this.typeName() + " @" + this.stream.position;
|
||||
if (this.length >= 0)
|
||||
s += "+";
|
||||
s += this.length;
|
||||
if (this.tag.tagConstructed)
|
||||
s += " (constructed)";
|
||||
else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.children !== null))
|
||||
s += " (encapsulates)";
|
||||
let content = this.content();
|
||||
if (content)
|
||||
s += ": " + content.replace(/\n/g, '|');
|
||||
s += "\n";
|
||||
if (this.children !== null) {
|
||||
indent += ' ';
|
||||
for (let i = 0, max = this.children.length; i < max; ++i)
|
||||
s += this.children[i].toPrettyString(indent);
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
posStart() {
|
||||
return this.stream.position;
|
||||
};
|
||||
|
||||
posContent() {
|
||||
return this.stream.position + this.header;
|
||||
};
|
||||
|
||||
posEnd() {
|
||||
return this.stream.position + this.header + Math.abs(this.length);
|
||||
};
|
||||
|
||||
static decodeLength(stream: Stream) {
|
||||
let buf = stream.get();
|
||||
const len = buf & 0x7F;
|
||||
if (len == buf)
|
||||
return len;
|
||||
if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
|
||||
throw "Length over 48 bits not supported at position " + (stream.position - 1);
|
||||
if (len === 0)
|
||||
return null; // undefined
|
||||
|
||||
buf = 0;
|
||||
for (let i = 0; i < len; ++i)
|
||||
buf = (buf << 8) + stream.get();
|
||||
return buf;
|
||||
};
|
||||
|
||||
static encodeLength(buffer: Uint8Array, offset: number, length: number) {
|
||||
if(length < 0x7F) {
|
||||
buffer[offset] = length;
|
||||
} else {
|
||||
buffer[offset] = 0x80;
|
||||
let index = 1;
|
||||
while(length > 0) {
|
||||
buffer[offset + index++] = length & 0xFF;
|
||||
length >>= 8;
|
||||
buffer[offset] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function decode0(stream: Stream) {
|
||||
const streamStart = new Stream(stream, 0); /* copy */
|
||||
const tag = new ASN1Tag(stream);
|
||||
let len = ASN1.decodeLength(stream);
|
||||
const start = stream.position;
|
||||
const length_header = start - streamStart.position;
|
||||
let children = null;
|
||||
const query_children = () => {
|
||||
children = [];
|
||||
if (len !== null) {
|
||||
const end = start + len;
|
||||
if (end > stream.length())
|
||||
throw 'Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream';
|
||||
while (stream.position < end)
|
||||
children[children.length] = decode0(stream);
|
||||
if (stream.position != end)
|
||||
throw 'Content size is not correct for container at offset ' + start;
|
||||
} else {
|
||||
// undefined length
|
||||
try {
|
||||
while (true) {
|
||||
const s = decode0(stream);
|
||||
if (s.tag.isEOC()) break;
|
||||
children[children.length] = s;
|
||||
}
|
||||
len = start - stream.position; // undefined lengths are represented as negative values
|
||||
} catch (e) {
|
||||
throw 'Exception while decoding undefined length content at offset ' + start + ': ' + e;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (tag.tagConstructed) {
|
||||
// must have valid content
|
||||
query_children();
|
||||
} else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) {
|
||||
// sometimes BitString and OctetString are used to encapsulate ASN.1
|
||||
try {
|
||||
if (tag.tagNumber == 0x03)
|
||||
if (stream.get() != 0)
|
||||
throw "BIT STRINGs with unused bits cannot encapsulate.";
|
||||
query_children();
|
||||
for (let i = 0; i < children.length; ++i)
|
||||
if (children[i].tag.isEOC())
|
||||
throw 'EOC is not supposed to be actual content.';
|
||||
} catch (e) {
|
||||
// but silently ignore when they don't
|
||||
children = null;
|
||||
//DEBUG console.log('Could not decode structure at ' + start + ':', e);
|
||||
}
|
||||
}
|
||||
if (children === null) {
|
||||
if (len === null)
|
||||
throw "We can't skip over an invalid tag with undefined length at offset " + start;
|
||||
stream.position = start + Math.abs(len);
|
||||
}
|
||||
return new ASN1(streamStart, length_header, len, tag, children);
|
||||
}
|
||||
|
||||
export function decode(stream: string | ArrayBuffer) {
|
||||
return decode0(new Stream(stream, 0));
|
||||
}
|
||||
}
|
|
@ -392,6 +392,8 @@ namespace sha {
|
|||
return result.buffer;
|
||||
}
|
||||
export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> {
|
||||
if(!(typeof(message) === "string" || message instanceof ArrayBuffer)) throw "Invalid type!";
|
||||
|
||||
let buffer = message instanceof ArrayBuffer ? message : encode_text(message as string);
|
||||
|
||||
if(!crypto || !crypto.subtle || !crypto.subtle.digest || /Edge/.test(navigator.userAgent))
|
||||
|
|
|
@ -312,6 +312,7 @@ const loader_javascript = {
|
|||
|
||||
"js/crypto/sha.js",
|
||||
"js/crypto/hex.js",
|
||||
"js/crypto/asn1.js",
|
||||
|
||||
//load the profiles
|
||||
"js/profiles/ConnectionProfile.js",
|
||||
|
|
|
@ -74,12 +74,12 @@ function setup_jsrender() : boolean {
|
|||
return (Math.round(Math.random() * (min + max + 1) - min)).toString();
|
||||
});
|
||||
|
||||
js_render.views.tags("fmt_date", (...arguments) => {
|
||||
return moment(arguments[0]).format(arguments[1]);
|
||||
js_render.views.tags("fmt_date", (...args) => {
|
||||
return moment(args[0]).format(args[1]);
|
||||
});
|
||||
|
||||
js_render.views.tags("tr", (...arguments) => {
|
||||
return tr(arguments[0]);
|
||||
js_render.views.tags("tr", (...args) => {
|
||||
return tr(args[0]);
|
||||
});
|
||||
|
||||
$(".jsrender-template").each((idx, _entry) => {
|
||||
|
@ -129,11 +129,7 @@ async function initialize() {
|
|||
}
|
||||
|
||||
AudioController.initializeAudioController();
|
||||
if(!profiles.identities.setup_teamspeak()) {
|
||||
console.error(tr("Could not setup the TeamSpeak identity parser!"));
|
||||
return;
|
||||
}
|
||||
profiles.load();
|
||||
await profiles.load();
|
||||
|
||||
try {
|
||||
await ppt.initialize();
|
||||
|
@ -144,6 +140,92 @@ async function initialize() {
|
|||
}
|
||||
}
|
||||
|
||||
function ab2str(buf) {
|
||||
return String.fromCharCode.apply(null, new Uint16Array(buf));
|
||||
}
|
||||
|
||||
function str2ab8(str) {
|
||||
const buf = new ArrayBuffer(str.length);
|
||||
const bufView = new Uint8Array(buf);
|
||||
for (let i = 0, strLen = str.length; i < strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* FIXME Dont use atob, because it sucks for non UTF-8 tings */
|
||||
function arrayBufferBase64(base64: string) {
|
||||
base64 = atob(base64);
|
||||
const buf = new ArrayBuffer(base64.length);
|
||||
const bufView = new Uint8Array(buf);
|
||||
for (let i = 0, strLen = base64.length; i < strLen; i++) {
|
||||
bufView[i] = base64.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
function base64ArrayBuffer(arrayBuffer) {
|
||||
var base64 = ''
|
||||
var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
|
||||
var bytes = new Uint8Array(arrayBuffer)
|
||||
var byteLength = bytes.byteLength
|
||||
var byteRemainder = byteLength % 3
|
||||
var mainLength = byteLength - byteRemainder
|
||||
|
||||
var a, b, c, d
|
||||
var chunk
|
||||
|
||||
// Main loop deals with bytes in chunks of 3
|
||||
for (var i = 0; i < mainLength; i = i + 3) {
|
||||
// Combine the three bytes into a single integer
|
||||
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
|
||||
|
||||
// Use bitmasks to extract 6-bit segments from the triplet
|
||||
a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
|
||||
b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12
|
||||
c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6
|
||||
d = chunk & 63 // 63 = 2^6 - 1
|
||||
|
||||
// Convert the raw binary segments to the appropriate ASCII encoding
|
||||
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
|
||||
}
|
||||
|
||||
// Deal with the remaining bytes and padding
|
||||
if (byteRemainder == 1) {
|
||||
chunk = bytes[mainLength]
|
||||
|
||||
a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
|
||||
|
||||
// Set the 4 least significant bits to zero
|
||||
b = (chunk & 3) << 4 // 3 = 2^2 - 1
|
||||
|
||||
base64 += encodings[a] + encodings[b] + '=='
|
||||
} else if (byteRemainder == 2) {
|
||||
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
|
||||
|
||||
a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
|
||||
b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4
|
||||
|
||||
// Set the 2 least significant bits to zero
|
||||
c = (chunk & 15) << 2 // 15 = 2^4 - 1
|
||||
|
||||
base64 += encodings[a] + encodings[b] + encodings[c] + '='
|
||||
}
|
||||
|
||||
return base64
|
||||
}
|
||||
|
||||
function Base64EncodeUrl(str){
|
||||
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
|
||||
}
|
||||
|
||||
function Base64DecodeUrl(str: string, pad?: boolean){
|
||||
if(typeof(pad) === 'undefined' || pad)
|
||||
str = (str + '===').slice(0, str.length + (str.length % 4));
|
||||
return str.replace(/-/g, '+').replace(/_/g, '/');
|
||||
}
|
||||
|
||||
function main() {
|
||||
//http://localhost:63343/Web-Client/index.php?_ijt=omcpmt8b9hnjlfguh8ajgrgolr&default_connect_url=true&default_connect_type=teamspeak&default_connect_url=localhost%3A9987&disableUnloadDialog=1&loader_ignore_age=1
|
||||
|
||||
|
@ -164,7 +246,9 @@ function main() {
|
|||
|
||||
if(settings.static("connect_default", false) && settings.static("connect_address", "")) {
|
||||
const profile_uuid = settings.static("connect_profile") as string;
|
||||
console.log("UUID: %s", profile_uuid);
|
||||
const profile = profiles.find_profile(profile_uuid) || profiles.default_profile();
|
||||
console.log("UUID: %s", profile.id);
|
||||
const address = settings.static("connect_address", "");
|
||||
const username = settings.static("connect_username", "Another TeaSpeak user");
|
||||
|
||||
|
@ -204,7 +288,7 @@ function main() {
|
|||
|
||||
setup_close();
|
||||
|
||||
let _resize_timeout: NodeJS.Timer;
|
||||
let _resize_timeout: number;
|
||||
$(window).on('resize', () => {
|
||||
if(_resize_timeout)
|
||||
clearTimeout(_resize_timeout);
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace profiles {
|
|||
}
|
||||
}
|
||||
|
||||
function decode_profile(data) : ConnectionProfile | string {
|
||||
async function decode_profile(data) : Promise<ConnectionProfile | string> {
|
||||
data = JSON.parse(data);
|
||||
if(data.version !== 1)
|
||||
return "invalid version";
|
||||
|
@ -87,7 +87,7 @@ namespace profiles {
|
|||
const _data = data.identity_data[key];
|
||||
if(type == undefined) continue;
|
||||
|
||||
const identity = identities.decode_identity(type, _data);
|
||||
const identity = await identities.decode_identity(type, _data);
|
||||
if(identity == undefined) continue;
|
||||
|
||||
result.identities[key.toLowerCase()] = identity;
|
||||
|
@ -103,7 +103,7 @@ namespace profiles {
|
|||
}
|
||||
|
||||
let available_profiles: ConnectionProfile[] = [];
|
||||
export function load() {
|
||||
export async function load() {
|
||||
available_profiles = [];
|
||||
|
||||
const profiles_json = localStorage.getItem("profiles");
|
||||
|
@ -117,7 +117,7 @@ namespace profiles {
|
|||
}
|
||||
if(profiles_data.version == 1) {
|
||||
for(const profile_data of profiles_data.profiles) {
|
||||
const profile = decode_profile(profile_data);
|
||||
const profile = await decode_profile(profile_data);
|
||||
if(typeof(profile) === 'string') {
|
||||
console.error(tr("Failed to load profile. Reason: %s, Profile data: %s"), profile, profiles_data);
|
||||
continue;
|
||||
|
|
|
@ -13,12 +13,12 @@ namespace profiles.identities {
|
|||
valid() : boolean;
|
||||
|
||||
encode?() : string;
|
||||
decode(data: string) : boolean;
|
||||
decode(data: string) : Promise<void>;
|
||||
|
||||
spawn_identity_handshake_handler(connection: ServerConnection) : HandshakeIdentityHandler;
|
||||
}
|
||||
|
||||
export function decode_identity(type: IdentitifyType, data: string) : Identity {
|
||||
export async function decode_identity(type: IdentitifyType, data: string) : Promise<Identity> {
|
||||
let identity: Identity;
|
||||
switch (type) {
|
||||
case IdentitifyType.NICKNAME:
|
||||
|
@ -28,14 +28,19 @@ namespace profiles.identities {
|
|||
identity = new TeaForumIdentity(undefined, undefined);
|
||||
break;
|
||||
case IdentitifyType.TEAMSPEAK:
|
||||
identity = new TeamSpeakIdentity(undefined, undefined);
|
||||
identity = new TeaSpeakIdentity(undefined, undefined);
|
||||
break;
|
||||
}
|
||||
if(!identity)
|
||||
return undefined;
|
||||
|
||||
if(!identity.decode(data))
|
||||
try {
|
||||
await identity.decode(data)
|
||||
} catch(error) {
|
||||
/* todo better error handling! */
|
||||
console.error(error);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
@ -50,7 +55,7 @@ namespace profiles.identities {
|
|||
identity = new TeaForumIdentity(undefined, undefined);
|
||||
break;
|
||||
case IdentitifyType.TEAMSPEAK:
|
||||
identity = new TeamSpeakIdentity(undefined, undefined);
|
||||
identity = new TeaSpeakIdentity(undefined, undefined);
|
||||
break;
|
||||
}
|
||||
return identity;
|
||||
|
|
|
@ -51,13 +51,13 @@ namespace profiles.identities {
|
|||
return this._name != undefined && this._name.length >= 3;
|
||||
}
|
||||
|
||||
decode(data) {
|
||||
decode(data) : Promise<void> {
|
||||
data = JSON.parse(data);
|
||||
if(data.version !== 1)
|
||||
return false;
|
||||
throw "invalid version";
|
||||
|
||||
this._name = data["name"];
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
encode?() : string {
|
||||
|
|
|
@ -61,15 +61,15 @@ namespace profiles.identities {
|
|||
uid() : string { return "TeaForo#" + this.identityData["user_id"]; }
|
||||
type() : IdentitifyType { return IdentitifyType.TEAFORO; }
|
||||
|
||||
decode(data) {
|
||||
decode(data) : Promise<void> {
|
||||
data = JSON.parse(data);
|
||||
if(data.version !== 1)
|
||||
return false;
|
||||
throw "invalid version";
|
||||
|
||||
this.identityDataJson = data["identity_data"];
|
||||
this.identitySign = data["identity_sign"];
|
||||
this.identityData = JSON.parse(this.identityData);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
encode?() : string {
|
||||
|
|
|
@ -1,86 +1,180 @@
|
|||
/// <reference path="../Identity.ts" />
|
||||
|
||||
namespace profiles.identities {
|
||||
export namespace TSIdentityHelper {
|
||||
export let funcationParseIdentity: any;
|
||||
export let funcationParseIdentityByFile: any;
|
||||
export let funcationCalculateSecurityLevel: any;
|
||||
export let functionUid: any;
|
||||
export let funcationExportIdentity: any;
|
||||
export let funcationPublicKey: any;
|
||||
export let funcationSignMessage: any;
|
||||
|
||||
let functionLastError: any;
|
||||
let functionClearLastError: any;
|
||||
|
||||
let functionDestroyString: any;
|
||||
let functionDestroyIdentity: any;
|
||||
|
||||
export function setup() : boolean {
|
||||
functionDestroyString = Module.cwrap("destroy_string", "pointer", []);
|
||||
functionLastError = Module.cwrap("last_error_message", null, ["string"]);
|
||||
funcationParseIdentity = Module.cwrap("parse_identity", "pointer", ["string"]);
|
||||
funcationParseIdentityByFile = Module.cwrap("parse_identity_file", "pointer", ["string"]);
|
||||
functionDestroyIdentity = Module.cwrap("delete_identity", null, ["pointer"]);
|
||||
|
||||
funcationCalculateSecurityLevel = Module.cwrap("identity_security_level", "pointer", ["pointer"]);
|
||||
funcationExportIdentity = Module.cwrap("identity_export", "pointer", ["pointer"]);
|
||||
funcationPublicKey = Module.cwrap("identity_key_public", "pointer", ["pointer"]);
|
||||
funcationSignMessage = Module.cwrap("identity_sign", "pointer", ["pointer", "string", "number"]);
|
||||
functionUid = Module.cwrap("identity_uid", "pointer", ["pointer"]);
|
||||
|
||||
return Module.cwrap("tomcrypt_initialize", "number", [])() == 0;
|
||||
}
|
||||
|
||||
export function last_error() : string {
|
||||
return unwarpString(functionLastError());
|
||||
}
|
||||
|
||||
export function unwarpString(str) : string {
|
||||
if(str == "") return "";
|
||||
try {
|
||||
if(!$.isFunction(window.Pointer_stringify)) {
|
||||
displayCriticalError(tr("Missing required wasm function!<br>Please reload the page!"));
|
||||
export namespace CryptoHelper {
|
||||
export async function export_ecc_key(crypto_key: CryptoKey, public_key: boolean) {
|
||||
/*
|
||||
Tomcrypt public key export:
|
||||
if (type == PK_PRIVATE) {
|
||||
flags[0] = 1;
|
||||
err = der_encode_sequence_multi(out, outlen,
|
||||
LTC_ASN1_BIT_STRING, 1UL, flags,
|
||||
LTC_ASN1_SHORT_INTEGER, 1UL, &key_size,
|
||||
LTC_ASN1_INTEGER, 1UL, key->pubkey.x,
|
||||
LTC_ASN1_INTEGER, 1UL, key->pubkey.y,
|
||||
LTC_ASN1_INTEGER, 1UL, key->k,
|
||||
LTC_ASN1_EOL, 0UL, NULL);
|
||||
} else {
|
||||
flags[0] = 0;
|
||||
err = der_encode_sequence_multi(out, outlen,
|
||||
LTC_ASN1_BIT_STRING, 1UL, flags,
|
||||
LTC_ASN1_SHORT_INTEGER, 1UL, &key_size,
|
||||
LTC_ASN1_INTEGER, 1UL, key->pubkey.x,
|
||||
LTC_ASN1_INTEGER, 1UL, key->pubkey.y,
|
||||
LTC_ASN1_EOL, 0UL, NULL);
|
||||
}
|
||||
let message: string = window.Pointer_stringify(str);
|
||||
functionDestroyString(str);
|
||||
return message;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return "";
|
||||
|
||||
*/
|
||||
|
||||
const key_data = await crypto.subtle.exportKey("jwk", crypto_key);
|
||||
|
||||
let index = 0;
|
||||
const length = public_key ? 79 : 114; /* max lengths! Depends on the padding could be less */
|
||||
const buffer = new Uint8Array(length); /* fixed ASN1 length */
|
||||
{ /* the initial sequence */
|
||||
buffer[index++] = 0x30; /* type */
|
||||
buffer[index++] = 0x00; /* we will set the sequence length later */
|
||||
}
|
||||
}
|
||||
|
||||
export function loadIdentity(key: string) : TeamSpeakIdentity {
|
||||
try {
|
||||
let handle = funcationParseIdentity(key);
|
||||
if(!handle) return undefined;
|
||||
return new TeamSpeakIdentity(handle, "TeaWeb user");
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
{ /* the flags bit string */
|
||||
buffer[index++] = 0x03; /* type */
|
||||
buffer[index++] = 0x02; /* length */
|
||||
buffer[index++] = 0x07; /* data */
|
||||
buffer[index++] = public_key ? 0x00 : 0x80; /* flag 1 or 0 (1 = private key)*/
|
||||
}
|
||||
return undefined;
|
||||
{ /* key size (const 32 for P-256) */
|
||||
buffer[index++] = 0x02; /* type */
|
||||
buffer[index++] = 0x01; /* length */
|
||||
buffer[index++] = 0x20;
|
||||
}
|
||||
{ /* Public kex X */
|
||||
buffer[index++] = 0x02; /* type */
|
||||
buffer[index++] = 0x20; /* length */
|
||||
|
||||
const raw = atob(Base64DecodeUrl(key_data.x, false));
|
||||
if(raw.charCodeAt(0) > 0x7F) {
|
||||
buffer[index - 1] += 1;
|
||||
buffer[index++] = 0;
|
||||
}
|
||||
|
||||
for(let i = 0; i < 32; i++)
|
||||
buffer[index++] = raw.charCodeAt(i);
|
||||
}
|
||||
{ /* Public kex Y */
|
||||
buffer[index++] = 0x02; /* type */
|
||||
buffer[index++] = 0x20; /* length */
|
||||
|
||||
const raw = atob(Base64DecodeUrl(key_data.y, false));
|
||||
if(raw.charCodeAt(0) > 0x7F) {
|
||||
buffer[index - 1] += 1;
|
||||
buffer[index++] = 0;
|
||||
}
|
||||
|
||||
for(let i = 0; i < 32; i++)
|
||||
buffer[index++] = raw.charCodeAt(i);
|
||||
}
|
||||
if(!public_key) { /* Public kex K */
|
||||
buffer[index++] = 0x02; /* type */
|
||||
buffer[index++] = 0x20; /* length */
|
||||
|
||||
const raw = atob(Base64DecodeUrl(key_data.d, false));
|
||||
if(raw.charCodeAt(0) > 0x7F) {
|
||||
buffer[index - 1] += 1;
|
||||
buffer[index++] = 0;
|
||||
}
|
||||
|
||||
for(let i = 0; i < 32; i++)
|
||||
buffer[index++] = raw.charCodeAt(i);
|
||||
}
|
||||
buffer[1] = index - 2; /* set the final sequence length */
|
||||
|
||||
return base64ArrayBuffer(buffer.buffer.slice(0, index));
|
||||
}
|
||||
|
||||
export function loadIdentityFromFileContains(contains: string) : TeamSpeakIdentity {
|
||||
let handle = funcationParseIdentityByFile(contains);
|
||||
if(!handle) return undefined;
|
||||
return new TeamSpeakIdentity(handle, "TeaWeb user");
|
||||
const crypt_key = "b9dfaa7bee6ac57ac7b65f1094a1c155e747327bc2fe5d51c512023fe54a280201004e90ad1daaae1075d53b7d571c30e063b5a62a4a017bb394833aa0983e6e";
|
||||
function c_strlen(buffer: Uint8Array, offset: number) : number {
|
||||
let index = 0;
|
||||
while(index + offset < buffer.length && buffer[index + offset] != 0)
|
||||
index++;
|
||||
return index;
|
||||
}
|
||||
|
||||
export function load_identity(handle: TeamSpeakIdentity, key) : boolean {
|
||||
let native_handle = funcationParseIdentity(key);
|
||||
if(!native_handle) return false;
|
||||
export async function decrypt_ts_identity(buffer: Uint8Array) : Promise<string> {
|
||||
/* buffer could contains a zero! */
|
||||
const hash = new Uint8Array(await sha.sha1(buffer.buffer.slice(20, 20 + c_strlen(buffer, 20))));
|
||||
for(let i = 0; i < 20; i++)
|
||||
buffer[i] ^= hash[i];
|
||||
|
||||
handle["handle"] = native_handle;
|
||||
return true;
|
||||
const length = Math.min(buffer.length, 100);
|
||||
for(let i = 0; i < length; i++)
|
||||
buffer[i] ^= crypt_key.charCodeAt(i);
|
||||
|
||||
return ab2str(buffer);
|
||||
}
|
||||
|
||||
export async function encrypt_ts_identity(buffer: Uint8Array) : Promise<string> {
|
||||
const length = Math.min(buffer.length, 100);
|
||||
for(let i = 0; i < length; i++)
|
||||
buffer[i] ^= crypt_key.charCodeAt(i);
|
||||
|
||||
const hash = new Uint8Array(await sha.sha1(buffer.buffer.slice(20, 20 + c_strlen(buffer, 20))));
|
||||
for(let i = 0; i < 20; i++)
|
||||
buffer[i] ^= hash[i];
|
||||
|
||||
return base64ArrayBuffer(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param buffer base64 encoded ASN.1 string
|
||||
*/
|
||||
export function decode_tomcrypt_key(buffer: string) {
|
||||
const decoded = asn1.decode(atob(buffer));
|
||||
|
||||
let {x, y, k} = {
|
||||
x: decoded.children[2].content(Infinity, asn1.TagType.VisibleString),
|
||||
y: decoded.children[3].content(Infinity, asn1.TagType.VisibleString),
|
||||
k: decoded.children[4].content(Infinity, asn1.TagType.VisibleString)
|
||||
};
|
||||
|
||||
if(x.length > 32) {
|
||||
if(x.charCodeAt(0) != 0)
|
||||
throw "Invalid X coordinate! (Too long)";
|
||||
x = x.substr(1);
|
||||
}
|
||||
|
||||
if(y.length > 32) {
|
||||
if(y.charCodeAt(0) != 0)
|
||||
throw "Invalid Y coordinate! (Too long)";
|
||||
y = y.substr(1);
|
||||
}
|
||||
|
||||
if(k.length > 32) {
|
||||
if(k.charCodeAt(0) != 0)
|
||||
throw "Invalid private coordinate! (Too long)";
|
||||
k = k.substr(1);
|
||||
}
|
||||
|
||||
/*
|
||||
console.log("Key x: %s (%d)", btoa(x), x.length);
|
||||
console.log("Key y: %s (%d)", btoa(y), y.length);
|
||||
console.log("Key k: %s (%d)", btoa(k), k.length);
|
||||
*/
|
||||
return {
|
||||
crv: "P-256",
|
||||
d: Base64EncodeUrl(btoa(k)),
|
||||
x: Base64EncodeUrl(btoa(x)),
|
||||
y: Base64EncodeUrl(btoa(y)),
|
||||
|
||||
ext: true,
|
||||
key_ops:["deriveKey", "sign"],
|
||||
kty:"EC",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TeamSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||
identity: TeamSpeakIdentity;
|
||||
class TeaSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||
identity: TeaSpeakIdentity;
|
||||
|
||||
constructor(connection: ServerConnection, identity: TeamSpeakIdentity) {
|
||||
constructor(connection: ServerConnection, identity: TeaSpeakIdentity) {
|
||||
super(connection);
|
||||
this.identity = identity;
|
||||
}
|
||||
|
@ -91,7 +185,7 @@ namespace profiles.identities {
|
|||
this.connection.sendCommand("handshakebegin", {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type(),
|
||||
publicKey: this.identity.publicKey()
|
||||
publicKey: this.identity.public_key
|
||||
}).catch(error => {
|
||||
console.error(tr("Failed to initialize TeamSpeak based handshake. Error: %o"), error);
|
||||
|
||||
|
@ -102,83 +196,590 @@ namespace profiles.identities {
|
|||
}
|
||||
|
||||
private handle_proof(json) {
|
||||
const proof = this.identity.signMessage(json[0]["message"]);
|
||||
if(!json[0]["digest"]) {
|
||||
this.trigger_fail("server too old");
|
||||
return;
|
||||
}
|
||||
|
||||
this.connection.sendCommand("handshakeindentityproof", {proof: proof}).catch(error => {
|
||||
console.error(tr("Failed to proof the identity. Error: %o"), error);
|
||||
this.identity.sign_message(json[0]["message"], json[0]["digest"]).then(proof => {
|
||||
this.connection.sendCommand("handshakeindentityproof", {proof: proof}).catch(error => {
|
||||
console.error(tr("Failed to proof the identity. Error: %o"), error);
|
||||
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute proof (" + error + ")");
|
||||
}).then(() => this.trigger_success());
|
||||
if(error instanceof CommandResult)
|
||||
error = error.extra_message || error.message;
|
||||
this.trigger_fail("failed to execute proof (" + error + ")");
|
||||
}).then(() => this.trigger_success());
|
||||
}).catch(error => {
|
||||
this.trigger_fail("failed to sign message");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class TeamSpeakIdentity implements Identity {
|
||||
private handle: any;
|
||||
private _name: string;
|
||||
class IdentityPOWWorker {
|
||||
private _worker: Worker;
|
||||
private _current_hash: string;
|
||||
private _best_level: number;
|
||||
|
||||
constructor(handle: any, name: string) {
|
||||
this.handle = handle;
|
||||
async initialize(key: string) {
|
||||
this._worker = new Worker(settings.static("worker_directory", "js/workers/") + "WorkerPOW.js");
|
||||
|
||||
/* initialize */
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout_id = setTimeout(() => reject("timeout"), 1000);
|
||||
|
||||
this._worker.onmessage = event => {
|
||||
clearTimeout(timeout_id);
|
||||
|
||||
if(!event.data) {
|
||||
reject("invalid data");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!event.data.success) {
|
||||
reject("initialize failed (" + event.data.success + " | " + (event.data.message || "unknown eroror") + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
this._worker.onmessage = event => this.handle_message(event.data);
|
||||
resolve();
|
||||
};
|
||||
this._worker.onerror = event => {
|
||||
console.error("POW Worker error %o", event);
|
||||
clearTimeout(timeout_id);
|
||||
reject("Failed to load worker (" + event.message + ")");
|
||||
};
|
||||
});
|
||||
|
||||
/* set data */
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
this._worker.postMessage({
|
||||
type: "set_data",
|
||||
private_key: key,
|
||||
code: "set_data"
|
||||
});
|
||||
|
||||
const timeout_id = setTimeout(() => reject("timeout (data)"), 1000);
|
||||
|
||||
this._worker.onmessage = event => {
|
||||
clearTimeout(timeout_id);
|
||||
|
||||
if (!event.data) {
|
||||
reject("invalid data");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.data.success) {
|
||||
reject("initialize of data failed (" + event.data.success + " | " + (event.data.message || "unknown eroror") + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
this._worker.onmessage = event => this.handle_message(event.data);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async mine(hash: string, iterations: number, target: number, timeout?: number) : Promise<Boolean> {
|
||||
this._current_hash = hash;
|
||||
if(target < this._best_level)
|
||||
return true;
|
||||
|
||||
return await new Promise<Boolean>((resolve, reject) => {
|
||||
this._worker.postMessage({
|
||||
type: "mine",
|
||||
hash: this._current_hash,
|
||||
iterations: iterations,
|
||||
target: target,
|
||||
code: "mine"
|
||||
});
|
||||
|
||||
const timeout_id = setTimeout(() => reject("timeout (mine)"), timeout || 5000);
|
||||
|
||||
this._worker.onmessage = event => {
|
||||
this._worker.onmessage = event => this.handle_message(event.data);
|
||||
|
||||
clearTimeout(timeout_id);
|
||||
if (!event.data) {
|
||||
reject("invalid data");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.data.success) {
|
||||
reject("mining failed (" + event.data.success + " | " + (event.data.message || "unknown eroror") + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
if(event.data.result) {
|
||||
this._best_level = event.data.level;
|
||||
this._current_hash = event.data.hash;
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false); /* no result */
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
current_hash() : string {
|
||||
return this._current_hash;
|
||||
}
|
||||
|
||||
current_level() : number {
|
||||
return this._best_level;
|
||||
}
|
||||
|
||||
async finalize(timeout?: number) {
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
this._worker.postMessage({
|
||||
type: "finalize",
|
||||
code: "finalize"
|
||||
});
|
||||
|
||||
const timeout_id = setTimeout(() => reject("timeout"), timeout || 250);
|
||||
|
||||
this._worker.onmessage = event => {
|
||||
this._worker.onmessage = event => this.handle_message(event.data);
|
||||
|
||||
clearTimeout(timeout_id);
|
||||
|
||||
if (!event.data) {
|
||||
reject("invalid data");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.data.success) {
|
||||
reject("failed to finalize (" + event.data.success + " | " + (event.data.message || "unknown eroror") + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
} catch(error) {
|
||||
console.warn("Failed to finalize POW worker! (%o)", error);
|
||||
}
|
||||
|
||||
this._worker.terminate();
|
||||
this._worker = undefined;
|
||||
}
|
||||
|
||||
private handle_message(message: any) {
|
||||
console.log("Received message: %o", message);
|
||||
}
|
||||
}
|
||||
|
||||
export class TeaSpeakIdentity implements Identity {
|
||||
static async generate_new() : Promise<TeaSpeakIdentity> {
|
||||
const key = await crypto.subtle.generateKey({name:'ECDH', namedCurve: 'P-256'}, true, ["deriveKey"]);
|
||||
const private_key = await CryptoHelper.export_ecc_key(key.privateKey, false);
|
||||
console.log("Private key: %s (%d)", private_key, atob(private_key).length);
|
||||
const identity = new TeaSpeakIdentity(private_key, "0", undefined, false);
|
||||
await identity.initialize();
|
||||
return identity;
|
||||
}
|
||||
|
||||
static async import_ts(ts_string: string, ini?: boolean) : Promise<TeaSpeakIdentity> {
|
||||
const parse_string = string => {
|
||||
/* parsing without INI structure */
|
||||
const V_index = string.indexOf('V');
|
||||
if(V_index == -1) throw "invalid input (missing V)";
|
||||
|
||||
return {
|
||||
hash: string.substr(0, V_index),
|
||||
data: string.substr(V_index + 1),
|
||||
name: "TeaSpeak user"
|
||||
}
|
||||
};
|
||||
|
||||
const {hash, data, name} = (!ini ? () => parse_string(ts_string) : () => {
|
||||
/* parsing with INI structure */
|
||||
let identity: string, name: string;
|
||||
|
||||
for(const line of ts_string.split("\n")) {
|
||||
if(line.startsWith("identity="))
|
||||
identity = line.substr(9);
|
||||
else if(line.startsWith("nickname="))
|
||||
name = line.substr(9);
|
||||
}
|
||||
|
||||
if(!identity) throw "missing identity keyword";
|
||||
if(identity[0] == "\"" && identity[identity.length - 1] == "\"")
|
||||
identity = identity.substr(1, identity.length - 1);
|
||||
|
||||
const result = parse_string(identity);
|
||||
result.name = name || result.name;
|
||||
return result;
|
||||
})();
|
||||
|
||||
if(!ts_string.match(/[0-9]+/g)) throw "invalid hash!";
|
||||
|
||||
const key64 = await CryptoHelper.decrypt_ts_identity(new Uint8Array(arrayBufferBase64(data)));
|
||||
|
||||
const identity = new TeaSpeakIdentity(key64, hash, name, false);
|
||||
await identity.initialize();
|
||||
return identity;
|
||||
}
|
||||
|
||||
hash_number: string; /* hash suffix for the private key */
|
||||
private_key: string; /* base64 representation of the private key */
|
||||
_name: string;
|
||||
|
||||
public_key: string; /* only set when initialized */
|
||||
|
||||
private _initialized: boolean;
|
||||
private _crypto_key: CryptoKey;
|
||||
private _crypto_key_sign: CryptoKey;
|
||||
|
||||
private _unique_id: string;
|
||||
|
||||
constructor(private_key?: string, hash?: string, name?: string, initialize?: boolean) {
|
||||
this.private_key = private_key;
|
||||
this.hash_number = hash || "0";
|
||||
this._name = name;
|
||||
|
||||
if(this.private_key && (typeof(initialize) === "undefined" || initialize)) {
|
||||
this.initialize().catch(error => {
|
||||
console.error("Failed to initialize TeaSpeakIdentity (%s)", error);
|
||||
this._initialized = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
securityLevel() : number | undefined {
|
||||
return parseInt(TSIdentityHelper.unwarpString(TSIdentityHelper.funcationCalculateSecurityLevel(this.handle)));
|
||||
name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
name() : string { return this._name; }
|
||||
|
||||
uid() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.functionUid(this.handle));
|
||||
uid(): string {
|
||||
return this._unique_id;
|
||||
}
|
||||
|
||||
type() : IdentitifyType { return IdentitifyType.TEAMSPEAK; }
|
||||
|
||||
signMessage(message: string): string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationSignMessage(this.handle, message, message.length));
|
||||
type(): IdentitifyType {
|
||||
return IdentitifyType.TEAMSPEAK;
|
||||
}
|
||||
|
||||
exported() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationExportIdentity(this.handle));
|
||||
valid(): boolean {
|
||||
return this._initialized && !!this._crypto_key && !!this._crypto_key_sign;
|
||||
}
|
||||
|
||||
publicKey() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationPublicKey(this.handle));
|
||||
}
|
||||
async decode(data: string) : Promise<void> {
|
||||
const json = JSON.parse(data);
|
||||
if(!json) throw "invalid json";
|
||||
|
||||
valid() : boolean { return this.handle !== undefined; }
|
||||
if(json.version == 2) {
|
||||
this.private_key = json.key;
|
||||
this.hash_number = json.hash;
|
||||
this._name = json.name;
|
||||
} else if(json.version == 1) {
|
||||
const key = json.key;
|
||||
this._name = json.name;
|
||||
|
||||
decode(data) : boolean {
|
||||
data = JSON.parse(data);
|
||||
if(data.version != 1) return false;
|
||||
const clone = await TeaSpeakIdentity.import_ts(key, false);
|
||||
this.private_key = clone.private_key;
|
||||
this.hash_number = clone.hash_number;
|
||||
} else
|
||||
throw "invalid version";
|
||||
|
||||
if(!TSIdentityHelper.load_identity(this, data["key"]))
|
||||
return false;
|
||||
this._name = data["name"];
|
||||
return true;
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
encode?() : string {
|
||||
if(!this.handle) return undefined;
|
||||
|
||||
const key = this.exported();
|
||||
if(!key) return undefined;
|
||||
|
||||
return JSON.stringify({
|
||||
key: key,
|
||||
key: this.private_key,
|
||||
hash: this.hash_number,
|
||||
name: this._name,
|
||||
version: 1
|
||||
})
|
||||
version: 2
|
||||
});
|
||||
}
|
||||
|
||||
async level() : Promise<number> {
|
||||
if(!this._initialized || !this.public_key)
|
||||
throw "not initialized";
|
||||
|
||||
const hash = new Uint8Array(await sha.sha1(this.public_key + this.hash_number));
|
||||
|
||||
let level = 0;
|
||||
while(level < hash.byteLength && hash[level] == 0)
|
||||
level++;
|
||||
|
||||
if(level >= hash.byteLength) {
|
||||
level = 256;
|
||||
} else {
|
||||
let byte = hash[level];
|
||||
level <<= 3;
|
||||
while((byte & 0x1) == 0) {
|
||||
level++;
|
||||
byte >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} a
|
||||
* @param {string} b
|
||||
* @description b must be smaller (in bytes) then a
|
||||
*/
|
||||
private string_add(a: string, b: string) {
|
||||
const char_result: number[] = [];
|
||||
const char_a = [...a].reverse().map(e => e.charCodeAt(0));
|
||||
const char_b = [...b].reverse().map(e => e.charCodeAt(0));
|
||||
|
||||
let carry = false;
|
||||
while(char_b.length > 0) {
|
||||
let result = char_b.pop_front() + char_a.pop_front() + (carry ? 1 : 0) - 48;
|
||||
if((carry = result > 57))
|
||||
result -= 10;
|
||||
char_result.push(result);
|
||||
}
|
||||
|
||||
while(char_a.length > 0) {
|
||||
let result = char_a.pop_front() + (carry ? 1 : 0);
|
||||
if((carry = result > 57))
|
||||
result -= 10;
|
||||
char_result.push(result);
|
||||
}
|
||||
|
||||
if(carry)
|
||||
char_result.push(49);
|
||||
|
||||
return String.fromCharCode.apply(null, char_result.reverse());
|
||||
}
|
||||
|
||||
|
||||
spawn_identity_handshake_handler(connection: ServerConnection) : HandshakeIdentityHandler {
|
||||
return new TeamSpeakHandshakeHandler(connection, this);
|
||||
}
|
||||
}
|
||||
async improve_level_for(time: number, threads: number) : Promise<Boolean> {
|
||||
let active = true;
|
||||
setTimeout(() => active = false, time);
|
||||
|
||||
export function setup_teamspeak() : boolean {
|
||||
return TSIdentityHelper.setup();
|
||||
return await this.improve_level(-1, threads, () => active);
|
||||
}
|
||||
|
||||
async improve_level(target: number, threads: number, active_callback: () => boolean) : Promise<Boolean> {
|
||||
if(!this._initialized || !this.public_key)
|
||||
throw "not initialized";
|
||||
if(target == -1) /* get the highest level possible */
|
||||
target = 0;
|
||||
else if(target <= await this.level())
|
||||
return true;
|
||||
|
||||
const workers: IdentityPOWWorker[] = [];
|
||||
|
||||
const iterations = 100000;
|
||||
let current_hash;
|
||||
const next_hash = () => {
|
||||
if(!current_hash)
|
||||
return (current_hash = this.hash_number);
|
||||
|
||||
if(current_hash.length < iterations.toString().length) {
|
||||
current_hash = this.string_add(iterations.toString(), current_hash);
|
||||
} else {
|
||||
current_hash = this.string_add(current_hash, iterations.toString());
|
||||
}
|
||||
return current_hash;
|
||||
};
|
||||
|
||||
{ /* init */
|
||||
const initialize_promise: Promise<void>[] = [];
|
||||
for(let index = 0; index < threads; index++) {
|
||||
const worker = new IdentityPOWWorker();
|
||||
workers.push(worker);
|
||||
initialize_promise.push(worker.initialize(this.public_key));
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(initialize_promise);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
throw "failed to initialize";
|
||||
}
|
||||
}
|
||||
|
||||
let result = false;
|
||||
let best_level = 0;
|
||||
let target_level = target > 0 ? target : await this.level() + 1;
|
||||
|
||||
const worker_promise: Promise<void>[] = [];
|
||||
try {
|
||||
result = await new Promise<boolean>((resolve, reject) => {
|
||||
let active = true;
|
||||
|
||||
const exit = () => {
|
||||
const timeout = setTimeout(() => resolve(true), 1000);
|
||||
Promise.all(worker_promise).then(result => {
|
||||
clearTimeout(timeout);
|
||||
resolve(true);
|
||||
}).catch(error => resolve(true));
|
||||
active = false;
|
||||
};
|
||||
|
||||
for(const worker of workers) {
|
||||
const worker_mine = () => {
|
||||
if(!active) return;
|
||||
|
||||
const promise = worker.mine(next_hash(), iterations, target_level);
|
||||
const p = promise.then(result => {
|
||||
worker_promise.remove(p);
|
||||
|
||||
if(result.valueOf()) {
|
||||
if(worker.current_level() > best_level) {
|
||||
this.hash_number = worker.current_hash();
|
||||
|
||||
console.log("Found new best at %s (%d). Old was %d", this.hash_number, worker.current_level(), best_level);
|
||||
best_level = worker.current_level();
|
||||
}
|
||||
|
||||
if(active) {
|
||||
if(target > 0)
|
||||
exit();
|
||||
else
|
||||
target_level = best_level + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(active && (active = active_callback()))
|
||||
setTimeout(() => worker_mine(), 0);
|
||||
else {
|
||||
exit();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}).catch(error => {
|
||||
worker_promise.remove(p);
|
||||
|
||||
console.warn("POW worker error %o", error);
|
||||
reject(error);
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
worker_promise.push(p);
|
||||
};
|
||||
|
||||
worker_mine();
|
||||
}
|
||||
});
|
||||
} catch(error) {
|
||||
//error already printed before reject had been called
|
||||
}
|
||||
|
||||
{ /* shutdown */
|
||||
const finalize_promise: Promise<void>[] = [];
|
||||
for(const worker of workers)
|
||||
finalize_promise.push(worker.finalize(250));
|
||||
|
||||
try {
|
||||
await Promise.all(finalize_promise);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
throw "failed to finalize";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async initialize() {
|
||||
if(!this.private_key)
|
||||
throw "Invalid private key";
|
||||
|
||||
let jwk: any;
|
||||
try {
|
||||
jwk = await CryptoHelper.decode_tomcrypt_key(this.private_key);
|
||||
if(!jwk)
|
||||
throw "result undefined";
|
||||
} catch(error) {
|
||||
throw "failed to parse key (" + error + ")";
|
||||
}
|
||||
|
||||
try {
|
||||
this._crypto_key_sign = await crypto.subtle.importKey("jwk", jwk, {name:'ECDSA', namedCurve: 'P-256'}, false, ["sign"]);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
throw "failed to create crypto sign key";
|
||||
}
|
||||
|
||||
try {
|
||||
this._crypto_key = await crypto.subtle.importKey("jwk", jwk, {name:'ECDH', namedCurve: 'P-256'}, true, ["deriveKey"]);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
throw "failed to create crypto key";
|
||||
}
|
||||
|
||||
try {
|
||||
this.public_key = await CryptoHelper.export_ecc_key(this._crypto_key, true);
|
||||
this._unique_id = base64ArrayBuffer(await sha.sha1(this.public_key));
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
throw "failed to calculate unique id";
|
||||
}
|
||||
|
||||
this._initialized = true;
|
||||
//const public_key = await profiles.identities.CryptoHelper.export_ecc_key(key, true);
|
||||
}
|
||||
|
||||
async export_ts(ini?: boolean) : Promise<string> {
|
||||
if(!this.private_key)
|
||||
throw "Invalid private key";
|
||||
|
||||
const identity = this.hash_number + "V" + await CryptoHelper.encrypt_ts_identity(new Uint8Array(str2ab8(this.private_key)));
|
||||
if(!ini) return identity;
|
||||
|
||||
return "[Identity]\n" +
|
||||
"id=TeaWeb-Exported\n" +
|
||||
"identity=\"" + identity + "\"\n" +
|
||||
"nickname=\"" + this.name() + "\"\n" +
|
||||
"phonetic_nickname=";
|
||||
}
|
||||
|
||||
async sign_message(message: string, hash: string = "SHA-256") : Promise<string> {
|
||||
/* bring this to libtomcrypt format */
|
||||
const sign_buffer = await crypto.subtle.sign({
|
||||
name: "ECDSA",
|
||||
hash: hash
|
||||
}, this._crypto_key_sign, str2ab8(message));
|
||||
const sign = new Uint8Array(sign_buffer);
|
||||
/* first 32 r bits | last 32 s bits */
|
||||
|
||||
const buffer = new Uint8Array(72);
|
||||
let index = 0;
|
||||
|
||||
{ /* the initial sequence */
|
||||
buffer[index++] = 0x30; /* type */
|
||||
buffer[index++] = 0x00; /* we will set the sequence length later */
|
||||
}
|
||||
{ /* integer r */
|
||||
buffer[index++] = 0x02; /* type */
|
||||
buffer[index++] = 0x20; /* length */
|
||||
|
||||
if(sign[0] > 0x7F) {
|
||||
buffer[index - 1] += 1;
|
||||
buffer[index++] = 0;
|
||||
}
|
||||
|
||||
for(let i = 0; i < 32; i++)
|
||||
buffer[index++] = sign[i];
|
||||
}
|
||||
{ /* integer s */
|
||||
buffer[index++] = 0x02; /* type */
|
||||
buffer[index++] = 0x20; /* length */
|
||||
|
||||
if(sign[32] > 0x7F) {
|
||||
buffer[index - 1] += 1;
|
||||
buffer[index++] = 0;
|
||||
}
|
||||
|
||||
for(let i = 0; i < 32; i++)
|
||||
buffer[index++] = sign[32 + i];
|
||||
}
|
||||
buffer[1] = index - 2;
|
||||
|
||||
return base64ArrayBuffer(buffer.subarray(0, index));
|
||||
}
|
||||
|
||||
spawn_identity_handshake_handler(connection: ServerConnection): HandshakeIdentityHandler {
|
||||
return new TeaSpeakHandshakeHandler(connection, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -93,7 +93,7 @@ namespace Modals {
|
|||
select_invalid_tag.hide();
|
||||
updateFields();
|
||||
});
|
||||
select_tag.val('default').trigger('change');
|
||||
select_tag.val(connect_profile && connect_profile.enforce ? connect_profile.profile.id : connect_profile && connect_profile.profile ? connect_profile.profile.id : 'default').trigger('change');
|
||||
}
|
||||
|
||||
tag.find(".connect_nickname").on("keyup", () => updateFields());
|
||||
|
|
|
@ -496,10 +496,9 @@ namespace Modals {
|
|||
} else if(selected_type == IdentitifyType.TEAMSPEAK) {
|
||||
console.log("Set: " + identity);
|
||||
const teamspeak_tag = tag = settings_tag.find(".identity-settings-teamspeak");
|
||||
teamspeak_tag.find(".identity_string").val("");
|
||||
if(identity)
|
||||
teamspeak_tag.find(".identity_string").val((identity as profiles.identities.TeamSpeakIdentity).exported());
|
||||
else
|
||||
teamspeak_tag.find(".identity_string").val("");
|
||||
(identity as profiles.identities.TeaSpeakIdentity).export_ts().then(e => teamspeak_tag.find(".identity_string").val(e));
|
||||
} else if(selected_type == IdentitifyType.NICKNAME) {
|
||||
const name_tag = tag = settings_tag.find(".identity-settings-nickname");
|
||||
if(identity)
|
||||
|
@ -571,16 +570,16 @@ namespace Modals {
|
|||
const element = event.target as HTMLInputElement;
|
||||
const file_reader = new FileReader();
|
||||
file_reader.onload = function() {
|
||||
const identity = profiles.identities.TSIdentityHelper.loadIdentityFromFileContains(file_reader.result as string);
|
||||
if(!identity) {
|
||||
display_error(tr("Failed to parse identity.<br>Reason: ") + profiles.identities.TSIdentityHelper.last_error());
|
||||
return;
|
||||
} else {
|
||||
teamspeak_tag.find(".identity_string").val(identity.exported());
|
||||
const identity_promise = profiles.identities.TeaSpeakIdentity.import_ts(file_reader.result, true);
|
||||
identity_promise.then(identity => {
|
||||
(identity as profiles.identities.TeaSpeakIdentity).export_ts().then(e => teamspeak_tag.find(".identity_string").val(e));
|
||||
selected_profile.set_identity(IdentitifyType.TEAMSPEAK, identity as any);
|
||||
profiles.mark_need_save();
|
||||
display_error(undefined);
|
||||
}
|
||||
}).catch(error => {
|
||||
display_error(tr("Failed to parse identity.<br>Reason: ") + error);
|
||||
return;
|
||||
});
|
||||
};
|
||||
|
||||
file_reader.onerror = ev => {
|
||||
|
@ -602,18 +601,16 @@ namespace Modals {
|
|||
selected_profile.set_identity(IdentitifyType.TEAMSPEAK, undefined as any);
|
||||
profiles.mark_need_save();
|
||||
} else {
|
||||
const identity = profiles.identities.TSIdentityHelper.loadIdentity(element.value);
|
||||
if(!identity) {
|
||||
const identity_promise = profiles.identities.TeaSpeakIdentity.import_ts(element.value, false);
|
||||
identity_promise.then(identity => {
|
||||
(identity as profiles.identities.TeaSpeakIdentity).export_ts().then(e => teamspeak_tag.find(".identity_string").val(e));
|
||||
selected_profile.set_identity(IdentitifyType.TEAMSPEAK, identity as any);
|
||||
profiles.mark_need_save();
|
||||
|
||||
display_error("Failed to parse identity string!");
|
||||
display_error(undefined);
|
||||
}).catch(error => {
|
||||
display_error(tr("Failed to parse identity.<br>Reason: ") + error);
|
||||
return;
|
||||
}
|
||||
|
||||
selected_profile.set_identity(IdentitifyType.TEAMSPEAK, identity as any);
|
||||
profiles.mark_need_save();
|
||||
display_error(undefined);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -331,6 +331,8 @@ interface PPTKeySettings extends ppt.KeyDescriptor{
|
|||
class PushToTalkVAD extends VoiceActivityDetector {
|
||||
private _key: ppt.KeyDescriptor;
|
||||
private _key_hook: ppt.KeyHook;
|
||||
private _timeout: NodeJS.Timer;
|
||||
private _delay = /* 300 */ 0; //TODO configurable
|
||||
|
||||
private _pushed: boolean = false;
|
||||
|
||||
|
@ -338,8 +340,21 @@ class PushToTalkVAD extends VoiceActivityDetector {
|
|||
super();
|
||||
this._key = key;
|
||||
this._key_hook = {
|
||||
callback_release: () => this._pushed = false,
|
||||
callback_press: () => this._pushed = true,
|
||||
callback_release: () => {
|
||||
if(this._timeout)
|
||||
clearTimeout(this._timeout);
|
||||
|
||||
if(this._delay > 0)
|
||||
this._timeout = setTimeout(() => this._pushed = false, this._delay);
|
||||
else
|
||||
this._pushed = false;
|
||||
},
|
||||
callback_press: () => {
|
||||
if(this._timeout)
|
||||
clearTimeout(this._timeout);
|
||||
|
||||
this._pushed = true;
|
||||
},
|
||||
|
||||
cancel: false
|
||||
} as ppt.KeyHook;
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
declare namespace WebAssembly {
|
||||
export function instantiateStreaming(stream: Promise<Response>, imports?: any) : Promise<ResultObject>;
|
||||
}
|
||||
|
||||
const prefix = "[POWWorker] ";
|
||||
|
||||
let initialized = false;
|
||||
|
||||
let memory: WebAssembly.Memory;
|
||||
let memory_u8: Uint8Array;
|
||||
let wasm_object: WebAssembly.ResultObject;
|
||||
|
||||
function post_status(code: string | undefined, result: boolean | string | any) {
|
||||
let data: any = {};
|
||||
data.code = code;
|
||||
if(typeof(result) === "string") {
|
||||
data.success = false;
|
||||
data.message = result;
|
||||
} else if(typeof(result) === "boolean") {
|
||||
data.success = result;
|
||||
} else {
|
||||
data.success = true;
|
||||
Object.assign(data, result);
|
||||
}
|
||||
|
||||
postMessage(data);
|
||||
}
|
||||
|
||||
{ /* initialize WASM handle */
|
||||
memory = new WebAssembly.Memory({ initial: 1 });
|
||||
memory_u8 = new Uint8Array(memory.buffer);
|
||||
|
||||
if(typeof(WebAssembly.instantiateStreaming) === "undefined") {
|
||||
WebAssembly.instantiateStreaming = async (stream: Promise<Response>, imports?: any) => {
|
||||
const response = await stream;
|
||||
const buffer = await response.arrayBuffer();
|
||||
return WebAssembly.instantiate(buffer, imports);
|
||||
}
|
||||
}
|
||||
|
||||
WebAssembly.instantiateStreaming(fetch('../../wat/pow/sha1.wasm'), {
|
||||
env: {
|
||||
memory: memory
|
||||
}
|
||||
}).then(object => {
|
||||
wasm_object = object;
|
||||
|
||||
post_status("initialize", true);
|
||||
}).catch(error => {
|
||||
post_status("initialize", "failed to initialize WASM handle (" + error + ")");
|
||||
});
|
||||
}
|
||||
|
||||
let key_offset = 0;
|
||||
let hash_offset = 0;
|
||||
onmessage = function(e: MessageEvent) {
|
||||
let data = e.data;
|
||||
|
||||
if(data.type == "set_data") {
|
||||
const key = data.private_key;
|
||||
|
||||
key_offset = 0;
|
||||
for(const char of key)
|
||||
memory_u8[0x0A0 + key_offset++] = char.charCodeAt(0);
|
||||
|
||||
post_status(data.code, true);
|
||||
} else if(data.type == "mine") {
|
||||
let hash: string = data.hash;
|
||||
const iterations: number = data.iterations;
|
||||
const target: number = data.target;
|
||||
|
||||
hash_offset = 0;
|
||||
for(const char of hash) {
|
||||
memory_u8[0x0A0 + key_offset + hash_offset++] = char.charCodeAt(0);
|
||||
}
|
||||
|
||||
let level = wasm_object.instance.exports.mine(key_offset, hash_offset, iterations, target > 1 ? target - 1 : target);
|
||||
hash = "";
|
||||
|
||||
hash_offset = 0;
|
||||
while(memory_u8[0x0A0 + key_offset + hash_offset] != 0)
|
||||
hash = hash + String.fromCharCode(memory_u8[0x0A0 + key_offset + hash_offset++]);
|
||||
|
||||
// console.log(prefix + "New hash: %s, level %o", hash, level);
|
||||
post_status(data.code, {
|
||||
result: level >= target,
|
||||
hash: hash,
|
||||
level: level
|
||||
});
|
||||
} else if(data.type == "finalize") {
|
||||
wasm_object = undefined;
|
||||
memory = undefined;
|
||||
memory_u8 = undefined;
|
||||
|
||||
post_status(data.code, true);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "none",
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"outFile": "WorkerPOW.js"
|
||||
},
|
||||
"files": [
|
||||
"pow/POWWorker.ts",
|
||||
]
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,490 @@
|
|||
;; SHA-1 code from https://github.com/Snack-X/wasm-works/blob/master/modules/sha1.wat by Snack-X
|
||||
|
||||
(module
|
||||
;; import 1 page of memory from env.memory
|
||||
;; [0x000;0x03f] will be used as input chunk
|
||||
;; [0x040;0x053] will be used as output value
|
||||
;; [0x0A0;0x1FF] base64 memory
|
||||
(import "env" "memory" (memory 1))
|
||||
|
||||
;; functions to export
|
||||
(export "mine" (func $mine))
|
||||
|
||||
;; global variables
|
||||
(global $message_len (mut i32) (i32.const 0))
|
||||
(global $h0 (mut i32) (i32.const 0))
|
||||
(global $h1 (mut i32) (i32.const 0))
|
||||
(global $h2 (mut i32) (i32.const 0))
|
||||
(global $h3 (mut i32) (i32.const 0))
|
||||
(global $h4 (mut i32) (i32.const 0))
|
||||
|
||||
;; helper function `get_word`
|
||||
;; input - word index
|
||||
;; output - offset
|
||||
(func $get_word (param $w i32) (result i32)
|
||||
;; offset = ($w & 0xf) * 4
|
||||
(return (call $flip_endian (i32.load
|
||||
(i32.mul
|
||||
(i32.and (get_local $w) (i32.const 0xf))
|
||||
(i32.const 4)
|
||||
)
|
||||
)))
|
||||
)
|
||||
|
||||
;; helper function `set_word`
|
||||
;; input - word index, value
|
||||
(func $set_word (param $w i32) (param $v i32)
|
||||
;; offset = ($w & 0xf) * 4
|
||||
(i32.store (call $flip_endian
|
||||
(i32.mul
|
||||
(i32.and (get_local $w) (i32.const 0xf))
|
||||
(i32.const 4)
|
||||
)
|
||||
(get_local $v)
|
||||
))
|
||||
)
|
||||
|
||||
;; helper function `flip_endian`
|
||||
;; once `i32.bswap` is landed, this function is useless
|
||||
(func $flip_endian (param $w i32) (result i32)
|
||||
;; (w & 0xff000000 >>> 24) |
|
||||
;; (w & 0x00ff0000 >>> 8) |
|
||||
;; (w & 0x0000ff00 << 8) |
|
||||
;; (w & 0x000000ff << 24)
|
||||
(return (i32.or
|
||||
(i32.or
|
||||
(i32.shr_u (i32.and (get_local $w) (i32.const 0xff000000)) (i32.const 24))
|
||||
(i32.shr_u (i32.and (get_local $w) (i32.const 0x00ff0000)) (i32.const 8))
|
||||
)
|
||||
(i32.or
|
||||
(i32.shl (i32.and (get_local $w) (i32.const 0x0000ff00)) (i32.const 8))
|
||||
(i32.shl (i32.and (get_local $w) (i32.const 0x000000ff)) (i32.const 24))
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
;; function `sha1_init`
|
||||
;; initialize memory
|
||||
(func $sha1_init
|
||||
(i64.store (i32.const 0x00) (i64.const 0))
|
||||
(i64.store (i32.const 0x08) (i64.const 0))
|
||||
(i64.store (i32.const 0x10) (i64.const 0))
|
||||
(i64.store (i32.const 0x18) (i64.const 0))
|
||||
(i64.store (i32.const 0x20) (i64.const 0))
|
||||
(i64.store (i32.const 0x28) (i64.const 0))
|
||||
(i64.store (i32.const 0x30) (i64.const 0))
|
||||
(i64.store (i32.const 0x38) (i64.const 0))
|
||||
|
||||
(set_global $message_len (i32.const 0))
|
||||
(set_global $h0 (i32.const 0x67452301))
|
||||
(set_global $h1 (i32.const 0xefcdab89))
|
||||
(set_global $h2 (i32.const 0x98badcfe))
|
||||
(set_global $h3 (i32.const 0x10325476))
|
||||
(set_global $h4 (i32.const 0xc3d2e1f0))
|
||||
)
|
||||
|
||||
;; function `sha1_update`
|
||||
;; process full block
|
||||
(func $sha1_update
|
||||
;; word counter
|
||||
(local $w i32)
|
||||
|
||||
;; internal variables
|
||||
(local $a i32) (local $b i32) (local $c i32) (local $d i32) (local $e i32)
|
||||
(local $f i32) (local $k i32) (local $t i32)
|
||||
|
||||
;; message_len += 64 bytes (512 bits)
|
||||
(set_global $message_len (i32.add (get_global $message_len) (i32.const 64)))
|
||||
|
||||
;; load h0 ~ h4
|
||||
(set_local $a (get_global $h0))
|
||||
(set_local $b (get_global $h1))
|
||||
(set_local $c (get_global $h2))
|
||||
(set_local $d (get_global $h3))
|
||||
(set_local $e (get_global $h4))
|
||||
|
||||
;; loop
|
||||
(set_local $w (i32.const 0))
|
||||
(block $done
|
||||
(loop $loop
|
||||
;; word 0 ~ 15 will be used as-is on memory
|
||||
;; word 16 ~ 79 will be calculated and replaced on memory
|
||||
(if
|
||||
;; if 16 <= $w
|
||||
(i32.ge_s (get_local $w) (i32.const 16))
|
||||
|
||||
;; calculate word to use
|
||||
(call $set_word
|
||||
(get_local $w)
|
||||
;; value = (words[w-3] ^ words[w-8] ^ words[w-14] ^ words[w-16]) rotl 1
|
||||
(i32.rotl
|
||||
(i32.xor
|
||||
(i32.xor
|
||||
(call $get_word (i32.sub (get_local $w) (i32.const 3)))
|
||||
(call $get_word (i32.sub (get_local $w) (i32.const 8)))
|
||||
)
|
||||
(i32.xor
|
||||
(call $get_word (i32.sub (get_local $w) (i32.const 14)))
|
||||
(call $get_word (i32.sub (get_local $w) (i32.const 16)))
|
||||
)
|
||||
)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; calculate f and determine k
|
||||
(block $get_key
|
||||
(if (i32.lt_s (get_local $w) (i32.const 20))
|
||||
(block
|
||||
;; f = (b & c) | (~b & d)
|
||||
(set_local $f
|
||||
(i32.or
|
||||
(i32.and (get_local $b) (get_local $c))
|
||||
;; ~a == a ^ 0xffffffff
|
||||
(i32.and (i32.xor (get_local $b) (i32.const 0xffffffff)) (get_local $d))
|
||||
)
|
||||
)
|
||||
(set_local $k (i32.const 0x5a827999))
|
||||
(br $get_key)
|
||||
)
|
||||
)
|
||||
(if (i32.lt_s (get_local $w) (i32.const 40))
|
||||
(block
|
||||
;; f = b ^ c ^ d
|
||||
(set_local $f
|
||||
(i32.xor
|
||||
(i32.xor (get_local $b) (get_local $c))
|
||||
(get_local $d)
|
||||
)
|
||||
)
|
||||
(set_local $k (i32.const 0x6ed9eba1))
|
||||
(br $get_key)
|
||||
)
|
||||
)
|
||||
(if (i32.lt_s (get_local $w) (i32.const 60))
|
||||
(block
|
||||
;; f = (b & c) | (b & d) | (c & d)
|
||||
(set_local $f
|
||||
(i32.or
|
||||
(i32.or
|
||||
(i32.and (get_local $b) (get_local $c))
|
||||
(i32.and (get_local $b) (get_local $d))
|
||||
)
|
||||
(i32.and (get_local $c) (get_local $d))
|
||||
)
|
||||
)
|
||||
(set_local $k (i32.const 0x8f1bbcdc))
|
||||
(br $get_key)
|
||||
)
|
||||
)
|
||||
(if (i32.lt_s (get_local $w) (i32.const 80))
|
||||
(block
|
||||
;; f = b ^ c ^ d
|
||||
(set_local $f
|
||||
(i32.xor
|
||||
(i32.xor (get_local $b) (get_local $c))
|
||||
(get_local $d)
|
||||
)
|
||||
)
|
||||
(set_local $k (i32.const 0xca62c1d6))
|
||||
(br $get_key)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; t = a rotl 5 + f + e + k + words[w]
|
||||
(set_local $t
|
||||
(i32.add
|
||||
(i32.add
|
||||
(i32.add
|
||||
(i32.rotl (get_local $a) (i32.const 5))
|
||||
(get_local $f)
|
||||
)
|
||||
(i32.add
|
||||
(get_local $e)
|
||||
(get_local $k)
|
||||
)
|
||||
)
|
||||
(call $get_word (get_local $w))
|
||||
)
|
||||
)
|
||||
|
||||
;; rotate variables
|
||||
(set_local $e (get_local $d))
|
||||
(set_local $d (get_local $c))
|
||||
(set_local $c (i32.rotl (get_local $b) (i32.const 30)))
|
||||
(set_local $b (get_local $a))
|
||||
(set_local $a (get_local $t))
|
||||
|
||||
;; w += 1
|
||||
(set_local $w (i32.add (get_local $w) (i32.const 1)))
|
||||
|
||||
;; if 80 <= w, break
|
||||
(br_if $done (i32.ge_s (get_local $w) (i32.const 80)))
|
||||
|
||||
;; else, continue
|
||||
(br $loop)
|
||||
)
|
||||
)
|
||||
|
||||
;; feed to h0 ~ h4
|
||||
(set_global $h0 (i32.add (get_local $a) (get_global $h0)))
|
||||
(set_global $h1 (i32.add (get_local $b) (get_global $h1)))
|
||||
(set_global $h2 (i32.add (get_local $c) (get_global $h2)))
|
||||
(set_global $h3 (i32.add (get_local $d) (get_global $h3)))
|
||||
(set_global $h4 (i32.add (get_local $e) (get_global $h4)))
|
||||
)
|
||||
|
||||
;; function `sha1_end`
|
||||
;; input - length of final chunk
|
||||
(func $sha1_end (param $final_len i32)
|
||||
(local $total_len i32)
|
||||
(local $i i32)
|
||||
|
||||
;; total_len = message_len + final_len
|
||||
(set_local $total_len (i32.add
|
||||
(get_global $message_len)
|
||||
(get_local $final_len)
|
||||
))
|
||||
|
||||
;; append 0x80
|
||||
(i32.store8 (get_local $final_len) (i32.const 0x80))
|
||||
|
||||
(set_local $i (i32.add (get_local $final_len) (i32.const 1)))
|
||||
|
||||
(if (i32.gt_s (get_local $i) (i32.const 56))
|
||||
;; if 56 < i
|
||||
(block
|
||||
(block $done_pad
|
||||
;; zero pad
|
||||
(loop $loop
|
||||
(br_if $done_pad (i32.ge_s (get_local $i) (i32.const 64)))
|
||||
|
||||
(i32.store8 (get_local $i) (i32.const 0))
|
||||
(set_local $i (i32.add (get_local $i) (i32.const 1)))
|
||||
(br $loop)
|
||||
)
|
||||
)
|
||||
|
||||
;; update
|
||||
(call $sha1_update)
|
||||
|
||||
;; fill 14 words with 0
|
||||
(i64.store (i32.const 0x00) (i64.const 0))
|
||||
(i64.store (i32.const 0x08) (i64.const 0))
|
||||
(i64.store (i32.const 0x10) (i64.const 0))
|
||||
(i64.store (i32.const 0x18) (i64.const 0))
|
||||
(i64.store (i32.const 0x20) (i64.const 0))
|
||||
(i64.store (i32.const 0x28) (i64.const 0))
|
||||
(i64.store (i32.const 0x30) (i64.const 0))
|
||||
)
|
||||
|
||||
;; else
|
||||
(block $done_pad
|
||||
;; zero pad
|
||||
(loop $loop
|
||||
(br_if $done_pad (i32.ge_s (get_local $i) (i32.const 56)))
|
||||
|
||||
(i32.store8 (get_local $i) (i32.const 0))
|
||||
(set_local $i (i32.add (get_local $i) (i32.const 1)))
|
||||
(br $loop)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; append length (in bits)
|
||||
(call $set_word (i32.const 14) (i32.const 0))
|
||||
(call $set_word (i32.const 15) (i32.mul (get_local $total_len) (i32.const 8)))
|
||||
|
||||
;; update final block
|
||||
(call $sha1_update)
|
||||
|
||||
;; copy h0~4 to memory
|
||||
(i32.store (i32.const 0x40) (get_global $h0))
|
||||
(i32.store (i32.const 0x44) (get_global $h1))
|
||||
(i32.store (i32.const 0x48) (get_global $h2))
|
||||
(i32.store (i32.const 0x4c) (get_global $h3))
|
||||
(i32.store (i32.const 0x50) (get_global $h4))
|
||||
)
|
||||
|
||||
(func $increase_counter
|
||||
;; $offset must be absolute in memory
|
||||
(param $offset i32)
|
||||
(param $length i32)
|
||||
|
||||
;; The new length
|
||||
(result i32)
|
||||
|
||||
(local $index i32)
|
||||
(local $character i32)
|
||||
(set_local $index (i32.add (get_local $offset) (get_local $length)))
|
||||
|
||||
|
||||
(loop $main_loop
|
||||
;; Decrease variable
|
||||
(set_local $index (i32.sub (get_local $index) (i32.const 1)))
|
||||
|
||||
(if
|
||||
;; Test if we're over the number bound
|
||||
(i32.lt_u (get_local $index) (get_local $offset))
|
||||
|
||||
(block
|
||||
;; Increase the index by one again to set the first character to 1
|
||||
(i32.store8 (i32.add (get_local $index) (i32.const 1)) (i32.const 49))
|
||||
|
||||
(i32.store8 (i32.add (get_local $offset) (get_local $length)) (i32.const 48))
|
||||
(set_local $length (i32.add (get_local $length) (i32.const 1)))
|
||||
|
||||
(return (get_local $length))
|
||||
)
|
||||
)
|
||||
|
||||
(set_local $character (i32.load8_u (get_local $index)))
|
||||
|
||||
(if
|
||||
;; $character == '9'
|
||||
(i32.eq (get_local $character) (i32.const 57))
|
||||
|
||||
(block
|
||||
;; Set it to '0' and decrease $index
|
||||
(i32.store8 (get_local $index) (i32.const 48))
|
||||
|
||||
(br $main_loop)
|
||||
)
|
||||
)
|
||||
|
||||
;; Increase by one
|
||||
(i32.store8 (get_local $index) (i32.add (get_local $character) (i32.const 1)))
|
||||
)
|
||||
|
||||
(return (get_local $length))
|
||||
)
|
||||
|
||||
|
||||
(func $mine
|
||||
;; Length of the base 64 string
|
||||
(param $length64 i32)
|
||||
;; Length of the counter
|
||||
(param $length_counter i32)
|
||||
;; Iterations to do
|
||||
(param $iterations i32)
|
||||
;; The current best level we want to overreach
|
||||
(param $target_level i32)
|
||||
|
||||
;; Returns the best found level
|
||||
(result i32)
|
||||
|
||||
(local $level i32)
|
||||
(local $best_level i32)
|
||||
|
||||
(local $write_offset i32)
|
||||
(local $write_index i32)
|
||||
(local $max_write_index i32)
|
||||
|
||||
(set_local $best_level (get_local $target_level))
|
||||
|
||||
(block $done
|
||||
(loop $main_loop
|
||||
call $sha1_init
|
||||
|
||||
;; Load the first 64 bytes
|
||||
(i64.store (i32.const 0x00) (i64.load (i32.const 0x0A0)))
|
||||
(i64.store (i32.const 0x08) (i64.load (i32.const 0x0A8)))
|
||||
(i64.store (i32.const 0x10) (i64.load (i32.const 0x0B0)))
|
||||
(i64.store (i32.const 0x18) (i64.load (i32.const 0x0B8)))
|
||||
(i64.store (i32.const 0x20) (i64.load (i32.const 0x0C0)))
|
||||
(i64.store (i32.const 0x28) (i64.load (i32.const 0x0C8)))
|
||||
(i64.store (i32.const 0x30) (i64.load (i32.const 0x0D0)))
|
||||
(i64.store (i32.const 0x38) (i64.load (i32.const 0x0D8)))
|
||||
call $sha1_update
|
||||
|
||||
(set_local $max_write_index (i32.add (i32.add (get_local $length64) (get_local $length_counter)) (i32.const 0x0A0)))
|
||||
(set_local $write_index (i32.const 0x0E0))
|
||||
(set_local $write_offset (i32.const 0))
|
||||
|
||||
(loop $write_loop
|
||||
(i32.store8 (get_local $write_offset) (i32.load8_u (get_local $write_index)))
|
||||
|
||||
(set_local $write_offset (i32.add (get_local $write_offset) (i32.const 1)))
|
||||
(if (i32.eq (get_local $write_offset) (i32.const 64))
|
||||
(block
|
||||
(call $sha1_update)
|
||||
(set_local $write_offset (i32.const 0))
|
||||
)
|
||||
)
|
||||
|
||||
(set_local $write_index (i32.add (get_local $write_index) (i32.const 1)))
|
||||
(br_if $write_loop (i32.lt_s (get_local $write_index) (get_local $max_write_index)))
|
||||
)
|
||||
(call $sha1_end (get_local $write_offset))
|
||||
|
||||
;; Count for each block the tailing zero bits. If the bits are 32 then add the next block
|
||||
(set_local $level (i32.ctz (call $flip_endian (get_global $h0)))) ;; First block [0;32[
|
||||
(if
|
||||
(i32.eq (get_local $level) (i32.const 32))
|
||||
|
||||
(block
|
||||
(set_local $level
|
||||
(i32.add (i32.ctz (call $flip_endian (get_global $h1))) (get_local $level)) ;; Second block [32;64[
|
||||
)
|
||||
|
||||
(if
|
||||
(i32.eq (get_local $level) (i32.const 64))
|
||||
|
||||
(block
|
||||
(set_local $level
|
||||
(i32.add (i32.ctz (call $flip_endian (get_global $h2))) (get_local $level)) ;; Third block [64;86[
|
||||
)
|
||||
|
||||
(if
|
||||
(i32.eq (get_local $level) (i32.const 84))
|
||||
|
||||
(block
|
||||
(set_local $level
|
||||
(i32.add (i32.ctz (call $flip_endian (get_global $h3))) (get_local $level)) ;; Fourth block [86;128[
|
||||
)
|
||||
|
||||
(if
|
||||
(i32.eq (get_local $level) (i32.const 128))
|
||||
|
||||
(block
|
||||
(set_local $level
|
||||
(i32.add (i32.ctz (call $flip_endian (get_global $h4))) (get_local $level)) ;; Fifth block [128;160[
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(if (i32.lt_u (get_local $best_level) (get_local $level))
|
||||
(block
|
||||
(set_local $best_level (get_local $level))
|
||||
|
||||
;; If we have a target level then break here
|
||||
(if
|
||||
(i32.ne (get_local $target_level) (i32.const 0))
|
||||
(br $done)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; Increase everything
|
||||
(set_local $iterations (i32.sub (get_local $iterations) (i32.const 1)))
|
||||
(set_local $length_counter
|
||||
(call $increase_counter (i32.add (i32.const 0x0A0) (get_local $length64)) (get_local $length_counter))
|
||||
)
|
||||
|
||||
(br_if $main_loop (i32.gt_u (get_local $iterations) (i32.const 0)))
|
||||
)
|
||||
)
|
||||
|
||||
;; May length had changed, so we null terminate it
|
||||
(i64.store (i32.add (i32.const 0x0A0) (i32.add (get_local $length64) (get_local $length_counter))) (i64.const 0x0))
|
||||
(return (get_local $best_level))
|
||||
)
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
/* File: /home/wolverindev/TeaSpeak/TeaSpeak/Web-Client/tools/dtsgen/test/test_03.ts */
|
||||
declare enum YY {
|
||||
H,
|
||||
B
|
||||
}
|
||||
declare interface X {
|
||||
type: any;
|
||||
c: YY.B;
|
||||
}
|
|
@ -1 +1 @@
|
|||
Subproject commit 8f2626cf1e24edca8484545841967525177ef3aa
|
||||
Subproject commit 8304246b4f651b9de141e4d3252b4f78e5c55391
|
Loading…
Reference in New Issue