1 /*
2 * (C) 2016 Jack Lloyd
3 *
4 * Botan is released under the Simplified BSD License (see license.txt)
5 */
6 
7 #include "cli.h"
8 
9 #if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
10 
11 #include <botan/tls_policy.h>
12 #include <botan/tls_version.h>
13 #include <botan/tls_messages.h>
14 #include <botan/loadstor.h>
15 #include <botan/hex.h>
16 #include <sstream>
17 
18 #include "tls_helpers.h"
19 
20 namespace Botan_CLI {
21 
22 class TLS_Ciphersuites final : public Command
23    {
24    public:
TLS_Ciphersuites()25       TLS_Ciphersuites()
26          : Command("tls_ciphers --policy=default --version=tls1.2") {}
27 
tls_version_from_str(const std::string & str)28       static Botan::TLS::Protocol_Version::Version_Code tls_version_from_str(const std::string& str)
29          {
30          if(str == "tls1.2" || str == "TLS1.2" || str == "TLS-1.2")
31             {
32             return Botan::TLS::Protocol_Version::TLS_V12;
33             }
34          else if(str == "tls1.1" || str == "TLS1.1" || str == "TLS-1.1")
35             {
36             return Botan::TLS::Protocol_Version::TLS_V11;
37             }
38          else if(str == "tls1.0" || str == "TLS1.1" || str == "TLS-1.1")
39             {
40             return Botan::TLS::Protocol_Version::TLS_V10;
41             }
42          if(str == "dtls1.2" || str == "DTLS1.2" || str == "DTLS-1.2")
43             {
44             return Botan::TLS::Protocol_Version::DTLS_V12;
45             }
46          else if(str == "dtls1.0" || str == "DTLS1.0" || str == "DTLS-1.0")
47             {
48             return Botan::TLS::Protocol_Version::DTLS_V10;
49             }
50          else
51             {
52             throw CLI_Error("Unknown TLS version '" + str + "'");
53             }
54          }
55 
group() const56       std::string group() const override
57          {
58          return "tls";
59          }
60 
description() const61       std::string description() const override
62          {
63          return "Lists all ciphersuites for a policy and TLS version";
64          }
65 
go()66       void go() override
67          {
68          const std::string policy_type = get_arg("policy");
69          const Botan::TLS::Protocol_Version version(tls_version_from_str(get_arg("version")));
70          const bool with_srp = false; // fixme
71 
72          auto policy = load_tls_policy(policy_type);
73 
74          if(policy->acceptable_protocol_version(version) == false)
75             {
76             error_output() << "Error: the policy specified does not allow the given TLS version\n";
77             return;
78             }
79 
80          for(uint16_t suite_id : policy->ciphersuite_list(version, with_srp))
81             {
82             const Botan::TLS::Ciphersuite suite(Botan::TLS::Ciphersuite::by_id(suite_id));
83             output() << suite.to_string() << "\n";
84             }
85          }
86    };
87 
88 BOTAN_REGISTER_COMMAND("tls_ciphers", TLS_Ciphersuites);
89 
90 class TLS_Client_Hello_Reader final : public Command
91    {
92    public:
TLS_Client_Hello_Reader()93       TLS_Client_Hello_Reader()
94          : Command("tls_client_hello --hex input") {}
95 
group() const96       std::string group() const override
97          {
98          return "tls";
99          }
100 
description() const101       std::string description() const override
102          {
103          return "Parse a TLS client hello message";
104          }
105 
go()106       void go() override
107          {
108          const std::string input_file = get_arg("input");
109          std::vector<uint8_t> input;
110 
111          if(flag_set("hex"))
112             {
113             input = Botan::hex_decode(slurp_file_as_str(input_file));
114             }
115          else
116             {
117             input = slurp_file(input_file);
118             }
119 
120          if(input.size() < 45)
121             {
122             error_output() << "Input too short to be valid\n";
123             return;
124             }
125 
126          // Input also contains the record layer header, strip it
127          if(input[0] == 22)
128             {
129             const size_t len = Botan::make_uint16(input[3], input[4]);
130 
131             if(input.size() != len + 5)
132                {
133                error_output() << "Record layer length invalid\n";
134                return;
135                }
136 
137             input = std::vector<uint8_t>(input.begin() + 5, input.end());
138             }
139 
140          // Assume the handshake header is there, strip it
141          if(input[0] == 1)
142             {
143             const size_t hs_len = Botan::make_uint32(0, input[1], input[2], input[3]);
144 
145             if(input.size() != hs_len + 4)
146                {
147                error_output() << "Handshake layer length invalid\n";
148                return;
149                }
150 
151             input = std::vector<uint8_t>(input.begin() + 4, input.end());
152             }
153 
154          try
155             {
156             Botan::TLS::Client_Hello hello(input);
157 
158             output() << format_hello(hello);
159             }
160          catch(std::exception& e)
161             {
162             error_output() << "Parsing client hello failed: " << e.what() << "\n";
163             }
164          }
165 
166    private:
format_hello(const Botan::TLS::Client_Hello & hello)167       std::string format_hello(const Botan::TLS::Client_Hello& hello)
168          {
169          std::ostringstream oss;
170          oss << "Version: " << hello.version().to_string() << "\n"
171              << "Random: " << Botan::hex_encode(hello.random()) << "\n";
172 
173          if(!hello.session_id().empty())
174             oss << "SessionID: " << Botan::hex_encode(hello.session_id()) << "\n";
175          for(uint16_t csuite_id : hello.ciphersuites())
176             {
177             auto csuite = Botan::TLS::Ciphersuite::by_id(csuite_id);
178             if(csuite.valid())
179                oss << "Cipher: " << csuite.to_string()  << "\n";
180             else if(csuite_id == 0x00FF)
181                oss << "Cipher: EMPTY_RENEGOTIATION_INFO_SCSV\n";
182             else
183                oss << "Cipher: Unknown (" << std::hex << csuite_id << ")\n";
184             }
185 
186          oss << "Supported signature schemes: ";
187 
188          if(hello.signature_schemes().empty())
189             {
190             oss << "Did not send signature_algorithms extension\n";
191             }
192          else
193             {
194             for(Botan::TLS::Signature_Scheme scheme : hello.signature_schemes())
195                {
196                try
197                   {
198                   auto s = sig_scheme_to_string(scheme);
199                   oss << s << " ";
200                   }
201                catch(...)
202                   {
203                   oss << "(" << std::hex << static_cast<uint16_t>(scheme) << ") ";
204                   }
205                }
206             oss << "\n";
207             }
208 
209          std::map<std::string, bool> hello_flags;
210          hello_flags["ALPN"] = hello.supports_alpn();
211          hello_flags["Encrypt Then Mac"] = hello.supports_encrypt_then_mac();
212          hello_flags["Extended Master Secret"] = hello.supports_extended_master_secret();
213          hello_flags["Session Ticket"] = hello.supports_session_ticket();
214 
215          for(auto&& i : hello_flags)
216             oss << "Supports " << i.first << "? " << (i.second ? "yes" : "no") << "\n";
217 
218          return oss.str();
219          }
220    };
221 
222 BOTAN_REGISTER_COMMAND("tls_client_hello", TLS_Client_Hello_Reader);
223 
224 }
225 
226 #endif
227