tls-map 1.3.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ac0d9c7dbfc9715ca7387df65617f1202a06ab70b72f764356e6bdb3cbeda3d
4
- data.tar.gz: 37f02d9953363e4fb90e940d894b1ecdafe9bba9ae1636540f9593ee3c37b808
3
+ metadata.gz: 1d94873d0b6f9c1274ca7ae4b4510cbbfb5351fb71b31d5fd6b7b51a700810b5
4
+ data.tar.gz: c706973ad2b5b944ad41b72447ad2b5b6fd0d6cd8e7697ed2e0441598c905157
5
5
  SHA512:
6
- metadata.gz: 8a514c524c42e6765e55817dea46b905fdf717e6609e0f1efc2f2555249938b750ae79325b5c2ba3cc58b645f0b378f0f3f13d877a9be37a74dff033b420edbd
7
- data.tar.gz: 8691bfcdb56bbfe8daa408b290f06f1eb2e9dd2f564a9c86e59b2c64e077b037b45b27e4d4e1000fab7b6fc418064b0d4d1b66e1eb174b221b0fe8c022b72b1c
6
+ metadata.gz: 36abc411fbfb1139a1abce1795dc76dad40baf48a801ff52302e0f13a5e8d62f484a68e024523417f6d926b9a175043d0256b15a3840f5b1ed3e1a90d26a7f65
7
+ data.tar.gz: c0274a56daae44a2192e761bc782ec87ec71d2616738ed8053f8e1e2beb8a6bce7d6e95d694356f72d3ae891c69d5b210e78f9b84f2bd308016205f70b9e852d
data/LICENSE CHANGED
@@ -1,5 +1,6 @@
1
1
  MIT License
2
2
 
3
+ Copyright (c) 2021 Alexandre ZANNI
3
4
  Copyright (c) 2021 Alexandre ZANNI at SEC-IT
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
data/bin/tls-map CHANGED
@@ -5,7 +5,7 @@
5
5
  require 'pp'
6
6
  # Project internal
7
7
  require 'tls_map'
8
- require 'tls_map/cli'
8
+ require 'tls_map/cli/cli'
9
9
  # External
10
10
  require 'docopt'
11
11
  require 'paint'
@@ -16,23 +16,23 @@ doc = <<~DOCOPT
16
16
  TLS map #{TLSmap::VERSION}
17
17
 
18
18
  Usage:
19
- tls-map search <critera> <term> [-o <output> --force -e -a] [--no-color --debug]
20
- tls-map bulk <critera> <file> [-q <output> --force] [--no-color --debug]
19
+ tls-map search <criteria> <term> [-o <output> --force -e -a] [--no-color --debug]
20
+ tls-map bulk <criteria> <file> [-q <output> --force] [--no-color --debug]
21
21
  tls-map export <filename> <format> [--force] [--debug]
22
- tls-map extract <filename> <format> [--no-color --debug]
23
- tls-map update [--debug]
22
+ tls-map extract <filename> <format> [--no-color --debug [--only-weak | --hide-weak]]
23
+ tls-map update [--with-extended] [--debug]
24
24
  tls-map -h | --help
25
25
  tls-map --version
26
26
 
27
27
  Search options: (offline) search and translate cipher names between SSL/TLS libraries
28
- <critera> The type of term. Accepted values: codepoint, iana, openssl, gnutls, nss.
28
+ <criteria> The type of term. Accepted values: codepoint, iana, openssl, gnutls, nss.
29
29
  <term> The cipher algorithm name.
30
30
  -o, --output <output> Displayed fields. Accepted values: all, codepoint, iana, openssl, gnutls, nss. [default: all]
31
31
  -e, --extended (Online) Display additional information about the cipher (requires output = all or iana)
32
32
  -a, --acronym (Online) Display full acronym name (requires -e / --extended option)
33
33
 
34
34
  Bulk options: (offline) search and translate cipher names between SSL/TLS libraries in bulk
35
- <critera> The type of term. Accepted values: codepoint, iana, openssl, gnutls, nss.
35
+ <criteria> The type of term. Accepted values: codepoint, iana, openssl, gnutls, nss.
36
36
  <file> File containing the cipher algorithm names, one per line.
37
37
  -q, --output2 <output> Displayed fields. Accepted values: codepoint, iana, openssl, gnutls, nss. [default: iana]
38
38
 
@@ -43,8 +43,11 @@ doc = <<~DOCOPT
43
43
  Extract options: (offline) extract ciphers from external tools output file
44
44
  <filename> The external tool output file
45
45
  <format> Supported formats: sslyze, sslscan2, testssl, ssllabs-scan (check the documentation for the expected file format)
46
+ --only-weak Show only ciphers with a security level equal to weak or insecure (hide secure and recommended) (work only with TLS not SSL).
47
+ --hide-weak Hide ciphers with a security level equal to weak or insecure (show only secure and recommended) (work only with TLS not SSL).
46
48
 
47
49
  Update options: (online) DANGEROUS, will break database integrity, force option will be required
50
+ --with-extended (Online) Also save extended information used by search --extended option.
48
51
 
49
52
  Other options:
50
53
  --force Force parsing even if integrity check failed (DANGEROUS, may result in command execution vulnerability)
@@ -60,7 +63,7 @@ begin
60
63
  pp args if args['--debug']
61
64
  if args['search']
62
65
  cli = TLSmap::CLI.new(args['--force'])
63
- res = cli.search(args['<critera>'].to_sym, args['<term>'], args['--output'].to_sym)
66
+ res = cli.search(args['<criteria>'].to_sym, args['<term>'], args['--output'].to_sym)
64
67
  puts Paint['No match found', :red] if res.empty?
65
68
  res.each do |k, v|
66
69
  puts "#{Paint[k, :green]}: #{Paint[v, :white]}"
@@ -71,6 +74,7 @@ begin
71
74
  ext = tmext_i.extend(res[:iana])
72
75
  dic = tmext::DICO
73
76
  sev = tmext::VULN_SEVERITY
77
+ sec_lvl = tmext::SECURITY_LEVEL
74
78
  ext.each do |k, v|
75
79
  case k
76
80
  when 'vulns'
@@ -81,6 +85,8 @@ begin
81
85
  end
82
86
  when 'tls_version'
83
87
  puts "#{Paint[dic[k], :magenta]}: #{Paint[v.join(', '), :white]}"
88
+ when 'security'
89
+ puts "#{Paint[dic[k], :magenta]}: #{Paint[v, sec_lvl[v][:color]]}"
84
90
  else
85
91
  print "#{Paint[dic[k], :magenta]}: #{Paint[v, :white]}"
86
92
  print " (#{tmext_i.translate_acronym(v)})" if args['--acronym'] && !tmext_i.translate_acronym(v).nil? # rubocop:disable Metrics/BlockNesting
@@ -90,10 +96,10 @@ begin
90
96
  end
91
97
  elsif args['bulk']
92
98
  cli = TLSmap::CLI.new(args['--force'])
93
- res = cli.bulk_search(args['<critera>'].to_sym, args['<file>'], args['--output2'].to_sym)
99
+ res = cli.bulk_search(args['<criteria>'].to_sym, args['<file>'], args['--output2'].to_sym)
94
100
  puts Paint['No match found', :red] if res.empty?
95
101
  res.each do |h|
96
- puts "#{Paint[h[args['--output2'].to_sym], :green]}"
102
+ puts Paint[h[args['--output2'].to_sym], :green]
97
103
  end
98
104
  elsif args['export']
99
105
  cli = TLSmap::CLI.new(args['--force'])
@@ -103,13 +109,26 @@ begin
103
109
  extractor = TLSmap::App::Extractor.new
104
110
  ciphers = extractor.parse(args['<format>'], args['<filename>'])
105
111
  ciphers.each do |k, v|
106
- puts Paint[k, :blue] unless v.empty?
107
- puts Paint[v.join("\n"), :white] unless v.empty?
112
+ if args['--only-weak'] || args['--hide-weak']
113
+ cliext = TLSmap::CLI::Extended.new
114
+ v.each do |alg|
115
+ ci = TLSmap::App::Cipher.new(:iana, alg, enhanced_data: cliext.enhanced_data)
116
+ puts Paint[alg, :white] if (args['--only-weak'] && !ci.should_i_use?) ||
117
+ (args['--hide-weak'] && ci.should_i_use?)
118
+ end
119
+ else
120
+ puts Paint[k, :blue] unless v.empty?
121
+ puts Paint[v.join("\n"), :white] unless v.empty?
122
+ end
108
123
  end
109
124
  elsif args['update']
110
125
  cli = TLSmap::CLI.new
111
126
  cli.update
112
- puts 'Database updated'
127
+ if args['--with-extended']
128
+ cliext = TLSmap::CLI::Extended.new
129
+ cliext.update
130
+ end
131
+ puts 'Database(s) updated'
113
132
  end
114
133
  rescue Docopt::Exit => e
115
134
  puts e.message
Binary file
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Project internal
4
+ require 'tls_map/cli/cli'
5
+
6
+ module TLSmap
7
+ # TLS mapping
8
+ class App
9
+ # Manipulate cipher suite information
10
+ class Cipher
11
+ # Get the hexadecimal codepoint of the cipher suite
12
+ # @return [String] Hexadecimal codepoint
13
+ attr_reader :codepoint
14
+
15
+ # Get the IANA name of the cipher suite
16
+ # @return [String] IANA name
17
+ attr_reader :iana
18
+
19
+ # Get the OpenSSL name of the cipher suite
20
+ # @return [String] OpenSSL name
21
+ attr_reader :openssl
22
+
23
+ # Get the GnuTLS name of the cipher suite
24
+ # @return [String] GnuTLS name
25
+ attr_reader :gnutls
26
+
27
+ # Get the NSS name of the cipher suite
28
+ # @return [String] NSS name
29
+ attr_reader :nss
30
+
31
+ # Get extended information
32
+ # @!attribute [r] extended
33
+ # @return [Hash]
34
+ # @example
35
+ # ci = TLSmap::App::Cipher.new(:iana, 'TLS_RSA_WITH_SEED_CBC_SHA')
36
+ # ci.extended
37
+ # # =>
38
+ # # {"protocol_version"=>"TLS",
39
+ # # "kex_algorithm"=>"RSA",
40
+ # # "auth_algorithm"=>"RSA",
41
+ # # "enc_algorithm"=>"SEED CBC",
42
+ # # "hash_algorithm"=>"SHA",
43
+ # # "security"=>"weak",
44
+ # # "tls_version"=>["TLS1.0", "TLS1.1", "TLS1.2"],
45
+ # # "vulns"=>
46
+ # # [{:severity=>1, :description=>"This key exchange algorithm does not support Perfect Forward Secrecy (PFS)
47
+ # # which is recommended, so attackers cannot decrypt the complete communication stream."},
48
+ # # {:severity=>1,
49
+ # # :description=>
50
+ # # "In 2013, researchers demonstrated a timing attack against several TLS implementations using the CBC
51
+ # # encryption algorithm (see [isg.rhul.ac.uk](http://www.isg.rhul.ac.uk/tls/Lucky13.html)). Additionally,
52
+ # # the CBC mode is vulnerable to plain-text attacks in TLS 1.0, SSL 3.0 and lower. A fix has been
53
+ # # introduced with TLS 1.2 in form of the GCM mode which is not vulnerable to the BEAST attack. GCM should
54
+ # # be preferred over CBC."},
55
+ # # {:severity=>1, :description=>"The Secure Hash Algorithm 1 has been proven to be insecure as of 2017 (see
56
+ # # [shattered.io](https://shattered.io))."}],
57
+ # # "url"=>"https://ciphersuite.info/cs/TLS_RSA_WITH_SEED_CBC_SHA/"}
58
+ def extended
59
+ fetch_extended
60
+ @extended
61
+ end
62
+
63
+ # Initialize {TLSmap::App::Cipher} instance
64
+ # @param type [Symbol] Same as `criteria` from {TLSmap::App#search}
65
+ # @param value [String] Same as `term` from {TLSmap::App#search}
66
+ # @param opts [Hash] the option hash
67
+ # @option opts [Hash] :tls_map mapping of all TLS cipher suites returned by {App#tls_map}.
68
+ # (better performance for batch usage)
69
+ # @option opts [Hash] :enhanced_data enhanced information of all cipher suites returned by
70
+ # {Extended#enhanced_data}. (better performance for batch usage)
71
+ # @example
72
+ # # Offline TLS data + online extended data
73
+ # ci = TLSmap::App::Cipher.new(:iana, 'TLS_DH_anon_WITH_RC4_128_MD5')
74
+ # # Online TLS data + online extended data
75
+ # tm = TLSmap::App.new
76
+ # ci = TLSmap::App::Cipher.new(:iana, 'TLS_DH_anon_WITH_RC4_128_MD5', tls_map: tm.tls_map)
77
+ # # Offline TLS data + online extended data but more efficient for batch requesting
78
+ # tmext = TLSmap::App::Extended.new
79
+ # tmext.enhance_all
80
+ # ci = TLSmap::App::Cipher.new(:iana, 'TLS_DH_anon_WITH_RC4_128_MD5', enhanced_data: tmext.enhanced_data)
81
+ # # Offline TLS data + offline extended data (better performance but may be outdated)
82
+ # cliext = TLSmap::CLI::Extended.new
83
+ # ci = TLSmap::App::Cipher.new(:iana, 'TLS_DH_anon_WITH_RC4_128_MD5', enhanced_data: cliext.enhanced_data)
84
+ def initialize(type, value, opts = {}) # rubocop:disable Metrics/MethodLength
85
+ res = if opts[:tls_map].nil?
86
+ TLSmap::CLI.new.search(type, value)
87
+ else
88
+ TLSmap::App.search(opts[:tls_map], type, value)
89
+ end
90
+ @codepoint = res[:codepoint]
91
+ @iana = res[:iana]
92
+ @openssl = res[:openssl]
93
+ @gnutls = res[:gnutls]
94
+ @nss = res[:nss]
95
+ @extended = opts.dig(:enhanced_data, @iana)
96
+ end
97
+
98
+ # Retrieve extended data by using #{App:Extended}
99
+ def fetch_extended
100
+ return unless @extended.nil?
101
+
102
+ tmext = TLSmap::App::Extended.new
103
+ @extended = tmext.extend(@iana)
104
+ end
105
+
106
+ # Is the security level defined to `weak`?
107
+ # @return [Boolean]
108
+ def weak?
109
+ fetch_extended
110
+ @extended['security'] == 'weak'
111
+ end
112
+
113
+ # Is the security level defined to `insecure`?
114
+ # @return [Boolean]
115
+ def insecure?
116
+ fetch_extended
117
+ @extended['security'] == 'insecure'
118
+ end
119
+
120
+ # Is the security level defined to `secure`?
121
+ # @return [Boolean]
122
+ def secure?
123
+ fetch_extended
124
+ @extended['security'] == 'secure'
125
+ end
126
+
127
+ # Is the security level defined to `recommended`?
128
+ # @return [Boolean]
129
+ def recommended?
130
+ fetch_extended
131
+ @extended['security'] == 'recommended'
132
+ end
133
+
134
+ # Is the security level defined to `secure` or `recommended`?
135
+ # It will return `false` for `weak` and `insecure` cipher suites.
136
+ # @return [Boolean]
137
+ def should_i_use?
138
+ recommended? || secure?
139
+ end
140
+
141
+ # Is the cipher suite vulnerable?
142
+ # @return [Boolean] `true` if one (or more) vulnerability is declared
143
+ def vulnerable?
144
+ fetch_extended
145
+ !@extended['vulns'].empty?
146
+ end
147
+
148
+ protected :fetch_extended
149
+ end
150
+ end
151
+ end
@@ -22,9 +22,9 @@ module TLSmap
22
22
  ROOT = 'https://ciphersuite.info/'
23
23
  # Root URL of Cipher Suite Info API
24
24
  API_ROOT = "#{ROOT}api/"
25
- # URL of the data file containig vulnerabilities information
25
+ # URL of the data file containing vulnerabilities information
26
26
  VULN_DATA = 'https://raw.githubusercontent.com/hcrudolph/ciphersuite.info/master/directory/fixtures/00_vulnerabilities.yaml'
27
- # URL of the data file containig technologies information
27
+ # URL of the data file containing technologies information
28
28
  TECH_DATA = 'https://raw.githubusercontent.com/hcrudolph/ciphersuite.info/master/directory/fixtures/01_technologies.yaml'
29
29
  # Hash mapping API key and display name for CLI
30
30
  DICO = {
@@ -44,26 +44,65 @@ module TLSmap
44
44
  1 => { title: 'Medium', color: 'orange' },
45
45
  2 => { title: 'High', color: :red }
46
46
  }.freeze
47
+ # Hash mapping the security level used by the API and color for the CLI
48
+ SECURITY_LEVEL = {
49
+ 'recommended' => { color: :green },
50
+ 'secure' => { color: :green },
51
+ 'weak' => { color: 'orange' },
52
+ 'insecure' => { color: :red }
53
+ }.freeze
47
54
 
48
- include Utils
49
- protected :tmpfile
55
+ # Get the enhanced information of all cipher suites returned by {enhance_all}.
56
+ # @return [Hash] Enhanced information of all cipher suites
57
+ attr_reader :enhanced_data
50
58
 
51
59
  # Will automatically fetch source files and parse them.
52
60
  def initialize
53
- @tech_file = tmpfile('tech', TECH_DATA)
54
- @vuln_file = tmpfile('vuln', VULN_DATA)
61
+ @tech_file = Utils.tmpfile('tech', TECH_DATA)
62
+ @vuln_file = Utils.tmpfile('vuln', VULN_DATA)
55
63
  @tech = parse_tech
56
64
  @vuln = parse_vuln
65
+ @ciphersuite_all = nil
66
+ @enhanced_data = nil
67
+ end
68
+
69
+ # Fetch all cipher suite data from ciphersuite.info and store it in the instance attribute for batch usage.
70
+ def fetch_ciphersuite
71
+ return unless @ciphersuite_all.nil?
72
+
73
+ @ciphersuite_all = JSON.parse(Net::HTTP.get(URI("#{API_ROOT}cs/")))['ciphersuites'].reduce(:merge!)
74
+ end
75
+
76
+ # Enhance data from ciphersuite.info for all cipher suites and store it
77
+ # for batch usage.
78
+ # The data will be available through {enhanced_data}.
79
+ def enhance_all
80
+ fetch_ciphersuite
81
+ out = {}
82
+ @ciphersuite_all.each do |k, _v|
83
+ out.store(k, extend(k, true))
84
+ end
85
+ @enhanced_data = out
57
86
  end
58
87
 
59
- # Retrieve advanced about a cipher on Cipher Suite Info API and enhanced it
88
+ # Retrieve advanced information about a cipher on Cipher Suite Info API and enhanced it.
89
+ # Fetch only the requested cipher suite, small network footprint, ideal for low bandwidth or punctual use.
60
90
  # @param iana_name [String] IANA cipher name
61
- # @return [Hash] Hash containing advanced information. The keys are the same as {DICO}. All valeus are string
91
+ # @param caching [Boolean] if true will fetch info for all cipher suites the 1st time and used the cached value
92
+ # for further requests
93
+ # @return [Hash] Hash containing advanced information. The keys are the same as {DICO}. All values are string
62
94
  # except `vulns` which is an array of hashes containing two keys: `:severity` (integer) and `:description`
63
95
  # (string). Each hash in `vulns` correspond to a vulnerability.
64
- def extend(iana_name) # rubocop:disable Metrics/MethodLength
65
- obj = Net::HTTP.get(URI("#{API_ROOT}cs/#{iana_name}/"))
66
- out = JSON.parse(obj)[iana_name]
96
+ def extend(iana_name, caching = false) # rubocop:disable Metrics/MethodLength
97
+ if caching
98
+ fetch_ciphersuite
99
+ out = @ciphersuite_all[iana_name]
100
+ else
101
+ obj = Net::HTTP.get(URI("#{API_ROOT}cs/#{iana_name}/"))
102
+ out = JSON.parse(obj)[iana_name]
103
+ end
104
+ return {} if out.nil?
105
+
67
106
  out.store('vulns', [])
68
107
  %w[openssl_name gnutls_name hex_byte_1 hex_byte_2].each do |key|
69
108
  out.delete(key)
@@ -117,7 +156,7 @@ module TLSmap
117
156
  nil
118
157
  end
119
158
 
120
- protected :parse_tech, :parse_vuln
159
+ protected :parse_tech, :parse_vuln, :fetch_ciphersuite
121
160
  end
122
161
  end
123
162
  end
@@ -3,7 +3,7 @@
3
3
  # Ruby internal
4
4
  require 'json'
5
5
  # Project internal
6
- require 'tls_map/cli'
6
+ require 'tls_map/cli/cli'
7
7
  # External
8
8
  require 'rexml/document'
9
9
 
@@ -80,6 +80,26 @@ module TLSmap
80
80
  def parse(tool, file)
81
81
  # Convert string to class
82
82
  @ciphers = Object.const_get("TLSmap::App::Extractor::#{normalize(tool)}").parse(file)
83
+ rescue StandardError
84
+ warn helper(tool)
85
+ end
86
+
87
+ # Commands for {helper}
88
+ CMD = {
89
+ 'sslyze' => 'sslyze --json_out=example.org.json example.org',
90
+ 'sslscan2' => 'sslscan2 --show-cipher-ids --xml=example.org.xml example.org',
91
+ 'testssl' => 'testssl --jsonfile-pretty example.org.json --mapping no-openssl --cipher-per-proto example.org',
92
+ 'ssllabs-scan' => 'ssllabs-scan --quiet example.org > example.org.json'
93
+ }.freeze
94
+
95
+ # Get the external tool command used to generate the expected result format
96
+ # @param tool [String] Possible values: `sslyze`, `sslscan2`, `testssl`, `ssllabs-scan`
97
+ # @return [String] external tool command used to generate the expected result format used in input of the extract
98
+ # command (CLI) / {parse} method (library)
99
+ def helper(tool)
100
+ intro = 'You may not be provinding the right format.'
101
+ outro = 'See https://noraj.github.io/tls-map/yard/TLSmap/App/Extractor'
102
+ "#{intro}\nUse this command: #{CMD[tool]}\n#{outro}"
83
103
  end
84
104
 
85
105
  # Convert cmdline tool name to Class name
@@ -87,7 +107,8 @@ module TLSmap
87
107
  tool.split('-').map(&:capitalize).join
88
108
  end
89
109
 
90
- protected :normalize
110
+ protected :normalize, :helper
111
+ private_constant :CMD
91
112
 
92
113
  # Parsing SSLyze
93
114
  class Sslyze
@@ -97,7 +118,7 @@ module TLSmap
97
118
  # See {TLSmap::App::Extractor}
98
119
  # @return [Array<String>] Cipher array (IANA names)
99
120
  def parse(file)
100
- data = JSON.load_file(file)
121
+ data = Utils.json_load_file(file)
101
122
  extract_cipher(data)
102
123
  end
103
124
 
@@ -164,7 +185,7 @@ module TLSmap
164
185
  # See {TLSmap::App::Extractor}
165
186
  # @return [Array<String>] Cipher array (IANA names)
166
187
  def parse(file)
167
- data = JSON.load_file(file)
188
+ data = Utils.json_load_file(file)
168
189
  extract_cipher(data)
169
190
  end
170
191
 
@@ -214,7 +235,7 @@ module TLSmap
214
235
  # See {TLSmap::App::Extractor}
215
236
  # @return [Array<String>] Cipher array (IANA names)
216
237
  def parse(file)
217
- data = JSON.load_file(file)
238
+ data = Utils.json_load_file(file)
218
239
  extract_cipher(data)
219
240
  end
220
241
 
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ruby internal
4
+ require 'digest'
5
+
6
+ # TLS map module
7
+ module TLSmap
8
+ # Offline version of {App}
9
+ class CLI < App
10
+ INTEGRITY = '42e44f89550365da2bc8d33d87f88b65d85d6474e90f9edb65e0ea6c78f61a53' # sha2-256
11
+
12
+ # Load and parse data from marshalized hash (`data/mapping.marshal`).
13
+ # It must match the integrity check for security purpose.
14
+ # @param force [Boolean] Force parsing even if integrity check failed (DANGEROUS,
15
+ # may result in command execution vulnerability)
16
+ def initialize(force = false) # rubocop:disable Lint/MissingSuper
17
+ @storage_location = 'data/'
18
+ @database_path = absolute_db_path('mapping.marshal')
19
+ database_exists?
20
+ @tls_map = []
21
+ parse(force)
22
+ end
23
+
24
+ # Find the absolute path of the a data file from its relative location
25
+ # @param filename [String] file name
26
+ # @return [String] absolute filename of the data file
27
+ def absolute_db_path(filename)
28
+ pn = Pathname.new(__FILE__)
29
+ install_dir = pn.dirname.parent.parent.parent.to_s + Pathname::SEPARATOR_LIST
30
+ install_dir + @storage_location + filename
31
+ end
32
+
33
+ # Check if the TLS database DB exists
34
+ # @return [Boolean] `true` if the file exists
35
+ def database_exists?
36
+ exists = File.file?(@database_path)
37
+ raise "Database does not exist: #{@database_path}" unless exists
38
+
39
+ exists
40
+ end
41
+
42
+ def parse(force = false)
43
+ if Digest::SHA256.file(@database_path).hexdigest == INTEGRITY || force # rubocop:disable Style/GuardClause
44
+ @tls_map = Marshal.load(File.read(@database_path)) # rubocop:disable Security/MarshalLoad
45
+ else
46
+ raise 'Integrity check failed, maybe be due to unvalidated database after update'
47
+ end
48
+ end
49
+
50
+ def update
51
+ tm = TLSmap::App.new
52
+ tm.export(@database_path, :marshal)
53
+ end
54
+
55
+ protected :database_exists?, :absolute_db_path, :parse
56
+
57
+ # Offline version of {App::Extended}
58
+ class Extended < App::Extended
59
+ INTEGRITY = '6573b7208668485e6bdd4495627f5fdbdd7f80040b277603bc42f20d16a665e7' # sha2-256
60
+
61
+ # Load and parse data from marshalized hash (`data/extended.marshal`).
62
+ # It must match the integrity check for security purpose.
63
+ # @param force [Boolean] Force parsing even if integrity check failed (DANGEROUS,
64
+ # may result in command execution vulnerability)
65
+ def initialize(force = false) # rubocop:disable Lint/MissingSuper
66
+ @storage_location = 'data/'
67
+ @extended_path = absolute_db_path('extended.marshal')
68
+ @enhanced_data = {}
69
+ extended_exists?
70
+ parse(force)
71
+ end
72
+
73
+ # Find the absolute path of the a data file from its relative location
74
+ # @param filename [String] file name
75
+ # @return [String] absolute filename of the data file
76
+ def absolute_db_path(filename)
77
+ pn = Pathname.new(__FILE__)
78
+ install_dir = pn.dirname.parent.parent.parent.to_s + Pathname::SEPARATOR_LIST
79
+ install_dir + @storage_location + filename
80
+ end
81
+
82
+ # Check if the extended DB exists
83
+ # @return [Boolean] `true` if the files exists
84
+ def extended_exists?
85
+ exists = File.file?(@extended_path)
86
+ raise "Database does not exist: #{@extended_path}" unless exists
87
+
88
+ exists
89
+ end
90
+
91
+ def parse(force = false)
92
+ if Digest::SHA256.file(@extended_path).hexdigest == INTEGRITY || force # rubocop:disable Style/GuardClause
93
+ @enhanced_data = Marshal.load(File.read(@extended_path)) # rubocop:disable Security/MarshalLoad
94
+ else
95
+ raise 'Integrity check failed, maybe be due to unvalidated database after update'
96
+ end
97
+ end
98
+
99
+ def update
100
+ tmext = TLSmap::App::Extended.new
101
+ tmext.enhance_all
102
+ File.write(@extended_path, Marshal.dump(tmext.enhanced_data))
103
+ end
104
+
105
+ # Same as {App::Extended} but loading data from offline database, so there
106
+ # is no caching option.
107
+ # @see App::Extended
108
+ def extend(iana_name)
109
+ @enhanced_data[iana_name]
110
+ end
111
+
112
+ protected :extended_exists?, :absolute_db_path, :parse
113
+ undef_method :enhance_all, :fetch_ciphersuite, :parse_tech, :parse_vuln
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ruby internal
4
+ require 'net/http'
5
+ require 'tempfile'
6
+ require 'json'
7
+
8
+ # TLS map module
9
+ module TLSmap
10
+ # Generic utilities
11
+ module Utils
12
+ def self.tmpfile(name, url)
13
+ tmp = Tempfile.new(name)
14
+ tmp.write(Net::HTTP.get(URI(url)))
15
+ tmp.close
16
+ tmp
17
+ end
18
+
19
+ # bring JSON.load_file before ruby 3.0.0
20
+ # https://ruby-doc.org/stdlib-3.0.0/libdoc/json/rdoc/JSON.html#method-i-load_file
21
+ def self.json_load_file(filespec, opts = {})
22
+ if RUBY_VERSION < '3.0.0'
23
+ JSON.parse(File.read(filespec), opts)
24
+ else
25
+ JSON.load_file(filespec, opts)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TLSmap
4
- VERSION = '1.3.0'
4
+ VERSION = '2.1.0'
5
5
  end
data/lib/tls_map.rb CHANGED
@@ -4,26 +4,31 @@
4
4
  require 'pathname'
5
5
  # Project internal
6
6
  require 'tls_map/version'
7
- require 'tls_map/utils'
8
- require 'tls_map/iana'
9
- require 'tls_map/openssl'
10
- require 'tls_map/gnutls'
11
- require 'tls_map/nss'
12
- require 'tls_map/output'
13
- require 'tls_map/ciphersuiteinfo'
14
- require 'tls_map/extractor'
7
+ require 'tls_map/utils/utils'
8
+ require 'tls_map/app/iana'
9
+ require 'tls_map/app/openssl'
10
+ require 'tls_map/app/gnutls'
11
+ require 'tls_map/app/nss'
12
+ require 'tls_map/app/output'
13
+ require 'tls_map/app/extended/ciphersuiteinfo'
14
+ require 'tls_map/app/extractor/extractor'
15
+ require 'tls_map/app/cipher/cipher'
15
16
 
16
17
  # TLS map module
17
18
  module TLSmap
18
19
  # TLS mapping
19
20
  class App
21
+ # Get the mapping of all TLS cipher suites
22
+ # @return [Hash] mapping of all TLS cipher suites
23
+ attr_reader :tls_map
24
+
20
25
  # Will automatically fetch source files and parse them.
21
26
  def initialize
22
- @iana_file = tmpfile('iana', IANA_URL)
23
- @openssl_file = tmpfile('openssl', OPENSSL_URL)
24
- @openssl_file2 = tmpfile('openssl', OPENSSL_URL2)
25
- @gnutls_file = tmpfile('gnutls', GNUTLS_URL)
26
- @nss_file = tmpfile('nss', NSS_URL)
27
+ @iana_file = Utils.tmpfile('iana', IANA_URL)
28
+ @openssl_file = Utils.tmpfile('openssl', OPENSSL_URL)
29
+ @openssl_file2 = Utils.tmpfile('openssl', OPENSSL_URL2)
30
+ @gnutls_file = Utils.tmpfile('gnutls', GNUTLS_URL)
31
+ @nss_file = Utils.tmpfile('nss', NSS_URL)
27
32
 
28
33
  @tls_map = []
29
34
  parse
@@ -37,17 +42,42 @@ module TLSmap
37
42
  end
38
43
 
39
44
  # Search for corresponding cipher algorithms in other libraries
40
- # @param critera [Symbol] The type of `term`.
45
+ # @param criteria [Symbol] The type of `term`.
41
46
  # Accepted values: `:codepoint`, `:iana`, `:openssl`, `:gnutls`, `:nss`.
42
47
  # @param term [String] The cipher algorithm name.
43
48
  # @param output [Symbol] The corresponding type to be included in the return value.
44
49
  # Accepted values: `:all` (default), `:codepoint`, `:iana`, `:openssl`,
45
50
  # `:gnutls`, `:nss`.
46
51
  # @return [Hash] The corresponding type matching `term`.
47
- def search(critera, term, output = :all)
52
+ def search(criteria, term, output = :all)
48
53
  @tls_map.each do |alg|
49
- term = term.upcase if critera == :codepoint
50
- next unless alg[critera] == term
54
+ term = term.upcase if criteria == :codepoint
55
+ next unless alg[criteria] == term
56
+ return alg if output == :all
57
+
58
+ return { output => alg[output] }
59
+ end
60
+ {}
61
+ end
62
+
63
+ # Stateless version of {App#search}.
64
+ # @param tls_map [Hash] mapping of all TLS cipher suites returned by {tls_map}.
65
+ # @param criteria [Symbol] Same as `criteria` from {TLSmap::App#search}
66
+ # @param term [String] Same as `term` from {TLSmap::App#search}
67
+ # @param output [Symbol] Same as `output` from {TLSmap::App#search}
68
+ # @see App#search
69
+ # @example
70
+ # tm = TLSmap::App.new
71
+ # TLSmap::App.search(tm.tls_map, :iana, 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256')
72
+ # # => {:codepoint=>"CCA9", :iana=>"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
73
+ # :openssl=>"ECDHE-ECDSA-CHACHA20-POLY1305", :gnutls=>"ECDHE_ECDSA_CHACHA20_POLY1305",
74
+ # :nss=>"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"}
75
+ # # or to use with the Cipher class
76
+ # ci = TLSmap::App::Cipher.new(:iana, 'TLS_DH_anon_WITH_RC4_128_MD5', tm.tls_map)
77
+ def self.search(tls_map, criteria, term, output = :all)
78
+ tls_map.each do |alg|
79
+ term = term.upcase if criteria == :codepoint
80
+ next unless alg[criteria] == term
51
81
  return alg if output == :all
52
82
 
53
83
  return { output => alg[output] }
@@ -56,7 +86,7 @@ module TLSmap
56
86
  end
57
87
 
58
88
  # Search for corresponding cipher algorithms in other libraries in bulk
59
- # @param critera [Symbol] The type of `term`.
89
+ # @param criteria [Symbol] The type of `term`.
60
90
  # Accepted values: `:codepoint`, `:iana`, `:openssl`, `:gnutls`, `:nss`.
61
91
  # @param file [String] File containing the cipher algorithm names, one per line.
62
92
  # @param output [Symbol] The corresponding type to be included in the return value.
@@ -64,10 +94,10 @@ module TLSmap
64
94
  # `:gnutls`, `:nss`.
65
95
  # @return [Array<Hash>] The corresponding type, same as {search} return value
66
96
  # but one per line stored in an array.
67
- def bulk_search(critera, file, output = :all)
97
+ def bulk_search(criteria, file, output = :all)
68
98
  res = []
69
99
  File.foreach(file) do |line|
70
- res.push(search(critera, line.chomp, output))
100
+ res.push(search(criteria, line.chomp, output))
71
101
  end
72
102
  res
73
103
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tls-map
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandre ZANNI
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-09 00:00:00.000000000 Z
11
+ date: 2022-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: docopt
@@ -52,154 +52,9 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.2'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: 2.1.0
62
- - - "<"
63
- - !ruby/object:Gem::Version
64
- version: '2.3'
65
- type: :development
66
- prerelease: false
67
- version_requirements: !ruby/object:Gem::Requirement
68
- requirements:
69
- - - ">="
70
- - !ruby/object:Gem::Version
71
- version: 2.1.0
72
- - - "<"
73
- - !ruby/object:Gem::Version
74
- version: '2.3'
75
- - !ruby/object:Gem::Dependency
76
- name: commonmarker
77
- requirement: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '0.21'
82
- type: :development
83
- prerelease: false
84
- version_requirements: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '0.21'
89
- - !ruby/object:Gem::Dependency
90
- name: github-markup
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: '4.0'
96
- type: :development
97
- prerelease: false
98
- version_requirements: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - "~>"
101
- - !ruby/object:Gem::Version
102
- version: '4.0'
103
- - !ruby/object:Gem::Dependency
104
- name: minitest
105
- requirement: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - "~>"
108
- - !ruby/object:Gem::Version
109
- version: '5.12'
110
- type: :development
111
- prerelease: false
112
- version_requirements: !ruby/object:Gem::Requirement
113
- requirements:
114
- - - "~>"
115
- - !ruby/object:Gem::Version
116
- version: '5.12'
117
- - !ruby/object:Gem::Dependency
118
- name: minitest-skip
119
- requirement: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - "~>"
122
- - !ruby/object:Gem::Version
123
- version: '0.0'
124
- type: :development
125
- prerelease: false
126
- version_requirements: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - "~>"
129
- - !ruby/object:Gem::Version
130
- version: '0.0'
131
- - !ruby/object:Gem::Dependency
132
- name: rake
133
- requirement: !ruby/object:Gem::Requirement
134
- requirements:
135
- - - "~>"
136
- - !ruby/object:Gem::Version
137
- version: '13.0'
138
- type: :development
139
- prerelease: false
140
- version_requirements: !ruby/object:Gem::Requirement
141
- requirements:
142
- - - "~>"
143
- - !ruby/object:Gem::Version
144
- version: '13.0'
145
- - !ruby/object:Gem::Dependency
146
- name: redcarpet
147
- requirement: !ruby/object:Gem::Requirement
148
- requirements:
149
- - - "~>"
150
- - !ruby/object:Gem::Version
151
- version: '3.5'
152
- type: :development
153
- prerelease: false
154
- version_requirements: !ruby/object:Gem::Requirement
155
- requirements:
156
- - - "~>"
157
- - !ruby/object:Gem::Version
158
- version: '3.5'
159
- - !ruby/object:Gem::Dependency
160
- name: rubocop
161
- requirement: !ruby/object:Gem::Requirement
162
- requirements:
163
- - - "~>"
164
- - !ruby/object:Gem::Version
165
- version: '1.10'
166
- type: :development
167
- prerelease: false
168
- version_requirements: !ruby/object:Gem::Requirement
169
- requirements:
170
- - - "~>"
171
- - !ruby/object:Gem::Version
172
- version: '1.10'
173
- - !ruby/object:Gem::Dependency
174
- name: webrick
175
- requirement: !ruby/object:Gem::Requirement
176
- requirements:
177
- - - "~>"
178
- - !ruby/object:Gem::Version
179
- version: '1.7'
180
- type: :development
181
- prerelease: false
182
- version_requirements: !ruby/object:Gem::Requirement
183
- requirements:
184
- - - "~>"
185
- - !ruby/object:Gem::Version
186
- version: '1.7'
187
- - !ruby/object:Gem::Dependency
188
- name: yard
189
- requirement: !ruby/object:Gem::Requirement
190
- requirements:
191
- - - "~>"
192
- - !ruby/object:Gem::Version
193
- version: '0.9'
194
- type: :development
195
- prerelease: false
196
- version_requirements: !ruby/object:Gem::Requirement
197
- requirements:
198
- - - "~>"
199
- - !ruby/object:Gem::Version
200
- version: '0.9'
201
55
  description: 'CLI & library for mapping TLS cipher algorithm names: IANA, OpenSSL,
202
- GnuTLS, NSS'
56
+ GnuTLS, NSS;get information and vulnerabilities about cipher suites;extract cipher
57
+ suites from external tools: SSLyze, sslscan2, testssl.sh, ssllabs-scan'
203
58
  email: alexandre.zanni@engineer.com
204
59
  executables:
205
60
  - tls-map
@@ -210,31 +65,34 @@ files:
210
65
  - LICENSE
211
66
  - bin/tls-map
212
67
  - bin/tls-map_console
68
+ - data/extended.marshal
213
69
  - data/mapping.json
214
70
  - data/mapping.marshal
215
71
  - data/mapping.md
216
72
  - data/mapping.min.json
217
73
  - lib/tls_map.rb
218
- - lib/tls_map/ciphersuiteinfo.rb
219
- - lib/tls_map/cli.rb
220
- - lib/tls_map/extractor.rb
221
- - lib/tls_map/gnutls.rb
222
- - lib/tls_map/iana.rb
223
- - lib/tls_map/nss.rb
224
- - lib/tls_map/openssl.rb
225
- - lib/tls_map/output.rb
226
- - lib/tls_map/utils.rb
74
+ - lib/tls_map/app/cipher/cipher.rb
75
+ - lib/tls_map/app/extended/ciphersuiteinfo.rb
76
+ - lib/tls_map/app/extractor/extractor.rb
77
+ - lib/tls_map/app/gnutls.rb
78
+ - lib/tls_map/app/iana.rb
79
+ - lib/tls_map/app/nss.rb
80
+ - lib/tls_map/app/openssl.rb
81
+ - lib/tls_map/app/output.rb
82
+ - lib/tls_map/cli/cli.rb
83
+ - lib/tls_map/utils/utils.rb
227
84
  - lib/tls_map/version.rb
228
- homepage: https://sec-it.github.io/tls-map/
85
+ homepage: https://noraj.github.io/tls-map/
229
86
  licenses:
230
87
  - MIT
231
88
  metadata:
232
89
  yard.run: yard
233
- bug_tracker_uri: https://github.com/sec-it/tls-map/issues
234
- changelog_uri: https://github.com/sec-it/tls-map/blob/master/docs/CHANGELOG.md
235
- documentation_uri: https://sec-it.github.io/tls-map/yard/
236
- homepage_uri: https://sec-it.github.io/tls-map/
237
- source_code_uri: https://github.com/sec-it/tls-map/
90
+ bug_tracker_uri: https://github.com/noraj/tls-map/issues
91
+ changelog_uri: https://github.com/noraj/tls-map/blob/master/docs/CHANGELOG.md
92
+ documentation_uri: https://noraj.github.io/tls-map/yard/
93
+ homepage_uri: https://noraj.github.io/tls-map/
94
+ source_code_uri: https://github.com/noraj/tls-map/
95
+ rubygems_mfa_required: 'true'
238
96
  post_install_message:
239
97
  rdoc_options: []
240
98
  require_paths:
@@ -246,16 +104,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
246
104
  version: 2.6.0
247
105
  - - "<"
248
106
  - !ruby/object:Gem::Version
249
- version: '3.1'
107
+ version: '3.2'
250
108
  required_rubygems_version: !ruby/object:Gem::Requirement
251
109
  requirements:
252
110
  - - ">="
253
111
  - !ruby/object:Gem::Version
254
112
  version: '0'
255
113
  requirements: []
256
- rubygems_version: 3.2.15
114
+ rubygems_version: 3.3.3
257
115
  signing_key:
258
116
  specification_version: 4
259
- summary: 'CLI & library for mapping TLS cipher algorithm names: IANA, OpenSSL, GnuTLS,
260
- NSS'
117
+ summary: CLI & library for TLS cipher suites manipulation
261
118
  test_files: []
data/lib/tls_map/cli.rb DELETED
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Ruby internal
4
- require 'digest'
5
-
6
- # TLS map module
7
- module TLSmap
8
- # TLS mapping
9
- class CLI < App
10
- INTEGRITY = '42e44f89550365da2bc8d33d87f88b65d85d6474e90f9edb65e0ea6c78f61a53' # sha2-256
11
-
12
- # Load and parse data from marshalized hash (`data/mapping.marshal`).
13
- # It must match the integrity check for security purpose.
14
- # @param force [Boolean] Force parsing even if intigrity check failed (DANGEROUS,
15
- # may result in command execution vulnerability)
16
- def initialize(force = false) # rubocop:disable Lint/MissingSuper
17
- @storage_location = 'data/'
18
- @database_name = 'mapping.marshal'
19
- @database_path = absolute_db_path
20
- database_exists?
21
- @tls_map = []
22
- parse(force)
23
- end
24
-
25
- # Find the absolute path of the DB from its relative location
26
- # @return [String] absolute filename of the DB
27
- def absolute_db_path
28
- pn = Pathname.new(__FILE__)
29
- install_dir = pn.dirname.parent.parent.to_s + Pathname::SEPARATOR_LIST
30
- install_dir + @storage_location + @database_name
31
- end
32
-
33
- # Check if the password database exists
34
- # @return [Boolean] `true` if the file exists
35
- def database_exists?
36
- exists = File.file?(@database_path)
37
- raise "Database does not exist: #{@database_path}" unless exists
38
-
39
- exists
40
- end
41
-
42
- def parse(force = false)
43
- if Digest::SHA256.file(@database_path).hexdigest == INTEGRITY || force # rubocop:disable Style/GuardClause
44
- @tls_map = Marshal.load(File.read(@database_path)) # rubocop:disable Security/MarshalLoad
45
- else
46
- raise 'Integry check failed, maybe be due to unavalidated database after update'
47
- end
48
- end
49
-
50
- def update
51
- tm = TLSmap::App.new
52
- tm.export(@database_path, :marshal)
53
- end
54
-
55
- protected :database_exists?, :absolute_db_path, :parse
56
- end
57
- end
data/lib/tls_map/utils.rb DELETED
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Ruby internal
4
- require 'net/http'
5
- require 'tempfile'
6
-
7
- # TLS map module
8
- module TLSmap
9
- # Generic utilities
10
- module Utils
11
- def tmpfile(name, url)
12
- tmp = Tempfile.new(name)
13
- tmp.write(Net::HTTP.get(URI(url)))
14
- tmp.close
15
- tmp
16
- end
17
- end
18
-
19
- # TLS mapping
20
- class App
21
- include Utils
22
- protected :tmpfile
23
- end
24
- end