Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
dist
.env
.npmrc
coverage/
25 changes: 20 additions & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,23 @@
"LICENSE": true,
"tsconfig.*": true,
"node_modules": true,
"*.code-workspace": true
"*.code-workspace": true,
"*.config.ts": true,
"coverage": true
},
"search.exclude": {
".*": false,
"*.sh": false,
"*.json": false,
"CODEOWNERS": false,
"dist": false,
"README.md": false,
"LICENSE": false,
"tsconfig.*": false,
"node_modules": false,
"*.code-workspace": false,
"*.config.ts": false,
"coverage": false
},
"files.insertFinalNewline": true,
"editor.tabSize": 2,
Expand All @@ -27,14 +43,11 @@
"typescript.format.enable": false,
"cSpell.words": [
"authly",
"bytewise",
"cloudly",
"crockford",
"cryptly",
"Encrypter",
"Encrypters",
"flagly",
"Ghpcy",
"gracely",
"isly",
"isoly",
Expand All @@ -47,6 +60,8 @@
"smoothly",
"tidily",
"transactly",
"typedly",
"uply"
]
],
"typescript.tsdk": "node_modules/typescript/lib"
}
2 changes: 1 addition & 1 deletion ArrayBuffer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export namespace ArrayBuffer {
for (const d of data.map(d => new Uint8Array(d))) {
const offset = result.length - d.length
result = result.reduceRight(
(r, value, index) => ((r[index] = index < offset ? value : reducer(value, d[index - offset])), r),
(r, value, index) => ((r[index] = index < offset ? value : reducer(value, d[index - offset]!)), r), // index is always in bounds due to arithmetic
result
)
}
Expand Down
2 changes: 1 addition & 1 deletion Base16/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export namespace Base16 {
value += "0"
const result = new Uint8Array(value.length / 2)
for (let index = 0; index < result.length; index++)
result[index] = Number.parseInt(value[index * 2], 16) * 16 + Number.parseInt(value[index * 2 + 1], 16)
result[index] = Number.parseInt(value[index * 2]!, 16) * 16 + Number.parseInt(value[index * 2 + 1]!, 16) // index is always in bounds due to the loop
return result
}
export function xor(data: Base16[]): Base16 {
Expand Down
4 changes: 2 additions & 2 deletions Base32/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export namespace Base32 {
let value = 0
let result = ""
for (let i = 0; i < data.length; i++) {
value = (value << 8) | data[i]
value = (value << 8) | data[i]! // data[i] is always defined as checked by the loop
bits += 8
while (bits >= 5) {
result += table[(value >>> (bits - 5)) & 31]
Expand All @@ -61,7 +61,7 @@ export namespace Base32 {
let value = 0
let index = 0
for (let i = 0; i < input.length; i++) {
value = (value << 5) | table.indexOf(input[i])
value = (value << 5) | table.indexOf(input[i]!) // input[i] is always defined as checked by the loop
bits += 5
if (bits >= 8) {
result[index++] = (value >>> (bits - 8)) & 255
Expand Down
19 changes: 10 additions & 9 deletions Base64/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ export namespace Base64 {
const table = tables[standard]
const result: string[] = []
for (let c = 0; c < data.length; c += 3) {
const c0 = data[c]
const c1 = c + 1 < data.length ? data[c + 1] : 0
const c2 = c + 2 < data.length ? data[c + 2] : 0
result.push(table[c0 >>> 2])
result.push(table[((c0 & 3) << 4) | (c1 >>> 4)])
result.push(table[((c1 & 15) << 2) | (c2 >>> 6)])
result.push(table[c2 & 63])
const c0 = data[c]! // data[c] is always defined as checked by the loop
const c1 = c + 1 < data.length ? data[c + 1]! : 0 // data[c + 1] is checked in the expression
const c2 = c + 2 < data.length ? data[c + 2]! : 0 // data[c + 1] is checked in the expression
// table contains all values from 0 to 63
result.push(table[c0 >>> 2]!)
result.push(table[((c0 & 3) << 4) | (c1 >>> 4)]!)
result.push(table[((c1 & 15) << 2) | (c2 >>> 6)]!)
result.push(table[c2 & 63]!)
}
const length = Math.ceil((data.length / 3) * 4)
return result.join("").substring(0, length) + padding.repeat(result.length - length)
Expand All @@ -68,8 +69,8 @@ export namespace Base64 {
}
export function next(value: Base64, increment = 1, standard: Standard = "standard"): Base64 {
const table = tables[standard]
const rest = value.length > 1 ? value.substring(0, value.length - 1) : increment > 0 ? "" : table[63]
const number = (value.length == 0 ? 0 : table.indexOf(value[value.length - 1])) + increment
const rest = value.length > 1 ? value.substring(0, value.length - 1) : increment > 0 ? "" : table[63]! // all tables are 64 characters long
const number = (value.length == 0 ? 0 : table.indexOf(value[value.length - 1]!)) + increment // guaranteed to be in the last value
return (
(number > 63 || number < 0 ? next(rest, Math.floor(number / 63), standard) : rest) + table[remainder(number, 64)]
)
Expand Down
49 changes: 38 additions & 11 deletions Encrypter/Aes/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isly } from "isly"
import { Base64 } from "../../Base64"
import { crypto } from "../../crypto"
import { Encrypted as AesEncrypted } from "./Encrypted"
Expand Down Expand Up @@ -38,43 +39,56 @@ export class Aes {
)
}
async export(): Promise<Base64>
async export(parts: 1): Promise<[Base64]>
async export(parts: 2): Promise<[Base64, Base64]>
async export(parts: 3): Promise<[Base64, Base64, Base64]>
async export(parts: 4): Promise<[Base64, Base64, Base64, Base64]>
async export(parts: 5): Promise<[Base64, Base64, Base64, Base64, Base64]>
async export(parts: number): Promise<Base64[]>
async export(parts: Uint8Array): Promise<Base64>
async export(parts: [Uint8Array]): Promise<[Base64, Base64]>
async export(parts: [Uint8Array, Uint8Array]): Promise<[Base64, Base64, Base64]>
async export(parts: [Uint8Array, Uint8Array, Uint8Array]): Promise<[Base64, Base64, Base64, Base64]>
async export(
parts: [Uint8Array, Uint8Array, Uint8Array, Uint8Array]
): Promise<[Base64, Base64, Base64, Base64, Base64]>
async export(parts: Uint8Array[]): Promise<Base64[]>
async export(parts: Base64): Promise<Base64>
async export(parts: Base64[]): Promise<Base64[]>
async export(parts?: number | Uint8Array | Uint8Array[] | Base64 | Base64[]): Promise<Base64 | Base64[]> {
let result: Base64 | Base64[]
const key = new Uint8Array(await crypto.subtle.exportKey("raw", await this.key))
if (parts == undefined) result = (await this.export(1))[0]
if (parts == undefined)
result = (await this.export(1))[0]
else if (typeof parts == "number")
result = await this.export(parts > 1 ? Aes.generateRandomKeys(key.length, parts - 1) : [])
else if (Base64.is(parts))
result = await this.export(Base64.decode(parts, "url"))
else if (parts instanceof Uint8Array)
result = (await this.export([parts]))[0]
else if (this.isBase64Array(parts))
result = await this.export(parts.map(part => Base64.decode(part, "url")))
else {
else if (isly.instanceOf(Uint8Array).array().is(parts)) {
parts = [Aes.reduceKeys([key, ...parts]), ...parts]
result = parts.map(r => Base64.encode(r, "url"))
}
} else
result = await this.export(parts.map(part => Base64.decode(part, "url")))
return result
}
private isBase64Array(value: unknown): value is Base64[] {
return Array.isArray(value) && value.length > 0 && value.every(Base64.is)
}
static cbc(key: 256 | Base64 | Base64[]): Aes {
return Aes.generate("AES-CBC", key)
}
static gcm(key: 256 | Base64 | Base64[]): Aes {
return Aes.generate("AES-GCM", key)
}
static random(length: 256): Base64
static random(length: 256, parts: 1): [Base64]
static random(length: 256, parts: 2): [Base64, Base64]
static random(length: 256, parts: 3): [Base64, Base64, Base64]
static random(length: 256, parts: 4): [Base64, Base64, Base64, Base64]
static random(length: 256, parts: 5): [Base64, Base64, Base64, Base64, Base64]
static random(length: 256, parts: number): Base64[]
static random(length: 256, parts?: number): Base64 | Base64[] {
const result = Aes.generateRandomKeys(length / 8, parts && parts > 0 ? parts : 1).map(r => Base64.encode(r, "url"))
return parts ? result : result[0]
return parts ? result : result[0]! // parts > 0 ensures that result is not empty
}
private static generate(algorithm: "AES-CBC" | "AES-GCM", key: 256 | Base64 | Base64[]): Aes {
return new Aes(
Expand All @@ -89,14 +103,27 @@ export class Aes {
)
)
}
private static generateRandomKeys(length: 1, parts: number): [Uint8Array]
private static generateRandomKeys(length: 2, parts: number): [Uint8Array, Uint8Array]
private static generateRandomKeys(length: 3, parts: number): [Uint8Array, Uint8Array, Uint8Array]
private static generateRandomKeys(length: 4, parts: number): [Uint8Array, Uint8Array, Uint8Array, Uint8Array]
private static generateRandomKeys(
length: 5,
parts: number
): [Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array]
private static generateRandomKeys(length: number, parts: number): Uint8Array[]
private static generateRandomKeys(length: number, parts: number): Uint8Array[] {
return parts > 0
? [crypto.getRandomValues(new Uint8Array(length)), ...this.generateRandomKeys(length, parts - 1)]
: []
}
private static reduceKeys(keys: Uint8Array[]): Uint8Array {
const result = new Uint8Array(keys[0].length)
for (let index = 0; index < keys[0].length; index++) result[index] = keys.reduce((p, c) => p ^ c[index], 0)
let length = keys[0]?.length ?? 0
if (keys.some(key => key.length != length))
length = 0
const result = new Uint8Array(length)
for (let index = 0; index < length; index++)
result[index] = keys.reduce((p, c) => p ^ c[index]!, 0) // if statement above loop ensures that all keys have the same length
return result
}
}
Expand Down
10 changes: 5 additions & 5 deletions Otp/Generator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ export class Generator {
)
else {
const hash = await this.signer.sign(Base16.decode(counter.toString(16).padStart(16, "0")))
const offset = hash[hash.length - 1] & 0xf
const offset = hash[hash.length - 1]! & 0xf // element has to exists by definition of has.length
const value =
((hash[offset] & 0x7f) << 24) |
((hash[offset + 1] & 0xff) << 16) |
((hash[offset + 2] & 0xff) << 8) |
(hash[offset + 3] & 0xff)
((hash[offset]! & 0x7f) << 24) | // offset is significantly smaller than hash.length due to the mask
((hash[offset + 1]! & 0xff) << 16) |
((hash[offset + 2]! & 0xff) << 8) |
(hash[offset + 3]! & 0xff)
// magic numbers explanation:
// (value % 10^digits).padstart(digits, "0")
result = (value % +"1".padEnd(this.settings.length + 1, "0")).toString().padStart(this.settings.length, "0")
Expand Down
13 changes: 9 additions & 4 deletions Signer/Ecdsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Base } from "./Base"
import { Hash } from "./Hash"

export class Ecdsa extends Base {
private publicKey: PromiseLike<CryptoKey>
private privateKey: PromiseLike<CryptoKey>
private publicKey: PromiseLike<CryptoKey> | undefined
private privateKey: PromiseLike<CryptoKey> | undefined
constructor(
private readonly hash: Hash,
publicKey: Uint8Array | Base64 | undefined,
Expand Down Expand Up @@ -37,10 +37,15 @@ export class Ecdsa extends Base {
}
protected async signBinary(data: Uint8Array): Promise<Uint8Array> {
return new Uint8Array(
await crypto.subtle.sign({ name: "ECDSA", hash: { name: this.hash } }, await this.privateKey, data)
this.privateKey
? await crypto.subtle.sign({ name: "ECDSA", hash: { name: this.hash } }, await this.privateKey, data)
: new ArrayBuffer(0)
)
}
protected async verifyBinary(data: Uint8Array, signature: Uint8Array): Promise<boolean> {
return crypto.subtle.verify({ name: "ECDSA", hash: { name: this.hash } }, await this.publicKey, signature, data)
return (
!!this.publicKey &&
crypto.subtle.verify({ name: "ECDSA", hash: { name: this.hash } }, await this.publicKey, signature, data)
)
}
}
16 changes: 16 additions & 0 deletions Signer/Key/Symmetric.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { isly } from "isly"
import { Base64 } from "../../Base64"

export type Symmetric = ArrayBufferView | Base64

export namespace Symmetric {
export const type = isly.named<Symmetric>(
"authly.Algorithm.Key.Symmetric",
isly.union(
isly.fromIs<Uint8Array>("Uint8Array", v => v instanceof Uint8Array),
Base64.type
)
)
export const is = type.is
export const flaw = type.flaw
}
7 changes: 7 additions & 0 deletions Signer/Key/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Base64 } from "../../Base64"

export type Key = ArrayBufferView | Base64

export namespace Key {
export type Asymmetric = { public?: Key; private?: Key }
}
4 changes: 2 additions & 2 deletions Signer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ export namespace Signer {
export function create(
algorithm: SignerAlgorithm | "None",
hash?: SignerHash | undefined,
...keys: (Base64 | Uint8Array)[]
...keys: (Base64 | Uint8Array | undefined)[]
): Signer | undefined {
let result: Signer | undefined
if (hash != undefined)
switch (algorithm) {
case "HMAC":
result = new Hmac(hash, keys[0])
result = keys[0] == undefined ? keys[0] : new Hmac(hash, keys[0])
break
case "RSA":
result = Rsa.import("SSA", hash, keys[0], keys[1])
Expand Down
1 change: 1 addition & 0 deletions crypto.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference types="node" />
import * as C from "crypto"

export const crypto = C.webcrypto as Crypto
Loading
Loading