Результат `req.socket.authorized` неверен с использованием nodejs https и экспресс [взаимной аутентификации]

Я пытаюсь настроить https-сервер с взаимной аутентификацией.
Я создал ключ и сертификат для сервера (автоподписанный).

Теперь я использую firefox для подключения к серверу без предоставления сертификата клиента.
Это должно привести кreq.socket.authorized будучи false(как указано здесь), но по какой-то причине после некоторых обновлений (и ничего не меняя) сообщение меняется справа
Unauthorized: Client certificate required (UNABLE_TO_GET_ISSUER_CERT)
к
Client certificate was authenticated but certificate information could not be retrieved.

Для меня это неожиданно, потому что это означает, что req.socket.authorized == trueдаже без клиентских сертификатов. Может кто-нибудь объяснить мне, почему это происходит?


Вот мой код:

const express = require('express')
const app = express()
const fs = require('fs')
const https = require('https')

// ...

const opts = { key: fs.readFileSync('./cryptoMaterial/private_key.pem'),
               cert: fs.readFileSync('./cryptoMaterial/certificate.pem'),
               requestCert: true,
               rejectUnauthorized: false,
               ca: [ fs.readFileSync('./cryptoMaterial/certificate.pem') ]
             }

const clientAuthMiddleware = () => (req, res, next) => {
    if (!req.secure && req.header('x-forwarded-proto') != 'https') {
        return res.redirect('https://' + req.header('host') + req.url);
    }

    // Ensure that the certificate was validated at the protocol level
    if (!req.socket.authorized) { // <-- THIS SHOULD BE ALWAYS FALSE
        res.status(401).send(
            'Unauthorized: Client certificate required ' + 
                '(' + req.socket.authorizationError + ')'
        );
        return
    }

    // Obtain certificate details
    var cert = req.socket.getPeerCertificate();
    if (!cert || !Object.keys(cert).length) {
        // Handle the bizarre and probably not-real case that a certificate was
        // validated but we can't actually inspect it
        res.status(500).send(
            'Client certificate was authenticated but certificate ' +
                'information could not be retrieved.'
        );
        return
    }
    return next();
};
app.use(clientAuthMiddleware());

// ...

https.createServer(opts, app).listen(PORT)

1 ответ

Я столкнулся с той же проблемой некоторое время назад и создал проблему на github. Похоже, это преднамеренное поведение. См. https://github.com/nodejs/node/issues/35317 .

Чтобы процитировать bnoordhuis, отвечающего на «Я предполагаю, что это может быть связано с некоторой логикой повторного использования соединения TLS». в выпуске:

Закройте, повторно используется не соединение, а сеанс TLS. :-)

Повторное использование сокращает рукопожатие (и прерывает обмен клиентскими сертификатами), поскольку повторно использует ранее установленные параметры сеанса. Это по спецификации и обычно то, что вы хотите. Chromium, вероятно, создает новый сеанс при перезагрузке.

[...]

является когда во время рукопожатия произошла ошибка проверки (например, недействительный или ненадежный сертификат), но в противном случае.

Новое соединение, запущенное из возобновленного сеанса, не выполняет эту проверку и, следовательно, предполагает . Природа сеансов TLS такова, что я не уверен, что это можно исправить, даже если бы мы захотели.


В качестве обходного пути вы должны отключить повторное согласование TLS и форсировать новый сеанс TLS для каждого подключения, что, насколько мне известно, можно сделать только на TLSv1.2.

Вот пример того, как я достиг этого с помощью Typescript:

      import fs from 'fs';
import path from 'path';
import https from 'https';
import tls from 'tls';
import express from 'express';

if (tls.DEFAULT_MAX_VERSION !== "TLSv1.2") {
    throw Error('Specify --tls-max-v1.2 as a node option (see https://github.com/nodejs/node/issues/35317)');
}
    
const httpsOptions = {
    key: fs.readFileSync(path.join('certs', 'key')),
    cert: fs.readFileSync(path.join('certs', 'cert')),
    ca: fs.readFileSync(path.join('certs', 'ca')),
    // crl: fs.readFileSync(path.join('certs', 'crl')), /* Enable this if you have a CRL */
    requestCert: true,
    rejectUnauthorized: false
};

expressServer = https.createServer(httpsOptions, expressApp);

/* Authentication middleware */
expressApp.use((req,res,next) => {
    let tlsSocket = (req.socket as tls.TLSSocket);
    if (tlsSocket.isSessionReused()) {
        /* Force renegotiation (see https://github.com/nodejs/node/issues/35317) */
        tlsSocket.renegotiate({rejectUnauthorized: false, requestCert: true}, (err) => {
            if (!(tlsSocket as tls.TLSSocket).authorized) {
                console.log('Unauthorized');
                return res.status(401).send('Unauthorized');
            }
        }
    }
    else {
        if (!(tlsSocket as tls.TLSSocket).authorized) {
            console.log('Unauthorized');
            return res.status(401).send('Unauthorized');
        }
    }
    next();
});
Другие вопросы по тегам