2 Commits

Author SHA1 Message Date
shankar0123 8b75e0311b chore: rename Go module path to github.com/certctl-io/certctl
Mechanical sed across the main go.mod's module declaration, the f5-mock-icontrol
sub-module's go.mod, every Go file's import path (361 files), and a rebuild of
the checked-in f5-mock-icontrol binary so its embedded build-info reflects the
new module path. No behavior change.

Choice B from cowork/transfer-certctl-to-org.md, executed 2026-05-04. Choice A
(keep module path declared as github.com/shankar0123/certctl regardless of
repo URL) shipped on the day of the org transfer (2026-05-03) since we had no
external Go consumers; this commit closes that deferral.

Backward-compat: GitHub HTTP redirects continue to forward
github.com/shankar0123/certctl → github.com/certctl-io/certctl at the URL
level, but Go's module proxy uses the path declared in go.mod as the
canonical name. Pre-fix, anyone trying `go get github.com/certctl-io/certctl/...`
hit a "module path mismatch" error because go.mod said
github.com/shankar0123/certctl and the URL they fetched it from said
certctl-io/certctl. Post-fix, the canonical name and the URL agree, so
go get / go install / external Go consumers / Go-tooling integrations
work cleanly via either the new path (preferred) or the old path (which
redirects and Go follows the redirect for source fetch).

Anyone still importing the old path inside their own code keeps working
provided they update their go.mod's `require` line to match — the module
path declared in their consumer's go.sum / go.mod is the authoritative
import name, so a mass sed across their import statements is the migration
on the consumer side. No external consumers exist today.

Diff shape:
  361 *.go files  — import path replacement only
    2 go.mod     — module declaration replacement only
    1 binary     — deploy/test/f5-mock-icontrol/f5-mock-icontrol rebuilt
                   so embedded build-info reflects the new path (8618965 vs
                   8618933 bytes; 32-byte diff is the build-info change)

  Total: 364 files, 730 insertions / 730 deletions, net-zero size, pure
  mechanical substitution.

Verification:
  gofmt: 17 files needed re-alignment after sed (the new path is one char
    shorter than the old, so column-aligned import groups drifted). Applied
    `gofmt -w` to fix.
  go mod tidy: clean exit on both modules.
  go vet ./...: clean exit.
  go build ./...: clean exit.
  go test -short -count=1 on representative packages: all green
    (internal/domain, internal/validation, internal/crypto, internal/crypto/signer,
    cmd/agent). Test output now reads `ok github.com/certctl-io/certctl/...`
    confirming the module path resolves correctly.
  binary: f5-mock-icontrol rebuilt; `strings | grep shankar0123` returns
    nothing; `strings | grep certctl-io/certctl` shows the new module path
    embedded in build-info.

Files intentionally NOT touched in this commit:
  README.md / CHANGELOG.md / docs/ / etc. — already swept to certctl-io
    URLs in commit 0729ee4 (the post-transfer URL refresh). This commit is
    purely the Go-tooling layer.
  Scarf pixels (`shankar0123.docker.scarf.sh/...`) — Scarf-account
    namespace, not a Go import or GitHub repo URL. Stays.

This is a non-blocking, non-customer-impacting change. Operators pulling
container images, running `make verify`, hitting the API, or installing the
agent see no functional difference. Only Go-tooling consumers (none today)
are affected, and they're enabled — not broken — by this commit.
2026-05-04 00:30:29 +00:00
shankar0123 a546a1bbef feat(scep): EnvelopedData decrypt + signerInfo POPO verify (RFC 8894 §3.2)
SCEP RFC 8894 + Intune master bundle — Phase 2 of 14.

Implements the new RFC 8894 PKIMessage parse path: EnvelopedData parser
+ decryptor, signerInfo parser + signature verifier, handler dispatch
that tries the RFC 8894 path FIRST and falls through to the legacy MVP
raw-CSR path on any parse failure. Backward compat with lightweight SCEP
clients is preserved by design — no behavior change for any existing
deploy that doesn't set CERTCTL_SCEP_RA_*.

internal/pkcs7/envelopeddata.go (new, ~330 LoC)
  * ParseEnvelopedData: parses CMS EnvelopedData per RFC 5652 §6.1, with
    optional outer ContentInfo unwrapping. Handles SET OF RecipientInfo
    + IssuerAndSerial form rid (RFC 8894 §3.2.2).
  * EnvelopedData.Decrypt: RSA PKCS#1 v1.5 key-trans + AES-CBC (128/192/
    256) or DES-EDE3-CBC content decryption with **constant-time PKCS#7
    padding strip** (no branch on padding-byte values; closes the
    padding-oracle leak surface). Recipient mismatch is BadMessageCheck
    per RFC 8894 §3.3.2.2 (NOT BadCertID); every failure mode returns
    the same ErrEnvelopedDataDecrypt sentinel to close timing-leak legs
    of Bleichenbacher attacks.
  * Equivalent to micromdm/scep's cryptoutil/cryptoutil.go::DecryptPKCS-
    Envelope (cited in code comments; not vendored — fuzz-target
    ownership stays in this sub-package per the operating rule).

internal/pkcs7/signedinfo.go (new, ~370 LoC)
  * ParseSignedData / ParseSignerInfos: parses CMS SignedData per RFC
    5652 §5.3. Resolves each SignerInfo's SID (IssuerAndSerial v1 OR
    [0] SubjectKeyId v3) against the SignedData certificates SET to
    pluck the device's transient signing cert.
  * SignerInfo.VerifySignature: re-serialises signedAttrs as the
    canonical SET OF Attribute (the RFC 5652 §5.4 quirk every CMS
    implementation hits — wire form is [0] IMPLICIT but the signature
    is over EXPLICIT SET OF). Hashes with SHA-1/SHA-256/SHA-512 +
    verifies via RSA PKCS1v15 or ECDSA per the cert's pubkey type.
  * Auth-attr extractors: GetMessageType (PrintableString-decimal),
    GetTransactionID, GetSenderNonce, GetMessageDigest. SCEP attr OIDs
    pinned (RFC 8894 §3.2.1.4).

internal/pkcs7/{envelopeddata,signedinfo}_fuzz_test.go (new)
  * FuzzParseEnvelopedData / FuzzParseSignedData / FuzzParseSignerInfos
    / FuzzVerifySignerInfoSignature — every parser certctl adds gets a
    panic-safety fuzzer (the fuzz-target-ownership rule from
    cowork/CLAUDE.md::Operating Rules). Local 5s runs hit ~270k
    executions per parser without panic. Errors are expected for
    arbitrary inputs; only panics are bugs.

internal/pkcs7/{envelopeddata,signedinfo}_test.go (new)
  * Round-trip tests that materialise real RSA/ECDSA pairs, hand-build
    the wire bytes, parse + decrypt + verify, and assert plaintext /
    auth-attr equality. The build helpers use this package's ASN1Wrap
    primitives directly (asn1.Marshal of structs containing nested
    asn1.RawValue is finicky for mixed Class/Tag); gives byte-level
    control matching what real SCEP clients emit.
  * Negative tests: tampered ciphertext / tampered auth-attrs / wrong
    RA / wrong key / mismatched recipients / random garbage all return
    the appropriate sentinel error without panic.

internal/service/scep.go
  * PKCSReqWithEnvelope: RFC 8894 envelope-aware variant. Returns
    *SCEPResponseEnvelope (not error + *SCEPEnrollResult) because RFC
    8894 §3.3 mandates a CertRep PKIMessage on every response, even
    failures — the handler shouldn't translate Go errors into SCEP
    failInfo codes. Returns nil to signal 'invalid challenge password'
    so the caller can translate to HTTP 403 (matches MVP path's wire
    shape; RFC 8894 §3.3.1 is silent on this case).
  * mapServiceErrorToFailInfo: exact mapping table from the prompt
    (CSR parse → BadRequest, CSR sig → BadMessageCheck, crypto policy
    → BadAlg, default → BadRequest).

internal/api/handler/scep.go
  * SCEPService interface gains PKCSReqWithEnvelope.
  * SCEPHandler now optionally carries an RA cert + key pair. SetRAPair
    upgrades the handler to the RFC 8894 path; without that call the
    handler stays MVP-only (the v2.0.x behavior).
  * pkiOperation: tries the RFC 8894 path FIRST when the RA pair is
    set. tryParseRFC8894 helper does the full pipeline (ParseSignedData
    → VerifySignature → extract auth-attrs → ParseEnvelopedData → Decrypt
    → x509.ParseCertificateRequest the recovered bytes). On any failure
    it falls through to the legacy extractCSRFromPKCS7 MVP path —
    backward compat is non-negotiable.
  * Phase 2 emits the legacy certs-only response on RFC 8894 success;
    Phase 3 (next commit) swaps in writeCertRepPKIMessage with the
    proper status / failInfo / nonce-echo wire shape.

cmd/server/main.go
  * Per-profile loop now calls loadSCEPRAPair after preflight to load
    the cert + key + inject via SetRAPair. crypto + crypto/tls imports
    added.
  * loadSCEPRAPair helper: tls.X509KeyPair-based parse + leaf cert
    extraction. Failures here indicate TOCTOU between preflight + load.

internal/api/handler/scep_handler_test.go +
internal/api/router/router_scep_profiles_test.go
  * mockSCEPService / scepProfileMockService gain PKCSReqWithEnvelope
    stubs to satisfy the extended interface. Existing test cases
    unchanged (they exercise the MVP path; RA pair is unset).

Verification:
  * gofmt + go vet clean for the files I touched.
  * go test -short -count=1 green across pkcs7 / api/handler /
    api/router / service / cmd/server.
  * Coverage: pkcs7 78.4% (was 100% — drops because new code includes
    paths the round-trip tests don't yet hit, like decryption alg
    fall-through and v3 SubjectKeyId SID matching).
  * Fuzz-target seed-corpus runs (5s each, ~270k execs/parser): no
    panic. Pre-merge fuzz-time bumps to 30s per the prompt's
    verification gate.

Phase 2 of 14 in SCEP RFC 8894 + Intune master bundle.
Living progress at cowork/scep-rfc8894-intune/progress.md.
2026-04-29 12:36:27 +00:00