34 lines
2.5 KiB
TypeScript
34 lines
2.5 KiB
TypeScript
type Row = Record<string, unknown>
|
|
function rows(value: unknown): Row[] {
|
|
if (!Array.isArray(value) || value.some(row => !row || typeof row !== "object" || Array.isArray(row))) throw new Error("Некорректный ответ проверки сертификата")
|
|
return value as Row[]
|
|
}
|
|
function fingerprint(value: unknown): string | null {
|
|
if (typeof value !== "string") return null
|
|
const normalized = value.replaceAll(":", "").toLowerCase()
|
|
return /^[a-f0-9]{64}$/.test(normalized) ? normalized : null
|
|
}
|
|
export function assertImportedCertificate(value: unknown, name: string, expectedFingerprint: string): void {
|
|
const matches = rows(value).filter(row => row.name === name)
|
|
if (matches.length !== 1) throw new Error("Импорт не подтверждён: имя сертификата отсутствует или неоднозначно. Назначение сервисам не подтверждено.")
|
|
const row = matches[0]!
|
|
const expected = fingerprint(expectedFingerprint)
|
|
if (!expected || fingerprint(row.fingerprint) !== expected) throw new Error("Импорт не подтверждён: отпечаток сертификата отличается от выданного")
|
|
const privateKey = row["private-key"] === "true" || row["private-key"] === "yes" || row["private-key"] === undefined && typeof row.flags === "string" && row.flags.toUpperCase().includes("K")
|
|
if (!privateKey) throw new Error("Импорт не подтверждён: закрытый ключ сертификата не обнаружен")
|
|
}
|
|
export async function applyVerifiedCertificateToServices(deps: {
|
|
certificates: () => Promise<unknown>
|
|
apply: () => Promise<void>
|
|
services: () => Promise<unknown>
|
|
}, name: string, expectedFingerprint: string): Promise<void> {
|
|
assertImportedCertificate(await deps.certificates(), name, expectedFingerprint)
|
|
await deps.apply()
|
|
assertImportedCertificate(await deps.certificates(), name, expectedFingerprint)
|
|
const services = rows(await deps.services()).filter(row => row.dynamic !== "true" && row.dynamic !== "yes")
|
|
for (const service of ["www-ssl", "api-ssl"]) {
|
|
const matching = services.filter(row => row.name === service)
|
|
if (matching.length !== 1 || matching[0]!.certificate !== name || !["false", "no"].includes(String(matching[0]!.disabled))) throw new Error(`Назначение сертификата сервису ${service} не подтверждено; проверьте фактические настройки устройства`)
|
|
}
|
|
}
|