app_signer 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +52 -0
- data/bin/app_signer +25 -0
- data/lib/app_signer.rb +189 -0
- metadata +85 -0
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# app_signer
|
2
|
+
|
3
|
+
app_signer is a tool to re-sign iOS .app files to create a new .ipa with a given
|
4
|
+
provisioning profile and certificate.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
### Explanation of Parameters
|
9
|
+
|
10
|
+
* `app_path` - Path to the iOS .app file to be signed.
|
11
|
+
* `provisioning_profile_path` - Path to the provisioning profile to be used to
|
12
|
+
sign the ipa.
|
13
|
+
* `signing_identity` - Common name in the certificate to be used to sign the
|
14
|
+
ipa. This can be found by opening the keychain, right clicking the certificate,
|
15
|
+
and clicking 'get info'.
|
16
|
+
* `signing_identity_SHA1` - SHA1 of the certificate to be used to sign the app.
|
17
|
+
This can be found by opening the keychain, right clicking the certificate,
|
18
|
+
and clicking 'get info'. This is needed because it's possible to have two
|
19
|
+
certificates with the same common name.
|
20
|
+
* `generated_ipa_name` - Name to use for the generated ipa file
|
21
|
+
* `bundle_id` - Used to changed the bundle id during re-signing. *(optional)*
|
22
|
+
|
23
|
+
### In a Ruby Script
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require 'app_signer'
|
27
|
+
|
28
|
+
# Create signer
|
29
|
+
signer = AppSigner::Signer.new
|
30
|
+
|
31
|
+
# Set needed params
|
32
|
+
signer.app_path = # path to .app
|
33
|
+
signer.provisioning_profile_path = # path to provisioning profile
|
34
|
+
signer.signing_identity = # signing identity common name
|
35
|
+
signer.signing_identity_SHA1 = # signing identity SHA1
|
36
|
+
signer.generated_ipa_name = # name for generated ipa
|
37
|
+
signer.bundle_id = # optional new bundle id to use in info plist
|
38
|
+
|
39
|
+
# Create new ipa
|
40
|
+
signer.sign
|
41
|
+
```
|
42
|
+
|
43
|
+
### From the Command Line
|
44
|
+
|
45
|
+
```bash
|
46
|
+
app_signer --app-path PATH_TO_APP\
|
47
|
+
--profile-path PATH_TO_PROVISIONING_PROFILE\
|
48
|
+
--signing-identity SIGNING_IDENTITY\
|
49
|
+
--signing-identity-SHA1 SIGNING_IDENTITY_SHA1\
|
50
|
+
--ipa-name IPA_NAME\
|
51
|
+
--bundle-id OPTIONAL_NEW_BUNDLE_ID
|
52
|
+
```
|
data/bin/app_signer
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'app_signer'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'pathname'
|
6
|
+
require 'trollop'
|
7
|
+
|
8
|
+
opts = Trollop::options do
|
9
|
+
opt :app_path, "Path to .app file", :type => :string, :required => true
|
10
|
+
opt :profile_path, "Path to provisioning profile", :type => :string, :required => true
|
11
|
+
opt :signing_identity, "Signing Identity", :type => :string, :required => true
|
12
|
+
opt :signing_identity_SHA1, "SHA1 of Signing Identity", :type => :string, :required => true
|
13
|
+
opt :ipa_name, "Name of signed IPA", :type => :string, :required => true
|
14
|
+
opt :bundle_id, "bundle id to be used in signed IPA", :type => :string
|
15
|
+
end
|
16
|
+
|
17
|
+
signer = AppSigner::Signer.new
|
18
|
+
signer.app_path = opts[:app_path]
|
19
|
+
signer.provisioning_profile_path = opts[:profile_path]
|
20
|
+
signer.signing_identity = opts[:signing_identity]
|
21
|
+
signer.signing_identity_SHA1 = opts[:signing_identity_SHA1]
|
22
|
+
signer.generated_ipa_name = opts[:ipa_name]
|
23
|
+
signer.bundle_id = opts[:bundle_id]
|
24
|
+
|
25
|
+
signer.sign
|
data/lib/app_signer.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'plist'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
# AppSigner is an easy way to re-sign iOS apps. This module contains only one
|
5
|
+
# class {AppSigner::Signer} which contains all functionality.
|
6
|
+
module AppSigner
|
7
|
+
|
8
|
+
# Used to re-sign iOS .app files. Generates a .ipa based upon the set
|
9
|
+
# attributes when calling {AppSigner::Signer#sign}
|
10
|
+
class Signer
|
11
|
+
|
12
|
+
# @!attribute app_path
|
13
|
+
# @return [String] path to the .app file to be re-signed
|
14
|
+
attr_accessor :app_path
|
15
|
+
|
16
|
+
# @!attribute provisioning_profile_path
|
17
|
+
# @return [String] path to provisioning profile to be used during signing
|
18
|
+
attr_accessor :provisioning_profile_path
|
19
|
+
|
20
|
+
# @!attribute signing_identity
|
21
|
+
# @return [String] signing identity of the certificate to be used to sign
|
22
|
+
attr_accessor :signing_identity
|
23
|
+
|
24
|
+
# @!attribute bundle_id
|
25
|
+
# @return [String] bundle id to set in the re-signed ipa's info plist
|
26
|
+
attr_accessor :bundle_id
|
27
|
+
|
28
|
+
# @!attribute signing_identity_SHA1
|
29
|
+
# @return [String] SHA1 of certificate to be used during signing
|
30
|
+
attr_reader :signing_identity_SHA1
|
31
|
+
|
32
|
+
# @!attribute signing_identity_SHA1
|
33
|
+
# @return [String] name tp be used for the generated .ipa
|
34
|
+
attr_accessor :generated_ipa_name
|
35
|
+
|
36
|
+
# Sets signing_identity_SHA1 by removing spaces in the giving hash
|
37
|
+
def signing_identity_SHA1=(value)
|
38
|
+
@signing_identity_SHA1 = value.gsub(" ", "")
|
39
|
+
end
|
40
|
+
|
41
|
+
# Extracts XML from provisioning profile to be used to for obtaining proper
|
42
|
+
# entitlements
|
43
|
+
#
|
44
|
+
# @param signed_data [String] contents of provisioing profile
|
45
|
+
# @return [String]
|
46
|
+
def unwrap_signed_data(signed_data)
|
47
|
+
pkcs7 = OpenSSL::PKCS7.new(signed_data)
|
48
|
+
store = OpenSSL::X509::Store.new
|
49
|
+
flags = OpenSSL::PKCS7::NOVERIFY
|
50
|
+
pkcs7.verify([], store, nil, flags) # VERIFY IT SO WE CAN PULL OUT THE DATA
|
51
|
+
return pkcs7.data
|
52
|
+
end
|
53
|
+
|
54
|
+
# Checks to see if a givin certificate exists in the keychain by looking it
|
55
|
+
# up by the name and SHA1
|
56
|
+
#
|
57
|
+
# @param common_name [String] common name used in the certificate
|
58
|
+
# @param sha1 [String] SHA1 of the certificate
|
59
|
+
# @return [Boolean]
|
60
|
+
def cert_valid?(common_name, sha1)
|
61
|
+
certs = `security find-certificate -c "#{common_name}" -Z -p -a`
|
62
|
+
end_certificate = "-----END CERTIFICATE-----"
|
63
|
+
state = :searching
|
64
|
+
cert = ""
|
65
|
+
|
66
|
+
certs.lines.each do |line|
|
67
|
+
case state
|
68
|
+
when :searching
|
69
|
+
|
70
|
+
if line.include? sha1
|
71
|
+
state = :found_hash
|
72
|
+
end
|
73
|
+
|
74
|
+
when :found_hash
|
75
|
+
cert << line
|
76
|
+
if line.include? end_certificate
|
77
|
+
state = :did_end
|
78
|
+
end
|
79
|
+
when :did_end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if cert.empty?
|
84
|
+
throw 'Failed to find Signing Certificate'
|
85
|
+
end
|
86
|
+
|
87
|
+
File.open("pem", 'w') {|f| f.write(cert) }
|
88
|
+
system("security verify-cert -c \"pem\"")
|
89
|
+
File.unlink("pem")
|
90
|
+
return $?.success?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns a PList by parsing the provisioning profile at the given path
|
94
|
+
#
|
95
|
+
# @param path [String] path to plist to parse
|
96
|
+
# @return [PList]
|
97
|
+
def parse_provisioning_proflie(path)
|
98
|
+
# Read provisioning profile into signedData
|
99
|
+
signed_data=File.read(path)
|
100
|
+
# Parse profile
|
101
|
+
r = Plist::parse_xml(unwrap_signed_data(signed_data))
|
102
|
+
return r
|
103
|
+
end
|
104
|
+
|
105
|
+
# Raises an exception if attributes needed for signing aren't set
|
106
|
+
def validate_attributes
|
107
|
+
if self.app_path.nil?
|
108
|
+
raise 'app_path required to sign'
|
109
|
+
end
|
110
|
+
|
111
|
+
if self.provisioning_profile_path.nil?
|
112
|
+
raise 'provisioning_profile_path required to sign'
|
113
|
+
end
|
114
|
+
|
115
|
+
if self.signing_identity.nil?
|
116
|
+
raise 'signing_identity required to sign'
|
117
|
+
end
|
118
|
+
|
119
|
+
if self.signing_identity_SHA1.nil?
|
120
|
+
raise 'signing_identity_SHA1 required to sign'
|
121
|
+
end
|
122
|
+
|
123
|
+
if self.generated_ipa_name.nil?
|
124
|
+
raise 'generated_ipa_name required to sign'
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Creates an ipa by using the signing information given in the set
|
129
|
+
# attributes
|
130
|
+
def sign
|
131
|
+
|
132
|
+
validate_attributes
|
133
|
+
|
134
|
+
# Parse profile
|
135
|
+
r = parse_provisioning_proflie(self.provisioning_profile_path)
|
136
|
+
|
137
|
+
# Grab entitlements
|
138
|
+
entitlements=r['Entitlements']
|
139
|
+
|
140
|
+
if self.bundle_id
|
141
|
+
# Update info plist to have bundle id specified in the config
|
142
|
+
info_plist_path="#{app_path}/Info.plist"
|
143
|
+
system("plutil -convert xml1 \"#{info_plist_path}\"")
|
144
|
+
file_data=File.read(info_plist_path)
|
145
|
+
info_plist=Plist::parse_xml(file_data)
|
146
|
+
info_plist['CFBundleIdentifier']=self.bundle_id
|
147
|
+
|
148
|
+
# Save updated info plist and entitlements plist
|
149
|
+
info_plist.save_plist info_plist_path
|
150
|
+
end
|
151
|
+
|
152
|
+
entitlements.save_plist("#{app_path}/Entitlements.plist")
|
153
|
+
|
154
|
+
# Remove old embedded provisioning profile
|
155
|
+
File.unlink("#{app_path}/embedded.mobileprovision") if File.exists? "#{app_path}/embedded.mobileprovision"
|
156
|
+
|
157
|
+
# Embed new profile
|
158
|
+
FileUtils.cp_r(self.provisioning_profile_path,"#{app_path}/embedded.mobileprovision")
|
159
|
+
|
160
|
+
puts 'Verifying Signing Certificate'
|
161
|
+
unless cert_valid?(self.signing_identity, self.signing_identity_SHA1)
|
162
|
+
throw 'Failed to verify Signing Certificate'
|
163
|
+
end
|
164
|
+
puts 'Certificate Valid'
|
165
|
+
|
166
|
+
# Re-sign application using correct profile and entitlements
|
167
|
+
$stderr.puts "running /usr/bin/codesign -f -s \"#{self.signing_identity}\" --resource-rules=\"#{app_path}/ResourceRules.plist\" \"#{app_path}\""
|
168
|
+
result=system("/usr/bin/codesign -f -s \"#{self.signing_identity_SHA1}\" --resource-rules=\"#{app_path}/ResourceRules.plist\" --entitlements=\"#{app_path}/Entitlements.plist\" \"#{app_path}\"")
|
169
|
+
|
170
|
+
$stderr.puts "codesigning returned #{result}"
|
171
|
+
throw 'Codesigning failed' if !result
|
172
|
+
|
173
|
+
# Create temporary folder to zip up the application
|
174
|
+
app_folder=Pathname.new(app_path).dirname.to_s
|
175
|
+
temp_folder="#{app_folder}/temp_#{generated_ipa_name}"
|
176
|
+
Dir.mkdir(temp_folder)
|
177
|
+
Dir.mkdir("#{temp_folder}/Payload")
|
178
|
+
FileUtils.cp_r(app_path,"#{temp_folder}/Payload")
|
179
|
+
|
180
|
+
# Zip it up into the correct directory
|
181
|
+
system("pushd \"#{temp_folder}\" && /usr/bin/zip -r \"../#{generated_ipa_name}.ipa\" Payload")
|
182
|
+
|
183
|
+
# Remove temporary folder
|
184
|
+
FileUtils.rm_rf(temp_folder)
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: app_signer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew Carter
|
9
|
+
- WillowTree Apps
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2014-05-15 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: plist
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - '='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.1.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - '='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 3.1.0
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: trollop
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - '='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '2.0'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - '='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.0'
|
47
|
+
description: Sign an iOS .app package with a given provisioning profile.
|
48
|
+
email:
|
49
|
+
- andrew.carter@willowtreeapps.com
|
50
|
+
- andrew.carter@gmail.com
|
51
|
+
executables:
|
52
|
+
- app_signer
|
53
|
+
extensions: []
|
54
|
+
extra_rdoc_files: []
|
55
|
+
files:
|
56
|
+
- lib/app_signer.rb
|
57
|
+
- README.md
|
58
|
+
- bin/app_signer
|
59
|
+
homepage: https://github.com/willowtreeapps/app_signer
|
60
|
+
licenses:
|
61
|
+
- MIT
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.8.23
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Easily sign an iOS .app package.
|
84
|
+
test_files: []
|
85
|
+
has_rdoc: yard
|