Class: AppSigner::Signer

Inherits:
Object
  • Object
show all
Defined in:
lib/app_signer.rb

Overview

Used to re-sign iOS .app files. Generates a .ipa based upon the set attributes when calling #sign

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#app_pathString

Returns path to the .app file to be re-signed.

Returns:

  • (String)

    path to the .app file to be re-signed



14
15
16
# File 'lib/app_signer.rb', line 14

def app_path
  @app_path
end

#bundle_idString

Returns bundle id to set in the re-signed ipa’s info plist.

Returns:

  • (String)

    bundle id to set in the re-signed ipa’s info plist



26
27
28
# File 'lib/app_signer.rb', line 26

def bundle_id
  @bundle_id
end

#generated_ipa_nameObject

Returns the value of attribute generated_ipa_name.



34
35
36
# File 'lib/app_signer.rb', line 34

def generated_ipa_name
  @generated_ipa_name
end

#provisioning_profile_pathString

Returns path to provisioning profile to be used during signing.

Returns:

  • (String)

    path to provisioning profile to be used during signing



18
19
20
# File 'lib/app_signer.rb', line 18

def provisioning_profile_path
  @provisioning_profile_path
end

#signing_identityString

Returns signing identity of the certificate to be used to sign.

Returns:

  • (String)

    signing identity of the certificate to be used to sign



22
23
24
# File 'lib/app_signer.rb', line 22

def signing_identity
  @signing_identity
end

#signing_identity_SHA1String

Returns name tp be used for the generated .ipa.

Returns:

  • (String)

    name tp be used for the generated .ipa



30
31
32
# File 'lib/app_signer.rb', line 30

def signing_identity_SHA1
  @signing_identity_SHA1
end

Instance Method Details

#cert_valid?(common_name, sha1) ⇒ Boolean

Checks to see if a givin certificate exists in the keychain by looking it up by the name and SHA1

Parameters:

  • common_name (String)

    common name used in the certificate

  • sha1 (String)

    SHA1 of the certificate

Returns:

  • (Boolean)


60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/app_signer.rb', line 60

def cert_valid?(common_name, sha1)
  certs = `security find-certificate -c "#{common_name}" -Z -p -a`
  end_certificate = "-----END CERTIFICATE-----"
  state = :searching
  cert = ""

  certs.lines.each do |line|
    case state
      when :searching

        if line.include? sha1
          state = :found_hash
        end

      when :found_hash
        cert << line
      if line.include? end_certificate
        state = :did_end
      end
      when :did_end
    end
  end

  if cert.empty?
    throw 'Failed to find Signing Certificate'
  end

  File.open("pem", 'w') {|f| f.write(cert) }
  system("security verify-cert -c \"pem\"")
  File.unlink("pem")
  return $?.success?
end

#parse_provisioning_proflie(path) ⇒ PList

Returns a PList by parsing the provisioning profile at the given path

Parameters:

  • path (String)

    path to plist to parse

Returns:

  • (PList)


97
98
99
100
101
102
103
# File 'lib/app_signer.rb', line 97

def parse_provisioning_proflie(path)
  # Read provisioning profile into signedData
  signed_data=File.read(path)
  # Parse profile
  r = Plist::parse_xml(unwrap_signed_data(signed_data))
  return r
end

#signObject

Creates an ipa by using the signing information given in the set attributes



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/app_signer.rb', line 130

def sign

  validate_attributes

  # Parse profile
  r = parse_provisioning_proflie(self.provisioning_profile_path)

  # Grab entitlements
  entitlements=r['Entitlements']

  if self.bundle_id
    # Update info plist to have bundle id specified in the config
    info_plist_path="#{app_path}/Info.plist"
    system("plutil -convert xml1 \"#{info_plist_path}\"")
    file_data=File.read(info_plist_path)
    info_plist=Plist::parse_xml(file_data)
    info_plist['CFBundleIdentifier']=self.bundle_id

    # Save updated info plist and entitlements plist
    info_plist.save_plist info_plist_path
  end

  entitlements.save_plist("#{app_path}/Entitlements.plist")

  # Remove old embedded provisioning profile
  File.unlink("#{app_path}/embedded.mobileprovision") if File.exists? "#{app_path}/embedded.mobileprovision"

  # Embed new profile
  FileUtils.cp_r(self.provisioning_profile_path,"#{app_path}/embedded.mobileprovision")

  puts 'Verifying Signing Certificate'
  unless cert_valid?(self.signing_identity, self.signing_identity_SHA1)
    throw 'Failed to verify Signing Certificate'
  end
  puts 'Certificate Valid'

  # Re-sign application using correct profile and entitlements
  $stderr.puts "running /usr/bin/codesign -f -s \"#{self.signing_identity}\" --resource-rules=\"#{app_path}/ResourceRules.plist\" \"#{app_path}\""
  result=system("/usr/bin/codesign -f -s \"#{self.signing_identity_SHA1}\" --resource-rules=\"#{app_path}/ResourceRules.plist\" --entitlements=\"#{app_path}/Entitlements.plist\" \"#{app_path}\"")

  $stderr.puts "codesigning returned #{result}"
  throw 'Codesigning failed' if !result

  # Create temporary folder to zip up the application
  app_folder=Pathname.new(app_path).dirname.to_s
  temp_folder="#{app_folder}/temp_#{generated_ipa_name}"
  Dir.mkdir(temp_folder)
  Dir.mkdir("#{temp_folder}/Payload")
  FileUtils.cp_r(app_path,"#{temp_folder}/Payload")

  # Zip it up into the correct directory
  system("pushd \"#{temp_folder}\" && /usr/bin/zip -r \"../#{generated_ipa_name}.ipa\" Payload")

  # Remove temporary folder
  FileUtils.rm_rf(temp_folder)
end

#unwrap_signed_data(signed_data) ⇒ String

Extracts XML from provisioning profile to be used to for obtaining proper entitlements

Parameters:

  • signed_data (String)

    contents of provisioing profile

Returns:

  • (String)


46
47
48
49
50
51
52
# File 'lib/app_signer.rb', line 46

def unwrap_signed_data(signed_data)
  pkcs7 = OpenSSL::PKCS7.new(signed_data)
  store = OpenSSL::X509::Store.new
  flags = OpenSSL::PKCS7::NOVERIFY
  pkcs7.verify([], store, nil, flags) # VERIFY IT SO WE CAN PULL OUT THE DATA
  return pkcs7.data
end

#validate_attributesObject

Raises an exception if attributes needed for signing aren’t set



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/app_signer.rb', line 106

def validate_attributes
  if self.app_path.nil?
    raise 'app_path required to sign'
  end

  if self.provisioning_profile_path.nil?
    raise 'provisioning_profile_path required to sign'
  end

  if self.signing_identity.nil?
    raise 'signing_identity required to sign'
  end

  if self.signing_identity_SHA1.nil?
    raise 'signing_identity_SHA1 required to sign'
  end

  if self.generated_ipa_name.nil?
    raise 'generated_ipa_name required to sign'
  end
end