Fastlane證書管理(一):cert、sigh

wangzzzzz發表於2018-06-12

1. 前言

cert、sigh和match是Fastlane中的三個Tool,他們都是與證書相關的工具。cert的作用是獲取簽名證書或刪除過期的證書;sigh的作用是管理配置檔案(provisioning profile),比如建立新的、修復過期的、刪除本地的等;match的主要作用是使用certsigh建立新的證書和配置檔案,並它們放置在git上,然後重複使用。

2. cert

cert這個Tool下定義了兩個Command,分別是createrevoke_expired,其中create是預設Command。 可以通過在終端中執行下列命令呼叫

#呼叫create
fastlane cert
fastlane cert create

#呼叫revoke_expired
fastlane cert revoke_expired
複製程式碼

除了在終端使用,cert還可以在lane中被當做action來呼叫,這也是使用最頻繁的呼叫方式。 當cert被當做action被呼叫時,其效果和在終端呼叫fastlane cert [create]的效果是一樣的。

cert中的create的作用是獲取簽名證書和其私鑰,然後將簽名證書和其私鑰(p12)匯入到鑰匙鏈中。 為了獲取證書,首先它會去檢測本地是否存在它想要的證書,如果沒有則它會去你的AppleID賬號中嘗試建立一個新的。

本文只討論create這個Command,下文中如果沒有特殊說明,指的都是這種情況。 當在終端執行fastlane cert時,其執行邏輯如下

Fastlane證書管理(一):cert、sigh

  1. 建立:output_path指向的目錄

  2. 獲取AppleID 可通過:username、環境變數CERT_USERNAME、DELIVER_USER、DELIVER_USERNAMEAppfile三種途徑獲取;如果沒有,則在終端請求使用者輸入AppleID。

  3. 獲取AppleID對應密碼 可通過環境變數FASTLANE_PASSWORDDELIVER_PASSWORD設定;如果沒有,則在終端使用security find-internet-password -g -s deliver.#{AppleID}檢視鑰匙鏈中是否儲存了對應密碼,其中AppleID是[步驟2]中獲取的;如果沒有,則在終端請求使用者輸入,並且會將使用者輸入的密碼儲存在鑰匙鏈中。

  4. 登入到蘋果開發網站 如果有兩步驗證,則還需要輸入對應手機的驗證碼

  5. 獲取TeamID 如果這個AppleID賬號加入了多個Team,可以通過設定TeamID或TeamName來指定一個Team,具體來說可以通過環境變數FASTLANE_TEAM_IDCERT_TEAM_ID:team_id指定TeamID,通過環境變數FASTLANE_TEAM_NAME,CERT_TEAM_NAME:team_name指定TeamName,否則,需要使用者手動來選擇。如果你的AppleID賬號只加入了一個Team,則直接使用此Team的TeamID。

  6. 檢測force 6.1. 當:forcetrue時,強制建立證書,執行[步驟8] 6.2. 當:forcefalse時,執行[步驟7]

  7. 檢測本地證書 遍歷AppleID賬號中的已建立證書,檢測此證書是否存在於鑰匙鏈中,或者:output_path目錄下是否存在此證書對應的金鑰(p12),其具體的檢測流程會在下文中講到。 7.1. 本地有可用證書,執行[步驟9] 7.2. 本地無可用證書,執行[步驟8]

  8. 建立新證書 首先生成CSR檔案和RSA金鑰對

 def create_certificate_signing_request
          key = OpenSSL::PKey::RSA.new(2048)
          csr = OpenSSL::X509::Request.new
          csr.version = 0
          csr.subject = OpenSSL::X509::Name.new([
                                                  ['CN', 'PEM', OpenSSL::ASN1::UTF8STRING]
                                                ])
          csr.public_key = key.public_key
          csr.sign(key, OpenSSL::Digest::SHA1.new)
          return [csr, key]
        end
複製程式碼

然後生成請求

r = request(:post, "account/#{platform_slug(mac)}/certificate/submitCertificateRequest.action", {
        teamId: team_id,
        type: type,
        csrContent: csr,
        appIdId: app_id # optional
      })
複製程式碼

若建立成功,則在output_path目錄下儲存此新建立的CSR檔案、簽名證書和簽名證書對應的私鑰。 AppleID賬戶下,相同型別的證書只能建立兩個,如果已經建立了兩個之後,再去嘗試建立證書,則會報錯。

  1. 匯入此證書和它的私鑰到鑰匙鏈中 在終端中使用security命令來匯入
security import certificate_path -k keychain_path -P certificate_password -T /usr/bin/codesign -T /usr/bin/security
複製程式碼

其中certificate_path表示要匯入證書的路徑; keychain_path表示鑰匙鏈的路徑,一般是~/Library/Keychains/login.keychain-dbcertificate_password表示證書的密碼,預設是空字串,通過cert建立的證書的密碼為空; -T usr/bin/codesign表示使用usr/bin/codesign訪問這個證書的時候不需要授權,也就是不需要輸入鑰匙鏈的密碼,這個在CI中會很有用。 最後需要注意的是,如果證書本來就是在鑰匙鏈中,則不會執行這個步驟,也不會執行這條命令,所以在CI中使用時,最好在構建指令碼中加上security unlock-keychain -p certificate_password ~/Library/Keychains/login.keychain-db,這條命令的作用和上面的-T類似,但是範圍更廣,即訪問整個鑰匙鏈都不需要輸入密碼。

  1. 設定全域性變數 設定CER_CERTIFICATE_IDCER_FILE_PATH這兩個環境變數,分別表示證書的id和證書的路徑,證書的路徑就是:output_path目錄下的證書檔案的路徑。 如果是在lane中呼叫cert,則還會設定環境變數SIGH_CERTIFICATE_ID,這樣設定之後,如果接下來sigh需要建立一個配置檔案,就會使用環境變數SIGH_CERTIFICATE_ID指向的簽名證書來建立。(環境變數SIGH_CERTIFICATE_ID僅僅只是在建立新的配置檔案的時候才會使用)

2.1. 檢測本地證書

Fastlane證書管理(一):cert、sigh

  1. 獲取AppleID中已建立的證書列表 根據:development指定證書的型別,true表示除錯證書,false表示生產證書,預設是false,本步驟只獲取指定型別的證書。證書列表中的物件的型別都是Spaceship::Portal::Certificate或其子類。 類Spaceship::Portal::Certificate中的例項變數
module Spaceship
  module Portal
    class Certificate < PortalBase
     # @return (String) The ID given from the developer portal. You'll probably not need it.
      attr_accessor :id

      # @return (String) The name of the certificate
      attr_accessor :name

      # @return (String) Status of the certificate
      attr_accessor :status

      # @return (Date) The date and time when the certificate was created
      attr_accessor :created

      # @return (Date) The date and time when the certificate will expire
      attr_accessor :expires

      # @return (String) The owner type that defines if it's a push profile or a code signing identity
      # @example Code Signing Identity
      #   "team"
      # @example Push Certificate
      #   "bundle"
      attr_accessor :owner_type

      # @return (String) The name of the owner
      # @example Code Signing Identity (usually the company name)
      #   "SunApps Gmbh"
      # @example Push Certificate (the bundle identifier)
      #   "tools.fastlane.app"
      attr_accessor :owner_name

      # @return (String) The ID of the owner, that can be used to fetch more information
      attr_accessor :owner_id

      # Indicates the type of this certificate
      attr_accessor :type_display_id

      # @return (Bool) Whether or not the certificate can be downloaded
      attr_accessor :can_download
    end
  end
end
複製程式碼
  1. 獲取證書列表中的下一個證書 遍歷[步驟1]獲取的證書列表 如果下一個證書不存在,則執行[步驟7],表明本地沒有可用證書 如果下一個證書存在,則執行[步驟3] 一個InHouse型別的證書物件
<Spaceship::Portal::Certificate::InHouse 
	id="GF0ZY66W6D", 
	name="iOS Distribution", 
	status="Issued", 
	created=2017-12-19 02:52:11 UTC, 
	expires=2020-12-18 02:42:11 UTC, 
	owner_type="team", 
	owner_name="Communications Corporation Limited", 
	owner_id="12GF5VQGBX", 
	type_display_id="9RQEK7MSXA", 
	can_download=true>

複製程式碼
  1. 下載此證書檔案到output_path 根據[步驟2]中獲取的證書物件,從AppleID中下載證書檔案
  r = request(:get, "account/#{platform_slug(mac)}/certificate/downloadCertificateContent.action", {
        teamId: team_id,
        certificateId: certificate_id,
        type: type
      })
複製程式碼

將下載的證書檔案儲存在:output_path指向的目錄中,指定檔名為#{certificate.id}.cercertificate.id表示上述證書物件的id。

  1. 檢測本地鑰匙鏈 這一步的目的就是檢測本地鑰匙鏈中是否存在[步驟2]中獲取的證書,由於無法從鑰匙鏈中獲取證書的唯一識別符號,所以這裡是通過對比證書檔案的SHA1摘要來判斷其是否存在。 使用security find-identity -v -p codesigning獲取鑰匙鏈中可用的簽名證書列表,下列每一條資料都包含了證書的SHA1摘要和其名稱
wang:temp mac$ security find-identity -v -p codesigning
  1) 9C3C5AE7820F33F6D919595E971C9B458519ACE5 "iPhone Developer"
  2) 57F720F51EA851BA8E2D6EC4D4D752F9EF43D2F7 "iPhone Distribution"
     2 valid identities found
複製程式碼

然後獲取[步驟3]中證書檔案的SHA1摘要,如果這個摘要存在於上述輸出中,則表示這個證書已經在鑰匙鏈中了,執行[步驟8] 如果沒有包含,則執行[步驟5]

  1. output_path中檢測私鑰 檢測:output_path目錄中是否存在#{certificate.id}.p12certificate.id表示[步驟2]中獲取的證書物件的id,這裡是僅僅只是通過檔名來判斷其是否存在。 若存在,說明本地存在可用證書,則執行[步驟8] 若不存在,說明本地不存在可用證書,則執行[步驟6]

  2. 從output_path中刪除此證書 刪除[步驟3]中下載的證書檔案

  3. 本地沒有可用證書

  4. 本地有可用證書

3. sigh

sigh是用於管理配置檔案profile,在 sigh這個Tool中,其內部整合了多個Command,分別是renew、download_all、repair、resign、manage,其中預設Command是renewrenew的作用是從AppleID賬號中獲取一個可用的配置檔案profile,如果沒有,則建立一個新的profile,然後將它按照到xcode中。

這裡只討論renew,如果沒有特殊說明,指的都是這種情況。 當在終端執行fastlane sigh [renew]時,其執行邏輯如下

Fastlane證書管理(一):cert、sigh

前幾步與cert類似,只是有一些用來傳值的環境變數有些不同。

  1. 獲取AppleID 可通過:username、環境變數SIGH_USERNAME、DELIVER_USER、DELIVER_USERNAMEAppfile三種途徑獲取;如果沒有,則在終端請求使用者輸入AppleID。

  2. 獲取AppleID對應密碼

  3. 登入到蘋果開發網站

  4. 獲取TeamID 通過環境變數FASTLANE_TEAM_ID、環境變數SIGH_TEAM_ID:team_id指定TeamID,通過環境變數FASTLANE_TEAM_NAME,環境變數SIGH_TEAM_NAME:team_name指定TeamName

  5. 獲取profile列表 首先從AppleID賬號中,獲取所有已建立的provisioning profiles的列表(也包含xcode管理的),然後經過一步步的過濾,最終得到所有可用的profile。 5.1 獲取的profile列表有值,則執行[步驟6] 5.2 獲取的profile列表有值,則執行[步驟16]

  6. 獲取第一個profile

  7. 檢測force :force指定是否強制建立新的provisioning profile 7.1 :force等於true,執行[步驟8] 7.2 :force等於false,執行[步驟10]

  8. 在AppleID中刪除此profile 在AppleID賬號中,刪除[步驟6]中獲取的profile

  9. 在AppleID中建立新的profile 如果是[步驟16]跳轉過來的,還需要保證AppleID賬號中存在此:app_identifier

  10. 返回profile 如果:force等於true,則返回[步驟9]中建立的profile; 如果:force等於false,則返回[步驟6]中獲取的profile.

  11. 下載profile檔案 之前步驟中提到profile是provisioning profile的概要描述,這裡下載的profile檔案,則是在專案中使用的配置檔案。下載完成後,將檔案儲存在臨時目錄中。

  12. output_path目錄下儲存profile檔案 將[步驟11]下載的檔案移動到:output_path目錄下,如果指定了:filename,則檔名為#{filename}.mobileprovision;否則,檔名為#{type}_#{app_identifier}.mobileprovision,其中type表示prifile的型別,可能是AppStore、AdHoc、InHouse和Development。

  13. 檢測skip_install :skip_install指定是否安裝profile到鑰匙鏈中 如果:skip_install等於true,則執行[步驟15] 如果:skip_install等於false,則執行[步驟14]

  14. 安裝profile到鑰匙鏈中 將[步驟12]中的profile檔案複製到~/Library/MobileDevice/Provisioning Profiles/目錄下,檔名為#{uuid}.mobileprovision,其中uuid指的是profile的uuid

  15. 返回output_path路徑 返回:output_path指定的目錄路徑,然後退出程式

  16. 檢測readonly :readonly指定是否在AppleID賬號中建立新的profile 如果:readonly等於false,則執行[步驟9] 如果:readonly等於true,異常退出

3.1 獲取profile列表

獲取所有已建立的provisioning profiles的列表,然後經過一步步的過濾,最終得到所有可用的profile。

Fastlane證書管理(一):cert、sigh

  1. 下載所有的profile 所有的pofile是指AppleID賬號中看得到的所有provisioning profile(即使是invalid)和通過xcode建立的,通過xcode建立的profile不會顯示在AppleID中。

  2. 檢測development和adhoc :development:adhoc用來指定profile的型別,profile的型別總共有四種,分別是Development、AppStore、AdHoc、InHouse

  3. 檢測force 如果:force是true,則不會刪除不可用的profile,因為後面會強制建立新的profile,不會使用當前這些profile,也就無所謂可用還是不可用了。

  4. 過濾adhoc或appstore 下面是sigh的原始碼,個人猜測,下載profile時,返回的json資料中有一個叫做distributionMethod的key,這個key的取值範圍是['inhouse', 'store', 'limited', 'direct']。adhocappstore型別的profile返回的distributionMethod的值都是store。在本步驟之前都沒有區分adhocappstore,在這一步驟中,會根據profile中是否帶有device來區分這兩種型別。

 klass = case attrs['distributionMethod']
                  when 'limited'
                    Development
                  when 'store'
                    AppStore
                  when 'inhouse'
                    InHouse
                  when 'direct'
                    Direct # Mac-only
                  else
                    raise "Can't find class '#{attrs['distributionMethod']}'"
                  end
複製程式碼
  1. 刪除不可用證書的profile 每一個profile都會關聯一個簽名證書的陣列(開發環境的profile的證書陣列裡可以包含多個簽名證書,生產環境的profile只能包含一個簽名證書),檢測與profile相關聯的證書是否在本地鑰匙鏈中,如果不在,則刪除此profile。

3.2. 在AppleID中建立新的profile

下面是建立profile時,請求的引數

    params = {
        teamId: team_id,
        provisioningProfileName: name,
        appIdId: app_id,
        distributionType: distribution_method,
        certificateIds: certificate_ids,
        deviceIds: device_ids
      }
    params[:subPlatform] = sub_platform if sub_platform
    # if `template_name` is nil, Default entitlements will be used
    params[:template] = template_name if template_name
複製程式碼

想要在AppleID賬號中建立新的profile,首先需要獲取上述程式碼中的各個引數,主要是簽名證書列表、包含的裝置、釋出型別和名稱等

下圖中,步驟1到步驟9都是在篩選可用的簽名證書列表

Fastlane證書管理(一):cert、sigh

  1. 下載當前平臺和釋出模式的證書列表 比如當前使用的AppleID賬號是一個企業開發者賬號,且:platform等於ios:development:adhoc都等於false,則在本步驟中會下載ios平臺下所有的In-House簽名證書。

  2. 檢測cert_id和cert_owner_name :cert_id是簽名證書的唯一識別符號,:cert_owner_name是簽名證書所屬的team的name。

  3. 刪除不匹配的證書 當:cert_id有值,且證書的cert_id和它不相等,則從證書列表中刪除此證書; :cert_owner_name有值,且證書的cert_owner_name和它不相等,則從證書列表中刪除此證書;

  4. 檢測skip_certificate_verification

  5. 刪除不在鑰匙鏈中的證書 檢測證書是否在本地鑰匙鏈,其具體步驟可檢視2.1節的步驟4

  6. 檢測剩餘證書的數目 剩餘的證書資料為0,異常退出

  7. 檢測development

  8. 返回所有剩餘證書 開發環境下的profile可以包含多個簽名證書,所有返回所有的剩餘證書

  9. 返回剩餘證書中的第一個 生產環境下的profile只能包含一個簽名證書,所有返回剩餘證書中的第一個。如果想使用特定的簽名證書,最好使用:cert_id指定。

  10. 獲取profile的name 首先,如果有設定:provisioning_name,則使用設定的值作為profile的name;否則,使用#{bundle_id} #{profile_type}這種格式,比如com.fastlane.demo InHouse 然後,如果skip_fetch_profiles的值是fasle,則會去檢測這個名字是否已經被使用了,如果被使用了,就在這個名字後面加上一個空格和一個當前的時間戳。

  11. 獲取註冊裝置的ids 如果當前的釋出模式是AppStore、InHouse、Direct,即development=false and adhoc=false,ids等於空陣列; 否則,ids等於當前平臺的所有註冊裝置的id集合;

  12. 獲取其他引數 其他引數還包含:team_id、:app_identifier、:template_name等,:app_identifier指定的bundle_id必須在AppleID賬號中有建立對應的App ID,否則會異常退出。

  13. 生成併發出建立profile的請求 到了這一步,建立profile請求的引數都已經獲取到了,接下來就是發出這個請求。

下面再來看看建立profile時的請求引數

    params = {
        teamId: team_id,
        provisioningProfileName: name,
        appIdId: app_id,
        distributionType: distribution_method,
        certificateIds: certificate_ids,
        deviceIds: device_ids
      }
    params[:subPlatform] = sub_platform if sub_platform
    # if `template_name` is nil, Default entitlements will be used
    params[:template] = template_name if template_name
複製程式碼

建立profile的前提就是要構建好上述程式碼中的引數,而這些引數又依賴於執行fastlane sigh時傳入的外部引數。

下面列出了一些請求引數與外部引數的對照關係

請求引數 外部引數
teamId :team_id
provisioningProfileName :provisioning_name
appIdId :app_identifier
distributionType :adhoc、:development
certificateIds :cert_id、:cert_owner_name
deviceIds :platform、:development、:adhoc
subPlatform :platform
template template_name

通過:platform,可以指定建立profile時的平臺。它有三種取值,分別是mac、ios、tvos

:platform等於macios時,請求引數subPlatform等於nil;否則subPlatform等於tvos

相關文章