Featured image of post How to Build and Sign iOs Application Using Azure DevOps

How to Build and Sign iOs Application Using Azure DevOps

When developing an app for Apple operating systems, you will eventually need to manage signing certificates, and provisioning profiles. In this article I describe 16 steps to securely manage them for signing and provisioning your app in a Azure DevOps Continuous Integration pipeline.

A Distribution Certificate is used to identify a developer for the purpose of installing and testing apps on iOS devices. Certificates can be obtained from Apple’s iOS Provisioning Portal and a Certificate Signing Request (CSR) file needs to be generated first.

The process for generating a CSR differs depending on your choice of operating system. In this post, I’ll cover how to generate Certificate Signing Request (CSR) file on Windows as well as how to use the generated certificate and provisioning profile to build and sign an iOS application in Azure DevOps.

  1. First open a PowerShell window, then run the following command to create a private key:

    1
    
    openssl genrsa -out appstore-distribution.key 2048
    
  2. Next, run the following command to generate the Certificate Signing Request (CSR) file using the previously created private key:

    1
    
    openssl req -new -key appstore-distribution.key -out appstore-distribution.csr  -subj "/emailAddress=devops@example.com, CN=Example, C=CA"
    
  3. Next, go to the Apple Developer Portal and click the “+” button next to “Certificates”:

    Add Certificate

  4. Choose the type of certificate you want to create, then click “Continue”

  5. Upload the Certificate Signing Request (CSR) file generated earlier and click “Continue”:

    Upload Certificate Signing Request (CSR) file

  6. A certificate will be generated and will be available for download. click “Download the Certificate”.

  7. In order to sign the application in Azure DevOps, we need the certificate in .p12 format. So to convert the .cer file to .p12, execute the following commands:

    1
    
    openssl x509 -in appstore-distribution.cer -inform DER -out appstore-distribution.pem -outform PEM
    

    followed by:

    1
    
    openssl pkcs12 -export -out appstore-distribution.p12 -inkey appstore-distribution.key -in appstore-distribution.pem
    

    This will prompt for a password. Please provide a password and store it in a safe place. This password will be needed when signing the application.

  8. Next, we need to generate a provisioning profile by going back to the Apple Developer Portal and clicking “+” next to “Profiles”:

    Add Profile

  9. Choose the type of Provisioning Profile you want to create, then click “Continue”

  10. Select the App ID you would like associated with the provisioning profile, then click “Continue”.

  11. Select the certificate we created earlier, then click “Continue”.

  12. Give the provisioning profile a name, then click “Generate”. You will be able to download the provisioning profile after it is generated.

  13. Upload the P12 certificate and provisioning profile to Azure DevOps Secure Files Library. During upload, your certificate will be encrypted and securely stored.

    Upload Secure Files

  14. Create a new pipeline, or go to the pre-existing pipeline if one already exists.

  15. Go to the Variables tab and add the following variables:

    • CertificateFile: Set the value to appstore-distribution.p12 (or whatever name you gave to your P12 certificate file in step #13).
    • CertificatePassword: Set the value to the password you set in step #7 above. Be sure check “Keep this value secret”. This will secure your password and obscure it in logs.
    • ProvisioningFile: Set the value to appstore-distribution.mobileprovision (or whatever name you gave your provisioning file in step #13).

    Add Variables

  16. Update the azure-pipelines.yml using the following example:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    
    name: $(Build.BuildId)
    trigger:
    - main
    - dev
    
    pool:
    vmImage: 'macOS-latest'
    
    variables:
    - name: configuration
      value: Release
    - name: sdk
      value: iphoneos
    - name: scheme
      value: Example
    - name: workspace
      value: Example.xcworkspace
    - name: plistFile
      value: Info.plist
    
    steps:
    - task: Cache@2
      inputs:
        key: 'pods | "$(Agent.OS)" | Podfile.lock'
        path: 'Pods'
        cacheHitVar: 'PODS_CACHE_RESTORED'
    
    - task: CocoaPods@0
      displayName: 'pod install using the CocoaPods task with defaults'
      inputs:
        forceRepoUpdate: true  
      condition: ne(variables.PODS_CACHE_RESTORED, 'true')
    
    - task: InstallAppleCertificate@2
      inputs:
        certSecureFile: '$(certificateFile)'
        certPwd: '$(CertificatePassword)'
        keychain: 'temp'
        deleteCert: true
    
    - task: InstallAppleProvisioningProfile@1
      inputs:
        provisioningProfileLocation: 'secureFiles'
        provProfileSecureFile: '$(provisioningFile)'
    
    - task: CmdLine@2
      displayName: 'Set Build Number'
      inputs:
        script: '/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $(Build.BuildId)" $(Build.SourcesDirectory)/$(scheme)/$(plistFile)'
    
    - task: Xcode@5
      inputs:
        actions: 'build'
        configuration: '$(configuration)'
        sdk: '$(sdk)'
        xcWorkspacePath: '$(workspace)'
        scheme: '$(scheme)'
        packageApp: true
        signingOption: 'manual'
        signingIdentity: '$(APPLE_CERTIFICATE_SIGNING_IDENTITY)'
        provisioningProfileUuid: '$(APPLE_PROV_PROFILE_UUID)'
    
    - task: CopyFiles@2
      inputs:
        contents: '**/*.ipa'
        targetFolder: '$(build.artifactStagingDirectory)'
    
    - task: PublishBuildArtifacts@1
      displayName: 'Publish artifact'
      condition: succeededOrFailed()
    

References:
Convert .cer to .p12
Build, test, and deploy Xcode apps
Sign your mobile app

Built with Hugo
Theme Stack designed by Jimmy