-
-
Notifications
You must be signed in to change notification settings - Fork 235
Description
I am trying to set up a CI/CD workflow for my Flutter iOS app on a macos-latest
runner. The workflow consistently fails at the Setup iOS Credentials
step with the error unable to load private key
followed by PEM routines:PEM_read_bio_ex:bad base64 decode
.
I have confirmed the following troubleshooting steps:
- Workflow Syntax: The
openssl
command is correctly formatted with-passin
and-passout
flags. - Secret Content: The
IOS_BUILD_PRIVATE_KEY_BASE64
secret has been regenerated multiple times to ensure it is a clean, single-line string of base64-encoded data, without any extra metadata. - Key Type: I have tried both
-----BEGIN RSA PRIVATE KEY-----
and-----BEGIN PRIVATE KEY-----
headers.
The error message bad base64 decode
strongly suggests that the decoded data itself is not a valid base64 stream, even though it was generated from a valid PEM key. This points to a potential incompatibility between the base64 encoding/decoding process or the OpenSSL version on the runner.
Workflow File (flutter_ios.yml):
name: Flutter iOS CI
on:
push:
branches:
- main
jobs:
build_ios:
name: Build iOS App
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: "stable"
- name: Clean Flutter Project
run: flutter clean
- run: flutter pub get
- name: Setup iOS Credentials
env:
BUILD_PRIVATE_KEY_BASE64: ${{ secrets.IOS_BUILD_PRIVATE_KEY_BASE64 }}
BUILD_CERTIFICATE_BASE64_RAW: ${{ secrets.IOS_BUILD_CERTIFICATE_BASE64_RAW }}
BUILD_CERTIFICATE_PASSWORD: ${{ secrets.IOS_BUILD_CERTIFICATE_PASSWORD }}
PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
run: |
# Create and unlock a temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
security create-keychain -p "" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security unlock-keychain -p "" "$KEYCHAIN_PATH"
# Decode and install the provisioning profile
PROVISIONING_PROFILE_PATH="$HOME/Library/MobileDevice/Provisioning Profiles/RoomEase_CICD_Ad_Hoc.mobileprovision"
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
echo "$PROVISIONING_PROFILE_BASE64" | base64 --decode > "$PROVISIONING_PROFILE_PATH"
# Set locale to C to avoid 'Illegal byte sequence'
export LC_ALL=C
PRIVATE_KEY_CONTENT=$(echo "$BUILD_PRIVATE_KEY_BASE64" | base64 -d | tr -d '[:cntrl:]' | tr -d ' ')
# CRITICAL CHANGE: Use generic PRIVATE KEY headers
printf "%s\n" "-----BEGIN PRIVATE KEY-----" > private.key
printf "%s" "$PRIVATE_KEY_CONTENT" >> private.key
printf "\n%s\n" "-----END PRIVATE KEY-----" >> private.key
# Reconstruct the certificate file using a direct pipe to openssl
echo "$BUILD_CERTIFICATE_BASE64_RAW" | openssl base64 -d -A | openssl x509 -inform DER -outform PEM -out certificate.cer
# Use the macOS OpenSSL to combine them into a p12 file
openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.cer -passin pass:"$BUILD_CERTIFICATE_PASSWORD" -passout pass:"$BUILD_CERTIFICATE_PASSWORD"
# Import the newly created p12 file into the keychain
security import certificate.p12 -k "$KEYCHAIN_PATH" -P "$BUILD_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
# Clean up temporary files
rm private.key certificate.cer certificate.p12
# Verify that the certificate was imported correctly
echo "Verifying certificates in keychain..."
security find-identity -p codesigning -v "$KEYCHAIN_PATH"
- name: Final Fix - Project Configuration
run: |
# Correct the signing identity in the project configuration
sed -i '' -E 's/"iPhone Developer"/"Apple Distribution: Sohan Lal Khazan Chand (DHG8JNTHZL)"/g' ios/Runner.xcodeproj/project.pbxproj
# Set the correct provisioning profile specifier
sed -i '' -E 's/PROVISIONING_PROFILE_SPECIFIER = RoomEase Ad Hoc Testing New;/PROVISIONING_PROFILE_SPECIFIER = "RoomEase_CICD_Ad_Hoc";/' ios/Runner.xcodeproj/project.pbxproj
echo "Verification: Printing code signing identity after fix."
grep -C 3 'CODE_SIGN_IDENTITY' ios/Runner.xcodeproj/project.pbxproj
- name: Build and Sign the IPA
env:
TEAM_ID: DHG8JNTHZL
run: flutter build ipa --release --export-options-plist ./ios/ExportOptions.plist
- name: Upload iOS App Artifact
if: success()
uses: actions/upload-artifact@v4
with:
name: ios-release
path: build/ios/ipa/*.ipa
Run Logs:
##[debug]Evaluating: secrets.IOS_BUILD_PRIVATE_KEY_BASE64
##[debug]Evaluating Index:
##[debug]..Evaluating secrets:
##[debug]..=> Object
##[debug]..Evaluating String:
##[debug]..=> 'IOS_BUILD_PRIVATE_KEY_BASE64'
##[debug]=> '***'
##[debug]Result: '***'
##[debug]Evaluating: secrets.IOS_BUILD_CERTIFICATE_BASE64_RAW
##[debug]Evaluating Index:
##[debug]..Evaluating secrets:
##[debug]..=> Object
##[debug]..Evaluating String:
##[debug]..=> 'IOS_BUILD_CERTIFICATE_BASE64_RAW'
##[debug]=> '***'
##[debug]Result: '***'
##[debug]Evaluating: secrets.IOS_BUILD_CERTIFICATE_PASSWORD
##[debug]Evaluating Index:
##[debug]..Evaluating secrets:
##[debug]..=> Object
##[debug]..Evaluating String:
##[debug]..=> 'IOS_BUILD_CERTIFICATE_PASSWORD'
##[debug]=> '***'
##[debug]Result: '***'
##[debug]Evaluating: secrets.IOS_PROVISIONING_PROFILE_BASE64
##[debug]Evaluating Index:
##[debug]..Evaluating secrets:
##[debug]..=> Object
##[debug]..Evaluating String:
##[debug]..=> 'IOS_PROVISIONING_PROFILE_BASE64'
##[debug]=> '***'
##[debug]Result: '***'
##[debug]Evaluating condition for step: 'Setup iOS Credentials'
##[debug]Evaluating: success()
##[debug]Evaluating success:
##[debug]=> true
##[debug]Result: true
##[debug]Starting: Setup iOS Credentials
##[debug]Loading inputs
##[debug]Loading env
Run # Create and unlock a temporary keychain
# Create and unlock a temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
security create-keychain -p "" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security unlock-keychain -p "" "$KEYCHAIN_PATH"
# Decode and install the provisioning profile
PROVISIONING_PROFILE_PATH="$HOME/Library/MobileDevice/Provisioning Profiles/RoomEase_CICD_Ad_Hoc.mobileprovision"
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
echo "$PROVISIONING_PROFILE_BASE64" | base64 --decode > "$PROVISIONING_PROFILE_PATH"
# Set locale to C to avoid 'Illegal byte sequence'
export LC_ALL=C
PRIVATE_KEY_CONTENT=$(echo "$BUILD_PRIVATE_KEY_BASE64" | base64 -d | tr -d '[:cntrl:]' | tr -d ' ')
# π CRITICAL CHANGE: Use generic PRIVATE KEY headers
printf "%s\n" "-----BEGIN PRIVATE KEY-----" > private.key
printf "%s" "$PRIVATE_KEY_CONTENT" >> private.key
printf "\n%s\n" "-----END PRIVATE KEY-----" >> private.key
# Reconstruct the certificate file using a direct pipe to openssl
echo "$BUILD_CERTIFICATE_BASE64_RAW" | openssl base64 -d -A | openssl x509 -inform DER -outform PEM -out certificate.cer
# Use the macOS OpenSSL to combine them into a p12 file
openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.cer -passin pass:"$BUILD_CERTIFICATE_PASSWORD" -passout pass:"$BUILD_CERTIFICATE_PASSWORD"
# Import the newly created p12 file into the keychain
security import certificate.p12 -k "$KEYCHAIN_PATH" -P "$BUILD_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
# Clean up temporary files
rm private.key certificate.cer certificate.p12
# Verify that the certificate was imported correctly
echo "Verifying certificates in keychain..."
security find-identity -p codesigning -v "$KEYCHAIN_PATH"
shell: /bin/bash -e {0}
env:
FLUTTER_ROOT: /Users/runner/hostedtoolcache/flutter/stable-3.32.8-arm64
PUB_CACHE: /Users/runner/.pub-cache
BUILD_PRIVATE_KEY_BASE64: ***
BUILD_CERTIFICATE_BASE64_RAW: ***
BUILD_CERTIFICATE_PASSWORD: ***
PROVISIONING_PROFILE_BASE64: ***
##[debug]/bin/bash -e /Users/runner/work/_temp/75bcd883-f8d6-4bd4-8d3b-653c00d75a15.sh
unable to load private key
8606519168:error:09091064:PEM routines:PEM_read_bio_ex:bad base64 decode:crypto/pem/pem_lib.c:949:
Error: Process completed with exit code 1.
##[debug]Finishing: Setup iOS Credentials
Environment:
- Runner:
macos-latest
- Flutter Version:
stable
- Local Machine OS (used for key generation): Windows
What I Expect:
I expect the workflow to successfully decode the IOS_BUILD_PRIVATE_KEY_BASE64
secret into a valid private.key
file and proceed with the code signing process.
What Actually Happens:
The openssl pkcs12
command fails with the error unable to load private key
and PEM routines:PEM_read_bio_ex:bad base64 decode
, causing the job to terminate.
Potential Clue:
The base64 string was generated on a Windows machine. It's possible there is a subtle difference in the encoding/decoding that is causing a problem on the macOS runner.