Green: unseal shared collection keys with the account keypair
decryptCollection now takes the full key material {masterKey,
publicKey, secretKey} and dispatches on keyDecryptionNonce: present
means an owned collection (secretbox under the master key), absent
means a shared collection (sealed box to our public key). Client
already held the keypair for unsealing the auth token, so it just
passes it through.
This commit is contained in:
@@ -156,7 +156,15 @@ export class Client {
|
|||||||
collections: RawCollection[];
|
collections: RawCollection[];
|
||||||
}>("/collections/v2", { sinceTime: 0 });
|
}>("/collections/v2", { sinceTime: 0 });
|
||||||
return collections.map((raw) =>
|
return collections.map((raw) =>
|
||||||
decryptCollection(raw, this.masterKey, this.userID),
|
decryptCollection(
|
||||||
|
raw,
|
||||||
|
{
|
||||||
|
masterKey: this.masterKey,
|
||||||
|
publicKey: this.publicKey,
|
||||||
|
secretKey: this.secretKey,
|
||||||
|
},
|
||||||
|
this.userID,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export type {
|
|||||||
FileBlob,
|
FileBlob,
|
||||||
FileMetadata,
|
FileMetadata,
|
||||||
FileType,
|
FileType,
|
||||||
|
KeyMaterial,
|
||||||
Microseconds,
|
Microseconds,
|
||||||
RawCollection,
|
RawCollection,
|
||||||
RawEnteFile,
|
RawEnteFile,
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import { decryptBlob, decryptBox, fromBase64 } from "../crypto/index.js";
|
import {
|
||||||
|
decryptBlob,
|
||||||
|
decryptBox,
|
||||||
|
decryptSealed,
|
||||||
|
fromBase64,
|
||||||
|
} from "../crypto/index.js";
|
||||||
import type {
|
import type {
|
||||||
Collection,
|
Collection,
|
||||||
CollectionType,
|
CollectionType,
|
||||||
EnteFile,
|
EnteFile,
|
||||||
FileMetadata,
|
FileMetadata,
|
||||||
FileType,
|
FileType,
|
||||||
|
KeyMaterial,
|
||||||
RawCollection,
|
RawCollection,
|
||||||
RawEnteFile,
|
RawEnteFile,
|
||||||
RawMagicMetadata,
|
RawMagicMetadata,
|
||||||
@@ -30,14 +36,25 @@ const parseFileType = (n: number): FileType => FILE_TYPE_MAP[n] ?? "unknown";
|
|||||||
|
|
||||||
export const decryptCollection = (
|
export const decryptCollection = (
|
||||||
raw: RawCollection,
|
raw: RawCollection,
|
||||||
masterKey: Uint8Array,
|
keys: KeyMaterial,
|
||||||
currentUserID?: number,
|
currentUserID?: number,
|
||||||
): Collection => {
|
): Collection => {
|
||||||
const key = decryptBox(
|
// Owned collections carry their key as a secretbox under our master
|
||||||
fromBase64(raw.encryptedKey),
|
// key, with the nonce in keyDecryptionNonce. Collections shared with
|
||||||
fromBase64(raw.keyDecryptionNonce),
|
// us carry it as an anonymous sealed box to our public key and have
|
||||||
masterKey,
|
// no keyDecryptionNonce at all (sealed boxes embed an ephemeral
|
||||||
);
|
// public key instead).
|
||||||
|
const key = raw.keyDecryptionNonce
|
||||||
|
? decryptBox(
|
||||||
|
fromBase64(raw.encryptedKey),
|
||||||
|
fromBase64(raw.keyDecryptionNonce),
|
||||||
|
keys.masterKey,
|
||||||
|
)
|
||||||
|
: decryptSealed(
|
||||||
|
fromBase64(raw.encryptedKey),
|
||||||
|
keys.publicKey,
|
||||||
|
keys.secretKey,
|
||||||
|
);
|
||||||
|
|
||||||
let name = "";
|
let name = "";
|
||||||
if (raw.encryptedName && raw.nameDecryptionNonce) {
|
if (raw.encryptedName && raw.nameDecryptionNonce) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export type {
|
|||||||
FileBlob,
|
FileBlob,
|
||||||
FileMetadata,
|
FileMetadata,
|
||||||
FileType,
|
FileType,
|
||||||
|
KeyMaterial,
|
||||||
Microseconds,
|
Microseconds,
|
||||||
RawCollection,
|
RawCollection,
|
||||||
RawEnteFile,
|
RawEnteFile,
|
||||||
|
|||||||
@@ -50,13 +50,25 @@ export interface EnteFile {
|
|||||||
updationTime: Microseconds;
|
updationTime: Microseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The key material a logged-in client holds, everything needed to decrypt
|
||||||
|
// any collection: the master key (secretbox for owned collection keys) and
|
||||||
|
// the X25519 keypair (sealed box for collection keys shared with us).
|
||||||
|
export interface KeyMaterial {
|
||||||
|
masterKey: Uint8Array;
|
||||||
|
publicKey: Uint8Array;
|
||||||
|
secretKey: Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
// Raw shapes as they arrive from the Ente API, before decryption.
|
// Raw shapes as they arrive from the Ente API, before decryption.
|
||||||
|
|
||||||
export interface RawCollection {
|
export interface RawCollection {
|
||||||
id: number;
|
id: number;
|
||||||
owner: { id: number };
|
owner: { id: number };
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
keyDecryptionNonce: string;
|
// Absent for collections shared with us: their encryptedKey is a
|
||||||
|
// sealed box to our public key, which embeds an ephemeral public key
|
||||||
|
// instead of using a nonce.
|
||||||
|
keyDecryptionNonce?: string;
|
||||||
encryptedName?: string;
|
encryptedName?: string;
|
||||||
nameDecryptionNonce?: string;
|
nameDecryptionNonce?: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ const main = async () => {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { masterKey, token } = await unwrapAuth(challenge.response, PASSWORD);
|
const { masterKey, secretKey, publicKey, token } = await unwrapAuth(
|
||||||
|
challenge.response,
|
||||||
|
PASSWORD,
|
||||||
|
);
|
||||||
api.setAuthToken(token);
|
api.setAuthToken(token);
|
||||||
console.log("Logged in, user ID:", challenge.response.id);
|
console.log("Logged in, user ID:", challenge.response.id);
|
||||||
|
|
||||||
@@ -37,7 +40,7 @@ const main = async () => {
|
|||||||
|
|
||||||
const userID = challenge.response.id;
|
const userID = challenge.response.id;
|
||||||
const collections = rawCollections.map((raw) =>
|
const collections = rawCollections.map((raw) =>
|
||||||
decryptCollection(raw, masterKey, userID),
|
decryptCollection(raw, { masterKey, publicKey, secretKey }, userID),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`${collections.length} collection(s):`);
|
console.log(`${collections.length} collection(s):`);
|
||||||
|
|||||||
Reference in New Issue
Block a user