sslcheck 0.9.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +177 -0
  8. data/Rakefile +14 -0
  9. data/acceptance/acceptance_helper.rb +1 -0
  10. data/acceptance/checking_certificates_spec.rb +23 -0
  11. data/acceptance/client_spec.rb +67 -0
  12. data/blinky_tests +3 -0
  13. data/lib/sslcheck.rb +20 -0
  14. data/lib/sslcheck/certificate.rb +121 -0
  15. data/lib/sslcheck/certificate_client.rb +10 -0
  16. data/lib/sslcheck/check.rb +65 -0
  17. data/lib/sslcheck/client.rb +68 -0
  18. data/lib/sslcheck/generic_error.rb +15 -0
  19. data/lib/sslcheck/parser.rb +96 -0
  20. data/lib/sslcheck/validator.rb +111 -0
  21. data/lib/sslcheck/validators/ca_bundle.rb +23 -0
  22. data/lib/sslcheck/validators/common_name.rb +27 -0
  23. data/lib/sslcheck/validators/errors.rb +15 -0
  24. data/lib/sslcheck/validators/expiration_date.rb +10 -0
  25. data/lib/sslcheck/validators/generic_validator.rb +15 -0
  26. data/lib/sslcheck/validators/issue_date.rb +10 -0
  27. data/lib/sslcheck/version.rb +3 -0
  28. data/run_acceptance_on_ci +7 -0
  29. data/sentinal +10 -0
  30. data/spec/ca_bundle_validator_spec.rb +24 -0
  31. data/spec/cert_fixtures.rb +814 -0
  32. data/spec/certificate_spec.rb +134 -0
  33. data/spec/check_spec.rb +172 -0
  34. data/spec/common_name_validator_spec.rb +40 -0
  35. data/spec/expiration_date_validator_spec.rb +36 -0
  36. data/spec/issue_date_validator_spec.rb +36 -0
  37. data/spec/parser_spec.rb +0 -0
  38. data/spec/response_spec.rb +13 -0
  39. data/spec/spec_helper.rb +100 -0
  40. data/spec/validator_spec.rb +84 -0
  41. data/sslcheck.gemspec +26 -0
  42. metadata +165 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 99dd5040fd8190da53d30ee4fedff05c25705c33
4
+ data.tar.gz: d1f0f5e802311ae580f6a1501c240e3f535f683d
5
+ SHA512:
6
+ metadata.gz: 2759629f5a99a2c23fba38453b56bda4287d64dc225a1d4584048dbf1b8a8eaf1fc8c62ab0ad6e9fb8368ab778e61006018be91c4062ddc0333f297e2ac6c6fe
7
+ data.tar.gz: eebf070b57b40f597fe535325a267f7caa917205c34fbb86ed9ff88caf5ee8c0080e325c44566abd110b29f7b3b3a08f966697f0d078580bb96af80f9b512f34
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ -f d
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0-p0
4
+ script:
5
+ - bundle exec rspec spec/
6
+ - ./run_acceptance_on_ci
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sslcheck.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Clayton Lengel-Zigich
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,177 @@
1
+ ## SSL Check
2
+
3
+ An easy way to verify the installation of SSL certificates.
4
+
5
+ [![Build Status](https://travis-ci.org/clayton/sslcheck.svg?branch=master)](https://travis-ci.org/clayton/sslcheck)
6
+
7
+ ## Installation
8
+
9
+ Install the gem on your system
10
+
11
+ ```
12
+ gem install sslcheck
13
+ ```
14
+
15
+ Use the gem with bundler by adding the following to your `Gemfile`
16
+
17
+ ```
18
+ gem "sslcheck"
19
+ ```
20
+
21
+ ## Using SSLCheck
22
+
23
+ The SSLCheck gem has a simple API.
24
+
25
+ First create a check:
26
+
27
+ ```
28
+ check = SSLCheck::Check.new
29
+ ```
30
+
31
+ Then, check a url:
32
+
33
+ ```
34
+ check.check("github.com")
35
+ ```
36
+
37
+ Is the certificate valid?
38
+
39
+ ```
40
+ check.valid?
41
+ => true
42
+ ```
43
+
44
+ Are there any errors?
45
+
46
+ ```
47
+ check.errors
48
+ => []
49
+ ```
50
+
51
+ What are the details of the certificate?
52
+
53
+ The peer certificate found during the check is available with a rich
54
+ (undocumented) API. See `SSLCheck::Certificate` for more details. A helper
55
+ method is provided on the `Certificate` to get at most of the important details.
56
+
57
+ ```
58
+ checker.peer_cert.to_h
59
+ {:common_name=>"www.sslinsight.com",
60
+ :organization_unit=>"Domain Control Validated",
61
+ :not_before=>
62
+ #<DateTime: 2014-07-25T00:00:00+00:00 ((2456864j,0s,0n),+0s,2299161j)>,
63
+ :not_after=>
64
+ #<DateTime: 2015-07-25T23:59:59+00:00 ((2457229j,86399s,0n),+0s,2299161j)>,
65
+ :issued=>true,
66
+ :expired=>false,
67
+ :issuer=>
68
+ {:common_name=>"COMODO RSA Domain Validation Secure Server CA",
69
+ :country=>"GB",
70
+ :state=>"Greater Manchester",
71
+ :locality=>"Salford",
72
+ :organization=>"COMODO CA Limited"}}
73
+ => {:common_name=>"www.sslinsight.com", :organization_unit=>"Domain Control Validated", :not_before=>#<DateTime: 2014-07-25T00:00:00+00:00 ((2456864j,0s,0n),+0s,2299161j)>, :not_after=>#<DateTime: 2015-07-25T23:59:59+00:00 ((2457229j,86399s,0n),+0s,2299161j)>, :issued=>true, :expired=>false, :issuer=>{:common_name=>"COMODO RSA Domain Validation Secure Server CA", :country=>"GB", :state=>"Greater Manchester", :locality=>"Salford", :organization=>"COMODO CA Limited"}}
74
+ ```
75
+
76
+ What are the details of the CA Bundle?
77
+
78
+ Each certificate in the CA Bundle is available as an `SSLCheck::Certificate`
79
+ instance if needed.
80
+
81
+
82
+ ### Potential Validation Errors
83
+
84
+ **SSLCheck::Errors::Validation::CommonNameMismatch**
85
+
86
+ Occurs when the common name supplied in the check does not match the common name
87
+ on the certificate, any alternate subject names or match a regex based on the
88
+ wildcard domain if the certificate was issued for a wildcard domain.
89
+
90
+ **SSLCheck::Errors::Validation::NotYetIssued**
91
+
92
+ Occurs when the certificates `not_before` date is in the future.
93
+
94
+ **SSLCheck::Errors::Validation::CertificateExpired**
95
+
96
+ Occurs when the certificates `not_after` date is in the past.
97
+
98
+ **SSLCheck::Errors::Validation::CABundleVerification**
99
+
100
+ Occurs when the CA Bundle (peer certificate chain) that is gathered during the
101
+ connection to the server cannot verify the certificate. This is a very common
102
+ error when setting up and install SSL Certificates and occurs when the web
103
+ server is not configured correctly or the CA Bundle certificates from the
104
+ certificate issuer were not configured or installed correctly.
105
+
106
+
107
+
108
+ ### Validating Certificates
109
+
110
+ A certificate is considered valid if the certificate is present, the CA
111
+ bundle is present and all of the default validations pass.
112
+
113
+ By default, SSLCheck validates the following:
114
+
115
+ * Common Name matches (accounting for alternate names and wildcard certificates)
116
+ * CA Bundle Verification (can the CA bundle verify the certificate?)
117
+ * Issue Date (is in the past)
118
+ * Expiration Date (is in the future)
119
+
120
+ ## Custom Client
121
+
122
+ By default, all certificates are fetched by opening an SSL Socket Connection and
123
+ grabbing the peer certificate and peer certificate chain (CA Bundle.)
124
+
125
+ A custom client should return a response that exposes the peer certificate,
126
+ peer certificate chain and url that was fetched. See `SSLCheck::Client::Response`
127
+ for more information.
128
+
129
+ ### Using a custom client
130
+
131
+ ```
132
+ check = SSLCheck::Check.new(MyClient.new)
133
+ ```
134
+
135
+ ## Custom Validators and Validations
136
+
137
+ A custom validator can be used to allow for additional validations or override
138
+ the default validations. For more information see `SSLCheck::Validator`
139
+
140
+ ### Using a custom validator
141
+
142
+ ```
143
+ # passing nil as the first argument to use the default client
144
+ check = SSLCheck::Check.new(nil, MyValidator.new)
145
+ ```
146
+
147
+ ## Contributing
148
+
149
+ * Fork
150
+ * Run the tests (`rake`)
151
+ * Commit & Push
152
+ * Submit a pull request
153
+
154
+
155
+ ## License
156
+
157
+ The MIT License (MIT)
158
+
159
+ Copyright (c) 2014 Clayton Lengel-Zigich
160
+
161
+ Permission is hereby granted, free of charge, to any person obtaining a copy
162
+ of this software and associated documentation files (the "Software"), to deal
163
+ in the Software without restriction, including without limitation the rights
164
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
165
+ copies of the Software, and to permit persons to whom the Software is
166
+ furnished to do so, subject to the following conditions:
167
+
168
+ The above copyright notice and this permission notice shall be included in all
169
+ copies or substantial portions of the Software.
170
+
171
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
172
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
173
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
174
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
175
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
176
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
177
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.pattern = Dir.glob('spec/**/*_spec.rb')
6
+ t.rspec_opts = '--format documentation'
7
+ end
8
+
9
+ RSpec::Core::RakeTask.new(:acceptance) do |t|
10
+ t.pattern = Dir.glob('acceptance/**/*_spec.rb')
11
+ t.rspec_opts = '--format documentation'
12
+ end
13
+
14
+ task :default => [:spec, :acceptance]
@@ -0,0 +1 @@
1
+ require_relative '../spec/spec_helper'
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ module SSLCheck
4
+ describe 'Checking Certificates' do
5
+ context "when the certificate is valid" do
6
+ before do
7
+ @check = Check.new.check("http://www.sslinsight.com")
8
+ end
9
+ it 'should be valid' do
10
+ expect(@check.valid?).to be
11
+ end
12
+ it 'should not have any errors' do
13
+ expect(@check.errors).to be_empty
14
+ end
15
+ it 'should know the peer certificate' do
16
+ expect(@check.peer_cert).to be
17
+ end
18
+ it 'should know the ca bundle' do
19
+ expect(@check.ca_bundle).to be
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ module SSLCheck
4
+ describe 'Client' do
5
+ context "Getting Certificates" do
6
+ context "When Things Go Well" do
7
+ it 'should have the host name' do
8
+ sut = Client.new
9
+ response = sut.get('https://www.sslinsight.com')
10
+ expect(response.host_name).to eq("www.sslinsight.com")
11
+ end
12
+
13
+ it 'should have the peer certificate' do
14
+ sut = Client.new
15
+ response = sut.get('https://www.sslinsight.com')
16
+ expect(response.peer_cert).to be_a(SSLCheck::Certificate)
17
+ end
18
+
19
+ it 'should have the peer cert chain' do
20
+ sut = Client.new
21
+ response = sut.get('https://www.sslinsight.com')
22
+ expect(response.ca_bundle.first).to be_a(SSLCheck::Certificate)
23
+ end
24
+ end
25
+
26
+ context "when the URL is missing the protocol" do
27
+ it 'should still provide a hostname for the response' do
28
+ sut = Client.new
29
+ response = sut.get('www.sslinsight.com')
30
+ expect(response.host_name).to eq("www.sslinsight.com")
31
+ end
32
+ end
33
+
34
+ context "when the URL has an http protocol" do
35
+ it 'should still provide a hostname for the response' do
36
+ sut = Client.new
37
+ response = sut.get('http://www.sslinsight.com')
38
+ expect(response.host_name).to eq("www.sslinsight.com")
39
+ end
40
+ end
41
+
42
+ context "When the URL is malformed" do
43
+ it 'should raise an invalid URI error' do
44
+ sut = Client.new
45
+ response = sut.get('this is not even close to a valid url.com')
46
+ expect(response.errors.first).to be_a(SSLCheck::Errors::Connection::InvalidURI)
47
+ end
48
+ end
49
+
50
+ context "When there is no SSL Certificate present" do
51
+ it 'should raise a verification error' do
52
+ sut = Client.new
53
+ response = sut.get('http://www.claytonlz.com')
54
+ expect(response.errors.first).to be_a(SSLCheck::Errors::Connection::SSLVerify)
55
+ end
56
+ end
57
+
58
+ context "When the certificate is self-signed" do
59
+ it 'should raise a verification error' do
60
+ sut = Client.new
61
+ response = sut.get('https://www.pcwebshop.co.uk')
62
+ expect(response.errors.first).to be_a(SSLCheck::Errors::Connection::SSLVerify)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ blinky-tape-test-tool f && bundle exec rspec spec && blinky-tape-test-tool gs || blinky-tape-test-tool rs
@@ -0,0 +1,20 @@
1
+ require "sslcheck/version"
2
+ require 'openssl'
3
+
4
+ module SSLCheck
5
+ # Your code goes here...
6
+ end
7
+
8
+ require 'sslcheck/certificate'
9
+ require 'sslcheck/validator'
10
+ require 'sslcheck/certificate_client'
11
+ require 'sslcheck/parser'
12
+ require 'sslcheck/generic_error'
13
+ require 'sslcheck/check'
14
+ require 'sslcheck/client'
15
+ require 'sslcheck/validators/generic_validator'
16
+ require 'sslcheck/validators/errors'
17
+ require 'sslcheck/validators/common_name'
18
+ require 'sslcheck/validators/issue_date'
19
+ require 'sslcheck/validators/expiration_date'
20
+ require 'sslcheck/validators/ca_bundle'
@@ -0,0 +1,121 @@
1
+ require 'openssl'
2
+
3
+ module SSLCheck
4
+ class Certificate
5
+ def initialize(cert, clock=nil)
6
+ @cert = bootstrap_certificate(cert)
7
+ @clock = clock || DateTime
8
+ end
9
+
10
+ def to_x509
11
+ OpenSSL::X509::Certificate.new @cert.to_s
12
+ end
13
+
14
+ def to_h
15
+ {
16
+ :common_name => common_name,
17
+ :organization_unit => organizational_unit,
18
+ :not_before => not_before,
19
+ :not_after => not_after,
20
+ :issued => true,
21
+ :expired => false,
22
+ :issuer => {
23
+ :common_name => issuer_common_name,
24
+ :country => issuer_country,
25
+ :state => issuer_state,
26
+ :locality => issuer_locality,
27
+ :organization => issuer_organization
28
+ }
29
+ }
30
+ end
31
+
32
+ def to_s
33
+ @cert.to_s
34
+ end
35
+
36
+ def subject
37
+ @cert.subject.to_s
38
+ end
39
+
40
+ def organizational_unit
41
+ match = subject.match(/OU=([\w\s]+)/)
42
+ match.captures.first if match
43
+ end
44
+
45
+ def common_name
46
+ subject.scan(/CN=(.*)/)[0][0]
47
+ end
48
+
49
+ def alternate_common_names
50
+ ext = @cert.extensions.find{|ext| ext.oid == "subjectAltName" }
51
+ return [] unless ext
52
+ alternates = ext.value.split(",")
53
+ names = alternates.map{|a| a.scan(/DNS:(.*)/)[0][0]}
54
+ names
55
+ end
56
+
57
+ def issuer
58
+ @cert.issuer.to_s
59
+ end
60
+
61
+ def issuer_country
62
+ match = issuer.match(/C=([\w\s]+)/)
63
+ match.captures.first if match
64
+ end
65
+
66
+ def issuer_state
67
+ match = issuer.match(/ST=([\w\s]+)/)
68
+ match.captures.first if match
69
+ end
70
+
71
+ def issuer_locality
72
+ match = issuer.match(/L=([\w\s]+)/)
73
+ match.captures.first if match
74
+ end
75
+
76
+ def issuer_organization
77
+ match = issuer.match(/O=([^\/]+)/)
78
+ match.captures.first if match
79
+ end
80
+
81
+ def issuer_common_name
82
+ issued_by
83
+ end
84
+
85
+ def issued_by
86
+ match = issuer.match("CN=(.*)")
87
+ match.captures.first if match
88
+ end
89
+
90
+ def public_key
91
+ @cert.public_key
92
+ end
93
+
94
+ def verify(ca)
95
+ @cert.verify(ca.public_key)
96
+ end
97
+
98
+ def not_before
99
+ DateTime.parse(@cert.not_before.to_s)
100
+ end
101
+
102
+ def not_after
103
+ DateTime.parse(@cert.not_after.to_s)
104
+ end
105
+
106
+ def expired?
107
+ @clock.now > not_after
108
+ end
109
+
110
+ def issued?
111
+ @clock.now > not_before
112
+ end
113
+
114
+ def bootstrap_certificate(cert)
115
+ return cert if cert.is_a?(OpenSSL::X509::Certificate)
116
+ return cert if cert.is_a?(SSLCheck::Certificate)
117
+ OpenSSL::X509::Certificate.new cert
118
+ end
119
+
120
+ end
121
+ end