Hoernchen has uploaded this change for review. (
https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/41120?usp=email )
Change subject: smdpp: add es2p test suite
......................................................................
smdpp: add es2p test suite
Integrated with es9p
Change-Id: I2865e016974d7d7a03e00a7795a42f573b147a4b
---
A smdpp/ES2Plus_Tests.cfg
A smdpp/ES2Plus_Tests.ttcn
M smdpp/rsp_client.cpp
M smdpp/smdpp_Tests.ttcn
M smdpp/smdpp_Tests_Functions.cc
A smdpp/test_certs/CERT_MNO_ECDSA_NIST.pem
A smdpp/test_certs/SK_MNO_ECDSA_NIST.pem
7 files changed, 1,695 insertions(+), 128 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks refs/changes/20/41120/1
diff --git a/smdpp/ES2Plus_Tests.cfg b/smdpp/ES2Plus_Tests.cfg
new file mode 100644
index 0000000..f98a65d
--- /dev/null
+++ b/smdpp/ES2Plus_Tests.cfg
@@ -0,0 +1,17 @@
+[LOGGING]
+FileMask := LOG_ALL | TTCN_DEBUG | TTCN_MATCHING | DEBUG_ENCDEC;
+SourceInfoFormat := Single;
+LogFile := "ES2Plus_Tests.%e.%h.%r.%s"
+LogSourceInfo := Stack
+
+[MODULE_PARAMETERS]
+# ES2+ specific parameters
+ES2Plus_Tests.mp_es2plus_server_host := "127.0.0.1"
+ES2Plus_Tests.mp_es2plus_server_port := 8000 # NIST tests use port 8000
+ES2Plus_Tests.mp_operator_id := "test.operator.com"
+
+[TESTPORT_PARAMETERS]
+
+[EXECUTE]
+# Execute all ES2+ tests
+ES2Plus_Tests.control
\ No newline at end of file
diff --git a/smdpp/ES2Plus_Tests.ttcn b/smdpp/ES2Plus_Tests.ttcn
new file mode 100644
index 0000000..c6e84cd
--- /dev/null
+++ b/smdpp/ES2Plus_Tests.ttcn
@@ -0,0 +1,1031 @@
+module ES2Plus_Tests {
+
+/* ES2+ Interface Test Suite
+ * Based on SGP.22 v2.5 Section 5.3 and SGP.23 v1.5 Section 4.3.1-4.3.5
+ */
+
+import from General_Types all;
+import from Osmocom_Types all;
+import from Native_Functions all;
+import from es2p_Types_JSON all;
+import from esx_header_Types_JSON all;
+import from TCCConversion_Functions all;
+import from smdpp_Tests all; // For RSPClient functions and types
+
+// ES2+ specific constants
+const charstring c_es2plus_base_path := "/gsma/rsp2/es2plus";
+const charstring c_path_download_order := c_es2plus_base_path &
"/downloadOrder";
+const charstring c_path_confirm_order := c_es2plus_base_path &
"/confirmOrder";
+const charstring c_path_cancel_order := c_es2plus_base_path &
"/cancelOrder";
+const charstring c_path_release_profile := c_es2plus_base_path &
"/releaseProfile";
+
+// Test ICCIDs that exist in Python server test mode
+const charstring c_iccid_test_1 := "89000000000000001010";
+const charstring c_iccid_test_2 := "89000000000000001028";
+const charstring c_iccid_test_3 := "89000000000000001036";
+const charstring c_iccid_test_4 := "89000000000000001044";
+const charstring c_iccid_test_5 := "89000000000000001051";
+
+// Test EIDs from test specification
+const charstring c_eid1 := "89049032123451234512345678901235";
+const charstring c_eid2 := "89049032123451234512345678901236";
+
+// Client certificate paths
+const charstring c_cert_path := "./test_certs/CERT_MNO_ECDSA_NIST.pem";
+const charstring c_key_path := "./test_certs/SK_MNO_ECDSA_NIST.pem";
+
+// Wrong certificate for testing
+const charstring c_wrong_cert_path :=
"./sgp26/eUICC/CERT_EUICC_ECDSA_NIST.der";
+const charstring c_wrong_key_path := "./sgp26/eUICC/SK_EUICC_ECDSA_NIST.pem";
+
+// Module parameters
+modulepar {
+ charstring mp_es2plus_server_host := "testsmdpplus1.example.com";
+ integer mp_es9_server_port := 8000;
+ integer mp_es2plus_server_port := 8000; // NIST tests use port 8000
+ charstring mp_operator_id := "test.operator.com";
+ charstring mp_nist_rsp_cert_path := "./sgp26/";
+ boolean mp_use_ssl := true; // SSL with mutual TLS authentication enabled
+}
+
+// Test component - extend smdpp_ConnHdlr to reuse RSPClient functionality
+type component ES2Plus_ConnHdlr extends smdpp_ConnHdlr {
+ // Inherits g_rsp_client_handle from smdpp_ConnHdlr
+}
+
+// Parameter structures for test cases
+type record DownloadOrderTestParams {
+ charstring testName,
+ charstring eid optional,
+ charstring iccid optional,
+ charstring profileType optional,
+ boolean expectError,
+ charstring expectedSubjectCode optional,
+ charstring expectedReasonCode optional
+}
+
+type record ConfirmOrderTestParams {
+ charstring testName,
+ charstring iccid,
+ charstring eid optional,
+ charstring matchingId optional,
+ charstring confirmationCode optional,
+ charstring smdsAddress optional,
+ boolean releaseFlag,
+ boolean expectError,
+ charstring expectedSubjectCode optional,
+ charstring expectedReasonCode optional
+}
+
+type record CancelOrderTestParams {
+ charstring testName,
+ charstring iccid,
+ charstring eid optional,
+ charstring matchingId optional,
+ charstring finalProfileStatusIndicator,
+ boolean expectError,
+ charstring expectedSubjectCode optional,
+ charstring expectedReasonCode optional
+}
+
+// Helper function to generate unique function call identifier
+function f_gen_es2plus_uuid() return charstring {
+ // Simple UUID based on random octets
+ var octetstring rnd_oct := f_rnd_octstring(4);
+ return "TTCN3-" & oct2str(rnd_oct);
+}
+
+// Helper function to build ES2+ request header
+function f_es2plus_request_header() return JSON_ESx_RequestHeader {
+ return {
+ functionRequesterIdentifier := mp_operator_id,
+ functionCallIdentifier := f_gen_es2plus_uuid()
+ };
+}
+
+// Helper function for ES2+ HTTP requests
+function f_es2plus_http_request(charstring path, charstring functionName,
JSON_ES2p_Request request) runs on ES2Plus_ConnHdlr return JSON_ES2p_Response {
+
+ var integer status_code;
+ var charstring response;
+
+ // Encode request
+ var octetstring req_enc := enc_JSON_ES2p_Request(request);
+
+ // Build full URL
+ var charstring url := path; // URL without host/port - the C++ function will add it
+
+ // Send request using pre-configured authentication
+ response := smdpp_Tests.ext_RSPClient_sendHttpsPostWithAuth(
+ g_rsp_client_handle,
+ url,
+ oct2char(req_enc),
+ mp_es2plus_server_port,
+ status_code
+ );
+
+ if (status_code != 200) {
+ setverdict(fail, "HTTP request failed with status ", status_code);
+ mtc.stop;
+ }
+
+ // Decode response based on function name
+ return dec_JSON_ES2p_Response(char2oct(response), functionName);
+}
+
+// Check if response indicates success
+function f_es2plus_check_success(JSON_ESx_ResponseHeader header) return boolean {
+ if (header.functionExecutionStatus.status == "Executed-Success") {
+ return true;
+ }
+ return false;
+}
+
+// Helper function to verify error response
+function f_es2plus_check_error(JSON_ESx_ResponseHeader header, charstring
expected_subject_code, charstring expected_reason_code) return boolean {
+ if (header.functionExecutionStatus.status != "Failed") {
+ log("Expected Failed status, got: ",
header.functionExecutionStatus.status);
+ return false;
+ }
+
+ if (not ispresent(header.functionExecutionStatus.statusCodeData)) {
+ log("Missing statusCodeData in error response");
+ return false;
+ }
+
+ var JSON_ESx_FunctionExecutionStatusCodeData statusData :=
header.functionExecutionStatus.statusCodeData;
+
+ if (statusData.subjectCode != expected_subject_code) {
+ log("Expected subject code ", expected_subject_code, ", got:
", statusData.subjectCode);
+ return false;
+ }
+
+ if (statusData.reasonCode != expected_reason_code) {
+ log("Expected reason code ", expected_reason_code, ", got: ",
statusData.reasonCode);
+ return false;
+ }
+
+ log("Got expected error: ", expected_subject_code, "/",
expected_reason_code);
+ return true;
+}
+
+// Initialize RSP client for ES2+
+function f_init_es2plus() runs on ES2Plus_ConnHdlr {
+ // Initialize RSP client using imported function from smdpp_Tests
+ g_rsp_client_handle := smdpp_Tests.ext_RSPClient_create(
+ mp_es2plus_server_host,
+ mp_es2plus_server_port,
+ mp_nist_rsp_cert_path,
+ "NIST"
+ );
+
+ if (g_rsp_client_handle < 0) {
+ setverdict(fail, "Failed to initialize RSP client");
+ mtc.stop;
+ }
+
+ // Set authentication parameters once for all ES2+ operations
+ var integer result := smdpp_Tests.ext_RSPClient_setAuthParams(
+ g_rsp_client_handle,
+ true, // useMutualTLS
+ c_cert_path,
+ c_key_path
+ );
+
+ if (result != 0) {
+ setverdict(fail, "Failed to set authentication parameters");
+ mtc.stop;
+ }
+}
+
+/***********************************************************************
+ * Generic Test Functions
+ ***********************************************************************/
+
+// Generic DownloadOrder test function
+private function f_TC_DownloadOrder_Generic(
+ DownloadOrderTestParams params
+) runs on ES2Plus_ConnHdlr {
+
+ log("=== Test Case: ", params.testName, " ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // Build request
+ var JSON_ES2p_Request req := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := params.eid,
+ iccid := params.iccid,
+ profileType := params.profileType
+ }
+ };
+
+ // Send and receive
+ var JSON_ES2p_Response resp := f_es2plus_http_request(c_path_download_order,
"downloadOrder", req);
+
+ // Check result
+ if (params.expectError) {
+ if (not f_es2plus_check_error(resp.downloadOrderResponse.header,
+ params.expectedSubjectCode,
+ params.expectedReasonCode)) {
+ setverdict(fail, "Expected error ", params.expectedSubjectCode,
"/",
+ params.expectedReasonCode, " not received");
+ mtc.stop;
+ }
+ } else {
+ if (not f_es2plus_check_success(resp.downloadOrderResponse.header)) {
+ setverdict(fail, "DownloadOrder failed unexpectedly");
+ mtc.stop;
+ }
+
+ // Verify ICCID returned
+ if (not ispresent(resp.downloadOrderResponse.iccid)) {
+ setverdict(fail, "No ICCID in response");
+ mtc.stop;
+ }
+
+ log("Reserved ICCID: ", resp.downloadOrderResponse.iccid);
+ }
+
+ setverdict(pass);
+}
+
+// Generic ConfirmOrder test function
+private function f_TC_ConfirmOrder_Generic(
+ ConfirmOrderTestParams params
+) runs on ES2Plus_ConnHdlr {
+
+ log("=== Test Case: ", params.testName, " ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // Build request
+ var JSON_ES2p_Request req := {
+ confirmOrderRequest := {
+ header := f_es2plus_request_header(),
+ iccid := params.iccid,
+ eid := params.eid,
+ matchingId := params.matchingId,
+ confirmationCode := params.confirmationCode,
+ smdsAddress := params.smdsAddress,
+ releaseFlag := params.releaseFlag
+ }
+ };
+
+ // Send and receive
+ var JSON_ES2p_Response resp := f_es2plus_http_request(c_path_confirm_order,
"confirmOrder", req);
+
+ // Check result
+ if (params.expectError) {
+ if (not f_es2plus_check_error(resp.confirmOrderResponse.header,
+ params.expectedSubjectCode,
+ params.expectedReasonCode)) {
+ setverdict(fail, "Expected error ", params.expectedSubjectCode,
"/",
+ params.expectedReasonCode, " not received");
+ mtc.stop;
+ }
+ } else {
+ if (not f_es2plus_check_success(resp.confirmOrderResponse.header)) {
+ setverdict(fail, "ConfirmOrder failed unexpectedly");
+ mtc.stop;
+ }
+
+ // Verify matchingId returned
+ if (not ispresent(resp.confirmOrderResponse.matchingId)) {
+ setverdict(fail, "No matchingId in response");
+ mtc.stop;
+ }
+
+ log("Got matchingId: ", resp.confirmOrderResponse.matchingId);
+ }
+
+ setverdict(pass);
+}
+
+// Generic CancelOrder test function
+private function f_TC_CancelOrder_Generic(
+ CancelOrderTestParams params
+) runs on ES2Plus_ConnHdlr {
+
+ log("=== Test Case: ", params.testName, " ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // Build request
+ var JSON_ES2p_Request req := {
+ cancelOrderRequest := {
+ header := f_es2plus_request_header(),
+ iccid := params.iccid,
+ eid := params.eid,
+ matchingId := params.matchingId,
+ finalProfileStatusIndicator := params.finalProfileStatusIndicator
+ }
+ };
+
+ // Send and receive
+ var JSON_ES2p_Response resp := f_es2plus_http_request(c_path_cancel_order,
"cancelOrder", req);
+
+ // Check result
+ if (params.expectError) {
+ if (not f_es2plus_check_error(resp.cancelOrderResponse.header,
+ params.expectedSubjectCode,
+ params.expectedReasonCode)) {
+ setverdict(fail, "Expected error ", params.expectedSubjectCode,
"/",
+ params.expectedReasonCode, " not received");
+ mtc.stop;
+ }
+ } else {
+ if (not f_es2plus_check_success(resp.cancelOrderResponse.header)) {
+ setverdict(fail, "CancelOrder failed unexpectedly");
+ mtc.stop;
+ }
+ }
+
+ setverdict(pass);
+}
+
+/***********************************************************************
+ * DownloadOrder Test Cases
+ ***********************************************************************/
+
+// Test: DownloadOrder with available ICCID (nominal)
+testcase TC_ES2Plus_DownloadOrder_01_Nominal_ICCID() runs on ES2Plus_ConnHdlr {
+ f_TC_DownloadOrder_Generic({
+ testName := "DownloadOrder - Nominal with ICCID",
+ eid := omit,
+ iccid := c_iccid_test_1,
+ profileType := omit,
+ expectError := false
+ });
+}
+
+// Test: DownloadOrder with profileType (nominal)
+testcase TC_ES2Plus_DownloadOrder_02_Nominal_ProfileType() runs on ES2Plus_ConnHdlr {
+ f_TC_DownloadOrder_Generic({
+ testName := "DownloadOrder - Nominal with ProfileType",
+ eid := c_eid1,
+ iccid := omit,
+ profileType := "Test",
+ expectError := false
+ });
+}
+
+// Test: DownloadOrder Error - ICCID already in use
+testcase TC_ES2Plus_DownloadOrder_Error_01_ICCID_AlreadyInUse() runs on ES2Plus_ConnHdlr
{
+ // This test requires two operations - first reserve, then try again
+ log("=== Test Case: DownloadOrder - Error: ICCID Already in Use ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // First reserve an ICCID and tie this ICCID to an EID immediately.
+ var JSON_ES2p_Request req := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := c_eid1,
+ iccid := c_iccid_test_3,
+ profileType := omit
+ }
+ };
+
+ var JSON_ES2p_Response resp := f_es2plus_http_request(c_path_download_order,
"downloadOrder", req);
+
+ // Verify first request succeeds
+ if (not f_es2plus_check_success(resp.downloadOrderResponse.header)) {
+ setverdict(fail, "Initial DownloadOrder failed");
+ mtc.stop;
+ }
+
+ // Now try to reserve the same ICCID again, but withot an EID. Since the EID is
missing, the SM-DP+ will now
+ // recognize this request as a new request and reject with the expeced error. (Two
requests with the same
+ // input parameters will be interpreted as a resent function call as long as the
outcome is the same)
+ var JSON_ES2p_Request req2 := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := omit,
+ iccid := c_iccid_test_3,
+ profileType := omit
+ }
+ };
+ resp := f_es2plus_http_request(c_path_download_order, "downloadOrder",
req2);
+
+ // Should get error 8.2.1/3.3
+ if (not f_es2plus_check_error(resp.downloadOrderResponse.header, "8.2.1",
"3.3")) {
+ setverdict(fail, "Expected error 8.2.1/3.3 for ICCID already in use");
+ mtc.stop;
+ }
+
+ setverdict(pass);
+}
+
+// Test: DownloadOrder Error - Unknown ICCID
+testcase TC_ES2Plus_DownloadOrder_Error_02_Unknown_ICCID() runs on ES2Plus_ConnHdlr {
+ f_TC_DownloadOrder_Generic({
+ testName := "DownloadOrder - Error: Unknown ICCID",
+ eid := omit,
+ iccid := "8900000000000099999",
+ profileType := omit,
+ expectError := true,
+ expectedSubjectCode := "8.2.1",
+ expectedReasonCode := "3.9"
+ });
+}
+
+/***********************************************************************
+ * ConfirmOrder Test Cases
+ ***********************************************************************/
+
+// Test: ConfirmOrder nominal case
+testcase TC_ES2Plus_ConfirmOrder_01_Nominal() runs on ES2Plus_ConnHdlr {
+ log("=== Test Case: ConfirmOrder - Nominal ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // First, do a DownloadOrder to get an ICCID
+ var JSON_ES2p_Request dl_req := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := omit,
+ iccid := omit,
+ profileType := "Test"
+ }
+ };
+
+ var JSON_ES2p_Response dl_resp := f_es2plus_http_request(c_path_download_order,
"downloadOrder", dl_req);
+
+ if (not f_es2plus_check_success(dl_resp.downloadOrderResponse.header)) {
+ setverdict(fail, "DownloadOrder failed");
+ mtc.stop;
+ }
+
+ var charstring reserved_iccid := dl_resp.downloadOrderResponse.iccid;
+ log("Got ICCID from DownloadOrder: ", reserved_iccid);
+
+ // Now confirm the order
+ f_TC_ConfirmOrder_Generic({
+ testName := "ConfirmOrder - Nominal",
+ iccid := reserved_iccid,
+ eid := omit,
+ matchingId := omit,
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := true,
+ expectError := false
+ });
+}
+
+// Test: ConfirmOrder Error - Unknown Profile
+testcase TC_ES2Plus_ConfirmOrder_Error_01_Unknown_Profile() runs on ES2Plus_ConnHdlr {
+ f_TC_ConfirmOrder_Generic({
+ testName := "ConfirmOrder - Error: Unknown Profile",
+ iccid := "8900000000000099999",
+ eid := omit,
+ matchingId := omit,
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := true,
+ expectError := true,
+ expectedSubjectCode := "8.2.1",
+ expectedReasonCode := "3.9"
+ });
+}
+
+// Test: ConfirmOrder Error - Profile in Available state
+testcase TC_ES2Plus_ConfirmOrder_Error_02_Profile_Available() runs on ES2Plus_ConnHdlr {
+ log("=== Test Case: ConfirmOrder - Error: Profile in Available State
===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // Try to confirm an ICCID that hasn't been ordered
+ var JSON_ES2p_Request req := {
+ confirmOrderRequest := {
+ header := f_es2plus_request_header(),
+ iccid := c_iccid_test_4,
+ eid := omit,
+ matchingId := omit,
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := true
+ }
+ };
+
+ var JSON_ES2p_Response resp := f_es2plus_http_request(c_path_confirm_order,
"confirmOrder", req);
+
+ // Should get an error (exact code may vary)
+ if (resp.confirmOrderResponse.header.functionExecutionStatus.status !=
"Failed") {
+ setverdict(fail, "Expected error for profile in Available state");
+ mtc.stop;
+ }
+
+ setverdict(pass);
+}
+
+// Test: ConfirmOrder Error - Conflicting matchingID
+testcase TC_ES2Plus_ConfirmOrder_Error_03_Conflicting_MatchingID() runs on
ES2Plus_ConnHdlr {
+ log("=== Test Case: ConfirmOrder - Error: Conflicting MatchingID ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // First, create a profile with a specific matchingID
+ var JSON_ES2p_Request dl_req := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := omit,
+ iccid := omit,
+ profileType := "Test"
+ }
+ };
+
+ var JSON_ES2p_Response dl_resp := f_es2plus_http_request(c_path_download_order,
"downloadOrder", dl_req);
+
+ if (not f_es2plus_check_success(dl_resp.downloadOrderResponse.header)) {
+ setverdict(fail, "Setup DownloadOrder failed");
+ mtc.stop;
+ }
+
+ var charstring iccid1 := dl_resp.downloadOrderResponse.iccid;
+
+ // Confirm with a specific matchingID
+ var JSON_ES2p_Request conf_req := {
+ confirmOrderRequest := {
+ header := f_es2plus_request_header(),
+ iccid := iccid1,
+ eid := omit,
+ matchingId := "MATCHING_ID_CONFLICT_TEST",
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := true
+ }
+ };
+
+ var JSON_ES2p_Response conf_resp := f_es2plus_http_request(c_path_confirm_order,
"confirmOrder", conf_req);
+
+ if (not f_es2plus_check_success(conf_resp.confirmOrderResponse.header)) {
+ setverdict(fail, "First ConfirmOrder failed");
+ mtc.stop;
+ }
+
+ // Now create another profile and try to use the same matchingID
+ dl_req.downloadOrderRequest.header := f_es2plus_request_header();
+ dl_resp := f_es2plus_http_request(c_path_download_order, "downloadOrder",
dl_req);
+
+ if (not f_es2plus_check_success(dl_resp.downloadOrderResponse.header)) {
+ setverdict(fail, "Second DownloadOrder failed");
+ mtc.stop;
+ }
+
+ var charstring iccid2 := dl_resp.downloadOrderResponse.iccid;
+
+ // Try to confirm with the same matchingID
+ f_TC_ConfirmOrder_Generic({
+ testName := "ConfirmOrder - Error: Conflicting MatchingID",
+ iccid := iccid2,
+ eid := omit,
+ matchingId := "MATCHING_ID_CONFLICT_TEST",
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := true,
+ expectError := true,
+ expectedSubjectCode := "8.2.6",
+ expectedReasonCode := "3.3"
+ });
+}
+
+// Test: ConfirmOrder Error - Incorrect smdsAddress
+testcase TC_ES2Plus_ConfirmOrder_Error_04_Incorrect_SmdsAddress() runs on
ES2Plus_ConnHdlr {
+ log("=== Test Case: ConfirmOrder - Error: Incorrect smdsAddress ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // First create a profile
+ var JSON_ES2p_Request dl_req := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := omit,
+ iccid := omit,
+ profileType := "Test"
+ }
+ };
+
+ var JSON_ES2p_Response dl_resp := f_es2plus_http_request(c_path_download_order,
"downloadOrder", dl_req);
+
+ if (not f_es2plus_check_success(dl_resp.downloadOrderResponse.header)) {
+ setverdict(fail, "DownloadOrder failed");
+ mtc.stop;
+ }
+
+ var charstring iccid := dl_resp.downloadOrderResponse.iccid;
+
+ // Try to confirm with invalid smdsAddress
+ f_TC_ConfirmOrder_Generic({
+ testName := "ConfirmOrder - Error: Incorrect smdsAddress",
+ iccid := iccid,
+ eid := c_eid1,
+ matchingId := omit,
+ confirmationCode := omit,
+ smdsAddress := "invalid.smds.address",
+ releaseFlag := true,
+ expectError := true,
+ expectedSubjectCode := "8.9",
+ expectedReasonCode := "5.1"
+ });
+}
+
+// Test: ConfirmOrder Error - Missing EID with empty matchingID
+testcase TC_ES2Plus_ConfirmOrder_Error_05_Missing_EID_Empty_MatchingID() runs on
ES2Plus_ConnHdlr {
+ log("=== Test Case: ConfirmOrder - Error: Missing EID with Empty MatchingID
===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // First create a profile
+ var JSON_ES2p_Request dl_req := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := omit,
+ iccid := omit,
+ profileType := "Test"
+ }
+ };
+
+ var JSON_ES2p_Response dl_resp := f_es2plus_http_request(c_path_download_order,
"downloadOrder", dl_req);
+
+ if (not f_es2plus_check_success(dl_resp.downloadOrderResponse.header)) {
+ setverdict(fail, "DownloadOrder failed");
+ mtc.stop;
+ }
+
+ var charstring iccid := dl_resp.downloadOrderResponse.iccid;
+
+ // Try to confirm without EID and with empty matchingID
+ f_TC_ConfirmOrder_Generic({
+ testName := "ConfirmOrder - Error: Missing EID with Empty MatchingID",
+ iccid := iccid,
+ eid := omit,
+ matchingId := "",
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := true,
+ expectError := true,
+ expectedSubjectCode := "8.1.1",
+ expectedReasonCode := "2.2"
+ });
+}
+
+// Test: ConfirmOrder Error - Different EID
+testcase TC_ES2Plus_ConfirmOrder_Error_06_Different_EID() runs on ES2Plus_ConnHdlr {
+ log("=== Test Case: ConfirmOrder - Error: Different EID ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // Create a profile linked to a specific EID
+ var JSON_ES2p_Request dl_req := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := c_eid1,
+ iccid := omit,
+ profileType := "Test"
+ }
+ };
+
+ var JSON_ES2p_Response dl_resp := f_es2plus_http_request(c_path_download_order,
"downloadOrder", dl_req);
+
+ if (not f_es2plus_check_success(dl_resp.downloadOrderResponse.header)) {
+ setverdict(fail, "DownloadOrder failed");
+ mtc.stop;
+ }
+
+ var charstring iccid := dl_resp.downloadOrderResponse.iccid;
+
+ // Try to confirm with a different EID
+ f_TC_ConfirmOrder_Generic({
+ testName := "ConfirmOrder - Error: Different EID",
+ iccid := iccid,
+ eid := c_eid2,
+ matchingId := omit,
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := true,
+ expectError := true,
+ expectedSubjectCode := "8.1.1",
+ expectedReasonCode := "3.10"
+ });
+}
+
+/***********************************************************************
+ * CancelOrder Test Cases
+ ***********************************************************************/
+
+// Test: CancelOrder nominal case
+testcase TC_ES2Plus_CancelOrder_01_Nominal() runs on ES2Plus_ConnHdlr {
+ log("=== Test Case: CancelOrder - Nominal ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // First, create and confirm a profile
+ var JSON_ES2p_Request dl_req := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := c_eid1,
+ iccid := omit,
+ profileType := "Test"
+ }
+ };
+
+ var JSON_ES2p_Response dl_resp := f_es2plus_http_request(c_path_download_order,
"downloadOrder", dl_req);
+
+ if (not f_es2plus_check_success(dl_resp.downloadOrderResponse.header)) {
+ setverdict(fail, "DownloadOrder failed");
+ mtc.stop;
+ }
+
+ var charstring iccid := dl_resp.downloadOrderResponse.iccid;
+ log("Reserved ICCID: ", iccid);
+
+ // Confirm the order
+ var JSON_ES2p_Request conf_req := {
+ confirmOrderRequest := {
+ header := f_es2plus_request_header(),
+ iccid := iccid,
+ eid := omit,
+ matchingId := omit,
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := false // Don't release yet
+ }
+ };
+
+ var JSON_ES2p_Response conf_resp := f_es2plus_http_request(c_path_confirm_order,
"confirmOrder", conf_req);
+
+ if (not f_es2plus_check_success(conf_resp.confirmOrderResponse.header)) {
+ setverdict(fail, "ConfirmOrder failed");
+ mtc.stop;
+ }
+
+ var charstring matching_id := conf_resp.confirmOrderResponse.matchingId;
+ log("Got matchingId: ", matching_id);
+
+ // Now cancel the order
+ f_TC_CancelOrder_Generic({
+ testName := "CancelOrder - Nominal",
+ iccid := iccid,
+ eid := c_eid1,
+ matchingId := matching_id,
+ finalProfileStatusIndicator := "Available",
+ expectError := false
+ });
+}
+
+// Test: CancelOrder Error - Unknown ICCID
+testcase TC_ES2Plus_CancelOrder_Error_01_Unknown_ICCID() runs on ES2Plus_ConnHdlr {
+ f_TC_CancelOrder_Generic({
+ testName := "CancelOrder - Error: Unknown ICCID",
+ iccid := "8900000000000099999",
+ eid := omit,
+ matchingId := omit,
+ finalProfileStatusIndicator := "Available",
+ expectError := true,
+ expectedSubjectCode := "8.2.1",
+ expectedReasonCode := "3.9"
+ });
+}
+
+// Test: CancelOrder Error - Missing EID
+testcase TC_ES2Plus_CancelOrder_Error_02_Missing_EID() runs on ES2Plus_ConnHdlr {
+ log("=== Test Case: CancelOrder - Error: Missing EID ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // First create a profile linked to an EID
+ var JSON_ES2p_Request dl_req := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := c_eid1,
+ iccid := omit,
+ profileType := "Test"
+ }
+ };
+
+ var JSON_ES2p_Response dl_resp := f_es2plus_http_request(c_path_download_order,
"downloadOrder", dl_req);
+
+ if (not f_es2plus_check_success(dl_resp.downloadOrderResponse.header)) {
+ setverdict(fail, "Setup DownloadOrder failed");
+ mtc.stop;
+ }
+
+ var charstring iccid := dl_resp.downloadOrderResponse.iccid;
+
+ // Try to cancel without providing the EID
+ var JSON_ES2p_Request req := {
+ cancelOrderRequest := {
+ header := f_es2plus_request_header(),
+ iccid := iccid,
+ eid := omit, // Missing required EID
+ matchingId := omit,
+ finalProfileStatusIndicator := "Available"
+ }
+ };
+
+ var JSON_ES2p_Response resp := f_es2plus_http_request(c_path_cancel_order,
"cancelOrder", req);
+
+ // Should get an error
+ if (resp.cancelOrderResponse.header.functionExecutionStatus.status !=
"Failed") {
+ setverdict(fail, "Expected error for missing EID");
+ mtc.stop;
+ }
+
+ setverdict(pass);
+}
+
+// Test: CancelOrder Error - Incorrect matchingID
+testcase TC_ES2Plus_CancelOrder_Error_03_Incorrect_MatchingID() runs on ES2Plus_ConnHdlr
{
+ log("=== Test Case: CancelOrder - Error: Incorrect MatchingID ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // Create and confirm a profile
+ var JSON_ES2p_Request dl_req := {
+ downloadOrderRequest := {
+ header := f_es2plus_request_header(),
+ eid := c_eid1,
+ iccid := omit,
+ profileType := "Test"
+ }
+ };
+
+ var JSON_ES2p_Response dl_resp := f_es2plus_http_request(c_path_download_order,
"downloadOrder", dl_req);
+
+ if (not f_es2plus_check_success(dl_resp.downloadOrderResponse.header)) {
+ setverdict(fail, "DownloadOrder failed");
+ mtc.stop;
+ }
+
+ var charstring iccid := dl_resp.downloadOrderResponse.iccid;
+
+ // Confirm to get a matchingID
+ var JSON_ES2p_Request conf_req := {
+ confirmOrderRequest := {
+ header := f_es2plus_request_header(),
+ iccid := iccid,
+ eid := omit,
+ matchingId := omit,
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := false
+ }
+ };
+
+ var JSON_ES2p_Response conf_resp := f_es2plus_http_request(c_path_confirm_order,
"confirmOrder", conf_req);
+
+ if (not f_es2plus_check_success(conf_resp.confirmOrderResponse.header)) {
+ setverdict(fail, "ConfirmOrder failed");
+ mtc.stop;
+ }
+
+ // Try to cancel with wrong matchingID
+ f_TC_CancelOrder_Generic({
+ testName := "CancelOrder - Error: Incorrect MatchingID",
+ iccid := iccid,
+ eid := c_eid1,
+ matchingId := "WRONG_MATCHING_ID",
+ finalProfileStatusIndicator := "Available",
+ expectError := true,
+ expectedSubjectCode := "8.2.6",
+ expectedReasonCode := "3.10"
+ });
+}
+
+// Test: CancelOrder Error - Profile in Available state
+testcase TC_ES2Plus_CancelOrder_Error_04_Profile_Available() runs on ES2Plus_ConnHdlr {
+ log("=== Test Case: CancelOrder - Error: Profile in Available State ===");
+
+ // Initialize
+ f_init_es2plus();
+
+ // Try to cancel an ICCID that hasn't been ordered
+ var JSON_ES2p_Request req := {
+ cancelOrderRequest := {
+ header := f_es2plus_request_header(),
+ iccid := c_iccid_test_5,
+ eid := omit,
+ matchingId := omit,
+ finalProfileStatusIndicator := "Available"
+ }
+ };
+
+ var JSON_ES2p_Response resp := f_es2plus_http_request(c_path_cancel_order,
"cancelOrder", req);
+
+ // Should get an error
+ if (resp.cancelOrderResponse.header.functionExecutionStatus.status !=
"Failed") {
+ setverdict(fail, "Expected error for profile in Available state");
+ mtc.stop;
+ }
+
+ setverdict(pass);
+}
+
+/***********************************************************************
+ * Special Test Cases
+ ***********************************************************************/
+
+// Test with wrong client certificate - should fail
+testcase TC_ES2Plus_Wrong_Client_Cert() runs on ES2Plus_ConnHdlr {
+ // Initialize
+ f_init_es2plus();
+
+ // Build request
+ var JSON_ES2p_DownloadOrderRequest req := {
+ header := f_es2plus_request_header(),
+ iccid := c_iccid_test_1
+ };
+
+ var JSON_ES2p_Request es2p_req := {
+ downloadOrderRequest := req
+ };
+
+ var integer status_code;
+ var charstring response;
+
+ // Temporarily set wrong authentication parameters
+ var integer result := smdpp_Tests.ext_RSPClient_setAuthParams(
+ g_rsp_client_handle,
+ true, // useMutualTLS
+ c_wrong_cert_path, // Using wrong certificate
+ c_wrong_key_path // Using wrong key
+ );
+
+ if (result != 0) {
+ setverdict(fail, "Failed to set wrong auth parameters");
+ mtc.stop;
+ }
+
+ // Send request using WRONG client certificate
+ var octetstring req_enc := enc_JSON_ES2p_Request(es2p_req);
+ var charstring url := c_path_download_order;
+
+ log("ES2+ Request with WRONG certificate to ", url);
+ response := smdpp_Tests.ext_RSPClient_sendHttpsPostWithAuth(
+ g_rsp_client_handle,
+ url,
+ oct2char(req_enc),
+ mp_es2plus_server_port,
+ status_code
+ );
+
+ // Should get 401 Unauthorized or error
+ if (status_code == 401 or status_code == 403 or status_code == 0) {
+ setverdict(pass, "Server correctly rejected wrong client
certificate");
+ } else {
+ setverdict(fail, "Server accepted wrong client certificate! Status: ",
status_code);
+ }
+
+ // Restore correct auth params for cleanup
+ smdpp_Tests.ext_RSPClient_setAuthParams(
+ g_rsp_client_handle,
+ true,
+ c_cert_path,
+ c_key_path
+ );
+}
+
+// Test control section
+control {
+ // Wrong certificate test
+ execute(TC_ES2Plus_Wrong_Client_Cert());
+
+ // DownloadOrder tests
+ execute(TC_ES2Plus_DownloadOrder_01_Nominal_ICCID());
+ execute(TC_ES2Plus_DownloadOrder_02_Nominal_ProfileType());
+ execute(TC_ES2Plus_DownloadOrder_Error_01_ICCID_AlreadyInUse());
+ execute(TC_ES2Plus_DownloadOrder_Error_02_Unknown_ICCID());
+
+ // ConfirmOrder tests
+ execute(TC_ES2Plus_ConfirmOrder_01_Nominal());
+ execute(TC_ES2Plus_ConfirmOrder_Error_01_Unknown_Profile());
+ execute(TC_ES2Plus_ConfirmOrder_Error_02_Profile_Available());
+ execute(TC_ES2Plus_ConfirmOrder_Error_03_Conflicting_MatchingID());
+ execute(TC_ES2Plus_ConfirmOrder_Error_04_Incorrect_SmdsAddress());
+ execute(TC_ES2Plus_ConfirmOrder_Error_05_Missing_EID_Empty_MatchingID());
+ execute(TC_ES2Plus_ConfirmOrder_Error_06_Different_EID());
+
+ // CancelOrder tests
+ execute(TC_ES2Plus_CancelOrder_01_Nominal());
+ execute(TC_ES2Plus_CancelOrder_Error_01_Unknown_ICCID());
+ execute(TC_ES2Plus_CancelOrder_Error_02_Missing_EID());
+ execute(TC_ES2Plus_CancelOrder_Error_03_Incorrect_MatchingID());
+ execute(TC_ES2Plus_CancelOrder_Error_04_Profile_Available());
+}
+
+}
\ No newline at end of file
diff --git a/smdpp/rsp_client.cpp b/smdpp/rsp_client.cpp
index fc4f223..d03a64e 100644
--- a/smdpp/rsp_client.cpp
+++ b/smdpp/rsp_client.cpp
@@ -922,6 +922,17 @@
std::string headers;
};
+ struct PostConfig {
+ bool useMutualTLS = false;
+ std::string clientCertPath;
+ std::string clientKeyPath;
+ bool includeAdminProtocolHeader = false;
+ bool verboseOutput = false;
+ };
+
+ ResponseData postJson(const std::string& url, unsigned int port, const
std::string& jsonData, X509_STORE* store, std::vector<X509*>& certPool,
+ const PostConfig& config);
+
private:
static size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp) {
size_t realSize = size * nmemb;
@@ -998,67 +1009,89 @@
return CURLE_OK;
}
+};
- public:
- ResponseData postJsonWithCustomVerification(const std::string& url, unsigned int
port, const std::string& jsonData, X509_STORE* store,
- std::vector<X509*>& certPool) {
- ResponseData response;
- CURL* curl = curl_easy_init();
+inline HttpClient::ResponseData HttpClient::postJson(const std::string& url, unsigned
int port, const std::string& jsonData, X509_STORE* store,
+ std::vector<X509*>& certPool, const PostConfig& config) {
+ ResponseData response;
+ CURL* curl = curl_easy_init();
- if (!curl) {
- throw std::runtime_error("Failed to initialize CURL");
+ if (!curl) {
+ throw std::runtime_error("Failed to initialize CURL");
+ }
+
+ struct curl_slist* headers = nullptr;
+ if (config.useMutualTLS) {
+ headers = curl_slist_append(headers, "Content-Type:
application/json;charset=UTF-8");
+ headers = curl_slist_append(headers, "Accept: application/json");
+ if (config.includeAdminProtocolHeader) {
+ headers = curl_slist_append(headers, "X-Admin-Protocol: gsma/rsp/v2.5.0");
}
-
- struct curl_slist* headers = nullptr;
+ } else {
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "Accept: application/json");
+ }
- SslCtxData ctxData = { .store = store, .certPool = &certPool, .verifyResult =
false, .errorMessage = "" };
+ SslCtxData ctxData = { .store = store, .certPool = &certPool, .verifyResult = false,
.errorMessage = "" };
- try {
- // Basic CURL setup
- curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
- curl_easy_setopt(curl, CURLOPT_PORT, port);
- curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str());
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response.body);
- curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerCallback);
- curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response.headers);
+ try {
+ // Basic CURL setup
+ curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(curl, CURLOPT_PORT, port);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str());
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response.body);
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerCallback);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response.headers);
- // Enable SSL verification
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
+ // Enable SSL verification
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
- // Use our custom SSL context function and data
- curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, sslCtxFunction);
- curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &ctxData);
+ if (config.useMutualTLS) {
+ if (!config.clientCertPath.empty()) {
+ curl_easy_setopt(curl, CURLOPT_SSLCERT, config.clientCertPath.c_str());
- // Don't disable the default CA bundle - custom certs will be added to it
- // This allows both system CAs and custom test certificates to work
- // together
-
- // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-
- CURLcode res = curl_easy_perform(curl);
-
- if (res != CURLE_OK) {
- throw std::runtime_error(std::string("CURL request failed: ") +
curl_easy_strerror(res));
+ std::string certType = "PEM";
+ if (config.clientCertPath.find(".der") != std::string::npos ||
config.clientCertPath.find(".DER") != std::string::npos) {
+ certType = "DER";
+ }
+ curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, certType.c_str());
+ LOG_DEBUG("Set client certificate: " + config.clientCertPath + "
(type: " + certType + ")");
}
- curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.statusCode);
-
- curl_slist_free_all(headers);
- curl_easy_cleanup(curl);
-
- return response;
- } catch (...) {
- curl_slist_free_all(headers);
- curl_easy_cleanup(curl);
- throw;
+ if (!config.clientKeyPath.empty()) {
+ curl_easy_setopt(curl, CURLOPT_SSLKEY, config.clientKeyPath.c_str());
+ curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
+ LOG_DEBUG("Set client private key: " + config.clientKeyPath);
+ }
}
+
+ // Use our custom SSL context function for server cert verification
+ curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, sslCtxFunction);
+ curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &ctxData);
+
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, config.verboseOutput ? 1L : 0L);
+
+ CURLcode res = curl_easy_perform(curl);
+
+ if (res != CURLE_OK) {
+ throw std::runtime_error(std::string("CURL request failed: ") +
curl_easy_strerror(res));
+ }
+
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.statusCode);
+
+ curl_slist_free_all(headers);
+ curl_easy_cleanup(curl);
+
+ return response;
+ } catch (...) {
+ curl_slist_free_all(headers);
+ curl_easy_cleanup(curl);
+ throw;
}
-};
+}
// Hashed Confirmation Code = SHA256(SHA256(Confirmation Code) | TransactionID)
static std::vector<uint8_t> computeConfirmationCodeHash(const std::string&
confirmationCode, const std::vector<uint8_t>& transactionId) {
@@ -1089,7 +1122,7 @@
public:
RSPClient(const std::string& serverUrl, const unsigned int serverPort, const
std::vector<std::string>& certPath,
const std::vector<std::string>& name_filters = {})
- : m_serverUrl(serverUrl), m_serverPort(serverPort) {
+ : m_serverUrl(serverUrl) {
// OpenSSL 3.0+ initializes automatically
for (auto cpath : certPath) {
@@ -1165,11 +1198,11 @@
try {
m_EID = CertificateUtil::getEID(m_euiccCert.get());
- LOG_INFO("Using EID from certificate: " + m_EID);
+ LOG_DEBUG("Using EID from certificate: " + m_EID);
auto ski = CertificateUtil::getSubjectKeyIdentifier(m_euiccCert.get());
m_euiccSKI = ski;
- LOG_INFO("Using eUICC SKI as PKID: " + HexUtil::bytesToHex(m_euiccSKI));
+ LOG_DEBUG("Using eUICC SKI as PKID: " + HexUtil::bytesToHex(m_euiccSKI));
} catch (const std::exception& e) {
LOG_ERROR("Failed to extract EUICC-specific data: " +
std::string(e.what()));
throw;
@@ -1576,7 +1609,29 @@
m_customTlsCertPath = customTlsCertPath;
}
- std::string sendHttpsPost(const std::string& endpoint, const std::string& body,
int& httpStatusCode) {
+ // Set authentication parameters for mutual TLS auth
+ void setAuthParams(bool useMutualTLS, const std::string& clientCertPath, const
std::string& clientKeyPath) {
+ m_useMutualTLS = useMutualTLS;
+ m_clientCertPath = clientCertPath;
+ m_clientKeyPath = clientKeyPath;
+ LOG_DEBUG("Auth params set: mutualTLS=" + std::to_string(useMutualTLS) +
", cert=" + clientCertPath + ", key=" + clientKeyPath);
+ }
+
+ // Send HTTPS POST without client authentication (for ES9+)
+ std::string sendHttpsPost(const std::string& endpoint, const std::string& body,
int& httpStatusCode, unsigned int portOverride) {
+ return sendHttpsPostUnified(endpoint, body, httpStatusCode, portOverride);
+ }
+
+ // Send HTTPS POST with pre-configured client authentication (for ES2+)
+ std::string sendHttpsPostWithAuth(const std::string& endpoint, const
std::string& body, int& httpStatusCode, unsigned int portOverride) {
+ if (!m_useMutualTLS) {
+ LOG_WARNING("sendHttpsPostWithAuth called but mutual TLS not configured");
+ }
+ return sendHttpsPostUnified(endpoint, body, httpStatusCode, portOverride,
m_useMutualTLS, m_clientCertPath, m_clientKeyPath);
+ }
+
+ std::string sendHttpsPostUnified(const std::string& endpoint, const std::string&
body, int& httpStatusCode, unsigned int portOverride,
+ bool useMutualTLS = false, const std::string& clientCertPath = "",
const std::string& clientKeyPath = "") {
if (!m_httpClient) {
m_httpClient = std::make_unique<HttpClient>();
}
@@ -1584,33 +1639,37 @@
// full URL without port (port set via CURLOPT_PORT)
std::string url = "https://" + m_serverUrl + endpoint;
- HttpClient::ResponseData response;
-
- if (m_useCustomTlsCert) {
- if (!m_customTlsCertPath.empty()) {
- try {
- auto tlsCerts = CertificateUtil::loadCertificateChain(m_customTlsCertPath);
- for (auto& cert : tlsCerts) {
- m_certPool.push_back(std::move(cert));
- }
- LOG_DEBUG("Loaded custom TLS certificate from: " + m_customTlsCertPath);
- } catch (const std::exception& e) {
- LOG_WARNING("Failed to load custom TLS certificate: " +
std::string(e.what()));
- }
- }
-
- std::vector<X509*> rawCertPool;
- for (const auto& cert : m_certPool) {
- rawCertPool.push_back(cert.get());
- }
-
- response = m_httpClient->postJsonWithCustomVerification(url, m_serverPort, body,
nullptr, rawCertPool);
- } else {
- std::vector<X509*> emptyCertPool;
- response = m_httpClient->postJsonWithCustomVerification(url, m_serverPort, body,
nullptr, emptyCertPool);
- LOG_INFO("Using standard CA bundle for TLS verification");
+ // Build certificate pool for server verification
+ std::vector<X509*> rawCertPool;
+ for (const auto& cert : m_certPool) {
+ rawCertPool.push_back(cert.get());
}
+ // Add custom TLS server certificate if configured globally
+ if (m_useCustomTlsCert && !m_customTlsCertPath.empty()) {
+ try {
+ auto tlsCerts = CertificateUtil::loadCertificateChain(m_customTlsCertPath);
+ for (auto& cert : tlsCerts) {
+ rawCertPool.push_back(cert.get());
+ }
+ LOG_DEBUG("Added custom TLS server certificate from: " +
m_customTlsCertPath);
+ } catch (const std::exception& e) {
+ LOG_WARNING("Failed to load custom TLS server certificate: " +
std::string(e.what()));
+ }
+ }
+
+ HttpClient::PostConfig config;
+ config.useMutualTLS = useMutualTLS;
+ config.clientCertPath = clientCertPath;
+ config.clientKeyPath = clientKeyPath;
+ config.includeAdminProtocolHeader = useMutualTLS; // ES2+ requires this header
+ config.verboseOutput = false;
+
+ LOG_DEBUG("Sending " + std::string(useMutualTLS ? "ES2+ request with
mutual TLS" : "HTTPS request") + " to: " + endpoint +
+ " (port: " + std::to_string(portOverride) + ")");
+
+ HttpClient::ResponseData response = m_httpClient->postJson(url, portOverride, body,
nullptr, rawCertPool, config);
+
httpStatusCode = response.statusCode;
LOG_DEBUG("HTTP " + std::to_string(httpStatusCode) + " response, body
length: " + std::to_string(response.body.length()));
@@ -1619,7 +1678,6 @@
}
std::string m_serverUrl;
- unsigned int m_serverPort;
std::unique_ptr<X509, X509Deleter> m_rootCA;
std::vector<std::unique_ptr<X509, X509Deleter>> m_intermediateCA;
@@ -1646,6 +1704,11 @@
std::unique_ptr<HttpClient> m_httpClient;
bool m_useCustomTlsCert = true; // Flag for custom vs public CA
std::string m_customTlsCertPath; // Path to custom TLS certificate
+
+ // Authentication parameters for mutual TLS auth
+ bool m_useMutualTLS = false;
+ std::string m_clientCertPath = "";
+ std::string m_clientKeyPath = "";
};
} // namespace RspCrypto
\ No newline at end of file
diff --git a/smdpp/smdpp_Tests.ttcn b/smdpp/smdpp_Tests.ttcn
index 91797d4..bca2261 100644
--- a/smdpp/smdpp_Tests.ttcn
+++ b/smdpp/smdpp_Tests.ttcn
@@ -32,6 +32,7 @@
import from es9p_Types_JSON all;
import from es2p_Types_JSON all;
import from esx_header_Types_JSON all;
+import from ES2Plus_Tests all;
/* C++ handles only crypto, TTCN-3 handles ASN.1 encoding/decoding most of the time */
@@ -207,6 +208,7 @@
integer clientHandle,
charstring endpoint,
charstring body,
+ integer dport,
out integer statusCode
) return charstring;
@@ -216,6 +218,21 @@
charstring customTlsCertPath
) return integer;
+external function ext_RSPClient_setAuthParams(
+ integer clientHandle,
+ boolean useMutualTLS,
+ charstring clientCertPath,
+ charstring clientKeyPath
+) return integer;
+
+external function ext_RSPClient_sendHttpsPostWithAuth(
+ integer clientHandle,
+ charstring endpoint,
+ charstring body,
+ integer dport,
+ out integer statusCode
+) return charstring;
+
/* RSP Protocol Constants */
const charstring c_oid_rspRole_dp_auth := "2.23.146.1.2.1.4";
const charstring c_oid_rspRole_dp_pb := "2.23.146.1.2.1.5";
@@ -233,6 +250,12 @@
const integer c_hostId_max_length := 16;
const integer c_min_segment_length := 2;
+// ensure canonical DER encoding, using a width 4 bitstring here leads to violations
+const NotificationEvent c_notificationInstall := '1'B; // Bit 0
+const NotificationEvent c_notificationEnable := '10'B; // Bit 1
+const NotificationEvent c_notificationDisable := '100'B; // Bit 2
+const NotificationEvent c_notificationDelete := '1000'B; // Bit 3
+
/* Error injection types for AuthenticateClient testing */
type union AuthClientErrorInjection {
CertificateError cert_error,
@@ -315,7 +338,8 @@
type record smdpp_ConnHdlrPars {
charstring smdp_server_url,
- integer smdp_server_port,
+ integer smdp_es9p_server_port,
+ integer smdp_es2p_server_port,
charstring cert_path,
charstring cert_name_filter,
charstring euicc_cert_path,
@@ -334,7 +358,8 @@
private function f_init_pars() runs on MTC_CT return smdpp_ConnHdlrPars {
var smdpp_ConnHdlrPars pars := {
smdp_server_url := "testsmdpplus1.example.com",
- smdp_server_port := 8000,
+ smdp_es9p_server_port := 8000,
+ smdp_es2p_server_port := 8000,
cert_path := "./sgp26/CertificateIssuer",
cert_name_filter := "", // Load all certificates (both NIST and BRP) or
"NIST" or "BRP"
euicc_cert_path := "./sgp26/eUICC/CERT_EUICC_ECDSA_NIST.der",
@@ -395,7 +420,7 @@
g_rsp_client_handle := ext_RSPClient_create(
g_pars_smdpp.smdp_server_url,
- g_pars_smdpp.smdp_server_port,
+ g_pars_smdpp.smdp_es9p_server_port,
g_pars_smdpp.cert_path,
g_pars_smdpp.cert_name_filter
);
@@ -447,6 +472,137 @@
}
}
+/* HELPER FUNCTIONS FOR ES2+ DYNAMIC PROFILE PROVISIONING */
+
+// Main ES2+ provisioning function - provisions a profile dynamically via ES2+
+private function f_provision_profile_via_es2plus(
+ charstring eid := "89049032123451234512345678901235",
+ boolean with_confirmation_code := false,
+ charstring profile_type := "Test",
+ boolean release_flag := true
+) runs on smdpp_ConnHdlr return charstring {
+
+ // ES2+ constants
+ const charstring c_es2plus_base_path := "/gsma/rsp2/es2plus";
+ const charstring c_path_download_order := c_es2plus_base_path &
"/downloadOrder";
+ const charstring c_path_confirm_order := c_es2plus_base_path &
"/confirmOrder";
+ const charstring c_cert_path := "./test_certs/CERT_MNO_ECDSA_NIST.pem";
+ const charstring c_key_path := "./test_certs/SK_MNO_ECDSA_NIST.pem";
+
+ // Generate unique function call identifier
+ var octetstring rnd_oct := f_rnd_octstring(4);
+ var charstring func_call_id := "TTCN3-" & oct2str(rnd_oct);
+
+ var integer result := smdpp_Tests.ext_RSPClient_setAuthParams(
+ g_rsp_client_handle,
+ true, // useMutualTLS
+ c_cert_path,
+ c_key_path
+ );
+
+ if (result != 0) {
+ setverdict(fail, "Failed to set authentication parameters");
+ mtc.stop;
+ }
+
+ // Step 1: DownloadOrder to reserve ICCID
+ var JSON_ES2p_Request dl_req := {
+ downloadOrderRequest := {
+ header := {
+ functionRequesterIdentifier := "test.operator.com",
+ functionCallIdentifier := func_call_id & "-DL"
+ },
+ eid := eid,
+ iccid := omit,
+ profileType := profile_type
+ }
+ };
+
+ var octetstring req_enc := enc_JSON_ES2p_Request(dl_req);
+ var integer status_code;
+ var charstring response := ext_RSPClient_sendHttpsPostWithAuth(
+ g_rsp_client_handle,
+ c_path_download_order,
+ oct2char(req_enc),
+ g_pars_smdpp.smdp_es2p_server_port,
+ status_code
+ );
+
+ if (status_code != 200) {
+ ext_logError("ES2+ DownloadOrder failed with status: " &
int2str(status_code));
+ return "";
+ }
+
+ var JSON_ES2p_Response dl_resp := dec_JSON_ES2p_Response(char2oct(response),
"downloadOrder");
+ if (dl_resp.downloadOrderResponse.header.functionExecutionStatus.status !=
"Executed-Success") {
+ ext_logError("ES2+ DownloadOrder returned error");
+ return "";
+ }
+
+ var charstring new_iccid := dl_resp.downloadOrderResponse.iccid;
+ ext_logInfo("ES2+ provisioned ICCID: " & new_iccid);
+
+ // Step 2: ConfirmOrder to create profile with matchingId
+ var JSON_ES2p_Request conf_req := {
+ confirmOrderRequest := {
+ header := {
+ functionRequesterIdentifier := "test.operator.com",
+ functionCallIdentifier := func_call_id & "-CF"
+ },
+ iccid := new_iccid,
+ eid := omit,
+ matchingId := omit, // Let server generate
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := release_flag
+ }
+ };
+
+ if (with_confirmation_code) {
+ conf_req.confirmOrderRequest.confirmationCode := "12345678";
+ }
+
+ req_enc := enc_JSON_ES2p_Request(conf_req);
+ response := ext_RSPClient_sendHttpsPostWithAuth(
+ g_rsp_client_handle,
+ c_path_confirm_order,
+ oct2char(req_enc),
+ g_pars_smdpp.smdp_es2p_server_port,
+ status_code
+ );
+
+ if (status_code != 200) {
+ ext_logError("ES2+ ConfirmOrder failed with status: " &
int2str(status_code));
+ return "";
+ }
+
+ var JSON_ES2p_Response conf_resp := dec_JSON_ES2p_Response(char2oct(response),
"confirmOrder");
+ if (conf_resp.confirmOrderResponse.header.functionExecutionStatus.status !=
"Executed-Success") {
+ ext_logError("ES2+ ConfirmOrder returned error");
+ return "";
+ }
+
+ var charstring matching_id := conf_resp.confirmOrderResponse.matchingId;
+ ext_logInfo("ES2+ provisioned profile with matchingId: " &
matching_id);
+
+ // Store confirmation code if requested
+ if (with_confirmation_code) {
+ ext_logInfo("Profile provisioned with confirmation code required");
+ }
+
+ return matching_id;
+}
+
+// Simplified variant for basic profile provisioning
+private function f_provision_simple_profile() runs on smdpp_ConnHdlr return charstring {
+ return f_provision_profile_via_es2plus();
+}
+
+// Variant for profile with confirmation code
+private function f_provision_profile_with_cc(charstring eid :=
"89049032123451234512345678901235") runs on smdpp_ConnHdlr return charstring {
+ return f_provision_profile_via_es2plus(eid, true);
+}
+
/* HELPER FUNCTIONS FOR CERTIFICATE PKID MANAGEMENT */
private function f_get_ci_pkids_list() runs on smdpp_ConnHdlr return
RSPDefinitions.EUICCInfo2.euiccCiPKIdListForVerification {
if (g_pars_smdpp.cert_name_filter == "BRP") {
@@ -721,6 +877,7 @@
g_rsp_client_handle,
g_last_es9p_endpoint,
g_last_es9p_request,
+ g_pars_smdpp.smdp_es9p_server_port,
http_status
);
@@ -770,6 +927,7 @@
g_rsp_client_handle,
g_last_es9p_endpoint,
g_last_es9p_request,
+ g_pars_smdpp.smdp_es9p_server_port,
http_status
);
@@ -903,8 +1061,13 @@
}
private function f_perform_authenticate_client_standard(
- charstring matchingId := "TS48v4_SAIP2.3_BERTLV"
+ charstring matchingId := "" /* Empty string will trigger dynamic
provisioning */
) runs on smdpp_ConnHdlr return AuthenticateClientOk {
+ /* Provision profile dynamically if matchingId not provided */
+ if (matchingId == "") {
+ matchingId := f_provision_simple_profile();
+ }
+
var EUICCInfo2 euiccInfo2 := valueof(ts_EUICCInfo2);
f_set_euicc_pkids(euiccInfo2);
@@ -939,6 +1102,11 @@
) return octetstring
with { extension "prototype(convert) encode(BER:BER_ENCODE_DER)" };
+external function encode_DER_profileInstallationResultData(
+ in ProfileInstallationResultData pdu
+) return octetstring
+with { extension "prototype(convert) encode(BER:BER_ENCODE_DER)" };
+
/* VALIDATION HELPERS */
private function
f_validate_initialise_secure_channel_request(InitialiseSecureChannelRequest iscReq)
@@ -1109,6 +1277,7 @@
g_rsp_client_handle,
g_last_es9p_endpoint,
g_last_es9p_request,
+ g_pars_smdpp.smdp_es9p_server_port,
http_status
);
@@ -1181,7 +1350,7 @@
runs on smdpp_ConnHdlr return ProfileInstallationResult {
var NotificationMetadata notifMeta := {
seqNumber := 1,
- profileManagementOperation := '00000001'B, /* notificationInstall - bit 0
set */
+ profileManagementOperation := c_notificationInstall,
notificationAddress := g_pars_smdpp.smdp_server_url,
iccid := omit
};
@@ -1213,11 +1382,7 @@
};
}
- var ProfileInstallationResult temp_pir := {
- profileInstallationResultData := pirData,
- euiccSignPIR := ''O /* Empty signature for encoding */
- };
- var octetstring full_encoded := enc_ProfileInstallationResult(temp_pir);
+ var octetstring full_encoded := encode_DER_profileInstallationResultData(pirData);
var octetstring euiccSignPIR := ext_RSPClient_signDataWithEUICC(g_rsp_client_handle,
full_encoded);
var ProfileInstallationResult pir := {
@@ -1243,7 +1408,7 @@
private function f_buildAuthenticateClientRequest_unified(
octetstring serverChallenge,
- charstring matchingId := "TS48v4_SAIP2.3_BERTLV",
+ charstring matchingId := "", /* Empty string will trigger dynamic
provisioning */
template (omit) EUICCInfo2 euiccInfo2 := omit, // omit = create default
template (omit) DeviceInfo deviceInfo := omit, // omit = no device info
template (omit) octetstring transactionIdOverride := omit, // omit = use correct ID
@@ -1264,12 +1429,16 @@
var CtxParams1 ctxParams := valueof(ts_ctxParams1);
/* matchingId:
* - "OMIT" -> field omitted from ASN.1 structure
- * - "" -> field is present but empty
+ * - "" -> triggers dynamic provisioning and uses the result
* - any other value -> field contains that value
*/
if (matchingId == "OMIT") {
ctxParams.ctxParamsForCommonAuthentication.matchingId := omit;
} else {
+ /* Provision profile dynamically if matchingId empty */
+ if (matchingId == "") {
+ matchingId := f_provision_simple_profile();
+ }
ctxParams.ctxParamsForCommonAuthentication.matchingId := matchingId;
}
@@ -1313,7 +1482,7 @@
private function f_buildAuthenticateClientRequest(
octetstring serverChallenge,
- charstring matchingId := "TS48v4_SAIP2.3_BERTLV",
+ charstring matchingId := "", /* Empty string will trigger dynamic
provisioning */
template (omit) octetstring transactionIdOverride := omit // error injection - omit
means correct ID
) runs on smdpp_ConnHdlr return RemoteProfileProvisioningRequest {
return f_buildAuthenticateClientRequest_unified(
@@ -1499,7 +1668,12 @@
}
}
-private function f_create_authenticate_client_request(charstring matchingId :=
"TS48v4_SAIP2.3_BERTLV") runs on smdpp_ConnHdlr return
RemoteProfileProvisioningRequest {
+private function f_create_authenticate_client_request(charstring matchingId :=
"") runs on smdpp_ConnHdlr return RemoteProfileProvisioningRequest {
+ /* Provision profile dynamically if matchingId not provided */
+ if (matchingId == "") {
+ matchingId := f_provision_simple_profile();
+ }
+
var EUICCInfo2 euiccInfo2 := valueof(ts_EUICCInfo2);
f_set_euicc_pkids(euiccInfo2);
@@ -1612,7 +1786,10 @@
/* Build CtxParams */
var CtxParams1 ctxParams := valueof(ts_ctxParams1);
- ctxParams.ctxParamsForCommonAuthentication.matchingId :=
"TS48v4_SAIP2.3_BERTLV";
+
+ /* Provision profile dynamically unless error injection overrides it */
+ var charstring provisionedMatchingId := f_provision_simple_profile();
+ ctxParams.ctxParamsForCommonAuthentication.matchingId := provisionedMatchingId;
/* Apply context parameter errors if specified */
if (ischosen(err_injection.ctx_error)) {
@@ -2059,7 +2236,7 @@
var AuthClientErrorTestParams params := {
testName := "AuthenticateClient - Mismatched Transaction ID",
stepDescription := "AuthenticateClient with wrong transaction ID in signed
data",
- matchingId := "TS48v4_SAIP2.3_BERTLV",
+ matchingId := omit, /* Will be provisioned dynamically */
transactionIdOverride := wrongTransactionId,
expectedSubjectCode := "8.10.1",
expectedReasonCode := "3.9",
@@ -2102,8 +2279,11 @@
var EUICCInfo2 euiccInfo2 := valueof(ts_EUICCInfo2);
f_set_euicc_pkids(euiccInfo2);
+ /* Provision profile dynamically */
+ var charstring provisionedMatchingId := f_provision_simple_profile();
+
var CtxParams1 ctxParams := valueof(ts_ctxParams1);
- ctxParams.ctxParamsForCommonAuthentication.matchingId :=
"TS48v4_SAIP2.3_BERTLV";
+ ctxParams.ctxParamsForCommonAuthentication.matchingId := provisionedMatchingId;
var EuiccSigned1 euiccSigned1 := f_create_euicc_signed1(euiccInfo2, ctxParams);
var octetstring euiccSigned1_raw := enc_EuiccSigned1(euiccSigned1);
@@ -2507,11 +2687,16 @@
}
private function f_perform_session_through_getBoundProfilePackage(
- charstring matchingId := "TS48v4_SAIP2.3_BERTLV"
+ charstring matchingId := "" /* Empty string will trigger dynamic
provisioning */
) runs on smdpp_ConnHdlr return GetBoundProfilePackageOk {
+ /* Provision profile dynamically if matchingId not provided */
+ if (matchingId == "") {
+ matchingId := f_provision_simple_profile();
+ }
+
var InitiateAuthenticationOkEs9 authOk := f_initiate_authentication_and_validate();
- var RemoteProfileProvisioningRequest authClientReq :=
f_create_authenticate_client_request();
+ var RemoteProfileProvisioningRequest authClientReq :=
f_create_authenticate_client_request(matchingId);
var RemoteProfileProvisioningResponse authClientResp :=
f_es9p_transceive_success(authClientReq);
var AuthenticateClientOk authClientOk :=
authClientResp.authenticateClientResponseEs9.authenticateClientOk;
@@ -3348,8 +3533,11 @@
euic2.euiccCiPKIdListForVerification := f_get_ci_pkids_for_verification();
euic2.euiccCiPKIdListForSigning := f_get_ci_pkids_for_signing();
+ /* Provision profile dynamically */
+ var charstring provisionedMatchingId := f_provision_simple_profile();
+
var CtxParams1 ctxpar := valueof(ts_ctxParams1);
- ctxpar.ctxParamsForCommonAuthentication.matchingId :=
"TS48v4_SAIP2.3_BERTLV";
+ ctxpar.ctxParamsForCommonAuthentication.matchingId := provisionedMatchingId;
var EuiccSigned1 euiccSig := {
transactionId := g_transactionId,
@@ -3717,7 +3905,7 @@
var NotificationMetadata notifMeta := {
seqNumber := 2, /* Different sequence number from PIR */
- profileManagementOperation := '00000010'B, /* notificationEnable */
+ profileManagementOperation := c_notificationEnable,
notificationAddress := g_pars_smdpp.smdp_server_url,
iccid := omit /* No specific ICCID for this test */
};
@@ -4446,14 +4634,40 @@
/* Test Case: TC_SM-DP+_ES9+.AuthenticateClientNIST - Error Test Sequence #15 // SGP.23
v1.15 section 4.3.14.2.2 Test Sequence #15 */
private function f_TC_AuthenticateClient_Error_15_NoEligibleProfile(charstring id) runs
on smdpp_ConnHdlr {
- f_test_authenticateClient_error({
- testName := "AuthenticateClient - Error: No Eligible Profile",
- stepDescription := "AuthenticateClient when no profile matches device
capabilities",
- matchingId := "AC_NO_ELIGIBLE", /* Profile with incompatible
requirements */
- expectedSubjectCode := "8.2.5",
- expectedReasonCode := "4.3",
- errorDescription := "No Eligible Profile test"
- }, id);
+ /* SGP.23 Section 4.3.14.2.2 Test Sequence #15
+ * The profile has PPR1 set (disabling not allowed) but the eUICC reports PPR1 as
forbidden
+ * because an operational profile is already installed */
+
+ if (not f_rsp_client_init()) {
+ setverdict(fail, "RSP client initialization failed");
+ return;
+ }
+
+ var InitiateAuthenticationOkEs9 authOk := f_initiate_authentication_and_validate();
+
+ ext_logInfo("Step 2: AuthenticateClient with eUICC reporting forbidden
PPR1");
+
+ var EUICCInfo2 euiccInfo2 := valueof(ts_EUICCInfo2);
+ f_set_euicc_pkids(euiccInfo2);
+ /* Set forbiddenProfilePolicyRules to indicate PPR1 is forbidden
+ * PPR1 (bit 1) = "Disabling of this Profile is not allowed"
+ * This is forbidden when an operational profile is already installed */
+ euiccInfo2.forbiddenProfilePolicyRules := '0100'B; /* PPR1 is bit 1 */
+
+ var CtxParams1 ctxParams := valueof(ts_ctxParams1);
+ /* Use AC_PPR1 which has PPR1 set in the profile */
+ ctxParams.ctxParamsForCommonAuthentication.matchingId := "AC_PPR1";
+
+ var EuiccSigned1 euiccSigned1 := f_create_euicc_signed1(euiccInfo2, ctxParams);
+ var octetstring euiccSignature1 := f_sign_euicc_signed1(euiccSigned1);
+
+ var RemoteProfileProvisioningRequest authReq :=
f_build_authenticate_client_request(euiccSigned1, euiccSignature1);
+ var JSON_ESx_FunctionExecutionStatusCodeData err :=
f_es9p_transceive_error(authReq);
+
+ f_validate_error_response(err, "8.2.5", "4.3", "No eligible
Profile for this eUICC/Device");
+ setverdict(pass);
+
+ f_rsp_client_cleanup();
}
/* Test Case: TC_SM-DP+_ES9+.AuthenticateClientNIST - Error Test Sequence #16 // SGP.23
v1.15 section 4.3.14.2.2 Test Sequence #16 */
@@ -5156,7 +5370,7 @@
testcase TC_SM_DP_ES9_InitiateAuthenticationBRP_01_Nominal() runs on MTC_CT {
var smdpp_ConnHdlrPars pars := f_init_pars();
- pars.smdp_server_port := 8001; /* BRP server port */
+ pars.smdp_es9p_server_port := 8001; /* BRP server port */
pars.cert_name_filter := "BRP"; /* Use Brainpool certificates */
pars.euicc_cert_path := "./sgp26/eUICC/CERT_EUICC_ECDSA_BRP.der";
pars.euicc_key_path := "./sgp26/eUICC/SK_EUICC_ECDSA_BRP.pem";
@@ -5169,7 +5383,7 @@
testcase TC_SM_DP_ES9_AuthenticateClientBRP_01_Nominal() runs on MTC_CT {
var smdpp_ConnHdlrPars pars := f_init_pars();
- pars.smdp_server_port := 8001; /* BRP server port */
+ pars.smdp_es9p_server_port := 8001; /* BRP server port */
pars.cert_name_filter := "BRP"; /* Use Brainpool certificates */
pars.euicc_cert_path := "./sgp26/eUICC/CERT_EUICC_ECDSA_BRP.der";
pars.euicc_key_path := "./sgp26/eUICC/SK_EUICC_ECDSA_BRP.pem";
@@ -5182,7 +5396,7 @@
testcase TC_SM_DP_ES9_GetBoundProfilePackageBRP_01_Nominal() runs on MTC_CT {
var smdpp_ConnHdlrPars pars := f_init_pars();
- pars.smdp_server_port := 8001; /* BRP server port */
+ pars.smdp_es9p_server_port := 8001; /* BRP server port */
pars.cert_name_filter := "BRP"; /* Use Brainpool certificates */
pars.euicc_cert_path := "./sgp26/eUICC/CERT_EUICC_ECDSA_BRP.der";
pars.euicc_key_path := "./sgp26/eUICC/SK_EUICC_ECDSA_BRP.pem";
@@ -5195,7 +5409,7 @@
testcase TC_rsp_complete_flow_BRP() runs on MTC_CT {
var smdpp_ConnHdlrPars pars := f_init_pars();
- pars.smdp_server_port := 8001; /* BRP server port */
+ pars.smdp_es9p_server_port := 8001; /* BRP server port */
pars.cert_name_filter := "BRP"; /* Use Brainpool certificates */
pars.euicc_cert_path := "./sgp26/eUICC/CERT_EUICC_ECDSA_BRP.der";
pars.euicc_key_path := "./sgp26/eUICC/SK_EUICC_ECDSA_BRP.pem";
@@ -5208,7 +5422,7 @@
testcase TC_SM_DP_ES9_HandleNotificationBRP() runs on MTC_CT {
var smdpp_ConnHdlrPars pars := f_init_pars();
- pars.smdp_server_port := 8001; /* BRP server port */
+ pars.smdp_es9p_server_port := 8001; /* BRP server port */
pars.cert_name_filter := "BRP"; /* Use Brainpool certificates */
pars.euicc_cert_path := "./sgp26/eUICC/CERT_EUICC_ECDSA_BRP.der";
pars.euicc_key_path := "./sgp26/eUICC/SK_EUICC_ECDSA_BRP.pem";
@@ -5221,7 +5435,7 @@
testcase TC_SM_DP_ES9_CancelSession_After_AuthenticateClientBRP() runs on MTC_CT {
var smdpp_ConnHdlrPars pars := f_init_pars();
- pars.smdp_server_port := 8001; /* BRP server port */
+ pars.smdp_es9p_server_port := 8001; /* BRP server port */
pars.cert_name_filter := "BRP"; /* Use Brainpool certificates */
pars.euicc_cert_path := "./sgp26/eUICC/CERT_EUICC_ECDSA_BRP.der";
pars.euicc_key_path := "./sgp26/eUICC/SK_EUICC_ECDSA_BRP.pem";
@@ -5234,7 +5448,7 @@
testcase TC_SM_DP_ES9_CancelSession_After_GetBoundProfilePackageBRP() runs on MTC_CT {
var smdpp_ConnHdlrPars pars := f_init_pars();
- pars.smdp_server_port := 8001; /* BRP server port */
+ pars.smdp_es9p_server_port := 8001; /* BRP server port */
pars.cert_name_filter := "BRP"; /* Use Brainpool certificates */
pars.euicc_cert_path := "./sgp26/eUICC/CERT_EUICC_ECDSA_BRP.der";
pars.euicc_key_path := "./sgp26/eUICC/SK_EUICC_ECDSA_BRP.pem";
@@ -5245,6 +5459,185 @@
setverdict(pass);
}
+/* ES2+/ES9+ Integration Test Case */
+testcase TC_ES2Plus_ES9Plus_FullFlow() runs on MTC_CT {
+ var smdpp_ConnHdlrPars pars := f_init_pars();
+ var smdpp_ConnHdlr vc_conn;
+ f_init(testcasename());
+ vc_conn := f_start_handler(refers(f_TC_ES2Plus_ES9Plus_FullFlow), pars);
+ vc_conn.done;
+ setverdict(pass);
+}
+
+/* ES2+/ES9+ Integration Test Implementation */
+private function f_TC_ES2Plus_ES9Plus_FullFlow(charstring id) runs on smdpp_ConnHdlr {
+ // ES2+ constants
+ const charstring c_es2plus_base_path := "/gsma/rsp2/es2plus";
+ const charstring c_path_download_order := c_es2plus_base_path &
"/downloadOrder";
+ const charstring c_path_confirm_order := c_es2plus_base_path &
"/confirmOrder";
+ const charstring c_eid1 := "89049032123451234512345678901235";
+ const charstring c_cert_path := "./test_certs/CERT_MNO_ECDSA_NIST.pem";
+ const charstring c_key_path := "./test_certs/SK_MNO_ECDSA_NIST.pem";
+
+ if (not f_rsp_client_init()) {
+ setverdict(fail, "RSP client initialization failed");
+ return;
+ }
+
+ var integer result := smdpp_Tests.ext_RSPClient_setAuthParams(
+ g_rsp_client_handle,
+ true, // useMutualTLS
+ c_cert_path,
+ c_key_path
+ );
+
+ if (result != 0) {
+ setverdict(fail, "Failed to set authentication parameters");
+ mtc.stop;
+ }
+
+ // Step 1: Order profile via ES2+
+
+ // 1.1: dl req
+ var JSON_ES2p_Request dl_req := {
+ downloadOrderRequest := {
+ header := {
+ functionRequesterIdentifier := "test.operator.com",
+ functionCallIdentifier := "01234567890123456789012345678901"
+ },
+ eid := c_eid1,
+ iccid := omit,
+ profileType := "Test"
+ }
+ };
+
+ var octetstring req_enc := enc_JSON_ES2p_Request(dl_req);
+ var integer status_code;
+ var charstring response := ext_RSPClient_sendHttpsPostWithAuth(
+ g_rsp_client_handle,
+ c_path_download_order,
+ oct2char(req_enc),
+ g_pars_smdpp.smdp_es2p_server_port,
+ status_code
+ );
+
+ if (status_code != 200) {
+ setverdict(fail, "DownloadOrder failed with status: ", status_code);
+ f_rsp_client_cleanup();
+ return;
+ }
+
+ var JSON_ES2p_Response dl_resp := dec_JSON_ES2p_Response(char2oct(response),
"downloadOrder");
+ if (dl_resp.downloadOrderResponse.header.functionExecutionStatus.status !=
"Executed-Success") {
+ setverdict(fail, "DownloadOrder returned error");
+ f_rsp_client_cleanup();
+ return;
+ }
+
+ var charstring new_iccid := dl_resp.downloadOrderResponse.iccid;
+
+ // 1.2: confirm
+ var JSON_ES2p_Request conf_req := {
+ confirmOrderRequest := {
+ header := {
+ functionRequesterIdentifier := "test.operator.com",
+ functionCallIdentifier := "02234567890123456789012345678901"
+ },
+ iccid := new_iccid,
+ eid := omit,
+ matchingId := omit,
+ confirmationCode := omit,
+ smdsAddress := omit,
+ releaseFlag := true
+ }
+ };
+
+ req_enc := enc_JSON_ES2p_Request(conf_req);
+ response := ext_RSPClient_sendHttpsPostWithAuth(
+ g_rsp_client_handle,
+ c_path_confirm_order,
+ oct2char(req_enc),
+ g_pars_smdpp.smdp_es2p_server_port,
+ status_code
+ );
+
+ if (status_code != 200) {
+ setverdict(fail, "ConfirmOrder failed with status: ", status_code);
+ f_rsp_client_cleanup();
+ return;
+ }
+
+ var JSON_ES2p_Response conf_resp := dec_JSON_ES2p_Response(char2oct(response),
"confirmOrder");
+ if (conf_resp.confirmOrderResponse.header.functionExecutionStatus.status !=
"Executed-Success") {
+ setverdict(fail, "ConfirmOrder returned error");
+ f_rsp_client_cleanup();
+ return;
+ }
+
+ var charstring matching_id := conf_resp.confirmOrderResponse.matchingId;
+
+ // Step 2: Download profile via ES9+
+
+ // Step 2.1: InitiateAuthentication
+ var RemoteProfileProvisioningRequest initAuthReq :=
f_create_initiate_authentication_request();
+ var RemoteProfileProvisioningResponse initAuthResp :=
f_es9p_transceive_success(initAuthReq);
+
+ if (not f_validate_initiate_authentication_response(initAuthResp)) {
+ setverdict(fail, "InitiateAuthentication validation failed");
+ f_rsp_client_cleanup();
+ return;
+ }
+
+ var InitiateAuthenticationOkEs9 initAuthOk :=
initAuthResp.initiateAuthenticationResponse.initiateAuthenticationOk;
+
+ // Step 2.2: AuthenticateClient with ES2+ matchingId
+
+ var RemoteProfileProvisioningRequest authClientReq :=
f_buildAuthenticateClientRequest(
+ initAuthOk.serverSigned1.serverChallenge,
+ matching_id // Using the matchingId from ES2+ ConfirmOrder
+ );
+
+ var RemoteProfileProvisioningResponse authClientResp :=
f_es9p_transceive_success(authClientReq);
+ var AuthenticateClientOk authClientOk :=
authClientResp.authenticateClientResponseEs9.authenticateClientOk;
+
+ f_validateAuthenticateClientResponse(authClientOk);
+
+ // Handle confirmation code if required
+ if (authClientOk.smdpSigned2.ccRequiredFlag == true) {
+ log("Confirmation code is required for this profile");
+ result := ext_RSPClient_setTransactionId(g_rsp_client_handle,
authClientOk.transactionId);
+ if (result != 0) {
+ setverdict(fail, "Failed to set transaction ID");
+ f_rsp_client_cleanup();
+ return;
+ }
+
+ var charstring confirmationCode := "12345678"; // Default test code
+ result := ext_RSPClient_setConfirmationCode(g_rsp_client_handle,
confirmationCode);
+ if (result != 0) {
+ setverdict(fail, "Failed to set confirmation code");
+ f_rsp_client_cleanup();
+ return;
+ }
+ log("Set confirmation code: ", confirmationCode);
+ }
+
+ // Step 2.3: GetBoundProfilePackage
+
+ var RemoteProfileProvisioningRequest packageReq :=
f_create_get_bound_profile_package_request(
+ authClientOk.smdpSignature2
+ );
+
+ var RemoteProfileProvisioningResponse packageResp :=
f_es9p_transceive_success(packageReq);
+ var GetBoundProfilePackageOk packageOk :=
packageResp.getBoundProfilePackageResponse.getBoundProfilePackageOk;
+
+ f_validate_get_bound_profile_package_response(packageResp);
+
+ f_rsp_client_cleanup();
+
+ setverdict(pass);
+}
+
control {
execute(TC_rsp_complete_flow());
@@ -5524,6 +5917,9 @@
execute(TC_SM_DP_ES9_CancelSession_After_GetBoundProfilePackageBRP());
/* Complete flow with BRP certificates */
execute(TC_rsp_complete_flow_BRP());
+
+ /* ES2+/ES9+ Integration Test */
+ execute(TC_ES2Plus_ES9Plus_FullFlow());
}
}
\ No newline at end of file
diff --git a/smdpp/smdpp_Tests_Functions.cc b/smdpp/smdpp_Tests_Functions.cc
index 52b6c6b..7bd08e3 100644
--- a/smdpp/smdpp_Tests_Functions.cc
+++ b/smdpp/smdpp_Tests_Functions.cc
@@ -534,12 +534,12 @@
// Derive BSP session keys using X9.63 KDF (from_kdf)
LOG_DEBUG("BSP KDF inputs:");
LOG_DEBUG(" Shared secret: " + HexUtil::bytesToHex(sharedSecretVec));
- LOG_DEBUG(" Key type: " + std::to_string(keyType.get_long_long_val()) +
+ LOG_DEBUG(" Key type: " + std::to_string(keyType.get_long_long_val()) +
" (0x" +
HexUtil::bytesToHex({static_cast<uint8_t>(keyType.get_long_long_val())}) +
")");
LOG_DEBUG(" Key length: " + std::to_string(keyLength.get_long_long_val()));
LOG_DEBUG(" Host ID: " + HexUtil::bytesToHex(hostIdVec));
LOG_DEBUG(" EID: " + HexUtil::bytesToHex(eidVec));
-
+
auto bsp = BspCryptoNS::BspCrypto::from_kdf(sharedSecretVec,
static_cast<uint8_t>(keyType.get_long_long_val()),
static_cast<uint8_t>(keyLength.get_long_long_val()),
@@ -574,19 +574,6 @@
});
}
-CHARSTRING ext__RSPClient__sendHttpsPost(const INTEGER& clientHandle, const
CHARSTRING& endpoint,
- const CHARSTRING& body, INTEGER& statusCode) {
- statusCode = INTEGER(0);
-
- return with_client(clientHandle, "ext__RSPClient__sendHttpsPost",
CHARSTRING(""), [&](RSPClient* client) {
- int httpStatus = 0;
- std::string response =
- client->sendHttpsPost(charstring_to_string(endpoint), charstring_to_string(body),
httpStatus);
- statusCode = INTEGER(httpStatus);
- return string_to_charstring(response);
- });
-}
-
INTEGER ext__RSPClient__configureHttpClient(const INTEGER& clientHandle, const
BOOLEAN& useCustomTlsCert,
const CHARSTRING& customTlsCertPath) {
return with_client(clientHandle, "ext__RSPClient__configureHttpClient",
INTEGER(-1), [&](RSPClient* client) {
@@ -599,4 +586,58 @@
});
}
+INTEGER ext__RSPClient__setAuthParams(const INTEGER& clientHandle, const BOOLEAN&
useMutualTLS,
+ const CHARSTRING& clientCertPath, const CHARSTRING& clientKeyPath) {
+ return with_client(clientHandle, "ext__RSPClient__setAuthParams", INTEGER(-1),
[&](RSPClient* client) {
+ bool useMutual = static_cast<bool>(useMutualTLS);
+ std::string certPath = charstring_to_string(clientCertPath);
+ std::string keyPath = charstring_to_string(clientKeyPath);
+
+ client->setAuthParams(useMutual, certPath, keyPath);
+ LOG_DEBUG("Set auth params - mutual TLS: " + std::string(useMutual ?
"true" : "false") +
+ ", cert: " + certPath + ", key: " + keyPath);
+ return INTEGER(0);
+ });
+}
+
+CHARSTRING ext__RSPClient__sendHttpsPost(const INTEGER& clientHandle, const
CHARSTRING& endpoint,
+ const CHARSTRING& body, const INTEGER& port, INTEGER& statusCode)
{
+ statusCode = INTEGER(0);
+
+ return with_client(clientHandle, "ext__RSPClient__sendHttpsPostSimple",
CHARSTRING(""), [&](RSPClient* client) {
+ int httpStatus = 0;
+ int dstport = static_cast<int>(port);
+
+ std::string response = client->sendHttpsPost(
+ charstring_to_string(endpoint),
+ charstring_to_string(body),
+ httpStatus,
+ dstport
+ );
+
+ statusCode = INTEGER(httpStatus);
+ return string_to_charstring(response);
+ });
+}
+
+CHARSTRING ext__RSPClient__sendHttpsPostWithAuth(const INTEGER& clientHandle, const
CHARSTRING& endpoint,
+ const CHARSTRING& body, const INTEGER& port, INTEGER& statusCode) {
+ statusCode = INTEGER(0);
+
+ return with_client(clientHandle, "ext__RSPClient__sendHttpsPostWithAuth",
CHARSTRING(""), [&](RSPClient* client) {
+ int httpStatus = 0;
+ int dstport = static_cast<int>(port);
+
+ std::string response = client->sendHttpsPostWithAuth(
+ charstring_to_string(endpoint),
+ charstring_to_string(body),
+ httpStatus,
+ dstport
+ );
+
+ statusCode = INTEGER(httpStatus);
+ return string_to_charstring(response);
+ });
+}
+
} // namespace smdpp__Tests
\ No newline at end of file
diff --git a/smdpp/test_certs/CERT_MNO_ECDSA_NIST.pem
b/smdpp/test_certs/CERT_MNO_ECDSA_NIST.pem
new file mode 100644
index 0000000..f626b2e
--- /dev/null
+++ b/smdpp/test_certs/CERT_MNO_ECDSA_NIST.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBpDCCAUugAwIBAgIUSeaTfsRONLZXImVBtISYmkx2cuYwCgYIKoZIzj0EAwIw
+QTELMAkGA1UEBhMCVVMxFjAUBgNVBAoMDVRlc3QgT3BlcmF0b3IxGjAYBgNVBAMM
+EXRlc3Qub3BlcmF0b3IuY29tMB4XDTI1MDgwNDE0MzU1OFoXDTI2MDgwNDE0MzU1
+OFowQTELMAkGA1UEBhMCVVMxFjAUBgNVBAoMDVRlc3QgT3BlcmF0b3IxGjAYBgNV
+BAMMEXRlc3Qub3BlcmF0b3IuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
+ml9gJuY6zXQS+lAjVTcRfCQpjfHqwFozg3NeyXjjf1DzKV7z+1OxI4wyjVEWDbXR
+y3LuqA0IJn9G4OlpPsQx9qMhMB8wHQYDVR0OBBYEFPrtOcEs4Pqf0ehIWfOwEnqD
+Z82OMAoGCCqGSM49BAMCA0cAMEQCIGGr25Njje34dwlkmRO1fEI1DfjzstTmBBCr
+WruTnbbkAiBsMnKf7EZ937+WIz4Re+lyEtQjEERuT9AUd/yTHqEY0Q==
+-----END CERTIFICATE-----
diff --git a/smdpp/test_certs/SK_MNO_ECDSA_NIST.pem
b/smdpp/test_certs/SK_MNO_ECDSA_NIST.pem
new file mode 100644
index 0000000..3c5e5b8
--- /dev/null
+++ b/smdpp/test_certs/SK_MNO_ECDSA_NIST.pem
@@ -0,0 +1,8 @@
+-----BEGIN EC PARAMETERS-----
+BggqhkjOPQMBBw==
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJKEROO3uDeVP5/iKmGuLK56kIdksG8PISUiCfq58HDfoAoGCCqGSM49
+AwEHoUQDQgAEml9gJuY6zXQS+lAjVTcRfCQpjfHqwFozg3NeyXjjf1DzKV7z+1Ox
+I4wyjVEWDbXRy3LuqA0IJn9G4OlpPsQx9g==
+-----END EC PRIVATE KEY-----
--
To view, visit
https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/41120?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmo-ttcn3-hacks
Gerrit-Branch: master
Gerrit-Change-Id: I2865e016974d7d7a03e00a7795a42f573b147a4b
Gerrit-Change-Number: 41120
Gerrit-PatchSet: 1
Gerrit-Owner: Hoernchen <ewild(a)sysmocom.de>