1

我正在尝试设置具有相互身份验证的 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)
4

1 回答 1

1

不久前我遇到了同样的问题,并在 github 上创建了一个问题。这似乎是故意的行为。见https://github.com/nodejs/node/issues/35317

引用bnoordhuis“我猜这可能是由于某些 TLS 连接重用逻辑”的回答。在问题中:

关闭,重用的不是连接而是 TLS 会话。:-)

重用缩短了握手时间(并切断了客户端证书交换),因为它重用了先前建立的会话参数。这是每个规格,通常是你想要的。Chromium 可能会在您重新加载时创建一个新会话。

[...]

socket.authorizedfalse在握手期间发生验证错误(例如无效或不受信任的证书),但true不是。

从恢复的会话开始的新连接不会进行该验证,因此假定socket.authorized = true. 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();
});
于 2021-01-18T14:16:45.170 回答