x509_sleuth 0.0.3

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a6d6c6b79aa075f55cc50230060d95749d4cc0a6
4
+ data.tar.gz: 72f5b9d78fb620d03cce1bbbb1a20ba662a18462
5
+ SHA512:
6
+ metadata.gz: 76eca03d48bdcf7b8f67b3fc4898a8d0af4b85680519222c8cad40c11a9419c7ae05022c48c56b4ffd554675f3a0d012d555c5241799ae57f5425977eeebb585
7
+ data.tar.gz: 6d2b0f8e992830068534bfbe5a3f822341ab4d0fbccd9bd8086bf453e8f4d71a3a3713de78c7f6b8ad4416908abab053cb8e11b5ae7c681987d060d8774c92aa
@@ -0,0 +1,29 @@
1
+ require 'thor'
2
+
3
+ module X509Sleuth
4
+ class Cli < Thor
5
+
6
+ def self.exit_on_failure?
7
+ true
8
+ end
9
+
10
+ class_option :target, :type => :array, :required => true
11
+
12
+ desc "scan", "Scan the specified target(s) for certificate details"
13
+ def scan
14
+ options[:target].each do |target|
15
+ my_client.add_target(target)
16
+ end
17
+ my_client.run
18
+ output = X509Sleuth::ScannerPresenter.new(my_client)
19
+ puts output.to_s
20
+ end
21
+
22
+ no_commands do
23
+ def my_client
24
+ @client ||= X509Sleuth::Scanner.new
25
+ @client
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ require "socket"
2
+ require "openssl"
3
+ require "timeout"
4
+
5
+ module X509Sleuth
6
+ class Client
7
+ attr_reader :host, :port, :timeout_secs, :connect_error
8
+
9
+ def initialize(host, options = {})
10
+ options = {
11
+ port: 443,
12
+ timeout_secs: 15
13
+ }.merge(options)
14
+
15
+ @host = host
16
+ @port = options[:port]
17
+ @timeout_secs = options[:timeout_secs]
18
+ end
19
+
20
+ def tcp_socket
21
+ @tcp_socket ||= TCPSocket.new(@host, @port)
22
+ end
23
+
24
+ def ssl_socket
25
+ @ssl_socket ||= OpenSSL::SSL::SSLSocket.new(tcp_socket)
26
+ @ssl_socket.hostname = @host
27
+ @ssl_socket
28
+ end
29
+
30
+ def connect
31
+ Timeout::timeout(@timeout_secs) { ssl_socket.connect }
32
+ rescue SystemCallError, SocketError, OpenSSL::SSL::SSLError, Timeout::Error => e
33
+ @connect_error = e
34
+ end
35
+
36
+ def connect_failed?
37
+ connect_error ? true : false
38
+ end
39
+
40
+ def peer_certificate
41
+ @peer_certficate ||= ssl_socket.peer_cert
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ require "netaddr"
2
+
3
+ module X509Sleuth
4
+ class Scanner
5
+ class Target
6
+ attr_reader :target
7
+
8
+ def initialize(target)
9
+ @target = target
10
+ end
11
+
12
+ def is_a_range?
13
+ NetAddr::CIDR.create(target).size > 1 ? true : false
14
+ rescue NetAddr::ValidationError
15
+ false
16
+ end
17
+
18
+ def hosts
19
+ @hosts ||=
20
+ if is_a_range?
21
+ cidr = NetAddr::CIDR.create(target)
22
+ cidr.enumerate.reject do |address|
23
+ [cidr.network, cidr.broadcast].include?(address)
24
+ end
25
+ else
26
+ [target]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ require "x509_sleuth/scanner/target"
2
+ require "parallel"
3
+
4
+ module X509Sleuth
5
+ class Scanner
6
+ attr_accessor :concurrency
7
+ attr_reader :clients, :targets
8
+
9
+ def initialize(options = {})
10
+ options = {
11
+ concurrency: 5
12
+ }.merge(options)
13
+
14
+ @concurrency = options[:concurrency]
15
+ @targets = []
16
+ end
17
+
18
+ def add_target(target_string)
19
+ @targets << X509Sleuth::Scanner::Target.new(target_string)
20
+ end
21
+
22
+ def clients
23
+ @clients ||=
24
+ targets.collect do |target|
25
+ target.hosts.collect do |host|
26
+ X509Sleuth::Client.new(host)
27
+ end
28
+ end.flatten
29
+ end
30
+
31
+ def run
32
+ Parallel.each(clients, in_threads: concurrency) do |client|
33
+ client.connect
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,72 @@
1
+ require "formatador"
2
+ require "x509_sleuth/scanner_presenter"
3
+
4
+ module X509Sleuth
5
+ class ScannerDetailedPresenter < ScannerPresenter
6
+ def tableize(clients)
7
+ clients.collect do |client|
8
+ if client.peer_certificate
9
+ {
10
+ host: client.host,
11
+ subject: client.peer_certificate.subject,
12
+ common_name: parse_cn(client.peer_certificate),
13
+ alt_names: parse_san(client.peer_certificate).join(","),
14
+ issuer: client.peer_certificate.issuer,
15
+ serial: client.peer_certificate.serial,
16
+ not_before: client.peer_certificate.not_before,
17
+ not_after: client.peer_certificate.not_after
18
+ }
19
+ else
20
+ {
21
+ host: client.host,
22
+ subject: "",
23
+ common_name: "",
24
+ alt_names: [],
25
+ issuer: "",
26
+ serial: "",
27
+ not_before: "",
28
+ not_after: ""
29
+ }
30
+ end
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ Formatador.display_compact_table(
36
+ tableize(filter),
37
+ [
38
+ :host,
39
+ :subject,
40
+ :common_name,
41
+ :alt_names,
42
+ :issuer,
43
+ :serial,
44
+ :not_before,
45
+ :not_after
46
+ ]
47
+ )
48
+ end
49
+
50
+ def parse_cn(cert)
51
+ subject_parts = cert.subject.to_s.split("/").collect{ |p| p.split("=") }
52
+ common_name = ""
53
+ subject_parts.each do |part|
54
+ if part[0] && part[0] == "CN"
55
+ common_name = part[1]
56
+ break
57
+ end
58
+ end
59
+ common_name
60
+ end
61
+
62
+ def parse_san(cert)
63
+ subject_alt_names = []
64
+ cert.extensions.each do |extension|
65
+ if extension.oid == "subjectAltName"
66
+ subject_alt_names = extension.value.split(/[:,]|\s/).reject{ |part| part.nil? || part.empty? || part == "DNS" }
67
+ end
68
+ end
69
+ subject_alt_names
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,54 @@
1
+ require "formatador"
2
+
3
+ module X509Sleuth
4
+ class ScannerPresenter
5
+ attr_reader :scanner
6
+
7
+ def initialize(scanner)
8
+ @scanner = scanner
9
+ end
10
+
11
+ def filter
12
+ @scanner.clients.reject do |client|
13
+ client.connect_failed?
14
+ end
15
+ end
16
+
17
+ def tableize(clients)
18
+ clients.collect do |client|
19
+ if client.peer_certificate
20
+ {
21
+ host: client.host,
22
+ subject: client.peer_certificate.subject,
23
+ issuer: client.peer_certificate.issuer,
24
+ serial: client.peer_certificate.serial,
25
+ not_before: client.peer_certificate.not_before,
26
+ not_after: client.peer_certificate.not_after
27
+ }
28
+ else
29
+ {
30
+ host: client.host,
31
+ subject: "",
32
+ issuer: "",
33
+ serial: "",
34
+ not_before: "",
35
+ not_after: ""
36
+ }
37
+ end
38
+ end
39
+ end
40
+
41
+ def to_s
42
+ Formatador.display_compact_table(
43
+ tableize(filter),
44
+ [
45
+ :host,
46
+ :subject,
47
+ :serial,
48
+ :not_before,
49
+ :not_after
50
+ ]
51
+ )
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,3 @@
1
+ module X509Sleuth
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,14 @@
1
+ require "x509_sleuth/cli"
2
+ require "x509_sleuth/client"
3
+ require "x509_sleuth/scanner"
4
+ require "x509_sleuth/scanner_detailed_presenter"
5
+ require "x509_sleuth/scanner_presenter"
6
+ require "x509_sleuth/version"
7
+
8
+ module X509Sleuth
9
+ class << self
10
+ def version_string
11
+ "X509 Sleuth version #{VERSION}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ require "rspec"
2
+ require "x509_sleuth"
3
+ require "x509_sleuth/scanner_presenter"
4
+
5
+ require "rspec/collection_matchers"
6
+
7
+ RSpec.configure do |config|
8
+ config.color = true
9
+ config.formatter = "documentation"
10
+ end
@@ -0,0 +1,145 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe X509Sleuth::Client do
4
+ let(:host) { "somehost" }
5
+ let(:tcp_socket_double) { double(TCPSocket) }
6
+ let(:ssl_socket_double) { double(OpenSSL::SSL::SSLSocket) }
7
+ let(:client) { described_class.new(host) }
8
+
9
+ before do
10
+ allow(TCPSocket).to receive(:new).and_return(tcp_socket_double)
11
+ allow(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(ssl_socket_double)
12
+ allow(ssl_socket_double).to receive(:hostname=)
13
+ end
14
+
15
+ context "when initialized" do
16
+ subject { described_class.new(host) }
17
+
18
+ it { should respond_to(:host) }
19
+ it { should respond_to(:port) }
20
+ it { should respond_to(:timeout_secs) }
21
+ it { should respond_to(:connect) }
22
+ it { should respond_to(:connect_failed?) }
23
+ it { should respond_to(:connect_error) }
24
+ it { should respond_to(:peer_certificate) }
25
+ #it { should respond_to(:peer_certificate_subject) }
26
+ #it { should respond_to(:peer_certificate_issuer) }
27
+ #it { should respond_to(:peer_certificate_serial) }
28
+ #it { should respond_to(:peer_certificate_activation_time) }
29
+ #it { should respond_to(:peer_certificate_expiration_time) }
30
+
31
+ context "with no options" do
32
+ it "returns defaults" do
33
+ expect(subject.host).to eq(host)
34
+ expect(subject.port).to eq(443)
35
+ expect(subject.timeout_secs).to eq(15)
36
+ end
37
+ end
38
+
39
+ context "with options" do
40
+ subject { described_class.new(host, port: 40443, timeout_secs: 3) }
41
+
42
+ it "returns options" do
43
+ expect(subject.host).to eq(host)
44
+ expect(subject.port).to eq(40443)
45
+ expect(subject.timeout_secs).to eq(3)
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "#tcp_socket" do
51
+ it "creates a TCP socket with the correct host and port" do
52
+ expect(TCPSocket).to receive(:new).with(host, 443).and_return(tcp_socket_double)
53
+
54
+ client.tcp_socket
55
+ end
56
+ end
57
+
58
+ describe "#ssl_socket" do
59
+ it "creates an SSL socket from the tcp_socket" do
60
+ expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket_double).and_return(ssl_socket_double)
61
+
62
+ client.ssl_socket
63
+ end
64
+ end
65
+
66
+ describe "#connect" do
67
+ it "connects to the remote server using OpenSSL" do
68
+ expect(ssl_socket_double).to receive(:connect).once
69
+
70
+ client.connect
71
+ end
72
+
73
+ context "gracefully handles some exceptions" do
74
+ [
75
+ SocketError,
76
+ OpenSSL::SSL::SSLError,
77
+ Timeout::Error,
78
+ Errno::EINTR,
79
+ Errno::ENETUNREACH,
80
+ Errno::ENETDOWN,
81
+ Errno::ENETRESET,
82
+ Errno::ECONNABORTED,
83
+ Errno::ECONNRESET,
84
+ Errno::ETIMEDOUT,
85
+ Errno::ECONNREFUSED,
86
+ Errno::EHOSTDOWN,
87
+ Errno::EHOSTUNREACH
88
+ ].each do |e|
89
+ it "handles #{e}" do
90
+ allow(ssl_socket_double).to receive(:connect).and_raise(e)
91
+
92
+ expect { client.connect }.to_not raise_error
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ describe "#peer_certificate" do
99
+ it "retrieves the remote host's X.509 certificate" do
100
+ expect(ssl_socket_double).to receive(:peer_cert).once
101
+
102
+ client.peer_certificate
103
+ end
104
+ end
105
+
106
+ context "when a connection error occurs" do
107
+ before do
108
+ allow(ssl_socket_double).to receive(:connect).and_raise(SocketError)
109
+ end
110
+
111
+ describe "#connect_error" do
112
+ it "returns the exception" do
113
+ client.connect
114
+ expect(client.connect_error).to be_instance_of(SocketError)
115
+ end
116
+ end
117
+
118
+ describe "#connect_failed?" do
119
+ it "returns true" do
120
+ client.connect
121
+ expect(client.connect_failed?).to eq(true)
122
+ end
123
+ end
124
+ end
125
+
126
+ context "when a successful connection occurs" do
127
+ before do
128
+ allow(ssl_socket_double).to receive(:connect)
129
+ end
130
+
131
+ describe "#connect_error" do
132
+ it "returns nil" do
133
+ client.connect
134
+ expect(client.connect_error).to be_nil
135
+ end
136
+ end
137
+
138
+ describe "#connect_failed?" do
139
+ it "returns false" do
140
+ client.connect
141
+ expect(client.connect_failed?).to eq(false)
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,87 @@
1
+ require_relative "../../spec_helper"
2
+
3
+ describe X509Sleuth::Scanner::Target do
4
+ let(:host_target) { described_class.new("localhost") }
5
+ let(:ip_target) { described_class.new("127.0.0.1") }
6
+ let(:cidr_target) { described_class.new("127.0.0.1/30") }
7
+ let(:ip_subnet_pair_target) { described_class.new("127.0.0.1/255.255.255.248") }
8
+
9
+ context "instances" do
10
+ subject { host_target }
11
+
12
+ it { should respond_to(:is_a_range?) }
13
+ it { should respond_to(:hosts) }
14
+ it { should respond_to(:target) }
15
+ end
16
+
17
+ context "when target is an IPv4 address and CIDR subnet mask" do
18
+ describe "#is_a_range?" do
19
+ it "returns true" do
20
+ expect(cidr_target.is_a_range?).to be true
21
+ end
22
+ end
23
+
24
+ describe "#hosts" do
25
+ it "returns the correct host ip addresses" do
26
+ expect(cidr_target.hosts).to have(2).items
27
+ expect(cidr_target.hosts).to include("127.0.0.1", "127.0.0.2")
28
+ end
29
+
30
+ it "omits the network and broadcast addresses" do
31
+ expect(cidr_target.hosts).to_not include("127.0.0.0", "127.0.0.3")
32
+ end
33
+ end
34
+ end
35
+
36
+ context "when target is an IPv4 address and dotted-decimal subnet mask" do
37
+ describe "#is_a_range?" do
38
+ it "returns true" do
39
+ expect(ip_subnet_pair_target.is_a_range?).to be true
40
+ end
41
+ end
42
+
43
+ describe "#hosts" do
44
+ it "returns the correct host ip addresses" do
45
+ expect(ip_subnet_pair_target.hosts).to have(6).items
46
+ expect(ip_subnet_pair_target.hosts).to include(
47
+ "127.0.0.1", "127.0.0.2", "127.0.0.3",
48
+ "127.0.0.4", "127.0.0.5", "127.0.0.6"
49
+ )
50
+ end
51
+
52
+ it "omits the network and broadcast addresses" do
53
+ expect(ip_subnet_pair_target.hosts).to_not include("127.0.0.0", "127.0.0.7")
54
+ end
55
+ end
56
+ end
57
+
58
+ context "when target is a single IPv4 address" do
59
+ describe "#is_a_range?" do
60
+ it "returns false" do
61
+ expect(ip_target.is_a_range?).to be false
62
+ end
63
+ end
64
+
65
+ describe "#hosts" do
66
+ it "returns the lone ip address" do
67
+ expect(ip_target.hosts).to have(1).item
68
+ expect(ip_target.hosts).to include("127.0.0.1")
69
+ end
70
+ end
71
+ end
72
+
73
+ context "when target is assumed to be a hostname" do
74
+ describe "#is_a_range?" do
75
+ it "returns false" do
76
+ expect(host_target.is_a_range?).to be false
77
+ end
78
+ end
79
+
80
+ describe "#hosts" do
81
+ it "returns the lone hostname" do
82
+ expect(host_target.hosts).to have(1).item
83
+ expect(host_target.hosts).to include("localhost")
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,116 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe X509Sleuth::ScannerDetailedPresenter do
4
+ let(:simple_x509_cert_double) do
5
+ double(
6
+ OpenSSL::X509::Certificate,
7
+ subject: "/OU=Domain Control Validated"\
8
+ "/CN=*.mydomain.tld",
9
+ issuer: "/C=US"\
10
+ "/ST=Arizona"\
11
+ "/L=Scottsdale"\
12
+ "/O=GoDaddy.com, Inc."\
13
+ "/OU=http://certs.godaddy.com/repository/"\
14
+ "/CN=Go Daddy Secure Certificate Authority - G2",
15
+ extensions: [],
16
+ serial: 12227668858515977,
17
+ not_before: Time.utc(2014, 4, 10, 20, 19, 9),
18
+ not_after: Time.utc(2014, 10, 3, 23, 22, 57)
19
+ )
20
+ end
21
+
22
+ let(:san_x509_cert_double) do
23
+ double(
24
+ OpenSSL::X509::Certificate,
25
+ subject: "/C=US"\
26
+ "/postalCode=19103"\
27
+ "/ST=PA"\
28
+ "/L=Philadelphia"\
29
+ "/street=123 Fake Street"\
30
+ "/O=Fake Corp"\
31
+ "/OU=Fake Department"\
32
+ "/CN=fake.example.com",
33
+ issuer: "/C=GB"\
34
+ "/ST=Greater Manchester"\
35
+ "/L=Salford"\
36
+ "/O=COMODO CA Limited"\
37
+ "/CN=COMODO High-Assurance Secure Server CA",
38
+ extensions: [
39
+ OpenSSL::X509::Extension.new("authorityKeyIdentifier", "keyid:3F:D5:B5:D0:D6:44:79:50:4A:17:A3:9B:8C:4A:DC:B8:B0:23:64:6F"),
40
+ OpenSSL::X509::Extension.new("subjectKeyIdentifier", "A3:F1:44:92:8F:53:2D:69:66:56:0A:69:20:8B:F4:6B:5F:18:88:1D"),
41
+ OpenSSL::X509::Extension.new("keyUsage", "Digital Signature, Key Encipherment", true),
42
+ OpenSSL::X509::Extension.new("basicConstraints", "CA:FALSE", true),
43
+ OpenSSL::X509::Extension.new("extendedKeyUsage", "TLS Web Server Authentication, TLS Web Client Authentication"),
44
+ OpenSSL::X509::Extension.new("certificatePolicies", "Policy: 1.3.6.1.4.1.6449.1.2.1.3.4\nCPS: https://secure.comodo.com/CPS\nPolicy: 2.23.140.1.2.2"),
45
+ OpenSSL::X509::Extension.new("crlDistributionPoints", "URI:http://crl.comodoca.com/COMODOHigh-AssuranceSecureServerCA.crl"),
46
+ OpenSSL::X509::Extension.new("authorityInfoAccess", "CA Issuers - URI:http://crt.comodoca.com/COMODOHigh-AssuranceSecureServerCA.crt\nOCSP - URI:http://ocsp.comodoca.com"),
47
+ OpenSSL::X509::Extension.new("subjectAltName", "DNS:foo.example.com, DNS:bar.example.com")
48
+
49
+ ],
50
+ serial: 12227668858515977,
51
+ not_before: Time.utc(2014, 4, 10, 20, 19, 9),
52
+ not_after: Time.utc(2014, 10, 3, 23, 22, 57)
53
+ )
54
+ end
55
+
56
+ let(:simple_client_double) do
57
+ double(
58
+ X509Sleuth::Client,
59
+ host: "www.mydomain.tld",
60
+ connect_failed?: false,
61
+ peer_certificate: simple_x509_cert_double
62
+ )
63
+ end
64
+
65
+ let(:san_client_double) do
66
+ double(
67
+ X509Sleuth::Client,
68
+ host: "fake.example.com",
69
+ connect_failed?: false,
70
+ peer_certificate: san_x509_cert_double
71
+ )
72
+ end
73
+
74
+ let(:scanner_double) do
75
+ double(
76
+ X509Sleuth::Scanner,
77
+ clients: [simple_client_double, san_client_double]
78
+ )
79
+ end
80
+
81
+ let(:scanner_presenter) { described_class.new(scanner_double) }
82
+
83
+
84
+ describe "#tableize" do
85
+ let(:ok_client_tableized_results) do
86
+ [
87
+ {
88
+ host: simple_client_double.host,
89
+ subject: simple_x509_cert_double.subject,
90
+ common_name: "*.mydomain.tld",
91
+ alt_names: "",
92
+ issuer: simple_x509_cert_double.issuer,
93
+ serial: simple_x509_cert_double.serial,
94
+ not_before: simple_x509_cert_double.not_before,
95
+ not_after: simple_x509_cert_double.not_after
96
+ },
97
+ {
98
+ host: san_client_double.host,
99
+ subject: san_x509_cert_double.subject,
100
+ common_name: "fake.example.com",
101
+ alt_names: "foo.example.com,bar.example.com",
102
+ issuer: san_x509_cert_double.issuer,
103
+ serial: san_x509_cert_double.serial,
104
+ not_before: san_x509_cert_double.not_before,
105
+ not_after: san_x509_cert_double.not_after
106
+ }
107
+ ]
108
+ end
109
+
110
+ it "returns the expected Formatador table" do
111
+ ok_client_tableized_results.each do |cert_details|
112
+ expect(scanner_presenter.tableize(scanner_double.clients)).to include(cert_details)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,94 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe X509Sleuth::ScannerPresenter do
4
+ let(:x509_cert_double) do
5
+ double(
6
+ OpenSSL::X509::Certificate,
7
+ subject: "/OU=Domain Control Validated"\
8
+ "/CN=*.mydomain.tld",
9
+ issuer: "/C=US"\
10
+ "/ST=Arizona"\
11
+ "/L=Scottsdale"\
12
+ "/O=GoDaddy.com, Inc."\
13
+ "/OU=http://certs.godaddy.com/repository/"\
14
+ "/CN=Go Daddy Secure Certificate Authority - G2",
15
+ serial: 12227668858515977,
16
+ not_before: Time.utc(2014, 4, 10, 20, 19, 9),
17
+ not_after: Time.utc(2014, 10, 3, 23, 22, 57)
18
+ )
19
+ end
20
+
21
+ let(:ok_client_double) do
22
+ instance_double(
23
+ X509Sleuth::Client,
24
+ host: "ok.client.tld",
25
+ connect_failed?: false,
26
+ peer_certificate: x509_cert_double
27
+ )
28
+ end
29
+
30
+ let(:failed_client_double) do
31
+ instance_double(
32
+ X509Sleuth::Client,
33
+ host: "failed.client.tld",
34
+ connect_failed?: true,
35
+ connect_error: TimeoutError.new,
36
+ peer_certificate: nil
37
+ )
38
+ end
39
+
40
+ let(:scanner_double) do
41
+ double(
42
+ X509Sleuth::Scanner,
43
+ clients: [ok_client_double, failed_client_double]
44
+ )
45
+ end
46
+
47
+ let(:scanner_presenter) { described_class.new(scanner_double) }
48
+
49
+ context "instances" do
50
+ subject { scanner_presenter }
51
+
52
+ it { should respond_to(:scanner).with(0).arguments }
53
+ it { should respond_to(:tableize).with(1).argument }
54
+ it { should respond_to(:filter).with(0).arguments }
55
+ it { should respond_to(:to_s).with(0).arguments }
56
+ end
57
+
58
+ describe "#filter" do
59
+ it "removes failed clients" do
60
+ filtered = scanner_presenter.filter()
61
+ expect(filtered).to include(ok_client_double)
62
+ expect(filtered).to_not include(failed_client_double)
63
+ end
64
+ end
65
+
66
+ describe "#tableize" do
67
+ let(:ok_client_tableized_result) do
68
+ {
69
+ host: ok_client_double.host,
70
+ subject: x509_cert_double.subject,
71
+ issuer: x509_cert_double.issuer,
72
+ serial: x509_cert_double.serial,
73
+ not_before: x509_cert_double.not_before,
74
+ not_after: x509_cert_double.not_after
75
+ }
76
+ end
77
+
78
+ let(:failed_client_tableized_result) do
79
+ {
80
+ host: "failed.client.tld",
81
+ connect_failed?: true,
82
+ connect_error: TimeoutError.new
83
+ }
84
+ end
85
+
86
+ it "returns the expected Formatador table" do
87
+ expect(scanner_presenter.tableize(scanner_double.clients())).to include(ok_client_tableized_result)
88
+ end
89
+
90
+ it "excludes clients with failed connections" do
91
+ expect(scanner_presenter.tableize(scanner_presenter.filter())).to_not include(failed_client_tableized_result)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,86 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe X509Sleuth::Scanner do
4
+ let(:scanner) { described_class.new }
5
+ let(:scanner_with_overrides) { described_class.new(concurrency: 127) }
6
+ let(:cidr_target_string) { "127.0.0.1/30" }
7
+ let(:host_target_string) { "bigserver.domain.local" }
8
+ let(:scanner_targets) do
9
+ [
10
+ X509Sleuth::Scanner::Target.new(cidr_target_string),
11
+ X509Sleuth::Scanner::Target.new(host_target_string)
12
+ ]
13
+ end
14
+
15
+ context "instances" do
16
+ subject { scanner }
17
+
18
+ it { should respond_to(:add_target).with(1).arguments }
19
+ it { should_not respond_to(:add_target).with(0).arguments }
20
+ it { should respond_to(:targets).with(0).arguments }
21
+ it { should respond_to(:clients).with(0).arguments }
22
+ it { should respond_to(:concurrency) }
23
+ it { should respond_to(:clients).with(0).arguments }
24
+ it { should respond_to(:run).with(0).arguments }
25
+
26
+ context "with option overrides" do
27
+ subject { scanner_with_overrides }
28
+ it "returns overrides" do
29
+ expect(subject.concurrency).to eq(127)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "#add_target" do
35
+ it "adds the target to the targets list" do
36
+ scanner.add_target(cidr_target_string)
37
+
38
+ expect(scanner.targets).to have(1).item
39
+ expect(scanner.targets).to be_all { |item| item.is_a?(X509Sleuth::Scanner::Target) }
40
+ end
41
+
42
+ it "appends to an existing targets list" do
43
+ scanner.add_target(cidr_target_string)
44
+ scanner.add_target(host_target_string)
45
+
46
+ expect(scanner.targets).to have(2).items
47
+ end
48
+ end
49
+
50
+ describe "#clients" do
51
+ before do
52
+ allow(scanner).to receive(:targets).and_return(scanner_targets)
53
+ end
54
+
55
+ it "contains a collection of clients from the loaded targets" do
56
+ scanner.add_target(cidr_target_string)
57
+ scanner.add_target(host_target_string)
58
+
59
+ expect(scanner.clients).to have(3).items
60
+ expect(scanner.clients).to be_all { |item| item.is_a?(X509Sleuth::Client) }
61
+ end
62
+ end
63
+
64
+ describe "#run" do
65
+ let(:client_double) { double(X509Sleuth::Client) }
66
+ let(:scanner_clients) { [client_double, client_double] }
67
+
68
+ before do
69
+ allow(scanner).to receive(:clients).and_return(scanner_clients)
70
+ end
71
+
72
+ it "connects to all the hosts" do
73
+ expect(client_double).to receive(:connect).exactly(2).times
74
+ scanner.run
75
+ end
76
+
77
+ it "scans the expected number of hosts in parallel" do
78
+ expect(Parallel).to receive(:each) do |arg1, arg2|
79
+ expect(arg1).to eq(scanner_clients)
80
+ expect(arg2).to include(in_threads: 5)
81
+ end
82
+
83
+ scanner.run
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,7 @@
1
+ require "spec_helper"
2
+
3
+ describe X509Sleuth do
4
+ it "returns the correct version string" do
5
+ expect(described_class.version_string).to eq("X509 Sleuth version #{X509Sleuth::VERSION}")
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: x509_sleuth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Richard Henning
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: formatador
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: netaddr
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
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: parallel
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
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: thor
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: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 3.1.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 3.1.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-collection_matchers
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: 1.1.2
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 1.1.2
111
+ - !ruby/object:Gem::Dependency
112
+ name: travis-lint
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description:
126
+ email: rich@tonaleclipse.com
127
+ executables: []
128
+ extensions: []
129
+ extra_rdoc_files: []
130
+ files:
131
+ - lib/x509_sleuth/cli.rb
132
+ - lib/x509_sleuth/client.rb
133
+ - lib/x509_sleuth/scanner/target.rb
134
+ - lib/x509_sleuth/scanner.rb
135
+ - lib/x509_sleuth/scanner_detailed_presenter.rb
136
+ - lib/x509_sleuth/scanner_presenter.rb
137
+ - lib/x509_sleuth/version.rb
138
+ - lib/x509_sleuth.rb
139
+ - spec/spec_helper.rb
140
+ - spec/x509_sleuth/client_spec.rb
141
+ - spec/x509_sleuth/scanner/target_spec.rb
142
+ - spec/x509_sleuth/scanner_detailed_presenter_spec.rb
143
+ - spec/x509_sleuth/scanner_presenter_spec.rb
144
+ - spec/x509_sleuth/scanner_spec.rb
145
+ - spec/x509_sleuth_spec.rb
146
+ homepage: https://github.com/rhenning/x509_sleuth
147
+ licenses:
148
+ - MIT
149
+ metadata: {}
150
+ post_install_message:
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - '>='
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 2.0.14
167
+ signing_key:
168
+ specification_version: 4
169
+ summary: A tool to remotely scan for and investigate X.509 certificates used in SSL/TLS
170
+ test_files:
171
+ - spec/spec_helper.rb
172
+ - spec/x509_sleuth/client_spec.rb
173
+ - spec/x509_sleuth/scanner/target_spec.rb
174
+ - spec/x509_sleuth/scanner_detailed_presenter_spec.rb
175
+ - spec/x509_sleuth/scanner_presenter_spec.rb
176
+ - spec/x509_sleuth/scanner_spec.rb
177
+ - spec/x509_sleuth_spec.rb
178
+ has_rdoc: