Skip to content

ogar-auth: the reusable auth arm (TOTP + password + legacy transition + forward re-export)#186

Merged
AdaWorldAPI merged 3 commits into
mainfrom
claude/happy-hamilton-0azlw4
Jul 9, 2026
Merged

ogar-auth: the reusable auth arm (TOTP + password + legacy transition + forward re-export)#186
AdaWorldAPI merged 3 commits into
mainfrom
claude/happy-hamilton-0azlw4

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

What

New workspace crate ogar-auth — the reusable authentication SDK every OGAR consumer pulls, so the auth primitives never diverge into per-consumer hand-rolled copies. Fully agnostic: names no consumer, carries no key table, no PII.

Surface

  • password — Argon2id PHC hash + verify (forward login credential); getrandom is the only entropy source.
  • totp — RFC 6238 (HMAC-SHA1 / 30 s / 6-digit), base32 secret, +/-1-step skew, uniform-work compare. RFC 6238 Appendix B vectors pinned.
  • legacy — agnostic 3DES-EDE2 / PBKDF1-MD5 / zero-IV-CBC / PKCS#7 transition verifier. The password table is a caller-supplied &[&str] — the algorithm is a documented .NET convention; the table is a private consumer secret this public crate never carries.
  • re-export — the ndarray encryption forward suite (XChaCha20-Poly1305 AEAD, SHA-384, Ed25519, wasm-capable zero-knowledge envelope), reused not re-implemented (git dep, mirroring the ogar-class-view -> lance-graph-contract precedent).

A reserved federation seam documents the endgame invariant: a federated (OGIT[auth] / OIDC / Zitadel) login and a local login converge on the same identity envelope before any authorization decision — the IdP adapter lands later as an ogar-adapter-* sibling.

Verified in-env (not just claimed)

  • cargo fmt -p ogar-auth -- --check — clean
  • cargo clippy -p ogar-auth --all-targets -- -D warnings — clean
  • cargo test -p ogar-auth14/14 passing
  • the encryption git dep resolves + compiles from ndarray master, exactly as CI would.

Applied from the provided patch onto latest main (the branch was restarted from main since the prior PR #177 had merged); commit trailer normalized.

🤖 Generated with Claude Code

https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1

… + forward re-export)

New workspace crate `ogar-auth` — one authentication SDK every OGAR consumer
pulls, so the auth primitives never diverge into per-consumer hand-rolled
copies (the dilution this crate prevents). Fully agnostic: names no consumer,
carries no key table, no PII.

Surface:
- password  — Argon2id PHC hash + verify (forward login credential). getrandom
              is the only entropy source (matches the encryption crate).
- totp      — RFC 6238 (HMAC-SHA1 / 30s / 6-digit), base32 secret, +/-1-step
              skew, uniform-work compare. RFC 6238 Appendix B vectors pinned.
- legacy    — agnostic 3DES-EDE2 / PBKDF1-MD5 / zero-IV-CBC / PKCS#7 transition
              verifier. The password TABLE is a caller-supplied &[&str] — the
              algorithm is a documented .NET convention, the table is a private
              consumer secret this public crate never carries.
- re-export — the ndarray `encryption` forward suite (XChaCha20-Poly1305 AEAD,
              SHA-384, Ed25519, wasm-capable zero-knowledge envelope), REUSED
              not re-implemented (git dep, mirrors the lance-graph-contract
              precedent in ogar-class-view).

Reserved seam `federation` documents the endgame invariant: a federated
(OGIT[auth] / OIDC / Zitadel) login and a local login converge on the SAME
identity envelope before any authorization decision — the IdP adapter lands
later as an ogar-adapter-* sibling.

Applied from the attached patch; trailer normalized. Gates re-run on apply.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_cb7120fc-14ad-46d3-8ca0-363140f98ebd)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e787d84fd9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +77 to +80
for _ in 1..iterations.saturating_sub(1) {
let mut h = Md5::new();
h.update(state);
state = h.finalize().into();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the Windows CryptDeriveKey derivation

When decrypting values produced by the documented legacy path (PasswordDeriveBytes(...).CryptDeriveKey("RC2", "MD5", 128, ...)), this PBKDF1-style fold derives a different key: the .NET reference CryptDeriveKey path passes the raw password to the Windows CryptoAPI derivation, not the PasswordDeriveBytes.GetBytes stream with 100 rounds and an extra final MD5. As a result, real legacy ciphertexts from that path will fail padding/UTF-8 checks and decrypt_prefixed only round-trips values created by this Rust helper, so the migration verifier is unusable for the corpus it targets.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right — thank you for the specific correction. The PBKDF1-fold likely diverges from .NET's real CryptDeriveKey (the Windows 0x36/0x5C key-material transform). This path is unverifiable in-repo without an oracle (plaintext,ciphertext) vector, so rather than ship a from-memory rewrite of security-critical crypto I recorded the exact discrepancy + a prominent ⚠ UNVERIFIED / SCAFFOLD-not-a-verifier note and the byte-parity gate required before production use (#187, 787cef7). The KDF stays until a real vector from the canonical Crypt.cs can prove the correct transform.

out.push(((bits >> n_bits) & 0xff) as u8);
}
}
Ok(out)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject invalid trailing base32 bits

When callers verify a stored or imported malformed TOTP secret whose unpadded base32 length leaves stray bits, this return path silently accepts the truncated bytes instead of rejecting the secret; for example "A" decodes to an empty HMAC key, so verify_code will accept codes for a zero-entropy secret. Generated secrets are fine, but imported/corrupted enrollment secrets should fail closed by validating the final leftover-bit count/padding before returning.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #187 (787cef7): base32_decode now fails closed on invalid trailing bits (5-7 stray bits, e.g. "A", or non-zero zero-pad) — corrupted/imported secrets are rejected; generated secrets unaffected. New test base32_rejects_invalid_trailing_bits.

claude added 2 commits July 9, 2026 01:29
…asm keystone)

Roadmap for OGAR as the crypto-kernel beast: SIMD-accelerated crypto in one
codebase, native (AVX-512/AVX2/NEON) + wasm browser, audited-reuse-first.

Keystone finding: ndarray::simd exposes the wide agnostic integer types
(U32x16 = one ChaCha20 block, U64x8 = BLAKE2b state) but they fall back to
scalar on wasm (simd_wasm.rs:75) while the float wides are [v128;4]-backed.
Backing U32x16/U64x8 as [v128;4] on wasm (+ rotl) is the single keystone that
lights up agnostic ChaCha/BLAKE2b in the browser.

Fork reality (P0 fork-first): AdaWorldAPI/AEADs + /hashes are forked
(patch-ready); stream-ciphers (chacha20) + password-hashes (argon2) are NOT
forked yet and hold the two P0 kernels; encryption currently deps crates.io
(a P0 violation). Step 0.0 = create those forks + re-wire encryption via
[patch.crates-io]. Patch the CIPHER (chacha20), not the audited AEAD.

Per-kernel plan, three decision axes (acceleration / AEAD-posture / browser
path), step-by-step phasing, node/wasmtime wasm-parity CI gate, non-goals
(never hand-roll Ed25519/AES).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
…hash split

- Fork coverage: stream-ciphers + universal-hashes now forked (0.10.1 / 0.9.1);
  password-hashes + block-ciphers still to fork.
- Version skew (the real integration cost): forks track RustCrypto 0.11.x/0.9.x,
  encryption is on 0.10.x. [patch.crates-io] can't bridge a major gap
  (patch-not-used); a direct git dep does, but carries a small 0.11 API
  migration. Step 0.0 revised accordingly.
- Axis 3 is now three tiers: wasm-CPU-SIMD (golden path) > WebCrypto (AES) >
  borrowed browser GPU (WebGPU/WebGL) — the last a bounded, sandboxed bulk path,
  never for Argon2.
- Acceleration ordering: ndarray-native and wasm are ONE polyfill (not a
  priority ladder) > WebGPU; flips to GPU-first only for bulk throughput.
- Hash role-split: chacha20poly1305 + BLAKE3 for fast/bulk; SHA-384 for the
  conservative audit digest — both, different roles.
- Effort estimate: ~a weekend, because the palette SIMD substrate paid the
  hard 90%.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_ce613856-a0a2-4273-b61c-f9675f69b7fb)

@AdaWorldAPI AdaWorldAPI merged commit 502949f into main Jul 9, 2026
2 checks passed
AdaWorldAPI pushed a commit that referenced this pull request Jul 9, 2026
…KDF caveat)

P2 (totp.rs) — base32_decode now rejects invalid trailing bits per RFC 4648: a
length leaving 5-7 stray bits (e.g. "A" -> empty HMAC key) or non-zero zero-pad
bits fails closed, so a corrupted/imported enrollment secret can't silently
become a shorter / zero-entropy key. Generated secrets unaffected. New test
base32_rejects_invalid_trailing_bits.

P1 (legacy.rs) — Codex correctly flags that the PBKDF1-fold KDF likely does not
match .NET's real CryptDeriveKey (which applies the Windows CryptoAPI
0x36/0x5C key-material transform, not the PasswordDeriveBytes 100-round stream +
final MD5). This path is unverifiable without an oracle vector, so rather than
ship a from-memory rewrite: softened the over-claim and added a prominent
UNVERIFIED / SCAFFOLD-not-verifier note recording the specific discrepancy + the
byte-parity gate required before any production use. No algorithm change.

fmt + clippy -D warnings clean; 15/15 tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016b33swuXE23hKtqxsHu9p1
AdaWorldAPI added a commit that referenced this pull request Jul 9, 2026
ogar-auth: address Codex PR #186 review (base32 fail-closed + legacy KDF caveat)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants