# CORS-прокси для получения сертификатов Инструмент цифровой подписи использует CORS-прокси для получения сертификатов издателей у внешних удостоверяющих центров (CA). Это нужно, потому что многие серверы CA не добавляют заголовки CORS в ответы, и браузер не может запросить сертификаты напрямую. Кроме того, многие серверы CA отдают сертификаты по обычному HTTP. Если ваш экземпляр BentoPDF работает по HTTPS, браузеры блокируют такие HTTP-запросы (политика смешанного содержимого). CORS-прокси решает обе проблемы: запросы идут через HTTPS-endpoint с нужными заголовками. ## Как это работает При подписании PDF сертификатом: 1. Библиотека `zgapdfsigner` пытается построить полную цепочку сертификатов 2. Она запрашивает сертификаты издателей по URL из расширения AIA (Authority Information Access) вашего сертификата 3. Эти запросы идут через CORS-прокси, который добавляет нужные заголовки `Access-Control-Allow-Origin` 4. Прокси возвращает данные сертификата в браузер ## Собственный CORS-прокси Если вы размещаете BentoPDF у себя, для работы цифровых подписей с сертификатами, которым нужна загрузка цепочки, потребуется развернуть собственный CORS-прокси. ### Вариант 1: Cloudflare Workers (рекомендуется) 1. **Установите Wrangler CLI**: ```bash npm install -g wrangler ``` 2. **Войдите в Cloudflare**: ```bash wrangler login ``` 3. **Клонируйте BentoPDF и обновите разрешённые источники**: ```bash git clone https://github.com/alam00000/bentopdf.git cd bentopdf/cloudflare ``` Откройте `cors-proxy-worker.js` и замените массив `ALLOWED_ORIGINS` на свой домен: ```js const ALLOWED_ORIGINS = [ 'https://your-domain.com', 'https://www.your-domain.com', ]; ``` ::: warning Важно Без этого изменения прокси отклонит все запросы с вашего сайта с ошибкой **403 Forbidden**. По умолчанию разрешены только запросы с `bentopdf.com`. ::: 4. **Разверните прокси**: ```bash wrangler deploy ``` Запомните URL своего worker (например, `https://bentopdf-cors-proxy.your-subdomain.workers.dev`). 5. **Пересоберите BentoPDF с URL прокси**: Если вы используете Docker: ```bash export VITE_CORS_PROXY_URL="https://your-worker.workers.dev" DOCKER_BUILDKIT=1 docker build \ --secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \ -t your-bentopdf . ``` Если вы собираете из исходников: ```bash VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build ``` ### Вариант 2: собственный серверный прокси Можно создать и собственный endpoint прокси. Требования: 1. Принимать GET-запросы с параметром запроса `url` 2. Запрашивать этот URL со своего сервера (на стороне сервера ограничений CORS нет) 3. Возвращать ответ с такими заголовками: - `Access-Control-Allow-Origin: https://your-domain.com` - `Access-Control-Allow-Methods: GET, OPTIONS` - `X-Content-Type-Options: nosniff` Пример реализации на Express.js: ```javascript app.get('/api/cert-proxy', async (req, res) => { const targetUrl = req.query.url; // Validate it's a certificate URL if (!isValidCertUrl(targetUrl)) { return res.status(400).json({ error: 'Invalid URL' }); } try { const response = await fetch(targetUrl); const data = await response.arrayBuffer(); res.set('Access-Control-Allow-Origin', 'https://your-domain.com'); res.set('Content-Type', 'application/octet-stream'); res.set('X-Content-Type-Options', 'nosniff'); res.send(Buffer.from(data)); } catch (error) { res.status(500).json({ error: 'Proxy error' }); } }); ``` ## Вопросы безопасности В поставляемом Cloudflare Worker предусмотрено несколько мер безопасности: | Функция | Описание | | --------------------------- | ------------------------------------------------------------------------------------------------ | | **Проверка источника** | Разрешает запросы только с доменов из `ALLOWED_ORIGINS` | | **Ограничение URL** | Разрешает только URL сертификатов (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) | | **Блокировка частных IP** | Блокирует частные диапазоны IPv4/IPv6, link-local, loopback, десятичные IP и облачные метаданные | | **Безопасный Content-Type** | Возвращает только безопасные MIME-типы сертификатов, блокирует подмену content-type источником | | **Лимит размера файла** | Передаёт ответ потоком с лимитом 10 МБ и прерывает загрузку при превышении | | **Ограничение частоты** | 60 запросов в минуту на IP или IPv6 /64 (нужен KV, при ошибках KV пропускает запросы) | | **Подписи HMAC** | Необязательная подпись на стороне клиента (сдерживает случайные злоупотребления) | ### DNS rebinding (хостинг вне Cloudflare) Worker отклоняет любой URL, который разрешается в частный или внутренний IP. Фиксированный список разрешённых хостов использовать нельзя, так как URL цепочек сертификатов указывают на тот CA, который выдал сертификат (FNMT, корпоративные CA и так далее), и адреса заранее неизвестны. Поэтому остаётся небольшое окно для rebinding: тот, кто управляет DNS своего домена, может отдать проверке безопасности публичный IP, а настоящему запросу чуть позже частный. В Cloudflare это не эксплуатируется: Workers не могут обращаться к частным IP и endpoint метаданных `169.254.169.254`, поэтому rebind ни к чему не приведёт. Только не давайте worker доступа внутрь: не подключайте Tunnel, Service или привязку к частной сети (ему нужен лишь `RATE_LIMIT_KV`). Если вы запускаете его вне Cloudflare (workerd, Node и подобное) внутри VPC, среда _может_ обращаться к внутренним IP. Поставьте перед ней исходящий файрвол и заблокируйте частные диапазоны: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16` и их аналоги IPv6. Код приложения не может этого обеспечить, когда вы вне Cloudflare. ## Отключение прокси Если CORS-прокси вам не нужен, задайте переменной окружения пустую строку: ``` VITE_CORS_PROXY_URL= ``` **Примечание**: без прокси подпись сертификатами, которым нужна внешняя загрузка цепочки (например, FNMT или некоторые корпоративные CA), завершится ошибкой "Failed to fetch". ## Устранение неполадок ### "Signing error: TypeError: Failed to fetch" Обычно это означает одно из двух: 1. **CORS-прокси не настроен**: задайте `VITE_CORS_PROXY_URL` и пересоберите проект 2. **Смешанное содержимое заблокировано**: ваш сайт работает по HTTPS, а URL издателя сертификата по HTTP. CORS-прокси решает эту проблему. 3. **CORS-прокси отклоняет ваш источник**: проверьте, что ваш домен есть в массиве `ALLOWED_ORIGINS` в `cors-proxy-worker.js` ### "403 Forbidden" от прокси Вашего домена нет в списке `ALLOWED_ORIGINS`. Отредактируйте `cors-proxy-worker.js`: ```js const ALLOWED_ORIGINS = ['https://your-domain.com']; ``` Затем разверните заново: `npx wrangler deploy` ### Проверка прокси ```bash curl -H "Origin: https://your-domain.com" \ "https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt" ``` ### Сертификаты, работающие без прокси Некоторые сертификаты содержат полную цепочку в файле P12/PFX и не требуют внешней загрузки: - Самоподписанные сертификаты - Некоторые коммерческие CA, которые включают промежуточные сертификаты - Сертификаты, для которых вы вручную собрали полную цепочку