Skip to content

Commit af6df8b

Browse files
aarltoberstet
authored andcommitted
Add basic cryptosign support.
1 parent 0fb8045 commit af6df8b

File tree

9 files changed

+485
-7
lines changed

9 files changed

+485
-7
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 2.8)
33
project(autobahn-cpp)
44

55
option(AUTOBAHN_BUILD_EXAMPLES "Build examples" ON)
6+
option(AUTOBAHN_BUILD_EXAMPLES_BOTAN "Build Botan cryptosign example" OFF)
67
option(AUTOBAHN_USE_LIBCXX "Use libc++ instead of libstdc++ when building with Clang" ON)
78

89
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Includes/CMakeLists.txt)

autobahn/wamp_challenge.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class wamp_challenge
5151
int iterations() const;
5252
int keylen() const;
5353

54+
void set_channel_id(std::string channel_id);
55+
const std::string & channel_id() const;
56+
5457
private:
5558
// authmethod
5659
std::string m_authmethod;
@@ -65,6 +68,9 @@ class wamp_challenge
6568
std::string m_salt;
6669
int m_iterations;
6770
int m_keylen;
71+
72+
// if authmethod is "cryptosign"
73+
std::string m_channel_id;
6874
};
6975

7076
} // namespace autobahn

autobahn/wamp_challenge.ipp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ inline const std::string & wamp_challenge::challenge() const { return m_challeng
4848
inline const std::string & wamp_challenge::salt() const { return m_salt; }
4949
inline int wamp_challenge::iterations() const { return m_iterations; }
5050
inline int wamp_challenge::keylen() const { return m_keylen; }
51+
inline void wamp_challenge::set_channel_id(std::string channel_id) { m_channel_id = std::move(channel_id); }
52+
inline const std::string & wamp_challenge::channel_id() const { return m_channel_id; }
5153

5254

5355
} // namespace autobahn

autobahn/wamp_session.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ class wamp_session :
130130
boost::future<uint64_t> join(
131131
const std::string& realm,
132132
const std::vector<std::string>& authmethods = std::vector<std::string>(),
133-
const std::string& authid = "");
133+
const std::string& authid = "",
134+
const std::map<std::string, std::string>& authentication_extra = {});
134135

135136
/*!
136137
* Leave the realm.

autobahn/wamp_session.ipp

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ inline boost::future<void> wamp_session::stop()
140140
inline boost::future<uint64_t> wamp_session::join(
141141
const std::string& realm,
142142
const std::vector<std::string>& authentication_methods,
143-
const std::string& authentication_id)
143+
const std::string& authentication_id,
144+
const std::map<std::string, std::string>& authentication_extra)
144145
{
145146
msgpack::zone zone;
146147
std::unordered_map<std::string, msgpack::object> roles;
@@ -168,6 +169,14 @@ inline boost::future<uint64_t> wamp_session::join(
168169
details["authmethods"] = msgpack::object(authentication_methods, zone);
169170
details["authid"] = msgpack::object(authentication_id, zone);
170171

172+
if (!authentication_extra.empty()) {
173+
std::unordered_map<std::string, msgpack::object> authextra;
174+
for (auto const &a : authentication_extra) {
175+
authextra[a.first] = msgpack::object(a.second, zone);
176+
}
177+
details["authextra"] = msgpack::object(authextra, zone);
178+
}
179+
171180
auto message = std::make_shared<wamp_message>(3, std::move(zone));
172181
message->set_field(0, static_cast<int>(message_type::HELLO));
173182
message->set_field(1, realm);
@@ -744,16 +753,52 @@ inline void wamp_session::process_challenge(wamp_message&& message)
744753
std::cerr << "failed to parse challenge details" << std::endl;
745754
}
746755
throw protocol_error("wampcra authentication: Failed parse challange details");
747-
};
756+
}
748757
/////////////////////////////////////////
749758
// ticket authentication
750759
/////////////////////////////////////////
751760
} else if ( whatAuth == "ticket" ) {
752761
// make the challenge object
753762
challenge_object = wamp_challenge("ticket");
754-
}
755-
else {
756-
throw protocol_error("not supported challenge type - can now only handle 'wampcra' and 'ticket'");
763+
/////////////////////////////////////////
764+
// cryptosign authentication
765+
/////////////////////////////////////////
766+
} else if (whatAuth == "cryptosign") {
767+
if (!message.is_field_type(2, msgpack::type::MAP)) {
768+
throw protocol_error("CHALLENGE - Details must be a dictionary");
769+
}
770+
771+
std::string challenge, channel_id;
772+
// parse the details, and fill variables above
773+
try {
774+
std::unordered_map<std::string, msgpack::object> details;
775+
message.field(2).convert(details);
776+
auto itr = details.find("challenge");
777+
if (itr != details.end()) {
778+
challenge = itr->second.as<std::string>();
779+
} else {
780+
throw protocol_error(
781+
"cryptosign must always introduce a challenge ( in details )");
782+
}
783+
itr = details.find("channel_id");
784+
if (itr != details.end()) {
785+
channel_id = itr->second.as<std::string>();
786+
}
787+
788+
// make the challenge object
789+
challenge_object = wamp_challenge("cryptosign", challenge);
790+
if (!channel_id.empty()) {
791+
challenge_object.set_channel_id(channel_id);
792+
}
793+
} catch (const std::exception &) {
794+
if (m_debug_enabled) {
795+
std::cerr << "failed to parse challenge details" << std::endl;
796+
}
797+
throw protocol_error("cryptosign authentication: Failed parse challenge details");
798+
}
799+
} else {
800+
throw protocol_error("not supported challenge type - can now only handle "
801+
"'wampcra', 'ticket' and 'cryptosign'");
757802
}
758803

759804
// I am not sure if this is neccesary. Looking at other

cmake/Modules/FindBotan2.cmake

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
if(MSVC OR MINGW)
2+
message(FATAL_ERROR "Using system Botan 2 library in Window not supported")
3+
endif()
4+
5+
if(TARGET Botan2::Botan2)
6+
message(STATUS "Target Botan::Botan already exists. Skipping searching")
7+
return()
8+
endif()
9+
10+
find_package(PkgConfig REQUIRED QUIET)
11+
find_package(PackageHandleStandardArgs REQUIRED QUIET)
12+
13+
14+
pkg_check_modules(Botan2
15+
botan-2
16+
)
17+
18+
find_library(Botan2_FullLibraryPath
19+
${Botan2_LIBRARIES}
20+
PATHS ${Botan2_LIBRARY_DIRS}
21+
NO_DEFAULT_PATH
22+
)
23+
24+
find_package_handle_standard_args(Botan2
25+
REQUIRED_VARS Botan2_LIBRARIES Botan2_INCLUDE_DIRS
26+
VERSION_VAR Botan2_VERSION
27+
)
28+
message("Botan2_INCLUDE_DIRS ${Botan2_INCLUDE_DIRS}")
29+
30+
if(Botan2_FOUND)
31+
if(NOT TARGET Botan2::Botan2)
32+
add_library(Botan2::Botan2
33+
UNKNOWN IMPORTED GLOBAL
34+
)
35+
set_target_properties(Botan2::Botan2 PROPERTIES
36+
INTERFACE_INCLUDE_DIRECTORIES ${Botan2_INCLUDE_DIRS}
37+
IMPORTED_LOCATION ${Botan2_FullLibraryPath}
38+
LINK_FLAGS ${Botan2_LDFLAGS_OTHER}
39+
)
40+
endif()
41+
endif()

examples/CMakeLists.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
set(CMAKE_EXE_LINKER_FLAGS " -static")
1+
if (!APPLE)
2+
set(CMAKE_EXE_LINKER_FLAGS " -static")
3+
endif()
24

35
add_library(examples_parameters parameters.cpp parameters.hpp)
46
target_include_directories(examples_parameters PUBLIC ${Boost_INCLUDE_DIRS})
@@ -17,6 +19,13 @@ make_example(publisher publisher.cpp)
1719
make_example(subscriber subscriber.cpp)
1820
make_example(wampcra wampcra.cpp)
1921
make_example(websocket_callee websocket_callee.cpp)
22+
make_example(cryptosign-openssl cryptosign-openssl.cpp)
23+
if (AUTOBAHN_BUILD_EXAMPLES_BOTAN)
24+
find_package(Botan2 REQUIRED)
25+
make_example(cryptosign-botan cryptosign-botan.cpp)
26+
target_include_directories(cryptosign-botan PRIVATE ${BOTAN_INCLUDE_DIRS})
27+
target_link_libraries(cryptosign-botan Botan2::Botan2)
28+
endif()
2029

2130
if(UNIX)
2231
make_example(uds uds.cpp)

examples/cryptosign-botan.cpp

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
///////////////////////////////////////////////////////////////////////////////
2+
//
3+
// Copyright (c) Crossbar.io Technologies GmbH and contributors
4+
//
5+
// Boost Software License - Version 1.0 - August 17th, 2003
6+
//
7+
// Permission is hereby granted, free of charge, to any person or organization
8+
// obtaining a copy of the software and accompanying documentation covered by
9+
// this license (the "Software") to use, reproduce, display, distribute,
10+
// execute, and transmit the Software, and to prepare derivative works of the
11+
// Software, and to permit third-parties to whom the Software is furnished to
12+
// do so, all subject to the following:
13+
//
14+
// The copyright notices in the Software and this entire statement, including
15+
// the above license grant, this restriction and the following disclaimer,
16+
// must be included in all copies of the Software, in whole or in part, and
17+
// all derivative works of the Software, unless such copies or derivative
18+
// works are solely in the form of machine-executable object code generated by
19+
// a source language processor.
20+
//
21+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
24+
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
25+
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
26+
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27+
// DEALINGS IN THE SOFTWARE.
28+
//
29+
///////////////////////////////////////////////////////////////////////////////
30+
31+
#include "parameters.hpp"
32+
33+
#include <botan/auto_rng.h>
34+
#include <botan/ecdsa.h>
35+
#include <botan/ec_group.h>
36+
#include <botan/pubkey.h>
37+
#include <botan/hex.h>
38+
#include <botan/ed25519.h>
39+
40+
#include <autobahn/autobahn.hpp>
41+
#include <utility>
42+
#include <autobahn/wamp_websocketpp_websocket_transport.hpp>
43+
#include <websocketpp/config/asio_no_tls_client.hpp>
44+
#include <websocketpp/client.hpp>
45+
#include <boost/version.hpp>
46+
#include <iostream>
47+
#include <memory>
48+
#include <string>
49+
#include <tuple>
50+
51+
void add2(autobahn::wamp_invocation invocation)
52+
{
53+
auto a = invocation->argument<uint64_t>(0);
54+
auto b = invocation->argument<uint64_t>(1);
55+
56+
std::cerr << "Procedure com.examples.calculator.add2 invoked: " << a << ", " << b << std::endl;
57+
58+
invocation->result(std::make_tuple(a + b));
59+
}
60+
61+
class auth_wamp_session :
62+
public autobahn::wamp_session
63+
{
64+
public:
65+
boost::promise<autobahn::wamp_authenticate> challenge_future;
66+
Botan::Ed25519_PrivateKey m_private_key;
67+
68+
auth_wamp_session(
69+
boost::asio::io_service& io,
70+
bool debug_enabled,
71+
const Botan::secure_vector<uint8_t>& private_key) :
72+
autobahn::wamp_session(io, debug_enabled),
73+
m_private_key(private_key)
74+
{
75+
}
76+
77+
boost::future<autobahn::wamp_authenticate> on_challenge(const autobahn::wamp_challenge& challenge)
78+
{
79+
Botan::AutoSeeded_RNG rng;
80+
Botan::PK_Signer signer(m_private_key, rng, "Pure");
81+
signer.update(Botan::hex_decode(challenge.challenge()));
82+
std::cerr << "responding to auth challenge: " << challenge.challenge() << std::endl;
83+
std::string signature = Botan::hex_encode(signer.signature(rng));
84+
challenge_future.set_value(autobahn::wamp_authenticate(signature + challenge.challenge()));
85+
std::cerr << "signature: " << signature << std::endl;
86+
return challenge_future.get_future();
87+
}
88+
};
89+
90+
typedef websocketpp::client<websocketpp::config::asio_client> client;
91+
92+
int main(int argc, char** argv)
93+
{
94+
std::cerr << "Boost: " << BOOST_VERSION << std::endl;
95+
try {
96+
auto parameters = get_parameters(argc, argv);
97+
98+
std::cerr << "Connecting to realm: " << parameters->realm() << std::endl;
99+
100+
boost::asio::io_service io;
101+
bool debug = parameters->debug();
102+
103+
client ws_clinet;
104+
ws_clinet.init_asio(&io);
105+
auto transport = std::make_shared < autobahn::wamp_websocketpp_websocket_transport<websocketpp::config::asio_client> >(
106+
ws_clinet, "ws://127.0.0.1:8080/ws", debug);
107+
108+
Botan::secure_vector<uint8_t> privateKey(32, 0);
109+
auto session = std::make_shared<auth_wamp_session>(io, debug, privateKey);
110+
111+
// Create a thread to run the telemetry loop
112+
transport->attach(std::static_pointer_cast<autobahn::wamp_transport_handler>(session));
113+
114+
// Make sure the continuation futures we use do not run out of scope prematurely.
115+
// Since we are only using one thread here this can cause the io service to block
116+
// as a future generated by a continuation will block waiting for its promise to be
117+
// fulfilled when it goes out of scope. This would prevent the session from receiving
118+
// responses from the router.
119+
boost::future<void> connect_future;
120+
boost::future<void> start_future;
121+
boost::future<void> join_future;
122+
boost::future<void> provide_future;
123+
124+
connect_future = transport->connect().then([&](boost::future<void> connected) {
125+
try {
126+
connected.get();
127+
} catch (const std::exception& e) {
128+
std::cerr << e.what() << std::endl;
129+
io.stop();
130+
return;
131+
}
132+
133+
std::cerr << "transport connected" << std::endl;
134+
135+
start_future = session->start().then([&](boost::future<void> started) {
136+
try {
137+
started.get();
138+
} catch (const std::exception& e) {
139+
std::cerr << e.what() << std::endl;
140+
io.stop();
141+
return;
142+
}
143+
144+
std::cerr << "session started" << std::endl;
145+
146+
std::vector<std::string> authmethods{"cryptosign"};
147+
std::string pubkey{"3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"};
148+
join_future = session->join(parameters->realm(), authmethods, "", {{"pubkey", pubkey}}).then([&](boost::future<uint64_t> joined) {
149+
try {
150+
std::cerr << "joined realm: " << joined.get() << std::endl;
151+
} catch (const std::exception& e) {
152+
std::cerr << e.what() << std::endl;
153+
io.stop();
154+
return;
155+
}
156+
157+
provide_future = session->provide("com.examples.calculator.add2", &add2).then(
158+
[&](boost::future<autobahn::wamp_registration> registration) {
159+
try {
160+
std::cerr << "registered procedure:" << registration.get().id() << std::endl;
161+
} catch (const std::exception& e) {
162+
std::cerr << e.what() << std::endl;
163+
io.stop();
164+
return;
165+
}
166+
});
167+
});
168+
});
169+
});
170+
171+
std::cerr << "starting io service" << std::endl;
172+
173+
io.run();
174+
175+
std::cerr << "stopped io service" << std::endl;
176+
}
177+
catch (const std::exception& e) {
178+
std::cerr << "exception: " << e.what() << std::endl;
179+
return -1;
180+
}
181+
182+
return 0;
183+
}

0 commit comments

Comments
 (0)