使用nodejs https和express [相互身份验证]时,“req.socket.authorized”的结果不正确

vlf7wbxs  于 2023-02-18  发布在  Node.js
关注(0)|答案(1)|浏览(132)

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

esbemjvw1#

不久前我也遇到过同样的问题,并在github上创建了一个问题。看起来像是故意的行为。请参见https://github.com/nodejs/node/issues/35317
引用bnoordhuis回答 "我猜这可能是由于一些TLS连接重用逻辑。" 的问题:
关闭,重用的不是连接而是TLS会话。:-)
Reuse缩短了握手时间(并切断了客户端证书交换),因为它重用了之前建立的会话参数。这是每个规范,通常也是你想要的。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';

const expressApp = 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
};

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();
});

相关问题