3
votes

I am using the http-proxy-middleware (https://www.npmjs.com/package/http-proxy-middleware) to implement a proxy to another REST API that has client-side certificate based authentication enabled (requestCert: true, rejectUnauthorized: true).

Client calls to the Proxy API ( https://localhost:3000/auth ) where http-proxy-middleware is configured and is supposed to proxy it to another REST API ( https://localhost:3002/auth ) that has client-side certificate based authentication enabled (requestCert: true, rejectUnauthorized: true).

I don't want any specific authentication to happen at the proxy. When I invoke the proxy with a path that will route to this target end-point with client-side certs based authentication, it is failing with error message:

Error received in proxy server:

[HPM] Rewriting path from "/auth" to ""
[HPM] GET /auth ~> https://localhost:3002/auth

RAW REQUEST from the target {
  "host": "localhost:3000",
  "connection": "close"
}
redirecting to auth
[HPM] Error occurred while trying to proxy request  from localhost:3000 to https://localhost:3002/auth (EPROTO) (https://nodejs.org/api/errors.html#errors_common_system_errors)

Error received in client side:

Proxy error: Error: write EPROTO 28628:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:c:\ws\deps\openssl\openssl\ssl\record\rec_layer_s3.c:1536:SSL alert number 40

I don't need the proxy to validate/act on client-side certs coming with the incoming request in any way (I have set secure: false for this), but rather just forward it to the target end point. We are seeing the the certs received from the client are not being passed/proxied/forwarded to the target end-point and hence cert based auth fails on the target end-point.

The client request when sent to the target end-point directly is working, but NOT when sent via http-proxy-middleware proxy.

My test server, client code is given below for reference.

Is there some way to configure the http-proxy-middleware so that it forwards/proxies the client-side certs received from the client to the target end-point so that the client-side certs sent by the client are available for cert based validation on the target REST end-point?

Could you please guide me on how to do this with http-proxy-middleware package or any other suitable way? Thanks in advance.

Server code

// Certificate based HTTPS Server

var authOptions = {
    key: fs.readFileSync('./certs/server-key.pem'),
    cert: fs.readFileSync('./certs/server-crt.pem'),
    ca: fs.readFileSync('./certs/ca-crt.pem'),
    requestCert: true,
    rejectUnauthorized: true
};  

var authApp = express();
authApp.get('/auth', function (req, res) {
    res.send("data from auth");
});

var authServer = https.createServer(authOptions, authApp);
authServer.listen(3002);


// HTTP Proxy Middleware

var authProxyConfig = proxy({
    target: 'https://localhost:3002/auth',
    pathRewrite: {
        '^/auth': '' // rewrite path
    },
    changeOrigin: true,
    logLevel: 'debug',
    secure: false,
    onProxyReq: (proxyReq, req, res) => {
        // Incoming request ( req ) : Not able to see the certificate that was passed by client.
        // Refer the following client code for the same
    },
    onError: (err, req, res) => {
         res.end(`Proxy error: ${err}.`);
    }
});

proxyApp.use('/auth', authProxyConfig);

var unAuthOptions = {
    key: fs.readFileSync('./certs/server-key.pem'),
    cert: fs.readFileSync('./certs/server-crt.pem'),
    ca: fs.readFileSync('./certs/ca-crt.pem'),
    requestCert: false,
    rejectUnauthorized: false
};

var proxyServer = https.createServer(unAuthOptions, proxyApp);
proxyServer.listen(3000);

Client Code

var fs = require('fs');
var https = require('https');

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
var options = {
    hostname: 'localhost',
    port: 3000,
    path: '/auth',
    method: 'GET',
    key: fs.readFileSync('./certs/client1-key.pem'),
    cert: fs.readFileSync('./certs/client1-crt.pem'),
    ca: fs.readFileSync('./certs/ca-crt.pem')
};

var req = https.request(options, function (res) {
    res.on('data', function (data) {
        process.stdout.write(data);
    });
});
req.end();
1
Have you tried to pass the certificates from the client via httpagent? Does this work?shashi
Thanks for sharing the hint. Yes, I have tried passing the certificate via HTTP agent but it does not work, the client receives the same error as posted.Neeraj
@Neeraj Did you ever figure out a solution to your problem? I am running into the same problem.user2566987

1 Answers

3
votes

You specifically say // Incoming request ( req ) : Not able to see the certificate that was passed by client., so perhaps you have already looked at getPeerCertificate and found the connection closed.

That said, in the onProxyReq handler, you might try adding the cert to the proxyReq from the req with the getPeerCertificate method (docs).

This SO answer + comment shows how to get and convert to valid cert.

const parseReqCert = (req) => {
   const { socket } = req; 
   const prefix = '-----BEGIN CERTIFICATE-----'; 
   const postfix = '-----END CERTIFICATE-----'; 
   const pemText = socket.getPeerCertificate(true).raw.toString('base64').match(/.{0,64}/g);

   return [prefix, pemText, postfix].join("\n");
}

const addClientCert = (proxyReq, req) => {
   proxyReq.cert = parseReqCert(req);
   return proxyReq;
}

const authProxyConfig = proxy({
    ...
    onProxyReq: (proxyReq, req, res) => {
       addClientCert(proxyReq, req);
    },
    ...
})