4 Commits

Author SHA1 Message Date
534cf8c800 better theme for typedoc 2025-02-03 11:16:34 +01:00
5b4a043f6c lock file 2025-02-03 10:56:47 +01:00
f87c1aef53 use vitest browser mode 2025-02-03 10:29:44 +01:00
91693e55e1 cleanup types 2025-02-03 10:29:33 +01:00
13 changed files with 1246 additions and 1060 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "occulto", "name": "occulto",
"version": "2.0.6", "version": "2.1.0",
"description": "crypt utility", "description": "encryption utility",
"keywords": [ "keywords": [
"isomorphic", "isomorphic",
"crypto", "crypto",
@@ -29,9 +29,7 @@
], ],
"scripts": { "scripts": {
"docs": "typedoc", "docs": "typedoc",
"test:node": "vitest", "test": "vitest",
"test:browsers": "zx test.browsers.js",
"test": "CI=1 run-s build test:*",
"build": "tsc", "build": "tsc",
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"dev": "vitest", "dev": "vitest",
@@ -39,17 +37,14 @@
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/strictest": "^2.0.5", "@tsconfig/strictest": "^2.0.5",
"@types/node": "^22.5.2", "@types/node": "^22.13.0",
"@vitest/browser": "^2.0.5", "@vitest/browser": "^3.0.4",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"playwright": "^1.46.1", "playwright": "^1.50.1",
"typedoc": "^0.26.6", "typedoc": "^0.27.6",
"typescript": "^5.5.4", "typedoc-material-theme": "^1.3.0",
"vitest": "^2.0.5", "typescript": "^5.7.3",
"zx": "^8.1.5" "vitest": "^3.0.4"
}, },
"packageManager": "pnpm@9.8.0", "packageManager": "pnpm@9.15.4"
"engines": {
"node": ">=18"
}
} }

2173
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
import { type TypedArray } from '../utils/base.js'
import { getCrypto } from './crypto.js' import { getCrypto } from './crypto.js'
import { Base64, Bytes } from './encoding.js' import { Base64, Bytes } from './encoding.js'
import { Hashes } from './hash.js' import { Hashes } from './hash.js'
@@ -15,7 +14,7 @@ export type KeyData = {
name: 'PBKDF2' name: 'PBKDF2'
hash: Hashes hash: Hashes
iterations: number iterations: number
salt: TypedArray salt: ArrayBufferLike
length: number length: number
} }
@@ -35,12 +34,12 @@ export class AES {
private static InvalidCiphertext = new Error('Invalid ciphertext') private static InvalidCiphertext = new Error('Invalid ciphertext')
private static async join(...args: TypedArray[]): Promise<string> { private static async join(...args: ArrayBufferLike[]): Promise<string> {
const strings = await Promise.all(args.map(Base64.encode)) const strings = await Promise.all(args.map(Base64.encode))
return strings.join(AES.delimiter) return strings.join(AES.delimiter)
} }
private static async split(ciphertext: string): Promise<TypedArray[]> { private static async split(ciphertext: string): Promise<ArrayBufferLike[]> {
const splitted = ciphertext.split(AES.delimiter) const splitted = ciphertext.split(AES.delimiter)
return Promise.all(splitted.map(Base64.decode)) return Promise.all(splitted.map(Base64.decode))
} }
@@ -49,7 +48,7 @@ export class AES {
* Derive a key from a password. * Derive a key from a password.
* To be used if the password is not 128, 192 or 256 bits or human made, non generated keys. * To be used if the password is not 128, 192 or 256 bits or human made, non generated keys.
*/ */
static async derive(key: string, options?: KeyData): Promise<[TypedArray, KeyData]> { static async derive(key: string, options?: KeyData): Promise<[ArrayBufferLike, KeyData]> {
options ??= { options ??= {
name: 'PBKDF2', name: 'PBKDF2',
hash: Hashes.SHA_512, hash: Hashes.SHA_512,
@@ -66,7 +65,7 @@ export class AES {
return [new Uint8Array(bits), options] return [new Uint8Array(bits), options]
} }
static async encrypt(data: TypedArray, key: TypedArray, mode: Modes = Modes.AES_GCM): Promise<string> { static async encrypt(data: ArrayBufferLike, key: ArrayBufferLike, mode: Modes = Modes.AES_GCM): Promise<string> {
const c = await getCrypto() const c = await getCrypto()
let iv: Uint8Array let iv: Uint8Array
@@ -88,7 +87,7 @@ export class AES {
return AES.join(Bytes.encode(alg), iv, encryptedBuffer) return AES.join(Bytes.encode(alg), iv, encryptedBuffer)
} }
static async decrypt(ciphertext: string, key: TypedArray): Promise<TypedArray> { static async decrypt(ciphertext: string, key: ArrayBufferLike): Promise<ArrayBufferLike> {
const c = await getCrypto() const c = await getCrypto()
const [alg, iv, data] = await AES.split(ciphertext) const [alg, iv, data] = await AES.split(ciphertext)
@@ -107,7 +106,7 @@ export class AES {
return new Uint8Array(decrypted) return new Uint8Array(decrypted)
} }
static async encryptEasy(data: string | TypedArray, key: string, mode: Modes = Modes.AES_GCM): Promise<string> { static async encryptEasy(data: string | ArrayBufferLike, key: string, mode: Modes = Modes.AES_GCM): Promise<string> {
const dataBuffer = typeof data === 'string' ? Bytes.encode(data) : data const dataBuffer = typeof data === 'string' ? Bytes.encode(data) : data
const [keyDerived, options] = await AES.derive(key) const [keyDerived, options] = await AES.derive(key)
@@ -143,7 +142,7 @@ export class AES {
return Bytes.decode(decrypted) return Bytes.decode(decrypted)
} }
static async generateKey(): Promise<TypedArray> { static async generateKey(): Promise<ArrayBufferLike> {
const c = await getCrypto() const c = await getCrypto()
const key = await c.subtle.generateKey( const key = await c.subtle.generateKey(
{ {

View File

@@ -1,9 +1,9 @@
import { split, type TypedArray } from '../utils/base.js' import { split } from '../utils/base.js'
export class Base64 { export class Base64 {
private static prefix = 'data:application/octet-stream;base64,' private static prefix = 'data:application/octet-stream;base64,'
static encode(s: TypedArray): Promise<string> { static encode(s: ArrayBufferLike): Promise<string> {
return split({ return split({
async node() { async node() {
return Buffer.from(s).toString('base64') return Buffer.from(s).toString('base64')
@@ -22,7 +22,7 @@ export class Base64 {
}) })
} }
static decode(s: string): Promise<TypedArray> { static decode(s: string): Promise<ArrayBufferLike> {
return split({ return split({
async node() { async node() {
return Buffer.from(s, 'base64') return Buffer.from(s, 'base64')
@@ -38,7 +38,7 @@ export class Base64 {
} }
export class Hex { export class Hex {
static encode(buffer: TypedArray): string { static encode(buffer: ArrayBufferLike): string {
let s = '' let s = ''
for (const i of new Uint8Array(buffer)) { for (const i of new Uint8Array(buffer)) {
s += i.toString(16).padStart(2, '0') s += i.toString(16).padStart(2, '0')
@@ -46,7 +46,7 @@ export class Hex {
return s return s
} }
static decode(s: string): TypedArray { static decode(s: string): ArrayBufferLike {
const size = s.length / 2 const size = s.length / 2
const buffer = new Uint8Array(size) const buffer = new Uint8Array(size)
for (let i = 0; i < size; i++) { for (let i = 0; i < size; i++) {
@@ -59,7 +59,7 @@ export class Hex {
} }
export class Bytes { export class Bytes {
static decode(data: TypedArray): string { static decode(data: ArrayBufferLike): string {
return split({ return split({
node() { node() {
return Buffer.from(data).toString('utf-8') return Buffer.from(data).toString('utf-8')
@@ -70,7 +70,7 @@ export class Bytes {
}) })
} }
static encode(data: string): TypedArray { static encode(data: string): ArrayBufferLike {
return split({ return split({
node() { node() {
return Buffer.from(data) return Buffer.from(data)

View File

@@ -1,4 +1,3 @@
import { type TypedArray } from '../utils/base.js'
import { getCrypto } from './crypto.js' import { getCrypto } from './crypto.js'
import { Bytes, Hex } from './encoding.js' import { Bytes, Hex } from './encoding.js'
@@ -21,8 +20,8 @@ export enum Hashes {
export class Hash { export class Hash {
static async hash(data: string, hash: Hashes): Promise<string> static async hash(data: string, hash: Hashes): Promise<string>
static async hash(data: TypedArray, hash: Hashes): Promise<TypedArray> static async hash(data: ArrayBufferLike, hash: Hashes): Promise<ArrayBufferLike>
static async hash(data: string | TypedArray, hash: Hashes): Promise<string | TypedArray> { static async hash(data: string | ArrayBufferLike, hash: Hashes): Promise<string | ArrayBufferLike> {
const isString = typeof data === 'string' const isString = typeof data === 'string'
const c = await getCrypto() const c = await getCrypto()
const result = await c.subtle.digest(hash, isString ? Bytes.encode(data) : data) const result = await c.subtle.digest(hash, isString ? Bytes.encode(data) : data)

View File

@@ -1,7 +1,6 @@
import { type TypedArray } from '../utils/base.js'
import { getCrypto } from './crypto.js' import { getCrypto } from './crypto.js'
export async function getRandomBytes(bytes: number): Promise<TypedArray> { export async function getRandomBytes(bytes: number): Promise<ArrayBufferLike> {
if (bytes <= 0) throw new Error('Invalid number of bytes') if (bytes <= 0) throw new Error('Invalid number of bytes')
const buffer = new Uint8Array(bytes) const buffer = new Uint8Array(bytes)

View File

@@ -1,4 +1,3 @@
import type { TypedArray } from '../utils/base.js'
import { getCrypto } from './crypto.js' import { getCrypto } from './crypto.js'
import { Base64 } from './encoding.js' import { Base64 } from './encoding.js'
@@ -71,7 +70,8 @@ class Key {
// @ts-ignore // @ts-ignore
const mod = key?.algorithm?.modulusLength const mod = key?.algorithm?.modulusLength
if (isNaN(mod)) throw Constants.error.invalidKey if (isNaN(mod)) throw Constants.error.invalidKey
return mod / 8 - (2 * 512) / 8 - 2 const maxBytes = mod / 8 - (2 * 512) / 8 - 2
return maxBytes
} }
} }
@@ -100,7 +100,7 @@ export class RSA {
} }
} }
static async encrypt(data: TypedArray, key: string): Promise<TypedArray> { static async encrypt(data: ArrayBufferLike, key: string): Promise<ArrayBufferLike> {
let keyObj: CryptoKey let keyObj: CryptoKey
try { try {
keyObj = await Key.decode(key) keyObj = await Key.decode(key)
@@ -112,14 +112,14 @@ export class RSA {
} }
// Check if data is too large // Check if data is too large
if (data.length > Key.getMaxMessageSize(keyObj)) throw Constants.error.dataTooLong if (new Uint8Array(data).byteLength > Key.getMaxMessageSize(keyObj)) throw Constants.error.dataTooLong
const c = await getCrypto() const c = await getCrypto()
const encrypted = await c.subtle.encrypt({ name: Constants.name }, keyObj, data) const encrypted = await c.subtle.encrypt({ name: Constants.name }, keyObj, data)
return new Uint8Array(encrypted) return new Uint8Array(encrypted)
} }
static async decrypt(data: TypedArray, key: string): Promise<TypedArray> { static async decrypt(data: ArrayBufferLike, key: string): Promise<ArrayBufferLike> {
let keyObj: CryptoKey let keyObj: CryptoKey
try { try {
keyObj = await Key.decode(key) keyObj = await Key.decode(key)

View File

@@ -3,4 +3,3 @@ export * from './crypto/encoding.js'
export * from './crypto/hash.js' export * from './crypto/hash.js'
export * from './crypto/random.js' export * from './crypto/random.js'
export * from './crypto/rsa.js' export * from './crypto/rsa.js'
export { TypedArray } from './utils/base.js'

View File

@@ -8,17 +8,6 @@ export const isBrowser = typeof window !== 'undefined'
*/ */
export type PromiseOrValue<T> = T | Promise<T> export type PromiseOrValue<T> = T | Promise<T>
export type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| BigInt64Array
| BigUint64Array
/** /**
* @internal * @internal
*/ */

View File

@@ -1,10 +0,0 @@
#!/usr/bin/env zx
import 'zx/globals'
$.verbose = true
const BROWSERS = ['firefox', 'chromium', 'webkit']
await Promise.all([
BROWSERS.map((browser) => $`pnpm vitest --browser.provider=playwright --browser.name=${browser} --browser.headless`),
])

View File

@@ -5,6 +5,9 @@
"name": "Occulto", "name": "Occulto",
"includeVersion": true, "includeVersion": true,
"plugin": ["typedoc-material-theme"],
"themeColor": "#cb9820",
"excludeInternal": true, "excludeInternal": true,
"excludePrivate": true "excludePrivate": true
} }

11
vitest.config.ts.old Normal file
View File

@@ -0,0 +1,11 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
browser: {
provider: 'playwright',
enabled: true,
headless: true,
instances: [{ browser: 'firefox' }, { browser: 'webkit' }, { browser: 'chromium' }],
},
},
})

21
vitest.workspace.ts Normal file
View File

@@ -0,0 +1,21 @@
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
{
test: {
name: 'node',
environment: 'node',
},
},
{
test: {
name: 'browser',
browser: {
provider: 'playwright',
enabled: true,
headless: true,
instances: [{ browser: 'firefox' }, { browser: 'webkit' }, { browser: 'chromium' }],
},
},
},
])