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

相关文章
相关标签/搜索