Signing code with a hardware-based code-signing identity

This thread has been locked by a moderator; it no longer accepts new replies.

Both the codesign tool and Xcode allow you to sign code with a hardware-based code-signing identity. However, setting that up can be a bit of a challenge. Recently a developer open a DTS tech support incident requesting help with this, and so I thought I’d post my instructions here for the benefit of all.

If you have any questions or comments about this, please start a new thread, tagging it with Code Signing so that I see it.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


Signing code with a hardware-based code-signing identity

Both the codesign tool and Xcode allow you to sign code with a hardware-based code-signing identity. This post explains how to set that up.

I used macOS 14.2.1 with Xcode 15.2. For my hardware-based key I used a YubiKey 5 NFC that I reset to its defaults. I installed YubiKey Manager 1.2.5.

IMPORTANT While I used a YubiKey, the code signing parts of this process should work with any token that has a functioning CryptoTokenKit driver.

In the case of the YubiKey, it presents a PIV interface and thus it’s supported by macOS’s built-in PIV CryptoTokenKit driver.

In this example I created an Apple Development certificate because those are dime a dozen. This process should work with any other type of code-signing certificate. Indeed, it make sense to store your most precious keys in a hardware token, including your Developer ID keys. For more on that topic, see The Care and Feeding of Developer ID.

Generate a certificate signing request

To generate a certificate signing request (CSR):

  1. Connect the YubiKey via USB.

  2. Dismiss any system alerts:

    • If the “Allow this accessory to connect?” alert comes up, click Allow.

    • If the Keyboard Setup Assistant comes up, quit that.

    • If the ctkbind notification comes up, dismiss that. Coded signing does not require that you bind your login account to your hardware token.

  3. Launch YubiKey Manager.

  4. Choose Applications > PIV.

  5. Click Configure Certificates.

  6. Select Digital Signature (slot 9c). In the past I’ve run into situations where signing fails if you don’t use this slot, although I haven’t tested that in this particular case.

  7. Click Generate.

  8. Select Certificate Signing Request (CSR) and click Next.

  9. Select the RSA2048 algorithm and click Next.

  10. Enter a subject and click Next. The value you use here doesn’t matter because Apple ignores pretty much everything in the CSR except the public key.

  11. Click Generate.

  12. Choose a save location and name. Don’t include a file name extension.

  13. When prompted for the management key, enter that and click OK.

  14. When prompted for the PIN, enter that and click OK.

  15. The app will generate a .csr file at your chosen location.

  16. Quit YubiKey Manager.

Note Apple typically uses the .certSigningRequest extension for CSRs, but this process works just fine with the .csr extension used by YubiKey Manager.

Generate a certificate from your CSR

To generate a certificate from that CSR:

  1. In Safari, go to Developer > Account and log in.

  2. If you’re a member of multiple teams, make sure you have the correct one selected at the top right.

  3. Click Certificates.

  4. Click the add (+) button to create a new certificate.

  5. Select Apple Development and click Continue.

  6. Click Choose File, select your CSR file, and click Upload.

  7. Click Continue to generate your certificate.

  8. That takes you to the Download Your Certificate page. Click Download.

  9. In Terminal, calculate a SHA-1 hash of your .cer file.

% shasum "development.cer"
840f40ef6b10bedfb2315ac49e07f7e6508a1680  development.cer

Import the certificate to form a code-signing identity

To import this certificate into your YubiKey:

  1. Convert the certificate to PEM form:

    % openssl x509 -in "development.cer" -inform der -out "development.pem"
    
  2. Launch YubiKey Manager.

  3. Choose Applications > PIV.

  4. Click Configure Certificates.

  5. Select Digital Signature (slot 9c).

  6. Click Import.

  7. In the file dialog, select the PEM and click Import.

  8. When prompted for the management key, enter that and click OK. The UI updates to show the certificate issuer (Apple Worldwide Developer Relations Certificate Authority) and subject (Apple Development: UUU, where UUU identifies you).

  9. Quit YubiKey Manager.

  10. Unplug the YubiKey and then plug it back in.

Sign a test program

Before digging into Xcode, check that you can sign code with the codesign tool:

  1. Create a small program to test with. In my case I decided to re-sign the built-in true command-line tool:

    % cp "/usr/bin/true" "MyTool"
    % codesign -s - -f "MyTool"
    
  2. Run codesign to sign your program, passing in the SHA-1 hash of the certificate you imported into the YubiKey:

    % codesign -s 840f40ef6b10bedfb2315ac49e07f7e6508a1680 -f "MyTool"
    
  3. When prompted for the PIN, enter that and click OK. The codesign invocation completes like so:

    % codesign -s 840f40ef6b10bedfb2315ac49e07f7e6508a1680 -f "MyTool"
    MyTool: replacing existing signature
    

Sign from Xcode

To sign from Xcode:

  1. Open your project in Xcode. In my case I created a new project by choosing File > New then selecting macOS > Command Line tool.

  2. In Signing & Capabilities for the tool target, turn off “Automatically manage signing”.

  3. In Build Settings, find the Code Signing Identity build setting, choose Other, and then enter the SHA-1 hash of your certificate.

  4. Choose Product > Build.

  5. When prompted for the PIN, enter that and click OK. The build then completes.

IMPORTANT This requires Xcode 13 or later. Earlier versions of Xcode only work with file-based code-signing identities.

Boost
Signing code with a hardware-based code-signing identity
 
 
Q