alexa_verifier 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []