mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:21:30 +00:00
95d0d85391
Five small closures wrapping the Low-tier and Info-tier audit findings. Q.1 — cmd/cli round-out (L-001 closed) ====================================== cmd/cli/dispatch_test.go: ~30 dispatch tests across handleCerts / handleAgents / handleJobs / handleImport / handleStatus. httptest.NewTLSServer mocks the API; cli.NewClient(_, _, _, _, true) constructs an insecure-skip-verify client. Each test pins the missing-args usage-print path AND the happy-path delegation. Result: 7.1% -> 63.5% coverage (gate: >=30%). Q.2 — awssm round-out (L-002 closed) ====================================== internal/connector/discovery/awssm/awssm_edge_test.go: New() default constructor, extractKeyInfo (ECDSA/Ed25519/unknown — was RSA-only), processSecret filter arms (NamePrefix mismatch / TagFilter mismatch / empty-value / GetSecretValue error), realSMClient stub-contract pin (ListSecrets / GetSecretValue / NewRealSMClient), and EmailAddresses SAN extraction. Result: 78.2% -> 96.0% coverage (gate: >=85%). Q.3 — Property-based testing pilot (L-003 closed) ====================================== gopter@v0.2.11 added to go.mod (test-only). internal/crypto/encryption_property_test.go: - TestProperty_EncryptDecryptRoundTrip — 50 successful tests, DecryptIfKeySet(EncryptIfKeySet(x, k), k) == x - TestProperty_WrongPassphraseRejected — 30 successful tests, AEAD never returns nil-error AND bytes-equal plaintext under wrong passphrase Both skipped under -short to keep developer loop fast (PBKDF2 600k rounds × 50 iters ≈ 15s on -race CI). internal/pkcs7/length_property_test.go: - TestProperty_ASN1LengthRoundTrip — three sub-properties: decodeLength(encode(x)) == x for x ∈ [0, 2³¹−1]; short-form invariant (length<128 → 1 byte == length); long-form invariant (length>=128 → high bit set + N bytes follow). 500 successful tests in <10ms. Q.4 — Architecture diagram multi-agent update (L-004 closed) ====================================== docs/qa-test-guide.md::Architecture: ASCII diagram updated to show 'certctl-agent (×N)' + callout explaining seed_demo.sql provisions 12 agent rows (1 active, 2 retired, 9 reserved/sentinel) for Parts 04, 05, 55 + FSM coverage. Operators running parallel-agent topologies guided to AGENT_COUNT=N + 'make qa-stats'. Q.5 — Test-naming CI guard (I-001 closed) ====================================== .github/workflows/ci.yml: Test-naming convention guard added after the QA-doc seed-count drift guard. Greps for func Test<X>( missing the <X>_<Scenario> suffix. Prints first 20 non-conformant as ::warning:: annotations. continue-on-error: true (informational). Excludes TestMain + TestProperty_*. Promotion to hard-fail tracked as I-001-extended. Verification ====================================== - python3 yaml.safe_load on ci.yml: OK - go vet ./cmd/cli/... ./internal/connector/discovery/awssm/... ./internal/crypto/... ./internal/pkcs7/...: clean - go test -short -count=1 across all four packages: PASS - go test -count=1 (full property tests): PASS - crypto 15.4s (50 + 30 × 600k PBKDF2) - pkcs7 5ms Audit deliverables ====================================== - gap-backlog.md: strikethroughs on L-001/L-002/L-003/L-004/I-001 with per-finding closure note - closure-plan.md: ticks Bundle Q [x] with per-item breakdown Closes: L-001, L-002, L-003, L-004, I-001 Bundle: Q (Property-Based + Hygiene)
111 lines
2.9 KiB
Go
111 lines
2.9 KiB
Go
package pkcs7
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/leanovate/gopter"
|
|
"github.com/leanovate/gopter/gen"
|
|
"github.com/leanovate/gopter/prop"
|
|
)
|
|
|
|
// Bundle Q (L-003 closure): property-based test for ASN.1 length encoding.
|
|
//
|
|
// The pkcs7 package implements DER-encoded length under [ASN1EncodeLength];
|
|
// the inverse parser is provided here as `decodeLength` (tracked under the
|
|
// EST/SCEP code path that consumes the DER framing). The property is the
|
|
// classic encode/decode round-trip:
|
|
//
|
|
// decodeLength(encodeLength(x)) == x for all 0 ≤ x ≤ math.MaxInt32
|
|
//
|
|
// In addition, structural invariants are pinned:
|
|
//
|
|
// - 0 ≤ x < 128 → output is 1 byte, equal to x
|
|
// - x ≥ 128 → output[0] has the high bit set; output[0]&0x7f == len(rest)
|
|
// and rest is big-endian
|
|
//
|
|
// These match X.690 §8.1.3.
|
|
|
|
// decodeLength is the inverse of ASN1EncodeLength, defined in this test file
|
|
// because the production code only needs the encoder. It returns the decoded
|
|
// length and the number of bytes consumed.
|
|
func decodeLength(b []byte) (int, int, bool) {
|
|
if len(b) == 0 {
|
|
return 0, 0, false
|
|
}
|
|
first := b[0]
|
|
if first < 0x80 {
|
|
return int(first), 1, true
|
|
}
|
|
n := int(first & 0x7f)
|
|
if n == 0 || n > 4 || len(b) < 1+n {
|
|
return 0, 0, false
|
|
}
|
|
v := 0
|
|
for i := 0; i < n; i++ {
|
|
v = (v << 8) | int(b[1+i])
|
|
}
|
|
return v, 1 + n, true
|
|
}
|
|
|
|
func TestProperty_ASN1LengthRoundTrip(t *testing.T) {
|
|
parameters := gopter.DefaultTestParameters()
|
|
parameters.MinSuccessfulTests = 500
|
|
properties := gopter.NewProperties(parameters)
|
|
|
|
properties.Property("decodeLength(ASN1EncodeLength(x)) == x", prop.ForAll(
|
|
func(x int32) bool {
|
|
if x < 0 {
|
|
return true // out of contract domain (lengths are non-negative)
|
|
}
|
|
encoded := ASN1EncodeLength(int(x))
|
|
got, n, ok := decodeLength(encoded)
|
|
if !ok {
|
|
t.Logf("decodeLength failed on encoded form of %d: %x", x, encoded)
|
|
return false
|
|
}
|
|
if n != len(encoded) {
|
|
t.Logf("consumed %d bytes but encoded form is %d bytes (%d → %x)", n, len(encoded), x, encoded)
|
|
return false
|
|
}
|
|
if got != int(x) {
|
|
t.Logf("round-trip mismatch: %d → %x → %d", x, encoded, got)
|
|
return false
|
|
}
|
|
return true
|
|
},
|
|
gen.Int32Range(0, 0x7fffffff),
|
|
))
|
|
|
|
properties.Property("short-form encoding for x < 128", prop.ForAll(
|
|
func(x int8) bool {
|
|
if x < 0 {
|
|
return true
|
|
}
|
|
encoded := ASN1EncodeLength(int(x))
|
|
return len(encoded) == 1 && encoded[0] == byte(x)
|
|
},
|
|
gen.Int8Range(0, 127),
|
|
))
|
|
|
|
properties.Property("long-form encoding sets high bit on first byte", prop.ForAll(
|
|
func(x int32) bool {
|
|
if x < 128 {
|
|
return true
|
|
}
|
|
encoded := ASN1EncodeLength(int(x))
|
|
if len(encoded) < 2 {
|
|
return false
|
|
}
|
|
if encoded[0]&0x80 == 0 {
|
|
t.Logf("long-form first byte %02x missing high bit for x=%d", encoded[0], x)
|
|
return false
|
|
}
|
|
n := int(encoded[0] & 0x7f)
|
|
return n == len(encoded)-1
|
|
},
|
|
gen.Int32Range(128, 0x7fffffff),
|
|
))
|
|
|
|
properties.TestingRun(t)
|
|
}
|