From e8cebb399356f4f8fbaa4d29c14b0773c5ccbc48 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Mon, 13 Apr 2026 00:08:32 +0000 Subject: [PATCH 1/5] integrate code for stir/shaken --- configs/opensips.cfg.template | 32 ++++++++++++++++++++++++++++++++ tls/vs/ca_bundle.crt | 0 tls/vs/cert.pem | 0 tls/vs/key.pem | 0 4 files changed, 32 insertions(+) create mode 100644 tls/vs/ca_bundle.crt create mode 100644 tls/vs/cert.pem create mode 100644 tls/vs/key.pem diff --git a/configs/opensips.cfg.template b/configs/opensips.cfg.template index 8f5235e..0f95b2b 100644 --- a/configs/opensips.cfg.template +++ b/configs/opensips.cfg.template @@ -189,6 +189,11 @@ loadmodule "rtpengine.so" modparam("rtpengine", "db_url", "mysql://{{{db_user}}}:{{{db_pass}}}@{{{db_host}}}/{{{db_opensips}}}") #modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223") +loadmodule "stir_shaken.so" +# Path to your signing certificate (Public Key) +modparam("stir_shaken", "ca_list", "/etc/opensips/tls/vs/ca_bundle.crt") + + loadmodule "uac.so" #### ACCounting module @@ -413,6 +418,33 @@ route{ route(RELAY); } +route[AS_SERVICE] { + # Parameters: Cert Path, Private Key Path, Attestation Level, Orig-ID + $var(cert) = "/etc/opensips/tls/vs/cert.pem"; + $var(key) = "/etc/opensips/tls/vs/key.pem"; + + # Attestation levels: A (Full), B (Partial), C (Gateway) + if (!stir_shaken_auth("A", $var(cert), $var(key), "your-orig-uuid")) { + xlog("L_ERR", "STIR/SHAKEN signing failed!\n"); + } +} + +route[VS_SERVICE] { + if (is_present_hf("Identity")) { + # Verifies the Identity header against the provided CA list + if (stir_shaken_verify()) { + xlog("L_INFO", "STIR/SHAKEN Verification Successful - Identity Verified\n"); + } else { + xlog("L_WARN", "STIR/SHAKEN Verification Failed - Code: $rc\n"); + # Optional: Reject the call or add a 'verstat' parameter to the From header + # send_reply(438, "Invalid Identity Header"); + # exit; + } + } else { + xlog("L_INFO", "No Identity header present to verify\n"); + } +} + route[WITHINDLG] { # Handle in-dialog requests (ACK/BYE/reINVITE/UPDATE/INFO/OPTIONS/etc.) # Must be called only when has_totag() is true. diff --git a/tls/vs/ca_bundle.crt b/tls/vs/ca_bundle.crt new file mode 100644 index 0000000..e69de29 diff --git a/tls/vs/cert.pem b/tls/vs/cert.pem new file mode 100644 index 0000000..e69de29 diff --git a/tls/vs/key.pem b/tls/vs/key.pem new file mode 100644 index 0000000..e69de29 From da8c3119a2e4dc9f7758ab1a1aab238c15483c08 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Mon, 13 Apr 2026 00:35:01 +0000 Subject: [PATCH 2/5] fix stir/shaken integration. --- Dockerfile | 2 +- configs/opensips.cfg.template | 39 ++++++++++++++++++++++++----------- tls/vs/ca_bundle.crt | 15 ++++++++++++++ tls/vs/cert.pem | 15 ++++++++++++++ tls/vs/key.pem | 5 +++++ 5 files changed, 63 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index f12e3c3..ad45e5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ RUN curl -o /usr/share/keyrings/opensips-org.gpg https://apt.opensips.org/opensi # 3. Install OpenSIPS & Modules RUN apt-get -y update -qq && apt-get -y install opensips opensips-mysql-module \ opensips-regex-module opensips-restclient-module opensips-http-modules \ - opensips-json-module opensips-tls-module opensips-auth-modules opensips-wss-module make && \ + opensips-json-module opensips-tls-module opensips-auth-modules opensips-wss-module opensips-stir-shaken-module make && \ rm -rf /var/lib/apt/lists/* # 4. Setup Dual Logging Routing (rsyslog) diff --git a/configs/opensips.cfg.template b/configs/opensips.cfg.template index 0f95b2b..a797238 100644 --- a/configs/opensips.cfg.template +++ b/configs/opensips.cfg.template @@ -419,26 +419,41 @@ route{ } route[AS_SERVICE] { - # Parameters: Cert Path, Private Key Path, Attestation Level, Orig-ID - $var(cert) = "/etc/opensips/tls/vs/cert.pem"; - $var(key) = "/etc/opensips/tls/vs/key.pem"; + # 1. Define your parameters + $var(attest) = "A"; # Attestation Level (A, B, or C) + $var(origid) = "your-orig-uuid"; # Your UUID/Originating ID + $var(cert) = "/etc/opensips/tls/vs/cert.pem"; # Local Path to Public Cert + $var(key) = "/etc/opensips/tls/vs/key.pem"; # Local Path to Private Key - # Attestation levels: A (Full), B (Partial), C (Gateway) - if (!stir_shaken_auth("A", $var(cert), $var(key), "your-orig-uuid")) { - xlog("L_ERR", "STIR/SHAKEN signing failed!\n"); + # The X5U is the URL where the CALLEE can download your public cert + $var(x5u) = "https://your-public-url.com/cert.pem"; + + # 2. Call the function with correct parameter order: + # stir_shaken_auth(attest, origid, cert, pkey, x5u) + if (!stir_shaken_auth($var(attest), $var(origid), $var(cert), $var(key), $var(x5u))) { + xlog("L_ERR", "STIR/SHAKEN signing failed with error code $rc!\n"); + # Optional: You may want to exit or send a reply if signing is mandatory + } else { + xlog("L_INFO", "STIR/SHAKEN Identity header successfully generated\n"); } } route[VS_SERVICE] { if (is_present_hf("Identity")) { - # Verifies the Identity header against the provided CA list - if (stir_shaken_verify()) { + # 1. Provide the variable names directly (no quotes) + # OpenSIPS will populate these variables automatically on call + if (stir_shaken_verify($var(cert_out), $var(err_code), $var(err_reason))) { xlog("L_INFO", "STIR/SHAKEN Verification Successful - Identity Verified\n"); } else { - xlog("L_WARN", "STIR/SHAKEN Verification Failed - Code: $rc\n"); - # Optional: Reject the call or add a 'verstat' parameter to the From header - # send_reply(438, "Invalid Identity Header"); - # exit; + # 2. Now you can use them as normal with the $ prefix + xlog("L_WARN", "STIR/SHAKEN Verification Failed - Code: $var(err_code) Reason: $var(err_reason)\n"); + + if ($var(err_code) == "-4") { + send_reply(438, "Stale Identity Header"); + } else { + send_reply(438, "Invalid Identity Header"); + } + exit; } } else { xlog("L_INFO", "No Identity header present to verify\n"); diff --git a/tls/vs/ca_bundle.crt b/tls/vs/ca_bundle.crt index e69de29..3a7d76d 100644 --- a/tls/vs/ca_bundle.crt +++ b/tls/vs/ca_bundle.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSzCCAfGgAwIBAgIUL1NXIQGwQ1KYrDV2jUm1h2jcuWgwCgYIKoZIzj0EAwIw +ezELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB0FsYmVydGExETAPBgNVBAcMCEVkbW9u +dG9uMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxJDAiBgkqhkiG +9w0BCQEWFW5oYW1pZEBkeW5hbW9jbG91ZC5jYTAeFw0yNjA0MTMwMDI2NDBaFw0y +NzA0MTMwMDI2NDBaMHsxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdBbGJlcnRhMREw +DwYDVQQHDAhFZG1vbnRvbjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg +THRkMSQwIgYJKoZIhvcNAQkBFhVuaGFtaWRAZHluYW1vY2xvdWQuY2EwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAAThGp9Vwm6X9rKZaRujtr75d7Ll7hfPcM0s1mp7 +Cne5SeueDGvYALmaLHtvMMzHC1vI4KxeUfJuPEdeZptQSOngo1MwUTAdBgNVHQ4E +FgQUIFeNyAHqObnvm/XCtqQL5SgUiWMwHwYDVR0jBBgwFoAUIFeNyAHqObnvm/XC +tqQL5SgUiWMwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAoqYv +39l3OS/Q6GLcBC7d1yGBoih10+haa/YM+7l+UTYCIB7F32kL8J0W70t5M/k6zdWQ +5O/O+pS/162fbt6VEOev +-----END CERTIFICATE----- diff --git a/tls/vs/cert.pem b/tls/vs/cert.pem index e69de29..3a7d76d 100644 --- a/tls/vs/cert.pem +++ b/tls/vs/cert.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSzCCAfGgAwIBAgIUL1NXIQGwQ1KYrDV2jUm1h2jcuWgwCgYIKoZIzj0EAwIw +ezELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB0FsYmVydGExETAPBgNVBAcMCEVkbW9u +dG9uMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxJDAiBgkqhkiG +9w0BCQEWFW5oYW1pZEBkeW5hbW9jbG91ZC5jYTAeFw0yNjA0MTMwMDI2NDBaFw0y +NzA0MTMwMDI2NDBaMHsxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdBbGJlcnRhMREw +DwYDVQQHDAhFZG1vbnRvbjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg +THRkMSQwIgYJKoZIhvcNAQkBFhVuaGFtaWRAZHluYW1vY2xvdWQuY2EwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAAThGp9Vwm6X9rKZaRujtr75d7Ll7hfPcM0s1mp7 +Cne5SeueDGvYALmaLHtvMMzHC1vI4KxeUfJuPEdeZptQSOngo1MwUTAdBgNVHQ4E +FgQUIFeNyAHqObnvm/XCtqQL5SgUiWMwHwYDVR0jBBgwFoAUIFeNyAHqObnvm/XC +tqQL5SgUiWMwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAoqYv +39l3OS/Q6GLcBC7d1yGBoih10+haa/YM+7l+UTYCIB7F32kL8J0W70t5M/k6zdWQ +5O/O+pS/162fbt6VEOev +-----END CERTIFICATE----- diff --git a/tls/vs/key.pem b/tls/vs/key.pem index e69de29..67a1e97 100644 --- a/tls/vs/key.pem +++ b/tls/vs/key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHQ9A7eboPAePSYseNa2XKmgB6OuH4yr13T1Rb2LVvT+oAoGCCqGSM49 +AwEHoUQDQgAE4RqfVcJul/aymWkbo7a++Xey5e4Xz3DNLNZqewp3uUnrngxr2AC5 +mix7bzDMxwtbyOCsXlHybjxHXmabUEjp4A== +-----END EC PRIVATE KEY----- From 0e54d020d663996bab5d595cec29f4a637bf77dc Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Mon, 13 Apr 2026 01:05:37 +0000 Subject: [PATCH 3/5] add VS_SERVICE call to incoming PSTN routing flow. --- configs/opensips.cfg.template | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configs/opensips.cfg.template b/configs/opensips.cfg.template index a797238..3c98713 100644 --- a/configs/opensips.cfg.template +++ b/configs/opensips.cfg.template @@ -711,6 +711,9 @@ route[INTERNAL_EXTENSION_ROUTING] { route[PSTN_ROUTING] { xlog("SOURCE IP IS: $si"); + + # STIR/SHAKEN verification + # route(VS_SERVICE); if ($hdr(X-LineBlocs-RingSubscriber) == "true") { xlog("sending to extension...\r\n"); From 77f5e0bb5a6b9d155a950c96787bd9128185c049 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Mon, 13 Apr 2026 01:14:26 +0000 Subject: [PATCH 4/5] change HTTP path of public stir/shaken cert --- configs/opensips.cfg.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/opensips.cfg.template b/configs/opensips.cfg.template index 3c98713..6789967 100644 --- a/configs/opensips.cfg.template +++ b/configs/opensips.cfg.template @@ -426,7 +426,7 @@ route[AS_SERVICE] { $var(key) = "/etc/opensips/tls/vs/key.pem"; # Local Path to Private Key # The X5U is the URL where the CALLEE can download your public cert - $var(x5u) = "https://your-public-url.com/cert.pem"; + $var(x5u) = "https://{{{deployment_domain}}}/stir-shaken/cert.pem"; # 2. Call the function with correct parameter order: # stir_shaken_auth(attest, origid, cert, pkey, x5u) @@ -711,7 +711,7 @@ route[INTERNAL_EXTENSION_ROUTING] { route[PSTN_ROUTING] { xlog("SOURCE IP IS: $si"); - + # STIR/SHAKEN verification # route(VS_SERVICE); if ($hdr(X-LineBlocs-RingSubscriber) == "true") From 030489cbfd758f6eccd470d0f7c04e2c4662eb42 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Fri, 17 Apr 2026 19:51:07 +0000 Subject: [PATCH 5/5] add scenario codes to 400 bad request response. --- configs/opensips.cfg.template | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/configs/opensips.cfg.template b/configs/opensips.cfg.template index 6789967..41e725c 100644 --- a/configs/opensips.cfg.template +++ b/configs/opensips.cfg.template @@ -862,7 +862,7 @@ route[INCOMING_VALIDATION_2] { route[DID_ASSIGNED] { if ($var(rcode) != 200) { - send_reply(400, "Bad Request -- DID lookup failed"); + send_reply(400, "Bad Request (scenario 1)"); exit; } @@ -878,7 +878,7 @@ route[DID_ASSIGNED] { route[TRUNK_ASSIGNED] { if ($var(rcode) != 200) { - send_reply(400, "Bad Request"); + send_reply(400, "Bad Request (scenario 2)"); exit; } @@ -895,7 +895,7 @@ route[TRUNK_ASSIGNED] { route[CALL_ACTION] { if ($var(rcode) != 200) { - send_reply(400, "Bad Request"); + send_reply(400, "Bad Request (scenario 3)"); exit; } @@ -1638,4 +1638,4 @@ route[RTPENGINE_ANSWER] { rtpengine_use_set(1); xlog("L_INFO", "RTP Answer Flags: $var(ans_flags)\n"); rtpengine_answer("$var(ans_flags)"); -} \ No newline at end of file +}