mirror of
https://github.com/cupcakearmy/occulto.git
synced 2025-12-11 20:04:58 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 534cf8c800 | |||
| 5b4a043f6c | |||
| f87c1aef53 | |||
| 91693e55e1 |
27
package.json
27
package.json
@@ -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
2173
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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'
|
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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`),
|
|
||||||
])
|
|
||||||
@@ -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
11
vitest.config.ts.old
Normal 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
21
vitest.workspace.ts
Normal 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' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
Reference in New Issue
Block a user