diff --git a/README.rcodesign.md b/README.rcodesign.md new file mode 100644 index 0000000..d7be186 --- /dev/null +++ b/README.rcodesign.md @@ -0,0 +1,288 @@ +# Using applesign with rcodesign + +This document explains how to use applesign with +[rcodesign](https://github.com/indygreg/apple-platform-rs), a pure Rust +implementation of Apple code signing that works on Linux, Windows, and macOS. + +## Overview + +rcodesign is an open-source alternative to Apple's native `codesign` tool that +provides: + +- Cross-platform code signing (Linux, Windows, macOS) +- Pure Rust implementation (no Apple dependencies) +- Support for Mach-O binaries, app bundles, installers, and disk images +- Notarization support + +## Installation + +### Install rcodesign + +#### Option 1: Using GitHub Action (Recommended for CI) + +```yaml +- name: Setup rcodesign + uses: ./.github/actions/action-setup-rcodesign + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + version: "0.22.0" +``` + +#### Option 2: Manual Installation + +```bash +# Download from releases +curl -L https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign/0.22.0/apple-codesign-0.22.0-x86_64-apple-darwin.tar.gz | tar xz +sudo mv rcodesign /usr/local/bin/ + +# Or install from source +cargo install --git https://github.com/indygreg/apple-platform-rs --bin rcodesign apple-codesign +``` + +### Install applesign + +```bash +npm install -g applesign +``` + +## Usage + +### Basic Usage with rcodesign + +```bash +# Use rcodesign instead of Apple's codesign +applesign --codesign-tool=rcodesign -m embedded.mobileprovision target.ipa + +# With explicit certificate file +applesign --codesign-tool=rcodesign -i /path/to/certificate.p12 -m embedded.mobileprovision target.ipa + +# With PEM certificate +applesign --codesign-tool=rcodesign -i /path/to/certificate.pem -m embedded.mobileprovision target.ipa +``` + +### Certificate Formats + +rcodesign supports multiple certificate formats: + +#### P12 Certificate (Recommended) + +```bash +applesign --codesign-tool=rcodesign -i /path/to/developer.p12 -m embedded.mobileprovision target.ipa +``` + +#### PEM Certificate + +```bash +applesign --codesign-tool=rcodesign -i /path/to/developer.pem -m embedded.mobileprovision target.ipa +``` + +#### Certificate Fingerprint + +```bash +applesign --codesign-tool=rcodesign -i "SHA256:ABC123..." -m embedded.mobileprovision target.ipa +``` + +### Advanced Options + +```bash +# Clone entitlements from provisioning profile +applesign --codesign-tool=rcodesign -c -m embedded.mobileprovision target.ipa + +# Custom entitlements file +applesign --codesign-tool=rcodesign -e custom.entitlements -m embedded.mobileprovision target.ipa + +# Remove WatchApp and plugins +applesign --codesign-tool=rcodesign -w -p -m embedded.mobileprovision target.ipa + +# Verify after signing +applesign --codesign-tool=rcodesign -v -m embedded.mobileprovision target.ipa + +# Debug mode +applesign --codesign-tool=rcodesign -d debug.json -m embedded.mobileprovision target.ipa +``` + +## Certificate Preparation + +### Converting from Apple Keychain to P12 + +```bash +# Export certificate from keychain +security find-certificate -c "iPhone Developer" -p > devcert.pem +security find-certificate -c "iPhone Developer" -c > devcert.key + +# Convert to P12 +openssl pkcs12 -export -inkey devcert.key -in devcert.pem -out developer.p12 +``` + +### Converting from Apple Keychain to PEM + +```bash +# Export certificate and key +security find-certificate -c "iPhone Developer" -p > certificate.pem +security find-certificate -c "iPhone Developer" -c > private-key.pem + +# Combine into single PEM +cat certificate.pem private-key.pem > developer.pem +``` + +## Differences from Apple codesign + +### Key Differences + +1. **No Keychain Integration**: rcodesign doesn't use macOS keychain directly +2. **Cross-Platform**: Works on Linux and Windows, not just macOS +3. **Certificate Format**: Supports PEM and P12 files directly +4. **No Notarization Integration**: Separate notarization step required + +### Limitations + +- No automatic certificate discovery from keychain +- Must specify certificate file explicitly +- Some advanced codesign flags may not be supported +- Keychain-related options are ignored + +## Troubleshooting + +### Common Issues + +#### "Certificate not found" + +```bash +# Ensure certificate file exists and is readable +ls -la /path/to/certificate.p12 + +# Try with absolute path +applesign --codesign-tool=rcodesign -i /full/path/to/certificate.p12 -m embedded.mobileprovision target.ipa +``` + +#### "Invalid certificate format" + +```bash +# Verify certificate format +file /path/to/certificate.p12 +# Should show: data + +# For PEM files +file /path/to/certificate.pem +# Should show: ASCII text +``` + +#### "rcodesign not found" + +```bash +# Check if rcodesign is in PATH +which rcodesign + +# Or use full path +applesign --codesign-tool=/usr/local/bin/rcodesign -m embedded.mobileprovision target.ipa +``` + +### Debug Mode + +Enable debug mode to see detailed rcodesign commands: + +```bash +applesign --codesign-tool=rcodesign -d debug.json -m embedded.mobileprovision target.ipa +``` + +## CI/CD Integration + +### GitHub Actions Example + +```yaml +name: Sign with rcodesign +on: [push] + +jobs: + sign: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: "18" + + - name: Install applesign + run: npm install -g applesign + + - name: Setup rcodesign + uses: ./.github/actions/action-setup-rcodesign + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Sign IPA + env: + CERTIFICATE: ${{ secrets.DEVELOPER_CERTIFICATE }} + run: | + echo "$CERTIFICATE" | base64 -d > developer.p12 + applesign --codesign-tool=rcodesign -i developer.p12 -m embedded.mobileprovision target.ipa +``` + +### Docker Example + +```dockerfile +FROM ubuntu:22.04 + +# Install dependencies +RUN apt-get update && apt-get install -y \ + nodejs \ + npm \ + curl \ + unzip + +# Install rcodesign +RUN curl -L https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign/0.22.0/apple-codesign-0.22.0-x86_64-unknown-linux-musl.tar.gz | tar xz \ + && mv rcodesign /usr/local/bin/ + +# Install applesign +RUN npm install -g applesign + +WORKDIR /app +COPY . . + +# Sign application +CMD applesign --codesign-tool=rcodesign -i certificate.p12 -m embedded.mobileprovision app.ipa +``` + +## Migration from Apple codesign + +### Before (Apple codesign) + +```bash +applesign -i "iPhone Developer: John Doe (ABC123DEF)" -m embedded.mobileprovision target.ipa +``` + +### After (rcodesign) + +```bash +# Step 1: Export certificate to P12 (one-time) +security find-certificate -c "iPhone Developer: John Doe (ABC123DEF)" -p > cert.pem +security find-certificate -c "iPhone Developer: John Doe (ABC123DEF)" -c > key.pem +openssl pkcs12 -export -inkey key.pem -in cert.pem -out developer.p12 + +# Step 2: Use with rcodesign +applesign --codesign-tool=rcodesign -i developer.p12 -m embedded.mobileprovision target.ipa +``` + +## Additional Resources + +- [rcodesign Documentation](https://gregoryszorc.com/docs/apple-codesign/main/) +- [apple-platform-rs GitHub](https://github.com/indygreg/apple-platform-rs) +- [applesign GitHub](https://github.com/nowsecure/node-applesign) +- [Apple Code Signing Guide](https://developer.apple.com/support/code-signing/) + +## Contributing + +To contribute to rcodesign integration in applesign: + +1. Test with different certificate formats +2. Report issues with rcodesign compatibility +3. Submit pull requests for additional rcodesign features +4. Update documentation for new use cases + +## License + +This integration follows the same MIT license as applesign. rcodesign is +licensed under MPL-2.0. diff --git a/index.ts b/index.ts index d5b996c..d62212e 100644 --- a/index.ts +++ b/index.ts @@ -647,7 +647,13 @@ class Applesign { res = await tools.pseudoSign(entitlements, file); } else { const keychain = getKeychain(); - res = await tools.codesign(identity, entitlements, keychain, file); + res = await tools.codesign( + identity, + entitlements, + keychain, + file, + this.config.codeSign, + ); if (res.code !== 0 && codesignHasFailed(config, res.code, res.stderr)) { return this.emit("end", res.stderr); } @@ -655,7 +661,11 @@ class Applesign { this.emit("message", "Signed " + file); if (config.verifyTwice) { this.emit("message", "Verify " + file); - const res = await tools.verifyCodesign(file, config.keychain); + const res = await tools.verifyCodesign( + file, + this.config.keychain, + this.config.codeSign, + ); if (res.code !== 0) { const type = config.ignoreVerificationErrors ? "warning" : "error"; return this.emit(type, res.stderr); @@ -760,7 +770,7 @@ class Applesign { await this.signFile(lib); if (this.config.verify) { this.emit("message", "Verifying " + lib); - await tools.verifyCodesign(lib); + await tools.verifyCodesign(lib, undefined, this.config.codeSign); } } }; diff --git a/lib/config.ts b/lib/config.ts index 367b818..30756e6 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -61,6 +61,7 @@ const helpMessage = `Usage: -k, --keychain [KEYCHAIN] Specify custom keychain file -K, --add-access-group [NAME] Add $(TeamIdentifier).NAME to keychain-access-groups -L, --identities List local codesign identities + --codesign-tool=rcodesign Use rcodesign instead of codesign (EXPERIMENTAL) -m, --mobileprovision [FILE] Specify the mobileprovision file to use -s, --single Sign a single file instead of an IPA -S, --self-sign-provision Self-sign mobile provisioning (EXPERIMENTAL) @@ -127,6 +128,7 @@ export interface ConfigOptions { bundleIdKeychainGroup: string | false; bundleid: string | undefined; cloneEntitlements: boolean; + codeSign: string | undefined; customKeychainGroup: string | undefined; debug: any; // opt.d || opt.debug || "" deviceProvision: any; // opt.D || opt.deviceProvision || false @@ -206,6 +208,7 @@ const fromOptions = function (opt: any): ConfigOptions { bundleIdKeychainGroup: opt.bundleIdKeychainGroup || false, bundleid: opt.bundleid || undefined, cloneEntitlements: opt.cloneEntitlements || false, + codeSign: opt.codeSign || undefined, customKeychainGroup: opt.customKeychainGroup || undefined, debug: opt.d || opt.debug || "", deviceProvision: opt.D || opt.deviceProvision || false, @@ -338,6 +341,7 @@ function compile(conf: any) { bundleIdKeychainGroup: conf.B || conf["bundleid-access-group"], bundleid: conf.bundleid || conf.b, cloneEntitlements: conf.c || conf["clone-entitlements"], + codeSign: conf["codesign-tool"], customKeychainGroup: conf.K || conf["add-access-group"], debug: conf.debug || conf.d || "", deviceProvision: conf.D || conf.deviceProvision || false, diff --git a/lib/tools.ts b/lib/tools.ts index d52a95e..fe1c4d4 100644 --- a/lib/tools.ts +++ b/lib/tools.ts @@ -22,6 +22,7 @@ const cmdSpec = { lipo: "/usr/bin/lipo", /* only when useOpenSSL is true */ openssl: "/usr/local/bin/openssl", + rcodesign: "rcodesign", security: "/usr/bin/security", unzip: "/usr/bin/unzip", xcodebuild: "/usr/bin/xcodebuild", @@ -141,11 +142,71 @@ async function codesign( entitlement: string | undefined, keychain: string | undefined, file: string, + tool?: string, ) { if (identity === undefined) { // XXX: typescript can ensure this at compile time throw new Error("--identity is required to sign"); } + if (tool === "rcodesign") { + console.error("Signing with rcodesign tool"); + const args = ["sign"]; + + // Handle certificate source - try multiple formats + if (identity.endsWith(".p12")) { + args.push("--p12-file", identity); + } else if (identity.endsWith(".pem")) { + args.push("--pem-file", identity); + } else if (fs.existsSync(identity + ".p12")) { + args.push("--p12-file", identity + ".p12"); + } else if (fs.existsSync(identity + ".pem")) { + args.push("--pem-file", identity + ".pem"); + } else { + // Assume identity is a certificate fingerprint or common name + args.push("--certificate-fingerprint", identity); + } + + // Handle entitlements + if ( + typeof entitlement === "string" && entitlement !== "" && + fs.existsSync(entitlement) + ) { + args.push("--entitlements-xml-path", entitlement); + } + + // Handle keychain (rcodesign uses different approach) + if (typeof keychain === "string") { + // rcodesign doesn't use keychains directly, but we can log this for compatibility + console.error( + "Note: rcodesign does not use keychains like Apple codesign", + ); + } + + // Add timestamp server for notarization compatibility + args.push("--timestamp-url", "http://timestamp.apple.com/ts01"); + + // Add output file (rcodesign writes to a separate file) + const outputFile = file + ".rcodesigned"; + args.push("--output", outputFile); + + // Add input file as last argument + args.push(file); + + console.error("rcodesign " + args.join(" ")); + const result = await execProgram(getTool("rcodesign")!, args); + + // If successful, replace original with signed file + if (result.code === 0) { + await fs.promises.rename(outputFile, file); + } else { + // Clean up partial output on failure + try { + await fs.promises.unlink(outputFile); + } catch {} + } + + return result; + } /* use the --no-strict to avoid the "resource envelope is obsolete" error */ const args = ["--no-strict"]; // http://stackoverflow.com/a/26204757 args.push("-fs", identity); @@ -180,7 +241,12 @@ async function pseudoSign(entitlement: any, file: string): Promise { async function verifyCodesign( file: string, keychain?: string, + tool?: string, ): Promise { + if (tool === "rcodesign") { + const args = ["verify", "--verbose", file]; + return execProgram(getTool("rcodesign")!, args); + } const args = ["-v", "--no-strict"]; if (typeof keychain === "string") { args.push("--keychain=" + keychain);