"DOC VN CA chain — VNeID + VnPay/MK Group/Viettel-CA partners for VN-residency qualified digital signatures per Decree 130/2018"
§1 — Description (BCP-14 normative)
The DOC service MUST ship VN CA integration at services/doc/src/vn_ca/ with 3 partners + VNeID linkage + VN Root CA validation, 5 memory audit kinds.
- MUST support 3 partners per DEC-1790 — VnPay, MK Group, Viettel-CA; abstraction at
abstraction.rs::dispatcher.
- MUST validate
vn_ca_partnerenum cardinality 3 per DEC-1791.
- MUST validate
vn_ca_request_kindenum cardinality 5 per DEC-1792.
- MUST support VNeID identity linkage per DEC-1793 at
vneid_linker.rs::link(signer, vneid_token):
- Verify VNeID token via gov OAuth (TASK-DOC-006 vneid handler shared).
- Submit verified identity to chosen CA for qualified cert enrollment.
- Store cert in tenant's KMS for signer reuse.
- MUST dispatch per partner — each implements
enroll_cert(signer, vneid_verified),sign(pdf_hash, cert_id),validate_chain(cert),revoke(cert_id).
- MUST validate chain to VN National Root CA per DEC-1790 at
vn_root_validator.rs::validate(cert)— anchor must be in VN gov root list (refreshed quarterly).
- MUST return CMS signature with VN trust chain per DEC-1795; TASK-DOC-011 extends to LT.
- MUST store creds in KMS per DEC-1794 — CISO-only.
- MUST define tables at migration
0010: ```sql CREATE TABLE tenant_vn_ca_creds ( tenant_id UUID PRIMARY KEY, partner TEXT NOT NULL CHECK (partner IN ('vnpay','mk_group','viettel_ca')), encrypted_creds_arn TEXT NOT NULL, api_account_id TEXT, set_by UUID NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); ALTER TABLE tenant_vn_ca_creds ENABLE ROW LEVEL SECURITY; CREATE POLICY vn_ca_creds_rls ON tenant_vn_ca_creds USING (tenant_id = current_setting('auth.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('auth.tenant_id')::uuid); GRANT UPDATE (partner, encrypted_creds_arn, api_account_id, set_by, updated_at) ON tenant_vn_ca_creds TO cyberos_app;
CREATE TABLE doc_vn_ca_signatures ( vn_ca_sig_id UUID PRIMARY KEY, tenant_id UUID NOT NULL, document_id UUID NOT NULL, signer_id UUID NOT NULL, vneid_subject_id TEXT, partner TEXT NOT NULL CHECK (partner IN ('vnpay','mk_group','viettel_ca')), request_kind TEXT NOT NULL CHECK (request_kind IN ('certificate_enroll','signature_request','vneid_link','validation_check','revocation_check')), cert_chain_pem TEXT NOT NULL, signature_value BYTEA NOT NULL, timestamp_token BYTEA, vn_root_validated BOOLEAN NOT NULL, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','succeeded','failed','revoked')), trace_id CHAR(32), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); ALTER TABLE doc_vn_ca_signatures ENABLE ROW LEVEL SECURITY; CREATE POLICY vn_ca_sigs_rls ON doc_vn_ca_signatures USING (tenant_id = current_setting('auth.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('auth.tenant_id')::uuid); REVOKE UPDATE, DELETE ON doc_vn_ca_signatures FROM cyberos_app; GRANT UPDATE (status) ON doc_vn_ca_signatures TO cyberos_app; ```
- MUST expose endpoints: ``
text PUT /v1/doc/vn-ca/creds (CISO-only) POST /v1/doc/vn-ca/sign (internal — TASK-DOC-005 caller) POST /v1/doc/vn-ca/vneid-link (signer-initiated VNeID linkage) GET /v1/doc/vn-ca/signatures/{doc_id}``
- MUST emit 5 memory audit kinds per DEC-1796. PII per TASK-MEMORY-111: vneid_subject_id SHA-256 hashed (national ID is sensitive); signature + cert chain hashed.
- MUST thread trace_id end-to-end.
- MUST NOT allow non-CISO creds per DEC-1794.
- MUST NOT skip VN Root CA validation per DEC-1790.
- MUST NOT sign without VNeID-linked identity for qualified signatures per DEC-1793.
§2 — Why this design
Why VN-domestic partners (DEC-1790)? Decree 130/2018 requires VN root CA chain for qualified signatures; EU/US CAs not accepted by VN courts.
Why 3 partners (DEC-1791)? Market diversity; CLO chooses based on industry vertical (VnPay = fintech focus, Viettel = enterprise, MK Group = SME).
Why VNeID linkage (DEC-1793)? Decree 130 requires verified national identity for qualified-level signatures; VNeID is the gov-blessed identity rail.
Why CISO creds (DEC-1794)? VN CA creds compromise = mass forgery on VN contracts; CISO has authority.
§3 — API contract
PUT /v1/doc/vn-ca/creds body: {partner, creds}
POST /v1/doc/vn-ca/sign body: {document_id, signer_id, signer_cert_id}
POST /v1/doc/vn-ca/vneid-link body: {signer_id, vneid_oauth_callback_token}
GET /v1/doc/vn-ca/signatures/{doc_id}
Sample VNeID link:
{
"signer_id": "uuid",
"vneid_oauth_callback_token": "encoded-token"
}
Response:
{
"vneid_subject_id": "VN-citizen-id-hash",
"cert_enrolled": true,
"cert_id": "uuid",
"expires_at": "2027-05-17"
}
§4 — Acceptance criteria
- 3 partners + cardinality test. 2. VnPay client works. 3. MK Group client works. 4. Viettel-CA client works. 5. VNeID link required for qualified. 6. VN Root CA validation enforced. 7. 5 request_kind enum. 8. CISO-only creds. 9. Creds in KMS. 10. 5 memory audit kinds emitted. 11. PII scrubbed (vneid_subject_id+signature+cert SHA256). 12. RLS denies cross-tenant. 13. Trace_id preserved. 14. TASK-DOC-005 integration. 15. Append-only sigs via REVOKE except status. 16. Non-VN chain rejected. 17. Sandbox + prod env per partner. 18. Cert revoke path. 19. Composes with TASK-DOC-011 for LT. 20. VNeID linkage 1-per-signer (idempotent).
§5 — Verification
#[tokio::test]
async fn vnpay_signature_with_vneid_link() {
let ctx = TestContext::with_vnpay_creds_and_vneid_signer().await;
let r = ctx.vn_ca_sign(ctx.doc_id, ctx.signer_id).await;
assert!(r.vn_root_validated);
assert_eq!(r.partner, "vnpay");
assert!(r.vneid_subject_id.is_some());
}
#[tokio::test]
async fn non_vneid_signer_rejected_for_qualified() {
let ctx = TestContext::vn_signer_no_vneid().await;
let r = ctx.try_vn_ca_sign(ctx.doc_id, ctx.signer_id).await;
assert!(r.is_err());
}
#[tokio::test]
async fn non_vn_root_chain_rejected() {
let ctx = TestContext::with_eu_chain_cert().await;
let r = ctx.try_vn_ca_sign(ctx.doc_id, ctx.signer_id).await;
assert!(r.is_err());
}
// 5.4..5.10
§7 — Dependencies
Upstream: TASK-DOC-001. Cross-module: TASK-DOC-005 (caller), TASK-DOC-006 (VNeID handler shared), TASK-DOC-011 (LT extend), TASK-AUTH-105 (KMS), TASK-AUTH-101 (CISO), TASK-MEMORY-111 (PII).
§10 — Failure modes (similar to DOC-002/003)
| Failure | Detection | Outcome | Recovery |
|---|---|---|---|
| Partner API down | retry | sev-1; failed | switch partner |
| VNeID OAuth denied | callback | failed_no_match | retry |
| Cert chain not VN root | validator | reject; sev-1 | new enroll |
| Cert revoked | OCSP | status=revoked | new cert |
| Creds expired | 401 | sev-1; CISO | rotate |
| VN Root CA list lag | quarterly refresh | sev-3 | maintenance |
| Sandbox vs prod confusion | env flag | hard reject mismatch | inherent |
| Cross-tenant cert use | RLS | 403 | inherent |
| Signer VNeID not VN citizen | VNeID rejects | failed_no_match | inherent |
| Multi-partner conflict (tenant switched) | use current creds row | last-wins | inherent |
§11 — Implementation notes
- §11.1 Each partner has REST API; VnPay = OAuth + signature endpoint, MK Group + Viettel similar.
- §11.2 VNeID OAuth: gov-managed identity rail; tokens short-lived (5min); cert enrollment downstream.
- §11.3 VN Root CA list maintained by Ministry of Information & Communication; refreshed quarterly.
- §11.4 memory audit body: doc_id, signer_id, partner, vn_root_validated; signatures + vneid_subject_id SHA256.
- §11.5 Future partners: extend enum + new client file (abstraction is the seam).
End of TASK-DOC-004 spec.