Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions server/news/+authority-credential-profiles.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Offline authority initialization now creates separate owner-issuer, server-authority
and server-TLS credentials atomically with the private identity database. Strict
role, issuer, identity, size and validity checks reject mixed-purpose credentials;
private keys are never included in status output. Runtime caller authentication
remains a separate milestone. The development authority schema is now version 2;
older experimental state is rejected without modification.
18 changes: 17 additions & 1 deletion server/src/arbiter_server/authority.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
DATABASE = "authority.sqlite3"
_PENDING = ".authority.pending.sqlite3"
_LOCK = ".authority.lock"
SCHEMA_VERSION = 1
SCHEMA_VERSION = 2
# Windows Modules Installer is part of the trusted host OS. This exception is
# for ancestors only; authority-root and file ACL allowlists remain unchanged.
_TRUSTED_INSTALLER = "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464"
Expand Down Expand Up @@ -246,6 +246,8 @@ def _remove_pending(root: Path) -> None:


def _write_database(path: Path, record: AuthorityRecord) -> None:
from .credentials import store_setup_credentials

fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o600)
os.close(fd)
_check_path(path)
Expand All @@ -261,6 +263,12 @@ def _write_database(path: Path, record: AuthorityRecord) -> None:
"INSERT INTO authority VALUES (1, ?, ?, ?)",
(record.owner_id, record.server_id, record.created_at),
)
store_setup_credentials(
db,
record.owner_id,
record.server_id,
datetime.fromisoformat(record.created_at),
)
db.execute(f"PRAGMA user_version={SCHEMA_VERSION}")
db.commit()
with path.open("r+b") as handle:
Expand Down Expand Up @@ -303,6 +311,8 @@ def initialize_authority(state_dir: Path) -> AuthorityRecord:

def authority_status(state_dir: Path) -> AuthorityRecord | None:
"""Inspect committed records without creating or repairing a database."""
from .credentials import verify_setup_credentials

root = _root(state_dir, create=False)
if not root.exists():
return None
Expand All @@ -323,6 +333,9 @@ def authority_status(state_dir: Path) -> AuthorityRecord | None:
rows = db.execute(
"SELECT singleton, owner_id, server_id, created_at FROM authority"
).fetchall()
credentials = db.execute(
"SELECT role, certificate_der, private_key_der FROM credentials"
).fetchall()
if len(rows) != 1:
raise ValueError("expected one authority record")
singleton, owner, server, created = rows[0]
Expand All @@ -336,6 +349,9 @@ def authority_status(state_dir: Path) -> AuthorityRecord | None:
when = datetime.fromisoformat(created)
if when.tzinfo is None or when.utcoffset() != timezone.utc.utcoffset(None):
raise ValueError("invalid creation timestamp")
# Offline inspection checks the initial setup at its recorded time.
# Network verifiers must use the current time, independently.
verify_setup_credentials(credentials, owner, server, when)
return AuthorityRecord(owner, server, created)
except (sqlite3.Error, ValueError) as exc:
if isinstance(exc, AuthorityError):
Expand Down
Loading
Loading