diff --git a/include/jwt/base64.hpp b/include/jwt/base64.hpp index 2460afb..761dcea 100644 --- a/include/jwt/base64.hpp +++ b/include/jwt/base64.hpp @@ -36,41 +36,41 @@ std::string base64_encode(const char* in, size_t len) std::string result; result.resize(128); - constexpr static const EMap bmap{}; + constexpr static const EMap emap{}; int i = 0; - + int j = 0; for (; i < len - 2; i += 3) { - auto first = in[i]; - auto second = in[i+1]; - auto third = in[i+2]; + auto& first = in[i]; + auto& second = in[i+1]; + auto& third = in[i+2]; - result[i] = bmap.at( (first >> 2) & 0x3F ); - result[i + 1] = bmap.at(((first & 0x03) << 4) | ((second & 0xF0) >> 4)); - result[i + 2] = bmap.at(((second & 0x0F) << 2) | ((third & 0xC0) >> 6)); - result[i + 3] = bmap.at( (third & 0x3F) ); + result[j++] = emap.at( (first >> 2) & 0x3F ); + result[j++] = emap.at(((first & 0x03) << 4) | ((second & 0xF0) >> 4)); + result[j++] = emap.at(((second & 0x0F) << 2) | ((third & 0xC0) >> 6)); + result[j++] = emap.at( (third & 0x3F) ); } switch (len % 3) { case 2: { - auto first = in[i]; - auto second = in[i+1]; + auto& first = in[i]; + auto& second = in[i+1]; - result[i] = bmap.at( (first >> 2) & 0x3F ); - result[i + 1] = bmap.at(((first & 0x03) << 4) | ((second & 0xF0) >> 4)); - result[i + 2] = bmap.at( (second & 0x0F) << 2 ); - result[i + 3] = '='; + result[j++] = emap.at( (first >> 2) & 0x3F ); + result[j++] = emap.at(((first & 0x03) << 4) | ((second & 0xF0) >> 4)); + result[j++] = emap.at( (second & 0x0F) << 2 ); + result[j++] = '='; break; } case 1: { auto& first = in[i]; - result[i] = bmap.at((first >> 2) & 0x3F ); - result[i + 1] = bmap.at((first & 0x03) << 4 ); - result[i + 2] = '='; - result[i + 3] = '='; + result[j++] = emap.at((first >> 2) & 0x3F); + result[j++] = emap.at((first & 0x03) << 4); + result[j++] = '='; + result[j++] = '='; break; } case 0: @@ -135,7 +135,7 @@ std::string base64_decode(const char* in, size_t len) while (bytes_rem > 4) { // Error case in input - if (dmap.at(*in) == -1) return; + if (dmap.at(*in) == -1) return result; auto first = dmap.at(in[0]); auto second = dmap.at(in[1]); diff --git a/include/jwt/detail/meta.hpp b/include/jwt/detail/meta.hpp new file mode 100644 index 0000000..4626c82 --- /dev/null +++ b/include/jwt/detail/meta.hpp @@ -0,0 +1,10 @@ +#ifndef JWT_META_HPP +#define JWT_META_HPP + +namespace jwt { +namespace detail { + +} +} // END namespace jwt + +#endif diff --git a/include/jwt/impl/jwt.ipp b/include/jwt/impl/jwt.ipp new file mode 100644 index 0000000..af84bd8 --- /dev/null +++ b/include/jwt/impl/jwt.ipp @@ -0,0 +1,36 @@ +#ifndef JWT_IPP +#define JWT_IPP + +namespace jwt { + +template <typename T> +std::string to_json_str(const T& obj, bool pretty) +{ + return pretty ? obj.create_json_obj().dump(2) + : obj.create_json_obj().dump() + ; +} + + +template <typename T> +std::ostream& write(std::ostream& os, const T& obj, bool pretty) +{ + pretty ? (os << std::setw(2) << obj.create_json_obj()) + : (os << obj.create_json_obj()) + ; + + return os; +} + + +template <typename T> +std::ostream& operator<< (std::ostream& os, const T& obj) +{ + os << obj.create_json_obj(); + return os; +} + +} // END namespace jwt + + +#endif diff --git a/include/jwt/json/test_json b/include/jwt/json/test_json new file mode 100755 index 0000000..6bf91cf Binary files /dev/null and b/include/jwt/json/test_json differ diff --git a/include/jwt/json/test_json.cc b/include/jwt/json/test_json.cc new file mode 100644 index 0000000..869d24f --- /dev/null +++ b/include/jwt/json/test_json.cc @@ -0,0 +1,21 @@ +#include <iostream> +#include <string> +#include "./json.hpp" + +using json = nlohmann::json; + +void basic_json_test() +{ + json obj = json::object(); + obj["test"] = "value-test"; + obj["test-int"] = 42; + + std::string jstr = obj.dump(0); + std::cout << jstr << std::endl; +} + +int main() { + basic_json_test(); + + return 0; +} diff --git a/include/jwt/jwt.hpp b/include/jwt/jwt.hpp index 86e5904..884dbf0 100644 --- a/include/jwt/jwt.hpp +++ b/include/jwt/jwt.hpp @@ -1,8 +1,23 @@ #ifndef JWT_HPP #define JWT_HPP +#include <cassert> +#include <string> +#include <ostream> + +#include "jwt/base64.hpp" +#include "jwt/string_view.hpp" +#include "jwt/detail/meta.hpp" +#include "jwt/json/json.hpp" + +// For convenience +using json_t = nlohmann::json; + namespace jwt { +/*! + * JWT signing algorithm. + */ enum class algorithm { NONE = 0, @@ -19,6 +34,239 @@ enum class algorithm }; +/*! + */ +string_view alg_to_str(enum algorithm alg) noexcept +{ + switch (alg) { + case algorithm::HS256: return "HS256"; + case algorithm::HS384: return "HS384"; + case algorithm::HS512: return "HS512"; + case algorithm::RS256: return "RS256"; + case algorithm::RS384: return "RS384"; + case algorithm::RS512: return "RS512"; + case algorithm::ES256: return "ES256"; + case algorithm::ES384: return "ES384"; + case algorithm::ES512: return "ES512"; + case algorithm::TERM: return "TERM"; + case algorithm::NONE: return "NONE"; + default: assert (0 && "Unknown Algorithm"); + }; + + assert (0 && "Code not reached"); +} + + +/*! + */ +enum class type +{ + JWT = 0, +}; + + +/*! + */ +string_view type_to_str(enum type typ) +{ + switch (typ) { + case type::JWT: return "JWT"; + default: assert (0 && "Unknown type"); + }; + + assert (0 && "Code not reached"); +} + + +/*! + * Registered claim names. + */ +enum class registered_claims +{ + // Expiration Time claim + expiration = 0, + // Not Before Time claim + not_before, + // Issuer name claim + issuer, + // Audience claim + audience, + // Issued At Time claim + issued_at, + // Subject claim + subject, + // JWT ID claim + jti, +}; + + +/*! + */ +string_view reg_claims_to_str(enum registered_claims claim) noexcept +{ + switch (claim) { + case registered_claims::expiration: return "exp"; + case registered_claims::not_before: return "nbf"; + case registered_claims::issuer: return "iss"; + case registered_claims::audience: return "aud"; + case registered_claims::issued_at: return "iat"; + case registered_claims::subject: return "sub"; + case registered_claims::jti: return "jti"; + default: assert (0 && "Not a registered claim"); + }; + + assert (0 && "Code not reached"); +} + +// Fwd declaration for friend functions to specify the +// default arguments +// See: https://stackoverflow.com/a/23336823/434233 +template <typename T> +std::string to_json_str(const T& obj, bool pretty=false); + +template <typename T> +std::ostream& write(std::ostream& os, const T& obj, bool pretty=false); + +/*! + */ +struct write_interface +{ + /*! + */ + template <typename T> + friend std::string to_json_str(const T& obj, bool pretty); + + /*! + */ + template <typename T> + friend std::ostream& write( + std::ostream& os, const T& obj, bool pretty); + + /*! + */ + template <typename T> + friend std::ostream& operator<< (std::ostream& os, const T& obj); +}; + +/*! + */ +template <typename Derived> +struct base64_enc_dec +{ + std::string base64_encode(bool with_pretty = false) const + { + std::string jstr = to_json_str(*static_cast<const Derived*>(this), with_pretty); + return jwt::base64_encode(jstr.c_str(), jstr.length()); + } + + static std::string base64_decode(const std::string& encoded_str) + { + return jwt::base64_decode(encoded_str.c_str(), encoded_str.length()); + } + +}; + + +/*! + * JWT Header. + */ +struct jwt_header: write_interface + , base64_enc_dec<jwt_header> +{ +public: // 'tors + /*! + */ + jwt_header() = default; + + /*! + */ + jwt_header(enum algorithm alg, enum type typ = type::JWT) + : alg_(alg) + , typ_(typ) + { + } + + /// Default Copy and assignment + jwt_header(const jwt_header&) = default; + jwt_header& operator=(const jwt_header&) = default; + + ~jwt_header() = default; + +public: // Exposed APIs + /*! + * NOTE: Any previously saved json dump or the encoding of the + * header would not be valid after modifying the algorithm. + */ + void algo(enum algorithm alg) noexcept + { + alg_ = alg; + } + + /*! + */ + enum algorithm algo() const noexcept + { + return alg_; + } + + /*! + * NOTE: Any previously saved json dump or the encoding of the + * header would not be valid after modifying the type. + */ + void typ(enum type typ) noexcept + { + typ_ = typ; + } + + /*! + */ + enum type typ() const noexcept + { + return typ_; + } + + /*! + */ + json_t create_json_obj() const + { + json_t obj = json_t::object(); + obj["typ"] = type_to_str(typ_).to_string(); + obj["alg"] = alg_to_str(alg_).to_string(); + + return obj; + } + +private: // Data members + /// The Algorithm to use for signature creation + enum algorithm alg_ = algorithm::NONE; + + /// The type of header + enum type typ_ = type::JWT; +}; + +/*! + */ +struct jwt_payload +{ +}; + +/*! + */ +struct jwt_signature +{ +}; + + +/*! + */ +class jwt_object +{ +}; + + } // END namespace jwt + +#include "jwt/impl/jwt.ipp" + #endif diff --git a/include/jwt/string_view.hpp b/include/jwt/string_view.hpp index f7c7500..e14ef93 100644 --- a/include/jwt/string_view.hpp +++ b/include/jwt/string_view.hpp @@ -342,6 +342,10 @@ private: }; +/// Helper typedef +using string_view = basic_string_view<char>; + + } // END namespace jwt #include "jwt/impl/string_view.ipp" diff --git a/include/jwt/test/test_jwt_header b/include/jwt/test/test_jwt_header new file mode 100755 index 0000000..5ff066f Binary files /dev/null and b/include/jwt/test/test_jwt_header differ diff --git a/include/jwt/test/test_jwt_header.cc b/include/jwt/test/test_jwt_header.cc new file mode 100644 index 0000000..5f79b38 --- /dev/null +++ b/include/jwt/test/test_jwt_header.cc @@ -0,0 +1,19 @@ +#include <iostream> +#include "jwt/jwt.hpp" + +void test_basic_header() +{ + jwt::jwt_header hdr; + hdr = jwt::jwt_header{jwt::algorithm::HS256}; + std::string jstr = to_json_str(hdr); + std::cout << jstr << std::endl; + + std::string enc_str = hdr.base64_encode(); + std::cout << "Base64: " << enc_str << std::endl; + std::cout << "Decoded: " << jwt::jwt_header::base64_decode(enc_str) << std::endl; +} + +int main() { + test_basic_header(); + return 0; +} diff --git a/include/jwt/test_stack_alloc b/include/jwt/test/test_stack_alloc similarity index 100% rename from include/jwt/test_stack_alloc rename to include/jwt/test/test_stack_alloc diff --git a/include/jwt/test_stack_alloc.cc b/include/jwt/test/test_stack_alloc.cc similarity index 100% rename from include/jwt/test_stack_alloc.cc rename to include/jwt/test/test_stack_alloc.cc diff --git a/include/jwt/test_sv b/include/jwt/test/test_sv similarity index 100% rename from include/jwt/test_sv rename to include/jwt/test/test_sv diff --git a/include/jwt/test_sv.cc b/include/jwt/test/test_sv.cc similarity index 100% rename from include/jwt/test_sv.cc rename to include/jwt/test/test_sv.cc