@kojinx/collab-crypto 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/kojinx-collab-crypto.mjs +5 -0
- package/esm2022/lib/models/crypto.models.mjs +2 -0
- package/esm2022/lib/services/e2e-chat-crypto.service.mjs +148 -0
- package/esm2022/lib/services/session-key-manager.service.mjs +267 -0
- package/esm2022/lib/tokens.mjs +36 -0
- package/esm2022/lib/utils/safety-number.utils.mjs +72 -0
- package/esm2022/public-api.mjs +9 -0
- package/fesm2022/kojinx-collab-crypto.mjs +528 -0
- package/fesm2022/kojinx-collab-crypto.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/models/crypto.models.d.ts +99 -0
- package/lib/services/e2e-chat-crypto.service.d.ts +58 -0
- package/lib/services/session-key-manager.service.d.ts +71 -0
- package/lib/tokens.d.ts +24 -0
- package/lib/utils/safety-number.utils.d.ts +30 -0
- package/package.json +26 -0
- package/public-api.d.ts +5 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypted message payload format transmitted over HTTP/SSE and stored in database.
|
|
3
|
+
*/
|
|
4
|
+
export interface EncryptedMessagePayload {
|
|
5
|
+
/** Base64-encoded AES-256-GCM ciphertext */
|
|
6
|
+
ciphertext: string;
|
|
7
|
+
/** Base64-encoded 12-byte (96-bit) Initialization Vector (Nonce) */
|
|
8
|
+
nonce: string;
|
|
9
|
+
/** UUID of the Session Key used to encrypt this message */
|
|
10
|
+
sessionKeyId: string;
|
|
11
|
+
/** Generation number of the Session Key (incremented during key rotation) */
|
|
12
|
+
generation: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* In-memory active Session Key holding standard W3C CryptoKey.
|
|
16
|
+
*/
|
|
17
|
+
export interface ConversationSessionKey {
|
|
18
|
+
/** Unique UUID identifier for this session key */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Conversation scope */
|
|
21
|
+
type: 'dm' | 'channel' | 'group';
|
|
22
|
+
/** Target conversation ID (recipientId for 1:1 DM, channelId for channels, groupId for groups) */
|
|
23
|
+
conversationId: string;
|
|
24
|
+
/** W3C SubtleCrypto AES-GCM 256-bit Key */
|
|
25
|
+
rawKey: CryptoKey;
|
|
26
|
+
/** Generation index (starts at 1, incremented upon rotation) */
|
|
27
|
+
generation: number;
|
|
28
|
+
/** Creation epoch timestamp in milliseconds */
|
|
29
|
+
createdAt: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Wire-format for storing and synchronizing encrypted session keys across members.
|
|
33
|
+
*/
|
|
34
|
+
export interface SessionKeyExchangePayload {
|
|
35
|
+
sessionKeyId: string;
|
|
36
|
+
conversationType: 'dm' | 'channel' | 'group';
|
|
37
|
+
conversationId: string;
|
|
38
|
+
generation: number;
|
|
39
|
+
/** Map of UserId -> RSA-OAEP Base64 Encrypted AES Raw Key */
|
|
40
|
+
memberPayloads: Record<string, string>;
|
|
41
|
+
createdAt?: string;
|
|
42
|
+
createdBy?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Member public key entry returned by public key directory.
|
|
46
|
+
*/
|
|
47
|
+
export interface MemberPublicKey {
|
|
48
|
+
userId: string;
|
|
49
|
+
publicKey: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Safety Number verification model (Signal-style).
|
|
53
|
+
*/
|
|
54
|
+
export interface SafetyNumber {
|
|
55
|
+
/** Raw 60-character hexadecimal digest */
|
|
56
|
+
raw: string;
|
|
57
|
+
/** Standardized formatted display in 6 blocks of 5 decimal digits */
|
|
58
|
+
formatted: string;
|
|
59
|
+
/** Fingerprint component for the local user */
|
|
60
|
+
localFingerprint: string;
|
|
61
|
+
/** Fingerprint component for the remote user */
|
|
62
|
+
remoteFingerprint: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Cryptography provider abstraction interface (RSA-2048 OAEP).
|
|
66
|
+
*/
|
|
67
|
+
export interface KojinxCryptoProvider {
|
|
68
|
+
/** Returns the base64-encoded RSA public key */
|
|
69
|
+
getPublicKey(): Promise<string>;
|
|
70
|
+
/** Asymmetrically encrypts plaintext using target base64 RSA public key */
|
|
71
|
+
encrypt(publicKeyBase64: string, plaintext: string): Promise<string>;
|
|
72
|
+
/** Asymmetrically decrypts ciphertext using local private key */
|
|
73
|
+
decrypt(ciphertextBase64: string): Promise<string>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Client-agnostic fetch options.
|
|
77
|
+
*/
|
|
78
|
+
export interface KojinxFetchOptions {
|
|
79
|
+
method?: string;
|
|
80
|
+
headers?: Record<string, string>;
|
|
81
|
+
body?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Client-agnostic fetch response.
|
|
85
|
+
*/
|
|
86
|
+
export interface KojinxFetchResponse<T = any> {
|
|
87
|
+
status: number;
|
|
88
|
+
data: T;
|
|
89
|
+
headers?: Record<string, string>;
|
|
90
|
+
}
|
|
91
|
+
export type KojinxFetchFunction = <T = any>(url: string, options?: KojinxFetchOptions) => Promise<KojinxFetchResponse<T>>;
|
|
92
|
+
/**
|
|
93
|
+
* Local storage interface for session key caching.
|
|
94
|
+
*/
|
|
95
|
+
export interface KojinxStorageProvider {
|
|
96
|
+
getItem(key: string): Promise<string | null>;
|
|
97
|
+
setItem(key: string, value: string): Promise<void>;
|
|
98
|
+
removeItem(key: string): Promise<void>;
|
|
99
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ConversationSessionKey, EncryptedMessagePayload } from '../models/crypto.models';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Pure End-to-End Encryption cryptographic service.
|
|
5
|
+
* Handles AES-256-GCM message encryption/decryption and RSA key wrapping/unwrapping.
|
|
6
|
+
*/
|
|
7
|
+
export declare class E2EChatCryptoService {
|
|
8
|
+
private cryptoProvider;
|
|
9
|
+
/**
|
|
10
|
+
* Generates a new cryptographically secure 256-bit AES-GCM session key.
|
|
11
|
+
*/
|
|
12
|
+
generateAesKey(): Promise<CryptoKey>;
|
|
13
|
+
/**
|
|
14
|
+
* Encrypts plaintext string using AES-256-GCM with a fresh 12-byte (96-bit) nonce.
|
|
15
|
+
*
|
|
16
|
+
* @param sessionKey In-memory active session key
|
|
17
|
+
* @param plaintext Plaintext message content
|
|
18
|
+
*/
|
|
19
|
+
encryptMessage(sessionKey: ConversationSessionKey, plaintext: string): Promise<EncryptedMessagePayload>;
|
|
20
|
+
/**
|
|
21
|
+
* Decrypts an EncryptedMessagePayload using AES-256-GCM.
|
|
22
|
+
*
|
|
23
|
+
* @param sessionKey In-memory session key corresponding to payload.sessionKeyId
|
|
24
|
+
* @param payload Encrypted payload containing ciphertext and nonce
|
|
25
|
+
*/
|
|
26
|
+
decryptMessage(sessionKey: ConversationSessionKey, payload: EncryptedMessagePayload): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Exports an AES-GCM key and wraps (encrypts) it with a recipient's RSA public key.
|
|
29
|
+
*
|
|
30
|
+
* @param aesKey AES CryptoKey to wrap
|
|
31
|
+
* @param recipientPublicKey Base64-encoded recipient RSA public key
|
|
32
|
+
*/
|
|
33
|
+
wrapSessionKey(aesKey: CryptoKey, recipientPublicKey: string): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Unwraps (decrypts) an RSA-encrypted AES raw key and imports it back into a W3C CryptoKey.
|
|
36
|
+
*
|
|
37
|
+
* @param encryptedAesKeyBase64 RSA-encrypted AES raw key in base64 format
|
|
38
|
+
*/
|
|
39
|
+
unwrapSessionKey(encryptedAesKeyBase64: string): Promise<CryptoKey>;
|
|
40
|
+
/**
|
|
41
|
+
* Exports a CryptoKey to base64 raw bytes.
|
|
42
|
+
*/
|
|
43
|
+
exportRawKeyBase64(key: CryptoKey): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Imports a base64 raw key string into a CryptoKey.
|
|
46
|
+
*/
|
|
47
|
+
importRawKeyBase64(base64: string): Promise<CryptoKey>;
|
|
48
|
+
/**
|
|
49
|
+
* Encodes ArrayBuffer to Base64 string.
|
|
50
|
+
*/
|
|
51
|
+
arrayBufferToBase64(buffer: ArrayBuffer | Uint8Array): string;
|
|
52
|
+
/**
|
|
53
|
+
* Decodes Base64 string to ArrayBuffer.
|
|
54
|
+
*/
|
|
55
|
+
base64ToArrayBuffer(base64: string): ArrayBuffer;
|
|
56
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<E2EChatCryptoService, never>;
|
|
57
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<E2EChatCryptoService>;
|
|
58
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ConversationSessionKey, MemberPublicKey } from '../models/crypto.models';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Manages the lifecycle, caching, negotiation, and rotation of conversation session keys.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SessionKeyManagerService {
|
|
7
|
+
private cryptoService;
|
|
8
|
+
private httpFetch;
|
|
9
|
+
private getApiUrl;
|
|
10
|
+
private getAuthToken;
|
|
11
|
+
private storage;
|
|
12
|
+
/** Fast in-memory cache mapped by conversationId */
|
|
13
|
+
private activeKeysByConversation;
|
|
14
|
+
/** Fast in-memory cache mapped by sessionKeyId (for historic messages) */
|
|
15
|
+
private keysById;
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves or creates an active session key for a 1:1 Direct Message conversation.
|
|
18
|
+
*
|
|
19
|
+
* @param localUserId Current user's ID
|
|
20
|
+
* @param remoteUserId Remote contact's ID
|
|
21
|
+
* @param remotePublicKey Base64 RSA public key of remote contact
|
|
22
|
+
* @param localPublicKey Base64 RSA public key of local user
|
|
23
|
+
*/
|
|
24
|
+
getOrCreateDmSessionKey(localUserId: string, remoteUserId: string, remotePublicKey: string, localPublicKey: string): Promise<ConversationSessionKey>;
|
|
25
|
+
/**
|
|
26
|
+
* Retrieves or creates an active session key for a Native Channel or Group.
|
|
27
|
+
*
|
|
28
|
+
* @param channelId Unique channel ID
|
|
29
|
+
* @param members List of all workspace/channel members with their public keys
|
|
30
|
+
*/
|
|
31
|
+
getOrCreateChannelSessionKey(channelId: string, members: MemberPublicKey[]): Promise<ConversationSessionKey>;
|
|
32
|
+
/**
|
|
33
|
+
* Retrieves a session key by its specific sessionKeyId (used for decrypting historic messages).
|
|
34
|
+
*/
|
|
35
|
+
getSessionKeyById(sessionKeyId: string): Promise<ConversationSessionKey | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Generates a new AES session key, wraps it for all members, and uploads to the server.
|
|
38
|
+
*/
|
|
39
|
+
createAndDistributeSessionKey(params: {
|
|
40
|
+
type: 'dm' | 'channel' | 'group';
|
|
41
|
+
conversationId: string;
|
|
42
|
+
generation: number;
|
|
43
|
+
members: MemberPublicKey[];
|
|
44
|
+
}): Promise<ConversationSessionKey>;
|
|
45
|
+
/**
|
|
46
|
+
* Helper to check if a key has exceeded the 30-day rotation threshold.
|
|
47
|
+
*/
|
|
48
|
+
isKeyExpired(key: ConversationSessionKey): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Builds a deterministic conversation identifier for 1:1 DMs (lexicographical sort).
|
|
51
|
+
*/
|
|
52
|
+
buildDmConversationId(userIdA: string, userIdB: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* Caches a session key in memory.
|
|
55
|
+
*/
|
|
56
|
+
private cacheKey;
|
|
57
|
+
/**
|
|
58
|
+
* Persists an unwrapped session key to local storage for fast startup.
|
|
59
|
+
*/
|
|
60
|
+
private persistKeyLocally;
|
|
61
|
+
/**
|
|
62
|
+
* Fetches latest session key from server and unwraps it locally.
|
|
63
|
+
*/
|
|
64
|
+
private fetchSessionKeyFromServer;
|
|
65
|
+
/**
|
|
66
|
+
* Generates a random UUID (W3C standard or fallback).
|
|
67
|
+
*/
|
|
68
|
+
private generateUuid;
|
|
69
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SessionKeyManagerService, never>;
|
|
70
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<SessionKeyManagerService>;
|
|
71
|
+
}
|
package/lib/tokens.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
import { KojinxCryptoProvider, KojinxFetchFunction, KojinxStorageProvider } from './models/crypto.models';
|
|
3
|
+
/**
|
|
4
|
+
* Injected Asymmetric Cryptography Provider (RSA-2048 OAEP).
|
|
5
|
+
* On desktop: Implemented via Tauri Rust Keyring IPC.
|
|
6
|
+
* On mobile: Implemented via SubtleCrypto + Capacitor SecureStorage.
|
|
7
|
+
*/
|
|
8
|
+
export declare const KOJINX_CRYPTO_PROVIDER: InjectionToken<KojinxCryptoProvider>;
|
|
9
|
+
/**
|
|
10
|
+
* Injected Client-Agnostic HTTP Fetcher (bypass CORS on native).
|
|
11
|
+
*/
|
|
12
|
+
export declare const KOJINX_HTTP_FETCH: InjectionToken<KojinxFetchFunction>;
|
|
13
|
+
/**
|
|
14
|
+
* Injected Storage Provider for local session key caching.
|
|
15
|
+
*/
|
|
16
|
+
export declare const KOJINX_CRYPTO_STORAGE: InjectionToken<KojinxStorageProvider>;
|
|
17
|
+
/**
|
|
18
|
+
* Injected API Base URL accessor function.
|
|
19
|
+
*/
|
|
20
|
+
export declare const KOJINX_API_BASE_URL: InjectionToken<() => string>;
|
|
21
|
+
/**
|
|
22
|
+
* Injected Auth Bearer Token accessor function.
|
|
23
|
+
*/
|
|
24
|
+
export declare const KOJINX_AUTH_TOKEN: InjectionToken<() => Promise<string | null>>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SafetyNumber } from '../models/crypto.models';
|
|
2
|
+
/**
|
|
3
|
+
* Computes deterministic SHA-256 safety verification numbers (Signal-style).
|
|
4
|
+
* Users can compare these 30-digit numbers out-of-band to verify end-to-end identity.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SafetyNumberUtils {
|
|
7
|
+
/**
|
|
8
|
+
* Generates a Safety Number between two identity public keys.
|
|
9
|
+
*
|
|
10
|
+
* @param localPublicKey Base64-encoded local RSA public key
|
|
11
|
+
* @param remotePublicKey Base64-encoded remote RSA public key
|
|
12
|
+
*/
|
|
13
|
+
static generate(localPublicKey: string, remotePublicKey: string): Promise<SafetyNumber>;
|
|
14
|
+
/**
|
|
15
|
+
* Hashes a single public key using SHA-256.
|
|
16
|
+
*/
|
|
17
|
+
static hashPublicKey(publicKeyBase64: string): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Computes SHA-256 hex string using W3C Web Crypto API.
|
|
20
|
+
*/
|
|
21
|
+
private static sha256;
|
|
22
|
+
/**
|
|
23
|
+
* Converts a hexadecimal string into a deterministic 30-digit decimal string.
|
|
24
|
+
*/
|
|
25
|
+
private static hexTo30Digits;
|
|
26
|
+
/**
|
|
27
|
+
* Formats a 30-digit string into 6 blocks of 5 digits separated by spaces.
|
|
28
|
+
*/
|
|
29
|
+
private static formatBlocks;
|
|
30
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kojinx/collab-crypto",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Kojinx End-to-End Encryption Trust Module for Collaboration Hub",
|
|
5
|
+
"peerDependencies": {
|
|
6
|
+
"@angular/common": "^17.3.0",
|
|
7
|
+
"@angular/core": "^17.3.0"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"tslib": "^2.3.0"
|
|
11
|
+
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"module": "fesm2022/kojinx-collab-crypto.mjs",
|
|
14
|
+
"typings": "index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./package.json": {
|
|
17
|
+
"default": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./index.d.ts",
|
|
21
|
+
"esm2022": "./esm2022/kojinx-collab-crypto.mjs",
|
|
22
|
+
"esm": "./esm2022/kojinx-collab-crypto.mjs",
|
|
23
|
+
"default": "./fesm2022/kojinx-collab-crypto.mjs"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/public-api.d.ts
ADDED