feat: add sncrypto client side packages
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,6 +24,8 @@ packages/services/dist
|
||||
packages/utils/dist
|
||||
packages/api/dist
|
||||
packages/responses/dist
|
||||
packages/sncrypto-common/dist
|
||||
packages/sncrypto-web/dist
|
||||
|
||||
**/.pnp.*
|
||||
**/.yarn/*
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/@types-libsodium-wrappers-npm-0.7.9-4b14af299f-5ddf61b804.zip
vendored
Normal file
BIN
.yarn/cache/@types-libsodium-wrappers-npm-0.7.9-4b14af299f-5ddf61b804.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/assertion-error-npm-1.1.0-66b893015e-fd9429d3a3.zip
vendored
Normal file
BIN
.yarn/cache/assertion-error-npm-1.1.0-66b893015e-fd9429d3a3.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/chai-npm-4.3.6-dba90e4b0b-acff93fd53.zip
vendored
Normal file
BIN
.yarn/cache/chai-npm-4.3.6-dba90e4b0b-acff93fd53.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/check-error-npm-1.0.2-00c540c6e9-d9d1065044.zip
vendored
Normal file
BIN
.yarn/cache/check-error-npm-1.0.2-00c540c6e9-d9d1065044.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/deep-eql-npm-3.0.1-9a66c09c65-4f4c9fb79e.zip
vendored
Normal file
BIN
.yarn/cache/deep-eql-npm-3.0.1-9a66c09c65-4f4c9fb79e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/get-func-name-npm-2.0.0-afbf363765-8d82e69f3e.zip
vendored
Normal file
BIN
.yarn/cache/get-func-name-npm-2.0.0-afbf363765-8d82e69f3e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/loupe-npm-2.3.4-2067703c8d-5af91db61a.zip
vendored
Normal file
BIN
.yarn/cache/loupe-npm-2.3.4-2067703c8d-5af91db61a.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/pathval-npm-1.1.1-ce0311d7e0-090e314771.zip
vendored
Normal file
BIN
.yarn/cache/pathval-npm-1.1.1-ce0311d7e0-090e314771.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/typedarray-to-buffer-npm-4.0.0-128d1f56cb-c1e4dc6597.zip
vendored
Normal file
BIN
.yarn/cache/typedarray-to-buffer-npm-4.0.0-128d1f56cb-c1e4dc6597.zip
vendored
Normal file
Binary file not shown.
@@ -42,7 +42,7 @@
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "^1.6.39",
|
||||
"@standardnotes/services": "workspace:*",
|
||||
"@standardnotes/sncrypto-common": "^1.9.0",
|
||||
"@standardnotes/sncrypto-common": "workspace:*",
|
||||
"@standardnotes/utils": "workspace:*",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"webpack-dev-server": "^4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/sncrypto-web": "^1.7.0",
|
||||
"@standardnotes/sncrypto-web": "workspace:*",
|
||||
"@standardnotes/snjs": "^2.61.3",
|
||||
"regenerator-runtime": "^0.13.9"
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "^1.6.39",
|
||||
"@standardnotes/services": "workspace:*",
|
||||
"@standardnotes/sncrypto-common": "^1.9.0",
|
||||
"@standardnotes/sncrypto-common": "workspace:*",
|
||||
"@standardnotes/utils": "workspace:*",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,19 @@ module.exports = (async () => {
|
||||
} = await getDefaultConfig()
|
||||
|
||||
return {
|
||||
watchFolders: [__dirname, '../icons', '../styles', '../components', '../features', '../encryption', '../filepicker', '../services', '../files', '../utils'],
|
||||
watchFolders: [
|
||||
__dirname,
|
||||
'../icons',
|
||||
'../styles',
|
||||
'../components',
|
||||
'../features',
|
||||
'../encryption',
|
||||
'../filepicker',
|
||||
'../services',
|
||||
'../files',
|
||||
'../utils',
|
||||
'../sncrypto-common'
|
||||
],
|
||||
transformer: {
|
||||
getTransformOptions: async () => ({
|
||||
transform: {
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"@standardnotes/react-native-aes": "^1.4.3",
|
||||
"@standardnotes/react-native-textview": "1.1.0",
|
||||
"@standardnotes/react-native-utils": "1.0.1",
|
||||
"@standardnotes/sncrypto-common": "1.9.0",
|
||||
"@standardnotes/sncrypto-common": "workspace:*",
|
||||
"@standardnotes/snjs": "^2.118.3",
|
||||
"@standardnotes/stylekit": "5.29.3",
|
||||
"@standardnotes/utils": "workspace:*",
|
||||
|
||||
1
packages/sncrypto-common/.eslintignore
Normal file
1
packages/sncrypto-common/.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
dist
|
||||
6
packages/sncrypto-common/.eslintrc
Normal file
6
packages/sncrypto-common/.eslintrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../.eslintrc",
|
||||
"parserOptions": {
|
||||
"project": "./linter.tsconfig.json"
|
||||
}
|
||||
}
|
||||
80
packages/sncrypto-common/CHANGELOG.md
Normal file
80
packages/sncrypto-common/CHANGELOG.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.9.1](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.9.0...@standardnotes/sncrypto-common@1.9.1) (2022-07-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing reflect-metadata package to all packages ([ce3a5bb](https://github.com/standardnotes/snjs/commit/ce3a5bbf3f1d2276ac4abc3eec3c6a44c8c3ba9b))
|
||||
|
||||
# [1.9.0](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.8.2...@standardnotes/sncrypto-common@1.9.0) (2022-05-20)
|
||||
|
||||
### Features
|
||||
|
||||
* authentication with PKCE mechanism ([#719](https://github.com/standardnotes/snjs/issues/719)) ([1bc19b7](https://github.com/standardnotes/snjs/commit/1bc19b79decf83a563d1cf095ee2e56f738152d1))
|
||||
|
||||
## [1.8.2](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.8.0...@standardnotes/sncrypto-common@1.8.2) (2022-05-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* config package missing dependencies ([3dec12f](https://github.com/standardnotes/snjs/commit/3dec12fa4a83a8aed8419819eafb7c34795cb09f))
|
||||
|
||||
## [1.8.1](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.8.0...@standardnotes/sncrypto-common@1.8.1) (2022-05-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* config package missing dependencies ([3dec12f](https://github.com/standardnotes/snjs/commit/3dec12fa4a83a8aed8419819eafb7c34795cb09f))
|
||||
|
||||
# [1.8.0](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.7.7...@standardnotes/sncrypto-common@1.8.0) (2022-04-28)
|
||||
|
||||
### Features
|
||||
|
||||
* refactor sncrypto to add unified sha256 and base64 usage ([#715](https://github.com/standardnotes/snjs/issues/715)) ([93aef4d](https://github.com/standardnotes/snjs/commit/93aef4d39228a63f01aa90a88e5d28c3375ed707))
|
||||
|
||||
## [1.7.7](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.7.6...@standardnotes/sncrypto-common@1.7.7) (2022-04-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-common
|
||||
|
||||
## [1.7.6](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.7.5...@standardnotes/sncrypto-common@1.7.6) (2022-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-common
|
||||
|
||||
## [1.7.5](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.7.4...@standardnotes/sncrypto-common@1.7.5) (2022-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-common
|
||||
|
||||
## [1.7.4](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.7.3...@standardnotes/sncrypto-common@1.7.4) (2022-03-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-common
|
||||
|
||||
## [1.7.3](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.7.1...@standardnotes/sncrypto-common@1.7.3) (2022-02-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add pseudo change to get lerna to trigger ([41e6817](https://github.com/standardnotes/snjs/commit/41e6817bbf726b0932cdf16f58622328b9e42803))
|
||||
* add pseudo change to get lerna to trigger ([74e8af6](https://github.com/standardnotes/snjs/commit/74e8af640e3d0b8c2f0fc7cf792f4e2cdf33b50c))
|
||||
|
||||
## [1.7.2](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.7.2...@standardnotes/sncrypto-common@1.7.2) (2022-02-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add pseudo change to get lerna to trigger ([41e6817](https://github.com/standardnotes/snjs/commit/41e6817bbf726b0932cdf16f58622328b9e42803))
|
||||
* add pseudo change to get lerna to trigger ([74e8af6](https://github.com/standardnotes/snjs/commit/74e8af640e3d0b8c2f0fc7cf792f4e2cdf33b50c))
|
||||
|
||||
## [1.7.1](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.7.0...@standardnotes/sncrypto-common@1.7.1) (2022-02-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-common
|
||||
|
||||
# [1.7.0](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-common@1.6.0...@standardnotes/sncrypto-common@1.7.0) (2022-02-16)
|
||||
|
||||
### Features
|
||||
|
||||
* syncronous crypto ([#600](https://github.com/standardnotes/snjs/issues/600)) ([66496f6](https://github.com/standardnotes/snjs/commit/66496f6487630689b76eae6cd15bcb0c31e6b9cc))
|
||||
|
||||
# 1.6.0 (2022-01-14)
|
||||
|
||||
### Features
|
||||
|
||||
* move sncrypto packages to snjs monorepo ([#554](https://github.com/standardnotes/snjs/issues/554)) ([db83991](https://github.com/standardnotes/snjs/commit/db8399190d9d10fdc31060568b836c62933fd525))
|
||||
20
packages/sncrypto-common/README.md
Normal file
20
packages/sncrypto-common/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# SNCrypto Common
|
||||
|
||||
[](https://lerna.js.org/)
|
||||
|
||||
Cryptographic primitives used by [SNJS](https://github.com/standardnotes/snjs).
|
||||
|
||||
## Installing
|
||||
|
||||
```
|
||||
yarn add @standardnotes/sncrypto-common
|
||||
```
|
||||
|
||||
## Supported Algorithms
|
||||
|
||||
- Argon2id (Libsodium.js)
|
||||
- XChaCha20+Poly1305 (Libsodium.js)
|
||||
- PBDKF2 (WebCrypto)
|
||||
- AES-CBC (WebCrypto)
|
||||
- HMAC SHA-256
|
||||
- SHA256
|
||||
11
packages/sncrypto-common/jest.config.js
Normal file
11
packages/sncrypto-common/jest.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const base = require('../../node_modules/@standardnotes/config/src/jest.json');
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.json',
|
||||
},
|
||||
}
|
||||
};
|
||||
4
packages/sncrypto-common/linter.tsconfig.json
Normal file
4
packages/sncrypto-common/linter.tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
34
packages/sncrypto-common/package.json
Normal file
34
packages/sncrypto-common/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@standardnotes/sncrypto-common",
|
||||
"version": "1.10.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
"description": "SNCrypto common",
|
||||
"main": "dist/index.js",
|
||||
"author": "Standard Notes",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
"prebuild": "yarn clean",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"watch": "tsc -p tsconfig.json --watch",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"test:unit": "yarn lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||
"@typescript-eslint/parser": "^5.12.1",
|
||||
"eslint-plugin-prettier": "^4.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"reflect-metadata": "^0.1.13"
|
||||
}
|
||||
}
|
||||
17
packages/sncrypto-common/src/AES-GCM/Aes256GcmEncrypted.ts
Normal file
17
packages/sncrypto-common/src/AES-GCM/Aes256GcmEncrypted.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Base64String } from '../Types/Base64String'
|
||||
import { HexString } from '../Types/HexString'
|
||||
|
||||
/**
|
||||
* @param iv initialization vector as a hex string
|
||||
* @param tag authentication tag as a hex string
|
||||
* @param ciphertext as a base64 string
|
||||
* @param encoding that will be applied after decrypting
|
||||
* @param aad additional authenticated data as a hex string
|
||||
*/
|
||||
export type Aes256GcmEncrypted<EncodingType> = {
|
||||
iv: HexString
|
||||
tag: HexString
|
||||
ciphertext: Base64String
|
||||
encoding: EncodingType
|
||||
aad: HexString
|
||||
}
|
||||
15
packages/sncrypto-common/src/AES-GCM/Aes256GcmInput.ts
Normal file
15
packages/sncrypto-common/src/AES-GCM/Aes256GcmInput.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { HexString } from '../Types/HexString'
|
||||
import { Unencrypted } from '../Types/Unencrypted'
|
||||
|
||||
/**
|
||||
* @param unencrypted -- UTF-8 string or a `string` with `encoding`
|
||||
* @param iv initialization vector as a hex string
|
||||
* @param key encryption key as a hex string
|
||||
* @param aad additional authenticated data as a hex string
|
||||
*/
|
||||
export type Aes256GcmInput<EncodingType> = {
|
||||
unencrypted: Unencrypted<EncodingType>
|
||||
iv: HexString
|
||||
key: HexString
|
||||
aad?: HexString
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { HexString } from '../Types/HexString'
|
||||
import { Aes256GcmEncrypted } from './Aes256GcmEncrypted'
|
||||
import { Aes256GcmInput } from './Aes256GcmInput'
|
||||
|
||||
export interface CryptoAes256GcmInterface<EncodingType> {
|
||||
/**
|
||||
* Encrypts a string using AES-GCM.
|
||||
* @param input
|
||||
* @returns An object which can be run through aes256GcmDecrypt to retrieve the input text.
|
||||
*/
|
||||
aes256GcmEncrypt(input: Aes256GcmInput<EncodingType>): Promise<Aes256GcmEncrypted<EncodingType>>
|
||||
|
||||
/**
|
||||
* Decrypts a string using AES-GCM.
|
||||
* @param encrypted
|
||||
* @param key - encryption key as a hex string
|
||||
* @returns A string encoded with encoding provided in the input
|
||||
*/
|
||||
aes256GcmDecrypt(encrypted: Aes256GcmEncrypted<EncodingType>, key: HexString): Promise<string>
|
||||
}
|
||||
3
packages/sncrypto-common/src/AES-GCM/index.ts
Normal file
3
packages/sncrypto-common/src/AES-GCM/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './Aes256GcmEncrypted'
|
||||
export * from './Aes256GcmInput'
|
||||
export * from './CryptoAes256GcmInterface'
|
||||
25
packages/sncrypto-common/src/Base64/CryptoBase64Interface.ts
Normal file
25
packages/sncrypto-common/src/Base64/CryptoBase64Interface.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Base64String } from '../Types/Base64String'
|
||||
import { Utf8String } from '../Types/Utf8String'
|
||||
|
||||
export interface CryptoBase64Interface {
|
||||
/**
|
||||
* Converts a plain string into base64
|
||||
* @param text - A plain string
|
||||
* @returns A base64 encoded string
|
||||
*/
|
||||
base64Encode(text: Utf8String): Base64String
|
||||
|
||||
/**
|
||||
* Converts a plain string into url-safe base64
|
||||
* @param text - A plain string
|
||||
* @returns A base64 encoded string
|
||||
*/
|
||||
base64URLEncode(text: Utf8String): Base64String
|
||||
|
||||
/**
|
||||
* Converts a base64 string into a plain string
|
||||
* @param base64String - A base64 encoded string
|
||||
* @returns A plain string
|
||||
*/
|
||||
base64Decode(base64String: Base64String): Utf8String
|
||||
}
|
||||
1
packages/sncrypto-common/src/Base64/index.ts
Normal file
1
packages/sncrypto-common/src/Base64/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './CryptoBase64Interface'
|
||||
200
packages/sncrypto-common/src/Common/PureCryptoInterface.ts
Normal file
200
packages/sncrypto-common/src/Common/PureCryptoInterface.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { Base64String } from '../Types/Base64String'
|
||||
import { Base64URLSafeString } from '../Types/Base64URLSafeString'
|
||||
import { HexString } from '../Types/HexString'
|
||||
import { SodiumConstant } from '../Types/SodiumConstant'
|
||||
import { StreamDecryptor } from '../Types/StreamDecryptor'
|
||||
import { StreamEncryptor } from '../Types/StreamEncryptor'
|
||||
import { Utf8String } from '../Types/Utf8String'
|
||||
|
||||
/**
|
||||
* Interface that clients have to implement to use snjs
|
||||
*/
|
||||
export interface PureCryptoInterface {
|
||||
initialize(): Promise<void>
|
||||
|
||||
/**
|
||||
* Derives a key from a password and salt using PBKDF2 via WebCrypto.
|
||||
* @param password - utf8 string
|
||||
* @param salt - utf8 string
|
||||
* @param iterations
|
||||
* @param length - In bits
|
||||
* @returns Hex string
|
||||
*/
|
||||
pbkdf2(password: Utf8String, salt: Utf8String, iterations: number, length: number): Promise<string | null>
|
||||
|
||||
/**
|
||||
* Generates a random key in hex format
|
||||
* @param bits - Length of key in bits
|
||||
* @returns A string key in hex format
|
||||
*/
|
||||
generateRandomKey(bits: number): string
|
||||
|
||||
/**
|
||||
* @legacy
|
||||
* Encrypts a string using AES-CBC via WebCrypto.
|
||||
* @param plaintext
|
||||
* @param iv - In hex format
|
||||
* @param key - In hex format
|
||||
* @returns Ciphertext in Base64 format.
|
||||
*/
|
||||
aes256CbcEncrypt(plaintext: Utf8String, iv: HexString, key: HexString): Promise<Base64String>
|
||||
|
||||
/**
|
||||
* @legacy
|
||||
* Decrypts a string using AES-CBC via WebCrypto.
|
||||
* @param ciphertext - Base64 format
|
||||
* @param iv - In hex format
|
||||
* @param key - In hex format
|
||||
* @returns Plain utf8 string or null if decryption fails
|
||||
*/
|
||||
aes256CbcDecrypt(ciphertext: Base64String, iv: HexString, key: HexString): Promise<Utf8String | null>
|
||||
|
||||
/**
|
||||
* Runs HMAC with SHA-256 on a message with key.
|
||||
* @param message - Plain utf8 string
|
||||
* @param key - In hex format
|
||||
* @returns Hex string or null if computation fails
|
||||
*/
|
||||
hmac256(message: Utf8String, key: HexString): Promise<HexString | null>
|
||||
|
||||
/**
|
||||
* @param text - Plain utf8 string
|
||||
* @returns Hex string
|
||||
*/
|
||||
sha256(text: string): Promise<string>
|
||||
|
||||
/**
|
||||
* Runs HMAC with SHA-1 on a message with key.
|
||||
* @param message - Plain utf8 string
|
||||
* @param key - In hex format
|
||||
* @returns Hex string or null if computation fails
|
||||
*/
|
||||
hmac1(message: Utf8String, key: HexString): Promise<HexString | null>
|
||||
|
||||
/**
|
||||
* Use only for legacy applications.
|
||||
* @param text - Plain utf8 string
|
||||
* @returns Hex string
|
||||
*/
|
||||
unsafeSha1(text: string): Promise<string>
|
||||
|
||||
/**
|
||||
* Derives a key from a password and salt using
|
||||
* argon2id (crypto_pwhash_ALG_DEFAULT).
|
||||
* @param password - Plain text string
|
||||
* @param salt - Salt in hex format
|
||||
* @param iterations - The algorithm's opslimit (recommended min 2)
|
||||
* @param bytes - The algorithm's memory limit (memlimit) (recommended min 67108864)
|
||||
* @param length - The output key length
|
||||
* @returns Derived key in hex format
|
||||
*/
|
||||
argon2(password: Utf8String, salt: HexString, iterations: number, bytes: number, length: number): HexString
|
||||
|
||||
/**
|
||||
* Encrypt a message (and associated data) with XChaCha20-Poly1305.
|
||||
* @param plaintext
|
||||
* @param nonce - In hex format
|
||||
* @param key - In hex format
|
||||
* @param assocData
|
||||
* @returns Base64 ciphertext string
|
||||
*/
|
||||
xchacha20Encrypt(plaintext: Utf8String, nonce: HexString, key: HexString, assocData: Utf8String): Base64String
|
||||
|
||||
/**
|
||||
* Decrypt a message (and associated data) with XChaCha20-Poly1305
|
||||
* @param ciphertext
|
||||
* @param nonce - In hex format
|
||||
* @param key - In hex format
|
||||
* @param assocData
|
||||
* @returns Plain utf8 string or null if decryption fails
|
||||
*/
|
||||
xchacha20Decrypt(
|
||||
ciphertext: Base64String,
|
||||
nonce: HexString,
|
||||
key: HexString,
|
||||
assocData: Utf8String | Uint8Array,
|
||||
): Utf8String | null
|
||||
|
||||
xchacha20StreamInitEncryptor(key: HexString): StreamEncryptor
|
||||
|
||||
xchacha20StreamEncryptorPush(
|
||||
encryptor: StreamEncryptor,
|
||||
plainBuffer: Uint8Array,
|
||||
assocData: Utf8String,
|
||||
tag?: SodiumConstant,
|
||||
): Uint8Array
|
||||
|
||||
xchacha20StreamInitDecryptor(header: Base64String, key: HexString): StreamDecryptor
|
||||
|
||||
xchacha20StreamDecryptorPush(
|
||||
decryptor: StreamDecryptor,
|
||||
encryptedBuffer: Uint8Array,
|
||||
assocData: Utf8String,
|
||||
): { message: Uint8Array; tag: SodiumConstant } | false
|
||||
|
||||
/**
|
||||
* Converts a plain string into base64
|
||||
* @param text - A plain string
|
||||
* @returns A base64 encoded string
|
||||
*/
|
||||
base64Encode(text: Utf8String): Base64String
|
||||
|
||||
/**
|
||||
* Converts a plain string into url safe base64
|
||||
* @param text - A plain string
|
||||
* @returns A base64 url safe encoded string
|
||||
*/
|
||||
base64URLEncode(text: Utf8String): Base64URLSafeString
|
||||
|
||||
/**
|
||||
* Converts a base64 string into a plain string
|
||||
* @param base64String - A base64 encoded string
|
||||
* @returns A plain string
|
||||
*/
|
||||
base64Decode(base64String: Base64String): Utf8String
|
||||
|
||||
deinit(): void
|
||||
|
||||
/**
|
||||
* Generates a UUID string syncronously.
|
||||
*/
|
||||
generateUUID(): string
|
||||
|
||||
/**
|
||||
* Constant-time string comparison
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
timingSafeEqual(a: string, b: string): boolean
|
||||
|
||||
/**
|
||||
* Generates a random secret for TOTP authentication
|
||||
*
|
||||
* RFC4226 reccomends a length of at least 160 bits = 32 b32 chars
|
||||
* https://datatracker.ietf.org/doc/html/rfc4226#section-4
|
||||
*/
|
||||
generateOtpSecret(): Promise<string>
|
||||
|
||||
/**
|
||||
* Generates a HOTP code as per RFC4226 specification
|
||||
* using HMAC-SHA1
|
||||
* https://datatracker.ietf.org/doc/html/rfc4226
|
||||
*
|
||||
* @param secret OTP shared secret
|
||||
* @param counter HOTP counter
|
||||
* @returns HOTP auth code
|
||||
*/
|
||||
hotpToken(secret: string, counter: number, tokenLength: number): Promise<string>
|
||||
|
||||
/**
|
||||
* Generates a TOTP code as per RFC6238 specification
|
||||
* using HMAC-SHA1
|
||||
* https://datatracker.ietf.org/doc/html/rfc6238
|
||||
*
|
||||
* @param secret OTP shared secret
|
||||
* @param timestamp time specified in milliseconds since UNIX epoch
|
||||
* @param step time step specified in seconds
|
||||
* @returns TOTP auth code
|
||||
*/
|
||||
totpToken(secret: string, timestamp: number, tokenLength: number, step: number): Promise<string>
|
||||
}
|
||||
22
packages/sncrypto-common/src/Common/Utils.ts
Normal file
22
packages/sncrypto-common/src/Common/Utils.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Constant-time string comparison
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
export function timingSafeEqual(a: string, b: string): boolean {
|
||||
const strA = String(a)
|
||||
let strB = String(b)
|
||||
const lenA = strA.length
|
||||
let result = 0
|
||||
|
||||
if (lenA !== strB.length) {
|
||||
strB = strA
|
||||
result = 1
|
||||
}
|
||||
|
||||
for (let i = 0; i < lenA; i++) {
|
||||
result |= strA.charCodeAt(i) ^ strB.charCodeAt(i)
|
||||
}
|
||||
|
||||
return result === 0
|
||||
}
|
||||
2
packages/sncrypto-common/src/Common/index.ts
Normal file
2
packages/sncrypto-common/src/Common/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './PureCryptoInterface'
|
||||
export * from './Utils'
|
||||
@@ -0,0 +1,6 @@
|
||||
import { HexString } from '../Types/HexString'
|
||||
import { Utf8String } from '../Types/Utf8String'
|
||||
|
||||
export interface CryptoSha256Interface {
|
||||
sha256(text: Utf8String): HexString
|
||||
}
|
||||
1
packages/sncrypto-common/src/SHA/index.ts
Normal file
1
packages/sncrypto-common/src/SHA/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './CryptoSha256Interface'
|
||||
1
packages/sncrypto-common/src/Types/Base64String.ts
Normal file
1
packages/sncrypto-common/src/Types/Base64String.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type Base64String = string
|
||||
@@ -0,0 +1 @@
|
||||
export type Base64URLSafeString = string
|
||||
1
packages/sncrypto-common/src/Types/HexString.ts
Normal file
1
packages/sncrypto-common/src/Types/HexString.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type HexString = string
|
||||
11
packages/sncrypto-common/src/Types/SodiumConstant.ts
Normal file
11
packages/sncrypto-common/src/Types/SodiumConstant.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export enum SodiumConstant {
|
||||
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES = 52,
|
||||
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES = 17,
|
||||
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES = 24,
|
||||
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES = 32,
|
||||
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH = 0,
|
||||
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL = 1,
|
||||
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY = 2,
|
||||
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL = 3,
|
||||
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX = 0x3fffffff80,
|
||||
}
|
||||
1
packages/sncrypto-common/src/Types/SodiumStateAddress.ts
Normal file
1
packages/sncrypto-common/src/Types/SodiumStateAddress.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type SodiumStateAddress = unknown
|
||||
5
packages/sncrypto-common/src/Types/StreamDecryptor.ts
Normal file
5
packages/sncrypto-common/src/Types/StreamDecryptor.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { SodiumStateAddress } from './SodiumStateAddress'
|
||||
|
||||
export type StreamDecryptor = {
|
||||
state: SodiumStateAddress
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { SodiumConstant } from './SodiumConstant'
|
||||
|
||||
export type StreamDecryptorResult = {
|
||||
message: Uint8Array
|
||||
tag: SodiumConstant
|
||||
}
|
||||
7
packages/sncrypto-common/src/Types/StreamEncryptor.ts
Normal file
7
packages/sncrypto-common/src/Types/StreamEncryptor.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Base64String } from './Base64String'
|
||||
import { SodiumStateAddress } from './SodiumStateAddress'
|
||||
|
||||
export type StreamEncryptor = {
|
||||
state: SodiumStateAddress
|
||||
header: Base64String
|
||||
}
|
||||
6
packages/sncrypto-common/src/Types/Unencrypted.ts
Normal file
6
packages/sncrypto-common/src/Types/Unencrypted.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Utf8String } from './Utf8String'
|
||||
|
||||
/**
|
||||
* Either a plaintext (UTF-8 string) or a `string` with an `encoding`.
|
||||
*/
|
||||
export type Unencrypted<EncodingType> = Utf8String | { string: string; encoding: EncodingType }
|
||||
1
packages/sncrypto-common/src/Types/Utf8String.ts
Normal file
1
packages/sncrypto-common/src/Types/Utf8String.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type Utf8String = string
|
||||
10
packages/sncrypto-common/src/Types/index.ts
Normal file
10
packages/sncrypto-common/src/Types/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export * from './Base64String'
|
||||
export * from './Base64URLSafeString'
|
||||
export * from './HexString'
|
||||
export * from './SodiumConstant'
|
||||
export * from './SodiumStateAddress'
|
||||
export * from './StreamDecryptor'
|
||||
export * from './StreamDecryptorResult'
|
||||
export * from './StreamEncryptor'
|
||||
export * from './Unencrypted'
|
||||
export * from './Utf8String'
|
||||
5
packages/sncrypto-common/src/index.ts
Normal file
5
packages/sncrypto-common/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './AES-GCM'
|
||||
export * from './Base64'
|
||||
export * from './Common'
|
||||
export * from './SHA'
|
||||
export * from './Types'
|
||||
13
packages/sncrypto-common/tsconfig.json
Normal file
13
packages/sncrypto-common/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../node_modules/@standardnotes/config/src/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"references": [],
|
||||
"exclude": ["**/*.spec.ts", "dist", "node_modules"]
|
||||
}
|
||||
4
packages/sncrypto-web/.eslintignore
Normal file
4
packages/sncrypto-web/.eslintignore
Normal file
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
test
|
||||
*.config.js
|
||||
test-server.js
|
||||
6
packages/sncrypto-web/.eslintrc
Normal file
6
packages/sncrypto-web/.eslintrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../.eslintrc",
|
||||
"parserOptions": {
|
||||
"project": "./linter.tsconfig.json"
|
||||
}
|
||||
}
|
||||
98
packages/sncrypto-web/CHANGELOG.md
Normal file
98
packages/sncrypto-web/CHANGELOG.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.10.2](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.10.1...@standardnotes/sncrypto-web@1.10.2) (2022-07-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing reflect-metadata package to all packages ([ce3a5bb](https://github.com/standardnotes/snjs/commit/ce3a5bbf3f1d2276ac4abc3eec3c6a44c8c3ba9b))
|
||||
|
||||
## [1.10.1](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.10.0...@standardnotes/sncrypto-web@1.10.1) (2022-05-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* base64 url encode to be with no padding on sncrypto-web ([57905ed](https://github.com/standardnotes/snjs/commit/57905eda8b4532b468c970f2f0a1cb71bc1e217d))
|
||||
|
||||
# [1.10.0](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.9.2...@standardnotes/sncrypto-web@1.10.0) (2022-05-20)
|
||||
|
||||
### Features
|
||||
|
||||
* authentication with PKCE mechanism ([#719](https://github.com/standardnotes/snjs/issues/719)) ([1bc19b7](https://github.com/standardnotes/snjs/commit/1bc19b79decf83a563d1cf095ee2e56f738152d1))
|
||||
|
||||
## [1.9.2](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.9.0...@standardnotes/sncrypto-web@1.9.2) (2022-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-web
|
||||
|
||||
## [1.9.1](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.9.0...@standardnotes/sncrypto-web@1.9.1) (2022-05-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-web
|
||||
|
||||
# [1.9.0](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.8.4...@standardnotes/sncrypto-web@1.9.0) (2022-04-28)
|
||||
|
||||
### Features
|
||||
|
||||
* refactor sncrypto to add unified sha256 and base64 usage ([#715](https://github.com/standardnotes/snjs/issues/715)) ([93aef4d](https://github.com/standardnotes/snjs/commit/93aef4d39228a63f01aa90a88e5d28c3375ed707))
|
||||
|
||||
## [1.8.4](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.8.3...@standardnotes/sncrypto-web@1.8.4) (2022-04-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-web
|
||||
|
||||
## [1.8.3](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.8.2...@standardnotes/sncrypto-web@1.8.3) (2022-04-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-web
|
||||
|
||||
## [1.8.2](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.8.1...@standardnotes/sncrypto-web@1.8.2) (2022-04-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-web
|
||||
|
||||
## [1.8.1](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.8.0...@standardnotes/sncrypto-web@1.8.1) (2022-03-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-web
|
||||
|
||||
# [1.8.0](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.7.3...@standardnotes/sncrypto-web@1.8.0) (2022-03-14)
|
||||
|
||||
### Features
|
||||
|
||||
* move vault into applications package ([#653](https://github.com/standardnotes/snjs/issues/653)) ([3d320eb](https://github.com/standardnotes/snjs/commit/3d320eb51ac74729ab8864f1c4c4f24d8fb794d5))
|
||||
|
||||
## [1.7.3](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.7.1...@standardnotes/sncrypto-web@1.7.3) (2022-02-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add pseudo change to get lerna to trigger ([41e6817](https://github.com/standardnotes/snjs/commit/41e6817bbf726b0932cdf16f58622328b9e42803))
|
||||
* add pseudo change to get lerna to trigger ([74e8af6](https://github.com/standardnotes/snjs/commit/74e8af640e3d0b8c2f0fc7cf792f4e2cdf33b50c))
|
||||
|
||||
## [1.7.2](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.7.1...@standardnotes/sncrypto-web@1.7.2) (2022-02-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add pseudo change to get lerna to trigger ([41e6817](https://github.com/standardnotes/snjs/commit/41e6817bbf726b0932cdf16f58622328b9e42803))
|
||||
* add pseudo change to get lerna to trigger ([74e8af6](https://github.com/standardnotes/snjs/commit/74e8af640e3d0b8c2f0fc7cf792f4e2cdf33b50c))
|
||||
|
||||
## [1.7.1](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.7.0...@standardnotes/sncrypto-web@1.7.1) (2022-02-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-web
|
||||
|
||||
# [1.7.0](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.6.2...@standardnotes/sncrypto-web@1.7.0) (2022-02-16)
|
||||
|
||||
### Features
|
||||
|
||||
* syncronous crypto ([#600](https://github.com/standardnotes/snjs/issues/600)) ([66496f6](https://github.com/standardnotes/snjs/commit/66496f6487630689b76eae6cd15bcb0c31e6b9cc))
|
||||
|
||||
## [1.6.2](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.6.1...@standardnotes/sncrypto-web@1.6.2) (2022-01-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* CodeQL analysis and SNCrypto-* packages ([#568](https://github.com/standardnotes/snjs/issues/568)) ([137ed46](https://github.com/standardnotes/snjs/commit/137ed46d8f16509211cda265f653c016fe111974))
|
||||
|
||||
## [1.6.1](https://github.com/standardnotes/snjs/compare/@standardnotes/sncrypto-web@1.6.0...@standardnotes/sncrypto-web@1.6.1) (2022-01-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-web
|
||||
|
||||
# 1.6.0 (2022-01-14)
|
||||
|
||||
### Features
|
||||
|
||||
* move sncrypto packages to snjs monorepo ([#554](https://github.com/standardnotes/snjs/issues/554)) ([db83991](https://github.com/standardnotes/snjs/commit/db8399190d9d10fdc31060568b836c62933fd525))
|
||||
28
packages/sncrypto-web/README.md
Normal file
28
packages/sncrypto-web/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# SNCrypto Web
|
||||
|
||||
[](https://lerna.js.org/)
|
||||
|
||||
Cryptographic primitives as a web library (UMD) - used by [SNJS](https://github.com/standardnotes/snjs).
|
||||
|
||||
## Installing
|
||||
|
||||
```
|
||||
yarn add @standardnotes/sncrypto-web
|
||||
```
|
||||
|
||||
## Supported Algorithms
|
||||
|
||||
- Argon2id (Libsodium.js)
|
||||
- XChaCha20+Poly1305 (Libsodium.js)
|
||||
- PBDKF2 (WebCrypto)
|
||||
- AES-CBC (WebCrypto)
|
||||
- HMAC SHA-256
|
||||
- SHA256
|
||||
|
||||
## Tests
|
||||
|
||||
Tests are run in the browser due to WebCrypto and WebAssembly dependency.
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
8
packages/sncrypto-web/babel.config.js
Normal file
8
packages/sncrypto-web/babel.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = function (api) {
|
||||
api.cache.forever()
|
||||
|
||||
return {
|
||||
presets: ['@babel/preset-env'],
|
||||
plugins: ['@babel/plugin-syntax-dynamic-import'],
|
||||
}
|
||||
}
|
||||
4
packages/sncrypto-web/linter.tsconfig.json
Normal file
4
packages/sncrypto-web/linter.tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
53
packages/sncrypto-web/package.json
Normal file
53
packages/sncrypto-web/package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@standardnotes/sncrypto-web",
|
||||
"version": "1.11.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
"description": "SNCrypto Web",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist/**/*.js",
|
||||
"dist/**/*.js.map",
|
||||
"dist/**/*.d.ts"
|
||||
],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
"prebuild": "yarn clean",
|
||||
"build": "webpack --mode production && tsc",
|
||||
"watch": "webpack --mode production --watch",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"test:e2e": "node test-server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/sncrypto-common": "workspace:*",
|
||||
"buffer": "^6.0.3",
|
||||
"libsodium-wrappers": "^0.7.10",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/preset-env": "^7.18.6",
|
||||
"@types/libsodium-wrappers": "^0.7.9",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||
"babel-loader": "^8.2.3",
|
||||
"chai": "^4.3.6",
|
||||
"connect": "^3.7.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"serve-static": "^1.14.2",
|
||||
"ts-loader": "^9.2.6",
|
||||
"typedarray-to-buffer": "^4.0.0",
|
||||
"uuid": "^8.3.2",
|
||||
"webpack": "^5.69.1",
|
||||
"webpack-cli": "^4.9.2"
|
||||
}
|
||||
}
|
||||
400
packages/sncrypto-web/src/crypto.ts
Normal file
400
packages/sncrypto-web/src/crypto.ts
Normal file
@@ -0,0 +1,400 @@
|
||||
import {
|
||||
StreamEncryptor,
|
||||
StreamDecryptor,
|
||||
SodiumConstant,
|
||||
StreamDecryptorResult,
|
||||
Base64String,
|
||||
Base64URLSafeString,
|
||||
HexString,
|
||||
PureCryptoInterface,
|
||||
Utf8String,
|
||||
timingSafeEqual,
|
||||
} from '@standardnotes/sncrypto-common'
|
||||
import * as Utils from './utils'
|
||||
import * as sodium from './libsodium'
|
||||
|
||||
enum WebCryptoAlgs {
|
||||
AesCbc = 'AES-CBC',
|
||||
Sha512 = 'SHA-512',
|
||||
Sha256 = 'SHA-256',
|
||||
Pbkdf2 = 'PBKDF2',
|
||||
Sha1 = 'SHA-1',
|
||||
Hmac = 'HMAC',
|
||||
}
|
||||
|
||||
enum WebCryptoActions {
|
||||
DeriveBits = 'deriveBits',
|
||||
Encrypt = 'encrypt',
|
||||
Decrypt = 'decrypt',
|
||||
Sign = 'sign',
|
||||
}
|
||||
|
||||
type WebCryptoParams = {
|
||||
name: string
|
||||
hash?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The web crypto class allows access to a set of cryptographic primitives available
|
||||
* in a web environment, consisting of two main sources:
|
||||
* — Built-in browser WebCrypto
|
||||
* — Libsodium.js library integration
|
||||
*/
|
||||
export class SNWebCrypto implements PureCryptoInterface {
|
||||
private ready: Promise<void> | null
|
||||
|
||||
constructor() {
|
||||
/** Functions using Libsodium must await this
|
||||
* promise before performing any library functions */
|
||||
this.ready = sodium.ready
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
await this.ready
|
||||
}
|
||||
|
||||
deinit(): void {
|
||||
this.ready = null
|
||||
}
|
||||
|
||||
public generateUUID(): string {
|
||||
return Utils.generateUUID()
|
||||
}
|
||||
|
||||
public timingSafeEqual(a: string, b: string): boolean {
|
||||
return timingSafeEqual(a, b)
|
||||
}
|
||||
|
||||
public base64Encode(text: Utf8String): string {
|
||||
return Utils.base64Encode(text)
|
||||
}
|
||||
|
||||
public base64URLEncode(text: Utf8String): Base64URLSafeString {
|
||||
return Utils.base64URLEncode(text)
|
||||
}
|
||||
|
||||
public base64Decode(base64String: Base64String): string {
|
||||
return Utils.base64Decode(base64String)
|
||||
}
|
||||
|
||||
public async pbkdf2(
|
||||
password: Utf8String,
|
||||
salt: Utf8String,
|
||||
iterations: number,
|
||||
length: number,
|
||||
): Promise<HexString | null> {
|
||||
const keyData = await Utils.stringToArrayBuffer(password)
|
||||
const key = await this.webCryptoImportKey(keyData, WebCryptoAlgs.Pbkdf2, [WebCryptoActions.DeriveBits])
|
||||
if (!key) {
|
||||
console.error('Key is null, unable to continue')
|
||||
return null
|
||||
}
|
||||
return this.webCryptoDeriveBits(key, salt, iterations, length)
|
||||
}
|
||||
|
||||
public generateRandomKey(bits: number): string {
|
||||
const bytes = bits / 8
|
||||
const arrayBuffer = Utils.getGlobalScope().crypto.getRandomValues(new Uint8Array(bytes))
|
||||
return Utils.arrayBufferToHexString(arrayBuffer)
|
||||
}
|
||||
|
||||
public async aes256CbcEncrypt(plaintext: Utf8String, iv: HexString, key: HexString): Promise<Base64String> {
|
||||
const keyData = await Utils.hexStringToArrayBuffer(key)
|
||||
const ivData = await Utils.hexStringToArrayBuffer(iv)
|
||||
const alg = { name: WebCryptoAlgs.AesCbc, iv: ivData }
|
||||
const importedKeyData = await this.webCryptoImportKey(keyData, alg.name, [WebCryptoActions.Encrypt])
|
||||
const textData = await Utils.stringToArrayBuffer(plaintext)
|
||||
const result = await crypto.subtle.encrypt(alg, importedKeyData, textData)
|
||||
return Utils.arrayBufferToBase64(result)
|
||||
}
|
||||
|
||||
public async aes256CbcDecrypt(ciphertext: Base64String, iv: HexString, key: HexString): Promise<Utf8String | null> {
|
||||
const keyData = await Utils.hexStringToArrayBuffer(key)
|
||||
const ivData = await Utils.hexStringToArrayBuffer(iv)
|
||||
const alg = { name: WebCryptoAlgs.AesCbc, iv: ivData }
|
||||
const importedKeyData = await this.webCryptoImportKey(keyData, alg.name, [WebCryptoActions.Decrypt])
|
||||
const textData = await Utils.base64ToArrayBuffer(ciphertext)
|
||||
|
||||
try {
|
||||
const result = await crypto.subtle.decrypt(alg, importedKeyData, textData)
|
||||
|
||||
return Utils.arrayBufferToString(result)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
public async hmac256(message: Utf8String, key: HexString): Promise<HexString | null> {
|
||||
const keyHexData = Utils.hexStringToArrayBuffer(key)
|
||||
const keyData = await this.webCryptoImportKey(keyHexData, WebCryptoAlgs.Hmac, [WebCryptoActions.Sign], {
|
||||
name: WebCryptoAlgs.Sha256,
|
||||
})
|
||||
const messageData = Utils.stringToArrayBuffer(message)
|
||||
const funcParams = { name: WebCryptoAlgs.Hmac }
|
||||
|
||||
try {
|
||||
const signature = await crypto.subtle.sign(funcParams, keyData, messageData)
|
||||
|
||||
return Utils.arrayBufferToHexString(signature)
|
||||
} catch (error) {
|
||||
console.error('Error computing HMAC:', error)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
public async sha256(text: string): Promise<string> {
|
||||
const textData = Utils.stringToArrayBuffer(text)
|
||||
const digest = await crypto.subtle.digest(WebCryptoAlgs.Sha256, textData)
|
||||
return Utils.arrayBufferToHexString(digest)
|
||||
}
|
||||
|
||||
public async hmac1(message: Utf8String, key: HexString): Promise<HexString | null> {
|
||||
const keyHexData = await Utils.hexStringToArrayBuffer(key)
|
||||
const keyData = await this.webCryptoImportKey(keyHexData, WebCryptoAlgs.Hmac, [WebCryptoActions.Sign], {
|
||||
name: WebCryptoAlgs.Sha1,
|
||||
})
|
||||
const messageData = await Utils.stringToArrayBuffer(message)
|
||||
const funcParams = { name: WebCryptoAlgs.Hmac }
|
||||
|
||||
try {
|
||||
const signature = await crypto.subtle.sign(funcParams, keyData, messageData)
|
||||
|
||||
return Utils.arrayBufferToHexString(signature)
|
||||
} catch (error) {
|
||||
console.error('Error computing HMAC:', error)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
public async unsafeSha1(text: string): Promise<string> {
|
||||
const textData = await Utils.stringToArrayBuffer(text)
|
||||
const digest = await crypto.subtle.digest(WebCryptoAlgs.Sha1, textData)
|
||||
return Utils.arrayBufferToHexString(digest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a raw string key to a WebCrypto CryptoKey object.
|
||||
* @param rawKey
|
||||
* A plain utf8 string or an array buffer
|
||||
* @param alg
|
||||
* The name of the algorithm this key will be used for (i.e 'AES-CBC' or 'HMAC')
|
||||
* @param actions
|
||||
* The actions this key will be used for (i.e 'deriveBits' or 'encrypt')
|
||||
* @param hash
|
||||
* An optional object representing the hashing function this key is intended to be
|
||||
* used for. This option is only supplied when the `alg` is HMAC.
|
||||
* @param hash.name
|
||||
* The name of the hashing function to use with HMAC.
|
||||
* @returns A WebCrypto CryptoKey object
|
||||
*/
|
||||
private async webCryptoImportKey(
|
||||
keyData: Uint8Array,
|
||||
alg: WebCryptoAlgs,
|
||||
actions: Array<WebCryptoActions>,
|
||||
hash?: WebCryptoParams,
|
||||
): Promise<CryptoKey> {
|
||||
return Utils.getSubtleCrypto().importKey(
|
||||
'raw',
|
||||
keyData,
|
||||
{
|
||||
name: alg,
|
||||
hash: hash,
|
||||
},
|
||||
false,
|
||||
actions,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs WebCrypto PBKDF2 derivation.
|
||||
* @param key - A WebCrypto CryptoKey object
|
||||
* @param length - In bits
|
||||
*/
|
||||
private async webCryptoDeriveBits(
|
||||
key: CryptoKey,
|
||||
salt: Utf8String,
|
||||
iterations: number,
|
||||
length: number,
|
||||
): Promise<HexString> {
|
||||
const params = {
|
||||
name: WebCryptoAlgs.Pbkdf2,
|
||||
salt: await Utils.stringToArrayBuffer(salt),
|
||||
iterations: iterations,
|
||||
hash: { name: WebCryptoAlgs.Sha512 },
|
||||
}
|
||||
|
||||
return Utils.getSubtleCrypto()
|
||||
.deriveBits(params, key, length)
|
||||
.then((bits) => {
|
||||
return Utils.arrayBufferToHexString(new Uint8Array(bits))
|
||||
})
|
||||
}
|
||||
|
||||
public argon2(password: Utf8String, salt: HexString, iterations: number, bytes: number, length: number): HexString {
|
||||
const result = sodium.crypto_pwhash(
|
||||
length,
|
||||
Utils.stringToArrayBuffer(password),
|
||||
Utils.hexStringToArrayBuffer(salt),
|
||||
iterations,
|
||||
bytes,
|
||||
sodium.crypto_pwhash_ALG_DEFAULT,
|
||||
'hex',
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
public xchacha20Encrypt(
|
||||
plaintext: Utf8String,
|
||||
nonce: HexString,
|
||||
key: HexString,
|
||||
assocData: Utf8String,
|
||||
): Base64String {
|
||||
if (nonce.length !== 48) {
|
||||
throw Error('Nonce must be 24 bytes')
|
||||
}
|
||||
const arrayBuffer = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||
plaintext,
|
||||
assocData,
|
||||
null,
|
||||
Utils.hexStringToArrayBuffer(nonce),
|
||||
Utils.hexStringToArrayBuffer(key),
|
||||
)
|
||||
return Utils.arrayBufferToBase64(arrayBuffer)
|
||||
}
|
||||
|
||||
public xchacha20Decrypt(
|
||||
ciphertext: Base64String,
|
||||
nonce: HexString,
|
||||
key: HexString,
|
||||
assocData: Utf8String | Uint8Array,
|
||||
): Utf8String | null {
|
||||
if (nonce.length !== 48) {
|
||||
throw Error('Nonce must be 24 bytes')
|
||||
}
|
||||
try {
|
||||
return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||
null,
|
||||
Utils.base64ToArrayBuffer(ciphertext),
|
||||
assocData,
|
||||
Utils.hexStringToArrayBuffer(nonce),
|
||||
Utils.hexStringToArrayBuffer(key),
|
||||
'text',
|
||||
)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
public xchacha20StreamInitEncryptor(key: HexString): StreamEncryptor {
|
||||
const res = sodium.crypto_secretstream_xchacha20poly1305_init_push(Utils.hexStringToArrayBuffer(key))
|
||||
return {
|
||||
state: res.state,
|
||||
header: Utils.arrayBufferToBase64(res.header),
|
||||
}
|
||||
}
|
||||
|
||||
public xchacha20StreamEncryptorPush(
|
||||
encryptor: StreamEncryptor,
|
||||
plainBuffer: Uint8Array,
|
||||
assocData: Utf8String,
|
||||
tag: SodiumConstant = SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH,
|
||||
): Uint8Array {
|
||||
const encryptedBuffer = sodium.crypto_secretstream_xchacha20poly1305_push(
|
||||
encryptor.state as sodium.StateAddress,
|
||||
plainBuffer,
|
||||
assocData.length > 0 ? Utils.stringToArrayBuffer(assocData) : null,
|
||||
tag,
|
||||
)
|
||||
return encryptedBuffer
|
||||
}
|
||||
|
||||
public xchacha20StreamInitDecryptor(header: Base64String, key: HexString): StreamDecryptor {
|
||||
const rawHeader = Utils.base64ToArrayBuffer(header)
|
||||
|
||||
if (rawHeader.length !== SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES) {
|
||||
throw new Error(`Header must be ${SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES} bytes long`)
|
||||
}
|
||||
|
||||
const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(rawHeader, Utils.hexStringToArrayBuffer(key))
|
||||
|
||||
return { state }
|
||||
}
|
||||
|
||||
public xchacha20StreamDecryptorPush(
|
||||
decryptor: StreamDecryptor,
|
||||
encryptedBuffer: Uint8Array,
|
||||
assocData: Utf8String,
|
||||
): StreamDecryptorResult | false {
|
||||
if (encryptedBuffer.length < SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) {
|
||||
throw new Error('Invalid ciphertext size')
|
||||
}
|
||||
|
||||
const result = sodium.crypto_secretstream_xchacha20poly1305_pull(
|
||||
decryptor.state as sodium.StateAddress,
|
||||
encryptedBuffer,
|
||||
assocData.length > 0 ? Utils.stringToArrayBuffer(assocData) : null,
|
||||
)
|
||||
|
||||
if ((result as unknown) === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random secret for TOTP authentication
|
||||
*
|
||||
* RFC4226 reccomends a length of at least 160 bits = 32 b32 chars
|
||||
* https://datatracker.ietf.org/doc/html/rfc4226#section-4
|
||||
*/
|
||||
public async generateOtpSecret(): Promise<string> {
|
||||
const bits = 160
|
||||
const bytes = bits / 8
|
||||
const secretBytes = Utils.getGlobalScope().crypto.getRandomValues(new Uint8Array(bytes))
|
||||
const secret = Utils.base32Encode(secretBytes)
|
||||
return secret
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a HOTP code as per RFC4226 specification
|
||||
* using HMAC-SHA1
|
||||
* https://datatracker.ietf.org/doc/html/rfc4226
|
||||
*
|
||||
* @param secret OTP shared secret
|
||||
* @param counter HOTP counter
|
||||
* @returns HOTP auth code
|
||||
*/
|
||||
public async hotpToken(secret: string, counter: number, tokenLength = 6): Promise<string> {
|
||||
const bytes = new Uint8Array(Utils.base32Decode(secret))
|
||||
|
||||
const key = await this.webCryptoImportKey(bytes, WebCryptoAlgs.Hmac, [WebCryptoActions.Sign], {
|
||||
name: WebCryptoAlgs.Sha1,
|
||||
})
|
||||
|
||||
const counterArray = Utils.padStart(counter)
|
||||
const hs = await Utils.getSubtleCrypto().sign('HMAC', key, counterArray)
|
||||
const sNum = Utils.truncateOTP(hs)
|
||||
const padded = ('0'.repeat(tokenLength) + (sNum % 10 ** tokenLength)).slice(-tokenLength)
|
||||
|
||||
return padded
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a TOTP code as per RFC6238 specification
|
||||
* using HMAC-SHA1
|
||||
* https://datatracker.ietf.org/doc/html/rfc6238
|
||||
*
|
||||
* @param secret OTP shared secret
|
||||
* @param timestamp time specified in milliseconds since UNIX epoch
|
||||
* @param step time step specified in seconds
|
||||
* @returns TOTP auth code
|
||||
*/
|
||||
public async totpToken(secret: string, timestamp: number, tokenLength = 6, step = 30): Promise<string> {
|
||||
const time = Math.floor(timestamp / step / 1000.0)
|
||||
const token = await this.hotpToken(secret, time, tokenLength)
|
||||
return token
|
||||
}
|
||||
}
|
||||
16
packages/sncrypto-web/src/index.ts
Normal file
16
packages/sncrypto-web/src/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export { SNWebCrypto } from './crypto'
|
||||
export {
|
||||
arrayBufferToBase64,
|
||||
arrayBufferToHexString,
|
||||
arrayBufferToString,
|
||||
base64Decode,
|
||||
base64Encode,
|
||||
base64ToArrayBuffer,
|
||||
base64ToHex,
|
||||
hexStringToArrayBuffer,
|
||||
hexToBase64,
|
||||
isWebCryptoAvailable,
|
||||
stringToArrayBuffer,
|
||||
base32Decode,
|
||||
base32Encode,
|
||||
} from './utils'
|
||||
21
packages/sncrypto-web/src/libsodium.ts
Normal file
21
packages/sncrypto-web/src/libsodium.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable camelcase */
|
||||
export {
|
||||
base64_variants,
|
||||
crypto_aead_xchacha20poly1305_ietf_decrypt,
|
||||
crypto_aead_xchacha20poly1305_ietf_encrypt,
|
||||
crypto_secretstream_xchacha20poly1305_push,
|
||||
crypto_secretstream_xchacha20poly1305_pull,
|
||||
crypto_secretstream_xchacha20poly1305_init_push,
|
||||
crypto_secretstream_xchacha20poly1305_init_pull,
|
||||
crypto_pwhash_ALG_DEFAULT,
|
||||
crypto_pwhash,
|
||||
from_base64,
|
||||
from_hex,
|
||||
from_string,
|
||||
ready,
|
||||
to_base64,
|
||||
to_hex,
|
||||
to_string,
|
||||
} from 'libsodium-wrappers'
|
||||
|
||||
export type { StateAddress } from 'libsodium-wrappers'
|
||||
252
packages/sncrypto-web/src/utils.ts
Normal file
252
packages/sncrypto-web/src/utils.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { base64_variants, from_base64, from_hex, from_string, to_base64, to_hex, to_string } from './libsodium'
|
||||
import { Buffer } from 'buffer'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
const SN_BASE64_VARIANT = base64_variants.ORIGINAL
|
||||
|
||||
/**
|
||||
* Libsodium's to_* functions take either a Buffer or String, but do not take raw buffers,
|
||||
* as may be returned by WebCrypto API.
|
||||
*/
|
||||
|
||||
declare global {
|
||||
interface Document {
|
||||
documentMode?: string
|
||||
}
|
||||
interface Window {
|
||||
msCrypto?: Crypto
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `window` if available, or `global` if supported in environment.
|
||||
*/
|
||||
export function getGlobalScope(): Window & typeof globalThis {
|
||||
return window
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether we are in an Internet Explorer or Edge environment
|
||||
* @access public
|
||||
*/
|
||||
export function ieOrEdge(): boolean {
|
||||
return (typeof document !== 'undefined' && !!document.documentMode) || /Edge/.test(navigator.userAgent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if WebCrypto is available
|
||||
* @access public
|
||||
*/
|
||||
export function isWebCryptoAvailable(): boolean {
|
||||
return !ieOrEdge() && getGlobalScope().crypto && !!getGlobalScope().crypto.subtle
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WebCrypto instance
|
||||
* @access public
|
||||
*/
|
||||
export function getSubtleCrypto(): SubtleCrypto {
|
||||
if (!getGlobalScope().crypto) {
|
||||
throw Error('Could not obtain SubtleCrypto instance')
|
||||
}
|
||||
|
||||
return getGlobalScope().crypto.subtle
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a UUID syncronously
|
||||
* @access public
|
||||
*/
|
||||
export function generateUUID(): string {
|
||||
return uuidv4()
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a plain string into an ArrayBuffer
|
||||
* @param {string} string - A plain string
|
||||
*/
|
||||
export function stringToArrayBuffer(string: string): Uint8Array {
|
||||
return from_string(string)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an ArrayBuffer into a plain string
|
||||
* @param {ArrayBuffer} arrayBuffer
|
||||
*/
|
||||
export function arrayBufferToString(arrayBuffer: ArrayBuffer): string {
|
||||
return to_string(arrayBuffer as Uint8Array)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an ArrayBuffer into a hex string
|
||||
* @param arrayBuffer
|
||||
*/
|
||||
export function arrayBufferToHexString(arrayBuffer: ArrayBuffer): string {
|
||||
return to_hex(Buffer.from(arrayBuffer))
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a hex string into an ArrayBuffer
|
||||
* @access public
|
||||
* @param hex - A hex string
|
||||
*/
|
||||
export function hexStringToArrayBuffer(hex: string): Uint8Array {
|
||||
return from_hex(hex)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a base64 string into an ArrayBuffer
|
||||
* @param base64 - A base64 string
|
||||
*/
|
||||
export function base64ToArrayBuffer(base64: string): Uint8Array {
|
||||
return from_base64(base64, SN_BASE64_VARIANT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an ArrayBuffer into a base64 string
|
||||
* @param buffer
|
||||
*/
|
||||
export function arrayBufferToBase64(arrayBuffer: ArrayBuffer): string {
|
||||
return to_base64(Buffer.from(arrayBuffer), SN_BASE64_VARIANT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a hex string into a base64 string
|
||||
* @param hex - A hex string
|
||||
*/
|
||||
export function hexToBase64(hex: string): string {
|
||||
return to_base64(from_hex(hex), SN_BASE64_VARIANT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a base64 string into a hex string
|
||||
* @param base64 - A base64 string
|
||||
*/
|
||||
export function base64ToHex(base64: string): string {
|
||||
return to_hex(from_base64(base64, SN_BASE64_VARIANT))
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a plain string into base64
|
||||
* @param text - A plain string
|
||||
* @returns A base64 encoded string
|
||||
*/
|
||||
export function base64Encode(text: string): string {
|
||||
return to_base64(text, SN_BASE64_VARIANT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a plain string into base64 url safe
|
||||
* @param text - A plain string
|
||||
* @returns A base64 url safe encoded string
|
||||
*/
|
||||
export function base64URLEncode(text: string): string {
|
||||
return to_base64(text, base64_variants.URLSAFE_NO_PADDING)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a base64 string into a plain string
|
||||
* @param base64String - A base64 encoded string
|
||||
* @returns A plain string
|
||||
*/
|
||||
export function base64Decode(base64String: string): string {
|
||||
return to_string(from_base64(base64String, SN_BASE64_VARIANT))
|
||||
}
|
||||
|
||||
const RFC4648 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
|
||||
export function base32Encode(input: ArrayBuffer): string {
|
||||
const length = input.byteLength
|
||||
const buffer = new Uint8Array(input)
|
||||
|
||||
let bitIdx = 0
|
||||
let currentVal = 0
|
||||
let output = ''
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
currentVal = (currentVal << 8) | buffer[i]
|
||||
bitIdx += 8
|
||||
|
||||
while (bitIdx >= 5) {
|
||||
output += RFC4648[(currentVal >>> (bitIdx - 5)) & 31]
|
||||
bitIdx -= 5
|
||||
}
|
||||
}
|
||||
|
||||
if (bitIdx > 0) {
|
||||
output += RFC4648[(currentVal << (5 - bitIdx)) & 31]
|
||||
}
|
||||
|
||||
while (output.length % 8 > 0) {
|
||||
output += '='
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
export function base32Decode(b32Input: string): ArrayBuffer {
|
||||
const input = b32Input.toUpperCase().replace(/=+$/, '')
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (!RFC4648.includes(input[i])) {
|
||||
throw new Error(`Invalid RFC4648 char ${input[i]} at index ${i}`)
|
||||
}
|
||||
}
|
||||
|
||||
const output = new Uint8Array(((input.length * 5) / 8) | 0)
|
||||
|
||||
let outIdx = 0
|
||||
let bitIdx = 0
|
||||
let currentVal = 0
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
currentVal = (currentVal << 5) | RFC4648.indexOf(input[i])
|
||||
bitIdx += 5
|
||||
|
||||
if (bitIdx >= 8) {
|
||||
output[outIdx++] = (currentVal >>> (bitIdx - 8)) & 255
|
||||
bitIdx -= 8
|
||||
}
|
||||
}
|
||||
|
||||
return output.buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate HMAC-SHA1 calculated value for HOTP code generation
|
||||
*/
|
||||
export function truncateOTP(hsBuffer: ArrayBuffer): number {
|
||||
const hs = new Uint8Array(hsBuffer)
|
||||
// First we take the last byte of our generated HS and extract last 4 bits out of it.
|
||||
// This will be our _offset_, a number between 0 and 15.
|
||||
const offset = hs[19] & 0b1111
|
||||
|
||||
// Next we take 4 bytes out of the HS, starting at the offset
|
||||
const P = ((hs[offset] & 0x7f) << 24) | (hs[offset + 1] << 16) | (hs[offset + 2] << 8) | hs[offset + 3]
|
||||
|
||||
// Finally, convert it into a binary string representation
|
||||
const pString = P.toString(2)
|
||||
|
||||
const Snum = parseInt(pString, 2)
|
||||
|
||||
return Snum
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad HOTP counter with leading zeros producing an 8 byte array
|
||||
*/
|
||||
export function padStart(counter: number): ArrayBuffer {
|
||||
const buffer = new ArrayBuffer(8)
|
||||
const bView = new DataView(buffer)
|
||||
|
||||
const byteString = '0'.repeat(64)
|
||||
const bCounter = (byteString + counter.toString(2)).slice(-64)
|
||||
|
||||
for (let byte = 0; byte < 64; byte += 8) {
|
||||
const byteValue = parseInt(bCounter.slice(byte, byte + 8), 2)
|
||||
bView.setUint8(byte / 8, byteValue)
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
17
packages/sncrypto-web/test-server.js
Normal file
17
packages/sncrypto-web/test-server.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/* Used for running mocha tests */
|
||||
const connect = require('connect')
|
||||
const serveStatic = require('serve-static')
|
||||
const port = 9003
|
||||
connect()
|
||||
.use(serveStatic(__dirname))
|
||||
.listen(port, () => {
|
||||
const url = `http://localhost:${port}/test/test.html`
|
||||
console.log(`Open browser to ${url}`)
|
||||
const start =
|
||||
process.platform === 'darwin'
|
||||
? 'open'
|
||||
: process.platform === 'win32'
|
||||
? 'start'
|
||||
: 'xdg-open'
|
||||
require('child_process').exec(start + ' ' + url)
|
||||
})
|
||||
421
packages/sncrypto-web/test/crypto.test.js
Normal file
421
packages/sncrypto-web/test/crypto.test.js
Normal file
@@ -0,0 +1,421 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import './vendor/chai-as-promised-built.js'
|
||||
import './vendor/buffer@5.6.0.js'
|
||||
|
||||
const Buffer = window.buffer.Buffer
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('crypto operations', async function () {
|
||||
let webCrypto = new SNWebCrypto()
|
||||
|
||||
after(() => {
|
||||
webCrypto.deinit()
|
||||
webCrypto = null
|
||||
})
|
||||
|
||||
it('webcrypto should be defined', function () {
|
||||
expect(window.crypto).to.not.be.null
|
||||
})
|
||||
|
||||
it('generates valid uuid', async function () {
|
||||
expect(webCrypto.generateUUID().length).to.equal(36)
|
||||
})
|
||||
|
||||
it('properly encodes base64', async function () {
|
||||
const source = 'hello world 🌍'
|
||||
const target = 'aGVsbG8gd29ybGQg8J+MjQ=='
|
||||
expect(await base64Encode(source)).to.equal(target)
|
||||
})
|
||||
|
||||
it('properly encodes base64 in url safe mode', async function () {
|
||||
const source = 'hello world 🌍'
|
||||
const target = 'aGVsbG8gd29ybGQg8J-MjQ'
|
||||
expect(await base64URLEncode(source)).to.equal(target)
|
||||
})
|
||||
|
||||
it('properly decodes base64', async function () {
|
||||
const source = 'aGVsbG8gd29ybGQg8J+MjQ=='
|
||||
const target = 'hello world 🌍'
|
||||
expect(await base64Decode(source)).to.equal(target)
|
||||
})
|
||||
|
||||
it('generates proper length generic key', async function () {
|
||||
const length = 256
|
||||
const wcResult = await webCrypto.generateRandomKey(length)
|
||||
expect(wcResult.length).to.equal(length / 4)
|
||||
})
|
||||
|
||||
it('compares strings with timing safe comparison', async function () {
|
||||
const crypto = new SNWebCrypto()
|
||||
expect(crypto.timingSafeEqual('hello world 🌍', 'hello world 🌍')).to.equal(
|
||||
true,
|
||||
)
|
||||
expect(crypto.timingSafeEqual('helo world 🌍', 'hello world 🌍')).to.equal(
|
||||
false,
|
||||
)
|
||||
expect(crypto.timingSafeEqual('', 'a')).to.equal(false)
|
||||
expect(crypto.timingSafeEqual('', '')).to.equal(true)
|
||||
expect(
|
||||
crypto.timingSafeEqual(
|
||||
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
|
||||
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
|
||||
),
|
||||
).to.equal(true)
|
||||
expect(
|
||||
crypto.timingSafeEqual(
|
||||
'1e1ee7920bb188a88f94bb912153befd83cc55cd',
|
||||
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
|
||||
),
|
||||
).to.equal(false)
|
||||
expect(
|
||||
crypto.timingSafeEqual(
|
||||
'2e1ee7920bb188a88f94bb912153befd83cc55cc',
|
||||
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
|
||||
),
|
||||
).to.equal(false)
|
||||
})
|
||||
|
||||
it('random key length', async function () {
|
||||
const key = await webCrypto.generateRandomKey(256)
|
||||
expect(key.length).to.equal(64)
|
||||
})
|
||||
|
||||
it('pbkdf2 1', async function () {
|
||||
const password = 'very_secure🔒'
|
||||
const salt =
|
||||
'c3feb78823adce65c4ab024dab9c5cdcda5a04cdbd98f65eac0311dfa432d67b'
|
||||
const expected =
|
||||
'bbb3d3af19dd1cbb901c958003faa55f193aad6a57fff30e51a62591bdc054d8'
|
||||
const result = await webCrypto.pbkdf2(password, salt, 100000, 256)
|
||||
expect(result).to.equal(expected)
|
||||
})
|
||||
|
||||
it('pbkdf2 2', async function () {
|
||||
const password = 'correct horse battery staple ✅'
|
||||
const salt = Buffer.from(
|
||||
'808182838485868788898a8b8c8d8e8f',
|
||||
'hex',
|
||||
).toString('utf8')
|
||||
const expected =
|
||||
'795d83b18e55d860d3799f85a20f66ee17eb9dcf041df1d7a13fac30af7103d9'
|
||||
const result = await webCrypto.pbkdf2(password, salt, 100000, 256)
|
||||
expect(result).to.equal(expected)
|
||||
})
|
||||
|
||||
it('aes cbc', async function () {
|
||||
const iv = await webCrypto.generateRandomKey(128)
|
||||
const key = await webCrypto.generateRandomKey(256)
|
||||
const text = 'hello world 🌍'
|
||||
const encrypted = await webCrypto.aes256CbcEncrypt(text, iv, key)
|
||||
const decrypted = await webCrypto.aes256CbcDecrypt(encrypted, iv, key)
|
||||
expect(decrypted).to.equal(text)
|
||||
})
|
||||
|
||||
it('hmac 256', async function () {
|
||||
const text = 'hello world 🌍'
|
||||
const key =
|
||||
'e802dc953f3f1f7b5db62409b74ac848559d4711c4e0047ecc5e312ad8ab8397'
|
||||
const hash = await webCrypto.hmac256(text, key)
|
||||
const expected =
|
||||
'b63f94ee33a067ffac3ee97c7987dd3171dcdc747a322bb3f3ab890201c8e6f9'
|
||||
expect(hash).to.equal(expected)
|
||||
})
|
||||
|
||||
it('sha256', async function () {
|
||||
const text = 'hello world 🌍'
|
||||
const hash = await webCrypto.sha256(text)
|
||||
const expected =
|
||||
'1e71fe32476da1ff115b44dfd74aed5c90d68a1d80a2033065e30cff4335211a'
|
||||
expect(hash).to.equal(expected)
|
||||
})
|
||||
|
||||
it('hmac 1', async function () {
|
||||
const text = 'hello world 🌍'
|
||||
const key = '73756d6d657274696d65'
|
||||
const hash = await webCrypto.hmac1(text, key)
|
||||
const expected = '534bc6ff40d4616e9be4fb530093d5f7f87173fa'
|
||||
expect(hash).to.equal(expected)
|
||||
})
|
||||
|
||||
it('sha1', async function () {
|
||||
const text = 'hello world 🌍'
|
||||
const hash = await webCrypto.unsafeSha1(text)
|
||||
const expected = '0818667aed20ac104ca8f300f8df9753e1937983'
|
||||
expect(hash).to.equal(expected)
|
||||
})
|
||||
|
||||
it('argon2 predefined salt', async function () {
|
||||
/** This differs from libsodium.js test matching below in that we include an emoji at the end */
|
||||
const password = 'correct horse battery staple ✅'
|
||||
const salt = '808182838485868788898a8b8c8d8e8f'
|
||||
const bytes = 67108864
|
||||
const length = 16
|
||||
const iterations = 2
|
||||
const result = await webCrypto.argon2(
|
||||
password,
|
||||
salt,
|
||||
iterations,
|
||||
bytes,
|
||||
length,
|
||||
)
|
||||
const expectedResult = '18dfbc268f251701652c8e38b5273f73'
|
||||
expect(result).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
it('argon2 generated salt', async function () {
|
||||
const rawSalt = await webCrypto.sha256(['foo', 'bar'].join(':'))
|
||||
const truncatedSalt = rawSalt.substring(0, rawSalt.length / 2)
|
||||
const password = 'foobarfoo🔒'
|
||||
const bytes = 67108864
|
||||
const length = 32
|
||||
const iterations = 5
|
||||
const result = await webCrypto.argon2(
|
||||
password,
|
||||
truncatedSalt,
|
||||
iterations,
|
||||
bytes,
|
||||
length,
|
||||
)
|
||||
const expected =
|
||||
'bb6ec440708c271ce34decd7f997e2444d309b1105992779ccdb47f78a5fda6f'
|
||||
expect(result).to.equal(expected)
|
||||
})
|
||||
|
||||
it('xchacha20 encrypt/decrypt', async function () {
|
||||
const key = await webCrypto.generateRandomKey(256)
|
||||
const nonce = await webCrypto.generateRandomKey(192)
|
||||
const plaintext = 'hello world 🌍'
|
||||
const aad = JSON.stringify({ uuid: '123🎤' })
|
||||
const ciphertext = await webCrypto.xchacha20Encrypt(
|
||||
plaintext,
|
||||
nonce,
|
||||
key,
|
||||
aad,
|
||||
)
|
||||
const decrypted = await webCrypto.xchacha20Decrypt(
|
||||
ciphertext,
|
||||
nonce,
|
||||
key,
|
||||
aad,
|
||||
)
|
||||
expect(decrypted).to.equal(plaintext)
|
||||
})
|
||||
|
||||
it('xchacha20 streaming encrypt/decrypt', async function () {
|
||||
const key = await webCrypto.generateRandomKey(256)
|
||||
const bigFile = await fetch(
|
||||
'http://localhost:9003/test/resources/big_file.md',
|
||||
)
|
||||
const bigText = await bigFile.text()
|
||||
const plaintext = bigText
|
||||
const plainBuffer = stringToArrayBuffer(plaintext)
|
||||
const encryptor = webCrypto.xchacha20StreamEncryptInitEncryptor(key)
|
||||
const header = base64StringToArrayBuffer(encryptor.header)
|
||||
|
||||
let encryptedBuffer = Buffer.concat([header])
|
||||
const pushChunkSize = plainBuffer.length / 200
|
||||
const pullChunkSize =
|
||||
pushChunkSize +
|
||||
SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
|
||||
|
||||
for (let i = 0; i < plainBuffer.length; i += pushChunkSize) {
|
||||
const readUntil =
|
||||
i + pushChunkSize > plainBuffer.length
|
||||
? plainBuffer.length
|
||||
: i + pushChunkSize
|
||||
const chunk = webCrypto.xchacha20StreamEncryptorPush(
|
||||
encryptor,
|
||||
plainBuffer.slice(i, readUntil),
|
||||
)
|
||||
encryptedBuffer = Buffer.concat([encryptedBuffer, chunk])
|
||||
}
|
||||
|
||||
const decryptor = webCrypto.xchacha20StreamEncryptInitDecryptor(
|
||||
header,
|
||||
key,
|
||||
)
|
||||
|
||||
let decryptedBuffer = Buffer.alloc(0)
|
||||
for (
|
||||
let i = header.length;
|
||||
i < encryptedBuffer.length;
|
||||
i += pullChunkSize
|
||||
) {
|
||||
const readUntil =
|
||||
i + pullChunkSize > encryptedBuffer.length
|
||||
? encryptedBuffer.length
|
||||
: i + pullChunkSize
|
||||
const chunk = webCrypto.xchacha20StreamDecryptorPush(
|
||||
decryptor,
|
||||
encryptedBuffer.slice(i, readUntil),
|
||||
)
|
||||
decryptedBuffer = Buffer.concat([decryptedBuffer, chunk.message])
|
||||
}
|
||||
|
||||
const decryptedPlain = arrayBufferToString(decryptedBuffer)
|
||||
expect(decryptedPlain).to.equal(plaintext)
|
||||
})
|
||||
|
||||
it('xchacha20 should fail with nonmatching aad', async function () {
|
||||
const key = await webCrypto.generateRandomKey(256)
|
||||
const nonce = await webCrypto.generateRandomKey(192)
|
||||
const plaintext = 'hello world 🌍'
|
||||
const ciphertext = await webCrypto.xchacha20Encrypt(
|
||||
plaintext,
|
||||
nonce,
|
||||
key,
|
||||
JSON.stringify({ uuid: 'foo🎲' }),
|
||||
)
|
||||
const result = await webCrypto.xchacha20Decrypt(
|
||||
ciphertext,
|
||||
nonce,
|
||||
key,
|
||||
JSON.stringify({ uuid: 'bar🎲' }),
|
||||
)
|
||||
expect(result).to.not.be.ok
|
||||
})
|
||||
|
||||
it('xchacha predefined string', async function () {
|
||||
/** This differs from libsodium.js test matching below in that we include an emoji at the end */
|
||||
const plaintext =
|
||||
"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.🌞"
|
||||
const assocData = await hexStringToArrayBuffer('50515253c0c1c2c3c4c5c6c7')
|
||||
const nonce = '404142434445464748494a4b4c4d4e4f5051525354555657'
|
||||
const key =
|
||||
'808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'
|
||||
const ciphertext = await webCrypto.xchacha20Encrypt(
|
||||
plaintext,
|
||||
nonce,
|
||||
key,
|
||||
assocData,
|
||||
)
|
||||
const expected =
|
||||
'vW0XnT6D1DuVdleUk8DpOVcqFwAlK/rMvtKQLCE5bLtzHH8bC0qmRAvzqC9O2n45rmTGcIxUwhbLlrcuEhO0Ui+Mm6QNtdlFsRtpuYLBu54/P6wrw2lIj3ayODVl0//5IflmTJdjfal2iBL2FcaLE7UuOqw6kdl7HV6PKzn0pIOeHH3rkwQ='
|
||||
expect(ciphertext).to.equal(expected)
|
||||
const decrypted = await webCrypto.xchacha20Decrypt(
|
||||
ciphertext,
|
||||
nonce,
|
||||
key,
|
||||
assocData,
|
||||
)
|
||||
expect(decrypted).to.equal(plaintext)
|
||||
})
|
||||
|
||||
it('xchacha libsodium.js test matching', async function () {
|
||||
/* Same values as https://github.com/jedisct1/libsodium.js/blob/master/test/sodium_utils.js */
|
||||
const plaintext = Buffer.from(
|
||||
'4c616469657320616e642047656e746c656d656e206f662074686520636c6173' +
|
||||
'73206f66202739393a204966204920636f756c64206f6666657220796f75206f' +
|
||||
'6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73' +
|
||||
'637265656e20776f756c642062652069742e',
|
||||
'hex',
|
||||
).toString('utf8')
|
||||
const assocData = Buffer.from('50515253c0c1c2c3c4c5c6c7', 'hex')
|
||||
const nonce = '404142434445464748494a4b4c4d4e4f5051525354555657'
|
||||
const key =
|
||||
'808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'
|
||||
|
||||
/** Encrypt */
|
||||
const ciphertextBase64 = await webCrypto.xchacha20Encrypt(
|
||||
plaintext,
|
||||
nonce,
|
||||
key,
|
||||
assocData,
|
||||
)
|
||||
const ciphertextHex = await base64ToHex(ciphertextBase64)
|
||||
const expected =
|
||||
'bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb' +
|
||||
'731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b452' +
|
||||
'2f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff9' +
|
||||
'21f9664c97637da9768812f615c68b13b52e' +
|
||||
'c0875924c1c7987947deafd8780acf49'
|
||||
expect(ciphertextHex).to.equal(expected)
|
||||
|
||||
/** Decrypt */
|
||||
const decrypted = await webCrypto.xchacha20Decrypt(
|
||||
ciphertextBase64,
|
||||
nonce,
|
||||
key,
|
||||
assocData,
|
||||
)
|
||||
expect(decrypted).to.equal(plaintext)
|
||||
})
|
||||
|
||||
it('argon2 libsodium.js test matching', async function () {
|
||||
/* Same values as https://github.com/jedisct1/libsodium.js/blob/master/test/sodium_utils.js */
|
||||
const password = 'correct horse battery staple'
|
||||
const salt = '808182838485868788898a8b8c8d8e8f'
|
||||
const bytes = 67108864
|
||||
const length = 16
|
||||
const iterations = 2
|
||||
const result = await webCrypto.argon2(
|
||||
password,
|
||||
salt,
|
||||
iterations,
|
||||
bytes,
|
||||
length,
|
||||
)
|
||||
const expectedResult = '720f95400220748a811bca9b8cff5d6e'
|
||||
expect(result).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
it('generates random OTP secret 160 bits long', async function () {
|
||||
const secret = await webCrypto.generateOtpSecret()
|
||||
expect(secret).to.have.length(32)
|
||||
expect(secret).to.not.include('=')
|
||||
})
|
||||
|
||||
it('generates valid HOTP tokens', async function () {
|
||||
/**
|
||||
* Test data acquired from RFC4226
|
||||
* https://datatracker.ietf.org/doc/html/rfc4226#page-32
|
||||
*/
|
||||
const encoder = new TextEncoder()
|
||||
const secret = '12345678901234567890'
|
||||
const b32Secret = base32Encode(encoder.encode(secret))
|
||||
const hotpTest = [
|
||||
'755224',
|
||||
'287082',
|
||||
'359152',
|
||||
'969429',
|
||||
'338314',
|
||||
'254676',
|
||||
'287922',
|
||||
'162583',
|
||||
'399871',
|
||||
'520489',
|
||||
]
|
||||
|
||||
for (let counter = 0; counter < hotpTest.length; counter++) {
|
||||
const hotp = hotpTest[counter]
|
||||
const result = await webCrypto.hotpToken(b32Secret, counter)
|
||||
expect(result).to.equal(hotp)
|
||||
}
|
||||
})
|
||||
|
||||
it('generates valid TOTP tokens', async function () {
|
||||
/**
|
||||
* Test data acquired from RFC6238
|
||||
* https://datatracker.ietf.org/doc/html/rfc6238#appendix-B
|
||||
*/
|
||||
const encoder = new TextEncoder()
|
||||
const secret = '12345678901234567890'
|
||||
const b32Secret = base32Encode(encoder.encode(secret))
|
||||
const tokenLength = 8
|
||||
const totpTest = [
|
||||
{ time: 59000, totp: '94287082' },
|
||||
{ time: 1111111109000, totp: '07081804' },
|
||||
{ time: 1111111111000, totp: '14050471' },
|
||||
{ time: 1234567890000, totp: '89005924' },
|
||||
{ time: 2000000000000, totp: '69279037' },
|
||||
]
|
||||
|
||||
for (let { time, totp } of totpTest) {
|
||||
const result = await webCrypto.totpToken(b32Secret, time, tokenLength)
|
||||
expect(result).to.equal(totp)
|
||||
}
|
||||
})
|
||||
})
|
||||
17
packages/sncrypto-web/test/memory.test.js
Normal file
17
packages/sncrypto-web/test/memory.test.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import './vendor/chai-as-promised-built.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
|
||||
/**
|
||||
* Simple empty test page to create and deinit empty page
|
||||
* Then check browser Memory tool to make sure there are no leaks.
|
||||
*/
|
||||
describe('memory', async function () {
|
||||
it('cleanup', function () {
|
||||
this.webCrypto = new SNWebCrypto()
|
||||
this.webCrypto.deinit()
|
||||
this.webCrypto = null
|
||||
})
|
||||
})
|
||||
7020
packages/sncrypto-web/test/resources/big_file.md
Normal file
7020
packages/sncrypto-web/test/resources/big_file.md
Normal file
File diff suppressed because it is too large
Load Diff
28
packages/sncrypto-web/test/test.html
Normal file
28
packages/sncrypto-web/test/test.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Mocha Tests</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
|
||||
<script src="https://unpkg.com/chai/chai.js"></script>
|
||||
<script src="https://unpkg.com/mocha/mocha.js"></script>
|
||||
<script src="../../../node_modules/regenerator-runtime/runtime.js"></script>
|
||||
<script src="../dist/sncrypto-web.js"></script>
|
||||
<script>
|
||||
Object.assign(window, SNCrypto);
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
<script type="module" src="utils.test.js"></script>
|
||||
<script type="module" src="memory.test.js"></script>
|
||||
<script type="module" src="crypto.test.js"></script>
|
||||
<script type="module">
|
||||
mocha.checkLeaks();
|
||||
mocha.run();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
110
packages/sncrypto-web/test/utils.test.js
Normal file
110
packages/sncrypto-web/test/utils.test.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import './vendor/chai-as-promised-built.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('utils', async function () {
|
||||
it('stringToArrayBuffer', async function () {
|
||||
const str = 'hello world'
|
||||
const buffer = await stringToArrayBuffer(str)
|
||||
expect(buffer.byteLength).to.equal(11)
|
||||
expect(await arrayBufferToString(buffer)).to.equal(str)
|
||||
})
|
||||
|
||||
it('arrayBufferToString', async function () {
|
||||
const str = 'hello world'
|
||||
const buffer = await stringToArrayBuffer(str)
|
||||
const result = await arrayBufferToString(buffer)
|
||||
expect(result).to.equal(str)
|
||||
expect(await stringToArrayBuffer(result)).to.eql(buffer)
|
||||
})
|
||||
|
||||
it('arrayBufferToHexString', async function () {
|
||||
const str = 'hello world'
|
||||
const buffer = await stringToArrayBuffer(str)
|
||||
const hex = await arrayBufferToHexString(buffer)
|
||||
expect(hex).to.equal('68656c6c6f20776f726c64')
|
||||
})
|
||||
|
||||
it('hexStringToArrayBuffer', async function () {
|
||||
const hex = '68656c6c6f20776f726c64'
|
||||
const buffer = await hexStringToArrayBuffer(hex)
|
||||
expect(buffer.byteLength).to.equal(11)
|
||||
})
|
||||
|
||||
it('base64Encode', async function () {
|
||||
const str = 'hello world'
|
||||
const b64 = await base64Encode(str)
|
||||
expect(b64).to.equal('aGVsbG8gd29ybGQ=')
|
||||
})
|
||||
|
||||
it('base64URLEncode', async function () {
|
||||
const str = 'hello world'
|
||||
const b64 = await base64Encode(str)
|
||||
expect(b64).to.equal('aGVsbG8gd29ybGQ')
|
||||
})
|
||||
|
||||
it('base64Decode', async function () {
|
||||
const b64 = 'aGVsbG8gd29ybGQ='
|
||||
const str = await base64Decode(b64)
|
||||
expect(str).to.equal('hello world')
|
||||
})
|
||||
|
||||
it('base64ToArrayBuffer', async function () {
|
||||
const b64 = 'aGVsbG8gd29ybGQ='
|
||||
const buffer = await base64ToArrayBuffer(b64)
|
||||
expect(buffer.byteLength).to.equal(11)
|
||||
})
|
||||
|
||||
it('arrayBufferToBase64', async function () {
|
||||
const b64 = 'aGVsbG8gd29ybGQ='
|
||||
const buffer = await base64ToArrayBuffer(b64)
|
||||
const result = await arrayBufferToBase64(buffer)
|
||||
expect(result).to.equal(b64)
|
||||
})
|
||||
|
||||
it('hexToBase64', async function () {
|
||||
const hex = '68656c6c6f20776f726c64'
|
||||
const result = await hexToBase64(hex)
|
||||
expect(result).to.equal('aGVsbG8gd29ybGQ=')
|
||||
})
|
||||
|
||||
it('base64ToHex', async function () {
|
||||
const b64 = 'aGVsbG8gd29ybGQ='
|
||||
const result = await base64ToHex(b64)
|
||||
expect(result).to.equal('68656c6c6f20776f726c64')
|
||||
})
|
||||
|
||||
/**
|
||||
* Table of test values for base32 from RFC4648
|
||||
* https://datatracker.ietf.org/doc/html/rfc4648#section-10
|
||||
*/
|
||||
const base32TestPair = [
|
||||
{ text: '', base32: '' },
|
||||
{ text: 'f', base32: 'MY======' },
|
||||
{ text: 'fo', base32: 'MZXQ====' },
|
||||
{ text: 'foo', base32: 'MZXW6===' },
|
||||
{ text: 'foob', base32: 'MZXW6YQ=' },
|
||||
{ text: 'fooba', base32: 'MZXW6YTB' },
|
||||
{ text: 'foobar', base32: 'MZXW6YTBOI======' },
|
||||
]
|
||||
|
||||
it('base32Encode', async function () {
|
||||
const encoder = new TextEncoder()
|
||||
for (let pair of base32TestPair) {
|
||||
const result = base32Encode(encoder.encode(pair.text).buffer)
|
||||
expect(result).to.equal(pair.base32)
|
||||
}
|
||||
})
|
||||
|
||||
it('base32Decode', async function () {
|
||||
const decoder = new TextDecoder()
|
||||
for (let pair of base32TestPair) {
|
||||
const bufferResult = base32Decode(pair.base32)
|
||||
const result = decoder.decode(bufferResult)
|
||||
expect(result).to.equal(pair.text)
|
||||
}
|
||||
})
|
||||
})
|
||||
1
packages/sncrypto-web/test/vendor/buffer@5.6.0.js
vendored
Normal file
1
packages/sncrypto-web/test/vendor/buffer@5.6.0.js
vendored
Normal file
File diff suppressed because one or more lines are too long
539
packages/sncrypto-web/test/vendor/chai-as-promised-built.js
vendored
Normal file
539
packages/sncrypto-web/test/vendor/chai-as-promised-built.js
vendored
Normal file
@@ -0,0 +1,539 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.chaiAsPromised = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
/* eslint-disable no-invalid-this */
|
||||
let checkError = require("check-error");
|
||||
|
||||
module.exports = (chai, utils) => {
|
||||
const Assertion = chai.Assertion;
|
||||
const assert = chai.assert;
|
||||
const proxify = utils.proxify;
|
||||
|
||||
// If we are using a version of Chai that has checkError on it,
|
||||
// we want to use that version to be consistent. Otherwise, we use
|
||||
// what was passed to the factory.
|
||||
if (utils.checkError) {
|
||||
checkError = utils.checkError;
|
||||
}
|
||||
|
||||
function isLegacyJQueryPromise(thenable) {
|
||||
// jQuery promises are Promises/A+-compatible since 3.0.0. jQuery 3.0.0 is also the first version
|
||||
// to define the catch method.
|
||||
return typeof thenable.catch !== "function" &&
|
||||
typeof thenable.always === "function" &&
|
||||
typeof thenable.done === "function" &&
|
||||
typeof thenable.fail === "function" &&
|
||||
typeof thenable.pipe === "function" &&
|
||||
typeof thenable.progress === "function" &&
|
||||
typeof thenable.state === "function";
|
||||
}
|
||||
|
||||
function assertIsAboutPromise(assertion) {
|
||||
if (typeof assertion._obj.then !== "function") {
|
||||
throw new TypeError(utils.inspect(assertion._obj) + " is not a thenable.");
|
||||
}
|
||||
if (isLegacyJQueryPromise(assertion._obj)) {
|
||||
throw new TypeError("Chai as Promised is incompatible with thenables of jQuery<3.0.0, sorry! Please " +
|
||||
"upgrade jQuery or use another Promises/A+ compatible library (see " +
|
||||
"http://promisesaplus.com/).");
|
||||
}
|
||||
}
|
||||
|
||||
function proxifyIfSupported(assertion) {
|
||||
return proxify === undefined ? assertion : proxify(assertion);
|
||||
}
|
||||
|
||||
function method(name, asserter) {
|
||||
utils.addMethod(Assertion.prototype, name, function () {
|
||||
assertIsAboutPromise(this);
|
||||
return asserter.apply(this, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
function property(name, asserter) {
|
||||
utils.addProperty(Assertion.prototype, name, function () {
|
||||
assertIsAboutPromise(this);
|
||||
return proxifyIfSupported(asserter.apply(this, arguments));
|
||||
});
|
||||
}
|
||||
|
||||
function doNotify(promise, done) {
|
||||
promise.then(() => done(), done);
|
||||
}
|
||||
|
||||
// These are for clarity and to bypass Chai refusing to allow `undefined` as actual when used with `assert`.
|
||||
function assertIfNegated(assertion, message, extra) {
|
||||
assertion.assert(true, null, message, extra.expected, extra.actual);
|
||||
}
|
||||
|
||||
function assertIfNotNegated(assertion, message, extra) {
|
||||
assertion.assert(false, message, null, extra.expected, extra.actual);
|
||||
}
|
||||
|
||||
function getBasePromise(assertion) {
|
||||
// We need to chain subsequent asserters on top of ones in the chain already (consider
|
||||
// `eventually.have.property("foo").that.equals("bar")`), only running them after the existing ones pass.
|
||||
// So the first base-promise is `assertion._obj`, but after that we use the assertions themselves, i.e.
|
||||
// previously derived promises, to chain off of.
|
||||
return typeof assertion.then === "function" ? assertion : assertion._obj;
|
||||
}
|
||||
|
||||
function getReasonName(reason) {
|
||||
return reason instanceof Error ? reason.toString() : checkError.getConstructorName(reason);
|
||||
}
|
||||
|
||||
// Grab these first, before we modify `Assertion.prototype`.
|
||||
|
||||
const propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
|
||||
|
||||
const propertyDescs = {};
|
||||
for (const name of propertyNames) {
|
||||
propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
|
||||
}
|
||||
|
||||
property("fulfilled", function () {
|
||||
const derivedPromise = getBasePromise(this).then(
|
||||
value => {
|
||||
assertIfNegated(this,
|
||||
"expected promise not to be fulfilled but it was fulfilled with #{act}",
|
||||
{ actual: value });
|
||||
return value;
|
||||
},
|
||||
reason => {
|
||||
assertIfNotNegated(this,
|
||||
"expected promise to be fulfilled but it was rejected with #{act}",
|
||||
{ actual: getReasonName(reason) });
|
||||
return reason;
|
||||
}
|
||||
);
|
||||
|
||||
module.exports.transferPromiseness(this, derivedPromise);
|
||||
return this;
|
||||
});
|
||||
|
||||
property("rejected", function () {
|
||||
const derivedPromise = getBasePromise(this).then(
|
||||
value => {
|
||||
assertIfNotNegated(this,
|
||||
"expected promise to be rejected but it was fulfilled with #{act}",
|
||||
{ actual: value });
|
||||
return value;
|
||||
},
|
||||
reason => {
|
||||
assertIfNegated(this,
|
||||
"expected promise not to be rejected but it was rejected with #{act}",
|
||||
{ actual: getReasonName(reason) });
|
||||
|
||||
// Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
|
||||
// `promise.should.be.rejected.and.eventually.equal("reason")`.
|
||||
return reason;
|
||||
}
|
||||
);
|
||||
|
||||
module.exports.transferPromiseness(this, derivedPromise);
|
||||
return this;
|
||||
});
|
||||
|
||||
method("rejectedWith", function (errorLike, errMsgMatcher, message) {
|
||||
let errorLikeName = null;
|
||||
const negate = utils.flag(this, "negate") || false;
|
||||
|
||||
// rejectedWith with that is called without arguments is
|
||||
// the same as a plain ".rejected" use.
|
||||
if (errorLike === undefined && errMsgMatcher === undefined &&
|
||||
message === undefined) {
|
||||
/* eslint-disable no-unused-expressions */
|
||||
return this.rejected;
|
||||
/* eslint-enable no-unused-expressions */
|
||||
}
|
||||
|
||||
if (message !== undefined) {
|
||||
utils.flag(this, "message", message);
|
||||
}
|
||||
|
||||
if (errorLike instanceof RegExp || typeof errorLike === "string") {
|
||||
errMsgMatcher = errorLike;
|
||||
errorLike = null;
|
||||
} else if (errorLike && errorLike instanceof Error) {
|
||||
errorLikeName = errorLike.toString();
|
||||
} else if (typeof errorLike === "function") {
|
||||
errorLikeName = checkError.getConstructorName(errorLike);
|
||||
} else {
|
||||
errorLike = null;
|
||||
}
|
||||
const everyArgIsDefined = Boolean(errorLike && errMsgMatcher);
|
||||
|
||||
let matcherRelation = "including";
|
||||
if (errMsgMatcher instanceof RegExp) {
|
||||
matcherRelation = "matching";
|
||||
}
|
||||
|
||||
const derivedPromise = getBasePromise(this).then(
|
||||
value => {
|
||||
let assertionMessage = null;
|
||||
let expected = null;
|
||||
|
||||
if (errorLike) {
|
||||
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with #{act}";
|
||||
expected = errorLikeName;
|
||||
} else if (errMsgMatcher) {
|
||||
assertionMessage = `expected promise to be rejected with an error ${matcherRelation} #{exp} but ` +
|
||||
`it was fulfilled with #{act}`;
|
||||
expected = errMsgMatcher;
|
||||
}
|
||||
|
||||
assertIfNotNegated(this, assertionMessage, { expected, actual: value });
|
||||
return value;
|
||||
},
|
||||
reason => {
|
||||
const errorLikeCompatible = errorLike && (errorLike instanceof Error ?
|
||||
checkError.compatibleInstance(reason, errorLike) :
|
||||
checkError.compatibleConstructor(reason, errorLike));
|
||||
|
||||
const errMsgMatcherCompatible = errMsgMatcher && checkError.compatibleMessage(reason, errMsgMatcher);
|
||||
|
||||
const reasonName = getReasonName(reason);
|
||||
|
||||
if (negate && everyArgIsDefined) {
|
||||
if (errorLikeCompatible && errMsgMatcherCompatible) {
|
||||
this.assert(true,
|
||||
null,
|
||||
"expected promise not to be rejected with #{exp} but it was rejected " +
|
||||
"with #{act}",
|
||||
errorLikeName,
|
||||
reasonName);
|
||||
}
|
||||
} else {
|
||||
if (errorLike) {
|
||||
this.assert(errorLikeCompatible,
|
||||
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
|
||||
"expected promise not to be rejected with #{exp} but it was rejected " +
|
||||
"with #{act}",
|
||||
errorLikeName,
|
||||
reasonName);
|
||||
}
|
||||
|
||||
if (errMsgMatcher) {
|
||||
this.assert(errMsgMatcherCompatible,
|
||||
`expected promise to be rejected with an error ${matcherRelation} #{exp} but got ` +
|
||||
`#{act}`,
|
||||
`expected promise not to be rejected with an error ${matcherRelation} #{exp}`,
|
||||
errMsgMatcher,
|
||||
checkError.getMessage(reason));
|
||||
}
|
||||
}
|
||||
|
||||
return reason;
|
||||
}
|
||||
);
|
||||
|
||||
module.exports.transferPromiseness(this, derivedPromise);
|
||||
return this;
|
||||
});
|
||||
|
||||
property("eventually", function () {
|
||||
utils.flag(this, "eventually", true);
|
||||
return this;
|
||||
});
|
||||
|
||||
method("notify", function (done) {
|
||||
doNotify(getBasePromise(this), done);
|
||||
return this;
|
||||
});
|
||||
|
||||
method("become", function (value, message) {
|
||||
return this.eventually.deep.equal(value, message);
|
||||
});
|
||||
|
||||
// ### `eventually`
|
||||
|
||||
// We need to be careful not to trigger any getters, thus `Object.getOwnPropertyDescriptor` usage.
|
||||
const methodNames = propertyNames.filter(name => {
|
||||
return name !== "assert" && typeof propertyDescs[name].value === "function";
|
||||
});
|
||||
|
||||
methodNames.forEach(methodName => {
|
||||
Assertion.overwriteMethod(methodName, originalMethod => function () {
|
||||
return doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
||||
});
|
||||
});
|
||||
|
||||
const getterNames = propertyNames.filter(name => {
|
||||
return name !== "_obj" && typeof propertyDescs[name].get === "function";
|
||||
});
|
||||
|
||||
getterNames.forEach(getterName => {
|
||||
// Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
|
||||
// `should.be.an("object")`. We need to handle those specially.
|
||||
const isChainableMethod = Assertion.prototype.__methods.hasOwnProperty(getterName);
|
||||
|
||||
if (isChainableMethod) {
|
||||
Assertion.overwriteChainableMethod(
|
||||
getterName,
|
||||
originalMethod => function () {
|
||||
return doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
||||
},
|
||||
originalGetter => function () {
|
||||
return doAsserterAsyncAndAddThen(originalGetter, this);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Assertion.overwriteProperty(getterName, originalGetter => function () {
|
||||
return proxifyIfSupported(doAsserterAsyncAndAddThen(originalGetter, this));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function doAsserterAsyncAndAddThen(asserter, assertion, args) {
|
||||
// Since we're intercepting all methods/properties, we need to just pass through if they don't want
|
||||
// `eventually`, or if we've already fulfilled the promise (see below).
|
||||
if (!utils.flag(assertion, "eventually")) {
|
||||
asserter.apply(assertion, args);
|
||||
return assertion;
|
||||
}
|
||||
|
||||
const derivedPromise = getBasePromise(assertion).then(value => {
|
||||
// Set up the environment for the asserter to actually run: `_obj` should be the fulfillment value, and
|
||||
// now that we have the value, we're no longer in "eventually" mode, so we won't run any of this code,
|
||||
// just the base Chai code that we get to via the short-circuit above.
|
||||
assertion._obj = value;
|
||||
utils.flag(assertion, "eventually", false);
|
||||
|
||||
return args ? module.exports.transformAsserterArgs(args) : args;
|
||||
}).then(newArgs => {
|
||||
asserter.apply(assertion, newArgs);
|
||||
|
||||
// Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
|
||||
// flag), we need to communicate this value change to subsequent chained asserters. Since we build a
|
||||
// promise chain paralleling the asserter chain, we can use it to communicate such changes.
|
||||
return assertion._obj;
|
||||
});
|
||||
|
||||
module.exports.transferPromiseness(assertion, derivedPromise);
|
||||
return assertion;
|
||||
}
|
||||
|
||||
// ### Now use the `Assertion` framework to build an `assert` interface.
|
||||
const originalAssertMethods = Object.getOwnPropertyNames(assert).filter(propName => {
|
||||
return typeof assert[propName] === "function";
|
||||
});
|
||||
|
||||
assert.isFulfilled = (promise, message) => (new Assertion(promise, message)).to.be.fulfilled;
|
||||
|
||||
assert.isRejected = (promise, errorLike, errMsgMatcher, message) => {
|
||||
const assertion = new Assertion(promise, message);
|
||||
return assertion.to.be.rejectedWith(errorLike, errMsgMatcher, message);
|
||||
};
|
||||
|
||||
assert.becomes = (promise, value, message) => assert.eventually.deepEqual(promise, value, message);
|
||||
|
||||
assert.doesNotBecome = (promise, value, message) => assert.eventually.notDeepEqual(promise, value, message);
|
||||
|
||||
assert.eventually = {};
|
||||
originalAssertMethods.forEach(assertMethodName => {
|
||||
assert.eventually[assertMethodName] = function (promise) {
|
||||
const otherArgs = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
let customRejectionHandler;
|
||||
const message = arguments[assert[assertMethodName].length - 1];
|
||||
if (typeof message === "string") {
|
||||
customRejectionHandler = reason => {
|
||||
throw new chai.AssertionError(`${message}\n\nOriginal reason: ${utils.inspect(reason)}`);
|
||||
};
|
||||
}
|
||||
|
||||
const returnedPromise = promise.then(
|
||||
fulfillmentValue => assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs)),
|
||||
customRejectionHandler
|
||||
);
|
||||
|
||||
returnedPromise.notify = done => {
|
||||
doNotify(returnedPromise, done);
|
||||
};
|
||||
|
||||
return returnedPromise;
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.transferPromiseness = (assertion, promise) => {
|
||||
assertion.then = promise.then.bind(promise);
|
||||
};
|
||||
|
||||
module.exports.transformAsserterArgs = values => values;
|
||||
|
||||
},{"check-error":2}],2:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
/* !
|
||||
* Chai - checkError utility
|
||||
* Copyright(c) 2012-2016 Jake Luer <jake@alogicalparadox.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* ### .checkError
|
||||
*
|
||||
* Checks that an error conforms to a given set of criteria and/or retrieves information about it.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
/**
|
||||
* ### .compatibleInstance(thrown, errorLike)
|
||||
*
|
||||
* Checks if two instances are compatible (strict equal).
|
||||
* Returns false if errorLike is not an instance of Error, because instances
|
||||
* can only be compatible if they're both error instances.
|
||||
*
|
||||
* @name compatibleInstance
|
||||
* @param {Error} thrown error
|
||||
* @param {Error|ErrorConstructor} errorLike object to compare against
|
||||
* @namespace Utils
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function compatibleInstance(thrown, errorLike) {
|
||||
return errorLike instanceof Error && thrown === errorLike;
|
||||
}
|
||||
|
||||
/**
|
||||
* ### .compatibleConstructor(thrown, errorLike)
|
||||
*
|
||||
* Checks if two constructors are compatible.
|
||||
* This function can receive either an error constructor or
|
||||
* an error instance as the `errorLike` argument.
|
||||
* Constructors are compatible if they're the same or if one is
|
||||
* an instance of another.
|
||||
*
|
||||
* @name compatibleConstructor
|
||||
* @param {Error} thrown error
|
||||
* @param {Error|ErrorConstructor} errorLike object to compare against
|
||||
* @namespace Utils
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function compatibleConstructor(thrown, errorLike) {
|
||||
if (errorLike instanceof Error) {
|
||||
// If `errorLike` is an instance of any error we compare their constructors
|
||||
return thrown.constructor === errorLike.constructor || thrown instanceof errorLike.constructor;
|
||||
} else if (errorLike.prototype instanceof Error || errorLike === Error) {
|
||||
// If `errorLike` is a constructor that inherits from Error, we compare `thrown` to `errorLike` directly
|
||||
return thrown.constructor === errorLike || thrown instanceof errorLike;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ### .compatibleMessage(thrown, errMatcher)
|
||||
*
|
||||
* Checks if an error's message is compatible with a matcher (String or RegExp).
|
||||
* If the message contains the String or passes the RegExp test,
|
||||
* it is considered compatible.
|
||||
*
|
||||
* @name compatibleMessage
|
||||
* @param {Error} thrown error
|
||||
* @param {String|RegExp} errMatcher to look for into the message
|
||||
* @namespace Utils
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function compatibleMessage(thrown, errMatcher) {
|
||||
var comparisonString = typeof thrown === 'string' ? thrown : thrown.message;
|
||||
if (errMatcher instanceof RegExp) {
|
||||
return errMatcher.test(comparisonString);
|
||||
} else if (typeof errMatcher === 'string') {
|
||||
return comparisonString.indexOf(errMatcher) !== -1; // eslint-disable-line no-magic-numbers
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ### .getFunctionName(constructorFn)
|
||||
*
|
||||
* Returns the name of a function.
|
||||
* This also includes a polyfill function if `constructorFn.name` is not defined.
|
||||
*
|
||||
* @name getFunctionName
|
||||
* @param {Function} constructorFn
|
||||
* @namespace Utils
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var functionNameMatch = /\s*function(?:\s|\s*\/\*[^(?:*\/)]+\*\/\s*)*([^\(\/]+)/;
|
||||
function getFunctionName(constructorFn) {
|
||||
var name = '';
|
||||
if (typeof constructorFn.name === 'undefined') {
|
||||
// Here we run a polyfill if constructorFn.name is not defined
|
||||
var match = String(constructorFn).match(functionNameMatch);
|
||||
if (match) {
|
||||
name = match[1];
|
||||
}
|
||||
} else {
|
||||
name = constructorFn.name;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* ### .getConstructorName(errorLike)
|
||||
*
|
||||
* Gets the constructor name for an Error instance or constructor itself.
|
||||
*
|
||||
* @name getConstructorName
|
||||
* @param {Error|ErrorConstructor} errorLike
|
||||
* @namespace Utils
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function getConstructorName(errorLike) {
|
||||
var constructorName = errorLike;
|
||||
if (errorLike instanceof Error) {
|
||||
constructorName = getFunctionName(errorLike.constructor);
|
||||
} else if (typeof errorLike === 'function') {
|
||||
// If `err` is not an instance of Error it is an error constructor itself or another function.
|
||||
// If we've got a common function we get its name, otherwise we may need to create a new instance
|
||||
// of the error just in case it's a poorly-constructed error. Please see chaijs/chai/issues/45 to know more.
|
||||
constructorName = getFunctionName(errorLike).trim() ||
|
||||
getFunctionName(new errorLike()); // eslint-disable-line new-cap
|
||||
}
|
||||
|
||||
return constructorName;
|
||||
}
|
||||
|
||||
/**
|
||||
* ### .getMessage(errorLike)
|
||||
*
|
||||
* Gets the error message from an error.
|
||||
* If `err` is a String itself, we return it.
|
||||
* If the error has no message, we return an empty string.
|
||||
*
|
||||
* @name getMessage
|
||||
* @param {Error|String} errorLike
|
||||
* @namespace Utils
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function getMessage(errorLike) {
|
||||
var msg = '';
|
||||
if (errorLike && errorLike.message) {
|
||||
msg = errorLike.message;
|
||||
} else if (typeof errorLike === 'string') {
|
||||
msg = errorLike;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
compatibleInstance: compatibleInstance,
|
||||
compatibleConstructor: compatibleConstructor,
|
||||
compatibleMessage: compatibleMessage,
|
||||
getMessage: getMessage,
|
||||
getConstructorName: getConstructorName,
|
||||
};
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
19
packages/sncrypto-web/tsconfig.json
Normal file
19
packages/sncrypto-web/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"composite": true,
|
||||
"inlineSourceMap": true,
|
||||
"types": ["node"],
|
||||
"typeRoots": ["node_modules/@types", "typings"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["dist", "test/**/*"]
|
||||
}
|
||||
43
packages/sncrypto-web/webpack.config.js
Normal file
43
packages/sncrypto-web/webpack.config.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const path = require('path')
|
||||
module.exports = {
|
||||
entry: {
|
||||
'sncrypto-web.js': './src/index',
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js'],
|
||||
fallback: {
|
||||
crypto: false,
|
||||
path: false,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: './[name]',
|
||||
chunkFilename: '[name].bundle.js',
|
||||
library: 'SNCrypto',
|
||||
libraryTarget: 'umd',
|
||||
umdNamedDefine: true,
|
||||
publicPath: '/dist/',
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts(x?)$/,
|
||||
exclude: /node_modules/,
|
||||
use: [{ loader: 'babel-loader' }, { loader: 'ts-loader' }],
|
||||
},
|
||||
{
|
||||
test: /\.(js)$/,
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [],
|
||||
stats: {
|
||||
colors: true,
|
||||
},
|
||||
devtool: 'source-map',
|
||||
}
|
||||
@@ -73,7 +73,7 @@
|
||||
"@standardnotes/files": "workspace:*",
|
||||
"@standardnotes/icons": "workspace:*",
|
||||
"@standardnotes/services": "workspace:*",
|
||||
"@standardnotes/sncrypto-web": "1.10.1",
|
||||
"@standardnotes/sncrypto-web": "workspace:*",
|
||||
"@standardnotes/snjs": "^2.118.3",
|
||||
"@standardnotes/styles": "workspace:*",
|
||||
"@standardnotes/toast": "workspace:*",
|
||||
|
||||
153
yarn.lock
153
yarn.lock
@@ -287,7 +287,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/core@npm:^7.1.0, @babel/core@npm:^7.1.6, @babel/core@npm:^7.11.1, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.16, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.10, @babel/core@npm:^7.13.13, @babel/core@npm:^7.13.14, @babel/core@npm:^7.13.8, @babel/core@npm:^7.14.0, @babel/core@npm:^7.14.6, @babel/core@npm:^7.15.5, @babel/core@npm:^7.16.0, @babel/core@npm:^7.17.10, @babel/core@npm:^7.17.9, @babel/core@npm:^7.18.2, @babel/core@npm:^7.18.5, @babel/core@npm:^7.7.0, @babel/core@npm:^7.7.2, @babel/core@npm:^7.7.7, @babel/core@npm:^7.8.0":
|
||||
"@babel/core@npm:^7.1.0, @babel/core@npm:^7.1.6, @babel/core@npm:^7.11.1, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.16, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.10, @babel/core@npm:^7.13.13, @babel/core@npm:^7.13.14, @babel/core@npm:^7.13.8, @babel/core@npm:^7.14.0, @babel/core@npm:^7.14.6, @babel/core@npm:^7.15.5, @babel/core@npm:^7.16.0, @babel/core@npm:^7.17.10, @babel/core@npm:^7.17.9, @babel/core@npm:^7.18.2, @babel/core@npm:^7.18.5, @babel/core@npm:^7.18.6, @babel/core@npm:^7.7.0, @babel/core@npm:^7.7.2, @babel/core@npm:^7.7.7, @babel/core@npm:^7.8.0":
|
||||
version: 7.18.6
|
||||
resolution: "@babel/core@npm:7.18.6"
|
||||
dependencies:
|
||||
@@ -1638,7 +1638,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.12.1, @babel/preset-env@npm:^7.12.11, @babel/preset-env@npm:^7.12.16, @babel/preset-env@npm:^7.13.10, @babel/preset-env@npm:^7.13.12, @babel/preset-env@npm:^7.13.8, @babel/preset-env@npm:^7.14.7, @babel/preset-env@npm:^7.15.6, @babel/preset-env@npm:^7.16.11, @babel/preset-env@npm:^7.16.4, @babel/preset-env@npm:^7.17.10, @babel/preset-env@npm:^7.18.2, @babel/preset-env@npm:^7.7.1, @babel/preset-env@npm:^7.7.7":
|
||||
"@babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.12.1, @babel/preset-env@npm:^7.12.11, @babel/preset-env@npm:^7.12.16, @babel/preset-env@npm:^7.13.10, @babel/preset-env@npm:^7.13.12, @babel/preset-env@npm:^7.13.8, @babel/preset-env@npm:^7.14.7, @babel/preset-env@npm:^7.15.6, @babel/preset-env@npm:^7.16.11, @babel/preset-env@npm:^7.16.4, @babel/preset-env@npm:^7.17.10, @babel/preset-env@npm:^7.18.2, @babel/preset-env@npm:^7.18.6, @babel/preset-env@npm:^7.7.1, @babel/preset-env@npm:^7.7.7":
|
||||
version: 7.18.6
|
||||
resolution: "@babel/preset-env@npm:7.18.6"
|
||||
dependencies:
|
||||
@@ -6524,7 +6524,7 @@ __metadata:
|
||||
"@standardnotes/models": "workspace:*"
|
||||
"@standardnotes/responses": ^1.6.39
|
||||
"@standardnotes/services": "workspace:*"
|
||||
"@standardnotes/sncrypto-common": ^1.9.0
|
||||
"@standardnotes/sncrypto-common": "workspace:*"
|
||||
"@standardnotes/utils": "workspace:*"
|
||||
"@types/jest": ^27.4.1
|
||||
"@types/node": ^18.0.0
|
||||
@@ -6601,7 +6601,7 @@ __metadata:
|
||||
"@standardnotes/models": "workspace:*"
|
||||
"@standardnotes/responses": ^1.6.39
|
||||
"@standardnotes/services": "workspace:*"
|
||||
"@standardnotes/sncrypto-common": ^1.9.0
|
||||
"@standardnotes/sncrypto-common": "workspace:*"
|
||||
"@standardnotes/utils": "workspace:*"
|
||||
"@types/jest": ^27.4.1
|
||||
"@typescript-eslint/eslint-plugin": ^5.30.0
|
||||
@@ -6905,7 +6905,7 @@ __metadata:
|
||||
"@standardnotes/react-native-aes": ^1.4.3
|
||||
"@standardnotes/react-native-textview": 1.1.0
|
||||
"@standardnotes/react-native-utils": 1.0.1
|
||||
"@standardnotes/sncrypto-common": 1.9.0
|
||||
"@standardnotes/sncrypto-common": "workspace:*"
|
||||
"@standardnotes/snjs": ^2.118.3
|
||||
"@standardnotes/stylekit": 5.29.3
|
||||
"@standardnotes/utils": "workspace:*"
|
||||
@@ -7166,23 +7166,45 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@standardnotes/sncrypto-common@npm:1.9.0, @standardnotes/sncrypto-common@npm:^1.7.1, @standardnotes/sncrypto-common@npm:^1.9.0":
|
||||
version: 1.9.0
|
||||
resolution: "@standardnotes/sncrypto-common@npm:1.9.0"
|
||||
checksum: 42252d71984b52756dff44ec3721961858e9f4227ca6555c0d60551852cb5f0a938b2b4969177c23c85d34e7f182369393f8b795afc65cff65b1c30b139f8f68
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@standardnotes/sncrypto-web@npm:1.10.1, @standardnotes/sncrypto-web@npm:^1.6.1":
|
||||
version: 1.10.1
|
||||
resolution: "@standardnotes/sncrypto-web@npm:1.10.1"
|
||||
"@standardnotes/sncrypto-common@^1.7.1, @standardnotes/sncrypto-common@^1.9.0, @standardnotes/sncrypto-common@workspace:*, @standardnotes/sncrypto-common@workspace:packages/sncrypto-common":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@standardnotes/sncrypto-common@workspace:packages/sncrypto-common"
|
||||
dependencies:
|
||||
"@standardnotes/sncrypto-common": ^1.9.0
|
||||
"@typescript-eslint/eslint-plugin": ^5.30.0
|
||||
"@typescript-eslint/parser": ^5.12.1
|
||||
eslint-plugin-prettier: ^4.2.1
|
||||
reflect-metadata: ^0.1.13
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@standardnotes/sncrypto-web@^1.6.1, @standardnotes/sncrypto-web@workspace:*, @standardnotes/sncrypto-web@workspace:packages/sncrypto-web":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@standardnotes/sncrypto-web@workspace:packages/sncrypto-web"
|
||||
dependencies:
|
||||
"@babel/core": ^7.18.6
|
||||
"@babel/plugin-syntax-dynamic-import": ^7.8.3
|
||||
"@babel/preset-env": ^7.18.6
|
||||
"@standardnotes/sncrypto-common": "workspace:*"
|
||||
"@types/libsodium-wrappers": ^0.7.9
|
||||
"@types/node": ^18.0.0
|
||||
"@types/uuid": ^8.3.4
|
||||
"@typescript-eslint/eslint-plugin": ^5.30.0
|
||||
babel-loader: ^8.2.3
|
||||
buffer: ^6.0.3
|
||||
libsodium-wrappers: ^0.7.9
|
||||
checksum: bce6e92e15303a1f3640615a87d50c618aebec1f960ff1aeaacb2097d5d535148256b620767ff002fc626785c68ec9329639f868647f74410c8b6b857a6443bd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
chai: ^4.3.6
|
||||
connect: ^3.7.0
|
||||
eslint-plugin-prettier: ^4.2.1
|
||||
libsodium-wrappers: ^0.7.10
|
||||
reflect-metadata: ^0.1.13
|
||||
regenerator-runtime: ^0.13.9
|
||||
serve-static: ^1.14.2
|
||||
ts-loader: ^9.2.6
|
||||
typedarray-to-buffer: ^4.0.0
|
||||
uuid: ^8.3.2
|
||||
webpack: ^5.69.1
|
||||
webpack-cli: ^4.9.2
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@standardnotes/snjs@npm:^2.118.3, @standardnotes/snjs@npm:^2.41.1":
|
||||
version: 2.118.3
|
||||
@@ -7372,7 +7394,7 @@ __metadata:
|
||||
"@standardnotes/files": "workspace:*"
|
||||
"@standardnotes/icons": "workspace:*"
|
||||
"@standardnotes/services": "workspace:*"
|
||||
"@standardnotes/sncrypto-web": 1.10.1
|
||||
"@standardnotes/sncrypto-web": "workspace:*"
|
||||
"@standardnotes/snjs": ^2.118.3
|
||||
"@standardnotes/styles": "workspace:*"
|
||||
"@standardnotes/toast": "workspace:*"
|
||||
@@ -8318,6 +8340,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/libsodium-wrappers@npm:^0.7.9":
|
||||
version: 0.7.9
|
||||
resolution: "@types/libsodium-wrappers@npm:0.7.9"
|
||||
checksum: 5ddf61b8047e38f00c1509369ad868a724235a67940971cc671b070ab9d6b2e6872407253be1163b174f742ecbb0986fda7b91aa0b7069437359b4671bfc529b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/lodash@npm:4.14.179":
|
||||
version: 4.14.179
|
||||
resolution: "@types/lodash@npm:4.14.179"
|
||||
@@ -9012,7 +9041,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/uuid@npm:8.3.4":
|
||||
"@types/uuid@npm:8.3.4, @types/uuid@npm:^8.3.4":
|
||||
version: 8.3.4
|
||||
resolution: "@types/uuid@npm:8.3.4"
|
||||
checksum: 6f11f3ff70f30210edaa8071422d405e9c1d4e53abbe50fdce365150d3c698fe7bbff65c1e71ae080cbfb8fded860dbb5e174da96fdbbdfcaa3fb3daa474d20f
|
||||
@@ -10579,6 +10608,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"assertion-error@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "assertion-error@npm:1.1.0"
|
||||
checksum: fd9429d3a3d4fd61782eb3962ae76b6d08aa7383123fca0596020013b3ebd6647891a85b05ce821c47d1471ed1271f00b0545cf6a4326cf2fc91efcc3b0fbecf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"assign-symbols@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "assign-symbols@npm:1.0.0"
|
||||
@@ -12652,6 +12688,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chai@npm:^4.3.6":
|
||||
version: 4.3.6
|
||||
resolution: "chai@npm:4.3.6"
|
||||
dependencies:
|
||||
assertion-error: ^1.1.0
|
||||
check-error: ^1.0.2
|
||||
deep-eql: ^3.0.1
|
||||
get-func-name: ^2.0.0
|
||||
loupe: ^2.3.1
|
||||
pathval: ^1.1.1
|
||||
type-detect: ^4.0.5
|
||||
checksum: acff93fd537f96d4a4d62dd83810285dffcfccb5089e1bf2a1205b28ec82d93dff551368722893cf85004282df10ee68802737c33c90c5493957ed449ed7ce71
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:2.4.2, chalk@npm:^2.0.0, chalk@npm:^2.0.1, chalk@npm:^2.4.1, chalk@npm:^2.4.2":
|
||||
version: 2.4.2
|
||||
resolution: "chalk@npm:2.4.2"
|
||||
@@ -12778,6 +12829,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"check-error@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "check-error@npm:1.0.2"
|
||||
checksum: d9d106504404b8addd1ee3f63f8c0eaa7cd962a1a28eb9c519b1c4a1dc7098be38007fc0060f045ee00f075fbb7a2a4f42abcf61d68323677e11ab98dc16042e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"check-types@npm:^11.1.1":
|
||||
version: 11.1.2
|
||||
resolution: "check-types@npm:11.1.2"
|
||||
@@ -13737,7 +13795,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"connect@npm:^3.6.5":
|
||||
"connect@npm:^3.6.5, connect@npm:^3.7.0":
|
||||
version: 3.7.0
|
||||
resolution: "connect@npm:3.7.0"
|
||||
dependencies:
|
||||
@@ -15796,6 +15854,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"deep-eql@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "deep-eql@npm:3.0.1"
|
||||
dependencies:
|
||||
type-detect: ^4.0.0
|
||||
checksum: 4f4c9fb79eb994fb6e81d4aa8b063adc40c00f831588aa65e20857d5d52f15fb23034a6576ecf886f7ff6222d5ae42e71e9b7d57113e0715b1df7ea1e812b125
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"deep-equal@npm:^1.0.1":
|
||||
version: 1.1.1
|
||||
resolution: "deep-equal@npm:1.1.1"
|
||||
@@ -19273,6 +19340,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-func-name@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "get-func-name@npm:2.0.0"
|
||||
checksum: 8d82e69f3e7fab9e27c547945dfe5cc0c57fc0adf08ce135dddb01081d75684a03e7a0487466f478872b341d52ac763ae49e660d01ab83741f74932085f693c3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1":
|
||||
version: 1.1.2
|
||||
resolution: "get-intrinsic@npm:1.1.2"
|
||||
@@ -24335,7 +24409,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"libsodium-wrappers@npm:^0.7.9":
|
||||
"libsodium-wrappers@npm:^0.7.10":
|
||||
version: 0.7.10
|
||||
resolution: "libsodium-wrappers@npm:0.7.10"
|
||||
dependencies:
|
||||
@@ -24919,6 +24993,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"loupe@npm:^2.3.1":
|
||||
version: 2.3.4
|
||||
resolution: "loupe@npm:2.3.4"
|
||||
dependencies:
|
||||
get-func-name: ^2.0.0
|
||||
checksum: 5af91db61aa18530f1749a64735ee194ac263e65e9f4d1562bf3036c591f1baa948289c193e0e34c7b5e2c1b75d3c1dc4fce87f5edb3cee10b0c0df46bc9ffb3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lower-case@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "lower-case@npm:2.0.2"
|
||||
@@ -28939,6 +29022,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pathval@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "pathval@npm:1.1.1"
|
||||
checksum: 090e3147716647fb7fb5b4b8c8e5b55e5d0a6086d085b6cd23f3d3c01fcf0ff56fd3cc22f2f4a033bd2e46ed55d61ed8379e123b42afe7d531a2a5fc8bb556d6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pbkdf2@npm:^3.0.3":
|
||||
version: 3.1.2
|
||||
resolution: "pbkdf2@npm:3.1.2"
|
||||
@@ -34543,7 +34633,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"serve-static@npm:1.15.0, serve-static@npm:^1.13.1":
|
||||
"serve-static@npm:1.15.0, serve-static@npm:^1.13.1, serve-static@npm:^1.14.2":
|
||||
version: 1.15.0
|
||||
resolution: "serve-static@npm:1.15.0"
|
||||
dependencies:
|
||||
@@ -37059,7 +37149,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-loader@npm:^9.2.8, ts-loader@npm:^9.3.0":
|
||||
"ts-loader@npm:^9.2.6, ts-loader@npm:^9.2.8, ts-loader@npm:^9.3.0":
|
||||
version: 9.3.1
|
||||
resolution: "ts-loader@npm:9.3.1"
|
||||
dependencies:
|
||||
@@ -37214,7 +37304,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-detect@npm:4.0.8":
|
||||
"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.5":
|
||||
version: 4.0.8
|
||||
resolution: "type-detect@npm:4.0.8"
|
||||
checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15
|
||||
@@ -37329,6 +37419,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typedarray-to-buffer@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "typedarray-to-buffer@npm:4.0.0"
|
||||
checksum: c1e4dc6597c98de417c3363da88263d92aefd23569a892b2d8a9b9385858b2c7323f6cae010ecb73fa63cae403d20763b8cad9a25a77f5597a9fb3da506ac7df
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typedarray@npm:^0.0.6":
|
||||
version: 0.0.6
|
||||
resolution: "typedarray@npm:0.0.6"
|
||||
@@ -38943,7 +39040,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webpack@npm:*, webpack@npm:^5.64.4, webpack@npm:^5.72.0, webpack@npm:^5.72.1":
|
||||
"webpack@npm:*, webpack@npm:^5.64.4, webpack@npm:^5.69.1, webpack@npm:^5.72.0, webpack@npm:^5.72.1":
|
||||
version: 5.73.0
|
||||
resolution: "webpack@npm:5.73.0"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user