Windows Settings rejected the Windows profile as invalid provisioning XML.
The CarrierControl WLAN schema allows authentication only up to WPA2; WPA3ENT is not in it (WLAN schema). The profile now uses WPA2 (Enterprise); Windows adds the PMF the WPA3-Enterprise network requires.
Otherwise aligned with Microsoft's signed Passpoint provisioning samples: Hotspot2 with DomainName only, <?xml version="1.0"?> in front.
All profiles are built with serializers instead of strings:
Android: PPS MO from encoding/xml structs, MIME multipart via mime/multipart (boundary renner-passpoint-profile; Android's ConfigParser reads part headers into a map).
Apple: .mobileconfig via howett.net/plist.
Windows: etree document; signature as an etree subtree, digest and SignedInfo canonicalized with goxmldsig's C14N 1.0 canonicalizer. Same form as Microsoft's samples (one transform, rsa-sha1, sha1 digest, chain in KeyInfo).
Tests parse every output (multipart reader, encoding/xml, plist, etree) and verify the Windows signature with goxmldsig.
Windows Settings rejected the Windows profile as invalid provisioning XML.
- The CarrierControl WLAN schema allows `authentication` only up to `WPA2`; `WPA3ENT` is not in it ([WLAN schema](https://learn.microsoft.com/en-us/uwp/schemas/mobilebroadbandschema/wlan/schema-root)). The profile now uses `WPA2` (Enterprise); Windows adds the PMF the WPA3-Enterprise network requires.
- Otherwise aligned with Microsoft's signed Passpoint provisioning samples: `Hotspot2` with `DomainName` only, `<?xml version="1.0"?>` in front.
- All profiles are built with serializers instead of strings:
- Android: PPS MO from `encoding/xml` structs, MIME multipart via `mime/multipart` (boundary `renner-passpoint-profile`; Android's ConfigParser reads part headers into a map).
- Apple: `.mobileconfig` via `howett.net/plist`.
- Windows: etree document; signature as an etree subtree, digest and SignedInfo canonicalized with goxmldsig's C14N 1.0 canonicalizer. Same form as Microsoft's samples (one transform, rsa-sha1, sha1 digest, chain in KeyInfo).
- Tests parse every output (multipart reader, encoding/xml, plist, etree) and verify the Windows signature with goxmldsig.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Windows Settings rejected the profile as invalid provisioning XML. The
CarrierControl WLAN schema allows authentication only up to WPA2; WPA3ENT is
not in it. The profile now uses WPA2 (Enterprise; Windows adds the PMF the
WPA3-Enterprise network requires) and follows Microsoft's signed Passpoint
samples otherwise: Hotspot2 with DomainName only, XML declaration in front
(outside the root, so not part of the digest).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot
changed title from fix(wlan-setup): Windows provisioning XML within the CarrierControl schema to fix(wlan-setup): schema-valid Windows profile, profiles built with serializers2026-11-05 14:18:18 +00:00
All three profiles were assembled from strings. They are now typed
documents:
- Android: PPS MO from encoding/xml structs; MIME multipart with
mime/multipart (boundary renner-passpoint-profile; Android's ConfigParser
reads part headers into a map, so their order does not matter).
- Apple: .mobileconfig marshalled with howett.net/plist.
- Windows: CarrierProvisioning built as an etree document; the signature is
an etree subtree, digest and SignedInfo canonicalized with goxmldsig's
C14N 1.0 canonicalizer (SignedInfo in place, so it carries the inherited
xmldsig namespace). Same form as Microsoft's samples: one transform,
rsa-sha1, sha1 digest, chain in KeyInfo.
Tests parse the outputs (multipart reader, encoding/xml, plist, etree) and
verify the Windows signature with goxmldsig.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🤖Automated review — model claude-opus-5-5, commit 9a1dc89326e9944019af2b8cab1c7d69f0cc78ec.
Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Request a review from review-bot again for a fresh one.
Summary
This PR fixes the Windows provisioning profile so it validates against the schema: authentication changes from WPA3ENT to WPA2, NAIRealm is dropped from Hotspot2, and an <?xml version="1.0"?> declaration now comes first. It also replaces the hand-built profile strings with real serializers: encoding/xml and mime/multipart for Android, howett.net/plist for Apple, and etree plus goxmldsig C14N for the Windows document and its signature. The change looks correct to me. Head CI (wlan-setup-test) is green, and the tests now parse every profile and check the Windows signature with goxmldsig's validator. The PR is mergeable, and the only other open PR (#1534, YouTrack) doesn't touch wlan-setup/.
⚪ Unverified
Android device acceptance.mime/multipart writes part headers in sorted order, so Content-Transfer-Encoding now comes before Content-Type. It also leaves the boundary parameter unquoted (multipart/mixed; boundary=renner-passpoint-profile) and adds CRLFs around boundaries differently from the old literal. The PR says Android's ConfigParser reads part headers into a map, so order shouldn't matter. I did not read AOSP to confirm this, and the tests only parse the output with Go's own reader. Worth one install on a real Android device.
Windows behaviour with WPA2 on a WPA3-Enterprise network. The claim that "Windows adds the PMF" when the profile says WPA2 and the network is WPA3-Enterprise describes current Windows behaviour. I can't check that from the repo. Same for dropping NAIRealm, which matches the PR's account of Microsoft's samples. Only a real Windows install will confirm both.
SignedInfo canonicalization. The comment at wlan-setup/sign.go ("In place, the canonicalizer adds the namespace SignedInfo inherits from Signature") depends on how goxmldsig's internals behave. Upstream goxmldsig's own signer uses etreeutils.NSDetatch before canonicalizing SignedInfo. Here, TestCallbackWindows and TestWindowsProfileDocument run ctx.Validate (server_test.go:550-557) and pass in CI, so the signature does verify with goxmldsig. Whether Windows' verifier agrees can only be shown on a device.
🟡 Suggestions
wlan-setup/sign.go (Sign): the function now changes the caller's *etree.Document by appending Signature to it. Today's only caller (server.go windows case) passes a fresh document, so this works. A line in the doc comment saying doc is modified would stop a future caller from reusing it. The test that signing twice fails partly covers this already.
🟢 Nits
wlan-setup/sign.go: const dsigNS = dsig.Namespace is now just an alias. Using dsig.Namespace directly would be slightly clearer.
wlan-setup/go.sum gains go.mod-only lines for go-flags, check.v1 and yaml.v1, which come in through howett.net/plist. This is expected; I'm only noting it.
Tests
Test coverage is good. The Android test parses the MIME envelope and parts and walks the PPS MO tree. The Apple test round-trips the plist, compares it with reflect.DeepEqual and checks that the password is XML-escaped. The Windows tests check the XML declaration, element values, namespaces, signature algorithms, the transform count and the certificate chain order, and confirm the digest equals the C14N output of the unsigned document and that re-signing is rejected. The remaining gap is device-level acceptance, which unit tests can't cover.
Recommendation
Looks reasonable — no blocking issues; comments above are optional.
🤖 **Automated review** — model `claude-opus-5-5`, commit `9a1dc89326e9944019af2b8cab1c7d69f0cc78ec`.
> Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Request a review from `review-bot` again for a fresh one.
---
## Summary
This PR fixes the Windows provisioning profile so it validates against the schema: `authentication` changes from `WPA3ENT` to `WPA2`, `NAIRealm` is dropped from `Hotspot2`, and an `<?xml version="1.0"?>` declaration now comes first. It also replaces the hand-built profile strings with real serializers: `encoding/xml` and `mime/multipart` for Android, `howett.net/plist` for Apple, and etree plus goxmldsig C14N for the Windows document and its signature. The change looks correct to me. Head CI (`wlan-setup-test`) is green, and the tests now parse every profile and check the Windows signature with goxmldsig's validator. The PR is mergeable, and the only other open PR (#1534, YouTrack) doesn't touch `wlan-setup/`.
## ⚪ Unverified
- **Android device acceptance.** `mime/multipart` writes part headers in sorted order, so `Content-Transfer-Encoding` now comes before `Content-Type`. It also leaves the boundary parameter unquoted (`multipart/mixed; boundary=renner-passpoint-profile`) and adds CRLFs around boundaries differently from the old literal. The PR says Android's ConfigParser reads part headers into a map, so order shouldn't matter. I did not read AOSP to confirm this, and the tests only parse the output with Go's own reader. Worth one install on a real Android device.
- **Windows behaviour with `WPA2` on a WPA3-Enterprise network.** The claim that "Windows adds the PMF" when the profile says `WPA2` and the network is WPA3-Enterprise describes current Windows behaviour. I can't check that from the repo. Same for dropping `NAIRealm`, which matches the PR's account of Microsoft's samples. Only a real Windows install will confirm both.
- **SignedInfo canonicalization.** The comment at `wlan-setup/sign.go` ("In place, the canonicalizer adds the namespace SignedInfo inherits from Signature") depends on how goxmldsig's internals behave. Upstream goxmldsig's own signer uses `etreeutils.NSDetatch` before canonicalizing SignedInfo. Here, `TestCallbackWindows` and `TestWindowsProfileDocument` run `ctx.Validate` (`server_test.go:550-557`) and pass in CI, so the signature does verify with goxmldsig. Whether Windows' verifier agrees can only be shown on a device.
## 🟡 Suggestions
- `wlan-setup/sign.go` (`Sign`): the function now changes the caller's `*etree.Document` by appending `Signature` to it. Today's only caller (`server.go` windows case) passes a fresh document, so this works. A line in the doc comment saying `doc` is modified would stop a future caller from reusing it. The test that signing twice fails partly covers this already.
## 🟢 Nits
- `wlan-setup/sign.go`: `const dsigNS = dsig.Namespace` is now just an alias. Using `dsig.Namespace` directly would be slightly clearer.
- `wlan-setup/go.sum` gains `go.mod`-only lines for `go-flags`, `check.v1` and `yaml.v1`, which come in through `howett.net/plist`. This is expected; I'm only noting it.
## Tests
Test coverage is good. The Android test parses the MIME envelope and parts and walks the PPS MO tree. The Apple test round-trips the plist, compares it with `reflect.DeepEqual` and checks that the password is XML-escaped. The Windows tests check the XML declaration, element values, namespaces, signature algorithms, the transform count and the certificate chain order, and confirm the digest equals the C14N output of the unsigned document and that re-signing is rejected. The remaining gap is device-level acceptance, which unit tests can't cover.
## Recommendation
Looks reasonable — no blocking issues; comments above are optional.
<!-- pr-review:sha=9a1dc89326e9944019af2b8cab1c7d69f0cc78ec -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Windows Settings rejected the Windows profile as invalid provisioning XML.
authenticationonly up toWPA2;WPA3ENTis not in it (WLAN schema). The profile now usesWPA2(Enterprise); Windows adds the PMF the WPA3-Enterprise network requires.Hotspot2withDomainNameonly,<?xml version="1.0"?>in front.encoding/xmlstructs, MIME multipart viamime/multipart(boundaryrenner-passpoint-profile; Android's ConfigParser reads part headers into a map)..mobileconfigviahowett.net/plist.🤖 Generated with Claude Code
fix(wlan-setup): Windows provisioning XML within the CarrierControl schemato fix(wlan-setup): schema-valid Windows profile, profiles built with serializers🤖 Automated review — model
claude-opus-5-5, commit9a1dc89326e9944019af2b8cab1c7d69f0cc78ec.Summary
This PR fixes the Windows provisioning profile so it validates against the schema:
authenticationchanges fromWPA3ENTtoWPA2,NAIRealmis dropped fromHotspot2, and an<?xml version="1.0"?>declaration now comes first. It also replaces the hand-built profile strings with real serializers:encoding/xmlandmime/multipartfor Android,howett.net/plistfor Apple, and etree plus goxmldsig C14N for the Windows document and its signature. The change looks correct to me. Head CI (wlan-setup-test) is green, and the tests now parse every profile and check the Windows signature with goxmldsig's validator. The PR is mergeable, and the only other open PR (#1534, YouTrack) doesn't touchwlan-setup/.⚪ Unverified
mime/multipartwrites part headers in sorted order, soContent-Transfer-Encodingnow comes beforeContent-Type. It also leaves the boundary parameter unquoted (multipart/mixed; boundary=renner-passpoint-profile) and adds CRLFs around boundaries differently from the old literal. The PR says Android's ConfigParser reads part headers into a map, so order shouldn't matter. I did not read AOSP to confirm this, and the tests only parse the output with Go's own reader. Worth one install on a real Android device.WPA2on a WPA3-Enterprise network. The claim that "Windows adds the PMF" when the profile saysWPA2and the network is WPA3-Enterprise describes current Windows behaviour. I can't check that from the repo. Same for droppingNAIRealm, which matches the PR's account of Microsoft's samples. Only a real Windows install will confirm both.wlan-setup/sign.go("In place, the canonicalizer adds the namespace SignedInfo inherits from Signature") depends on how goxmldsig's internals behave. Upstream goxmldsig's own signer usesetreeutils.NSDetatchbefore canonicalizing SignedInfo. Here,TestCallbackWindowsandTestWindowsProfileDocumentrunctx.Validate(server_test.go:550-557) and pass in CI, so the signature does verify with goxmldsig. Whether Windows' verifier agrees can only be shown on a device.🟡 Suggestions
wlan-setup/sign.go(Sign): the function now changes the caller's*etree.Documentby appendingSignatureto it. Today's only caller (server.gowindows case) passes a fresh document, so this works. A line in the doc comment sayingdocis modified would stop a future caller from reusing it. The test that signing twice fails partly covers this already.🟢 Nits
wlan-setup/sign.go:const dsigNS = dsig.Namespaceis now just an alias. Usingdsig.Namespacedirectly would be slightly clearer.wlan-setup/go.sumgainsgo.mod-only lines forgo-flags,check.v1andyaml.v1, which come in throughhowett.net/plist. This is expected; I'm only noting it.Tests
Test coverage is good. The Android test parses the MIME envelope and parts and walks the PPS MO tree. The Apple test round-trips the plist, compares it with
reflect.DeepEqualand checks that the password is XML-escaped. The Windows tests check the XML declaration, element values, namespaces, signature algorithms, the transform count and the certificate chain order, and confirm the digest equals the C14N output of the unsigned document and that re-signing is rejected. The remaining gap is device-level acceptance, which unit tests can't cover.Recommendation
Looks reasonable — no blocking issues; comments above are optional.