Перейти к содержимому

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',
    ];

    Важно

    Без этого изменения прокси отклонит все запросы с вашего сайта с ошибкой 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, которые включают промежуточные сертификаты
  • Сертификаты, для которых вы вручную собрали полную цепочку

Двойное лицензирование: AGPL-3.0 и коммерческая лицензия.