Skip to content

Fix "400 bad request" from SCEP PKIOperation when base64 message contains "+"#43319

Open
sergey-cheperis wants to merge 10 commits intofleetdm:mainfrom
sergey-cheperis:fix/scep-pkiop-plus-sign-url-query
Open

Fix "400 bad request" from SCEP PKIOperation when base64 message contains "+"#43319
sergey-cheperis wants to merge 10 commits intofleetdm:mainfrom
sergey-cheperis:fix/scep-pkiop-plus-sign-url-query

Conversation

@sergey-cheperis
Copy link
Copy Markdown

@sergey-cheperis sergey-cheperis commented Apr 9, 2026

Related issue: none

Problem

Apple MacOS devices fail SCEP enrollment with a 400. The proxy sees the request arrive with + signs in the base64 payload:

request_uri: /mdm/apple/scep?operation=PKIOperation&message=MIA...MokYg+nl4TGkZi...k0+BJ/...

Fleet logs show those + signs are interpreted as spaces, and the decode fails:

component=http-mdm-apple-scep method=GET status=400
err="failed to base64 decode message: illegal base64 data at input byte 375:
     ...MokYg nl4TGkZi...k0 BJ/..."

Root cause

message() in server/mdm/scep/server/transport.go reads the query parameter via r.URL.Query(), which internally calls url.QueryUnescape and converts every + to a space.

The bug is present on main as of 2026-04-09.

Go's url.Query() (via url.QueryUnescape) follows the
application/x-www-form-urlencoded convention and converts every
literal '+' in a query string to a space. The SCEP 'message'
parameter for PKIOperation carries standard base64-encoded DER data
where '+' is a valid and common character.

iOS sends '+' unencoded in the URL (correct per RFC 3986 – '+' is
only special in form-encoded request bodies), so every '+' in the
certificate payload becomes a space before base64.StdEncoding.
DecodeString is called, causing a 400 "illegal base64 data" error
and preventing devices from enrolling.

The fix inserts strings.ReplaceAll(msg2, " ", "+") immediately
before the decode call. Standard base64 has no spaces, so every
space at that point is a '+' that was corrupted by url.Query().
Replacing it back is safe and correct.

Also present on the main branch as of 2026-04-09.
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 66.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 66.85%. Comparing base (7b90f2d) to head (795cb96).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
server/mdm/scep/server/transport.go 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #43319      +/-   ##
==========================================
- Coverage   66.86%   66.85%   -0.02%     
==========================================
  Files        2587     2589       +2     
  Lines      207476   207428      -48     
  Branches     9314     9202     -112     
==========================================
- Hits       138737   138670      -67     
- Misses      56109    56127      +18     
- Partials    12630    12631       +1     
Flag Coverage Δ
backend 68.64% <66.66%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • :ai

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 519009dd-df8f-4283-a343-bf00bf0d0768

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sergey-cheperis sergey-cheperis marked this pull request as ready for review April 9, 2026 14:29
@sergey-cheperis sergey-cheperis requested a review from a team as a code owner April 9, 2026 14:29
Copilot AI review requested due to automatic review settings April 9, 2026 14:29
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@sergey-cheperis sergey-cheperis changed the title Fix SCEP PKIOperation 400: url.Query() corrupts base64 '+' as space, breaking iOS/macOS MDM enrollment Fix "400 bad request" from SCEP PKIOperation when base64 message contains "+" Apr 9, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes Apple SCEP PKIOperation GET handling where + characters in base64 payloads are interpreted as spaces, causing base64 decode failures during iOS/macOS MDM enrollment.

Changes:

  • Replace spaces with + before base64 decoding the message query parameter for PKIOperation.
  • Add a release note entry describing the SCEP decoding fix.

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 2 comments.

File Description
server/mdm/scep/server/transport.go Adjusts PKIOperation query message handling prior to base64 decode.
changes/43319-fix-scep-pkiop-url-query-plus-sign Adds release note for the SCEP decoding fix.
Comments suppressed due to low confidence (1)

server/mdm/scep/server/transport.go:192

  • The 400 error returned on base64 decode failure includes the full msg2 payload. That value comes from the request and can be very large; echoing it back to the client and into logs via http.Error is both noisy and risks exposing request contents unnecessarily. Consider removing msg2 from the error message or truncating/sanitizing it (e.g., include only length / a short prefix).
			decoded, err := base64.StdEncoding.DecodeString(msg2)
			if err != nil {
				return nil, &BadRequestError{Message: fmt.Sprintf("failed to base64 decode message: %s: %s", err.Error(), msg2)}
			}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sergey-cheperis sergey-cheperis marked this pull request as ready for review April 9, 2026 15:30
return nil, &BadRequestError{Message: "missing PKIOperation message"}
}

msg2, err := url.PathUnescape(msg)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

PathUnescape() removed because Query() already provides unescaped values.

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.

3 participants