code signing - nself-org/nchat GitHub Wiki

Code Signing Guide

Complete guide for code signing nchat desktop applications on all platforms.

Table of Contents

Why Code Signing

Code signing is essential for desktop application distribution:

macOS

  • Required for distribution outside Mac App Store
  • Gatekeeper will block unsigned apps
  • Users see scary warnings
  • Notarization requires valid signing

Windows

  • Recommended for professional distribution
  • SmartScreen flags unsigned apps
  • Users see warnings: "Unknown publisher"
  • Enterprise deployment requires signing

Linux

  • Optional but recommended
  • Builds trust with users
  • Enables repository distribution

macOS Code Signing

Prerequisites

  1. Apple Developer Account ($99/year)

  2. Xcode (macOS only)

    xcode-select --install

Step 1: Create Certificates

Option A: Using Xcode (Easiest)

  1. Open Xcode
  2. Go to Xcode → Preferences → Accounts
  3. Click + to add Apple ID
  4. Select your account → Manage Certificates
  5. Click + → Create:
    • Developer ID Application (for apps)
    • Developer ID Installer (for pkg installers)

Option B: Using Developer Portal

  1. Go to https://developer.apple.com/account/resources/certificates
  2. Click + to create certificate
  3. Choose Developer ID Application
  4. Follow instructions to create CSR:
    # On macOS, open Keychain Access
    # Certificate Assistant → Request a Certificate from a Certificate Authority
    # Save to disk
  5. Upload CSR, download certificate
  6. Double-click to install in Keychain

Step 2: Verify Certificates

# List all code signing identities
security find-identity -v -p codesigning

# Output should show:
# 1) ABC123... "Developer ID Application: Your Name (TEAM123)"
# 2) DEF456... "Developer ID Installer: Your Name (TEAM123)"

Step 3: Get Team ID

# From developer.apple.com → Membership
# Or from certificate:
security find-identity -v -p codesigning | grep "Developer ID"
# Team ID is in parentheses: (TEAM123)

Step 4: Create App-Specific Password

For notarization:

  1. Go to https://appleid.apple.com
  2. Sign in with Apple ID
  3. Security → App-Specific Passwords
  4. Click + to generate
  5. Name: "nchat Desktop Notarization"
  6. Copy password (format: xxxx-xxxx-xxxx-xxxx)

Step 5: Set Environment Variables

# In ~/.zshrc or ~/.bashrc
export APPLE_ID="[email protected]"
export APPLE_ID_PASSWORD="xxxx-xxxx-xxxx-xxxx"  # App-specific password
export APPLE_TEAM_ID="TEAM123456"
export APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAM123456)"

# Reload shell
source ~/.zshrc

Or for CI/CD, use GitHub Secrets:

# .github/workflows/build-electron.yml
env:
  APPLE_ID: ${{ secrets.APPLE_ID }}
  APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
  APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

Step 6: Configure Entitlements

Entitlements are already configured in:

  • platforms/electron/resources/entitlements.mac.plist
  • platforms/tauri/src-tauri/Info.plist (auto-generated)

To customize, edit the plist file to add/remove permissions.

Step 7: Sign and Notarize

Electron

# Build will automatically sign and notarize
./scripts/deploy-desktop-electron.sh --platform mac --env prod

# Or manually:
cd platforms/electron
npm run dist:mac

# Check notarization status
xcrun notarytool history --apple-id "$APPLE_ID" --password "$APPLE_ID_PASSWORD"

Tauri

# Build will automatically sign
./scripts/deploy-desktop-tauri.sh --platform mac --env prod

# Manually notarize (if needed)
xcrun notarytool submit \
  target/release/bundle/dmg/nchat.dmg \
  --apple-id "$APPLE_ID" \
  --password "$APPLE_ID_PASSWORD" \
  --team-id "$APPLE_TEAM_ID" \
  --wait

# Staple ticket
xcrun stapler staple target/release/bundle/dmg/nchat.dmg

Step 8: Verify Signature

# Verify app signature
codesign --verify --deep --strict /Applications/nchat.app

# Display signature info
codesign -dv --verbose=4 /Applications/nchat.app

# Check notarization
spctl -a -vvv -t install /Applications/nchat.app

# Should output: "accepted"

Windows Code Signing

Prerequisites

  1. Code Signing Certificate

    • Purchase from certificate authority (CA)
    • Recommended: DigiCert, Sectigo, SSL.com
    • Choose "Code Signing Certificate"
    • ~$100-500/year
  2. Certificate Types

    • Standard Code Signing: Basic signing
    • EV (Extended Validation): Bypasses SmartScreen immediately
    • OV (Organization Validation): Business verification required

Step 1: Purchase Certificate

Recommended Providers

DigiCert (Premium)

Sectigo (Mid-tier)

SSL.com (Budget)

Purchase Process

  1. Choose certificate type (Standard or EV)
  2. Complete organization validation
  3. Verify domain/business (can take 1-3 days)
  4. Download certificate + private key

Step 2: Export Certificate

You need a PFX/P12 file with private key:

If you have .cer + .key

# Combine into PFX
openssl pkcs12 -export \
  -out certificate.pfx \
  -inkey private.key \
  -in certificate.cer \
  -password pass:YourStrongPassword

If using Windows Certificate Manager

  1. Open certmgr.msc
  2. Find your certificate under Personal → Certificates
  3. Right-click → All Tasks → Export
  4. Choose Yes, export the private key
  5. Select Personal Information Exchange (PFX)
  6. Set strong password
  7. Save as certificate.pfx

Step 3: Set Environment Variables

# Linux/macOS
export WIN_CSC_LINK="/path/to/certificate.pfx"
export WIN_CSC_KEY_PASSWORD="your-certificate-password"

# Windows (PowerShell)
$env:WIN_CSC_LINK="C:\path\to\certificate.pfx"
$env:WIN_CSC_KEY_PASSWORD="your-certificate-password"

# For Tauri
export WINDOWS_CERTIFICATE="/path/to/certificate.pfx"
export WINDOWS_CERTIFICATE_PASSWORD="your-certificate-password"

For CI/CD:

# .github/workflows/build-electron.yml
env:
  WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }} # Base64 encoded cert
  WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}

Encoding Certificate for CI/CD

# Encode certificate to base64
base64 -i certificate.pfx -o certificate.txt

# In GitHub Secrets, paste the base64 content
# The build will decode it automatically

Step 4: Sign Application

Electron

# Build will automatically sign
./scripts/deploy-desktop-electron.sh --platform win --env prod

# Or manually
cd platforms/electron
npm run dist:win

Tauri

# Build will automatically sign
./scripts/deploy-desktop-tauri.sh --platform win --env prod

Step 5: Verify Signature

On Windows:

# Using signtool (Windows SDK)
signtool verify /pa /v nchat-setup.exe

# Or right-click .exe → Properties → Digital Signatures
# Should show your certificate details

Step 6: Build SmartScreen Reputation

Even with valid signature, new apps show SmartScreen warnings.

Solutions:

  1. Get EV Certificate ($599/year)

    • Bypasses SmartScreen immediately
    • Requires hardware token
  2. Build Reputation (Free, slow)

    • Get downloads from users
    • Takes weeks to months
    • No guarantee
  3. Submit to Microsoft (Free)

Linux Code Signing

Linux doesn't require code signing, but you can sign packages for trust.

GPG Signing

Step 1: Generate GPG Key

# Generate key
gpg --full-generate-key

# Choose:
# - Key type: RSA and RSA
# - Key size: 4096
# - Expiration: 2 years
# - Name: Your Name
# - Email: [email protected]

# List keys
gpg --list-keys

Step 2: Export Public Key

# Export public key
gpg --export --armor [email protected] > public-key.asc

# Upload to key server
gpg --send-keys KEY_ID --keyserver keyserver.ubuntu.com

Step 3: Sign Packages

# Sign .deb
dpkg-sig --sign builder nchat_1.0.0_amd64.deb

# Sign .rpm
rpm --addsign nchat-1.0.0-1.x86_64.rpm

# Sign .AppImage (detached signature)
gpg --detach-sign --armor nchat.AppImage
# Creates nchat.AppImage.asc

Step 4: Verify Signatures

# Verify .deb
dpkg-sig --verify nchat_1.0.0_amd64.deb

# Verify .rpm
rpm --checksig nchat-1.0.0-1.x86_64.rpm

# Verify .AppImage
gpg --verify nchat.AppImage.asc nchat.AppImage

Repository Signing (Advanced)

For hosting your own APT/YUM repository:

# Create GPG key for repository
gpg --gen-key

# Sign repository metadata
# APT
gpg --clearsign -o InRelease Release
gpg -abs -o Release.gpg Release

# YUM
gpg --detach-sign --armor repodata/repomd.xml

Configuration

Environment Variables Reference

# ============================================
# macOS Code Signing
# ============================================
APPLE_ID="[email protected]"
APPLE_ID_PASSWORD="xxxx-xxxx-xxxx-xxxx"  # App-specific password
APPLE_TEAM_ID="TEAM123456"
APPLE_SIGNING_IDENTITY="Developer ID Application: Name (TEAM)"

# For Tauri
APPLE_SIGNING_IDENTITY="Developer ID Application: Name (TEAM)"
APPLE_ID="[email protected]"
APPLE_PASSWORD="xxxx-xxxx-xxxx-xxxx"

# ============================================
# Windows Code Signing
# ============================================
WIN_CSC_LINK="/path/to/certificate.pfx"
WIN_CSC_KEY_PASSWORD="certificate-password"

# For Tauri
WINDOWS_CERTIFICATE="/path/to/certificate.pfx"
WINDOWS_CERTIFICATE_PASSWORD="certificate-password"

# ============================================
# Linux GPG Signing
# ============================================
GPG_KEY_ID="your-gpg-key-id"
GPG_PASSPHRASE="gpg-key-passphrase"

# ============================================
# Common
# ============================================
# Skip signing (for testing)
CSC_IDENTITY_AUTO_DISCOVERY=false  # Electron
TAURI_SKIP_SIGNING=1               # Tauri

Storage Best Practices

DO:

  • Store certificates in secure location
  • Use environment variables for passwords
  • Use CI/CD secrets for automation
  • Rotate passwords regularly
  • Use hardware tokens for EV certificates

DON'T:

  • Commit certificates to git
  • Share private keys
  • Use weak passwords
  • Store passwords in plain text
  • Reuse passwords across services

CI/CD Configuration

GitHub Actions

name: Build and Sign

on:
  push:
    tags:
      - 'v*'

jobs:
  build-macos:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3

      - name: Import Code Signing Certificate
        env:
          CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
          CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
        run: |
          # Create keychain
          security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
          security default-keychain -s build.keychain
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain

          # Import certificate
          echo "$CERTIFICATE_BASE64" | base64 --decode > certificate.p12
          security import certificate.p12 -k build.keychain \
            -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign

          security set-key-partition-list -S apple-tool:,apple:,codesign: \
            -s -k "$KEYCHAIN_PASSWORD" build.keychain

      - name: Build and Sign
        env:
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
        run: |
          ./scripts/deploy-desktop-electron.sh --platform mac

  build-windows:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v3

      - name: Build and Sign
        env:
          WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
          WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
        run: |
          .\scripts\deploy-desktop-electron.sh --platform win

Troubleshooting

macOS: "App is damaged and can't be opened"

Cause: Gatekeeper quarantine attribute

Solution:

# Remove quarantine
xattr -cr /Applications/nchat.app

# Or users: Right-click → Open (first time only)

macOS: "Developer cannot be verified"

Cause: App not notarized or signature invalid

Solution:

# Check signature
codesign --verify --deep --strict /Applications/nchat.app

# Check notarization
spctl -a -vvv -t install /Applications/nchat.app

# Re-notarize if needed
xcrun notarytool submit nchat.dmg \
  --apple-id "$APPLE_ID" \
  --password "$APPLE_ID_PASSWORD" \
  --wait

Windows: SmartScreen Warning

Cause: New certificate or low download count

Solutions:

  1. Get EV certificate ($599/year) - instant bypass
  2. Submit to Microsoft for analysis
  3. Wait for reputation to build (weeks/months)

Windows: "Certificate not trusted"

Cause: Certificate not from trusted CA

Solution:

  • Purchase from major CA (DigiCert, Sectigo, SSL.com)
  • Ensure certificate chain is complete
  • Check certificate expiration date

Electron: "No identity found for code signing"

Cause: Certificate not in keychain or environment variable not set

Solution:

# macOS: Check keychain
security find-identity -v -p codesigning

# Ensure APPLE_SIGNING_IDENTITY is set
echo $APPLE_SIGNING_IDENTITY

# Or disable signing for testing
export CSC_IDENTITY_AUTO_DISCOVERY=false

Tauri: Build fails with signing error

Cause: Certificate path incorrect or password wrong

Solution:

# Verify certificate exists
ls -la /path/to/certificate.pfx

# Check environment variables
echo $WINDOWS_CERTIFICATE
echo $WINDOWS_CERTIFICATE_PASSWORD

# Try manual signing first
# Windows:
signtool sign /f certificate.pfx /p password /tr http://timestamp.digicert.com app.exe

# macOS:
codesign --force --sign "Developer ID" --timestamp app.app

Resources

Documentation

Certificate Providers

Tools

Support

For code signing issues:

⚠️ **GitHub.com Fallback** ⚠️