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

1. 前言

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

2. cert

cert這個Tool下定義了兩個Command,分別是createrevoke_expired,其中create是默認Command。 能夠經過在終端中執行下列命令調用git

#調用create
fastlane cert
fastlane cert create

#調用revoke_expired
fastlane cert revoke_expired
複製代碼

除了在終端使用,cert還能夠在lane中被當作action來調用,這也是使用最頻繁的調用方式。 當cert被當作action被調用時,其效果和在終端調用fastlane cert [create]的效果是同樣的。json

cert中的create的做用是獲取簽名證書和其私鑰,而後將簽名證書和其私鑰(p12)導入到鑰匙鏈中。 爲了獲取證書,首先它會去檢測本地是否存在它想要的證書,若是沒有則它會去你的AppleID帳號中嘗試建立一個新的。數組

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

  1. 建立:output_path指向的目錄bash

  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]中獲取的;若是沒有,則在終端請求用戶輸入,而且會將用戶輸入的密碼存儲在鑰匙鏈中。app

  4. 登陸到蘋果開發網站 若是有兩步驗證,則還須要輸入對應手機的驗證碼ide

  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. 檢測本地證書

  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]時,其執行邏輯以下

前幾步與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。

  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都是在篩選可用的簽名證書列表

  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

相關文章
相關標籤/搜索