osmith has submitted this change. ( 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(-)
Approvals: laforge: Looks good to me, but someone else must approve Jenkins Builder: Verified dexter: Looks good to me, approved
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::vectorstd::string& certPath, const std::vectorstd::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 40d663e..b2842a1 100644 --- a/smdpp/smdpp_Tests.ttcn +++ b/smdpp/smdpp_Tests.ttcn @@ -33,6 +33,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 */
@@ -208,6 +209,7 @@ integer clientHandle, charstring endpoint, charstring body, + integer dport, out integer statusCode ) return charstring;
@@ -217,6 +219,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"; @@ -234,6 +251,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, @@ -316,7 +339,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, @@ -335,7 +359,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", @@ -396,7 +421,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 ); @@ -448,6 +473,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") { @@ -722,6 +878,7 @@ g_rsp_client_handle, g_last_es9p_endpoint, g_last_es9p_request, + g_pars_smdpp.smdp_es9p_server_port, http_status );
@@ -771,6 +928,7 @@ g_rsp_client_handle, g_last_es9p_endpoint, g_last_es9p_request, + g_pars_smdpp.smdp_es9p_server_port, http_status );
@@ -904,8 +1062,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);
@@ -940,6 +1103,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) @@ -1110,6 +1278,7 @@ g_rsp_client_handle, g_last_es9p_endpoint, g_last_es9p_request, + g_pars_smdpp.smdp_es9p_server_port, http_status );
@@ -1182,7 +1351,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 }; @@ -1214,11 +1383,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 := { @@ -1244,7 +1409,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 @@ -1265,12 +1430,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; }
@@ -1314,7 +1483,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( @@ -1500,7 +1669,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);
@@ -1613,7 +1787,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)) { @@ -2060,7 +2237,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", @@ -2103,8 +2280,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); @@ -2508,11 +2688,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;
@@ -3349,8 +3534,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, @@ -3718,7 +3906,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 */ }; @@ -4447,14 +4635,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 */ @@ -5157,7 +5371,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"; @@ -5170,7 +5384,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"; @@ -5183,7 +5397,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"; @@ -5196,7 +5410,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"; @@ -5209,7 +5423,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"; @@ -5222,7 +5436,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"; @@ -5235,7 +5449,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"; @@ -5246,6 +5460,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());
@@ -5525,6 +5918,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-----