From 362434cd2918ad392fdb912fd4489760e5f8da1f Mon Sep 17 00:00:00 2001 From: jingzhaoyang Date: Sat, 13 Apr 2024 21:16:31 +0800 Subject: [PATCH] feat: add SIP authentication change icon --- components.d.ts | 30 +++++++++++++++- locales/en.yml | 8 +++++ locales/zh.yml | 10 ++++++ src/tools/index.ts | 3 +- src/tools/sip-auth/index.ts | 12 +++++++ src/tools/sip-auth/sip-auth.service.test.ts | 30 ++++++++++++++++ src/tools/sip-auth/sip-auth.service.ts | 36 +++++++++++++++++++ src/tools/sip-auth/sip-auth.vue | 39 +++++++++++++++++++++ 8 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 src/tools/sip-auth/index.ts create mode 100644 src/tools/sip-auth/sip-auth.service.test.ts create mode 100644 src/tools/sip-auth/sip-auth.service.ts create mode 100644 src/tools/sip-auth/sip-auth.vue diff --git a/components.d.ts b/components.d.ts index e31119b3..27540e01 100644 --- a/components.d.ts +++ b/components.d.ts @@ -89,17 +89,28 @@ declare module '@vue/runtime-core' { HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default'] IbanValidatorAndParser: typeof import('./src/tools/iban-validator-and-parser/iban-validator-and-parser.vue')['default'] 'IconMdi:brushVariant': typeof import('~icons/mdi/brush-variant')['default'] + 'IconMdi:contentCopy': typeof import('~icons/mdi/content-copy')['default'] 'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default'] + IconMdiArrowDown: typeof import('~icons/mdi/arrow-down')['default'] + IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default'] + IconMdiCamera: typeof import('~icons/mdi/camera')['default'] IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default'] IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default'] IconMdiClose: typeof import('~icons/mdi/close')['default'] IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default'] + IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default'] + IconMdiDownload: typeof import('~icons/mdi/download')['default'] IconMdiEye: typeof import('~icons/mdi/eye')['default'] IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default'] IconMdiHeart: typeof import('~icons/mdi/heart')['default'] + IconMdiPause: typeof import('~icons/mdi/pause')['default'] + IconMdiPlay: typeof import('~icons/mdi/play')['default'] + IconMdiRecord: typeof import('~icons/mdi/record')['default'] + IconMdiRefresh: typeof import('~icons/mdi/refresh')['default'] IconMdiSearch: typeof import('~icons/mdi/search')['default'] IconMdiTranslate: typeof import('~icons/mdi/translate')['default'] IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] + IconMdiVideo: typeof import('~icons/mdi/video')['default'] InputCopyable: typeof import('./src/components/InputCopyable.vue')['default'] IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default'] Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default'] @@ -126,25 +137,40 @@ declare module '@vue/runtime-core' { MenuLayout: typeof import('./src/components/MenuLayout.vue')['default'] MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] + NAlert: typeof import('naive-ui')['NAlert'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] + NCheckbox: typeof import('naive-ui')['NCheckbox'] NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] + NColorPicker: typeof import('naive-ui')['NColorPicker'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] + NDatePicker: typeof import('naive-ui')['NDatePicker'] NDivider: typeof import('naive-ui')['NDivider'] + NDynamicInput: typeof import('naive-ui')['NDynamicInput'] NEllipsis: typeof import('naive-ui')['NEllipsis'] + NForm: typeof import('naive-ui')['NForm'] NFormItem: typeof import('naive-ui')['NFormItem'] NGi: typeof import('naive-ui')['NGi'] NGrid: typeof import('naive-ui')['NGrid'] NH1: typeof import('naive-ui')['NH1'] + NH2: typeof import('naive-ui')['NH2'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] + NImage: typeof import('naive-ui')['NImage'] + NInputGroup: typeof import('naive-ui')['NInputGroup'] + NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel'] NInputNumber: typeof import('naive-ui')['NInputNumber'] - NLabel: typeof import('naive-ui')['NLabel'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] + NProgress: typeof import('naive-ui')['NProgress'] NScrollbar: typeof import('naive-ui')['NScrollbar'] + NSlider: typeof import('naive-ui')['NSlider'] NSpin: typeof import('naive-ui')['NSpin'] + NStatistic: typeof import('naive-ui')['NStatistic'] + NSwitch: typeof import('naive-ui')['NSwitch'] + NTable: typeof import('naive-ui')['NTable'] + NTag: typeof import('naive-ui')['NTag'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] @@ -159,6 +185,8 @@ declare module '@vue/runtime-core' { RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] + SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default'] + SipAuth: typeof import('./src/tools/sip-auth/sip-auth.vue')['default'] SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default'] SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] diff --git a/locales/en.yml b/locales/en.yml index 50d48af9..12028552 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -391,3 +391,11 @@ tools: text-to-binary: title: Text to ASCII binary description: Convert text to its ASCII binary representation and vice versa. + sip-auth: + password: SIP password + password-tips: Enter a password... + message: SIP message + message-tips: Paste your SIP message here... + result: Result + sucess: Sucess + failure: Failure diff --git a/locales/zh.yml b/locales/zh.yml index 160fe1fa..6627f36c 100644 --- a/locales/zh.yml +++ b/locales/zh.yml @@ -387,3 +387,13 @@ tools: text-to-binary: title: 文本到 ASCII 二进制 description: 将文本转换为其 ASCII 二进制表示形式,反之亦然。 + sip-auth: + title: SIP 认证校验 + description: 这个工具能够帮你验证 SIP 授权头。该工具所有运算过程都在浏览器中进行,并不会通过网络传输敏感信息,所以请放心使用。 + password: SIP 密码 + password-tips: 输入密码... + message: SIP 消息文本 + message-tips: 在此处粘贴您的 SIP 消息... + result: 检测结果 + sucess: 成功 + failure: 失败 diff --git a/src/tools/index.ts b/src/tools/index.ts index aa861c93..fdf71e90 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as sipAuth } from './sip-auth'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -85,7 +86,7 @@ import { tool as yamlViewer } from './yaml-viewer'; export const toolsByCategory: ToolCategory[] = [ { name: 'Crypto', - components: [tokenGenerator, hashText, bcrypt, uuidGenerator, ulidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser, pdfSignatureChecker], + components: [tokenGenerator, hashText, bcrypt, uuidGenerator, ulidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser, pdfSignatureChecker, sipAuth], }, { name: 'Converter', diff --git a/src/tools/sip-auth/index.ts b/src/tools/sip-auth/index.ts new file mode 100644 index 00000000..db4b0559 --- /dev/null +++ b/src/tools/sip-auth/index.ts @@ -0,0 +1,12 @@ +import { ArrowsShuffle, ShieldCheck, ShieldLock } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'SIP Authorization', + path: '/sip-auth', + description: 'This tool helps you to verify the SIP authorization header. The tool performs all calculations in the browser and does not transmit sensitive information over the network, so feel free to use it.', + keywords: ['sip', 'authorization'], + component: () => import('./sip-auth.vue'), + icon: ShieldCheck, + createdAt: new Date('2024-04-11'), +}); diff --git a/src/tools/sip-auth/sip-auth.service.test.ts b/src/tools/sip-auth/sip-auth.service.test.ts new file mode 100644 index 00000000..564f553c --- /dev/null +++ b/src/tools/sip-auth/sip-auth.service.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; +import { getAuthHeaderCheckResult } from './sip-auth.service'; + +const message = `REGISTER sip:172.21.169.226 SIP/2.0 +Via: SIP/2.0/UDP 172.21.160.1:64712;rport;branch=z9hG4bKPja70ba2f5f4464081a3925196ebd4e7f9 +Max-Forwards: 70 +From: ;tag=8db088f1039e49b691e53e5f172ace98 +To: +Call-ID: be95702faee84a8ebc8ccf94c5ca5492 +CSeq: 37808 REGISTER +User-Agent: MicroSIP/3.21.3 +Contact: +Expires: 300 +Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS +Authorization: Digest username="1000", realm="172.21.169.226", nonce="d99a372c-340a-4ec9-91ed-53ad8f318ae0", uri="sip:172.21.169.226", response="7bdeb758d949a3c5e8d125fafbac1d5e", algorit=MD5, cnonce="3a8c445fb7ff48aeb8c422b00afaaddc", qop=auth, nc=00000001 +Content-Length: 0`; + +describe('getAuthHeaderCheckResult', () => { + it('should return "check success" for valid authentication', () => { + const password = '1234'; + const result = getAuthHeaderCheckResult({ message, password }); + expect(result).toBe(true); + }); + + it('should return "check failed" for invalid authentication', () => { + const password = '12345'; + const result = getAuthHeaderCheckResult({ message, password }); + expect(result).toBe(false); + }); +}); diff --git a/src/tools/sip-auth/sip-auth.service.ts b/src/tools/sip-auth/sip-auth.service.ts new file mode 100644 index 00000000..0de35cc6 --- /dev/null +++ b/src/tools/sip-auth/sip-auth.service.ts @@ -0,0 +1,36 @@ +import { MD5 } from 'crypto-js'; + +export { getAuthHeaderCheckResult }; + +function getAuthHeaderCheckResult({ message, password }: { message: string; password: string }) { + let calculatedHash; + const method = extractComponent('^([A-Z]+) sip', message); + const username = extractComponent('username="([^"]+)"', message); + const realm = extractComponent('realm="([^"]+)"', message); + const uri = extractComponent('uri="([^"]+)"', message); + const nonce = extractComponent('[^c]nonce="([^"]+)"', message); + const cnonce = extractComponent('cnonce="([^"]+)"', message); + const nc = extractComponent('nc=([0-9a-f]+)', message); + const qop = extractComponent('qop="?(auth|auth-int)"?', message); + const response = extractComponent('response="([^"]+)"', message); + const ha1 = MD5(`${username}:${realm}:${password}`).toString(); + const ha2 = MD5(`${method}:${uri}`).toString(); + if (qop.toLowerCase() === 'auth') { + calculatedHash = MD5(`${ha1}:${nonce}:${nc}:${cnonce}:${qop}:${ha2}`).toString(); + } + else { + calculatedHash = MD5(`${ha1}:${nonce}:${cnonce}`).toString(); + } + return (response === calculatedHash); +} + +function extractComponent(regex: string, source: string): string { + const processor = new RegExp(regex, 'i'); + const matchResult = processor.exec(source); + if (matchResult !== null && matchResult.length > 1) { + return matchResult[1]; + } + else { + return 'Not Found'; + } +} diff --git a/src/tools/sip-auth/sip-auth.vue b/src/tools/sip-auth/sip-auth.vue new file mode 100644 index 00000000..e7d3575a --- /dev/null +++ b/src/tools/sip-auth/sip-auth.vue @@ -0,0 +1,39 @@ + + +