feat: add SIP authentication

change icon
This commit is contained in:
jingzhaoyang 2024-04-13 21:16:31 +08:00
parent d3b32cc14e
commit 362434cd29
8 changed files with 166 additions and 2 deletions

30
components.d.ts vendored
View file

@ -89,17 +89,28 @@ declare module '@vue/runtime-core' {
HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default'] 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'] 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: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'] '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'] IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default'] IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default'] IconMdiClose: typeof import('~icons/mdi/close')['default']
IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['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'] IconMdiEye: typeof import('~icons/mdi/eye')['default']
IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default'] IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
IconMdiHeart: typeof import('~icons/mdi/heart')['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'] IconMdiSearch: typeof import('~icons/mdi/search')['default']
IconMdiTranslate: typeof import('~icons/mdi/translate')['default'] IconMdiTranslate: typeof import('~icons/mdi/translate')['default']
IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default']
IconMdiVideo: typeof import('~icons/mdi/video')['default']
InputCopyable: typeof import('./src/components/InputCopyable.vue')['default'] InputCopyable: typeof import('./src/components/InputCopyable.vue')['default']
IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.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'] 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'] MenuLayout: typeof import('./src/components/MenuLayout.vue')['default']
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.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'] 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'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCode: typeof import('naive-ui')['NCode'] NCode: typeof import('naive-ui')['NCode']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDivider: typeof import('naive-ui')['NDivider'] NDivider: typeof import('naive-ui')['NDivider']
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
NEllipsis: typeof import('naive-ui')['NEllipsis'] NEllipsis: typeof import('naive-ui')['NEllipsis']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem'] NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi'] NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid'] NGrid: typeof import('naive-ui')['NGrid']
NH1: typeof import('naive-ui')['NH1'] NH1: typeof import('naive-ui')['NH1']
NH2: typeof import('naive-ui')['NH2']
NH3: typeof import('naive-ui')['NH3'] NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon'] 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'] NInputNumber: typeof import('naive-ui')['NInputNumber']
NLabel: typeof import('naive-ui')['NLabel']
NLayout: typeof import('naive-ui')['NLayout'] NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu'] NMenu: typeof import('naive-ui')['NMenu']
NProgress: typeof import('naive-ui')['NProgress']
NScrollbar: typeof import('naive-ui')['NScrollbar'] NScrollbar: typeof import('naive-ui')['NScrollbar']
NSlider: typeof import('naive-ui')['NSlider']
NSpin: typeof import('naive-ui')['NSpin'] 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'] 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'] 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'] 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'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] 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'] SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']

View file

@ -391,3 +391,11 @@ tools:
text-to-binary: text-to-binary:
title: Text to ASCII binary title: Text to ASCII binary
description: Convert text to its ASCII binary representation and vice versa. 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

View file

@ -387,3 +387,13 @@ tools:
text-to-binary: text-to-binary:
title: 文本到 ASCII 二进制 title: 文本到 ASCII 二进制
description: 将文本转换为其 ASCII 二进制表示形式,反之亦然。 description: 将文本转换为其 ASCII 二进制表示形式,反之亦然。
sip-auth:
title: SIP 认证校验
description: 这个工具能够帮你验证 SIP 授权头。该工具所有运算过程都在浏览器中进行,并不会通过网络传输敏感信息,所以请放心使用。
password: SIP 密码
password-tips: 输入密码...
message: SIP 消息文本
message-tips: 在此处粘贴您的 SIP 消息...
result: 检测结果
sucess: 成功
failure: 失败

View file

@ -1,6 +1,7 @@
import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter'; import { tool as base64StringConverter } from './base64-string-converter';
import { tool as basicAuthGenerator } from './basic-auth-generator'; import { tool as basicAuthGenerator } from './basic-auth-generator';
import { tool as sipAuth } from './sip-auth';
import { tool as asciiTextDrawer } from './ascii-text-drawer'; import { tool as asciiTextDrawer } from './ascii-text-drawer';
@ -85,7 +86,7 @@ import { tool as yamlViewer } from './yaml-viewer';
export const toolsByCategory: ToolCategory[] = [ export const toolsByCategory: ToolCategory[] = [
{ {
name: 'Crypto', 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', name: 'Converter',

View file

@ -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'),
});

View file

@ -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: <sip:1000@172.21.169.226>;tag=8db088f1039e49b691e53e5f172ace98
To: <sip:1000@172.21.169.226>
Call-ID: be95702faee84a8ebc8ccf94c5ca5492
CSeq: 37808 REGISTER
User-Agent: MicroSIP/3.21.3
Contact: <sip:1000@172.21.160.1:64712;ob>
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);
});
});

View file

@ -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';
}
}

View file

@ -0,0 +1,39 @@
<script setup lang="ts">
import { getAuthHeaderCheckResult } from './sip-auth.service';
const password = ref('');
const { t } = useI18n();
function transformer(value: string) {
if (value !== '' && password.value !== '') {
const Result = getAuthHeaderCheckResult({ message: value, password: password.value });
if (Result) {
return t('tools.sip-auth.sucess');
}
else {
return t('tools.sip-auth.failure');
// return `11 calculatedHash: ${calculatedHash}, response: ${response}`;
}
}
}
</script>
<template>
<div flex flex-col gap-4>
<c-input-text
v-model:value="password"
type="password"
:placeholder="$t('tools.sip-auth.password-tips')"
autofocus
raw-text
:label="$t('tools.sip-auth.password')"
/>
<format-transformer
:input-label="$t('tools.sip-auth.message')"
:input-placeholder="$t('tools.sip-auth.message-tips')"
:output-label="$t('tools.sip-auth.result')"
:transformer="transformer"
autosize
/>
</div>
</template>