Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ignore everything
**/*

# Un-ignore the file types we want Prettier to format
!**/*.ts
!**/*.tsx
!**/*.js
!**/*.jsx
!**/*.json
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ The IronWeb SDK NPM releases follow standard [Semantic Versioning](https://semve

**Note:** The patch versions of the IronWeb SDK will not be sequential and might jump by multiple numbers between sequential releases.

## v4.3.0
- add streaming encrypt and decrypt functionality for managed and unmanaged documents.

## v4.2.49
- update `qs` to fix a security vulnerability
- switch release process to [Trusted Publishing](https://docs.npmjs.com/trusted-publishers)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ To run a subset of the tests you can use the `-t` option of Jest to only run tes

`yarn run unit GroupCrypto`

Copyright (c) 2022 IronCore Labs, Inc.
Copyright (c) 2026 IronCore Labs, Inc.
All rights reserved.
5 changes: 3 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
let
pkgs = nixpkgs.legacyPackages.${system};
in
rec {
{
devShell = pkgs.mkShell {
buildInputs = with pkgs.nodePackages; [
buildInputs = [
pkgs.prettier
pkgs.nodejs_24
(pkgs.yarn.override { nodejs = pkgs.nodejs_24; })
];
Expand Down
25 changes: 25 additions & 0 deletions ironweb.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,22 @@ export interface DocumentAccessResponse {
succeeded: UserOrGroup[];
failed: (UserOrGroup & {error: string})[];
}
export interface StreamEncryptResponse extends DocumentIDNameResponse {
encryptedStream: ReadableStream<Uint8Array>;
}
export interface StreamEncryptUnmanagedResponse {
documentID: string;
encryptedStream: ReadableStream<Uint8Array>;
edeks: Uint8Array;
}
export interface StreamDecryptResponse extends DocumentMetaResponse {
plaintextStream: ReadableStream<Uint8Array>;
}
export interface StreamDecryptUnmanagedResponse {
documentID: string;
plaintextStream: ReadableStream<Uint8Array>;
accessVia: UserOrGroup;
}

/**
* Group SDK response types
Expand Down Expand Up @@ -191,8 +207,10 @@ export interface Document {
getMetadata(documentID: string): Promise<DocumentMetaResponse>;
getDocumentIDFromBytes(encryptedDocument: Uint8Array): Promise<string | null>;
decrypt(documentID: string, encryptedDocument: Uint8Array): Promise<DecryptedDocumentResponse>;
decryptStream(documentID: string, encryptedStream: ReadableStream<Uint8Array>): Promise<StreamDecryptResponse>;
decryptFromStore(documentID: string): Promise<DecryptedDocumentResponse>;
encrypt(documentData: Uint8Array, options?: DocumentCreateOptions): Promise<EncryptedDocumentResponse>;
encryptStream(plaintextStream: ReadableStream<Uint8Array>, options?: DocumentCreateOptions): Promise<StreamEncryptResponse>;
encryptToStore(documentData: Uint8Array, options?: DocumentCreateOptions): Promise<DocumentIDNameResponse>;
updateEncryptedData(documentID: string, newDocumentData: Uint8Array): Promise<EncryptedDocumentResponse>;
updateEncryptedDataInStore(documentID: string, newDocumentData: Uint8Array): Promise<DocumentIDNameResponse>;
Expand All @@ -201,7 +219,12 @@ export interface Document {
revokeAccess(documentID: string, revokeList: DocumentAccessList): Promise<DocumentAccessResponse>;
advanced: {
decryptUnmanaged(data: Uint8Array, edeks: Uint8Array): Promise<DecryptedUnmanagedDocumentResponse>;
decryptStreamUnmanaged(encryptedStream: ReadableStream<Uint8Array>, edeks: Uint8Array): Promise<StreamDecryptUnmanagedResponse>;
encryptUnmanaged(documentData: Uint8Array, options?: Omit<DocumentCreateOptions, "documentName">): Promise<EncryptedUnmanagedDocumentResponse>;
encryptStreamUnmanaged(
plaintextStream: ReadableStream<Uint8Array>,
options?: Omit<DocumentCreateOptions, "documentName">
): Promise<StreamEncryptUnmanagedResponse>;
};
}

Expand Down Expand Up @@ -297,6 +320,8 @@ export interface ErrorCodes {
DOCUMENT_CREATE_WITH_ACCESS_FAILURE: 311;
DOCUMENT_HEADER_PARSE_FAILURE: 312;
DOCUMENT_TRANSFORM_REQUEST_FAILURE: 313;
DOCUMENT_STREAM_DECRYPT_FAILURE: 314;
DOCUMENT_STREAM_ENCRYPT_FAILURE: 315;
GROUP_LIST_REQUEST_FAILURE: 400;
GROUP_GET_REQUEST_FAILURE: 401;
GROUP_CREATE_REQUEST_FAILURE: 402;
Expand Down
58 changes: 29 additions & 29 deletions nightwatch.json
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
{
"src_folders": ["integration/nightwatch/tests/"],
"output_folder": "./bin/nightwatch",
"custom_assertions_path": "",
"page_objects_path": "./integration/nightwatch/pageObjects",
"globals_path": "./integration/nightwatch/globalsModule.js",
"src_folders": ["integration/nightwatch/tests/"],
"output_folder": "./bin/nightwatch",
"custom_assertions_path": "",
"page_objects_path": "./integration/nightwatch/pageObjects",
"globals_path": "./integration/nightwatch/globalsModule.js",

"selenium": {
"start_process": false
},

"test_settings": {
"default": {
"launch_url": "https://dev1.scrambledbits.org:4500",
"selenium_port": 9515,
"selenium_host": "localhost",
"default_path_prefix" : "",
"desiredCapabilities": {
"browserName": "chrome",
"comment": "'window-size=1920,1080', 'headless', 'disable-dev-shm-usage'",
"chromeOptions": {
"args": ["no-sandbox", "window-size=1920,1080", "headless", "disable-dev-shm-usage"]
},
"acceptSslCerts": true
}
"selenium": {
"start_process": false
},

"chrome": {
"desiredCapabilities": {
"browserName": "chrome"
}
"test_settings": {
"default": {
"launch_url": "https://dev1.scrambledbits.org:4500",
"selenium_port": 9515,
"selenium_host": "localhost",
"default_path_prefix": "",
"desiredCapabilities": {
"browserName": "chrome",
"comment": "'window-size=1920,1080', 'headless', 'disable-dev-shm-usage'",
"chromeOptions": {
"args": ["no-sandbox", "window-size=1920,1080", "headless", "disable-dev-shm-usage"]
},
"acceptSslCerts": true
}
},

"chrome": {
"desiredCapabilities": {
"browserName": "chrome"
}
}
}
}
}
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"license": "AGPL-3.0-only",
"version": "4.2.56",
"version": "4.3.0",
"scripts": {
"cleanTest": "find dist -type d -name tests -prune -exec rm -rf {} \\;",
"lint": "eslint . --ext .ts,.tsx",
Expand All @@ -16,6 +16,7 @@
},
"dependencies": {
"@ironcorelabs/recrypt-wasm-binding": "0.7.1",
"@noble/ciphers": "1.2.1",
"@stablelib/ed25519": "1.0.2",
"@stablelib/utf8": "1.0.1",
"base64-js": "1.5.1",
Expand Down Expand Up @@ -73,4 +74,4 @@
"jsxBracketSameLine": true,
"arrowParens": "always"
}
}
}
2 changes: 2 additions & 0 deletions src/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export enum ErrorCodes {
DOCUMENT_CREATE_WITH_ACCESS_FAILURE = 311,
DOCUMENT_HEADER_PARSE_FAILURE = 312,
DOCUMENT_TRANSFORM_REQUEST_FAILURE = 313,
DOCUMENT_STREAM_DECRYPT_FAILURE = 314,
DOCUMENT_STREAM_ENCRYPT_FAILURE = 315,
GROUP_LIST_REQUEST_FAILURE = 400,
GROUP_GET_REQUEST_FAILURE = 401,
GROUP_CREATE_REQUEST_FAILURE = 402,
Expand Down
82 changes: 82 additions & 0 deletions src/FrameMessageTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,80 @@ export interface SearchTransliterateStringResponse {
message: string;
}

/* Streaming Decrypt */
export interface DocumentStreamDecryptRequest {
type: "DOCUMENT_STREAM_DECRYPT";
message: {
documentID: string;
iv: Uint8Array;
encryptedStream: ReadableStream<Uint8Array>;
};
}
export interface DocumentStreamDecryptResponse {
type: "DOCUMENT_STREAM_DECRYPT_RESPONSE";
message: Omit<DocumentMetaResponse, "documentID"> & {plaintextStream: ReadableStream<Uint8Array>};
}

export interface DocumentUnmanagedStreamDecryptRequest {
type: "DOCUMENT_UNMANAGED_STREAM_DECRYPT";
message: {
edeks: Uint8Array;
iv: Uint8Array;
encryptedStream: ReadableStream<Uint8Array>;
};
}
export interface DocumentUnmanagedStreamDecryptResponse {
type: "DOCUMENT_UNMANAGED_STREAM_DECRYPT_RESPONSE";
message: {
accessVia: UserOrGroup;
plaintextStream: ReadableStream<Uint8Array>;
};
}

/* Streaming Encrypt */
export interface DocumentStreamEncryptRequest {
type: "DOCUMENT_STREAM_ENCRYPT";
message: {
documentID: string;
documentName: string;
plaintextStream: ReadableStream<Uint8Array>;
userGrants: string[];
groupGrants: string[];
grantToAuthor: boolean;
policy?: Policy;
};
}
export interface DocumentStreamEncryptResponse {
type: "DOCUMENT_STREAM_ENCRYPT_RESPONSE";
message: {
documentID: string;
documentName: string | null;
encryptedStream: ReadableStream<Uint8Array>;
created: string;
updated: string;
};
}

export interface DocumentUnmanagedStreamEncryptRequest {
type: "DOCUMENT_UNMANAGED_STREAM_ENCRYPT";
message: {
documentID: string;
plaintextStream: ReadableStream<Uint8Array>;
userGrants: string[];
groupGrants: string[];
grantToAuthor: boolean;
policy?: Policy;
};
}
export interface DocumentUnmanagedStreamEncryptResponse {
type: "DOCUMENT_UNMANAGED_STREAM_ENCRYPT_RESPONSE";
message: {
documentID: string;
edeks: Uint8Array;
encryptedStream: ReadableStream<Uint8Array>;
};
}

export interface ErrorResponse {
type: "ERROR_RESPONSE";
message: {
Expand All @@ -531,6 +605,10 @@ export type RequestMessage =
| DocumentStoreEncryptRequest
| DocumentEncryptRequest
| DocumentStoreUpdateDataRequest
| DocumentStreamDecryptRequest
| DocumentUnmanagedStreamDecryptRequest
| DocumentStreamEncryptRequest
| DocumentUnmanagedStreamEncryptRequest
| DocumentUpdateDataRequest
| DocumentUpdateNameRequest
| DocumentGrantRequest
Expand Down Expand Up @@ -571,6 +649,10 @@ export type ResponseMessage =
| DocumentStoreEncryptResponse
| DocumentEncryptResponse
| DocumentStoreUpdateDataResponse
| DocumentStreamDecryptResponse
| DocumentUnmanagedStreamDecryptResponse
| DocumentStreamEncryptResponse
| DocumentUnmanagedStreamEncryptResponse
| DocumentUpdateDataResponse
| DocumentUpdateNameResponse
| DocumentGrantResponse
Expand Down
41 changes: 41 additions & 0 deletions src/WorkerMessageTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,43 @@ export interface SearchTransliterateStringResponse {
message: string;
}

export interface StreamDecryptDocumentWorkerRequest {
type: "DOCUMENT_STREAM_DECRYPT";
message: {
encryptedSymmetricKey: TransformedEncryptedMessage;
privateKey: PrivateKey<Uint8Array>;
iv: Uint8Array;
encryptedStream: ReadableStream<Uint8Array>;
plaintextStream: WritableStream<Uint8Array>;
};
}
export interface StreamDecryptDocumentWorkerResponse {
type: "DOCUMENT_STREAM_DECRYPT_RESPONSE";
// There's nothing to send back (all the data already went out in the `plaintextResponse` from the request), but
// this response indicates to the frame that the decrypt work is done. The explicit void is to (try) to communicate
// that we have no data for the frame but we're done.
message: void;
}

export interface StreamEncryptDocumentWorkerRequest {
type: "DOCUMENT_STREAM_ENCRYPT";
message: {
plaintextStream: ReadableStream<Uint8Array>;
ciphertextStream: WritableStream<Uint8Array>;
userKeyList: UserOrGroupPublicKey[];
groupKeyList: UserOrGroupPublicKey[];
signingKeys: SigningKeyPair;
iv: Uint8Array;
};
}
export interface StreamEncryptDocumentWorkerResponse {
type: "DOCUMENT_STREAM_ENCRYPT_RESPONSE";
message: {
userAccessKeys: EncryptedAccessKey[];
groupAccessKeys: EncryptedAccessKey[];
};
}

export interface ErrorResponse {
type: "ERROR_RESPONSE";
message: {
Expand All @@ -304,6 +341,8 @@ export type RequestMessage =
| ReencryptDocumentWorkerRequest
| DecryptDocumentWorkerRequest
| DocumentEncryptToKeysWorkerRequest
| StreamDecryptDocumentWorkerRequest
| StreamEncryptDocumentWorkerRequest
| NewUserKeygenWorkerRequest
| NewUserAndDeviceKeygenWorkerRequest
| DeviceKeygenWorkerRequest
Expand All @@ -325,6 +364,8 @@ export type ResponseMessage =
| ReencryptDocumentWorkerResponse
| DecryptDocumentWorkerResponse
| DocumentEncryptToKeysWorkerResponse
| StreamDecryptDocumentWorkerResponse
| StreamEncryptDocumentWorkerResponse
| NewUserKeygenWorkerResponse
| NewUserAndDeviceKeygenWorkerResponse
| DeviceKeygenWorkerResponse
Expand Down
10 changes: 4 additions & 6 deletions src/frame/FrameMessenger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {RequestMessage, ResponseMessage} from "../FrameMessageTypes";
import {toTransferables} from "../lib/Utils";

type FrameMessageCallback = (message: RequestMessage, callback: (response: ResponseMessage, transferList?: Uint8Array[]) => void) => void;
type FrameMessageCallback = (message: RequestMessage, callback: (response: ResponseMessage, transferList?: (Uint8Array | Transferable)[]) => void) => void;

interface FrameEvent<T> {
replyID: number;
Expand Down Expand Up @@ -35,12 +36,9 @@ export default class FrameMessenger {
*/
processMessageIntoFrame = (event: MessageEvent) => {
const {data, replyID}: FrameEvent<RequestMessage> = event.data;
this.onMessageCallback(data, (responseData: ResponseMessage, transferList: Uint8Array[] = []) => {
this.onMessageCallback(data, (responseData: ResponseMessage, transferList: (Uint8Array | Transferable)[] = []) => {
if (this.messagePort) {
this.messagePort.postMessage(
{replyID, data: responseData},
transferList.map((int8Array) => int8Array.buffer)
);
this.messagePort.postMessage({replyID, data: responseData}, toTransferables(transferList));
}
});
};
Expand Down
Loading