#!/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 #include #include #include #include #include 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::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"