passbook2 1.0.0

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.
@@ -0,0 +1,75 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module Passbook
5
+ class Signer
6
+ attr_accessor :certificate,
7
+ :password,
8
+ :rsa_private_key,
9
+ :apple_intermediate_cert,
10
+ :p12_cert
11
+ attr_reader :key_hash
12
+
13
+ def initialize(params = {})
14
+
15
+ # Path to your X509 cert. This is downloaded after generating
16
+ # a certificate from your apple Pass Type ID on apple's developer site
17
+ @certificate = params[:certificate] || Passbook.certificate
18
+
19
+ # Path to the .pem file generated from public key of the RSA keypair
20
+ # that was generated when you made a Certificate Signing Request
21
+ # It'll be in your keychain under the "Common Name" you specified
22
+ # for the signing request.
23
+ @rsa_private_key = params[:rsa_private_key] || Passbook.rsa_private_key
24
+
25
+ # this should be the password that goes along with the rsa public key
26
+ @password = params[:password] || Passbook.password
27
+
28
+ # "Apple Intermediate Certificate Worldwide Developer Relations" certificate
29
+ # downloaded from here <https://www.apple.com/certificateauthority/>
30
+ # Path to your Apple Intermediate Certificate Worldwide Developer Relations
31
+ # cert.
32
+ # downloaded from here https://www.apple.com/certificateauthority/
33
+ # download that .cer file (binary)
34
+ @apple_intermediate_cert = params[:apple_intermediate_cert] || Passbook.apple_intermediate_cert
35
+ compute_cert
36
+ end
37
+
38
+ def sign(data)
39
+ apple_cert = OpenSSL::X509::Certificate.new file_data(apple_intermediate_cert)
40
+ # In PKCS#7 SignedData, attached and detached formats are supported… In
41
+ # detached format, data that is signed is not embedded inside the
42
+ # SignedData package instead it is placed at some external location…
43
+
44
+ pk7 = OpenSSL::PKCS7.sign(
45
+ key_hash[:certificate],
46
+ key_hash[:rsa_private_key],
47
+ data.to_s,
48
+ [apple_cert],
49
+ OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::DETACHED
50
+ )
51
+ pk7_data = OpenSSL::PKCS7.write_smime pk7
52
+
53
+ str_debut = "filename=\"smime.p7s\"\n\n"
54
+ pk7_data = pk7_data[pk7_data.index(str_debut)+str_debut.length..pk7_data.length-1]
55
+ str_end = "\n\n------"
56
+ pk7_data = pk7_data[0..pk7_data.index(str_end)-1]
57
+
58
+ Base64.decode64(pk7_data)
59
+ end
60
+
61
+ def compute_cert
62
+ @key_hash = {
63
+ rsa_private_key: OpenSSL::PKey::RSA.new(file_data(rsa_private_key), password),
64
+ certificate: OpenSSL::X509::Certificate.new(file_data(certificate))
65
+ }
66
+ end
67
+
68
+ def file_data(data)
69
+ raise "file_data passed nil" if data.nil?
70
+ return data if data.is_a? String
71
+
72
+ data.respond_to?(:read) ? data.read : File.read(data)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,3 @@
1
+ module Passbook
2
+ VERSION = "1.0.0"
3
+ end
data/lib/passbook2.rb ADDED
@@ -0,0 +1,21 @@
1
+ require "passbook/version"
2
+ require "passbook/pkpass"
3
+ require "passbook/pk_multi_pass"
4
+ require "passbook/signer"
5
+ require 'active_support/core_ext/module/attribute_accessors'
6
+ require 'passbook/push_notification'
7
+ require 'grocer'
8
+
9
+ module Passbook
10
+ mattr_accessor :certificate,
11
+ :password,
12
+ :apple_intermediate_cert,
13
+ :rsa_private_key,
14
+ :notification_cert,
15
+ :notification_gateway,
16
+ :notification_passphrase
17
+
18
+ def self.configure
19
+ yield self
20
+ end
21
+ end
data/passbook.gemspec ADDED
@@ -0,0 +1,59 @@
1
+ require_relative "lib/passbook/version"
2
+
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "passbook2"
6
+ s.version = Passbook::VERSION
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9
+ s.require_paths = ["lib"]
10
+ s.authors = ["Thomas Lauro", "Lance Gleason", "Kay Rhodes"]
11
+ s.date = "2024-06-11"
12
+ s.description = "This gem allows you to create Apple Passbook files."
13
+ s.email = ["thomas@lauro.fr", "lgleason@polyglotprogramminginc.com", "masukomi@masukomi.org"]
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".travis.yml",
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "LICENSE",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/passbook2.rb",
27
+ "lib/passbook/pk_multi_pass.rb",
28
+ "lib/passbook/pkpass.rb",
29
+ "lib/passbook/push_notification.rb",
30
+ "lib/passbook/signer.rb",
31
+ "lib/passbook/version.rb",
32
+ "passbook.gemspec",
33
+ "spec/data/icon.png",
34
+ "spec/data/icon@2x.png",
35
+ "spec/data/logo.png",
36
+ "spec/data/logo@2x.png",
37
+ "spec/lib/passbook/pk_multi_pass_spec.rb",
38
+ "spec/lib/passbook/pkpass_spec.rb",
39
+ "spec/lib/passbook/push_notification_spec.rb",
40
+ "spec/lib/passbook/signer_spec.rb",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+ s.homepage = "https://github.com/masukomi/passbook2"
44
+ s.licenses = ["MIT"]
45
+ s.rubygems_version = "3.4.17"
46
+ s.summary = "An Apple Passbook file generator."
47
+
48
+ s.specification_version = 4
49
+
50
+ # runtime dependencies
51
+ s.add_dependency(%q<grocer>, [">= 0"])
52
+ s.add_dependency(%q<rubyzip>, [">= 1.0.0"])
53
+ s.add_dependency(%q<activesupport>.freeze, [">= 0"])
54
+
55
+ # development dependencies
56
+ s.add_development_dependency(%q<debug>, [">= 0"])
57
+ s.add_development_dependency(%q<rspec>, [">= 0"])
58
+ s.add_development_dependency(%q<yard>, [">= 0"])
59
+ end
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'tmpdir'
3
+ require 'zip'
4
+
5
+ describe Passbook do
6
+ let(:content) do
7
+ {
8
+ formatVersion: 1,
9
+ passTypeIdentifier: 'pass.passbook.test',
10
+ serialNumber: '001',
11
+ teamIdentifier: ENV['APPLE_TEAM_ID'],
12
+ organizationName: 'WorldCo',
13
+ description: 'description',
14
+ eventTicket: {
15
+ primaryFields: [
16
+ {
17
+ key: 'date',
18
+ label: 'DATE',
19
+ value: 'date'
20
+ }
21
+ ]
22
+ }
23
+ }
24
+ end
25
+
26
+ let(:signer) { double 'signer' }
27
+ let(:pass) { Passbook::PKPass.new( content.to_json, signer) }
28
+ let(:base_path) { 'spec/data' }
29
+ let(:entries) { ['pass.json', 'manifest.json', 'signature', 'icon.png', 'icon@2x.png', 'logo.png', 'logo@2x.png'] }
30
+ let(:passes){ [pass, pass] }
31
+
32
+ before :each do
33
+ pass.add_files(
34
+ [
35
+ "#{base_path}/icon.png",
36
+ "#{base_path}/icon@2x.png",
37
+ "#{base_path}/logo.png",
38
+ "#{base_path}/logo@2x.png"
39
+ ]
40
+ )
41
+ allow(signer).to(receive(:sign).and_return('Signed by the Honey Badger'))
42
+ end
43
+
44
+ describe ".create_multi_pass" do
45
+ it "should create a file where specified" do
46
+ Dir.mktmpdir do |dir|
47
+ temp_file = Passbook::PKMultiPass.create_multipass(
48
+ passes,
49
+ File.join(dir, "foo.pkpasses")
50
+ )
51
+ expect(File.exist?(temp_file)).to(eq(true))
52
+ end
53
+ end
54
+
55
+ it "should contain 2 files", :aggregate_failures do
56
+ Dir.mktmpdir do |dir|
57
+ temp_file = Passbook::PKMultiPass.create_multipass(
58
+ passes,
59
+ File.join(dir, "foo.pkpasses")
60
+ )
61
+ entries = Zip::File.open(temp_file).entries
62
+ expect(entries.count).to(eq(2))
63
+ expect(entries.map(&:name)).to(match_array(["1.pkpass", "2.pkpass"]))
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+ require 'tmpdir'
4
+
5
+ describe Passbook do
6
+ let(:content) do
7
+ {
8
+ formatVersion: 1,
9
+ passTypeIdentifier: 'pass.passbook.test',
10
+ serialNumber: '001',
11
+ teamIdentifier: ENV['APPLE_TEAM_ID'],
12
+ relevantDate: '2012-10-02',
13
+ locations: [ # TODO
14
+ {
15
+ longitude: 2.35403,
16
+ latitude: 48.893855
17
+ }
18
+ ],
19
+ organizationName: 'WorldCo',
20
+ description: 'description',
21
+ foregroundColor: 'rgb(227,210,18)',
22
+ backgroundColor: 'rgb(60, 65, 76)',
23
+ logoText: 'Event',
24
+ eventTicket: {
25
+ primaryFields: [
26
+ {
27
+ key: 'date',
28
+ label: 'DATE',
29
+ value: 'date'
30
+ }
31
+ ],
32
+ backFields: [
33
+ {
34
+ key: 'description',
35
+ label: 'DESCRIPTION',
36
+ value: 'description'
37
+ },
38
+ {
39
+ key: 'aboutUs',
40
+ label: 'MORE',
41
+ value: 'about us'
42
+ }
43
+ ]
44
+ }
45
+ }
46
+ end
47
+
48
+ let(:signer) { double 'signer' }
49
+ let(:pass) { Passbook::PKPass.new content.to_json, signer }
50
+ let(:base_path) { 'spec/data' }
51
+ let(:entries) { ['pass.json', 'manifest.json', 'signature', 'icon.png', 'icon@2x.png', 'logo.png', 'logo@2x.png'] }
52
+
53
+ before :each do
54
+ allow(signer).to(receive(:sign).and_return('Signed by the Honey Badger'))
55
+ end
56
+ describe '#file' do
57
+ context 'when adding a file as File' do
58
+ before do
59
+ pass.add_file(File.new("#{base_path}/icon.png"))
60
+ pass.add_files(
61
+ [
62
+ "#{base_path}/icon@2x.png",
63
+ "#{base_path}/logo.png",
64
+ "#{base_path}/logo@2x.png"
65
+ ].map { |x| File.new(x) }
66
+ )
67
+ end
68
+
69
+ it 'should work with no options', :aggregate_failures do
70
+ temp_file = pass.file
71
+ expect(File.basename(temp_file.path)).to(eq('pass.pkpass'))
72
+ expect(File.dirname(temp_file)).to(eq(Dir.tmpdir))
73
+ end
74
+ end
75
+ context 'when adding files as strings' do
76
+ before do
77
+ pass.add_file("#{base_path}/icon.png")
78
+ pass.add_files(
79
+ [
80
+ "#{base_path}/icon@2x.png",
81
+ "#{base_path}/logo.png",
82
+ "#{base_path}/logo@2x.png"
83
+ ]
84
+ )
85
+ end
86
+
87
+ it 'should work with no options', :aggregate_failures do
88
+ temp_file = pass.file
89
+ expect(File.basename(temp_file.path)).to(eq('pass.pkpass'))
90
+ expect(File.dirname(temp_file)).to(eq(Dir.tmpdir))
91
+ end
92
+ it 'should honor the file_name specified' do
93
+ temp_file = pass.file(file_name: 'foo.pkpass')
94
+ expect(File.basename(temp_file)).to(eq('foo.pkpass'))
95
+ end
96
+
97
+ it 'should honor the directory specified as a string' do
98
+ Dir.mktmpdir do |dir| # dir is a String
99
+ temp_file = pass.file(directory: dir)
100
+ expect(File.dirname(temp_file)).to(eq(dir))
101
+ end
102
+ end
103
+
104
+ it 'should honor the directory specified as a Dir' do
105
+ Dir.mktmpdir do |dir|
106
+ temp_file = pass.file(directory: Dir.new(dir))
107
+ expect(File.dirname(temp_file)).to(eq(dir))
108
+ end
109
+ end
110
+ context 'outputs' do
111
+ before do
112
+ @file_entries = []
113
+ Zip::InputStream.open(zip_path) do |io|
114
+ while (entry = io.get_next_entry)
115
+ @file_entries << entry.name
116
+ end
117
+ end
118
+ end
119
+
120
+ context 'zip file' do
121
+ let(:zip_path) { pass.file.path }
122
+
123
+ it 'should have the expected files' do
124
+ expect(entries).to(eq(@file_entries))
125
+ end
126
+ end
127
+
128
+ context 'StringIO' do
129
+ let(:temp_file) { Tempfile.new('pass.pkpass') }
130
+ let(:zip_path) do
131
+ zip_out = pass.stream
132
+ expect(zip_out.class).to(eq(StringIO))
133
+ # creating file, re-reading zip to see if correctly formed
134
+ temp_file.write zip_out.string
135
+ temp_file.close
136
+ temp_file.path
137
+ end
138
+
139
+ it 'should contain the expected files' do
140
+ expect(entries).to(eq(@file_entries))
141
+ end
142
+
143
+ after do
144
+ temp_file.delete
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ # TODO: find a proper way to do this
152
+ context 'Error catcher' do
153
+ context 'formatVersion' do
154
+ let(:base_path) { 'spec/data' }
155
+
156
+ before :each do
157
+ pass.add_files ["#{base_path}/icon.png", "#{base_path}/icon@2x.png", "#{base_path}/logo.png",
158
+ "#{base_path}/logo@2x.png"]
159
+ tpass = JSON.parse(pass.pass)
160
+ tpass['formatVersion'] = 'It should be a numeric'
161
+ pass.pass = tpass.to_json
162
+ end
163
+
164
+ it 'raise an error' do
165
+ expect { pass.build }.to raise_error('Format Version should be a numeric')
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'grocer'
3
+
4
+ describe Passbook::PushNotification do
5
+
6
+ context 'send notification' do
7
+ let(:grocer_pusher) {double 'Grocer'}
8
+ let(:notification) {double 'Grocer::Notification'}
9
+ let(:notification_settings) {{:certificate => './notification_cert.pem', :gateway => 'honeybadger.apple.com', :passphrase => 'ah@rdvintAge'}}
10
+
11
+ before :each do
12
+ allow(Passbook).to(receive(:notification_cert).and_return('./notification_cert.pem'))
13
+ allow(Grocer::PassbookNotification).to(receive(:new).with(:device_token => 'my token').and_return(notification))
14
+ allow(grocer_pusher).to(receive(:push).with(notification).and_return(55))
15
+ allow(Grocer).to(receive(:pusher).with(notification_settings).and_return(grocer_pusher))
16
+ allow(Passbook).to(receive(:notification_gateway).and_return('honeybadger.apple.com'))
17
+ allow(Passbook).to(receive(:notification_passphrase).and_return('ah@rdvintAge'))
18
+ end
19
+
20
+ subject {Passbook::PushNotification.send_notification('my token')}
21
+ it {should eq 55}
22
+ end
23
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Signer' do
4
+ context 'signatures' do
5
+
6
+ context 'using default config info' do
7
+ before do
8
+ expect(Passbook).to(receive(:password).and_return 'password')
9
+ expect(Passbook).to(receive(:rsa_private_key).and_return 'my_rsa_key')
10
+ expect(Passbook).to(receive(:certificate).and_return 'my_X509_certificate')
11
+ expect(Passbook).to(
12
+ receive(:apple_intermediate_cert)
13
+ .and_return( 'apple_intermediate_cert_file')
14
+ )
15
+ end
16
+
17
+ context "getting the default config" do
18
+ context "ignoring signing" do
19
+ before do
20
+ # don't care if signing works yet. just dealing with defaults
21
+ allow_any_instance_of(Passbook::Signer).to(receive(:compute_cert))
22
+ end
23
+
24
+ let(:signer){
25
+ Passbook::Signer.new
26
+ }
27
+ it "should contain rsa_private_key" do
28
+ expect(signer.rsa_private_key).to(eq('my_rsa_key'))
29
+ end
30
+ it "should contain certificate" do
31
+ expect(signer.certificate).to(eq('my_X509_certificate'))
32
+ end
33
+ it "should contain apple_intermediate_cert" do
34
+ expect(signer.apple_intermediate_cert).to(eq('apple_intermediate_cert_file'))
35
+ end
36
+ it "should contain password" do
37
+ expect(signer.password).to(eq('password'))
38
+ end
39
+ end
40
+ end
41
+ context "computing cert" do
42
+ before do
43
+ expect(OpenSSL::PKey::RSA).to(
44
+ receive(:new)
45
+ .with('my_rsa_key', 'password')
46
+ .and_return 'my_rsa_key' # bogus example
47
+ )
48
+
49
+ expect(OpenSSL::X509::Certificate).to(
50
+ receive(:new)
51
+ .with('my_X509_certificate')
52
+ .and_return 'my_X509_data' # bogus example
53
+ )
54
+ end
55
+ context "signing" do
56
+ let(:signer){Passbook::Signer.new}
57
+
58
+ # this is just checking that
59
+ # the inputs and outputs of the cert things
60
+ # are all wired together correctly
61
+ it "should do the signing dance" do
62
+
63
+ # 2nd X509 cert loading...
64
+ expect(OpenSSL::X509::Certificate).to(
65
+ receive(:new)
66
+ .with('apple_intermediate_cert_file')
67
+ .and_return 'apples_X509_data' # bogus example
68
+ )
69
+
70
+ expect(OpenSSL::PKCS7).to(
71
+ receive(:sign)
72
+ .with(
73
+ 'my_X509_data', # from above
74
+ 'my_rsa_key', # also from above
75
+ 'passbook_sign_data', # the param to Signer#sign(...)
76
+ ['apples_X509_data'],
77
+ OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::DETACHED
78
+ )
79
+ .and_return('pk7') # bogus example
80
+ )
81
+ expect(OpenSSL::PKCS7).to(
82
+ receive(:write_smime)
83
+ .with('pk7')
84
+ .and_return("filename=\"smime.p7s\"\n\nsecret_pk7_stuff\n\n------")
85
+ )
86
+ expect(Base64).to(
87
+ receive(:decode64)
88
+ .with('secret_pk7_stuff') #from above
89
+ )
90
+
91
+ # and... kick it all off
92
+ signer.sign('passbook_sign_data')
93
+
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'json'
4
+ require 'grocer'
5
+ require 'debug'
6
+
7
+ Dir['lib/passbook/**/*.rb'].each {|f| require File.join(File.dirname(__FILE__), '..', f.gsub(/.rb/, ''))}
8
+ Dir['lib/rack/**/*.rb'].each {|f| require File.join(File.dirname(__FILE__), '..', f.gsub(/.rb/, ''))}
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: passbook2
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Lauro
8
+ - Lance Gleason
9
+ - Kay Rhodes
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2024-06-11 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: grocer
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rubyzip
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 1.0.0
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.0.0
43
+ - !ruby/object:Gem::Dependency
44
+ name: activesupport
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: debug
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: rspec
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ - !ruby/object:Gem::Dependency
86
+ name: yard
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ description: This gem allows you to create Apple Passbook files.
100
+ email:
101
+ - thomas@lauro.fr
102
+ - lgleason@polyglotprogramminginc.com
103
+ - masukomi@masukomi.org
104
+ executables: []
105
+ extensions: []
106
+ extra_rdoc_files:
107
+ - LICENSE
108
+ - README.md
109
+ files:
110
+ - ".travis.yml"
111
+ - Gemfile
112
+ - Gemfile.lock
113
+ - LICENSE
114
+ - README.md
115
+ - Rakefile
116
+ - VERSION
117
+ - lib/passbook/pk_multi_pass.rb
118
+ - lib/passbook/pkpass.rb
119
+ - lib/passbook/push_notification.rb
120
+ - lib/passbook/signer.rb
121
+ - lib/passbook/version.rb
122
+ - lib/passbook2.rb
123
+ - passbook.gemspec
124
+ - spec/data/icon.png
125
+ - spec/data/icon@2x.png
126
+ - spec/data/logo.png
127
+ - spec/data/logo@2x.png
128
+ - spec/lib/passbook/pk_multi_pass_spec.rb
129
+ - spec/lib/passbook/pkpass_spec.rb
130
+ - spec/lib/passbook/push_notification_spec.rb
131
+ - spec/lib/passbook/signer_spec.rb
132
+ - spec/spec_helper.rb
133
+ homepage: https://github.com/masukomi/passbook2
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.4.20
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: An Apple Passbook file generator.
156
+ test_files: []