pespin has submitted this change. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/37065?usp=email )
Change subject: ttcn3-asterisk: Validate Asterisk generates correct AKAv1-MD5 digest response ......................................................................
ttcn3-asterisk: Validate Asterisk generates correct AKAv1-MD5 digest response
Values taken from pcap "register-unregister-example.pcapng" in SYS#6960 against testing Kamailio as IMS Core.
Related: SYS#6960 Change-Id: I0c6a6ff86a8b32383f17487dc9eb46d0bf2bf7c2 --- M asterisk/Asterisk_Tests.ttcn M asterisk/IMS_ConnectionHandler.ttcn M asterisk/SIP_ConnectionHandler.ttcn M library/SIP_Templates.ttcn 4 files changed, 163 insertions(+), 53 deletions(-)
Approvals: laforge: Looks good to me, but someone else must approve Jenkins Builder: Verified pespin: Looks good to me, approved
diff --git a/asterisk/Asterisk_Tests.ttcn b/asterisk/Asterisk_Tests.ttcn index ba7cb38..a96e66a 100644 --- a/asterisk/Asterisk_Tests.ttcn +++ b/asterisk/Asterisk_Tests.ttcn @@ -66,13 +66,6 @@
const charstring broadcast_sip_extension := "0500";
-/* TODO: need to find correct RES, CK, IK, values for: - * Response: "a19de225d030e6e0ec20f6de5f9dd80d" - * CNonce Value: "9a6460bcc3c84a3eac69455a93b67a77" - */ -const charstring c_auth_res := "a19de225d030e6e0"; -const charstring c_auth_ck := "9a6460bcc3c84a3eac69455a93b67a77"; -const charstring c_auth_ik := "5238297dfcca759bd05d48ff49bc63fa";
function f_init_ConnHdlrPars(integer idx := 1) runs on test_CT return SIPConnHdlrPars { var template (value) CallPars cp := t_CallPars(mp_local_sip_host, 1234 + 2*idx); @@ -384,15 +377,17 @@ /* Trigger registration: */ f_ami_action_PJSIPRegister(AMI_CLIENT, mp_volte_ims_outbound_registration);
- var charstring rand_str := oct2str(pars.subscr.rand); - var charstring autn_str := oct2str(pars.subscr.autn); + var charstring rand_str := oct2str(pars.subscr.auth.rand); + var charstring autn_str := oct2str(pars.subscr.auth.autn); AMI_CLIENT.receive(tr_AMI_Event_AuthRequest(mp_volte_ims_outbound_registration, rand := pattern @nocase rand_str, autn := pattern @nocase autn_str));
f_ami_action_AuthResponse_RES(AMI_CLIENT, mp_volte_ims_outbound_registration, - c_auth_res, c_auth_ck, c_auth_ik); + f_str_tolower(oct2str(pars.subscr.auth.res)), + f_str_tolower(oct2str(pars.subscr.auth.ck)), + f_str_tolower(oct2str(pars.subscr.auth.ik)));
AMI_CLIENT.receive(tr_AMI_Event_Registry(f_sip_SipAddr_to_str(pars.subscr.local_sip_record), "sip:" & mp_ims_domain, diff --git a/asterisk/IMS_ConnectionHandler.ttcn b/asterisk/IMS_ConnectionHandler.ttcn index 9a37b05..f10e2aa 100644 --- a/asterisk/IMS_ConnectionHandler.ttcn +++ b/asterisk/IMS_ConnectionHandler.ttcn @@ -56,6 +56,14 @@ } type record of IMS_ConnHdlr IMS_ConnHdlrList;
+type record IMS_AuthVector { + OCT16 rand, + OCT16 autn, + OCT8 res, + OCT16 ck, + OCT16 ik +} + type record IMS_ConnHdlrSubscrPars { charstring remote_sip_host optional, uint16_t remote_sip_port optional, @@ -65,8 +73,7 @@ charstring msisdn, /* Expected User-Location-Info in P-Access-Network-Info */ charstring uli_str, - octetstring rand, - octetstring autn, + IMS_AuthVector auth, charstring ipsec_auth_key, integer ipsec_local_spi_c, integer ipsec_local_spi_s, @@ -158,10 +165,15 @@ password := password, msisdn := msisdn, uli_str := "2380100010000101", - /* The Nonce field is the Base64 encoded version of the RAND value and concatenated with the AUTN: */ - rand := '14987631f65f8e3788a0798b6ebcd08e'O, - autn := 'f6e19a7ccb028000a06b19c9544516e5'O, - ipsec_auth_key := "0x5238297dfcca759bd05d48ff49bc63fa00000000", + auth := { + /* The Nonce field is the Base64 encoded version of the RAND value and concatenated with the AUTN: */ + rand := 'd5d5de2bce418d7865ed7fa6956618a2'O, + autn := 'd42e61db5f15800067393a5b7691a227'O, + res := '6f2556bbe4366ab1'O, + ck := '0b389d08c833991734936bec55cac800'O, + ik := '17141862125bd30c81c4224391a0909a'O + }, + ipsec_auth_key := "0x17141862125bd30c81c4224391a0909a00000000", ipsec_local_spi_c := 4142, ipsec_local_spi_s := 4143, ipsec_remote_spi_c := omit, @@ -250,8 +262,7 @@ /* HTTP Digest Authentication Using AKA (AKAv1-MD5): RFC 3310 */ function f_tr_Authorization_AKAv1MD5(WwwAuthenticate www_authenticate, charstring username, - charstring uri, - integer nc_int := 1) + charstring uri) return template (present) Authorization { var CommaParam_List digestCln; var template (present) Authorization authorization; @@ -280,6 +291,12 @@ return authorization; }
+private function f_ims_validate_Authorization_AKAv1MD5_Response(Authorization authorization, charstring method) +runs on IMS_ConnHdlr { + f_sip_digest_validate_Authorization_AKAv1MD5(authorization, method, g_pars.subscr.auth.res); +} + + private function f_ims_validate_register_contact(Contact rx_contact) { /* IMS contact shows up like this: @@ -532,7 +549,8 @@ ts_Param("realm", f_sip_str_quote(g_pars.realm)), ts_Param("qop", f_sip_str_quote("auth")), ts_Param("algorithm", "AKAv1-MD5"), - ts_Param("nonce", f_sip_str_quote(f_nonce_from_rand_autn(g_pars.subscr.rand, g_pars.subscr.autn))) + ts_Param("nonce", f_sip_str_quote(f_nonce_from_rand_autn(g_pars.subscr.auth.rand, + g_pars.subscr.auth.autn))) /* "opaque not needed in IMS "*/ }; wwwAuthenticate := ts_WwwAuthenticate( { ts_Challenge_digestCln(digestCln) } ) @@ -569,17 +587,9 @@ SIP.send(tx_resp);
/* Now we should receive a new REGISTER over ipsec: */ - - /* TODO: Generate expected Authoritzation based on AKAv1-MD5: */ - /*authorization := f_sip_digest_gen_Authorization(valueof(wwwAuthenticate), - g_pars.user, g_pars.subscr.password, - "REGISTER", - f_sip_SipUrl_to_str(g_pars.subscr.registrar_sip_record.addr.nameAddr.addrSpec)) - */ authorization := f_tr_Authorization_AKAv1MD5(valueof(wwwAuthenticate), g_pars.subscr.imsi & "@" & g_pars.realm, f_sip_SipUrl_to_str(g_pars.registrar_sip_req_uri)); - /* TODO: match Authorization from above: */ exp_req := tr_SIP_REGISTER(g_pars.registrar_sip_req_uri, ?, @@ -607,6 +617,9 @@ userAgent := omit); SIP.send(tx_resp);
+ /* Validate Digest Response: */ + f_ims_validate_Authorization_AKAv1MD5_Response(g_rx_sip_req.msgHeader.authorization, "REGISTER"); + /* Validate P-Access-Network-Info: */ f_ims_validate_register_P_Access_Network_info(g_rx_sip_req, exp_present := true);
diff --git a/asterisk/SIP_ConnectionHandler.ttcn b/asterisk/SIP_ConnectionHandler.ttcn index a14dd03..cd50be6 100644 --- a/asterisk/SIP_ConnectionHandler.ttcn +++ b/asterisk/SIP_ConnectionHandler.ttcn @@ -296,10 +296,10 @@ as_SIP_expect_resp(exp);
/* Digest Auth: RFC 2617 */ - authorization := f_sip_digest_gen_Authorization(g_rx_sip_resp.msgHeader.wwwAuthenticate, - g_pars.user, g_pars.password, - "REGISTER", - f_sip_SipUrl_to_str(g_pars.registrar_sip_req_uri)) + authorization := f_sip_digest_gen_Authorization_MD5(g_rx_sip_resp.msgHeader.wwwAuthenticate, + g_pars.user, g_pars.password, + "REGISTER", + f_sip_SipUrl_to_str(g_pars.registrar_sip_req_uri))
/* New transaction: */ g_pars.registrar_sip_seq_nr := g_pars.registrar_sip_seq_nr + 1; @@ -381,7 +381,7 @@ as_SIP_expect_resp(exp);
/* Digest Auth: RFC 2617 */ - req.msgHeader.authorization := f_sip_digest_gen_Authorization( + req.msgHeader.authorization := f_sip_digest_gen_Authorization_MD5( g_rx_sip_resp.msgHeader.wwwAuthenticate, g_pars.user, g_pars.password, "INVITE", diff --git a/library/SIP_Templates.ttcn b/library/SIP_Templates.ttcn index f251767..77d4ee0 100644 --- a/library/SIP_Templates.ttcn +++ b/library/SIP_Templates.ttcn @@ -1275,7 +1275,17 @@ f_sip_str_unquote(realm) & ":" & password; var charstring digestA1 := f_str_tolower(f_calculateMD5(A1)); - log("A1: md5('", A1, "') = ", digestA1); + log("A1: md5(", A1, ") = ", digestA1); + return digestA1; +} + +/* RFC 3310: Same as f_sip_digest_A1(), but using an octet buffer as password (AKAv1-MD5, pwd=RES) */ +function f_sip_digest_A1_octpwd(charstring user, charstring realm, octetstring password) return charstring { + var charstring A1pre := f_sip_str_unquote(user) & ":" & + f_sip_str_unquote(realm) & ":"; + var octetstring A1 := char2oct(A1pre) & password; + var charstring digestA1 := f_str_tolower(oct2str(f_calculateMD5_oct(A1))); + log("A1-octpwd: md5(", A1, ") = ", digestA1); return digestA1; }
@@ -1284,7 +1294,7 @@
var charstring A2 := method & ":" & uri var charstring digestA2 := f_str_tolower(f_calculateMD5(A2)); - log("A2: md5('", A2, "') = ", digestA2); + log("A2: md5(", A2, ") = ", digestA2); return digestA2; }
@@ -1298,7 +1308,7 @@ f_sip_str_unquote(qop) & ":" & digestA2; var charstring req_digest := f_sip_digest_KD(digestA1, digest_data); - log("Request-Digest: md5('", digestA1, ":", digest_data ,"') = ", req_digest); + log("Request-Digest: md5("" & digestA1 & ":" & digest_data & "") = " & """ & req_digest & """); return req_digest; }
@@ -1310,7 +1320,41 @@ }
/* Digest Auth: RFC 2617 */ -function f_sip_digest_gen_Authorization(WwwAuthenticate www_authenticate, +function f_sip_digest_gen_Authorization_Response_MD5(charstring user, charstring realm, charstring password, + charstring method, charstring uri, charstring qop, + charstring nonce, charstring cnonce, charstring nc) +return charstring { + /* RFC 2617 3.2.2.2 A1 */ + var charstring digestA1 := f_sip_digest_A1(user, realm, password); + + /* RFC 2617 3.2.2.3 A2 */ + var charstring digestA2 := f_sip_digest_A2(method, uri); + + /* RFC 2617 3.2.2.1 Request-Digest */ + var charstring req_digest := f_sip_digest_RequestDigest(digestA1, nonce, + nc, cnonce, + qop, digestA2); + return req_digest; +} + +/* Digest Auth: RFC 2617 */ +function f_sip_digest_gen_Authorization_Response_AKAv1MD5(charstring user, charstring realm, octetstring password, + charstring method, charstring uri, charstring qop, + charstring nonce, charstring cnonce, charstring nc) +return charstring { + /* RFC 2617 3.2.2.2 A1 */ + var charstring digestA1 := f_sip_digest_A1_octpwd(user, realm, password); + /* RFC 2617 3.2.2.3 A2 */ + var charstring digestA2 := f_sip_digest_A2(method, uri); + + /* RFC 2617 3.2.2.1 Request-Digest */ + var charstring req_digest := f_sip_digest_RequestDigest(digestA1, nonce, + nc, cnonce, + qop, digestA2); + return req_digest; +} + +function f_sip_digest_gen_Authorization_MD5(WwwAuthenticate www_authenticate, charstring user, charstring password, charstring method, charstring uri, charstring cnonce := "0a4f113b", integer nc_int := 1) return Authorization { @@ -1343,17 +1387,13 @@ Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Unexpected qop: ", qop)); } var charstring selected_qop := "auth"; - - /* RFC 2617 3.2.2.2 A1 */ - var charstring digestA1 := f_sip_digest_A1(user, realm, password); - /* RFC 2617 3.2.2.3 A2 */ - var charstring digestA2 := f_sip_digest_A2(method, uri); - - /* RFC 2617 3.2.2.1 Request-Digest */ var charstring nc := f_str_tolower(hex2str(int2hex(nc_int, 8))); - var charstring req_digest := f_sip_digest_RequestDigest(digestA1, nonce, - nc, cnonce, - selected_qop, digestA2); + + var charstring req_digest; + req_digest := + f_sip_digest_gen_Authorization_Response_MD5(user, realm, password, + method, uri, selected_qop, + nonce, cnonce, nc);
cred := ts_Credentials_DigestResponseMD5(user, realm, nonce, uri, req_digest, @@ -1363,6 +1403,55 @@ return valueof(authorization); }
+/* RFC 3310: Same as f_sip_digest_validate_Authorization_MD5(), but using an octet buffer as password (AKAv1-MD5, pwd=RES) */ +function f_sip_digest_validate_Authorization_AKAv1MD5(Authorization authorization, charstring method, octetstring password) { + var CommaParam_List auth_pars := authorization.body.digestResponse; + var template (omit) GenericParam rx_param; + + rx_param := f_sip_param_find(auth_pars, "algorithm"); + if (istemplatekind(rx_param, "omit")) { + /* Assume MD5 if not set */ + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Algorithm param not present"); + } else { + var charstring algorithm := valueof(rx_param.paramValue); + if (f_strstr(algorithm, "AKAv1-MD5") == -1) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Unexpected algorithm: ", algorithm)); + } + } + + var charstring user := f_sip_param_get_value_present_or_fail(auth_pars, "username"); + var charstring realm := f_sip_param_get_value_present_or_fail(auth_pars, "realm"); + var charstring uri := f_sip_param_get_value_present_or_fail(auth_pars, "uri"); + var charstring nonce := f_sip_param_get_value_present_or_fail(auth_pars, "nonce"); + var charstring qop := f_sip_param_get_value_present_or_fail(auth_pars, "qop"); + var charstring response := f_sip_param_get_value_present_or_fail(auth_pars, "response"); + var charstring cnonce := f_sip_param_get_value_present_or_fail(auth_pars, "cnonce"); + var charstring nc := f_sip_param_get_value_present_or_fail(auth_pars, "nc"); + + if (f_strstr(qop, "auth") == -1) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Unexpected qop: ", qop)); + } + + var charstring exp_response := + f_sip_digest_gen_Authorization_Response_AKAv1MD5(f_sip_str_unquote(user), + f_sip_str_unquote(realm), + password, + method, + f_sip_str_unquote(uri), + qop, + f_sip_str_unquote(nonce), + f_sip_str_unquote(cnonce), + nc); + + response := f_sip_str_unquote(response); + if (response != exp_response) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Wrong digest response: ", response, + " vs exp: ", exp_response, ". Params: ", auth_pars)); + } +} + /* RFC 2617 3.5 Example */ function f_sip_digest_selftest() { /* @@ -1400,13 +1489,13 @@ ts_WwwAuthenticate( { ts_Challenge_digestCln(digestCln) } )
var Authorization authorization := - f_sip_digest_gen_Authorization(valueof(www_authenticate), - "Mufasa", - "Circle Of Life", - "GET", - "/dir/index.html", - cnonce := "0a4f113b", - nc_int := 1); + f_sip_digest_gen_Authorization_MD5(valueof(www_authenticate), + "Mufasa", + "Circle Of Life", + "GET", + "/dir/index.html", + cnonce := "0a4f113b", + nc_int := 1);
var CommaParam_List digestResp := authorization.body.digestResponse; f_sip_param_match_value_or_fail(digestResp, "realm", f_sip_str_quote("testrealm@host.com"));