Skip to main content

Doriath — Architecture & Data Model

1. Overview

Doriath is an encrypted secrets manager for Nextcloud — a password manager and key store for users and applications. It stores secrets (passwords, API keys, tokens, certificates) encrypted at rest using RSA-4096 public-key cryptography, with private keys protected by AES-256 encryption derived from a user's master password. A private Certificate Authority (root + intermediate) signs all user and application certificates.

Architecture Pattern

┌─────────────────────────────────────────────────────────────┐
│ Doriath Frontend (Vue 2 + Pinia) │
│ - Lock screen (master password entry) │
│ - Vault: secrets list with folder tree │
│ - Secret detail views (by type) │
│ - Sharing UI (user, link, request) │
│ - Key generator │
│ - Dashboard (KPI cards, vault health) │
│ - Admin settings (CA health, app management) │
│ - User settings (session timeout, notifications) │
└──────────────┬──────────────────────────────────────────────┘
│ REST API calls (Nextcloud session + X-Vault-Password)
┌──────────────▼──────────────────────────────────────────────┐
│ Doriath PHP Backend │
│ - Controllers: Secrets, EncryptionSuites, Applications, │
│ Sharing, KeyGenerator, Settings │
│ - Services: EncryptionService, CertificateAuthorityService,│
│ SecretService, SharingService, MigrationService │
│ - Entities: Doctrine ORM with Nextcloud migrations │
│ - Encryption: OpenSSL (RSA-4096 + AES-256) │
└──────────────┬──────────────────────────────────────────────┘
│ Doctrine ORM
┌──────────────▼──────────────────────────────────────────────┐
│ PostgreSQL Database │
│ - Own tables (ISchemaWrapper migrations) │
│ - Encrypted fields: private keys (AES), secret values (RSA)│
│ - Unencrypted fields: names, URLs, metadata │
└─────────────────────────────────────────────────────────────┘

Doriath owns all its database tables — it does NOT use OpenRegister. This is an explicit exception to the org-wide ADR-001, justified by the security requirements of field-level encryption control and the need to eliminate intermediary services from the data path. See ADR-001.

2. Standards Research

Before defining our encryption architecture and data model, we evaluated multiple standards across cryptography, key management, and password security.

2.1 Standards Evaluated

StandardTypeCoverageMaturityRelevance
X.509 / RFC 5280International (IETF)Certificate format, CA chains, certificate revocation, extensionsVery mature (since 1988)HIGH — defines certificate structure for our private CA
PKCS#1 / RFC 8017International (IETF)RSA encryption and signature schemes, OAEP paddingVery matureHIGH — RSA encryption of secrets
PKCS#8 / RFC 5958International (IETF)Private key storage format, encrypted private key wrappingVery matureHIGH — AES-encrypted private key storage
PKCS#10 / RFC 2986International (IETF)Certificate Signing Request (CSR) formatVery matureHIGH — application registration via CSR
NIST SP 800-57US GovernmentKey management lifecycle, key sizes, algorithm selectionCurrent (Rev. 5, 2020)HIGH — RSA-4096 and AES-256 recommendations
NIST SP 800-63BUS GovernmentAuthenticator assurance levels, memorized secret requirementsCurrent (Rev. 4 draft)HIGH — master password strength policy
OWASP Password StorageIndustry (OWASP)Password hashing, KDF selection (Argon2id, bcrypt, PBKDF2)Active, regularly updatedHIGH — Argon2id for link share KDF
OWASP Cryptographic StorageIndustry (OWASP)Encryption at rest, key management, algorithm selectionActiveHIGH — encryption architecture validation
zxcvbnIndustry (Dropbox)Password strength estimation, entropy-based scoring (0–4)Mature, widely adoptedHIGH — master password strength meter
Argon2 / RFC 9106International (IETF)Memory-hard KDF: Argon2d, Argon2i, Argon2idStandardized 2021HIGH — link share snapshot encryption
WebAuthn / FIDO2International (W3C)Passwordless authentication, hardware key supportMature, growing adoptionLOW — future: passwordless vault unlock
Schema.orgInternational (W3C)Structured data vocabularyVery matureLOW — no standard types for secrets/credentials

2.2 Design Principle: Security First, Standards-Based

All cryptographic operations use established standards via OpenSSL. No custom cryptography. The master password never touches the database.

This means:

  • RSA encryption follows PKCS#1 v1.5 / OAEP padding via OpenSSL
  • Private keys are stored in PKCS#8 PEM format, AES-256 encrypted
  • Certificates follow X.509 v3 format, signed by the private CA intermediate
  • CSR processing follows PKCS#10 for application registration
  • Key derivation for link shares uses Argon2id (memory-hard, RFC 9106)
  • Master password strength is validated using zxcvbn entropy scoring aligned with NIST SP 800-63B

2.3 Key Findings

  1. X.509 / RFC 5280 defines the certificate format Doriath uses for its private CA. The root certificate (20-year lifetime) signs an intermediate certificate (3-year lifetime), which in turn signs user and application certificates. This two-tier CA structure follows PKI best practice — the root key is used rarely, limiting exposure.

  2. NIST SP 800-57 recommends RSA-4096 for protection beyond 2031 (>128-bit security strength). AES-256 provides 256-bit security strength. Both exceed NIST's minimum recommendations for sensitive data protection. Key sizes in Doriath are only allowed to increase, never decrease.

  3. NIST SP 800-63B defines memorized secret (password) requirements: minimum 8 characters (we require 12), no composition rules (we use entropy-based scoring instead), and blocklist checking. Our zxcvbn-based approach aligns with NIST's shift from complexity rules to entropy measurement.

  4. OWASP Password Storage Cheat Sheet recommends Argon2id as the first choice for password hashing / key derivation. We use Argon2id for link share snapshot encryption (deriving an AES key from the link password). For master password → AES key derivation, we use a similar KDF approach.

  5. PKCS#10 (CSR) enables applications to register with Doriath by submitting a Certificate Signing Request containing their public key. Doriath signs the public key with the CA intermediate and never stores the application's private key — the application manages its own key externally. This is standard PKI practice for distributed systems.

  6. RSA chunking constraint (PKCS#1): RSA-4096 can encrypt at most ~500 bytes per operation (key size in bytes − padding overhead). Secrets larger than this limit must be chunked. This is a known limitation documented in ADR-003.

  7. zxcvbn scores passwords on a 0–4 scale: 0 = too guessable, 1 = very guessable, 2 = somewhat guessable, 3 = safely unguessable (resists online attacks, ~10^8 guesses), 4 = very unguessable (resists offline attacks, ~10^10 guesses). Doriath requires score ≥ 3 by default, configurable up to 4 by admins.

  8. No applicable Schema.org types — Schema.org has no standard vocabulary for secrets, credentials, or vault entries. Doriath entities do not carry schema.org type annotations, unlike other Conduction apps that model semantic data.

3. Data Model Decisions

3.1 Standards Hierarchy

LayerStandardPurpose
CryptographyRSA (PKCS#1), AES-256, OpenSSLEncryption of secrets and private keys
Certificate formatX.509 v3 (RFC 5280), PKCS#8, PKCS#10CA certificates, key storage, CSR processing
Key derivationArgon2id (RFC 9106)Link share password → AES key derivation
Password policyNIST SP 800-63B + zxcvbnMaster password strength enforcement
Key managementNIST SP 800-57Key size selection, lifecycle management
Nextcloud nativeOCP interfacesUsers, sessions, notifications, search, files

3.2 Entity Definitions

EncryptionSuite

The cryptographic identity of a user or application. Holds a public certificate (for encrypting secrets) and an AES-encrypted private key (for decrypting them). See encryption-suites spec for full requirements.

AspectDecisionRationale
StandardX.509 v3 certificate + PKCS#8 private keyIndustry-standard PKI formats
OwnershipPolymorphic: owner_type (user | application) + owner_idSingle table, single query path — see ADR-002
Key sizeRSA-4096 minimum (only allowed to increase)NIST SP 800-57 recommendation for long-term protection
Private key protectionAES-256 encrypted with master password derived keyMaster password never stored — see ADR-003
Certificate DNDefault DN fields + owner-specific CNSee certificate DN section below

Core properties:

PropertyTypeEncryptedStandardNotes
idUUIDNoPrimary key
owner_typeenum: user, applicationNoADR-002Polymorphic ownership
owner_idstringNoNextcloud user ID or Application ID
certificatetext (PEM)NoX.509 v3Public certificate signed by CA intermediate
private_keytext (PEM)Yes (AES-256)PKCS#8Encrypted with master password derived key
statusenum: active, revoked, compromisedNoX.509 CRL conceptLifecycle state
revoked_atdatetimeNoAudit: when revoked
revoked_reasonstringNoX.509 CRL reasonAudit: why revoked
revoked_bystringNoAudit: who revoked
reinstated_atdatetimeNoAudit: when reinstated
reinstated_bystringNoAudit: who reinstated
created_atdatetimeNo

Certificate Distinguished Name (DN)

All certificates issued by the Doriath CA share a common set of DN fields, with the commonName varying by certificate type:

Default DN fields (defined as DEFAULT_DN in CertificateAuthorityService):

FieldValue
countryNameNL
stateOrProvinceNameNoord-Holland
localityNameAmsterdam
organizationNameConduction
organizationalUnitNameDoriath

Common Name by certificate type:

CertificateCommon NameExample
Root CADoriath Root CADoriath Root CA
Intermediate CADoriath Intermediate CADoriath Intermediate CA
User certificateFederated cloud ID (user@instance), falls back to user IDadmin@nextcloud.local
Application certificateApplication IDa1b2c3d4-...

The cloud ID is resolved via IUserManager::get($ownerId)->getCloudId() in EncryptionSuiteService::resolveCommonName(). This ensures user certificates contain the full federated identity. When a certificate is re-signed during CA renewal, the original CN is preserved by extracting it from the existing certificate via openssl_x509_parse().

CACertificate

The private Certificate Authority that signs all user and application certificates.

AspectDecisionRationale
StructureTwo-tier: root (20yr) → intermediate (3yr)PKI best practice — root key used rarely
StandardX.509 v3 certificatesIndustry standard
RenewalIntermediate auto-renews; root requires manual admin actionMinimize disruption; root rotation is rare

Core properties:

PropertyTypeEncryptedStandardNotes
idUUIDNoPrimary key
typeenum: root, intermediateNoPKI conventionCertificate tier
certificatetext (PEM)NoX.509 v3Public certificate
private_keytext (PEM)Yes (AES-256)PKCS#8Only present for intermediate
is_activeboolNoOnly one intermediate active for signing
expires_atdatetimeNoX.509 validityFor efficient expiry queries
revoked_atdatetimeNoX.509 CRLSet on forced revocation
successor_idFKNoPoints to replacement certificate
created_atdatetimeNo

Secret

The core data entity — holds sensitive information encrypted with the owner's public certificate.

AspectDecisionRationale
EncryptionRSA-4096 (PKCS#1) per owner's public certificateAsymmetric: enables write-without-read
Searchable fieldsname and url stored unencryptedEnables search and Nextcloud unified search without master password
Folder organizationTree via parent_id on Folder entityUser-managed hierarchy, no stored path strings

Core properties:

PropertyTypeEncryptedStandardNotes
idUUIDNoPrimary key
namestringNoHuman-readable label — safe for lists/search
urlstringNoTarget URL — enables search without decryption
type_idFKNoSecretType reference; defaults to login
folder_idFKNoFolder reference; null = root level
keytextYes (RSA)PKCS#1The actual secret value
loginstringYes (RSA)Username, client ID, or equivalent
additional_fieldstextYes (RSA)JSON blob of extra key-value pairs
encryption_suite_idFKNoWhich suite encrypted this secret
owner_typeenum: user, applicationNoADR-002Polymorphic ownership
owner_idstringNoNextcloud user ID or Application ID
possibly_compromised_atdatetimeNoSet during compromise recovery
migration_errortextNoSet on re-encryption failure
created_atdatetimeNo
updated_atdatetimeNo

SecretType

Defines the type of a secret — a UI hint that drives how the interface labels and presents fields.

PropertyTypeNotes
idUUIDPrimary key
namestringSlug identifier (e.g., api_key) — unique
labelstringHuman-readable (e.g., "API Key")
scopeenum: system, user, globalsystem = built-in; user = per-user; global = admin-created
owner_idstringNextcloud user ID for user scope; null for system/global
created_atdatetime

System types (seeded on install, immutable): login, api_key, ssh_key, certificate, note, database

Folder

Organizes secrets into a per-user tree hierarchy.

PropertyTypeNotes
idUUIDPrimary key
namestringSingle path segment, no slashes
parent_idFK (Folder)Null = root level
owner_typeenum: user, applicationPolymorphic ownership
owner_idstringNextcloud user ID or Application ID
created_atdatetime
updated_atdatetime

Application

An external or internal application registered to use Doriath as a secret store.

PropertyTypeNotes
idUUIDPrimary key
namestringHuman-readable name
typeenum: internal, externalInformational; no functional difference currently
statusenum: pending, activeNon-admin registrations start as pending
registered_bystringNextcloud user ID or null (anonymous)
approved_bystringAdmin user ID; null if pending
created_atdatetime
approved_atdatetime

SecretShare (User-to-User)

Links an original secret to an encrypted copy in a recipient's vault.

PropertyTypeNotes
idUUIDPrimary key
source_secret_idFK (Secret)The original secret
target_user_idstringRecipient's Nextcloud user ID
secret_idFK (Secret)The encrypted copy in recipient's vault
group_share_idFK (GroupShare)Nullable — set if created from a group share
created_atdatetime

GroupShare

Tracks that a secret has been shared with a Nextcloud user group.

PropertyTypeNotes
idUUIDPrimary key
secret_idFK (Secret)The secret shared with the group
group_idstringNextcloud group ID
created_bystringOwner who created the share
created_atdatetime

SecretDelegation

Tracks ownership delegation of a secret.

PropertyTypeNotes
idUUIDPrimary key
secret_idFK (Secret)The delegated secret
original_owner_idstringOriginal owner's user ID
delegated_tostringDelegate's user ID
delegated_atdatetime
initiated_bystringWho created the delegation
is_permanentboolFalse = temporary; true = permanent (owner's suite revoked)
made_permanent_atdatetimeNull while temporary

LinkShare

A password-protected link for sharing a secret snapshot with external parties.

PropertyTypeEncryptedNotes
idUUIDNoPrimary key
secret_idFKNoThe secret being shared
tokenstringNoURL-safe random token (≥128 bits entropy)
encrypted_secret_snapshottextYes (AES-256 via Argon2id KDF)Point-in-time snapshot
encryption_suite_idFKNoSuite used to encrypt the snapshot
usage_limitintNoMax accesses; null = unlimited
usage_countintNoCurrent access count
created_atdatetimeNo
expires_atdatetimeNoOptional expiry

Security: Link password is never stored. Snapshot is encrypted with AES-256 using a key derived from the link password via Argon2id (RFC 9106). After 5 consecutive failed attempts, the link share is permanently deleted.

SecretRequest

A fill-in link for write-without-read secret submission.

PropertyTypeNotes
idUUIDPrimary key
secret_idFK (Secret)The unfilled Secret this request writes to
encryption_suite_idFKPublic certificate used to encrypt submitted values
tokenstringURL-safe random token (≥128 bits entropy)
requested_fieldsJSONArray of field names being requested
statusenum: pending, locked, fulfilledlocked during compromise recovery
expires_atdatetimeOptional expiry
created_atdatetime
fulfilled_atdatetime

SuiteMigration

Tracks compromise recovery migrations.

PropertyTypeNotes
idUUIDPrimary key
old_suite_idFK (EncryptionSuite)Compromised suite
new_suite_idFK (EncryptionSuite)Replacement suite
statusenum: in_progress, completed, completed_with_errorsMigration state
started_atdatetime
completed_atdatetimeNull while in progress

3.3 Encryption Flow Summary

Master Password (user input, never stored)


┌─────────────────┐
│ KDF (derive) │
└────────┬────────┘


AES-Derived Key (stored in session only)

┌─────────┴─────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Decrypt │ │ Encrypt │
│ Private Key │ │ Private Key │
│ (AES-256) │ │ (AES-256) │
└───────┬───────┘ └───────────────┘


Private Key (in memory only)

┌─────────┴─────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Decrypt │ │ Encrypt │
│ Secrets │ │ Secrets │
│ (RSA-4096) │ │ (RSA-4096) │
└───────────────┘ └───────────────┘

Write-without-read property: Because encryption uses the owner's public certificate (available to anyone), any party can write a secret into another user's or application's vault. Only the private key holder can decrypt — and the private key requires the master password. This is a direct consequence of asymmetric cryptography and is fundamental to secret requests and application secret management.

3.4 Nextcloud Integration Strategy

Principle: Doriath handles all encryption and secret storage itself. Nextcloud provides identity, session, notification, and search infrastructure.

REUSE from Nextcloud

FeatureOCP InterfaceWhat to ReuseHow
UsersOCP\IUserManagerAuthentication identity, vault ownershipReference by Nextcloud user UID
GroupsOCP\IGroupManagerGroup sharing, vault_admin roleQuery group membership for group shares
SessionOCP\ISessionStore AES-derived key during vault sessionISession::set('doriath_aes_key', $derivedKey)
NotificationsOCP\Notification\IManagerShare received, request fulfilled, CA expiry, app approvalImplement INotifier for rendering
SearchOCP\Search\IProviderUnified search (Ctrl+F) for secrets by name/URLQuery name + url without AES key; deep-link to secret
SettingsOCP\Settings\ISettingsAdmin settings panel (CA health, password policy)Register admin section
App ConfigOCP\IAppConfigApp-wide settings (min password length, session timeout)Store admin-configurable values
User ConfigOCP\IConfigPer-user settings (session timeout preference, notification toggles)Store user preferences
User DeletionOCP\User\Events\UserDeletedEventClean up EncryptionSuite, secrets, shares on user deleteEvent listener
Group MembershipOCP\Group\Events\UserAddedEvent / UserRemovedEventTrigger group share add/revokeEvent listeners

BUILD in Doriath (Security-specific)

WhatWhy Not Reuse
EncryptionSuites & CACore security model — no Nextcloud equivalent
Secret storage (encrypted)Field-level encryption with per-user keys — cannot use generic storage
Master password sessionCustom session management with configurable timeout and tab-close detection
Key generatorServer-side cryptographic randomness with configurable rules
Sharing (user/link/request)Encryption-aware sharing — each share is a re-encrypted copy
Suite migrationCompromise recovery with re-encryption — domain-specific
Application managementCSR processing, approval queue — domain-specific PKI

Key OCP Interface Examples

// Session — store AES-derived key
$session = \OCP\Server::get(\OCP\ISession::class);
$session->set('doriath_aes_key', $aesKey);
$session->get('doriath_aes_key'); // retrieve for decryption

// Notifications — secret shared
$manager = \OCP\Server::get(\OCP\Notification\IManager::class);
$notification = $manager->createNotification();
$notification->setApp('doriath')
->setUser($recipientId)
->setSubject('secret_shared', ['sharer' => $sharerId])
->setObject('secret', $secretId);
$manager->notify($notification);

// Search — register unified search provider
class SecretSearchProvider implements \OCP\Search\IProvider {
public function search(\OCP\IUser $user, \OCP\Search\ISearchQuery $query): SearchResult {
// Query name + url columns directly — no AES key needed
$secrets = $this->secretMapper->searchByNameOrUrl($user->getUID(), $query->getTerm());
// Return results with deep-link URLs (via lock screen if session inactive)
}
}

// User deletion cleanup
class UserDeletedListener implements \OCP\EventDispatcher\IEventListener {
public function handle(\OCP\EventDispatcher\Event $event): void {
// Delete user's EncryptionSuite, secrets, shares, folders
}
}

// Group membership — auto-revoke group shares
class UserRemovedFromGroupListener implements \OCP\EventDispatcher\IEventListener {
public function handle(\OCP\EventDispatcher\Event $event): void {
// Delete all SecretShares where group_share_id references a GroupShare for this group
}
}

3.5 @conduction/nextcloud-vue Library

Doriath uses its own database (not OpenRegister), so store-level components (useObjectStore, plugins) do not apply. However, the following UI components and patterns from @conduction/nextcloud-vue are used:

LayerWhat to UsePurpose
ComponentsCnDataTable, CnFilterBar, CnStatusBadge, CnEmptyState, CnPaginationConsistent list views for secrets, applications, shares
SettingsCnSettingsSection, CnVersionInfoCard, CnSettingsCardAdmin settings page (CA health, password policy, app management)
Detail pagesCnDetailPage, CnDetailCard, CnObjectSidebarSecret detail view with card-based layout + sidebar (files, notes, tags, audit trail)
CSSNL Design System double-fallback pattern (cn- prefix)Government theming compliance
Not useduseObjectStore, registerMappingPlugin, lifecyclePlugin, useListView, useDetailViewThese depend on OpenRegister API — Doriath has its own backend API

Doriath implements its own Pinia stores (useSecretStore, useEncryptionSuiteStore, useApplicationStore, etc.) that call Doriath's REST API instead of the OpenRegister API. The stores follow the same patterns (loading state, pagination, CRUD) but are not derived from useObjectStore.

package.json dependency (MUST be present):

"@conduction/nextcloud-vue": "^0.1.0-beta.1"

Webpack alias (MUST be in webpack.config.js):

const fs = require('fs')
const localLib = path.resolve(__dirname, '../nextcloud-vue/src')
const useLocalLib = fs.existsSync(localLib)

// In resolve.alias:
...(useLocalLib ? { '@conduction/nextcloud-vue': localLib } : {}),
'vue$': path.resolve(__dirname, 'node_modules/vue'),
'pinia$': path.resolve(__dirname, 'node_modules/pinia'),
'@nextcloud/vue$': path.resolve(__dirname, 'node_modules/@nextcloud/vue'),

3.6 Vue Router (Navigation)

All navigation uses Vue Router (hash mode). The lock screen is a route guard — if no AES key is in session, all routes redirect to the lock screen.

PathNameComponentProps
/DashboardDashboard
/lockLockLockScreenroute => ({ returnUrl: route.query.returnUrl })
/secretsSecretsSecretList
/secrets/:idSecretDetailSecretDetailroute => ({ secretId: route.params.id })
/folders/:idFolderViewSecretListroute => ({ folderId: route.params.id })
/applicationsApplicationsApplicationList
/applications/:idApplicationDetailApplicationDetailroute => ({ applicationId: route.params.id })
/share/link/:tokenLinkShareAccessLinkShareAccessroute => ({ token: route.params.token })
/share/request/:tokenSecretRequestFillSecretRequestFillroute => ({ token: route.params.token })
*redirect → /

Route guard pattern:

router.beforeEach((to, from, next) => {
// Public routes (link share, request fill-in) skip the lock screen
if (['LinkShareAccess', 'SecretRequestFill', 'Lock'].includes(to.name)) {
return next()
}
// Check if AES key is in session (API call to backend)
if (!store.isUnlocked) {
return next({ name: 'Lock', query: { returnUrl: to.fullPath } })
}
next()
})

Key files: src/router/index.js, registered in main.js, <router-view /> in App.vue. MainMenu navigation: use :to prop on NcAppNavigationItem (NOT @click + $router.push()).

The MainMenu footer contains two items: "Lock vault" (lock icon — calls session.lock() and navigates to /lock) and "Settings" (gear icon — opens NcAppSettingsDialog for user preferences).

3.7 OpenConnector Integration

Doriath acts as a secret store for OpenConnector. When OpenConnector needs API keys or credentials to connect to external services, it can retrieve them from Doriath's application vault via the API.

Integration pattern:

  1. Register OpenConnector as an Application in Doriath (admin auto-approved)
  2. OpenConnector receives an EncryptionSuite (via generated key pair or CSR)
  3. Admin writes API keys/credentials into OpenConnector's application vault
  4. OpenConnector retrieves its secrets via Doriath's API using X-Vault-Password header

This integration is via Doriath's REST API — no tight coupling or shared database.

4. Database Configuration

Doctrine Entities

All entities use Nextcloud's ISchemaWrapper migration pattern. Migrations are stored in lib/Migration/.

EntityTable NameNotes
EncryptionSuitedoriath_encryption_suitesComposite index on (owner_type, owner_id)
CACertificatedoriath_ca_certificates
Secretdoriath_secretsIndex on (owner_type, owner_id), folder_id, encryption_suite_id
SecretTypedoriath_secret_typesUnique index on name
Folderdoriath_foldersIndex on (owner_type, owner_id, parent_id)
Applicationdoriath_applications
SecretSharedoriath_secret_sharesIndex on source_secret_id, target_user_id
GroupSharedoriath_group_sharesIndex on (secret_id, group_id)
SecretDelegationdoriath_secret_delegationsIndex on secret_id
LinkSharedoriath_link_sharesUnique index on token
SecretRequestdoriath_secret_requestsUnique index on token
SuiteMigrationdoriath_suite_migrations

5. Open Research Questions

  1. Application API authentication — RFC 7523 (JWT Bearer / Private Key JWT) is the lean for how approved applications authenticate to retrieve secrets. Uses existing RSA key infrastructure, short-lived tokens, no new credential. Needs team discussion before finalizing. See application-mgmt spec.

  2. Pagination approach — 50 items per page (standard pagination) or 30 items with dynamic infinite scroll? To be decided during UI design.

  3. Subfolder cascade — Does ?cascade=delete and ?cascade=move apply recursively to subfolders, or only to direct contents? Unclear whether recursive folder deletion should be allowed.

  4. Internal application master password — How does a Nextcloud app running a cronjob authenticate to its Doriath vault? No solution found that doesn't compromise the security model. See Vault-app.docx for full analysis.

  5. Post-quantum cryptography — RSA-4096 is vulnerable to future quantum attacks. Post-quantum algorithms are not yet available in stable PHP/OpenSSL. Must be revisited when PHP gains support.

  6. Forced intermediate revocation and secret compromise — When an admin force-revokes the intermediate certificate (e.g., leaked key), should all secrets be flagged possibly_compromised_at? The intermediate signs certificates but doesn't directly encrypt secrets. Needs further analysis.

6. References

Cryptographic Standards

Key Management & Password Policy

Nextcloud OCP Interfaces

Architectural Decisions