diff options
author | Pau Espin Pedrol <pespin@sysmocom.de> | 2024-04-02 12:56:26 +0200 |
---|---|---|
committer | Pau Espin Pedrol <pespin@sysmocom.de> | 2024-04-08 17:36:18 +0200 |
commit | 05eaa1a531e6d45dc2cc7c52b59f667183d53fc0 (patch) | |
tree | 23421aaa44a97f38cd8727f998ddacafb9362fed /library | |
parent | 95058fd76c0bea7eb2b9d3b13b103b365bab3acb (diff) |
asterisk: Implement and test SIP Digest Authorization
Related: SYS#6782
Change-Id: Ib469f1906927a3f246876040086ff115fbf4c032
Diffstat (limited to 'library')
-rw-r--r-- | library/SIP_Templates.ttcn | 519 |
1 files changed, 513 insertions, 6 deletions
diff --git a/library/SIP_Templates.ttcn b/library/SIP_Templates.ttcn index fc8e23f6..7cb9d68d 100644 --- a/library/SIP_Templates.ttcn +++ b/library/SIP_Templates.ttcn @@ -1,7 +1,11 @@ module SIP_Templates { import from SIPmsg_Types all; +import from TCCConversion_Functions all; +import from TCCOpenSecurity_Functions all; +import from Native_Functions all; import from Osmocom_Types all; +import from Misc_Helpers all; /* wrapper type to encapsulate the Addr_Union + parameter list used in From, To. ... */ type record SipAddr { @@ -11,6 +15,26 @@ type record SipAddr { const charstring c_SIP_VERSION := "SIP/2.0"; +template (value) GenericParam ts_Param(template (value) charstring id, + template (omit) charstring paramValue := omit) := { + id := id, + paramValue := paramValue +} +template (present) GenericParam tr_Param(template (present) charstring id := ?, + template charstring paramValue := *) := { + id := id, + paramValue := paramValue +} +function f_ts_Param_omit(template (value) charstring id, + template (omit) charstring paramValue := omit) + return template (omit) GenericParam +{ + if (istemplatekind(paramValue, "omit")) { + return omit; + } + return ts_Param(id, paramValue); +} + template (value) SipUrl ts_SipUrl(template (value) HostPort host_port, template (omit) UserInfo user_info := omit) := { scheme := "sip", @@ -31,6 +55,49 @@ template (present) SipUrl tr_SipUrl(template (present) HostPort host_port := ?, template (value) SipUrl ts_SipUrlHost(template (value) charstring host) := ts_SipUrl(ts_HostPort(host)); +template (value) Credentials ts_Credentials_DigestResponse(template (value) CommaParam_List digestResponse) := { + digestResponse := digestResponse +} + +template (value) Credentials ts_Credentials_DigestResponseMD5( + template (value) charstring username, + template (value) charstring realm, + template (value) charstring nonce, + template (value) charstring uri, + template (value) charstring response, + template (value) charstring opaque, + template (value) charstring algorithm := "MD5", + template (value) charstring qop := "auth", + template (omit) charstring cnonce := omit, + template (omit) charstring nc := omit + ) := { + digestResponse := { + // Already added by digestResponse automatically: + //ts_Param("Digest", omit), + ts_Param("username", f_sip_str_quote(username)), + ts_Param("realm", f_sip_str_quote(realm)), + ts_Param("nonce", f_sip_str_quote(nonce)), + ts_Param("uri", f_sip_str_quote(uri)), + ts_Param("response", f_sip_str_quote(response)), + ts_Param("opaque", f_sip_str_quote(opaque)), + ts_Param("algorithm", algorithm), + ts_Param("qop", qop), + // FIXME: If "omit" is passed, these below end up in; + // "Dynamic test case error: Performing a valueof or send operation on a non-specific template of type @SIPmsg_Types.GenericParam" + f_ts_Param_omit("cnonce", f_sip_str_quote(cnonce)), + f_ts_Param_omit("nc", nc) + } +} + +template (value) Credentials ts_Credentials_OtherAuth(template (value) OtherAuth otherResponse) := { + otherResponse := otherResponse +} + +template (value) Authorization ts_Authorization(template (value) Credentials body) := { + fieldName := AUTHORIZATION_E, + body := body +} + // [20.10] template (present) NameAddr tr_NameAddr(template (present) SipUrl addrSpec := ?, template charstring displayName := *) := { @@ -262,7 +329,53 @@ template (present) Via tr_Via_from(template (present) HostPort host_port := ?, viaParams := viaParams } } - } +} + +template (present) OtherAuth +tr_OtherAuth(template (present) charstring authScheme := ?, + template (present) CommaParam_List authParams := ?) := { + authScheme := authScheme, + authParams := authParams +} + +template (value) OtherAuth +ts_OtherAuth(template (value) charstring authScheme, + template (value) CommaParam_List authParams) := { + authScheme := authScheme, + authParams := authParams +} + +template (present) Challenge +tr_Challenge_digestCln(template (present) CommaParam_List digestCln := ?) := { + digestCln := digestCln +} + +template (value) Challenge +ts_Challenge_digestCln(template (value) CommaParam_List digestCln) := { + digestCln := digestCln +} + +template (present) Challenge +tr_Challenge_otherChallenge(template (present) OtherAuth otherChallenge := ?) := { + otherChallenge := otherChallenge +} + +template (value) Challenge +ts_Challenge_otherChallenge(template (value) OtherAuth otherChallenge) := { + otherChallenge := otherChallenge +} + +template (present) WwwAuthenticate +tr_WwwAuthenticate(template (present) Challenge_list challenge := ?) := { + fieldName := WWW_AUTHENTICATE_E, + challenge := challenge +} + +template (value) WwwAuthenticate +ts_WwwAuthenticate(template (value) Challenge_list challenge) := { + fieldName := WWW_AUTHENTICATE_E, + challenge := challenge +} template (value) MessageHeader ts_SIP_msgHeader_empty := c_SIP_msgHeader_empty; template (value) MessageHeader @@ -274,6 +387,7 @@ ts_SIP_msgh_std(template (value) CallidString call_id, template (value) integer seq_nr, template (value) Via via, template (omit) ContentType content_type := omit, + template (omit)Authorization authorization := omit, template (value) Method_List allow_methods := c_SIP_defaultMethods, template (omit) Expires expires := omit ) modifies ts_SIP_msgHeader_empty := { @@ -281,6 +395,7 @@ ts_SIP_msgh_std(template (value) CallidString call_id, fieldName := ALLOW_E, methods := allow_methods }, + authorization := authorization, callId := { fieldName := CALL_ID_E, callid := call_id @@ -337,7 +452,8 @@ tr_SIP_msgh_std(template CallidString call_id, template ContentType content_type := *, template integer seq_nr := ?, template Method_List allow_methods := *, - template Expires expires := * + template Expires expires := *, + template WwwAuthenticate wwwAuthenticate := * ) modifies t_SIP_msgHeader_any := { allow := tr_AllowMethods(allow_methods), callId := { @@ -363,7 +479,8 @@ tr_SIP_msgh_std(template CallidString call_id, toParams := to_addr.params }, userAgent := *, - via := via + via := via, + wwwAuthenticate := wwwAuthenticate } @@ -376,11 +493,13 @@ ts_SIP_REGISTER(template (value) SipUrl sip_url_host_port, integer seq_nr, template (omit) Contact contact, template (omit) Expires expires, + template (omit) Authorization authorization := omit, template (omit) charstring body := omit) := { requestLine := ts_SIP_ReqLine(REGISTER_E, sip_url_host_port), msgHeader := ts_SIP_msgh_std(call_id, from_addr, to_addr, contact, "REGISTER", seq_nr, via, f_ContentTypeOrOmit(ts_CT_SDP, body), + authorization := authorization, expires := expires), messageBody := body, payload := omit @@ -511,15 +630,16 @@ template (present) PDU_SIP_Response tr_SIP_Response(template CallidString call_id, template SipAddr from_addr, template SipAddr to_addr, + template (present) Via via := tr_Via_from(?), template Contact contact, template charstring method, template integer status_code, template integer seq_nr := ?, template charstring reason := ?, - template charstring body := ?) := { + template charstring body := *) := { statusLine := tr_SIP_StatusLine(status_code, reason), msgHeader := tr_SIP_msgh_std(call_id, from_addr, to_addr, contact, - tr_Via_from(tr_HostPort(from_addr.addr.nameAddr.addrSpec.hostPort)), + via, method, *, seq_nr), messageBody := body, payload := omit @@ -533,6 +653,7 @@ tr_SIP_Response_REGISTER_Unauthorized( template SipAddr to_addr, template (present) Via via := tr_Via_from(?), template Contact contact := *, + template (present) WwwAuthenticate wwwAuthenticate := ?, template integer seq_nr := ?, template charstring method := "REGISTER", template integer status_code := 401, @@ -541,11 +662,333 @@ tr_SIP_Response_REGISTER_Unauthorized( statusLine := tr_SIP_StatusLine(status_code, reason), msgHeader := tr_SIP_msgh_std(call_id, from_addr, to_addr, contact, via, - method, *, seq_nr), + method, *, seq_nr, + wwwAuthenticate := wwwAuthenticate), messageBody := body, payload := omit } +function f_sip_param_find(GenericParam_List li, + template (present) charstring id := ?) +return template (omit) GenericParam { + var integer i; + + for (i := 0; i < lengthof(li); i := i + 1) { + if (not ispresent(li[i])) { + continue; + } + if (match(li[i].id, id)) { + return li[i]; + } + } + return omit; +} + +function f_sip_param_find_or_fail(GenericParam_List li, + template (present) charstring id := ?) +return GenericParam { + var template (omit) GenericParam parameter; + parameter := f_sip_param_find(li, id); + if (istemplatekind(parameter, "omit")) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Param ", id, " not found in ", li)); + } + return valueof(parameter); +} + +function f_sip_param_get_value(GenericParam_List li, + template (present) charstring id := ?) +return template (omit) charstring { + var template (omit) GenericParam parameter; + parameter := f_sip_param_find(li, id); + if (istemplatekind(parameter, "omit")) { + return omit; + } + return parameter.paramValue; +} + +function f_sip_param_get_value_or_fail(GenericParam_List li, + template (present) charstring id := ?) +return template (omit) charstring { + var GenericParam parameter; + parameter := f_sip_param_find_or_fail(li, id); + return parameter.paramValue; +} + +function f_sip_param_get_value_present_or_fail(GenericParam_List li, + template (present) charstring id := ?) +return charstring { + var GenericParam parameter; + parameter := f_sip_param_find_or_fail(li, id); + if (not ispresent(parameter.paramValue)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Param ", id, " value not present in ", li)); + } + return parameter.paramValue; +} + +function f_sip_param_match_value(GenericParam_List li, + template (present) charstring id := ?, + template charstring exp_paramValue := *) +return boolean { + var template (omit) charstring val; + val := f_sip_param_get_value_or_fail(li, id); + if (istemplatekind(val, "omit")) { + return istemplatekind(val, "omit") or istemplatekind(val, "*"); + } + return match(valueof(val), exp_paramValue); +} + +function f_sip_param_match_value_or_fail(GenericParam_List li, + template (present) charstring id := ?, + template charstring exp_paramValue := *) +{ + var template (omit) charstring val := f_sip_param_get_value_or_fail(li, id); + if (istemplatekind(val, "omit")) { + if (istemplatekind(val, "omit") or istemplatekind(val, "*")) { + return; + } else { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Param ", id, " match failed: val ", val, + " vs exp ", exp_paramValue)); + } + } + if (not match(valueof(val), exp_paramValue)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Param ", id, " match failed: val ", val, + " vs exp ", exp_paramValue)); + } +} + +function f_sip_param_remove(template (omit) GenericParam_List li_tpl, charstring id) +return GenericParam_List { + var integer i; + var GenericParam_List li; + var GenericParam_List new_li := {}; + + if (istemplatekind(li_tpl, "omit")) { + return {}; + } + + li := valueof(li_tpl); + for (i := 0; i < lengthof(li); i := i + 1) { + if (not ispresent(li[i]) or + not match(li[i].id, id)) { + new_li := new_li & {li[i]}; + } + } + return new_li; +} + +function f_sip_param_set(template (omit) GenericParam_List li_tpl, charstring id, charstring val) +return GenericParam_List { + var integer i; + var GenericParam_List li; + var GenericParam_List new_li := {}; + var boolean found := false; + + if (istemplatekind(li_tpl, "omit")) { + return { valueof(ts_Param(id, val)) }; + } + + li := valueof(li_tpl); + for (i := 0; i < lengthof(li); i := i + 1) { + if (not ispresent(li[i]) or + not match(li[i].id, id)) { + new_li := new_li & {li[i]}; + continue; + } + new_li := new_li & { valueof(ts_Param(li[i].id, val)) }; + found := true; + } + + if (not found) { + new_li := new_li & { valueof(ts_Param(id, val)) }; + } + return new_li; +} + +/* Make sure string is quoted. */ +function f_sip_str_quote(template (value) charstring val) return charstring { + var charstring str := valueof(val); + if (lengthof(str) == 0) { + return ""; + } + + if (str[0] != "\"") { + return "\"" & str & "\""; + } + return str; +} + +/* Make sure string is unquoted. + * Similar to unq() in RFC 2617 */ +function f_sip_str_unquote(template (value) charstring val) return charstring { + var charstring str := valueof(val); + var integer len := lengthof(str); + + if (len <= 1) { + return str; + } + + if (str[0] == "\"" and str[len - 1] == "\"") { + return substr(str, 1, len - 2); + } + return str; +} + +/* RFC 2617 3.2.2.2 A1 */ +function f_sip_digest_A1(charstring user, charstring realm, charstring password) return charstring { + + /* RFC 2617 3.2.2.2 A1 */ + var charstring A1 := f_sip_str_unquote(user) & ":" & + f_sip_str_unquote(realm) & ":" & + password; + var charstring digestA1 := f_str_tolower(f_calculateMD5(A1)); + log("A1: md5('", A1, "') = ", digestA1); + return digestA1; +} + +/* RFC 2617 3.2.2.2 A2 */ +function f_sip_digest_A2(charstring method, charstring uri) return charstring { + + var charstring A2 := method & ":" & uri + var charstring digestA2 := f_str_tolower(f_calculateMD5(A2)); + log("A2: md5('", A2, "') = ", digestA2); + return digestA2; +} + +/* RFC 2617 3.2.2.1 Request-Digest */ +function f_sip_digest_RequestDigest(charstring digestA1, charstring nonce, + charstring nc, charstring cnonce, + charstring qop, charstring digestA2) return charstring { + var charstring digest_data := f_sip_str_unquote(nonce) & ":" & + nc & ":" & + cnonce & ":" & + 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); + return req_digest; +} + +/* RFC 2617 3.2.1 The WWW-Authenticate Response Header + * KD(secret, data) = H(concat(secret, ":", data)) + */ +function f_sip_digest_KD(charstring secret, charstring data) return charstring { + return f_str_tolower(f_calculateMD5(secret & ":" & data)); +} + +/* Digest Auth: RFC 2617 */ +function f_sip_digest_gen_Authorization(WwwAuthenticate www_authenticate, + charstring user, charstring password, + charstring method, charstring uri, + charstring cnonce := "0a4f113b", integer nc_int := 1) return Authorization { + var CommaParam_List digestCln; + var template (value) Authorization authorization; + var template (value) Credentials cred; + var template (omit) GenericParam rx_param; + + digestCln := www_authenticate.challenge[0].digestCln; + + var charstring algorithm; + rx_param := f_sip_param_find(digestCln, "algorithm"); + if (istemplatekind(rx_param, "omit")) { + /* Assume MD5 if not set */ + algorithm := "MD5" + } else { + algorithm := valueof(rx_param.paramValue); + if (f_strstr(algorithm, "MD5") == -1) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Unexpected algorithm: ", algorithm)); + } + } + + var charstring realm := f_sip_param_get_value_present_or_fail(digestCln, "realm"); + var charstring nonce := f_sip_param_get_value_present_or_fail(digestCln, "nonce"); + var charstring opaque := f_sip_param_get_value_present_or_fail(digestCln, "opaque"); + var charstring qop := f_sip_param_get_value_present_or_fail(digestCln, "qop"); + + if (f_strstr(qop, "auth") == -1) { + 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); + + cred := ts_Credentials_DigestResponseMD5(user, realm, nonce, + uri, req_digest, + opaque, algorithm, selected_qop, cnonce, nc); + + authorization := ts_Authorization(cred); + return valueof(authorization); +} + +/* RFC 2617 3.5 Example */ +function f_sip_digest_selftest() { +/* +The following example assumes that an access-protected document is +being requested from the server via a GET request. The URI of the +document is "http://www.nowhere.org/dir/index.html". Both client and +server know that the username for this document is "Mufasa", and the +password is "Circle Of Life" (with one space between each of the +three words). + +HTTP/1.1 401 Unauthorized +WWW-Authenticate: Digest + realm="testrealm@host.com", + qop="auth,auth-int", + nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", + opaque="5ccc069c403ebaf9f0171e9517f40e41" + +Authorization: Digest username="Mufasa", + realm="testrealm@host.com", + nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", + uri="/dir/index.html", + qop=auth, + nc=00000001, + cnonce="0a4f113b", + response="6629fae49393a05397450978507c4ef1", + opaque="5ccc069c403ebaf9f0171e9517f40e41" +*/ + var template (value) CommaParam_List digestCln := { + ts_Param("realm", f_sip_str_quote("testrealm@host.com")), + ts_Param("qop", f_sip_str_quote("auth,auth-int")), + ts_Param("nonce", f_sip_str_quote("dcd98b7102dd2f0e8b11d0f600bfb0c093")), + ts_Param("opaque", f_sip_str_quote("5ccc069c403ebaf9f0171e9517f40e41")) + }; + var template (value) WwwAuthenticate www_authenticate := + 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); + + var CommaParam_List digestResp := authorization.body.digestResponse; + f_sip_param_match_value_or_fail(digestResp, "realm", f_sip_str_quote("testrealm@host.com")); + f_sip_param_match_value_or_fail(digestResp, "nonce", f_sip_str_quote("dcd98b7102dd2f0e8b11d0f600bfb0c093")); + f_sip_param_match_value_or_fail(digestResp, "uri", f_sip_str_quote("/dir/index.html")); + f_sip_param_match_value_or_fail(digestResp, "qop", "auth"); + f_sip_param_match_value_or_fail(digestResp, "nc", "00000001"); + f_sip_param_match_value_or_fail(digestResp, "cnonce", f_sip_str_quote("0a4f113b")); + f_sip_param_match_value_or_fail(digestResp, "response", f_sip_str_quote("6629fae49393a05397450978507c4ef1")); + f_sip_param_match_value_or_fail(digestResp, "opaque", f_sip_str_quote("5ccc069c403ebaf9f0171e9517f40e41")); +} + /* RFC 3261 8.1.1.5: * "The sequence number value MUST be expressible as a 32-bit unsigned integer * and MUST be less than 2**31." @@ -555,4 +998,68 @@ function f_sip_rand_seq_nr() return integer { return f_rnd_int(2147483648) } +/* Tags shall have at least 32 bit of randomness */ +function f_sip_rand_tag() return charstring { + var integer rnd_int := f_rnd_int(4294967296); + return hex2str(int2hex(rnd_int, 8)); +} + +/* Generate a "branch" tag value. + * RFC 3261 p.105 section 8: + * "A common way to create this value is to compute a + * cryptographic hash of the To tag, From tag, Call-ID header + * field, the Request-URI of the request received (before + * translation), the topmost Via header, and the sequence number + * from the CSeq header field, in addition to any Proxy-Require + * and Proxy-Authorization header fields that may be present. The + * algorithm used to compute the hash is implementation-dependent, + * but MD5 (RFC 1321 [35]),expressed in hexadecimal, is a reasonable + * choice." + * See also Section 8.1.1.7: + * "The branch ID inserted by an element compliant with this + * specification MUST always begin with the characters "z9hG4bK"." + */ +const charstring sip_magic_cookie := "z9hG4bK"; +function f_sip_gen_branch(charstring tag_to, + charstring tag_from, + charstring tag_call_id, + integer cseq) return charstring { + var charstring str := tag_to & tag_from & tag_call_id & int2str(cseq); + var charstring hash := f_calculateMD5(str); + var charstring branch := sip_magic_cookie & hash; + return branch; +} + +function f_sip_HostPort_to_str(HostPort host_port) return charstring { + var charstring str := ""; + if (ispresent(host_port.host)) { + str := host_port.host; + } + if (ispresent(host_port.portField)) { + str := str & ":" & int2str(host_port.portField); + } + return str; +} + +function f_sip_SipUrl_to_str(SipUrl uri) return charstring { + var charstring str := uri.scheme & f_sip_HostPort_to_str(uri.hostPort); + return str; +} + +function f_sip_NameAddr_to_str(NameAddr naddr) return charstring { + if (ispresent(naddr.displayName)) { + return naddr.displayName & " <" & f_sip_SipUrl_to_str(naddr.addrSpec) & ">"; + } else { + return f_sip_SipUrl_to_str(naddr.addrSpec); + } +} + +function f_sip_SipAddr_to_str(SipAddr sip_addr) return charstring { + if (ischosen(sip_addr.addr.nameAddr)) { + return f_sip_NameAddr_to_str(sip_addr.addr.nameAddr); + } else { + return f_sip_SipUrl_to_str(sip_addr.addr.addrSpecUnion); + } +} + } |