#!/usr/local/bin/lua -- -- Copyright (c) 2007, Cameron Rich -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, -- this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- * Neither the name of the axTLS project nor the names of its -- contributors may be used to endorse or promote products derived -- from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -- TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -- OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -- THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- -- -- Demonstrate the use of the axTLS library in Lua with a set of -- command-line parameters similar to openssl. In fact, openssl clients -- should be able to communicate with axTLS servers and visa-versa. -- -- This code has various bits enabled depending on the configuration. To enable -- the most interesting version, compile with the 'full mode' enabled. -- -- To see what options you have, run the following: -- > [lua] axssl s_server -? -- > [lua] axssl s_client -? -- -- The axtls/axtlsl shared libraries must be in the same directory or be found -- by the OS. -- -- require "bit" require("axtlsl") local socket = require("socket") -- print version? if #arg == 1 and arg[1] == "version" then print("axssl.lua "..axtlsl.ssl_version()) os.exit(1) end -- -- We've had some sort of command-line error. Print out the basic options. -- function print_options(option) print("axssl: Error: '"..option.."' is an invalid command.") print("usage: axssl [s_server|s_client|version] [args ...]") os.exit(1) end -- -- We've had some sort of command-line error. Print out the server options. -- function print_server_options(build_mode, option) local cert_size = axtlsl.ssl_get_config(axtlsl.SSL_MAX_CERT_CFG_OFFSET) local ca_cert_size = axtlsl.ssl_get_config( axtlsl.SSL_MAX_CA_CERT_CFG_OFFSET) print("unknown option "..option) print("usage: s_server [args ...]") print(" -accept\t- port to accept on (default is 4433)") print(" -quiet\t\t- No server output") if build_mode >= axtlsl.SSL_BUILD_SERVER_ONLY then print(" -cert arg\t- certificate file to add (in addition to ".. "default) to chain -") print("\t\t Can repeat up to "..cert_size.." times") print(" -key arg\t- Private key file to use - default DER format") print(" -pass\t\t- private key file pass phrase source") end if build_mode >= axtlsl.SSL_BUILD_ENABLE_VERIFICATION then print(" -verify\t- turn on peer certificate verification") print(" -CAfile arg\t- Certificate authority - default DER format") print("\t\t Can repeat up to "..ca_cert_size.." times") end if build_mode == axtlsl.SSL_BUILD_FULL_MODE then print(" -debug\t\t- Print more output") print(" -state\t\t- Show state messages") print(" -show-rsa\t- Show RSA state") end os.exit(1) end -- -- We've had some sort of command-line error. Print out the client options. -- function print_client_options(build_mode, option) local cert_size = axtlsl.ssl_get_config(axtlsl.SSL_MAX_CERT_CFG_OFFSET) local ca_cert_size = axtlsl.ssl_get_config( axtlsl.SSL_MAX_CA_CERT_CFG_OFFSET) print("unknown option "..option) if build_mode >= axtlsl.SSL_BUILD_ENABLE_CLIENT then print("usage: s_client [args ...]") print(" -connect host:port - who to connect to (default ".. "is localhost:4433)") print(" -verify\t- turn on peer certificate verification") print(" -cert arg\t- certificate file to use - default DER format") print(" -key arg\t- Private key file to use - default DER format") print("\t\t Can repeat up to "..cert_size.." times") print(" -CAfile arg\t- Certificate authority - default DER format") print("\t\t Can repeat up to "..ca_cert_size.."times") print(" -quiet\t\t- No client output") print(" -pass\t\t- private key file pass phrase source") print(" -reconnect\t- Drop and re-make the connection ".. "with the same Session-ID") if build_mode == axtlsl.SSL_BUILD_FULL_MODE then print(" -debug\t\t- Print more output") print(" -state\t\t- Show state messages") print(" -show-rsa\t- Show RSA state") end else print("Change configuration to allow this feature") end os.exit(1) end -- Implement the SSL server logic. function do_server(build_mode) local i = 2 local v local port = 4433 local options = axtlsl.SSL_DISPLAY_CERTS local quiet = false local password = "" local private_key_file = nil local cert_size = axtlsl.ssl_get_config(axtlsl.SSL_MAX_CERT_CFG_OFFSET) local ca_cert_size = axtlsl. ssl_get_config(axtlsl.SSL_MAX_CA_CERT_CFG_OFFSET) local cert = {} local ca_cert = {} while i <= #arg do if arg[i] == "-accept" then if i >= #arg then print_server_options(build_mode, arg[i]) end i = i + 1 port = arg[i] elseif arg[i] == "-quiet" then quiet = true options = bit.band(options, bit.bnot(axtlsl.SSL_DISPLAY_CERTS)) elseif build_mode >= axtlsl.SSL_BUILD_SERVER_ONLY then if arg[i] == "-cert" then if i >= #arg or #cert >= cert_size then print_server_options(build_mode, arg[i]) end i = i + 1 table.insert(cert, arg[i]) elseif arg[i] == "-key" then if i >= #arg then print_server_options(build_mode, arg[i]) end i = i + 1 private_key_file = arg[i] options = bit.bor(options, axtlsl.SSL_NO_DEFAULT_KEY) elseif arg[i] == "-pass" then if i >= #arg then print_server_options(build_mode, arg[i]) end i = i + 1 password = arg[i] elseif build_mode >= axtlsl.SSL_BUILD_ENABLE_VERIFICATION then if arg[i] == "-verify" then options = bit.bor(options, axtlsl.SSL_CLIENT_AUTHENTICATION) elseif arg[i] == "-CAfile" then if i >= #arg or #ca_cert >= ca_cert_size then print_server_options(build_mode, arg[i]) end i = i + 1 table.insert(ca_cert, arg[i]) elseif build_mode == axtlsl.SSL_BUILD_FULL_MODE then if arg[i] == "-debug" then options = bit.bor(options, axtlsl.SSL_DISPLAY_BYTES) elseif arg[i] == "-state" then options = bit.bor(options, axtlsl.SSL_DISPLAY_STATES) elseif arg[i] == "-show-rsa" then options = bit.bor(options, axtlsl.SSL_DISPLAY_RSA) else print_server_options(build_mode, arg[i]) end else print_server_options(build_mode, arg[i]) end else print_server_options(build_mode, arg[i]) end else print_server_options(build_mode, arg[i]) end i = i + 1 end -- Create socket for incoming connections local server_sock = socket.try(socket.bind("*", port)) --------------------------------------------------------------------------- -- This is where the interesting stuff happens. Up until now we've -- just been setting up sockets etc. Now we do the SSL handshake. --------------------------------------------------------------------------- local ssl_ctx = axtlsl.ssl_ctx_new(options, axtlsl.SSL_DEFAULT_SVR_SESS) if ssl_ctx == nil then error("Error: Server context is invalid") end if private_key_file ~= nil then local obj_type = axtlsl.SSL_OBJ_RSA_KEY if string.find(private_key_file, ".p8") then obj_type = axtlsl.SSL_OBJ_PKCS8 end if string.find(private_key_file, ".p12") then obj_type = axtlsl.SSL_OBJ_PKCS12 end if axtlsl.ssl_obj_load(ssl_ctx, obj_type, private_key_file, password) ~= axtlsl.SSL_OK then error("Private key '" .. private_key_file .. "' is undefined.") end end for _, v in ipairs(cert) do if axtlsl.ssl_obj_load(ssl_ctx, axtlsl.SSL_OBJ_X509_CERT, v, "") ~= axtlsl.SSL_OK then error("Certificate '"..v .. "' is undefined.") end end for _, v in ipairs(ca_cert) do if axtlsl.ssl_obj_load(ssl_ctx, axtlsl.SSL_OBJ_X509_CACERT, v, "") ~= axtlsl.SSL_OK then error("Certificate '"..v .."' is undefined.") end end while true do if not quiet then print("ACCEPT") end local client_sock = server_sock:accept(); local ssl = axtlsl.ssl_server_new(ssl_ctx, client_sock:getfd()) -- do the actual SSL handshake local connected = false local res local buf while true do socket.select({client_sock}, nil) res, buf = axtlsl.ssl_read(ssl) if res == axtlsl.SSL_OK then -- connection established and ok if axtlsl.ssl_handshake_status(ssl) == axtlsl.SSL_OK then if not quiet and not connected then display_session_id(ssl) display_cipher(ssl) end connected = true end end if res > axtlsl.SSL_OK then for _, v in ipairs(buf) do io.write(string.format("%c", v)) end elseif res < axtlsl.SSL_OK then if not quiet then axtlsl.ssl_display_error(res) end break end end -- client was disconnected or the handshake failed. print("CONNECTION CLOSED") axtlsl.ssl_free(ssl) client_sock:close() end axtlsl.ssl_ctx_free(ssl_ctx) end -- -- Implement the SSL client logic. -- function do_client(build_mode) local i = 2 local v local port = 4433 local options = bit.bor(axtlsl.SSL_SERVER_VERIFY_LATER, axtlsl.SSL_DISPLAY_CERTS) local private_key_file = nil local reconnect = 0 local quiet = false local password = "" local session_id = {} local host = "127.0.0.1" local cert_size = axtlsl.ssl_get_config(axtlsl.SSL_MAX_CERT_CFG_OFFSET) local ca_cert_size = axtlsl. ssl_get_config(axtlsl.SSL_MAX_CA_CERT_CFG_OFFSET) local cert = {} local ca_cert = {} while i <= #arg do if arg[i] == "-connect" then if i >= #arg then print_client_options(build_mode, arg[i]) end i = i + 1 local t = string.find(arg[i], ":") host = string.sub(arg[i], 1, t-1) port = string.sub(arg[i], t+1) elseif arg[i] == "-cert" then if i >= #arg or #cert >= cert_size then print_client_options(build_mode, arg[i]) end i = i + 1 table.insert(cert, arg[i]) elseif arg[i] == "-key" then if i >= #arg then print_client_options(build_mode, arg[i]) end i = i + 1 private_key_file = arg[i] options = bit.bor(options, axtlsl.SSL_NO_DEFAULT_KEY) elseif arg[i] == "-CAfile" then if i >= #arg or #ca_cert >= ca_cert_size then print_client_options(build_mode, arg[i]) end i = i + 1 table.insert(ca_cert, arg[i]) elseif arg[i] == "-verify" then options = bit.band(options, bit.bnot(axtlsl.SSL_SERVER_VERIFY_LATER)) elseif arg[i] == "-reconnect" then reconnect = 4 elseif arg[i] == "-quiet" then quiet = true options = bit.band(options, bnot(axtlsl.SSL_DISPLAY_CERTS)) elseif arg[i] == "-pass" then if i >= #arg then print_server_options(build_mode, arg[i]) end i = i + 1 password = arg[i] elseif build_mode == axtlsl.SSL_BUILD_FULL_MODE then if arg[i] == "-debug" then options = bit.bor(options, axtlsl.SSL_DISPLAY_BYTES) elseif arg[i] == "-state" then options = bit.bor(axtlsl.SSL_DISPLAY_STATES) elseif arg[i] == "-show-rsa" then options = bit.bor(axtlsl.SSL_DISPLAY_RSA) else -- don't know what this is print_client_options(build_mode, arg[i]) end else -- don't know what this is print_client_options(build_mode, arg[i]) end i = i + 1 end local client_sock = socket.try(socket.connect(host, port)) local ssl local res if not quiet then print("CONNECTED") end --------------------------------------------------------------------------- -- This is where the interesting stuff happens. Up until now we've -- just been setting up sockets etc. Now we do the SSL handshake. --------------------------------------------------------------------------- local ssl_ctx = axtlsl.ssl_ctx_new(options, axtlsl.SSL_DEFAULT_CLNT_SESS) if ssl_ctx == nil then error("Error: Client context is invalid") end if private_key_file ~= nil then local obj_type = axtlsl.SSL_OBJ_RSA_KEY if string.find(private_key_file, ".p8") then obj_type = axtlsl.SSL_OBJ_PKCS8 end if string.find(private_key_file, ".p12") then obj_type = axtlsl.SSL_OBJ_PKCS12 end if axtlsl.ssl_obj_load(ssl_ctx, obj_type, private_key_file, password) ~= axtlsl.SSL_OK then error("Private key '"..private_key_file.."' is undefined.") end end for _, v in ipairs(cert) do if axtlsl.ssl_obj_load(ssl_ctx, axtlsl.SSL_OBJ_X509_CERT, v, "") ~= axtlsl.SSL_OK then error("Certificate '"..v .. "' is undefined.") end end for _, v in ipairs(ca_cert) do if axtlsl.ssl_obj_load(ssl_ctx, axtlsl.SSL_OBJ_X509_CACERT, v, "") ~= axtlsl.SSL_OK then error("Certificate '"..v .."' is undefined.") end end -- Try session resumption? if reconnect ~= 0 then local session_id = nil local sess_id_size = 0 while reconnect > 0 do reconnect = reconnect - 1 ssl = axtlsl.ssl_client_new(ssl_ctx, client_sock:getfd(), session_id, sess_id_size) res = axtlsl.ssl_handshake_status(ssl) if res ~= axtlsl.SSL_OK then if not quiet then axtlsl.ssl_display_error(res) end axtlsl.ssl_free(ssl) os.exit(1) end display_session_id(ssl) session_id = axtlsl.ssl_get_session_id(ssl) sess_id_size = axtlsl.ssl_get_session_id_size(ssl) if reconnect > 0 then axtlsl.ssl_free(ssl) client_sock:close() client_sock = socket.try(socket.connect(host, port)) end end else ssl = axtlsl.ssl_client_new(ssl_ctx, client_sock:getfd(), nil, 0) end -- check the return status res = axtlsl.ssl_handshake_status(ssl) if res ~= axtlsl.SSL_OK then if not quiet then axtlsl.ssl_display_error(res) end os.exit(1) end if not quiet then local common_name = axtlsl.ssl_get_cert_dn(ssl, axtlsl.SSL_X509_CERT_COMMON_NAME) if common_name ~= nil then print("Common Name:\t\t\t"..common_name) end display_session_id(ssl) display_cipher(ssl) end while true do local line = io.read() if line == nil then break end local bytes = {} for i = 1, #line do bytes[i] = line.byte(line, i) end bytes[#line+1] = 10 -- add carriage return, null bytes[#line+2] = 0 res = axtlsl.ssl_write(ssl, bytes, #bytes) if res < axtlsl.SSL_OK then if not quiet then axtlsl.ssl_display_error(res) end break end end axtlsl.ssl_ctx_free(ssl_ctx) client_sock:close() end -- -- Display what cipher we are using -- function display_cipher(ssl) io.write("CIPHER is ") local cipher_id = axtlsl.ssl_get_cipher_id(ssl) if cipher_id == axtlsl.SSL_AES128_SHA then print("AES128-SHA") elseif cipher_id == axtlsl.SSL_AES256_SHA then print("AES256-SHA") elseif axtlsl.SSL_RC4_128_SHA then print("RC4-SHA") elseif axtlsl.SSL_RC4_128_MD5 then print("RC4-MD5") else print("Unknown - "..cipher_id) end end -- -- Display what session id we have. -- function display_session_id(ssl) local session_id = axtlsl.ssl_get_session_id(ssl) local v if #session_id > 0 then print("-----BEGIN SSL SESSION PARAMETERS-----") for _, v in ipairs(session_id) do io.write(string.format("%02x", v)) end print("\n-----END SSL SESSION PARAMETERS-----") end end -- -- Main entry point. Doesn't do much except works out whether we are a client -- or a server. -- if #arg == 0 or (arg[1] ~= "s_server" and arg[1] ~= "s_client") then print_options(#arg > 0 and arg[1] or "") end local build_mode = axtlsl.ssl_get_config(axtlsl.SSL_BUILD_MODE) _ = arg[1] == "s_server" and do_server(build_mode) or do_client(build_mode) os.exit(0)