Skip to content

Commit 8536b1c

Browse files
katipallyCopilot
andcommitted
ci: auto-reconstruct PEM with 64-char line wrapping from flat-pasted secret
GitHub Secrets strips newlines from multiline pastes, producing a flat PEM that CryptoKit rejects. Workflow now always extracts the base64 payload, re-wraps it at 64 chars via fold(1), and re-emits proper header/footer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e4be56b commit 8536b1c

1 file changed

Lines changed: 34 additions & 37 deletions

File tree

.github/workflows/ios-testflight.yml

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -132,55 +132,52 @@ jobs:
132132
133133
# Auto-detect: raw PEM (starts with -----BEGIN) or base64-encoded.
134134
if printf '%s' "$API_KEY" | head -c 20 | grep -q "^-----BEGIN"; then
135-
# Raw PEM: strip CR and trailing whitespace per line.
136-
printf '%s' "$API_KEY" | tr -d '\r' | sed 's/[[:space:]]*$//' > "$RAW_FILE"
135+
# Raw PEM: strip CR, write as-is.
136+
printf '%s' "$API_KEY" | tr -d '\r' > "$RAW_FILE"
137137
echo "Detected raw PEM format"
138138
else
139-
# Base64-encoded: decode then strip CR.
139+
# Base64-encoded outer wrapper: decode it first, strip CR.
140140
printf '%s' "$API_KEY" | tr -d '\r ' | base64 -D > "$RAW_FILE"
141141
echo "Detected base64-encoded format"
142142
fi
143143
144-
# Ensure file ends with exactly one newline.
145-
if [[ "$(tail -c 1 "$RAW_FILE" | xxd -p)" != "0a" ]]; then
146-
printf '\n' >> "$RAW_FILE"
144+
# ── CANONICAL PEM RECONSTRUCTION ─────────────────────────────────────
145+
# GitHub Secrets often strips newlines when pasting multiline values,
146+
# resulting in: -----BEGIN PRIVATE KEY-----<base64>-----END PRIVATE KEY-----
147+
# CryptoKit (used by xcodebuild) requires base64 wrapped at 64 chars.
148+
# We always reconstruct the PEM regardless, so we handle both cases.
149+
RAW_CONTENT=$(cat "$RAW_FILE" | tr -d '\r')
150+
# Extract type from first "-----BEGIN <TYPE>-----" token
151+
KEY_TYPE=$(printf '%s' "$RAW_CONTENT" | grep -o -- "-----BEGIN [^-]*-----" | head -1 | sed 's/-----BEGIN //;s/-----//')
152+
if [[ -z "$KEY_TYPE" ]]; then
153+
echo "::error::Could not detect PEM type from key content"
154+
exit 1
155+
fi
156+
echo "PEM type detected: ${KEY_TYPE}"
157+
# Extract raw base64 payload (strip all PEM headers/footers and whitespace)
158+
B64=$(printf '%s' "$RAW_CONTENT" | grep -v "^-----" | tr -d '\n\r ')
159+
if [[ -z "$B64" ]]; then
160+
echo "::error::Empty base64 payload in key"
161+
exit 1
147162
fi
163+
# Reconstruct with standard 64-char line wrapping
164+
{
165+
printf -- "-----BEGIN %s-----\n" "$KEY_TYPE"
166+
printf '%s' "$B64" | fold -w 64
167+
printf "\n-----END %s-----\n" "$KEY_TYPE"
168+
} > "$KEY_FILE"
169+
echo "PEM reconstructed: $(wc -l < "$KEY_FILE") lines, $(wc -c < "$KEY_FILE" | tr -d ' ') bytes"
170+
rm -f "$RAW_FILE"
148171
149-
# Normalize via openssl so CryptoKit gets a canonical PEM structure.
150-
# Use || RC=$? to prevent set -e from triggering — we handle the failure.
151-
# Try 1: unencrypted PKCS8 (standard Apple API key format)
172+
# Validate via openssl (non-fatal — CryptoKit may still accept it)
152173
OPENSSL_RC=0
153-
OPENSSL_OUT="$(openssl pkey -in "$RAW_FILE" -out "$KEY_FILE" 2>&1)" || OPENSSL_RC=$?
174+
OPENSSL_OUT="$(openssl pkey -in "$KEY_FILE" -noout 2>&1)" || OPENSSL_RC=$?
154175
if [ $OPENSSL_RC -eq 0 ]; then
155-
echo "Key normalized via openssl pkey (unencrypted)"
176+
echo "openssl validation: PASSED"
156177
else
157-
echo "openssl pkey failed (rc=${OPENSSL_RC}): ${OPENSSL_OUT}"
158-
# Try 2: encrypted PKCS8 with empty passphrase → strip encryption
159-
OPENSSL_RC2=0
160-
OPENSSL_OUT2="$(openssl pkey -in "$RAW_FILE" -out "$KEY_FILE" -passin pass: 2>&1)" || OPENSSL_RC2=$?
161-
if [ $OPENSSL_RC2 -eq 0 ]; then
162-
echo "Key decrypted (empty passphrase) and normalized"
163-
else
164-
echo "openssl pkey (empty passphrase) failed (rc=${OPENSSL_RC2}): ${OPENSSL_OUT2}"
165-
# Try 3: explicit pkcs8 decode with empty passphrase
166-
OPENSSL_RC3=0
167-
OPENSSL_OUT3="$(openssl pkcs8 -nocrypt -in "$RAW_FILE" -out "$KEY_FILE" -passin pass: 2>&1)" || OPENSSL_RC3=$?
168-
if [ $OPENSSL_RC3 -eq 0 ]; then
169-
echo "Key normalized via openssl pkcs8 (empty passphrase)"
170-
else
171-
echo "openssl pkcs8 (empty passphrase) also failed (rc=${OPENSSL_RC3}): ${OPENSSL_OUT3}"
172-
cp "$RAW_FILE" "$KEY_FILE"
173-
echo "::warning::All openssl normalization attempts failed — using raw key as-is"
174-
echo "::warning::If xcodebuild fails with invalidPEMDocument, the key in APP_STORE_CONNECT_PRIVATE_KEY may be corrupted."
175-
echo "::warning::Verify locally: openssl pkey -in AuthKey_*.p8 -noout"
176-
echo "::warning::If that fails, re-download the key from App Store Connect and update the secret."
177-
fi
178-
fi
178+
echo "::warning::openssl validation: ${OPENSSL_OUT}"
179+
echo "::warning::Key may still work with xcodebuild — proceeding"
179180
fi
180-
# Print PEM header for diagnostics (not secret content — header is masked)
181-
HEADER=$(head -1 "$RAW_FILE")
182-
echo "PEM header: '${HEADER}'"
183-
rm -f "$RAW_FILE"
184181
chmod 600 "$KEY_FILE"
185182
186183
# Validate written file

0 commit comments

Comments
 (0)