Skip to content

Commit 2e2fd74

Browse files
author
Greg Hewett
committed
adding import and export jwk keys into signatures
1 parent a9472e4 commit 2e2fd74

File tree

17 files changed

+730
-16
lines changed

17 files changed

+730
-16
lines changed

CMakeLists.txt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ target_include_directories(${LIB_NAME}
114114
${OPENSSL_INCLUDE_DIR}
115115
)
116116

117+
install(TARGETS ${LIB_NAME} EXPORT mlspp-targets)
118+
117119
###
118120
### Tests
119121
###
@@ -125,9 +127,7 @@ endif()
125127
### Exports
126128
###
127129
set(CMAKE_EXPORT_PACKAGE_REGISTRY ON)
128-
export(EXPORT mlspp-targets
129-
NAMESPACE MLSPP::
130-
FILE ${CMAKE_CURRENT_BINARY_DIR}/mlspp-targets.cmake)
130+
export(EXPORT mlspp-targets NAMESPACE MLSPP:: FILE mlspp-targets.cmake)
131131
export(PACKAGE MLSPP)
132132

133133
configure_package_config_file(cmake/config.cmake.in
@@ -144,8 +144,6 @@ write_basic_package_version_file(
144144
### Install
145145
###
146146

147-
install(TARGETS ${LIB_NAME} EXPORT mlspp-targets)
148-
149147
install(
150148
DIRECTORY
151149
include/
@@ -156,7 +154,16 @@ install(
156154
FILES
157155
${CMAKE_CURRENT_BINARY_DIR}/mlspp-config.cmake
158156
${CMAKE_CURRENT_BINARY_DIR}/mlspp-config-version.cmake
159-
${CMAKE_CURRENT_BINARY_DIR}/mlspp-targets.cmake
157+
DESTINATION
158+
${CMAKE_INSTALL_DATADIR}/mlspp)
159+
160+
install(
161+
EXPORT
162+
mlspp-targets
163+
NAMESPACE
164+
MLSPP::
165+
FILE
166+
mlspp-targets.cmake
160167
DESTINATION
161168
${CMAKE_INSTALL_DATADIR}/mlspp)
162169

alternatives/openssl_3/vcpkg.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"name": "openssl",
88
"version>=": "3.0.7"
99
},
10-
"doctest"
10+
"doctest",
11+
"nlohmann-json"
1112
],
1213
"builtin-baseline": "5908d702d61cea1429b223a0b7a10ab86bad4c78",
1314
"overrides": [

include/mls/credential.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ namespace mls {
1111
// } BasicCredential;
1212
struct BasicCredential
1313
{
14-
BasicCredential() = default;
14+
BasicCredential() {}
1515

16-
explicit BasicCredential(bytes identity_in)
16+
BasicCredential(bytes identity_in)
1717
: identity(std::move(identity_in))
1818
{
1919
}

include/mls/crypto.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,18 @@ extern const std::string multi_credential;
210210

211211
struct SignaturePublicKey
212212
{
213+
static SignaturePublicKey from_jwk(CipherSuite suite,
214+
const std::string& json_str);
215+
213216
bytes data;
214217

215218
bool verify(const CipherSuite& suite,
216219
const std::string& label,
217220
const bytes& message,
218221
const bytes& signature) const;
219222

223+
std::string to_jwk(CipherSuite suite) const;
224+
220225
TLS_SERIALIZABLE(data)
221226
};
222227

@@ -225,6 +230,8 @@ struct SignaturePrivateKey
225230
static SignaturePrivateKey generate(CipherSuite suite);
226231
static SignaturePrivateKey parse(CipherSuite suite, const bytes& data);
227232
static SignaturePrivateKey derive(CipherSuite suite, const bytes& secret);
233+
static SignaturePrivateKey from_jwk(CipherSuite suite,
234+
const std::string& json_str);
228235

229236
SignaturePrivateKey() = default;
230237

@@ -236,6 +243,7 @@ struct SignaturePrivateKey
236243
const bytes& message) const;
237244

238245
void set_public_key(CipherSuite suite);
246+
std::string to_jwk(CipherSuite suite) const;
239247

240248
TLS_SERIALIZABLE(data)
241249

lib/bytes/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src
99

1010
add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES})
1111
add_dependencies(${CURRENT_LIB_NAME} tls_syntax)
12-
target_link_libraries(${CURRENT_LIB_NAME} tls_syntax)
12+
target_link_libraries(${CURRENT_LIB_NAME}
13+
PUBLIC
14+
tls_syntax
15+
PRIVATE
16+
OpenSSL::Crypto)
1317
target_include_directories(${CURRENT_LIB_NAME}
1418
PUBLIC
1519
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>

lib/bytes/include/bytes/bytes.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,16 @@ to_hex(const bytes& data);
115115
bytes
116116
from_hex(const std::string& hex);
117117

118+
std::string
119+
to_base64(const bytes& data);
120+
121+
std::string
122+
to_base64url(const bytes& data);
123+
124+
bytes
125+
from_base64(const std::string& enc);
126+
127+
bytes
128+
from_base64url(const std::string& enc);
129+
118130
} // namespace bytes_ns

lib/bytes/src/bytes.cpp

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#include <bytes/bytes.h>
22

3+
#include <array>
34
#include <iomanip>
4-
#include <iostream>
5+
#include <openssl/bio.h>
6+
#include <openssl/err.h>
7+
#include <openssl/evp.h>
58
#include <sstream>
69
#include <stdexcept>
710

@@ -137,4 +140,123 @@ operator!=(const std::vector<uint8_t>& lhs, const bytes_ns::bytes& rhs)
137140
return rhs != lhs;
138141
}
139142

143+
std::string
144+
to_base64(const bytes& data)
145+
{
146+
bool done = false;
147+
int result = 0;
148+
149+
if (data.empty()) {
150+
return "";
151+
}
152+
153+
BIO* b64 = BIO_new(BIO_f_base64());
154+
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
155+
BIO* out = BIO_new(BIO_s_mem());
156+
BIO_push(b64, out);
157+
158+
while (!done) {
159+
result = BIO_write(b64, data.data(), static_cast<int>(data.size()));
160+
161+
if (result <= 0) {
162+
if (BIO_should_retry(b64)) {
163+
continue;
164+
}
165+
throw std::runtime_error("base64 encode failed");
166+
}
167+
done = true;
168+
}
169+
BIO_flush(b64);
170+
char* string_ptr = nullptr;
171+
// long string_len = BIO_get_mem_data(out, &string_ptr);
172+
// BIO_get_mem_data failed clang-tidy
173+
long string_len = BIO_ctrl(out, BIO_CTRL_INFO, 0, &string_ptr);
174+
auto return_value = std::string(string_ptr, string_len);
175+
176+
BIO_set_close(out, BIO_NOCLOSE);
177+
BIO_free(b64);
178+
BIO_free(out);
179+
return return_value;
180+
}
181+
182+
std::string
183+
to_base64url(const bytes& data)
184+
{
185+
if (data.empty()) {
186+
return "";
187+
}
188+
189+
std::string return_value = to_base64(data);
190+
191+
// remove the end padding
192+
auto sz = return_value.find_first_of('=');
193+
194+
if (sz != std::string::npos) {
195+
return_value = return_value.substr(0, sz);
196+
}
197+
198+
// replace plus with hyphen
199+
std::replace(return_value.begin(), return_value.end(), '+', '-');
200+
201+
// replace slash with underscore
202+
std::replace(return_value.begin(), return_value.end(), '/', '_');
203+
return return_value;
204+
}
205+
206+
bytes
207+
from_base64(const std::string& enc)
208+
{
209+
if (enc.length() == 0) {
210+
return {};
211+
}
212+
213+
if (enc.length() % 4 != 0) {
214+
throw std::runtime_error("Base64 length is not divisible by 4");
215+
}
216+
bytes input = from_ascii(enc);
217+
bytes output(input.size() / 4 * 3);
218+
int output_buffer_length = static_cast<int>(output.size());
219+
EVP_ENCODE_CTX* ctx = EVP_ENCODE_CTX_new();
220+
EVP_DecodeInit(ctx);
221+
222+
int result = EVP_DecodeUpdate(ctx,
223+
output.data(),
224+
&output_buffer_length,
225+
input.data(),
226+
static_cast<int>(input.size()));
227+
228+
if (result == -1) {
229+
auto code = ERR_get_error();
230+
throw std::runtime_error(ERR_error_string(code, nullptr));
231+
}
232+
233+
if (result == 0 && enc.substr(enc.length() - 2, enc.length()) == "==") {
234+
output = output.slice(0, output.size() - 2);
235+
} else if (result == 0 && enc.substr(enc.length() - 1, enc.length()) == "=") {
236+
output = output.slice(0, output.size() - 1);
237+
} else if (result == 0) {
238+
throw std::runtime_error("Base64 padding was malformed.");
239+
}
240+
EVP_DecodeFinal(ctx, output.data(), &output_buffer_length);
241+
EVP_ENCODE_CTX_free(ctx);
242+
return output;
243+
}
244+
245+
bytes
246+
from_base64url(const std::string& enc)
247+
{
248+
if (enc.empty()) {
249+
return {};
250+
}
251+
std::string enc_copy = enc; // copy
252+
std::replace(enc_copy.begin(), enc_copy.end(), '-', '+');
253+
std::replace(enc_copy.begin(), enc_copy.end(), '_', '/');
254+
255+
while (enc_copy.length() % 4 != 0) {
256+
enc_copy += "=";
257+
}
258+
bytes return_value = from_base64(enc_copy);
259+
return return_value;
260+
}
261+
140262
} // namespace bytes_ns

lib/bytes/test/bytes.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <doctest/doctest.h>
33
#include <memory>
44
#include <sstream>
5+
#include <vector>
56

67
using namespace bytes_ns;
78
using namespace std::literals::string_literals;
@@ -40,6 +41,33 @@ TEST_CASE("To/from hex/ASCII")
4041
REQUIRE(from_ascii(str) == ascii);
4142
}
4243

44+
TEST_CASE("To Base64 / To Base64Url")
45+
{
46+
struct KnownAnswerTest
47+
{
48+
bytes data;
49+
std::string base64;
50+
std::string base64u;
51+
};
52+
53+
const std::vector<KnownAnswerTest> cases{
54+
{ from_ascii("hello there"), "aGVsbG8gdGhlcmU=", "aGVsbG8gdGhlcmU" },
55+
{ from_ascii("A B C D E F "), "QSBCIEMgRCBFIEYg", "QSBCIEMgRCBFIEYg" },
56+
{ from_ascii("hello\xfethere"), "aGVsbG/+dGhlcmU=", "aGVsbG_-dGhlcmU" },
57+
{ from_ascii("\xfe"), "/g==", "_g" },
58+
{ from_ascii("\x01\x02"), "AQI=", "AQI" },
59+
{ from_ascii("\x01"), "AQ==", "AQ" },
60+
{ from_ascii(""), "", "" },
61+
};
62+
63+
for (const auto& tc : cases) {
64+
REQUIRE(to_base64(tc.data) == tc.base64);
65+
REQUIRE(to_base64url(tc.data) == tc.base64u);
66+
REQUIRE(from_base64(tc.base64) == tc.data);
67+
REQUIRE(from_base64url(tc.base64u) == tc.data);
68+
}
69+
}
70+
4371
TEST_CASE("Operators")
4472
{
4573
const auto lhs = from_hex("00010203");

lib/hpke/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ set(CURRENT_LIB_NAME hpke)
33
###
44
### Dependencies
55
###
6+
find_package(nlohmann_json REQUIRED)
67
find_package(OpenSSL 1.1 REQUIRED)
78

89
###
@@ -14,7 +15,11 @@ file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src
1415

1516
add_library(${CURRENT_LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES})
1617
add_dependencies(${CURRENT_LIB_NAME} bytes tls_syntax)
17-
target_link_libraries(${CURRENT_LIB_NAME} PRIVATE bytes tls_syntax OpenSSL::Crypto)
18+
target_link_libraries(${CURRENT_LIB_NAME}
19+
PRIVATE
20+
nlohmann_json::nlohmann_json OpenSSL::Crypto
21+
PUBLIC
22+
bytes tls_syntax)
1823
target_include_directories(${CURRENT_LIB_NAME}
1924
PUBLIC
2025
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>

lib/hpke/include/hpke/signature.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ struct Signature
5050
virtual std::unique_ptr<PrivateKey> deserialize_private(
5151
const bytes& skm) const;
5252

53+
virtual std::unique_ptr<PrivateKey> import_jwk_private(
54+
const std::string& json_str) const;
55+
virtual std::unique_ptr<PublicKey> import_jwk(
56+
const std::string& json_str) const;
57+
virtual std::string export_jwk_private(const bytes& env) const;
58+
virtual std::string export_jwk(const bytes& env) const;
59+
5360
virtual bytes sign(const bytes& data, const PrivateKey& sk) const = 0;
5461
virtual bool verify(const bytes& data,
5562
const bytes& sig,

0 commit comments

Comments
 (0)