Skip to content

Commit b9c4be7

Browse files
committed
First run of specs
1 parent d9f073f commit b9c4be7

13 files changed

Lines changed: 885 additions & 41 deletions

File tree

openspec/ROADMAP.md

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,62 @@
11
# Roadmap
22

3-
This document tracks the planned development of Nextcloud App Template.
3+
This document tracks the planned development of Doriath.
44

5-
Features are defined in [`appspec/features/`](../appspec/features/). When a feature reaches `planned` status during an `/app-explore` session, it is listed here and an OpenSpec change is created with `/opsx:ff`.
5+
Features are defined in [`openspec/specs/`](specs/). When a feature reaches `planned` status it is listed here and an OpenSpec change is created with `/opsx:ff`.
66

77
## Status Overview
88

99
| Feature | Status | Priority | OpenSpec Change |
1010
|---------|--------|----------|----------------|
11-
| _(no features defined yet — use `/app-explore` to start)_ ||||
11+
| [Encryption Suites](specs/encryption-suites/spec.md) | planned | 1 — must be first ||
12+
| [Secrets](specs/secrets/spec.md) | planned | 2 ||
13+
| [Key Generator](specs/key-generator/spec.md) | planned | 3 — needed for secret creation UI ||
14+
| [Application Management](specs/application-mgmt/spec.md) | planned | 4 ||
15+
| [User Sharing](specs/user-sharing/spec.md) | planned | 5 ||
16+
| [Link Sharing](specs/link-sharing/spec.md) | planned | 6 ||
17+
| [Secret Requests](specs/secret-requests/spec.md) | planned | 7 ||
1218

1319
## Phases
1420

15-
### Phase 1 — Foundation
21+
### Phase 1 — Foundation (Core)
1622

17-
_Define the core features needed for a working app. These are the minimum set that make the app useful._
23+
The minimum set that makes Doriath useful as a secrets manager.
1824

19-
### Phase 2 — Enhancement
25+
1. **Encryption Suites** — CA bootstrap, key pair generation, user setup on first login
26+
2. **Secrets** — CRUD with RSA encryption at rest
27+
3. **Key Generator** — random key generation integrated into secret creation
2028

21-
_Add features that improve the experience, extend functionality, and cover more use cases._
29+
### Phase 2 — Sharing (Core)
2230

23-
### Phase 3 — Polish
31+
Features that enable secrets to move between parties.
2432

25-
_Performance, accessibility improvements, full localization, and hardening for production._
33+
4. **Application Management** — register apps, approval queue, CSR-based EncryptionSuite
34+
5. **User Sharing** — copy + re-encrypt for Nextcloud users, sync on change
35+
6. **Link Sharing** — password-protected links with usage limits
36+
7. **Secret Requests** — fill-in links for write-without-read secrets
37+
38+
### Phase 3 — Advanced (Future)
39+
40+
_Not yet specced. To be explored in future `/opsx:app-explore` sessions._
41+
42+
- Multiple encryption suites per owner (key rotation, compromise recovery)
43+
- API (basic auth + OAuth)
44+
- Custom CA chain upload
45+
- Post-quantum cryptography (when available in PHP)
46+
47+
### Phase 4 — Future Development
48+
49+
_Noted in Vault-app.docx but explicitly out of scope for now._
50+
51+
- Encrypted mail integration
52+
- Certificate Authority (public CA functionality)
53+
- File encryption
2654

2755
---
2856

2957
## How This Works
3058

31-
1. Run `/app-explore` to define features in `appspec/features/`
59+
1. Run `/opsx:app-explore` to define or refine features in `openspec/specs/`
3260
2. When a feature is `planned`, add it to the table above
3361
3. Run `/opsx:ff {feature-name}` to create the implementation spec
3462
4. Update the **OpenSpec Change** column with a link to the change directory

openspec/app-config.json

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
{
2-
"id": "app-template",
3-
"name": "Nextcloud App Template",
4-
"namespace": "AppTemplate",
5-
"summary": "A template for creating new Nextcloud apps",
6-
"goal": "A starting point for building Nextcloud apps following ConductionNL conventions",
7-
"category": "tools",
2+
"id": "doriath",
3+
"name": "Doriath",
4+
"namespace": "Doriath",
5+
"summary": "Encrypted secrets manager for Nextcloud — password manager and key store for users and applications",
6+
"goal": "Securely store and share secrets (passwords, API keys, certificates) for Nextcloud users and applications, using end-to-end RSA/AES encryption backed by a private Certificate Authority",
7+
"category": "security",
88
"version": "0.1.0",
99
"license": "EUPL-1.2",
1010
"author": "Conduction B.V.",
11-
"repository": "https://github.com/ConductionNL/nextcloud-app-template",
11+
"repository": "https://github.com/ConductionNL/doriath",
1212
"dependencies": {
13-
"requiresOpenRegister": true,
14-
"additionalCiApps": [
15-
{"repo": "ConductionNL/openregister", "app": "openregister", "ref": "main"}
16-
]
13+
"requiresOpenRegister": false,
14+
"additionalCiApps": []
1715
},
1816
"cicd": {
1917
"phpVersions": ["8.3", "8.4"],
2018
"nextcloudRefs": ["stable31", "stable32"],
2119
"enableNewman": false
2220
},
23-
"createdAt": "2025-01-01",
21+
"architecture": {
22+
"dataLayer": "own-database-tables",
23+
"pattern": "Thick backend — Doriath owns its own encrypted database tables; no OpenRegister, no n8n",
24+
"encryption": "RSA-4096 + AES-256 via OpenSSL; private CA with root and intermediate certificates",
25+
"frontend": "Vue 2.7 + Pinia, queries Doriath REST API",
26+
"authentication": "Nextcloud session (users) or X-Vault-Password header (external apps)"
27+
},
28+
"createdAt": "2026-03-23",
2429
"updatedAt": "2026-03-23"
2530
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# ADR-001: Own Database Tables (No OpenRegister)
2+
3+
**Status**: accepted
4+
5+
**Date**: 2026-03-23
6+
7+
## Context
8+
9+
Org-wide ADR-001 mandates that Conduction apps use OpenRegister as their data layer (JSON object storage with schema validation). This keeps data architecture consistent across apps and avoids each app managing its own migrations.
10+
11+
Doriath is a secrets manager that stores highly sensitive encrypted data: private keys (AES-encrypted), encrypted secret values, and Certificate Authority certificates. The security model depends on:
12+
13+
- Strict schema control — encrypted fields must never be accidentally exposed or logged as plain text
14+
- Fine-grained DB-level isolation — secrets must be queryable by owner and encryption suite without exposing their contents to the query layer
15+
- No intermediary services in the data path — every hop between Doriath and the database is a potential point of failure or leakage
16+
17+
OpenRegister is a generic JSON object store. It introduces an external service in the data path and provides no mechanism for field-level encryption guarantees. Storing encrypted blobs in OpenRegister's generic object model would mix encrypted and non-encrypted data in a shared store, complicating auditing and access control.
18+
19+
## Decision
20+
21+
Doriath manages its own Doctrine entities and Nextcloud database migrations. It does **not** depend on OpenRegister for any data storage.
22+
23+
This is an explicit app-specific exception to org-wide ADR-001.
24+
25+
Doriath also does **not** use n8n for any internal workflow or automation — all business logic runs directly in PHP.
26+
27+
## Consequences
28+
29+
**Positive:**
30+
- Full control over schema — encrypted fields are typed and documented at the DB level
31+
- No external service dependency for core functionality — Doriath works standalone
32+
- Simpler audit trail — the only path to the data is through Doriath's own controllers and services
33+
- Migrations use Nextcloud's ISchemaWrapper, keeping the app upgrade path clean
34+
35+
**Negative / trade-offs:**
36+
- Doriath must maintain its own DB migrations — more maintenance than OpenRegister-managed objects
37+
- No benefit from OpenRegister's search, filtering, or schema validation infrastructure
38+
- The thin-client pattern used by other Conduction apps does not apply here — Doriath requires a proper backend service layer
39+
40+
## Alternatives Considered
41+
42+
| Option | Reason not chosen |
43+
|--------|------------------|
44+
| OpenRegister JSON object store | Introduces external service in data path; no field-level encryption guarantees; shared object store complicates access control auditing |
45+
| Nextcloud's UserPreferences / AppConfig | Not suited for structured relational data or large encrypted blobs |
46+
| External secrets backend (e.g., HashiCorp Vault) | Breaks Nextcloud-native design; introduces dependency outside the Nextcloud instance |
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# ADR-002: Polymorphic Ownership on EncryptionSuite
2+
3+
**Status**: accepted
4+
5+
**Date**: 2026-03-23
6+
7+
## Context
8+
9+
EncryptionSuites — the records that hold a public certificate and AES-encrypted private key — must be owned by either a Nextcloud user or a registered Application. Both owner types require identical encryption operations (generate, revoke, look up by owner).
10+
11+
Two design options exist:
12+
1. **Separate tables**`UserEncryptionSuite` and `AppEncryptionSuite`
13+
2. **Polymorphic association** — single `EncryptionSuite` table with `owner_type` (user|application) and `owner_id` columns
14+
15+
A complicating factor: Nextcloud users are not stored in Doriath's own database — they are managed by Nextcloud's user backend. This means that even with separate tables, the "user" side cannot use a native foreign key constraint to the users table.
16+
17+
## Decision
18+
19+
Use a single `EncryptionSuite` table with polymorphic `owner_type` and `owner_id` columns.
20+
21+
- `owner_type`: enum `user` | `application`
22+
- `owner_id`: string (Nextcloud user ID, or Doriath Application row ID)
23+
24+
Referential integrity for the `application` owner type is enforced at the application layer with cascade delete logic. For the `user` owner type, a Nextcloud `IUserDeletedEvent` listener handles cleanup.
25+
26+
## Consequences
27+
28+
**Positive:**
29+
- Single table, single query path for all encryption operations regardless of owner type
30+
- Owner types are small and fixed (user, application) — no combinatorial explosion
31+
- Encryption logic is identical regardless of owner type — no branching needed
32+
- Extension to a third owner type (e.g., group) requires only a new enum value and cleanup listener
33+
34+
**Negative / trade-offs:**
35+
- No database-level foreign key constraint for owner references — orphan risk must be handled in code
36+
- Queries filtering by owner require composite index on `(owner_type, owner_id)`
37+
- Code must explicitly handle the `user` case using Nextcloud's user management interfaces rather than a DB join
38+
39+
## Alternatives Considered
40+
41+
| Option | Reason not chosen |
42+
|--------|------------------|
43+
| Separate `UserEncryptionSuite` and `AppEncryptionSuite` tables | Duplicates identical schema and logic; does not solve the FK problem for Nextcloud users anyway |
44+
| Single table with nullable `user_id` and `application_id` columns | Allows invalid state (both null, or both set); more complex validation; no cleaner than polymorphic |
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# ADR-003: RSA + AES Encryption Architecture
2+
3+
**Status**: accepted
4+
5+
**Date**: 2026-03-23
6+
7+
## Context
8+
9+
Doriath stores secrets (passwords, API keys, and other sensitive values) for multiple users and applications. The core security requirement is that:
10+
11+
1. Secrets are encrypted at rest and can only be decrypted by the holder of the correct private key
12+
2. A user or application can write a secret into another party's vault without being able to read it afterwards
13+
3. Private keys are never stored in plaintext — they are always protected by a master password
14+
4. The master password is never stored in the database
15+
16+
These requirements rule out symmetric encryption (where the same key encrypts and decrypts, making write-without-read impossible) and any architecture where the server can decrypt secrets without user involvement.
17+
18+
## Decision
19+
20+
Doriath uses a hybrid RSA + AES encryption scheme, implemented via OpenSSL in PHP:
21+
22+
**Secrets are encrypted with RSA (asymmetric):**
23+
- Each user and application has an EncryptionSuite containing a public/private key pair
24+
- Secrets are encrypted with the owner's public certificate — only the private key can decrypt them
25+
- This enables write-without-read: any party can encrypt a secret for an owner without being able to read it back
26+
- Minimum key size: 4096 bits (system default; only allowed to increase)
27+
28+
**Private keys are encrypted with AES (symmetric):**
29+
- The private key in each EncryptionSuite is stored AES-256 encrypted using the owner's master password
30+
- The master password is never stored in the database
31+
- For Nextcloud users: master password is held in the user session (configurable timeout: session, 10 min, or 30 min)
32+
- For external applications: master password is passed via the `X-Vault-Password` HTTP header on each request
33+
34+
**Certificate Authority:**
35+
- A private CA is bootstrapped on first setup: one root certificate and one intermediate certificate
36+
- User and application public certificates are signed by the intermediate
37+
- The intermediate's private key is stored AES-encrypted in the database
38+
- Administrators may upload their own CA chain (root + signing intermediate) instead of using the generated one
39+
40+
**Chunking:**
41+
- RSA encryption has a per-chunk limit of approximately 500 bytes (key size in bytes − 1 − 11 bytes PKCS#1 padding)
42+
- Large values (additional fields, future file encryption) must be chunked before encryption
43+
- Chunking strategy will be defined before implementing support for additional encrypted fields
44+
45+
**Post-quantum note:**
46+
- Post-quantum cryptography algorithms are not yet available in stable PHP/OpenSSL libraries (as of 2026-03-23)
47+
- This architecture will need revisiting when post-quantum support becomes available in PHP
48+
49+
## Consequences
50+
51+
**Positive:**
52+
- Write-without-read is natively supported by asymmetric encryption — no additional mechanism needed
53+
- OpenSSL compatibility means Doriath encryption suites are interoperable with standard PKI tooling
54+
- Master password never at rest — even a full DB dump cannot be decrypted without user involvement
55+
- CA structure enables future Certificate Authority functionality with minimal additional work
56+
57+
**Negative / trade-offs:**
58+
- Chunking complexity for data larger than ~500 bytes — must be implemented carefully and uniformly
59+
- Master password in session is a temporary secret that can be lost (session expiry, server restart)
60+
- If the master password is lost, secrets become permanently inaccessible — no recovery path
61+
- RSA operations are slower than symmetric encryption — acceptable for secrets (small payloads), not for bulk data
62+
- Post-quantum vulnerability is a known future risk
63+
64+
## Alternatives Considered
65+
66+
| Option | Reason not chosen |
67+
|--------|------------------|
68+
| Symmetric AES only | Cannot support write-without-read; server holds decrypt key — breaks security model |
69+
| Client-side encryption (encrypt in browser) | Master password never reaches server, but complicates API access for applications; out of scope for current architecture |
70+
| External KMS (AWS KMS, HashiCorp Vault) | Breaks Nextcloud-native design; adds external dependency and cost |
71+
| Age / libsodium (modern alternatives to OpenSSL RSA) | Not yet universally available in PHP distributions; post-quantum support also not mature |

openspec/config.yaml

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,45 @@
11
schema: conduction
22

33
context: |
4-
Project: App Template
5-
Repo: ConductionNL/nextcloud-app-template
4+
Project: Doriath
5+
Repo: ConductionNL/doriath
66
Type: Nextcloud App (PHP backend + Vue 2 frontend)
7-
Description: Starter template for Conduction Nextcloud apps — replace this description with your app's purpose.
8-
Key components: Dashboard, Settings, Example objects
9-
Database: PostgreSQL (via OpenRegister's ObjectService)
10-
Mount path: /var/www/html/custom_apps/app-template
7+
Description: Encrypted secrets manager for Nextcloud — stores and shares secrets (passwords, API keys)
8+
for users and applications using end-to-end RSA/AES encryption with a private Certificate Authority.
9+
Key components: Secrets, EncryptionSuites, CACertificates, Applications, Sharing (user/link/request)
10+
Database: PostgreSQL (own tables — does NOT use OpenRegister)
11+
Mount path: /var/www/html/custom_apps/doriath
1112
1213
Architecture:
13-
Pattern: Thin client — App Template owns no database tables
14-
Data layer: OpenRegister (JSON object storage with schema validation)
15-
Frontend: Vue 2.7 + Pinia stores querying OpenRegister API directly
16-
Backend: Minimal — SettingsController + SettingsService for register setup
14+
Pattern: Thick backend — Doriath owns all database tables and handles all encryption server-side
15+
Data layer: Own Doctrine entities and migrations (no OpenRegister dependency)
16+
Encryption: RSA-4096 for secrets, AES-256 for private keys; OpenSSL; private CA (root + intermediate)
17+
Frontend: Vue 2.7 + Pinia stores querying Doriath REST API
18+
Backend: Controllers, Services, and Entities for secrets, encryption suites, applications, sharing
19+
Master password: Stored in Nextcloud user session or X-Vault-Password HTTP header (never in DB)
1720
1821
Shared specs: See ../openspec/specs/ for cross-project conventions
1922
Project guidelines: See ../project.md for workspace-wide standards
2023
2124
rules:
2225
proposal:
23-
- Reference shared nextcloud-app spec for app structure requirements
24-
- Consider impact on other Conduction apps
26+
- Doriath owns its own database — do NOT reference OpenRegister for data storage
27+
- Security implications must be explicitly addressed for any feature touching encryption
28+
- Consider impact on other Conduction apps only where Doriath acts as a secret provider
2529
specs:
26-
- Use Schema.org type annotations for all entities
27-
- Specify which Nextcloud OCP interfaces are used for integration features
30+
- Specify which fields are stored encrypted vs plain text
31+
- Specify which Nextcloud OCP interfaces are used for session, user, and auth integration
32+
- Encryption chunking constraints (RSA cap ~500 bytes/chunk) must be noted for large data fields
2833
design:
29-
- Uses OpenRegister API directly from frontend (no own backend CRUD)
30-
- Register config in lib/Settings/app_template_register.json (OpenAPI 3.0.0 format)
31-
- Imported via ConfigurationService::importFromApp() in repair step
34+
- All encryption/decryption happens in PHP backend using OpenSSL — never in frontend
35+
- Private keys are never stored unencrypted — always AES-256 encrypted with master password
36+
- Master password is never stored in the database
37+
- Migrations must use Nextcloud's ISchemaWrapper pattern
3238
tasks:
33-
- Test with OpenRegister to verify schema validation works
39+
- Test encryption round-trips (encrypt → store → retrieve → decrypt) in integration tests
40+
- Test that encrypted fields cannot be read without the correct master password
3441
review:
3542
- Cross-reference shared specs (nextcloud-app, api-patterns, nl-design, docker)
36-
- Flag spec deviations with WARNING and justification
43+
- Flag any pattern that stores a private key or master password unencrypted as a BLOCKER
44+
- Flag spec deviations from org-wide ADRs with WARNING and justification
45+
- ADR-001 (OpenRegister data layer) is explicitly superseded by Doriath ADR-001 for this app

0 commit comments

Comments
 (0)