diff --git a/CMakeLists.txt b/CMakeLists.txt index 3263ee9..ceb8e3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ else() endif() find_package(jwt-cpp) +find_package(argparse) find_package(OpenSSL) set(CMAKE_CXX_STANDARD 20) @@ -23,3 +24,11 @@ target_include_directories(j7s-plugin PUBLIC ${jwt-cpp_INCLUDE_DIR} ) target_link_libraries(j7s-plugin OpenSSL::Crypto) + +add_executable(gen-token src/gen-token.cpp) +target_include_directories(gen-token PUBLIC + $ + $ + ${jwt-cpp_INCLUDE_DIR} +) +target_link_libraries(gen-token OpenSSL::Crypto argparse::argparse) diff --git a/README.md b/README.md index d0a6824..e9acb81 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Authentication using JWTs for the mosquitto mqtt broker. ## Dependencies ``` -sudo apt install mosquitto-dev g++ cmake libmosquitto-dev mosquitto-clients -sudo apt install openssl libssl-dev +sudo apt install mosquitto-dev g++ cmake libmosquitto-dev mosquitto-clients openssl libssl-dev ``` + +## Generating offline keys +ssh-keygen -t rsa -b 4096 -m PEM -f priv.key +rm priv.key.pub +openssl rsa -in priv.key -pubout -outform PEM -out pub.key diff --git a/external-deps.cmake b/external-deps.cmake index 9266bf0..91a509a 100644 --- a/external-deps.cmake +++ b/external-deps.cmake @@ -16,6 +16,19 @@ list (APPEND EXTRA_CMAKE_ARGS -Djwt-cpp_DIR=${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp -Djwt-cpp_INCLUDE_DIR=${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp/include ) +list(APPEND DEPENDENCIES ep_argparse) +ExternalProject_Add(ep_argparse + PREFIX ep_argparse + GIT_REPOSITORY "https://github.com/p-ranav/argparse.git" + GIT_TAG "v2.2" + GIT_SHALLOW "True" + CMAKE_ARGS -DARGPARSE_CMAKE_FILES_INSTALL_DIR=${CMAKE_CURRENT_BINARY_DIR}/argparse -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/argparse + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/argparse +) +list (APPEND EXTRA_CMAKE_ARGS + -Dargparse_DIR=${CMAKE_CURRENT_BINARY_DIR}/argparse +) + ExternalProject_Add (ep_j7s-mosquitto-plugin PREFIX ep_j7s-mosquitto-plugin DEPENDS ${DEPENDENCIES} diff --git a/src/gen-token.cpp b/src/gen-token.cpp new file mode 100644 index 0000000..daaaf42 --- /dev/null +++ b/src/gen-token.cpp @@ -0,0 +1,108 @@ +// Copyright 2022 James Pace +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +std::optional read_key(const std::string& key_file); + +int main(int argc, char *argv[]) +{ + argparse::ArgumentParser program("gen-token", "0.0.0"); + + program.add_argument("--pub-key") + .required() + .help("Pub key of signer."); + program.add_argument("--priv-key") + .required() + .help("Private key of signer."); + program.add_argument("--issuer") + .required() + .help("Issuer to assign to signed key."); + program.add_argument("--username") + .required() + .help("Username assigned to key."); + program.add_argument("--valid-days") + .required() + .help("Days from now until the token will be valid."); + program.add_argument("--can-read") + .help("holder can read") + .default_value(false) + .implicit_value(true); + program.add_argument("--can-write") + .help("holder can write") + .default_value(false) + .implicit_value(true); + try + { + program.parse_args(argc, argv); + } + catch (const std::runtime_error & err) + { + std::cerr << err.what() << std::endl; + std::cerr << program; + return -1; + } + + const auto priv_key_file = program.get("--priv-key"); + const auto priv_key = read_key(std::filesystem::absolute(priv_key_file)); + const auto pub_key_file = program.get("--pub-key"); + const auto pub_key = read_key(std::filesystem::absolute(pub_key_file)); + + if(not pub_key or not priv_key) + { + std::cerr << "Could not open key!" << std::endl; + return -2; + } + + const std::string can_read = program.get("--can-read") ? "true" : "false"; + const std::string can_write = program.get("--can-write") ? "true" : "false"; + + const auto expr_time = std::chrono::system_clock::now() + + std::chrono::days(std::stoi(program.get("--valid-days"))); + + const auto token = jwt::create() + .set_type("JWT") + .set_issuer(program.get("--issuer")) + .set_payload_claim("upn", jwt::claim(program.get("--username"))) + .set_payload_claim("mqtt-write", jwt::claim(can_read)) + .set_payload_claim("mqtt-read", jwt::claim(can_write)) + .set_expires_at(expr_time) + .sign(jwt::algorithm::rs256(pub_key.value(), priv_key.value(), "", "")); + + std::cout << token; + + return 0; +} + +std::optional read_key(const std::string& key_file) +{ + // Read key from file. + std::ifstream key_stream(key_file, std::ios::binary); + if(not key_stream) + { + return std::nullopt; + } + std::stringstream ss; + ss << key_stream.rdbuf(); + return ss.str(); +} diff --git a/test/mosquitto.conf b/test/mosquitto.conf index 34ad561..9133de9 100644 --- a/test/mosquitto.conf +++ b/test/mosquitto.conf @@ -1,12 +1,17 @@ +per_listener_settings true +log_type all + listener 8082 protocol websockets - -listener 8081 -protocol mqtt - -log_type all -allow_anonymous true - +allow_anonymous false auth_plugin /home/jimmy/Develop/mosquitto-plugin/build/libj7s-plugin.so auth_opt_issuer https://auth.jpace121.net/realms/jpace121-main auth_opt_public_key /home/jimmy/Develop/mosquitto-plugin/test/key.pem + +listener 8081 +protocol mqtt +allow_anonymous false +auth_plugin /home/jimmy/Develop/mosquitto-plugin/build/libj7s-plugin.so +auth_opt_issuer https://auth.jpace121.net/realms/jpace121-main +auth_opt_public_key /home/jimmy/Develop/mosquitto-plugin/test/key.pem + diff --git a/test/python-client.py b/test/python-device-client.py similarity index 100% rename from test/python-client.py rename to test/python-device-client.py diff --git a/test/python-direct-client.py b/test/python-direct-client.py new file mode 100644 index 0000000..b394714 --- /dev/null +++ b/test/python-direct-client.py @@ -0,0 +1,36 @@ +import requests +import json +import time +import paho.mqtt.client +import time + +# RESULT=`curl --cacert ./ca.pem --cert jimmy-client.pem --key jimmy-client-key.pem --data "grant_type=password&client_id=test&username=jimmy&password=1234" https://nginx-test.internal.jpace121.net:8443/realms/master/protocol/openid-connect/token` + +s = requests.Session() +s.verify = "/home/jimmy/Develop/mosquitto-plugin/test/ca.pem" +s.cert = ("/home/jimmy/Develop/keycloak/jimmy-client.pem", "/home/jimmy/Develop/keycloak/jimmy-client-key.pem") + + +# Get urls. +print("Before get.") +well_known = s.get("https://nginx-test.internal.jpace121.net:8443/realms/jpace121-main/.well-known/openid-configuration", ).json() +print("After get.") +token_url = well_known['token_endpoint'] +# Override for now, url doesn't include port... +token_url = "https://nginx-test.internal.jpace121.net:8443/realms/jpace121-main/protocol/openid-connect/token" +# RESULT=`curl --cacert ./ca.pem --cert jimmy-client.pem --key jimmy-client-key.pem --data "grant_type=password&client_id=mqtt&username=jimmy&password=1234" https://nginx-test.internal.jpace121.net:8443/realms/jpace121-main/protocol/openid-connect/token` + + +data = {"grant_type" : "password", "client_id" : "mqtt", "username" : "jimmy", "password": "1234"} + +# Request token. +r = s.post(token_url, data=data) +token = r.json()['access_token'] + +client = paho.mqtt.client.Client(protocol=paho.mqtt.client.MQTTv5, + transport="tcp") +client.username_pw_set("jimmy", password=token) +client.connect("localhost", port=8081) + +print("Waiting on connection.") +time.sleep(20)