mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2025-10-03 17:59:24 +02:00
Add processing client certificates
This commit is contained in:
parent
cb1347fffa
commit
a79fcf235e
4 changed files with 237 additions and 2 deletions
129
package-lock.json
generated
129
package-lock.json
generated
|
@ -22,6 +22,7 @@
|
|||
"nodemailer": "^6.7.2",
|
||||
"pg": "^8.5.1",
|
||||
"pg-hstore": "^2.3.3",
|
||||
"pkijs": "^3.0.16",
|
||||
"rate-limiter-flexible": "^2.1.15",
|
||||
"sequelize": "^6.25.5",
|
||||
"socket.io": "^4.0.1",
|
||||
|
@ -2540,6 +2541,24 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1js": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz",
|
||||
"integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==",
|
||||
"dependencies": {
|
||||
"pvtsutils": "^1.3.2",
|
||||
"pvutils": "^1.1.3",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1js/node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
|
||||
|
@ -2764,6 +2783,14 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/bytestreamjs": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz",
|
||||
"integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cacache": {
|
||||
"version": "15.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz",
|
||||
|
@ -5915,6 +5942,26 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pkijs": {
|
||||
"version": "3.0.16",
|
||||
"resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.0.16.tgz",
|
||||
"integrity": "sha512-iDUm90wfgtfd1PDV1oEnQj/4jBIU9hCSJeV0kQKThwDpbseFxC4TdpoMYlwE9maol5u0wMGZX9cNG2h1/0Lhww==",
|
||||
"dependencies": {
|
||||
"asn1js": "^3.0.5",
|
||||
"bytestreamjs": "^2.0.0",
|
||||
"pvtsutils": "^1.3.2",
|
||||
"pvutils": "^1.1.3",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pkijs/node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/please-upgrade-node": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
|
||||
|
@ -6016,6 +6063,27 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pvtsutils": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz",
|
||||
"integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/pvtsutils/node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/pvutils": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz",
|
||||
"integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
|
@ -9712,6 +9780,23 @@
|
|||
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
|
||||
"dev": true
|
||||
},
|
||||
"asn1js": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz",
|
||||
"integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==",
|
||||
"requires": {
|
||||
"pvtsutils": "^1.3.2",
|
||||
"pvutils": "^1.1.3",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"async": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
|
||||
|
@ -9868,6 +9953,11 @@
|
|||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
|
||||
},
|
||||
"bytestreamjs": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz",
|
||||
"integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ=="
|
||||
},
|
||||
"cacache": {
|
||||
"version": "15.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz",
|
||||
|
@ -12148,6 +12238,25 @@
|
|||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true
|
||||
},
|
||||
"pkijs": {
|
||||
"version": "3.0.16",
|
||||
"resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.0.16.tgz",
|
||||
"integrity": "sha512-iDUm90wfgtfd1PDV1oEnQj/4jBIU9hCSJeV0kQKThwDpbseFxC4TdpoMYlwE9maol5u0wMGZX9cNG2h1/0Lhww==",
|
||||
"requires": {
|
||||
"asn1js": "^3.0.5",
|
||||
"bytestreamjs": "^2.0.0",
|
||||
"pvtsutils": "^1.3.2",
|
||||
"pvutils": "^1.1.3",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"please-upgrade-node": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
|
||||
|
@ -12225,6 +12334,26 @@
|
|||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||
},
|
||||
"pvtsutils": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz",
|
||||
"integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==",
|
||||
"requires": {
|
||||
"tslib": "^2.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"pvutils": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz",
|
||||
"integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ=="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
|
|
|
@ -60,10 +60,10 @@
|
|||
"nodemailer": "^6.7.2",
|
||||
"pg": "^8.5.1",
|
||||
"pg-hstore": "^2.3.3",
|
||||
"pkijs": "^3.0.16",
|
||||
"rate-limiter-flexible": "^2.1.15",
|
||||
"sequelize": "^6.25.5",
|
||||
"socket.io": "^4.0.1",
|
||||
"sqlite3": "^4.0.0",
|
||||
"umzug": "^2.3.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
|
|
@ -26,13 +26,15 @@ import {
|
|||
isSendMailLoginCodeRequest,
|
||||
isSignInByMailCodeRequest
|
||||
} from './validator'
|
||||
import { analyze } from './integrity'
|
||||
|
||||
export const createAuthRouter = (database: Database) => {
|
||||
const router = Router()
|
||||
|
||||
router.post('/send-mail-login-code-v2', json(), async (req, res, next) => {
|
||||
const info = {
|
||||
ua: req.headers['user-agent']
|
||||
ua: req.headers['user-agent'],
|
||||
cert: analyze(req),
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
104
src/api/integrity.ts
Normal file
104
src/api/integrity.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
import { X509Certificate } from 'crypto'
|
||||
import { Request } from 'express'
|
||||
import { fromBER, Sequence, Integer, OctetString, Set } from 'asn1js'
|
||||
import { Certificate } from 'pkijs'
|
||||
|
||||
export interface CertInfo {
|
||||
raw: string
|
||||
applicationCerts: Array<string>
|
||||
}
|
||||
|
||||
export function analyze(req: Request): CertInfo | null {
|
||||
try {
|
||||
const certStr = req.headers['clientcertificate']
|
||||
|
||||
if (typeof certStr !== 'string') return null
|
||||
|
||||
const nativeCert = new X509Certificate(decodeURIComponent(certStr))
|
||||
|
||||
const now = Date.now()
|
||||
const from = Date.parse(nativeCert.validFrom)
|
||||
const to = Date.parse(nativeCert.validTo)
|
||||
|
||||
if (from > now || to < now) return null
|
||||
if (from > to || from + 1000 * 60 * 5 < to) return null
|
||||
|
||||
const cert1 = fromBER(nativeCert.raw)
|
||||
|
||||
if (cert1.offset === -1) return null
|
||||
|
||||
const cert2 = new Certificate({ schema: cert1.result })
|
||||
const androidExtension = (cert2.extensions || []).find((item) => item.extnID === '1.3.6.1.4.1.11129.2.1.17')?.extnValue?.valueBlock?.valueHexView
|
||||
|
||||
if (!androidExtension) return null
|
||||
|
||||
const androidExtensionParsed = fromBER(androidExtension)
|
||||
|
||||
if (androidExtensionParsed.offset === -1) return null
|
||||
|
||||
const androidExtensionSequence = androidExtensionParsed.result
|
||||
|
||||
if (!(androidExtensionSequence instanceof Sequence)) return null
|
||||
|
||||
const versionInteger = androidExtensionSequence.valueBlock.value[0]
|
||||
|
||||
if (!(versionInteger instanceof Integer)) return null
|
||||
|
||||
const versionValue = versionInteger.valueBlock.valueDec
|
||||
|
||||
const authorizationLists = androidExtensionSequence.valueBlock.value.slice(6, 8)
|
||||
|
||||
let applicationId = null
|
||||
|
||||
for (const authList of authorizationLists) {
|
||||
if (!(authList instanceof Sequence)) continue
|
||||
|
||||
for (const authListItem of authList.valueBlock.value) {
|
||||
if (
|
||||
// version 1 does not provide this data structure
|
||||
versionValue !== 1 &&
|
||||
authListItem.idBlock.tagNumber === 709
|
||||
) {
|
||||
if (!('value' in authListItem.valueBlock)) continue
|
||||
|
||||
const value = (authListItem.valueBlock as unknown as { value: object }).value
|
||||
|
||||
if (!Array.isArray(value) || value.length !== 1) continue
|
||||
|
||||
if (value[0] instanceof OctetString) applicationId = value[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!applicationId) return null
|
||||
|
||||
const parsedApplicationId = fromBER(applicationId.valueBlock.valueHexView)
|
||||
|
||||
if (parsedApplicationId.offset === -1) return null
|
||||
|
||||
if (!(parsedApplicationId.result instanceof Sequence)) return null
|
||||
|
||||
const parsedApplicationIdInfo = parsedApplicationId.result.valueBlock.value
|
||||
|
||||
if (parsedApplicationIdInfo.length !== 2) return null
|
||||
|
||||
const signatureDigests = parsedApplicationIdInfo[1]
|
||||
|
||||
if (!(signatureDigests instanceof Set)) return null
|
||||
|
||||
const applicationCerts = []
|
||||
|
||||
for (const cert of signatureDigests.valueBlock.value) {
|
||||
if (cert instanceof OctetString) {
|
||||
applicationCerts.push(Buffer.from(cert.valueBlock.valueHexView).toString('hex'))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
raw: nativeCert.raw.toString('base64'),
|
||||
applicationCerts
|
||||
}
|
||||
} catch (ex) {
|
||||
return null
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue