smx509 Upgrade Skill — Go Stable Version Upgrade Workflow
Quick Reference: See
scripts/smx509/README.mdfor directory structure, all 3 workflows (direct edit / minor upgrade / major upgrade), and command cheat sheet.
Overview
This skill guides the upgrade of the smx509 module when a new Go stable version is released (e.g., Go 1.25 → 1.26). The smx509 module is a clean fork of Go stdlib crypto/x509 with declarative patches for SM2/PQC/SM4 extensions.
Prerequisites
- New Go stable version installed locally (e.g.,
go1.26or viago install) GOROOTpointing to the new Go version- Working tree clean on
developbranch - Baseline at
scripts/smx509/baseline/(committed, from previous sync)
Architecture Reference
scripts/smx509/
├── sync.ps1 # Copies stdlib → smx509/ + applies patches
├── gen_patches.go # Regenerates patches from baseline↔smx509 diff
├── baseline/ # Go stdlib baseline (committed, version-pinned)
│ ├── x509.go
│ ├── parser.go
│ └── ... (19 files)
├── patches/ # Declarative source patches (5 files)
│ ├── 001-root-platform.patch
│ ├── 010-sm2-pqc-core.patch
│ ├── 020-pkcs-keys.patch
│ ├── 030-sm4-pem.patch
│ └── 100-extensions.patch
├── test-patches/ # Declarative test patches (2 files)
│ ├── 010-testenv-stub.patch
│ └── 020-envvars-abs-path.patch
└── gen_test_patches.go # Regenerates test patches from stdlib↔smx509 diff
Patch numbering convention:
001: Platform-specific root stubs (darwin/linux)010: SM2 + PQC core integration (x509.go, parser.go, verify.go)020: PKCS#8 key encoding (pkcs8.go, sec1.go)030: SM4 PEM encryption (pem_decrypt.go)100: Extension files (new files: cfca_csr.go, verify_digest.go, etc.)
Workflow Steps
Step 1: Create Upgrade Branch
git checkout develop
git pull origin develop
git checkout -b upgrade-smx509-go1.N
Step 2: Analyze stdlib Changes
Compare the new Go stdlib crypto/x509/ against the current baseline:
# Copy new stdlib files to a temp location for comparison
mkdir -p .tmp/new-stdlib
cp $GOROOT/src/crypto/x509/*.go .tmp/new-stdlib/
# Diff against committed baseline
diff -r scripts/smx509/baseline/ .tmp/new-stdlib/ --brief
Key questions to answer:
- Which files changed? (affects which patches may conflict)
- Are there new files? (may need new patch or extension)
- Are there deleted files? (unlikely for crypto/x509)
- What's the nature of changes? (API additions, refactoring, security fixes)
Step 3: Update Baseline
Replace the baseline with the new stdlib:
rm -rf scripts/smx509/baseline/
mkdir scripts/smx509/baseline/
pwsh scripts/smx509/sync.ps1 -TargetDir scripts/smx509/baseline -PackageName smx509 -NoPatch
Step 4: Try Clean Rebuild
# Clean smx509/ source files (keep extension files and testdata)
# Re-run sync with new baseline
pwsh scripts/smx509/sync.ps1 -TargetDir smx509 -PackageName smx509
Expected outcomes:
- ✅ All patches apply cleanly → skip to Step 7
- ❌ Patch conflicts → proceed to Step 5
Step 5: Resolve Patch Conflicts
For each failing patch, analyze the conflict:
5.1 Check Which Patches Fail
# The sync script will stop at the first failing patch
# Use -DryRun to pre-validate all patches:
pwsh scripts/smx509/sync.ps1 -TargetDir smx509 -PackageName smx509 -DryRun
5.2 Conflict Classification
| Type | Description | Resolution |
|---|---|---|
| Context shift | Surrounding lines moved but logic unchanged | Regenerate patch against new baseline |
| Semantic overlap | stdlib changed same code we patched | Manual merge: keep our SM2/PQC logic + adopt stdlib changes |
| New API | stdlib added new functions/types | Usually no conflict; may need to extend our patches |
| Refactoring | stdlib restructured code | Major patch rewrite needed |
5.3 Resolution Strategy
For each conflicted patch:
- Temporarily skip the patch and let the clean baseline copy through
- Manually apply our modifications to the new baseline file
- Regenerate the patch using
gen_patches.go
# After manual fixes in smx509/:
go run scripts/smx509/gen_patches.go
5.4 SM2/PQC/SM4 Invariants
When resolving conflicts, these MUST be preserved:
- SM2 OID constants:
oidSignatureSM2WithSM3,oidPublicKeySM2 - SM2WithSM3 SignatureAlgorithm: entry in
signatureAlgorithmDetails, SM2 branch incheckSignature - PQC algorithms:
MLDSA44/65/87,SLHDSASHA2128s— same pattern as SM2 - SM4 PEM:
PEMCipherSM4constant, SM4-CBC inrfc1423Algos - PKCS#8: SM2/ML-DSA/SLH-DSA branches in
ParsePKCS8PrivateKeyandMarshalPKCS8PrivateKey - verify_digest.go:
CheckSignatureWithDigestwith SHA1 rejection (matchescheckSignaturebehavior) - Import rewrites:
internal/godebug→github.com/emmansun/gmsm/internal/godebug, same forinternal/goos - Platform stubs:
root_darwin.gostays as stub (no CGO),root_linux.gostays simplified
Step 6: Sync Test Files
Test files are synced automatically by sync.ps1 (test file sync section). The script:
- Copies
*_test.gofrom stdlibcrypto/x509/tosmx509/ - Copies
testdata/from stdlib tosmx509/testdata/ - Renames
package x509→package smx509in all test files - Applies declarative test patches from
scripts/smx509/test-patches/
# Test-only sync (source files unchanged):
pwsh scripts/smx509/sync.ps1 -TestOnly
# Full sync (source + test):
pwsh scripts/smx509/sync.ps1
Current test patches:
010-testenv-stub.patch: new fileinternal_testenv.go(stub forinternal/testenv)020-envvars-abs-path.patch: fixes TestEnvVarsSSL_CERT_FILEto use absolute paths
After sync, verify custom test fixes are preserved:
root_unix_test.go: TestEnvVars usesfilepath.Join(tmpDir, testFile)for SSL_CERT_FILEinternal_testenv.go: stub forinternal/testenvreplacement- Package name: all
*_test.gomust havepackage smx509
If test patches need updating (stdlib changed the affected code):
# Manually fix test files in smx509/
# Then regenerate test patches:
go run scripts/smx509/gen_test_patches.go
Step 7: Validate
# Build all platforms
go build ./...
GOOS=linux go build ./...
GOOS=darwin go build ./...
GOOS=darwin GOARCH=arm64 go build ./...
# Run tests
go test ./smx509/...
go test ./pkcs7/... ./cfca/... ./pkcs8/...
# Full project test
go test ./...
Step 8: Regenerate Patches
# Source patches
go run scripts/smx509/gen_patches.go
# Test patches
go run scripts/smx509/gen_test_patches.go
Verify all 5 patches are regenerated:
001-root-platform.patch010-sm2-pqc-core.patch020-pkcs-keys.patch030-sm4-pem.patch100-extensions.patch
Step 9: Commit and Merge
git add -A
git commit -m "feat(smx509): upgrade to Go 1.N stdlib baseline
- Update .tmp/patch-baseline to Go 1.N
- Regenerate all smx509 patches
- Sync test files from stdlib
- Re-apply custom test fixes"
# After CI passes:
git checkout develop
git merge --no-ff upgrade-smx509-go1.N
git push origin develop
Common Pitfalls
gen_patches.gomust run from repo root — it usesos.Getwd()to resolve paths- Baseline is committed —
scripts/smx509/baseline/is version-controlled, update it explicitly on Go upgrade sync.ps1defaults tosmx509—-TargetDirand-PackageNamecan be omitted for standard usage- PowerShell git ExitCode=1 — PowerShell treats git stderr as error; check actual output, not exit code
- Test files NOT in source patches —
*_test.gochanges are intest-patches/, not inpatches/ internal/imports — any newinternal/*orcrypto/internal/*imports in stdlib must be added toImportRewriteMapinsync.ps1- Test patches must be regenerated — if stdlib changed
root_unix_test.goor test files referencingtestenv, rungen_test_patches.go
Decision Checklist
Before merging, verify:
- All patches apply cleanly on fresh baseline
-
go build ./...passes on linux/darwin/windows × amd64/arm64 -
go test ./smx509/...passes -
go test ./pkcs7/... ./cfca/... ./pkcs8/...passes -
gen_patches.gooutput matches committed patches (CI check) -
gen_test_patches.gooutput matches committed test-patches - Test files have correct
package smx509 - Custom test fixes are re-applied
-
ImportRewriteMapupdated if stdlib added new internal imports