Vault extensions
Extension Type Vault
Vault extensions add a client side encryption scheme to OpenCloud Web. A vault is a folder or a space whose content, and usually also whose resource names, are encrypted in the browser before they reach the server.
The Web runtime stays scheme-agnostic. It only knows that some location is a vault and whether it is unlocked. All cryptography, all key handling and the unlock user interface come from the extension.
This extension type is meant for encryption schemes. If you only want to add functionality to a vault, use one of the other extension types instead. A reference implementation is the rclone-crypt app.
Configuration
To define a vault extension, you implement the VaultExtension interface. Here's what it looks like:
interface VaultExtension {
id: string
type: 'vault'
extensionPointIds?: string[]
claimsPath: (space: SpaceResource, path: string) => VaultClaim | null
resolve: (space: SpaceResource, path: string) => Promise<VaultEngine | null>
creation?: VaultCreation
}
For id, type, and extensionPointIds, please see base configuration section in the extensions docs.
claimsPath- Tells the runtime if your extension is responsible for the given location, no matter if the vault is unlocked. This is a cheap, synchronous check. Returnnullif the location is not one of your vaults.resolve- Returns the engine that encrypts and decrypts for the given location. Returnnullif your extension is not responsible, or if the vault is locked.creation- Optional. Its presence tells the user interface that your extension can create new vaults, so an encryption option is offered when a user creates a folder or a space.
VaultClaim
interface VaultClaim {
vaultRoot: string
encryptsNames: boolean
unlockRoute?: RouteLocationNamedRaw
}
vaultRoot- The clear text root of the vault, for example/my-vault.vault. Use/for a vault space.encryptsNames- Set this totrueif your scheme also encrypts resource names, not just their content.unlockRoute- The route that asks the user to unlock the vault. Your route handler fills the vault store and then redirects back toquery.redirectUrl. Without this route the vault is treated as permanently locked.
VaultEngine
The engine does the actual cryptography. All of its path methods work on paths that are relative to the vault root. A bare resource name is a relative path with one segment, so a name must encrypt independently of its position in the tree.
interface VaultEngine {
vaultRoot: string
encryptPath: (relativePath: string) => Promise<string>
decryptPath: (relativePath: string) => Promise<string>
encryptContent: (plaintext: ReadableStream<Uint8Array>) => ReadableStream<Uint8Array>
decryptContent: (encrypted: ReadableStream<Uint8Array>) => ReadableStream<Uint8Array>
createIntegrityToken: () => Promise<string>
verifyIntegrityToken: (token: string) => Promise<boolean>
verifySegment: (sampleEncryptedSegment: string) => Promise<boolean>
}
The integrity token commits a vault to the key of your engine. It gets written once, when the secret of a vault is first
set, and it is stored as a WebDAV property on the vault root. Its format is up to your engine. verifySegment is the
weaker fallback for vaults that carry no token, for example vaults created outside of OpenCloud Web.
If you have full clear text paths, use the encryptVaultPath and decryptVaultPath helpers from web-pkg instead of
calling the engine directly.
VaultCreation
interface VaultCreation {
vaultExtension: string
vaultContentType: string
setupComponent: Component
}
vaultExtension- The name extension a vault folder carries, without the leading dot, for examplevault.vaultContentType- The content type a vault space carries in its@libre.graph.contentTypedrive property.setupComponent- The component that collects and commits the secret of a new vault. It is rendered as the second step of the create folder or create space flow. It takes avaultNameprop, emitsupdate:valid, and exposes afinalizefunction that gets called once the folder or space exists on the server.
Example
import { markRaw } from 'vue'
import { VaultExtension } from '@opencloud-eu/web-pkg'
import VaultSetup from './components/VaultSetup.vue'
export const vaultSchemeExtension: VaultExtension = {
id: 'app.rclone-crypt.vault',
type: 'vault',
resolve(space, path) {
return Promise.resolve(resolveVault(space, path))
},
claimsPath(space, path) {
return claimsVaultPath(space, path)
},
creation: {
vaultExtension: 'vault',
vaultContentType: 'application/vnd.opencloud.vault',
setupComponent: markRaw(VaultSetup)
}
}