moved to occulto

This commit is contained in:
Niccolo Borgioli 2023-01-13 21:24:27 +01:00
parent 436ae2a7e5
commit 6fb7518b6a
No known key found for this signature in database
GPG Key ID: D93C615F75EE4F0B
11 changed files with 315 additions and 301 deletions

View File

@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.2.0] - 2023-01-13
### Changed
- Default port is now 8000, not 5000.
- Moved to generic encryption library `occulto`.
### Security
- Updated dependencies.
## [2.1.0] - 2023-01-04 ## [2.1.0] - 2023-01-04
### Added ### Added

View File

@ -10,8 +10,8 @@
"test:prepare": "docker compose -f docker-compose.dev.yaml build" "test:prepare": "docker compose -f docker-compose.dev.yaml build"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.29.1", "@playwright/test": "^1.29.2",
"@types/node": "^16.18.10", "@types/node": "^16.18.11",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"npm-run-all": "^4.1.5" "npm-run-all": "^4.1.5"
} }

View File

@ -425,7 +425,7 @@ dependencies = [
[[package]] [[package]]
name = "cryptgeon" name = "cryptgeon"
version = "2.1.0" version = "2.2.0"
dependencies = [ dependencies = [
"actix-files", "actix-files",
"actix-web", "actix-web",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "cryptgeon" name = "cryptgeon"
version = "2.1.0" version = "2.2.0"
authors = ["cupcakearmy <hi@nicco.io>"] authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2021" edition = "2021"

View File

@ -11,27 +11,28 @@
}, },
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@lokalise/node-api": "^9.3.0", "@lokalise/node-api": "^9.5.0",
"@sveltejs/adapter-static": "^1.0.0", "@sveltejs/adapter-static": "^1.0.2",
"@sveltejs/kit": "^1.0.1", "@sveltejs/kit": "^1.0.13",
"@types/dompurify": "^2.4.0", "@types/dompurify": "^2.4.0",
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"@zerodevx/svelte-toast": "^0.7.2", "@zerodevx/svelte-toast": "^0.7.2",
"adm-zip": "^0.5.10", "adm-zip": "^0.5.10",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"svelte": "^3.55.0", "svelte": "^3.55.1",
"svelte-check": "^2.10.3", "svelte-check": "^2.10.3",
"svelte-intl-precompile": "^0.10.1", "svelte-intl-precompile": "^0.10.1",
"svelte-preprocess": "^4.10.7", "svelte-preprocess": "^4.10.7",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^4.9.4", "typescript": "^4.9.4",
"vite": "^4.0.3" "vite": "^4.0.4"
}, },
"dependencies": { "dependencies": {
"@fontsource/fira-mono": "^4.5.10", "@fontsource/fira-mono": "^4.5.10",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
"dompurify": "^2.4.1", "dompurify": "^2.4.3",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"occulto": "2.0.0-rc.10",
"pretty-bytes": "^6.0.0", "pretty-bytes": "^6.0.0",
"qrious": "^4.0.2" "qrious": "^4.0.2"
} }

View File

@ -1,34 +1,33 @@
import { AES, Bytes, type TypedArray } from 'occulto'
import type { EncryptedFileDTO, FileDTO } from './api' import type { EncryptedFileDTO, FileDTO } from './api'
import { Crypto } from './crypto'
abstract class CryptAdapter<T> { abstract class CryptAdapter<T> {
abstract encrypt(plaintext: T, key: CryptoKey): Promise<string> abstract encrypt(plaintext: T, key: TypedArray): Promise<string>
abstract decrypt(ciphertext: string, key: CryptoKey): Promise<T> abstract decrypt(ciphertext: string, key: TypedArray): Promise<T>
} }
class CryptTextAdapter implements CryptAdapter<string> { class CryptTextAdapter implements CryptAdapter<string> {
async encrypt(plaintext: string, key: CryptoKey) { async encrypt(plaintext: string, key: TypedArray) {
return await Crypto.encrypt(new TextEncoder().encode(plaintext), key) return await AES.encrypt(Bytes.encode(plaintext), key)
} }
async decrypt(ciphertext: string, key: CryptoKey) { async decrypt(ciphertext: string, key: TypedArray) {
const plaintext = await Crypto.decrypt(ciphertext, key) return Bytes.decode(await AES.decrypt(ciphertext, key))
return new TextDecoder().decode(plaintext)
} }
} }
class CryptBlobAdapter implements CryptAdapter<Blob> { class CryptBlobAdapter implements CryptAdapter<Blob> {
async encrypt(plaintext: Blob, key: CryptoKey) { async encrypt(plaintext: Blob, key: TypedArray) {
return await Crypto.encrypt(await plaintext.arrayBuffer(), key) return await AES.encrypt(new Uint8Array(await plaintext.arrayBuffer()), key)
} }
async decrypt(ciphertext: string, key: CryptoKey) { async decrypt(ciphertext: string, key: TypedArray) {
const plaintext = await Crypto.decrypt(ciphertext, key) const plaintext = await AES.decrypt(ciphertext, key)
return new Blob([plaintext], { type: 'application/octet-stream' }) return new Blob([plaintext], { type: 'application/octet-stream' })
} }
} }
class CryptFilesAdapter implements CryptAdapter<FileDTO[]> { class CryptFilesAdapter implements CryptAdapter<FileDTO[]> {
async encrypt(plaintext: FileDTO[], key: CryptoKey) { async encrypt(plaintext: FileDTO[], key: TypedArray) {
const adapter = new CryptBlobAdapter() const adapter = new CryptBlobAdapter()
const data: Promise<EncryptedFileDTO>[] = plaintext.map(async (file) => ({ const data: Promise<EncryptedFileDTO>[] = plaintext.map(async (file) => ({
name: file.name, name: file.name,
@ -39,7 +38,7 @@ class CryptFilesAdapter implements CryptAdapter<FileDTO[]> {
return JSON.stringify(await Promise.all(data)) return JSON.stringify(await Promise.all(data))
} }
async decrypt(ciphertext: string, key: CryptoKey) { async decrypt(ciphertext: string, key: TypedArray) {
const adapter = new CryptBlobAdapter() const adapter = new CryptBlobAdapter()
const data: EncryptedFileDTO[] = JSON.parse(ciphertext) const data: EncryptedFileDTO[] = JSON.parse(ciphertext)
const files: FileDTO[] = await Promise.all( const files: FileDTO[] = await Promise.all(

View File

@ -1,89 +0,0 @@
export class Hex {
static encode(buffer: ArrayBuffer): string {
let s = ''
for (const i of new Uint8Array(buffer)) {
s += i.toString(16).padStart(2, '0')
}
return s
}
static decode(s: string): ArrayBuffer {
const size = s.length / 2
const buffer = new Uint8Array(size)
for (let i = 0; i < size; i++) {
const idx = i * 2
const segment = s.slice(idx, idx + 2)
buffer[i] = parseInt(segment, 16)
}
return buffer
}
}
export class ArrayBufferUtils {
static async toString(buffer: ArrayBuffer): Promise<string> {
const reader = new window.FileReader()
reader.readAsDataURL(new Blob([buffer]))
return new Promise((resolve) => {
reader.onloadend = () => resolve(reader.result as string)
})
}
static async fromString(s: string): Promise<ArrayBuffer> {
return fetch(s)
.then((r) => r.blob())
.then((b) => b.arrayBuffer())
}
}
export class Keys {
public static async generateKey(size: 128 | 192 | 256 = 256): Promise<CryptoKey> {
const key = await window.crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: size,
},
true,
['encrypt', 'decrypt']
)
return key
}
public static async export(key: CryptoKey): Promise<string> {
return Hex.encode(await window.crypto.subtle.exportKey('raw', key))
}
public static async import(key: string): Promise<CryptoKey> {
return window.crypto.subtle.importKey('raw', Hex.decode(key), { name: 'AES-GCM' }, true, [
'encrypt',
'decrypt',
])
}
}
export class Crypto {
private static ALG = 'AES-GCM'
private static DELIMITER = ':::'
public static getRandomBytes(size: number): Uint8Array {
return window.crypto.getRandomValues(new Uint8Array(size))
}
public static async encrypt(plaintext: ArrayBuffer, key: CryptoKey): Promise<string> {
const iv = this.getRandomBytes(12) // AES-GCM needs a 96bit IV
const encrypted: ArrayBuffer = await window.crypto.subtle.encrypt(
{ name: this.ALG, iv },
key,
plaintext
)
const data = [Hex.encode(iv), await ArrayBufferUtils.toString(encrypted)].join(this.DELIMITER)
return data
}
public static async decrypt(ciphertext: string, key: CryptoKey): Promise<ArrayBuffer> {
const splitted = ciphertext.split(this.DELIMITER)
const iv = Hex.decode(splitted[0])
const encrypted = await ArrayBufferUtils.fromString(splitted[1])
const plaintext = await window.crypto.subtle.decrypt({ name: this.ALG, iv }, key, encrypted)
return plaintext
}
}

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Crypto, Hex } from '$lib/crypto'
import Icon from '$lib/ui/Icon.svelte' import Icon from '$lib/ui/Icon.svelte'
import { copy as copyFN } from '$lib/utils' import { copy as copyFN } from '$lib/utils'
import { getRandomBytes, Hex } from 'occulto'
export let label: string = '' export let label: string = ''
export let value: any export let value: any
@ -23,8 +23,9 @@
function toggle() { function toggle() {
hidden = !hidden hidden = !hidden
} }
function randomFN() {
value = Hex.encode(Crypto.getRandomBytes(32)) async function randomFN() {
value = Hex.encode(await getRandomBytes(32))
} }
</script> </script>

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { AES, Hex } from 'occulto'
import { t } from 'svelte-intl-precompile' import { t } from 'svelte-intl-precompile'
import { blur } from 'svelte/transition' import { blur } from 'svelte/transition'
import { Adapters } from '$lib/adapters' import { Adapters } from '$lib/adapters'
import type { FileDTO, Note } from '$lib/api' import type { FileDTO, Note } from '$lib/api'
import { create, PayloadToLargeError } from '$lib/api' import { create, PayloadToLargeError } from '$lib/api'
import { Keys } from '$lib/crypto'
import { status } from '$lib/stores/status' import { status } from '$lib/stores/status'
import { notify } from '$lib/toast' import { notify } from '$lib/toast'
import AdvancedParameters from '$lib/ui/AdvancedParameters.svelte' import AdvancedParameters from '$lib/ui/AdvancedParameters.svelte'
@ -58,8 +58,8 @@
try { try {
loading = $t('common.encrypting') loading = $t('common.encrypting')
const key = await Keys.generateKey() const key = await AES.generateKey()
const password = await Keys.export(key) const password = await Hex.encode(key)
const data: Note = { const data: Note = {
contents: '', contents: '',

View File

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import { Hex } from 'occulto'
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { t } from 'svelte-intl-precompile' import { t } from 'svelte-intl-precompile'
import { Adapters } from '$lib/adapters' import { Adapters } from '$lib/adapters'
import { get, info } from '$lib/api' import { get, info } from '$lib/api'
import { Keys } from '$lib/crypto'
import Button from '$lib/ui/Button.svelte' import Button from '$lib/ui/Button.svelte'
import Loader from '$lib/ui/Loader.svelte' import Loader from '$lib/ui/Loader.svelte'
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte' import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
@ -43,7 +43,7 @@
loading = $t('common.downloading') loading = $t('common.downloading')
const data = await get(id) const data = await get(id)
loading = $t('common.decrypting') loading = $t('common.decrypting')
const key = await Keys.import(password) const key = await Hex.decode(password)
switch (data.meta.type) { switch (data.meta.type) {
case 'text': case 'text':
note = { note = {

451
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff