alexa_verifier 0.1.0 → 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,93 @@
1
+ module AlexaVerifier
2
+ class Verifier
3
+ # Given an Alexa certificate URI, validate it according to:
4
+ # https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-a-web-service.html#h2_verify_sig_cert
5
+ #
6
+ # @since 0.1
7
+ module CertificateURIVerifier
8
+ VALIDATIONS = {
9
+ scheme: 'https',
10
+ port: 443,
11
+ host: 's3.amazonaws.com'
12
+ }.freeze
13
+
14
+ PATH_REGEX = %r{\A\/echo.api\/}
15
+
16
+ class << self
17
+ # Check that a given certificate URI meets Amazon's requirements.
18
+ # Raise an error if it does not.
19
+ #
20
+ # @param [String] uri The URI value from HTTP_SIGNATURECERTCHAINURL
21
+ #
22
+ # @raise [AlexaVerifier::InvalidCertificateURIError] An error
23
+ # raised when the URI does not meet a requirement
24
+ #
25
+ # @return [true] This method will either raise an error or return true
26
+ def valid!(uri)
27
+ begin
28
+ uri = URI.parse(uri)
29
+ rescue URI::InvalidURIError => e
30
+ puts e
31
+
32
+ raise AlexaVerifier::InvalidCertificateURIError,
33
+ "#{uri} : #{e.message}"
34
+ end
35
+
36
+ test_validations(uri)
37
+
38
+ test_path(uri)
39
+
40
+ true
41
+ end
42
+
43
+ # Check that a given certificate URI meets Amazon's requirements
44
+ # Return true if it does, or false if it does not.
45
+ #
46
+ # @param [String] uri The URI value from HTTP_SIGNATURECERTCHAINURL
47
+ #
48
+ # @return [Boolean] Returns true if the uri is valid and false if not
49
+ def valid?(uri)
50
+ begin
51
+ valid!(uri)
52
+ rescue AlexaVerifier::InvalidCertificateURIError => e
53
+ puts e
54
+
55
+ return false
56
+ end
57
+
58
+ true
59
+ end
60
+
61
+ private
62
+
63
+ # Test that a given URI meets all of our 'simple' validation rules.
64
+ #
65
+ # @param [URI] uri the URI object to test
66
+ def test_validations(uri)
67
+ VALIDATIONS.each do |method, value|
68
+ next if uri.send(method) == value
69
+
70
+ raise AlexaVerifier::InvalidCertificateURIError.new(
71
+ "URI #{method} must be '#{value}'",
72
+ uri.send(method)
73
+ )
74
+ end
75
+ end
76
+
77
+ # Test that a given URI matches our 'path' regex.
78
+ #
79
+ # @param [URI] uri the URI object to test
80
+ def test_path(uri)
81
+ path = File.absolute_path(uri.path)
82
+
83
+ return if path.match(PATH_REGEX) # rubocop:disable Performance/RegexpMatch # Disabled for backwards compatibility below 2.4
84
+
85
+ raise AlexaVerifier::InvalidCertificateURIError.new(
86
+ "URI path must start with '/echo.api/'",
87
+ uri.path
88
+ )
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,99 @@
1
+ require 'json'
2
+ require 'time'
3
+ require 'net/http'
4
+ require 'openssl'
5
+ require 'base64'
6
+
7
+ module AlexaVerifier
8
+ class Verifier
9
+ # Given an OpenSSL certificate, validate it according to:
10
+ # https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-a-web-service.html#h2_verify_sig_cert
11
+ #
12
+ # @since 0.1
13
+ module CertificateVerifier
14
+ SAN = 'echo-api.amazon.com'.freeze
15
+
16
+ class << self
17
+ # Check that a given certificate meet's Amazon's requirements.
18
+ # Raise an error if it does not.
19
+ #
20
+ # @param [OpenSSL::X509::Certificate] certificate certificate to check.
21
+ # @param [Array<OpenSSL::X509::Certificate>] chain chain of certificates to a root trusted CA.
22
+ #
23
+ # @raise [AlexaVerifier::InvalidCertificateError] raised when
24
+ # the provided certificate does not meet a requirement
25
+ #
26
+ # @return [true] either returns true or raises an error.
27
+ def valid!(certificate, chain)
28
+ check_that_certificate_is_in_date(certificate)
29
+
30
+ check_that_certificate_has_the_expected_extensions(certificate)
31
+
32
+ check_that_we_can_create_a_chain_of_trust_to_a_root_ca(certificate, chain)
33
+
34
+ true
35
+ end
36
+
37
+ # Check that a given certificate meet's Amazon's requirements.
38
+ # Returns a boolean.
39
+ #
40
+ # @param [OpenSSL::X509::Certificate] certificate certificate to check.
41
+ # @param [Array<OpenSSL::X509::Certificate>] chain chain of certificates to a root CA.
42
+ #
43
+ # @return [Boolean] returns the result of our checks.
44
+ def valid?(certificate, chain)
45
+ begin
46
+ valid!(certificate, chain)
47
+ rescue AlexaVerifier::InvalidCertificateError => e
48
+ puts e
49
+
50
+ return false
51
+ end
52
+
53
+ true
54
+ end
55
+
56
+ private
57
+
58
+ # Given a certificate file, check that it is in date.
59
+ #
60
+ # @param [OpenSSL::X509::Certificate] certificate the certificate we should check
61
+ #
62
+ # @raise [AlexaVerifier::InvalidCertificateError] raised if the certificate is not in date
63
+ def check_that_certificate_is_in_date(certificate)
64
+ certificate_in_date = Time.now.between?(certificate.not_before, certificate.not_after)
65
+ raise AlexaVerifier::InvalidCertificateError, 'Certificate is not in date.' unless certificate_in_date
66
+ end
67
+
68
+ # Given a certificate file, check that it contains our expected extensions
69
+ #
70
+ # @param [OpenSSL::X509::Certificate] certificate the certificate we should check
71
+ #
72
+ # @raise [AlexaVerifier::InvalidCertificateError] raised if the extensions are not present
73
+ def check_that_certificate_has_the_expected_extensions(certificate)
74
+ valid_sans = certificate.extensions.select do |extension|
75
+ valid_oid = (extension.oid == 'subjectAltName')
76
+ valid_value = (extension.value == "DNS:#{SAN}")
77
+
78
+ valid_oid && valid_value
79
+ end
80
+ raise AlexaVerifier::InvalidCertificateError, "Certificate does not contain SAN: #{SAN}." if valid_sans.empty?
81
+ end
82
+
83
+ # Check that the certificate, along with any chain from our downloaded certificate file, makes a chain of trust to a trusted root CA
84
+ #
85
+ # @param [OpenSSL::X509::Certificate] certificate certificate we should check
86
+ # @param [Array<OpenSSL::X509::Certificate>] chain chain of certificates to a root trusted CA
87
+ #
88
+ # @raise [AlexaVerifier::InvalidCertificateError] raised if a chain of trust could not be established
89
+ def check_that_we_can_create_a_chain_of_trust_to_a_root_ca(certificate, chain)
90
+ openssl_x509_store = OpenSSL::X509::Store.new
91
+ openssl_x509_store.set_default_paths
92
+
93
+ valid_certificate_chain = openssl_x509_store.verify(certificate, chain)
94
+ raise AlexaVerifier::InvalidCertificateError, "Unable to create a 'chain of trust' from the provided certificate to a trusted root CA." unless valid_certificate_chain
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ module AlexaVerifier
2
+ VERSION = '1.0.0'.freeze
3
+ end
metadata CHANGED
@@ -1,102 +1,163 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alexa_verifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Mullins
8
+ - Matt Rayner
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2015-08-03 00:00:00.000000000 Z
12
+ date: 2017-11-24 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: bundler
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - ~>
18
+ - - "~>"
18
19
  - !ruby/object:Gem::Version
19
- version: '1.10'
20
+ version: '1.16'
20
21
  type: :development
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
- - - ~>
25
+ - - "~>"
25
26
  - !ruby/object:Gem::Version
26
- version: '1.10'
27
+ version: '1.16'
28
+ - !ruby/object:Gem::Dependency
29
+ name: coveralls
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 0.8.21
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 0.8.21
27
42
  - !ruby/object:Gem::Dependency
28
43
  name: rake
29
44
  requirement: !ruby/object:Gem::Requirement
30
45
  requirements:
31
- - - ~>
46
+ - - "~>"
32
47
  - !ruby/object:Gem::Version
33
48
  version: '10.0'
34
49
  type: :development
35
50
  prerelease: false
36
51
  version_requirements: !ruby/object:Gem::Requirement
37
52
  requirements:
38
- - - ~>
53
+ - - "~>"
39
54
  - !ruby/object:Gem::Version
40
55
  version: '10.0'
41
56
  - !ruby/object:Gem::Dependency
42
57
  name: rspec
43
58
  requirement: !ruby/object:Gem::Requirement
44
59
  requirements:
45
- - - '>='
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: simplecov
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '0.14'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0.14'
84
+ - !ruby/object:Gem::Dependency
85
+ name: timecop
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
46
89
  - !ruby/object:Gem::Version
47
- version: '0'
90
+ version: '0.9'
48
91
  type: :development
49
92
  prerelease: false
50
93
  version_requirements: !ruby/object:Gem::Requirement
51
94
  requirements:
52
- - - '>='
95
+ - - "~>"
53
96
  - !ruby/object:Gem::Version
54
- version: '0'
97
+ version: '0.9'
55
98
  - !ruby/object:Gem::Dependency
56
- name: curb
99
+ name: vcr
57
100
  requirement: !ruby/object:Gem::Requirement
58
101
  requirements:
59
- - - ~>
102
+ - - "~>"
60
103
  - !ruby/object:Gem::Version
61
- version: 0.7.16
104
+ version: '3.0'
62
105
  type: :development
63
106
  prerelease: false
64
107
  version_requirements: !ruby/object:Gem::Requirement
65
108
  requirements:
66
- - - ~>
109
+ - - "~>"
67
110
  - !ruby/object:Gem::Version
68
- version: 0.7.16
111
+ version: '3.0'
69
112
  - !ruby/object:Gem::Dependency
70
113
  name: webmock
71
114
  requirement: !ruby/object:Gem::Requirement
72
115
  requirements:
73
- - - '>='
116
+ - - "~>"
74
117
  - !ruby/object:Gem::Version
75
- version: '0'
118
+ version: '3.0'
76
119
  type: :development
77
120
  prerelease: false
78
121
  version_requirements: !ruby/object:Gem::Requirement
79
122
  requirements:
80
- - - '>='
123
+ - - "~>"
81
124
  - !ruby/object:Gem::Version
82
- version: '0'
83
- description:
125
+ version: '3.0'
126
+ description: This gem is designed to work with Rack applications that serve as back-ends
127
+ for Amazon Alexa skills.
84
128
  email:
85
129
  - chris@sidoh.org
130
+ - m@rayner.io
86
131
  executables: []
87
132
  extensions: []
88
133
  extra_rdoc_files: []
89
134
  files:
90
- - .gitignore
91
- - .rspec
92
- - .travis.yml
135
+ - ".gitignore"
136
+ - ".hound.yml"
137
+ - ".rspec"
138
+ - ".rubocop.yml"
139
+ - ".ruby-version"
140
+ - ".travis.yml"
141
+ - CODE_OF_CONDUCT.md
93
142
  - Gemfile
94
- - LICENSE.txt
143
+ - LICENSE
95
144
  - README.md
96
145
  - Rakefile
97
146
  - alexa_verifier.gemspec
147
+ - bin/console
148
+ - bin/setup
98
149
  - lib/alexa_verifier.rb
99
- homepage: http://www.github.com/sidoh/alexa_verifier
150
+ - lib/alexa_verifier/base_error.rb
151
+ - lib/alexa_verifier/certificate_store.rb
152
+ - lib/alexa_verifier/configuration.rb
153
+ - lib/alexa_verifier/invalid_certificate_error.rb
154
+ - lib/alexa_verifier/invalid_certificate_u_r_i_error.rb
155
+ - lib/alexa_verifier/invalid_request_error.rb
156
+ - lib/alexa_verifier/verifier.rb
157
+ - lib/alexa_verifier/verifier/certificate_u_r_i_verifier.rb
158
+ - lib/alexa_verifier/verifier/certificate_verifier.rb
159
+ - lib/alexa_verifier/version.rb
160
+ homepage: https://github.com/sidoh/alexa_verifier
100
161
  licenses:
101
162
  - MIT
102
163
  metadata: {}
@@ -106,18 +167,18 @@ require_paths:
106
167
  - lib
107
168
  required_ruby_version: !ruby/object:Gem::Requirement
108
169
  requirements:
109
- - - '>='
170
+ - - ">="
110
171
  - !ruby/object:Gem::Version
111
172
  version: '0'
112
173
  required_rubygems_version: !ruby/object:Gem::Requirement
113
174
  requirements:
114
- - - '>='
175
+ - - ">="
115
176
  - !ruby/object:Gem::Version
116
177
  version: '0'
117
178
  requirements: []
118
179
  rubyforge_project:
119
- rubygems_version: 2.0.14
180
+ rubygems_version: 2.7.2
120
181
  signing_key:
121
182
  specification_version: 4
122
- summary: Verifies requests sent to an Alexa skill are sent from Amazon
183
+ summary: Verify HTTP requests sent to an Alexa skill are sent from Amazon.
123
184
  test_files: []