Skip to content

Commit

Permalink
feat: handle ciphertext prefixed with iv
Browse files Browse the repository at this point in the history
  • Loading branch information
jarrvis committed Nov 2, 2023
1 parent 38a79f5 commit 14d2d1d
Show file tree
Hide file tree
Showing 5 changed files with 22 additions and 36 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
},
"dependencies": {
"@akord/akord-auth": "0.29.1",
"@akord/crypto": "0.14.0",
"@akord/crypto": "0.15.0-beta.0",
"@akord/ts-cacheable": "1.0.11",
"axios": "^0.24.0",
"fetch-undici": "^3.0.1",
Expand Down
2 changes: 0 additions & 2 deletions src/akord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export class Akord {
public membership: MembershipService;
public vault: VaultService;
public stack: StackService;
public file: FileService;
public note: NoteService;
public manifest: ManifestService;
public profile: ProfileService;
Expand All @@ -48,7 +47,6 @@ export class Akord {
this.memo = new MemoService(wallet, this.api);
this.folder = new FolderService(wallet, this.api);
this.stack = new StackService(wallet, this.api);
this.file = new FileService(wallet, this.api);
this.note = new NoteService(wallet, this.api);
this.manifest = new ManifestService(wallet, this.api);
this.membership = new MembershipService(wallet, this.api);
Expand Down
28 changes: 7 additions & 21 deletions src/core/file.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Service } from "./service";
import { protocolTags, encryptionTags as encTags, fileTags, dataTags, smartweaveTags } from "../constants";
import { base64ToArray, digestRaw, initDigest, signHash } from "@akord/crypto";
import { AUTH_TAG_LENGTH_IN_BYTES, IV_LENGTH_IN_BYTES, base64ToArray, digestRaw, initDigest, signHash } from "@akord/crypto";
import { Logger } from "../logger";
import { ApiClient } from "../api/api-client";
import { v4 as uuid } from "uuid";
import { FileLike, FileSource } from "../types/file";
import { Blob } from "buffer";
import { Tag, Tags } from "../types/contract";
Expand All @@ -14,13 +13,12 @@ import { FileVersion } from "../types";
import { Readable } from "stream";
import { BadRequest } from "../errors/bad-request";
import { StorageType } from "../types/node";
import { InternalError } from "../errors/internal-error";

const DEFAULT_FILE_TYPE = "text/plain";
const BYTES_IN_MB = 1000000;
const DEFAULT_CHUNK_SIZE_IN_BYTES = 10 * BYTES_IN_MB
const MINIMAL_CHUNK_SIZE_IN_BYTES = 5 * BYTES_IN_MB
export const IV_LENGTH_IN_BYTES = 16;


class FileService extends Service {
contentType = null as string;
Expand Down Expand Up @@ -113,23 +111,18 @@ class FileService extends Service {
options: FileUploadOptions
): Promise<FileUploadResult> {
const isPublic = options.public || this.isPublic
const iv : string[] = []
let encryptedKey : string

const { processedData, encryptionTags } = await this.processWriteRaw(await file.arrayBuffer());
const resourceHash = await digestRaw(new Uint8Array(processedData));
const fileSignatureTags = await this.getFileSignatureTags(resourceHash)
const resource = await this.api.uploadFile(processedData, tags.concat(encryptionTags).concat(fileSignatureTags), options);
const resourceUri = resource.resourceUri;
resourceUri.push(`hash:${resourceHash}`);
if (!isPublic) {
const chunkIv = encryptionTags.find((tag) => tag.name === encTags.IV)?.value;
if (!chunkIv) {
throw new InternalError("Failed to encrypt data");
}
iv.push(chunkIv);
encryptedKey = encryptionTags.find((tag) => tag.name === encTags.ENCRYPTED_KEY).value;
}
return { resourceUri: resourceUri, iv: iv, encryptedKey: encryptedKey }
return { resourceUri: resourceUri, encryptedKey: encryptedKey }
}

private async uploadChunked(
Expand All @@ -146,11 +139,10 @@ class FileService extends Service {

const isPublic = options.public || this.isPublic
const chunkSize = options.chunkSize || DEFAULT_CHUNK_SIZE_IN_BYTES;
const chunkSizeWithAuthTag = isPublic ? chunkSize : chunkSize + IV_LENGTH_IN_BYTES;
const chunkSizeWithNonceAndIv = isPublic ? chunkSize : chunkSize + AUTH_TAG_LENGTH_IN_BYTES + IV_LENGTH_IN_BYTES;
const numberOfChunks = Math.ceil(file.size / chunkSize);
const fileSize = isPublic ? file.size : file.size + numberOfChunks * IV_LENGTH_IN_BYTES;
const fileSize = isPublic ? file.size : file.size + numberOfChunks * (AUTH_TAG_LENGTH_IN_BYTES + IV_LENGTH_IN_BYTES);
const digestObject = initDigest();
const iv: Array<string> = [];
const etags: Array<string> = [];


Expand All @@ -162,11 +154,6 @@ class FileService extends Service {

if (!isPublic) {
encryptionTags = encryptedData.encryptionTags;
const chunkIv = encryptionTags.find((tag) => tag.name === encTags.IV)?.value;
if (!chunkIv) {
throw new InternalError("Failed to encrypt data");
}
iv.push(chunkIv);
if (!encryptedKey) {
encryptedKey = encryptionTags.find((tag) => tag.name === encTags.ENCRYPTED_KEY).value;
}
Expand Down Expand Up @@ -198,8 +185,7 @@ class FileService extends Service {
return {
resourceUri: resource.resourceUri,
numberOfChunks: numberOfChunks,
chunkSize: chunkSizeWithAuthTag,
iv: iv,
chunkSize: chunkSizeWithNonceAndIv,
encryptedKey: encryptedKey
};
}
Expand Down
3 changes: 1 addition & 2 deletions src/core/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,13 +217,12 @@ class Service {
} else {
let encryptedFile: EncryptedPayload;
try {
encryptedFile = await this.dataEncrypter.encryptRaw(new Uint8Array(data), false, encryptedKey) as EncryptedPayload;
encryptedFile = await this.dataEncrypter.encryptRaw(new Uint8Array(data), { prefixCiphertextWithIv: true, encode: false, encryptedKey: encryptedKey }) as EncryptedPayload;
} catch (error) {
throw new IncorrectEncryptionKey(error);
}
processedData = encryptedFile.encryptedData.ciphertext as ArrayBuffer;
const { address } = await this.getActiveKey();
tags.push(new Tag(encryptionTags.IV, encryptedFile.encryptedData.iv))
tags.push(new Tag(encryptionTags.ENCRYPTED_KEY, encryptedFile.encryptedKey))
tags.push(new Tag(encryptionTags.PUBLIC_ADDRESS, address))
}
Expand Down
23 changes: 13 additions & 10 deletions src/core/stack.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { NodeService } from "./node";
import { actionRefs, functions, objectType } from "../constants";
import { FileDownloadOptions, FileGetOptions, FileService, FileUploadOptions, FileVersionData, IV_LENGTH_IN_BYTES, createFileLike } from "./file";
import { FileDownloadOptions, FileGetOptions, FileService, FileUploadOptions, FileVersionData, createFileLike } from "./file";
import { FileSource } from "../types/file";
import { FileVersion, NodeCreateOptions, Stack, StackCreateOptions, StackCreateResult, StackUpdateResult, StorageType, nodeType } from "../types";
import { StreamConverter } from "../util/stream-converter";
import { ReadableStream } from 'web-streams-polyfill/ponyfill/es2018';
import { AUTH_TAG_LENGTH_IN_BYTES, IV_LENGTH_IN_BYTES, PROXY_DOWNLOAD_URL } from "@akord/crypto";

class StackService extends NodeService<Stack> {
public fileService = new FileService(this.wallet, this.api);
Expand Down Expand Up @@ -133,10 +134,10 @@ class StackService extends NodeService<Stack> {
if (service.isPublic) {
stream = file.fileData as ReadableStream<Uint8Array>
} else {
const encryptedKey = version.encryptedKey || file.metadata.encryptedKey;
const iv = version.iv || file.metadata.iv?.split(',');
const streamChunkSize = version.chunkSize || version.size + IV_LENGTH_IN_BYTES;
stream = await service.dataEncrypter.decryptStream(file.fileData as ReadableStream, encryptedKey, iv, streamChunkSize);
const encryptedKey = file.metadata.encryptedKey;
const iv = file.metadata.iv?.split(',');
const streamChunkSize = version.chunkSize || version.size + AUTH_TAG_LENGTH_IN_BYTES + (iv ? 0 : IV_LENGTH_IN_BYTES);
stream = await service.dataEncrypter.decryptStream(file.fileData as ReadableStream, encryptedKey, streamChunkSize, iv);
}

let data: ReadableStream<Uint8Array> | ArrayBuffer;
Expand All @@ -154,8 +155,8 @@ class StackService extends NodeService<Stack> {
* @param {number} [index] stack version index
* @returns Promise with version name & data buffer
*/
public async download(stackId: string, index: number = 0, options: FileDownloadOptions = {}): Promise<void> {
let downloadPromise: Promise<void>
public async download(stackId: string, index: number = 0, options: FileDownloadOptions = {}): Promise<string | void> {
let downloadPromise: Promise<string | void>
if (typeof window === 'undefined' || !navigator.serviceWorker?.controller) {
const { name, type, data } = await this.getVersion(stackId, index, { ...options, responseType: 'stream' });
const path = `${options.path}/${name}`;
Expand All @@ -167,14 +168,16 @@ class StackService extends NodeService<Stack> {
const stack = new Stack(stackProto, stackProto.__keys__);
const version = stack.getVersion(index);
const id = version.getUri(StorageType.S3);
const url = `${service.api.config.gatewayurl}/internal/${id}`
const proxyUrl = `${PROXY_DOWNLOAD_URL}/${id}`
await service.setVaultContext(stack.vaultId);

const workerMessage = {
type: 'init',
chunkSize: version.chunkSize,
size: version.size,
id: id,
url: `${service.api.config.gatewayurl}/internal/${id}`
url: url
} as Record<string, any>;

if (!service.isPublic) {
Expand All @@ -188,7 +191,7 @@ class StackService extends NodeService<Stack> {

downloadPromise = new Promise((resolve, reject) => {
if (options.skipSave) {
resolve();
resolve(proxyUrl);
} else {
const interval = setInterval(() => {
const channel = new MessageChannel();
Expand Down Expand Up @@ -226,7 +229,7 @@ class StackService extends NodeService<Stack> {
}
if (!options.skipSave) {
const anchor = document.createElement('a');
anchor.href = `/api/proxy/download/${id}`;
anchor.href = proxyUrl;
document.body.appendChild(anchor);
anchor.click();
}
Expand Down

0 comments on commit 14d2d1d

Please sign in to comment.