Add bounded structured crypto operations
This commit is contained in:
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
test_source="/tmp/uce-oauth-es256-native-$$.cpp"
|
||||
test_binary="/tmp/uce-oauth-es256-native-$$"
|
||||
cleanup() { rm -f "$test_source" "$test_binary"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
cat >"$test_source" <<'EOF'
|
||||
#include "src/lib/types.cpp"
|
||||
#include "src/lib/dvalue.cpp"
|
||||
#include "src/lib/functionlib.cpp"
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/core_names.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/params.h>
|
||||
#include <memory>
|
||||
|
||||
String base64_encode(String raw)
|
||||
{
|
||||
if(raw.empty()) return("");
|
||||
String out(4 * ((raw.size() + 2) / 3), 0);
|
||||
int size = EVP_EncodeBlock((unsigned char*)out.data(), (const unsigned char*)raw.data(), (int)raw.size());
|
||||
out.resize(size > 0 ? (size_t)size : 0);
|
||||
return(out);
|
||||
}
|
||||
|
||||
String base64_decode(String raw, bool& ok)
|
||||
{
|
||||
ok = false;
|
||||
if(raw.empty() || raw.size() % 4) return("");
|
||||
String out(3 * raw.size() / 4, 0);
|
||||
int size = EVP_DecodeBlock((unsigned char*)out.data(), (const unsigned char*)raw.data(), (int)raw.size());
|
||||
if(size < 0) return("");
|
||||
while(!raw.empty() && raw.back() == '=') { size--; raw.pop_back(); }
|
||||
out.resize((size_t)size); ok = true; return(out);
|
||||
}
|
||||
|
||||
#include "src/lib/hash.cpp"
|
||||
|
||||
static String b64url_decode(String text)
|
||||
{
|
||||
text = replace(replace(text, "-", "+"), "_", "/");
|
||||
while(text.size() % 4) text += "=";
|
||||
bool ok = false;
|
||||
String result = base64_decode(text, ok);
|
||||
return(ok ? result : String(""));
|
||||
}
|
||||
|
||||
static bool verify(DValue public_jwk, String jwt)
|
||||
{
|
||||
StringList parts = split(jwt, ".");
|
||||
if(parts.size() != 3) return(false);
|
||||
String x = b64url_decode(public_jwk["x"].to_string());
|
||||
String y = b64url_decode(public_jwk["y"].to_string());
|
||||
String raw = b64url_decode(parts[2]);
|
||||
if(x.size() != 32 || y.size() != 32 || raw.size() != 64) return(false);
|
||||
unsigned char point[65] = {4}; memcpy(point + 1, x.data(), 32); memcpy(point + 33, y.data(), 32);
|
||||
OSSL_PARAM params[] = { OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (char*)"prime256v1", 0), OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, point, sizeof(point)), OSSL_PARAM_construct_end() };
|
||||
EVP_PKEY_CTX* build = EVP_PKEY_CTX_new_from_name(0, "EC", 0); EVP_PKEY* key = 0;
|
||||
if(!build || EVP_PKEY_fromdata_init(build) <= 0 || EVP_PKEY_fromdata(build, &key, EVP_PKEY_PUBLIC_KEY, params) <= 0) { EVP_PKEY_CTX_free(build); return(false); }
|
||||
EVP_PKEY_CTX_free(build);
|
||||
BIGNUM* r = BN_bin2bn((const unsigned char*)raw.data(), 32, 0); BIGNUM* s = BN_bin2bn((const unsigned char*)raw.data() + 32, 32, 0);
|
||||
ECDSA_SIG* sig = ECDSA_SIG_new(); int der_size = r && s && sig && ECDSA_SIG_set0(sig, r, s) ? i2d_ECDSA_SIG(sig, 0) : 0; r = s = 0;
|
||||
String der(der_size > 0 ? (size_t)der_size : 0, 0); unsigned char* out = (unsigned char*)der.data();
|
||||
bool ok = der_size > 0 && i2d_ECDSA_SIG(sig, &out) == der_size;
|
||||
EVP_MD_CTX* verify_ctx = EVP_MD_CTX_new();
|
||||
String signing_input = parts[0] + "." + parts[1];
|
||||
ok = ok && verify_ctx && EVP_DigestVerifyInit(verify_ctx, 0, EVP_sha256(), 0, key) > 0 && EVP_DigestVerify(verify_ctx, (const unsigned char*)der.data(), der.size(), (const unsigned char*)signing_input.data(), signing_input.size()) == 1;
|
||||
EVP_MD_CTX_free(verify_ctx); ECDSA_SIG_free(sig); EVP_PKEY_free(key); return(ok);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
DValue key_request; key_request["operation"] = "key_generate"; key_request["algorithm"] = "ES256";
|
||||
DValue key = crypto_operation_native(key_request);
|
||||
DValue header; header["alg"] = "none"; header["kid"] = key["kid"]; DValue claims; claims["iss"] = "https://client.example";
|
||||
auto sign = [&](DValue private_jwk) { DValue request; request["operation"] = "jwt_sign"; request["algorithm"] = "ES256"; request["private_jwk"] = private_jwk; request["protected_header"] = header; request["claims"] = claims; return(crypto_operation_native(request)); };
|
||||
DValue signed_result = sign(key["private_jwk"]); String jwt = signed_result["jwt"].to_string();
|
||||
DValue wrong_curve = key["private_jwk"]; wrong_curve["crv"] = "P-384";
|
||||
DValue malformed = key["private_jwk"]; malformed["x"] = "bad=";
|
||||
DValue mismatch = key["private_jwk"]; String d = mismatch["d"].to_string(); d[0] = d[0] == 'A' ? 'B' : 'A'; mismatch["d"] = d;
|
||||
DValue unsupported; unsupported["operation"] = "encrypt"; unsupported["algorithm"] = "ES256";
|
||||
DValue unknown_algorithm; unknown_algorithm["operation"] = "key_generate"; unknown_algorithm["algorithm"] = "none";
|
||||
DValue untyped_algorithm = key_request; untyped_algorithm["algorithm"] = (f64)256;
|
||||
DValue list_header; list_header.set_array(); DValue list_item; list_item = "not-an-object"; list_header.push(list_item); DValue list_request; list_request["operation"] = "jwt_sign"; list_request["algorithm"] = "ES256"; list_request["private_jwk"] = key["private_jwk"]; list_request["protected_header"] = list_header; list_request["claims"] = claims;
|
||||
DValue control_request = list_request; control_request["protected_header"] = header; control_request["claims"] = claims; control_request["claims"]["bad"] = String("control\nbyte");
|
||||
DValue oversized = key_request; oversized["ignored"] = String(17000, 'x');
|
||||
DValue nonfinite = key_request; nonfinite["ignored"] = std::numeric_limits<f64>::quiet_NaN();
|
||||
String tampered = jwt; if(!tampered.empty()) tampered[tampered.size() - 1] = tampered.back() == 'A' ? 'B' : 'A';
|
||||
bool kid_ok = key["ok"].to_bool() && key["kid"].to_string() == key["thumbprint"].to_string();
|
||||
bool signed_ok = signed_result["ok"].to_bool() && jwt != "" && verify(key["public_jwk"], jwt);
|
||||
bool tamper_ok = !verify(key["public_jwk"], tampered);
|
||||
bool negatives_ok = !sign(wrong_curve)["ok"].to_bool() && !sign(malformed)["ok"].to_bool() && !sign(mismatch)["ok"].to_bool() && crypto_operation_native(unsupported)["error"].to_string() == "unsupported_operation" && crypto_operation_native(unknown_algorithm)["error"].to_string() == "unsupported_algorithm" && crypto_operation_native(untyped_algorithm)["error"].to_string() == "invalid_request" && crypto_operation_native(list_request)["error"].to_string() == "invalid_key_or_payload" && crypto_operation_native(control_request)["error"].to_string() == "invalid_request" && crypto_operation_native(oversized)["error"].to_string() == "invalid_request" && crypto_operation_native(nonfinite)["error"].to_string() == "invalid_request";
|
||||
if(!(kid_ok && signed_ok && tamper_ok && negatives_ok)) std::cerr << "kid=" << kid_ok << " signed=" << signed_ok << " tamper=" << tamper_ok << " negatives=" << negatives_ok << " jwt_size=" << jwt.size() << "\\n";
|
||||
return(kid_ok && signed_ok && tamper_ok && negatives_ok ? 0 : 1);
|
||||
}
|
||||
EOF
|
||||
clang++ -std=c++20 -fpermissive -I. "$test_source" -lpcre2-8 -lcrypto -o "$test_binary"
|
||||
"$test_binary"
|
||||
echo "native structured crypto operation passed"
|
||||
Reference in New Issue
Block a user