apple_push_certs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 222f59f98f7017522ba67147f454abe783bdd50f
4
+ data.tar.gz: 576094ed03822ed7dc475dbe52ff73c477cbd51a
5
+ SHA512:
6
+ metadata.gz: b91d64ad310dc91b7a8c636a3ed5a44866b274bd30afbde26c36fb444acc0ec24d09966108eda9a9f0360b33b6d58e411901556edb802e01778c9839e674d6d0
7
+ data.tar.gz: 244b0195fe1b812b9fa8d1e479c4e0f857ce4d20c74c9e11ed6f29e0362be6451482d92d053938885a4a80c6d954b8be59ad5b5d0c34328f0c91fadf24c3353c
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,50 @@
1
+ # Apple Push Certs
2
+
3
+ Helps with the process of getting a certificate for the Apple Push Notification Service.
4
+
5
+ This gem:
6
+
7
+ * Creates private keys
8
+ * Creates CSRs
9
+ * Signs CSRs
10
+ * Submits signed CSRs to Apple
11
+ * Retrieves push certificates from Apple
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'apple_push_certs'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install apple_push_certs
26
+
27
+ ## Usage
28
+
29
+ ### Create a New Certificate
30
+
31
+ include 'ApplePushCerts' # for the sake of terseness
32
+
33
+ key = Customer::Key.new
34
+ csr = Customer::CSR.new key
35
+
36
+ signed_csr = SignedCSR.new 'my_signing_cert.p12', 'my_cert_password', csr
37
+
38
+ certificate = Certificate.new signed_csr, 'apple_id', 'password'
39
+
40
+
41
+ ### Renew an Existing Certificate
42
+
43
+ key = [existing key]
44
+ certificate = [existing certificate] # should be of type OpenSSL::X509::Certificate
45
+
46
+ csr = Customer::CSR.new key
47
+
48
+ signed_csr = SignedCSR.new 'my_signing_cert.p12', 'my_cert_password', csr
49
+
50
+ certificate = Certificate.renew certificate, signed_csr, 'apple_id', 'password'
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'apple_push_certs/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "apple_push_certs"
8
+ spec.version = ApplePushCerts::VERSION
9
+ spec.authors = ["Taylor Boyko"]
10
+ spec.email = ["taylor@wrprojects.com"]
11
+ spec.description = %q{Generate Apple Push CSRs and Retrieve Certificates from the Apple Push Certificates Portal}
12
+ spec.summary = %q{}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency 'rspec'
24
+ spec.add_dependency 'plist'
25
+ spec.add_dependency 'multipart-post'
26
+ end
@@ -0,0 +1,3 @@
1
+ Dir[File.join(File.dirname(__FILE__), 'apple_push_certs', '**', '*.rb')].each do |file|
2
+ require file
3
+ end
@@ -0,0 +1,129 @@
1
+ require 'net/http'
2
+ require 'net/http/post/multipart'
3
+ require 'json'
4
+ require 'openssl'
5
+
6
+ module ApplePushCerts
7
+ class Certificate < OpenSSL::X509::Certificate
8
+
9
+ def initialize(signed_csr, apple_id, password)
10
+ cookie = self.class.sign_in(apple_id, password)
11
+ cert = self.class.generate_cert(cookie, signed_csr)
12
+
13
+ super cert
14
+ end
15
+
16
+ def self.renew(certificate, signed_csr, apple_id, password)
17
+ cookie = sign_in(apple_id, password)
18
+ cert = renew_cert(certificate, cookie, signed_csr)
19
+ end
20
+
21
+ private
22
+
23
+ # returns a cookie
24
+ def self.sign_in(apple_id, password)
25
+ uri = '/cgi-bin/WebObjects/DSAuthWeb.woa/wa/login?appIdKey=3fbfc9ad8dfedeb78be1d37f6458e72adc3160d1ad5b323a9e5c5eb2f8e7e3e2&rv=2'
26
+
27
+ http = Net::HTTP.new('daw.apple.com', 443)
28
+ http.use_ssl = true
29
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
30
+
31
+ # get form validation token
32
+
33
+ res = http.get(uri)
34
+ form_url = res.body.match(/action=\"(\/cgi\-bin[^\"]+)\"/)[1]
35
+ wosid = form_url.match(/([^\/]+)\/[^\/]+$/)[1]
36
+
37
+ # sign in
38
+
39
+ data = "theAccountName=#{apple_id}&theAccountPW=#{password}&wosid=#{wosid}"
40
+ headers = {
41
+ 'Content-Type' => 'application/x-www-form-urlencoded'
42
+ }
43
+
44
+ res = http.post(form_url, data, headers)
45
+
46
+ cookie = res.response['set-cookie']
47
+ raise AppleSignInError, "incorrect apple id or password" unless cookie
48
+
49
+ cookie
50
+ end
51
+
52
+ # returns a string containing the newly generated certificate
53
+ def self.generate_cert(cookie, signed_csr)
54
+ http = Net::HTTP.new('identity.apple.com', 443)
55
+ http.use_ssl = true
56
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
57
+
58
+ headers = {
59
+ 'Cookie' => cookie
60
+ }
61
+
62
+ # upload csr
63
+
64
+ scsr_io = StringIO.new signed_csr
65
+ req = Net::HTTP::Post::Multipart.new '/pushcert/create', {upfile: UploadIO.new(scsr_io, 'application/octet-stream')}, headers
66
+
67
+ res = http.request(req)
68
+ res_json = JSON.parse(res.body)
69
+ serial = res_json['CertSN']
70
+
71
+ raise StandardError, "apple did not respond with a serial number. response: #{res.body}" unless serial
72
+
73
+ # download cert
74
+
75
+ download_certificate(serial, http, headers)
76
+ end
77
+
78
+ def self.renew_cert(certificate, cookie, signed_csr)
79
+
80
+ # connect
81
+
82
+ http = Net::HTTP.new('identity.apple.com', 443)
83
+ http.use_ssl = true
84
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
85
+
86
+ headers = {
87
+ 'Cookie' => cookie
88
+ }
89
+
90
+ # get existing cert listing
91
+
92
+ res = http.get("/pushcert/userinfo", headers)
93
+ res_json = JSON.parse res.body
94
+
95
+ token = (certificate.subject.to_s.match /APSP:([^\/]+)/)[1]
96
+ raise AppleCertificateNotFoundError, "apple does not have any certificates on file" unless res_json['CertDetails']
97
+ cert_json = res_json['CertDetails'].select { |c| c['CertificateToken'].match token }.first
98
+ raise AppleCertificateNotFoundError, "apple does not have that certificate on file" unless cert_json
99
+
100
+ # upload csr
101
+
102
+ uri = "/pushcert/renew?csn=#{URI.escape(cert_json['SerialNumber'])}"
103
+ uri << "&idn=#{URI.escape(cert_json['IssuerDN'])}"
104
+ uri << "&ctkn=#{URI.escape(cert_json['CertificateToken'])}"
105
+
106
+ scsr_io = StringIO.new signed_csr
107
+ req = Net::HTTP::Post::Multipart.new uri, {upfile: UploadIO.new(scsr_io, 'application/octet-stream')}, headers
108
+
109
+ res = http.request(req)
110
+ res_json = JSON.parse(res.body)
111
+ serial = res_json['CertSN']
112
+
113
+ # download new cert
114
+
115
+ download_certificate(serial, http, headers)
116
+ end
117
+
118
+ # returns a certificate
119
+ def self.download_certificate(serial, http, headers)
120
+ res = http.get("/pushcert/download?csn=#{serial}", headers)
121
+
122
+ raise AppleCertificateNotFoundError, "apple did not respond with a certificate" unless res.body.match(/BEGIN CERTIFICATE/)
123
+
124
+ OpenSSL::X509::Certificate.new res.body
125
+ end
126
+
127
+
128
+ end
129
+ end
@@ -0,0 +1,6 @@
1
+ module ApplePushCerts
2
+ CONFIG = {
3
+ apple_intermediate_certificate_path: File.expand_path('../../../certificates/AppleWWDRCA.cer', __FILE__),
4
+ apple_root_certificate_path: File.expand_path('../../../certificates/AppleIncRootCertificate.cer', __FILE__)
5
+ }
6
+ end
@@ -0,0 +1,40 @@
1
+ require 'openssl'
2
+
3
+ module ApplePushCerts
4
+ module Customer
5
+ class CSR < OpenSSL::X509::Request
6
+
7
+ def initialize(key=nil)
8
+ key ||= Key.new
9
+ csr = OpenSSL::X509::Request.new
10
+
11
+ options = {
12
+ country: '',
13
+ state: '',
14
+ city: '',
15
+ organization: '',
16
+ department: '',
17
+ common_name: '',
18
+ email: ''
19
+ }
20
+
21
+ csr.version = 0
22
+ csr.subject = OpenSSL::X509::Name.new([
23
+ ['C', options[:country], OpenSSL::ASN1::PRINTABLESTRING],
24
+ ['ST', options[:state], OpenSSL::ASN1::PRINTABLESTRING],
25
+ ['L', options[:city], OpenSSL::ASN1::PRINTABLESTRING],
26
+ ['O', options[:organization], OpenSSL::ASN1::UTF8STRING],
27
+ ['OU', options[:department], OpenSSL::ASN1::UTF8STRING],
28
+ ['CN', options[:common_name], OpenSSL::ASN1::UTF8STRING],
29
+ ['emailAddress', options[:email], OpenSSL::ASN1::UTF8STRING]
30
+ ])
31
+
32
+ csr.public_key = key.public_key
33
+ csr.sign key, OpenSSL::Digest::SHA1.new
34
+
35
+ super csr
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,13 @@
1
+ require 'openssl'
2
+
3
+ module ApplePushCerts
4
+ module Customer
5
+ class Key < OpenSSL::PKey::RSA
6
+
7
+ def initialize
8
+ super 2048
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module ApplePushCerts
2
+ class Error < RuntimeError
3
+ end
4
+
5
+ class AppleSignInError < Error
6
+ end
7
+
8
+ class AppleCertificateNotFoundError < Error
9
+ end
10
+ end
@@ -0,0 +1,43 @@
1
+ require 'base64'
2
+ require 'plist'
3
+
4
+ module ApplePushCerts
5
+ class SignedCSR < String
6
+
7
+ def initialize(vendor_p12_path, p12_password, customer_csr)
8
+ vendor_p12 = OpenSSL::PKCS12.new(File.open(vendor_p12_path).read, p12_password)
9
+
10
+ csr_der = customer_csr.to_der
11
+ signed_csr_der = vendor_p12.key.sign(OpenSSL::Digest::SHA1.new, csr_der)
12
+
13
+ request = {
14
+ 'PushCertRequestCSR' => Base64.encode64(csr_der),
15
+ 'PushCertCertificateChain' => certificate_chain(vendor_p12),
16
+ 'PushCertSignature' => Base64.encode64(signed_csr_der)
17
+ }
18
+
19
+ # Apple doesn't like tab characters in the plist
20
+ plist = Plist::Emit.dump(request).gsub(/\t/,'')
21
+
22
+ signed_csr = Base64.encode64 plist
23
+
24
+ super signed_csr
25
+ end
26
+
27
+ private
28
+
29
+ def certificate_chain(vendor_p12)
30
+ apple_root = path_to_pem CONFIG[:apple_root_certificate_path]
31
+ apple_intermediate = path_to_pem CONFIG[:apple_intermediate_certificate_path]
32
+ vendor_cert = vendor_p12.certificate.to_pem
33
+
34
+ [apple_root, apple_intermediate, vendor_cert].join
35
+ end
36
+
37
+ def path_to_pem(path)
38
+ cert = OpenSSL::X509::Certificate.new File.open(path).read
39
+ cert.to_pem
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module ApplePushCerts
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,15 @@
1
+ require 'apple_push_certs'
2
+
3
+ describe ApplePushCerts do
4
+
5
+ it 'has the apple root certificate' do
6
+ root_certificate = File.open ApplePushCerts::CONFIG[:apple_root_certificate_path]
7
+ root_certificate.read.length.should be > 0
8
+ end
9
+
10
+ it 'has the apple intermediate certificate' do
11
+ root_certificate = File.open ApplePushCerts::CONFIG[:apple_intermediate_certificate_path]
12
+ root_certificate.read.length.should be > 0
13
+ end
14
+
15
+ end
@@ -0,0 +1,17 @@
1
+ require 'apple_push_certs'
2
+
3
+ describe ApplePushCerts::Customer::CSR do
4
+ before do
5
+ @csr = ApplePushCerts::Customer::CSR.new
6
+ end
7
+
8
+ it 'initializes' do
9
+ @csr.should be_a_kind_of(OpenSSL::X509::Request)
10
+ end
11
+
12
+ it 'generates a csr' do
13
+ matches = @csr.to_s.match /BEGIN CERTIFICATE REQUEST/
14
+ matches.should_not eq(nil)
15
+ end
16
+
17
+ end
@@ -0,0 +1,16 @@
1
+ require 'apple_push_certs'
2
+
3
+ describe ApplePushCerts::Customer::Key do
4
+ before do
5
+ @key = ApplePushCerts::Customer::Key.new
6
+ end
7
+
8
+ it 'initializes' do
9
+ @key.should be_a_kind_of(OpenSSL::PKey::RSA)
10
+ end
11
+
12
+ it 'generates a key' do
13
+ matches = @key.to_s.match(/BEGIN RSA PRIVATE KEY/)
14
+ matches.should_not eq(nil)
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apple_push_certs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Taylor Boyko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: plist
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: multipart-post
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Generate Apple Push CSRs and Retrieve Certificates from the Apple Push
84
+ Certificates Portal
85
+ email:
86
+ - taylor@wrprojects.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - apple_push_certs.gemspec
97
+ - certificates/AppleIncRootCertificate.cer
98
+ - certificates/AppleWWDRCA.cer
99
+ - lib/apple_push_certs.rb
100
+ - lib/apple_push_certs/certificate.rb
101
+ - lib/apple_push_certs/config.rb
102
+ - lib/apple_push_certs/customer/csr.rb
103
+ - lib/apple_push_certs/customer/key.rb
104
+ - lib/apple_push_certs/errors.rb
105
+ - lib/apple_push_certs/signed_csr.rb
106
+ - lib/apple_push_certs/version.rb
107
+ - spec/apple_push_certs_spec.rb
108
+ - spec/customer_csr_spec.rb
109
+ - spec/private_key_spec.rb
110
+ homepage: ''
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.4.2
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: ''
134
+ test_files:
135
+ - spec/apple_push_certs_spec.rb
136
+ - spec/customer_csr_spec.rb
137
+ - spec/private_key_spec.rb