Browse Source

Test cert signature for server

new-scoring
aeris 2 years ago
parent
commit
ef0834900f

+ 7
- 6
lib/cryptcheck/tls/cert.rb View File

@@ -5,7 +5,8 @@ module CryptCheck
5 5
 					'/usr/share/ca-certificates/mozilla'
6 6
 			]
7 7
 
8
-			SIGNATURE_ALGORITHMS = {
8
+			SIGNATURE_ALGORITHMS      = %i(md2 mdc2 md4 md5 ripemd160 sha sha1 sha2 rsa dss ecc ghost).freeze
9
+			SIGNATURE_ALGORITHMS_X509 = {
9 10
 					'dsaWithSHA'                             => %i(sha1 dss),
10 11
 					'dsaWithSHA1'                            => %i(sha1 dss),
11 12
 					'dsaWithSHA1_2'                          => %i(sha1 dss),
@@ -41,15 +42,15 @@ module CryptCheck
41 42
 					'id_GostR3411_94_with_GostR3410_94'      => %i(ghost),
42 43
 					'id_GostR3411_94_with_GostR3410_94_cc'   => %i(ghost),
43 44
 					'id_GostR3411_94_with_GostR3410_2001_cc' => %i(ghost)
44
-			}
45
-			WEAK_SIGN = {
45
+			}.freeze
46
+			WEAK_SIGN                 = {
46 47
 					critical: %i(mdc2 md2 md4 md5 sha sha1)
47
-			}
48
+			}.freeze
48 49
 
49
-			%i(md2 mdc2 md4 md5 ripemd160 sha sha1 sha2 rsa dss ecc ghost).each do |name|
50
+			SIGNATURE_ALGORITHMS.each do |name|
50 51
 				class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
51 52
 					def #{name}?
52
-						SIGNATURE_ALGORITHMS[@cert.signature_algorithm].include? :#{name}
53
+						SIGNATURE_ALGORITHMS_X509[@cert.signature_algorithm].include? :#{name}
53 54
 					end
54 55
 				RUBY_EVAL
55 56
 			end

+ 1
- 1
lib/cryptcheck/tls/grade.rb View File

@@ -108,7 +108,7 @@ module CryptCheck
108 108
 					#[:aead_only, Proc.new { |s| s.aead_only? }, :best],
109 109
 			] + Cert::WEAK_SIGN.collect do |level, hashes|
110 110
 				hashes.collect do |hash|
111
-					["#{hash}_sig?".to_sym, Proc.new { |s| s.certs.any? &"#{hash}?".to_sym }, level ]
111
+					["#{hash}_sig?".to_sym, Proc.new { |s| s.call "#{hash}_sig?".to_sym }, level ]
112 112
 				end
113 113
 			end.flatten(1)).freeze
114 114
 

+ 10
- 3
lib/cryptcheck/tls/server.rb View File

@@ -50,7 +50,6 @@ module CryptCheck
50 50
 				check_fallback_scsv
51 51
 
52 52
 				verify_certs
53
-				exit
54 53
 			end
55 54
 
56 55
 			def supported_method?(method)
@@ -456,7 +455,7 @@ module CryptCheck
456 455
 				# { curve => connection, ... }
457 456
 				certs  += @ecdsa_certs.values
458 457
 				# For anonymous cipher, there is no certificate at all
459
-				certs = certs.reject { |c| c.peer_cert.nil? }
458
+				certs  = certs.reject { |c| c.peer_cert.nil? }
460 459
 				# Then, fetch cert
461 460
 				certs  = certs.collect { |c| Cert.new c }
462 461
 				# Then, filter cert to keep uniq fingerprint
@@ -479,7 +478,7 @@ module CryptCheck
479 478
 						Logger.info { '    Trust : ' + 'untrusted'.colorize(:error) + ' - ' + trust }
480 479
 					end
481 480
 				end
482
-				@keys   = @certs.collect &:key
481
+				@keys = @certs.collect &:key
483 482
 			end
484 483
 
485 484
 			def uniq_dh
@@ -493,6 +492,14 @@ module CryptCheck
493 492
 				end
494 493
 				@dh = dh
495 494
 			end
495
+
496
+			Cert::SIGNATURE_ALGORITHMS.each do |s|
497
+				class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
498
+					def #{s}_sign?
499
+						@certs.any? &:#{s}?
500
+					end
501
+				RUBY_EVAL
502
+			end
496 503
 		end
497 504
 
498 505
 		class TcpServer < Server

+ 53
- 0
spec/cryptcheck/tls/server_spec.rb View File

@@ -0,0 +1,53 @@
1
+require 'faketime'
2
+
3
+describe CryptCheck::Tls::Server do
4
+	before :all do
5
+		FakeTime.freeze Time.utc(2000, 1, 1).to_i
6
+	end
7
+
8
+	after :all do
9
+		FakeTime.unfreeze
10
+	end
11
+
12
+	describe '#md5_sign?' do
13
+		it 'must detect server using MD5 certificate' do
14
+			tls_serv do
15
+				server = CryptCheck::Tls::TcpServer.new 'localhost', ::Socket::PF_INET, '127.0.0.1', 5000
16
+				expect(server.md5_sign?).to be false
17
+			end
18
+
19
+			tls_serv material: [:md5, [:rsa, 1024]] do
20
+				server = CryptCheck::Tls::TcpServer.new 'localhost', ::Socket::PF_INET, '127.0.0.1', 5000
21
+				expect(server.md5_sign?).to be true
22
+			end
23
+		end
24
+	end
25
+
26
+	describe '#sha1_sign?' do
27
+		it 'must detect server using SHA1 certificate' do
28
+			tls_serv do
29
+				server = CryptCheck::Tls::TcpServer.new 'localhost', ::Socket::PF_INET, '127.0.0.1', 5000
30
+				expect(server.sha1_sign?).to be false
31
+			end
32
+
33
+			tls_serv material: [:sha1, [:rsa, 1024]] do
34
+				server = CryptCheck::Tls::TcpServer.new 'localhost', ::Socket::PF_INET, '127.0.0.1', 5000
35
+				expect(server.sha1_sign?).to be true
36
+			end
37
+		end
38
+	end
39
+
40
+	describe '#sha2_sign?' do
41
+		it 'must detect server using SHA2 certificate' do
42
+			tls_serv do
43
+				server = CryptCheck::Tls::TcpServer.new 'localhost', ::Socket::PF_INET, '127.0.0.1', 5000
44
+				expect(server.sha2_sign?).to be true
45
+			end
46
+
47
+			tls_serv material: [:md5, :sha1] do
48
+				server = CryptCheck::Tls::TcpServer.new 'localhost', ::Socket::PF_INET, '127.0.0.1', 5000
49
+				expect(server.sha2_sign?).to be false
50
+			end
51
+		end
52
+	end
53
+end

+ 57
- 65
spec/helpers.rb View File

@@ -1,61 +1,42 @@
1 1
 $:.unshift File.expand_path File.join File.dirname(__FILE__), '../lib'
2 2
 require 'rubygems'
3 3
 require 'bundler/setup'
4
+Bundler.require :default, :development
4 5
 require 'cryptcheck'
5 6
 Dir['./spec/**/support/**/*.rb'].sort.each { |f| require f }
6 7
 
7 8
 CryptCheck::Logger.level = ENV['LOG'] || :none
8 9
 
9 10
 module Helpers
10
-	DEFAULT_KEY = 'rsa-1024'
11
-	DEFAULT_METHOD = :TLSv1_2
12
-	DEFAULT_CIPHERS = %w(AES128-SHA)
13
-	DEFAULT_ECC_CURVE = 'secp256k1'
14
-	DEFAULT_DH_SIZE = 1024
15
-
16
-	OpenSSL::PKey::EC.send :alias_method, :private?, :private_key?
17
-
18
-	def key(name)
19
-		open(File.join(File.dirname(__FILE__), 'resources', "#{name}.pem"), 'r') { |f| OpenSSL::PKey.read f }
11
+	DEFAULT_METHODS  = %i(TLSv1_2)
12
+	DEFAULT_CIPHERS  = %i(ECDHE+AES)
13
+	DEFAULT_CURVES   = %i(prime256v1)
14
+	DEFAULT_DH       = [:rsa, 4096]
15
+	DEFAULT_MATERIAL = [[:ecdsa, :prime256v1]]
16
+	DEFAULT_CHAIN    = %w(intermediate ca)
17
+	DEFAULT_HOST     = 'localhost'
18
+	DEFAULT_PORT     = 5000
19
+
20
+	def key(type, name=nil)
21
+		name = if name
22
+				   "#{type}-#{name}"
23
+			   else
24
+				   type
25
+			   end
26
+		OpenSSL::PKey.read File.read "spec/resources/#{name}.pem"
20 27
 	end
21 28
 
22
-	def dh(name)
23
-		open(File.join(File.dirname(__FILE__), 'resources', "dh-#{name}.pem"), 'r') { |f| OpenSSL::PKey::DH.new f }
29
+	def cert(type, name=nil)
30
+		name = if name
31
+				   "#{type}-#{name}"
32
+			   else
33
+				   type
34
+			   end
35
+		OpenSSL::X509::Certificate.new File.read "spec/resources/#{name}.crt"
24 36
 	end
25 37
 
26
-	def certificate(key, domain)
27
-		cert            = OpenSSL::X509::Certificate.new
28
-		cert.version    = 2
29
-		cert.serial     = rand 2**(20*8-1) .. 2**(20*8)
30
-		cert.not_before = Time.now
31
-		cert.not_after  = cert.not_before + 60*60
32
-
33
-		cert.public_key = case key
34
-							  when OpenSSL::PKey::EC
35
-								  curve             = key.group.curve_name
36
-								  public            = OpenSSL::PKey::EC.new curve
37
-								  public.public_key = key.public_key
38
-								  public
39
-							  else
40
-								  key.public_key
41
-						  end
42
-
43
-		name         = OpenSSL::X509::Name.parse "CN=#{domain}"
44
-		cert.subject = name
45
-		cert.issuer  = name
46
-
47
-		extension_factory                     = OpenSSL::X509::ExtensionFactory.new nil, cert
48
-		extension_factory.subject_certificate = cert
49
-		extension_factory.issuer_certificate  = cert
50
-
51
-		cert.add_extension extension_factory.create_extension 'basicConstraints', 'CA:TRUE', true
52
-		cert.add_extension extension_factory.create_extension 'keyUsage', 'keyEncipherment, dataEncipherment, digitalSignature,nonRepudiation,keyCertSign'
53
-		cert.add_extension extension_factory.create_extension 'extendedKeyUsage', 'serverAuth, clientAuth'
54
-		cert.add_extension extension_factory.create_extension 'subjectKeyIdentifier', 'hash'
55
-		cert.add_extension extension_factory.create_extension 'authorityKeyIdentifier', 'keyid:always'
56
-		cert.add_extension extension_factory.create_extension 'subjectAltName', "DNS:#{domain}"
57
-
58
-		cert.sign key, OpenSSL::Digest::SHA512.new
38
+	def dh(name)
39
+		OpenSSL::PKey::DH.new File.read "spec/resources/dh-#{name}.pem"
59 40
 	end
60 41
 
61 42
 	def serv(server, process, &block)
@@ -95,32 +76,43 @@ module Helpers
95 76
 		end
96 77
 	end
97 78
 
98
-	def context(key: DEFAULT_KEY, domain: 'localhost', # Key & certificate
99
-				version: DEFAULT_METHOD, ciphers: DEFAULT_CIPHERS, # TLS version and ciphers
100
-				dh: DEFAULT_DH_SIZE, ecdh: DEFAULT_ECC_CURVE) # DHE & ECDHE
101
-		key  = key key
102
-		cert = certificate key, domain
79
+	def context(certs, keys, chain=[],
80
+				methods: DEFAULT_METHODS, ciphers: DEFAULT_CIPHERS,
81
+				dh:, curves: DEFAULT_CURVES, server_preference: true)
82
+		context         = OpenSSL::SSL::SSLContext.new
103 83
 
104
-		context         = OpenSSL::SSL::SSLContext.new version
105
-		context.cert    = cert
106
-		context.key     = key
107
-		context.ciphers = ciphers
84
+		context.options |= OpenSSL::SSL::OP_NO_SSLv2 unless methods.include? :SSLv2
85
+		context.options |= OpenSSL::SSL::OP_NO_SSLv3 unless methods.include? :SSLv3
86
+		context.options |= OpenSSL::SSL::OP_NO_TLSv1 unless methods.include? :TLSv1
87
+		context.options |= OpenSSL::SSL::OP_NO_TLSv1_1 unless methods.include? :TLSv1_1
88
+		context.options |= OpenSSL::SSL::OP_NO_TLSv1_2 unless methods.include? :TLSv1_2
89
+		context.options |= OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE if server_preference
108 90
 
109
-		if dh
110
-			dh                      = dh dh
111
-			context.tmp_dh_callback = proc { dh }
112
-		end
113
-		context.ecdh_curves = ecdh if ecdh
91
+		context.certs            = certs
92
+		context.keys             = keys
93
+		context.extra_chain_cert = chain if chain
94
+
95
+		context.ciphers         = ciphers.join ':'
96
+		context.tmp_dh_callback = proc { dh } if dh
97
+		context.ecdh_curves     = curves.join ':' if curves
114 98
 
115 99
 		context
116 100
 	end
117 101
 
118
-	def tls_serv(key: DEFAULT_KEY, domain: 'localhost', # Key & certificate
119
-				 version: DEFAULT_METHOD, ciphers: DEFAULT_CIPHERS, # TLS version and ciphers
120
-				 dh: DEFAULT_DH_SIZE, ecdh: DEFAULT_ECC_CURVE, # DHE & ECDHE
121
-				 host: '127.0.0.1', port: 5000, # Binding
102
+	def tls_serv(host: DEFAULT_HOST, port: DEFAULT_PORT,
103
+				 material: DEFAULT_MATERIAL, chain: DEFAULT_CHAIN,
104
+				 methods: DEFAULT_METHODS, ciphers: DEFAULT_CIPHERS,
105
+				 dh: nil, curves: DEFAULT_CURVES, server_preference: true,
122 106
 				 process: nil, &block)
123
-		context    = context(key: key, domain: domain, version: version, ciphers: ciphers, dh: dh, ecdh: ecdh)
107
+		keys  = material.collect { |m| key *m }
108
+		certs = material.collect { |m| cert *m }
109
+		chain = chain.collect { |c| cert c }
110
+		dh    = dh dh if dh
111
+
112
+		context    = context certs, keys, chain,
113
+							 methods:           methods, ciphers: ciphers,
114
+							 dh:                dh, curves: curves,
115
+							 server_preference: server_preference
124 116
 		tcp_server = TCPServer.new host, port
125 117
 		tls_server = OpenSSL::SSL::SSLServer.new tcp_server, context
126 118
 		begin
@@ -131,7 +123,7 @@ module Helpers
131 123
 		end
132 124
 	end
133 125
 
134
-	def plain_serv(host: '127.0.0.1', port: 5000, process: nil, &block)
126
+	def plain_serv(host='127.0.0.1', port=5000, process: nil, &block)
135 127
 		tcp_server = TCPServer.new host, port
136 128
 		begin
137 129
 			serv tcp_server, process, &block

Loading…
Cancel
Save