tls-map 1.3.0 → 2.1.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.
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