From 77fff67ee4a7dc86b9d6eec32d1cd1c77d0126f5 Mon Sep 17 00:00:00 2001 From: pancake Date: Thu, 6 Nov 2025 21:26:18 +0100 Subject: [PATCH 1/2] Initial support for rcodesign --- index.ts | 4 ++-- lib/config.ts | 4 ++++ lib/tools.ts | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/index.ts b/index.ts index d5b996c..1ba27ed 100644 --- a/index.ts +++ b/index.ts @@ -647,9 +647,9 @@ 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); + return this.emit('end', res.stderr); } } this.emit("message", "Signed " + file); 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..28cf6eb 100644 --- a/lib/tools.ts +++ b/lib/tools.ts @@ -141,11 +141,62 @@ 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('WARNING: Signing with the experimental rcodesign tool'); + const args = []; + args.push('sign'); // action + + args.push('-v'); + args.push('--pem-source'); // action + const pemFile = '/Users/pancake/iphone.pem'; + args.push(pemFile); + + args.push('--code-resources-path'); + args.push('/tmp/csreq.bin'); + // rcodesign bug makes this flag to not sign the binary at all + args.push('--extra-digest'); + args.push('sha256'); + args.push('--extra-digest'); + args.push('sha384'); + // args.push('--binary-identifier'); + // args.push('com.tacobellspain.app'); + /* + args.push('--code-signature-flags'); + args.push('runtime'); + */ + // --p12-file developer-id.p12 + // --p12-password-file ~/.certificate-password + // --code-signature-flags runtime + // path/to/executable + /* + if (typeof entitlement === 'string' && entitlement !== '') { + args.push('-e'); + args.push(entitlement); + } + args.push('--binary-identifier'); + args.push(identity); + */ + if (typeof keychain === 'string') { + args.push('--keychain-fingerprint'); + args.push(keychain); + } + args.push(file); // input + args.push(file + '.signed'); // output + console.error('rcodesign ' + args.join(' ')); + const a = await execProgram('rcodesign', args, undefined); + if (a.code === 0) { + await execProgram('rm', ['-rf', file], undefined); + await execProgram('mv', [file + '.signed', file], undefined); + } + console.log(a.stderr); + return execProgram('cp', [file, '/tmp/newsigned'], undefined); + } /* 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); @@ -181,6 +232,12 @@ async function verifyCodesign( file: string, keychain?: string, ): Promise { + /* + if (tool === 'rcodesign') { + const args = ['verify', file]; + return execProgram(getTool('rcodesign'), args, null, cb); + } + */ const args = ["-v", "--no-strict"]; if (typeof keychain === "string") { args.push("--keychain=" + keychain); From a520343c7c143624aced57045d00b07e0341ba6c Mon Sep 17 00:00:00 2001 From: pancake Date: Thu, 6 Nov 2025 21:31:07 +0100 Subject: [PATCH 2/2] Update and document rcodesign support --- README.rcodesign.md | 288 ++++++++++++++++++++++++++++++++++++++++++++ index.ts | 18 ++- lib/tools.ts | 111 +++++++++-------- 3 files changed, 362 insertions(+), 55 deletions(-) create mode 100644 README.rcodesign.md 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 1ba27ed..d62212e 100644 --- a/index.ts +++ b/index.ts @@ -647,15 +647,25 @@ class Applesign { res = await tools.pseudoSign(entitlements, file); } else { const keychain = getKeychain(); - res = await tools.codesign(identity, entitlements, keychain, file, this.config.codeSign); + 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); + return this.emit("end", res.stderr); } } 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/tools.ts b/lib/tools.ts index 28cf6eb..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", @@ -147,55 +148,64 @@ async function codesign( // XXX: typescript can ensure this at compile time throw new Error("--identity is required to sign"); } - if (tool === 'rcodesign') { - console.error('WARNING: Signing with the experimental rcodesign tool'); - const args = []; - args.push('sign'); // action - - args.push('-v'); - args.push('--pem-source'); // action - const pemFile = '/Users/pancake/iphone.pem'; - args.push(pemFile); - - args.push('--code-resources-path'); - args.push('/tmp/csreq.bin'); - // rcodesign bug makes this flag to not sign the binary at all - args.push('--extra-digest'); - args.push('sha256'); - args.push('--extra-digest'); - args.push('sha384'); - // args.push('--binary-identifier'); - // args.push('com.tacobellspain.app'); - /* - args.push('--code-signature-flags'); - args.push('runtime'); - */ - // --p12-file developer-id.p12 - // --p12-password-file ~/.certificate-password - // --code-signature-flags runtime - // path/to/executable - /* - if (typeof entitlement === 'string' && entitlement !== '') { - args.push('-e'); - args.push(entitlement); + 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); } - args.push('--binary-identifier'); - args.push(identity); - */ - if (typeof keychain === 'string') { - args.push('--keychain-fingerprint'); - args.push(keychain); + + // Handle entitlements + if ( + typeof entitlement === "string" && entitlement !== "" && + fs.existsSync(entitlement) + ) { + args.push("--entitlements-xml-path", entitlement); } - args.push(file); // input - args.push(file + '.signed'); // output - console.error('rcodesign ' + args.join(' ')); - const a = await execProgram('rcodesign', args, undefined); - if (a.code === 0) { - await execProgram('rm', ['-rf', file], undefined); - await execProgram('mv', [file + '.signed', file], undefined); + + // 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", + ); } - console.log(a.stderr); - return execProgram('cp', [file, '/tmp/newsigned'], undefined); + + // 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 @@ -231,13 +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', file]; - return execProgram(getTool('rcodesign'), args, null, cb); + 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);