I am attempting to set up a node.js server that uses HTTPS. I will then write a scripts in Perl to make a HTTPS request to the server and measure latency of the round trip.
Here is my node.js:
var express = require('express');
var https = require('https');
var fs = require('fs');
var key = fs.readFileSync('encrypt/rootCA.key');
var cert = fs.readFileSync('encrypt/rootCA.pem');
// This line is from the Node.js HTTPS documentation.
var options = {
key: key,
cert: cert
};
https.createServer(options, function (req, res) {
res.writeHead(200);
res.end("hello world - https\n");
}).listen(8088);
Key/cert generation was done as follows:
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem
This is my Perl script:
#!/usr/bin/perl
use LWP::UserAgent;
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new(GET => 'https://127.0.0.1:8080');
my $res = $ua->request($req);
if ($res->is_success) {
print $res->as_string;
} else {
print "Failed: ", $res->status_line, "\n";
}
Returning the error:
Failed: 500 Can't verify SSL peers without knowing which Certificate Authorities to trust
The node.js documentation describes how to set up an HTTPS server but it is vague about generating primary cert and intermediate cert.
https://medium.com/netscape/everything-about-creating-an-https-server-using-node-js-2fc5c48a8d4e
To make LWP::UserAgent ignore server certificate use the following configuration:
my $ua = LWP::UserAgent->new;
$ua->ssl_opts(
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
verify_hostname => 0
);
The typical answer to such question is to disable certificate validation at all. But, this is totally insecure and essentially disables most of the protection offered by HTTPS. If validation is disabled completely a man in the middle attacker just can use an arbitrary certificate to intercept the connection and sniff and modify the data. Thus, don't do it.
The correct way to deal with such certificates is to add these certificates as trusted. This can be done with the SSL_ca_file argument:
my $ua = LWP::UserAgent->new;
$ua->ssl_opts(SSL_ca_file => 'rootCA.pem');
$ua->get('https://127.0.0.1:8080');
By explicitly trusting the self-signed server certificate as CA it will no longer throw "certificate verify failed".
But, unless your server certificate is actually issued to "127.0.0.1" you will now get "hostname verification failed" since the subject of the certificate does not match the domain of the URL. This can be fixed by setting the expected hostname:
my $ua = LWP::UserAgent->new;
$ua->ssl_opts(
SSL_ca_file => 'rootCA.pem',
SSL_verifycn_name => 'www.example.com',
);
$ua->get('https://127.0.0.1:8080');
Note that SSL_ca_file needs the self-signed certificate to have the CA flag set to true, i.e. that the certificate is a CA certificate which can be used to issue other certificates. If this is not the case with your certificate or if you just want to accept a specific certificate no matter if it is expired, revoked, does not match the hostname etc you can do the validation by using the fingerprint of the certificate.
my $ua = LWP::UserAgent->new;
$ua->ssl_opts(SSL_fingerprint => 'sha1$9AA5CFED857445259D90FE1B56B9F003C0187BFF')
$ua->get('https://127.0.0.1:8080');
The fingerprint here is the same you get with openssl x509 -noout -in rootCA.pem -fingerprint -sha1, only the algorithm added in front (sha1$...) and the colons removed.
Related
I'm hitting issues consuming a REST API I have written in Node.JS (running on localhost as https) from PL/SQL (using utl_http). This is a proof of concept I'm working on for work, but I've ran into many issues.
Technical environment:
Oracle EE 19.3 setup as multi-tenant with a single pluggable database (PDB1) running Windows 10 Pro on laptop
This is what I've done.
I have created a self signed certificate using openssl as follows:
REM created private key for server - key.pem
openssl genrsa -out key.pem
REM created CSR using private key generated above - csr.prm
openssl req -new -key key.pem -out csr.pem
REM create certificate - cert.pem
openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem
This has been used successfully on my Node.JS server code (running on https://localhost:4010). I connect using my browser and get past the warnings telling me the certificate is not trusted, etc. I expected this as I've self signed the certificate so no issues here.
The Node.JS server code is below:
const https = require("https");
const fs = require("fs");
const express = require("express");
const app_https = express();
securitydetails = {
key: fs.readFileSync("key.pem"),
cert: fs.readFileSync("cert.pem")
};
// https server port 4010 redirected to express
https
.createServer(securitydetails, app_https)
.listen(4010, function() {
console.log('https (secure) server started on port 4010');
});
// express https root endpoint
app_https.get('/', function(req,res) {
res.write('<h1>HTTPS SECURED</h1>');
res.write('<h1>The Heroes API</h1>');
res.write('<p>Available endpoints:</p>');
res.write('<p>https://localhost:4010/heroes</p>');
res.send();
})
//https: heroes endpoint
app_https.get('/heroes', function(req, res) {
let heroesObj = {
"securityStatus": "HTTPS (SECURE)",
"heroes": [
{"name": "Batman", "antagonist": "Joker"},
{"name": "Black Panther", "antoganist": "Erik Killmonger"},
{"name": "Storm", "antoganist": "Shadow King"},
{"name": "Wonder Woman", "antoganist": "Ares"}
]
};
heroesDetail = JSON.stringify(heroesObj);
res.write(heroesDetail);
res.send();
});
On the Oracle side, I've created a directory for the wallet, and added the cert.pem file to it like so:
REM create wallet
orapki wallet create -wallet E:\oracle\product\19.3\admin\wallet -pwd Stackoverflowisuseful1! -auto_login
REM add certificate cert.pem to wallet
orapki wallet add -wallet D:\oracle\product\19.3\admin\wallet -trusted_cert -cert "E:\certificates\cert.pem" -pwd Stackoverflowisuseful1!
Then checked to see if all is well:
REM display wallet
orapki wallet display -wallet E:\oracle\product\19.3\admin\wallet -complete
All looked well.
I added an ACE for the server:
BEGIN
DBMS_NETWORK_ACL_ADMIN.append_host_ace (
host => 'localhost',
lower_port => 4010,
upper_port => 4010,
ace => xs$ace_type(privilege_list => xs$name_list('http'),
principal_name => 'test1api',
principal_type => xs_acl.ptype_db));
END;
/
The client code written in PL/SQL as follows and run as test1api.
set serveroutput on
DECLARE
url VARCHAR2(50) := 'https://localhost:4010/heroes';
http_request UTL_HTTP.req;
http_response UTL_HTTP.resp;
http_value varchar2(1024) := NULL;
BEGIN
-- Make a HTTP request and get the response.
http_request := UTL_HTTP.begin_request(url);
http_response := UTL_HTTP.get_response(http_request);
UTL_HTTP.READ_LINE(http_response, http_value, TRUE);
DBMS_OUTPUT.PUT_LINE(http_value);
UTL_HTTP.end_response(http_response);
END;
/
And it errors with this:
Error report -
ORA-29273: HTTP request failed
ORA-29024: Certificate validation failure
ORA-06512: at "SYS.UTL_HTTP", line 380
ORA-06512: at "SYS.UTL_HTTP", line 1148
ORA-06512: at line 11
29273. 00000 - "HTTP request failed"
*Cause: The UTL_HTTP package failed to execute the HTTP request.
*Action: Use get_detailed_sqlerrm to check the detailed error message.
Fix the error and retry the HTTP request.
Using get_detailed_sqlerrm we get ...
select utl_http.get_detailed_sqlerrm from dual;
ORA-29024: Certificate validation failure
I've got this whole setup to work against http, so it's certainly my lack of understanding around certificate generation and/or adding certificates to the Oracle wallet and utilising them.
Has anyone got any ideas?
Thanks.
I need to run local Express server on https protocol. I used instructions from this site and the similar. But when I tried to open the page in browser, I get Your connection is not private error. When I opened Security tab from Developer tools, I saw This site is missing a valid, trusted certificate (net::ERR_CERT_INVALID) error. When I tried to send request via Postman, my server didn't response, and the curl request returns:
curl: (60) SSL certificate problem: unable to get local issuer certificate
Here is server code:
const app = require('express')();
const fs = require('fs');
const https = require('https');
const options = {
key: fs.readFileSync('./key.pem'),
cert: fs.readFileSync('./cert.pem')
};
app.get('/', (req, res) => {
res.send('hello world');
});
https
.createServer(options, app)
.listen(3000, '127.0.0.1', () => {
console.log('Run on: https://127.0.0.1:3000');
});
I've created certificates with the next command:
$ openssl req -nodes -sha256 -new -x509 -keyout key.pem -out cert.pem -days 365 -config req.cnf
where req.cnf file has the following content:
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = US
ST = VA
L = SomeCity
O = MyCompany
OU = MyDivision
CN = 127.0.0.1:3000
[v3_req]
keyUsage = critical, digitalSignature, keyAgreement
extendedKeyUsage = serverAuth
subjectAltName = #alt_names
[alt_names]
DNS.1 = IP:127.0.0.1:3000
I tried to use 443 port as well, but, unfortunately, I've got the same errors. Also, I tried to open page https://127.0.0.1:3000 with Incognito mode - nothing happened - errors are the same.
My questions are:
Where am I wrong with creating certificates?
Why I can't send a request to my server via Postman/curl?
What you created is a self-signed certificate, by default no networking application will accept them as they cannot verify them, so they assume the worse (MITM attack).
If you only need this for local checking then you can follow this
Getting Chrome to accept self-signed localhost certificate
Postman error is probably the equivalent error for chrome apps.
Using node.js, I'd like to write code to programmatically do the equivalent of the following:
openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
openssl rsa -passin pass:x -in server.pass.key -out server.key
rm server.pass.key
openssl req -new -key server.key -out server.csr
openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
When complete, I need the RSA key server.key and the self-signed SSL certificate server.crt.
forge looks the most promising, but so far I haven't figured out how to get it to work. I have the following code:
var pki = forge.pki;
var keys = pki.rsa.generateKeyPair(2048);
var privKey = forge.pki.privateKeyToPem(keys.privateKey);
var pubKey = forge.pki.publicKeyToPem(keys.publicKey);
But when I write the pubKey to a file, I've noticed it starts with ...
-----BEGIN PUBLIC KEY-----
MIIB...
-----END PUBLIC KEY-----
... and isn't recognized, whereas using openssl above it starts with:
-----BEGIN CERTIFICATE-----
MIID...
-----END CERTIFICATE-----
Since the original link went dead, I've made my own code that generates a self-signed certificate using node-forge (which it looks like they already have based on the original question), so I thought I'd put it here for someone who wants it
Simply creating a public and private key pair isn't enough to work as a certificate, you have to put in attributes, node-forge is incredibly useful this way, as its pki submodule is designed for this.
First, you need to create a certificate via pki.createCertificate(), this is where you'll assign all of your certificate attributes.
You need to set the certificate public key, serial number, and the valid from date and valid to date. In this example, the public key is set to the generated public key from before, the serial number is randomly generated, and the valid from and to dates are set to one day ago and one year in the future.
You then need to assign a subject, and extensions to your certificate, this is a very basic example, so the subject is just a name you can define (or let it default to 'Testing CA - DO NOT TRUST'), and the extensions are just a single 'Basic Constraints' extension, with certificate authority set to true.
We then set the issuer to itself, as all certificates need an issuer, and we don't have one.
Then we tell the certificate to sign itself, with the private key (corresponding to its public key we've assigned) that we generated earlier, this part is important when signing certificates (or child certificates), they need to be signed with the private key of its parent (this prevents you from making fake certificates with a trusted certificate parent, as you don't have that trusted parent's private key)
Then we return the new certificate in a PEM-encoded format, you could save this to a file or convert it to a buffer and use it for a https server.
const forge = require('node-forge')
const crypto = require('crypto')
const pki = forge.pki
//using a blank options is perfectly fine here
async function genCACert(options = {}) {
options = {...{
commonName: 'Testing CA - DO NOT TRUST',
bits: 2048
}, ...options}
let keyPair = await new Promise((res, rej) => {
pki.rsa.generateKeyPair({ bits: options.bits }, (error, pair) => {
if (error) rej(error);
else res(pair)
})
})
let cert = pki.createCertificate()
cert.publicKey = keyPair.publicKey
cert.serialNumber = crypto.randomUUID().replace(/-/g, '')
cert.validity.notBefore = new Date()
cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1)
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1)
cert.setSubject([{name: 'commonName', value: options.commonName}])
cert.setExtensions([{ name: 'basicConstraints', cA: true }])
cert.setIssuer(cert.subject.attributes)
cert.sign(keyPair.privateKey, forge.md.sha256.create())
return {
ca: {
key: pki.privateKeyToPem(keyPair.privateKey),
cert: pki.certificateToPem(cert)
},
fingerprint: forge.util.encode64(
pki.getPublicKeyFingerprint(keyPair.publicKey, {
type: 'SubjectPublicKeyInfo',
md: forge.md.sha256.create(),
encoding: 'binary'
})
)
}
}
//you need to put the output from genCACert() through this if you want to use it for a https server
/* e.g
let cert = await genCACert();
let buffers = caToBuffer(cert.ca);
let options = {};
options.key = buffers.key;
options.cert = buffers.cert;
let server = https.createServer(options, <listener here>);
*/
function caToBuffer(ca) {
return {
key: Buffer.from(ca.key),
cert: Buffer.from(ca.cert)
}
}
Do with this what you will.
Okay, as you probably realized, I wasn't generating a certificate. It required quite a bit more work, which you can find here.
Essentially, after a bunch of setup, I had to create, sign, and convert the certificate to Pem:
cert.sign(keys.privateKey);
var pubKey = pki.certificateToPem(cert);
Hope this helps someone else!
I'm using Alamofire on Swift 2.0 and implemented the ServerTrustPolicy for my local server as you can see here:
let defaultManager: Alamofire.Manager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
// "localhost": .PinCertificates(
// certificates: ServerTrustPolicy.certificatesInBundle(),
// validateCertificateChain: false,
// validateHost: false
// )
"localhost": .DisableEvaluation
]
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
return Alamofire.Manager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}()
The problem is when I do a request I always get the same error no matter if I use .PinCertificates or .DisableEvaluation:
Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made."
UserInfo={
NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x60c00004d980>,
NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?,
_kCFStreamErrorDomainKey=3,
_kCFStreamErrorCodeKey=-9802,
NSErrorPeerCertificateChainKey=<CFArray 0x6060001498a0 [0x10bb3c7b0]>{
type = immutable, count = 1, values = (
0 : <cert(0x61600006ed80) s: localhost i: localhost>
)},
NSUnderlyingError=0x6040000ad750 {
Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)"
UserInfo={
_kCFStreamPropertySSLClientCertificateState=0,
kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x60c00004d980>,
_kCFNetworkCFStreamSSLErrorOriginalValue=-9802,
_kCFStreamErrorDomainKey=3,
_kCFStreamErrorCodeKey=-9802,
kCFStreamPropertySSLPeerCertificates=<CFArray 0x6060001498a0 [0x10bb3c7b0]>{
type = immutable, count = 1, values = (
0 : <cert(0x61600006ed80) s: localhost i: localhost>
)}
}
},
NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made.,
NSErrorFailingURLKey=https://localhost:3000/auth/requestToken?auth_appId=d018ccd505db2cb1d5aacabb03fc2f3a,
NSErrorFailingURLStringKey=https://localhost:3000/auth/requestToken?auth_appId=d018ccd505db2cb1d5aacabb03fc2f3a,
NSErrorClientCertificateStateKey=0
}
I tried using
curl --cacert ./ca.pem https://localhost:3000
which throws works just fine
I use a self signed certificate which I generated like this:
Create Root CA private key
openssl genrsa -out ca.key.pem 2048
Self sign my Root CA
openssl req -x509 -new -nodes -key ca.key.pem -days 1024 -out ca.crt.pem
Create the server certificate by generating the key, then csr, then signing it with the CA
openssl genrsa -out server.key.pem 2048
openssl req -new -key server.key.pem -out server.csr.pem
openssl x509 -req -in server.csr.pem -CA ca.crt.pem -CAkey ca.key.pem -CAcreateserial -out server.crt.pem -days 500
I use nodejs's https module as my web server which I configure like this:
require('ssl-root-cas')
.inject()
.addFile('./ca.crt.pem');
var privateKey = fs.readFileSync('./server.key.pem');
var certificate = fs.readFileSync('./server.crt.pem');
var credentials = { key: privateKey, cert: certificate };
var server = https.createServer(credentials, app);
server.listen(port);
I had a look into the Alamofire source code and found out that in the delegate method:
public func URLSession(
session: NSURLSession,
didReceiveChallenge challenge: NSURLAuthenticationChallenge,
completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void))
{
var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
var credential: NSURLCredential?
if let sessionDidReceiveChallenge = sessionDidReceiveChallenge {
(disposition, credential) = sessionDidReceiveChallenge(session, challenge)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let host = challenge.protectionSpace.host
if let
serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicyForHost(host),
serverTrust = challenge.protectionSpace.serverTrust
{
if serverTrustPolicy.evaluateServerTrust(serverTrust, isValidForHost: host) {
disposition = .UseCredential
credential = NSURLCredential(forTrust: serverTrust)
} else {
disposition = .CancelAuthenticationChallenge
}
}
}
completionHandler(disposition, credential)
}
The call to serverTrustPolicy.evaluateServerTrust(serverTrust, isValidForHost: host) actually returns true. This means that the error is probably is happening somwhere in the completionHandler code, right?
Am I doing something terribly wrong at handling the certificate stuff?
PS: defaultManager is not beeing deallocated :)
First off, make sure your defaultManager isn't being deallocated. You'll always get the -999 if it is. With that said, if you use .DisableEvaluation then I would assume you have some bigger issues with your SSL configuration server-side.
Are you able to cURL your web service successfully?
I'll update my answer accordingly as you provide more info.
I'm writing a proxy support HTTPS-HTTPS proxy. Before i use Python as the main programming language, but i'm interested in node.js now, so i prepare to migrate.
The largest problem i'm facing is that i don't know how to generate CA and other server certificates in node.js. In Python, there is pyOpenSSL which is awesome. I don't find something similar in node.js until now.
Maybe i should use openssl-fork method? But how to handle the interactive operation in openssl.
Thank you.
Use shell for certificate:
openssl genrsa -out server-key.pem 1024
openssl req -new -key server-key.pem -out server-csr.pem
openssl x509 -req -in server-csr.pem -signkey server-key.pem -out server-cert.pem
Then use them in node.js
var https = require('https');
https.createServer({
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem')
},
function (req,res) {
...
})
EDIT:
You can also give a try to this project in NPM :
https://npmjs.org/package/openssl-wrapper
I found it searching the NPM repo : https://npmjs.org/search?q=openssl
I did not tried or checked it myself, but it looks like a way to generate the certificate using node, which is the original question.
var openssl = require('openssl-wrapper');
var password = 'github';
return openssl.exec('genrsa', {des3: true, passout: 'pass:' + password, '2048': false}, function(err, buffer) {
console.log(buffer.toString());
});
I'd be interested by a feedback. ;)
In case somebody does want to programatically create CSRs from node.js, I have created a nodejs module which uses openssl to create a private key and a CSR.
Edit: Use pem instead. It is much more complete and probably more reliable.
Edit2: Actually, pem is also just a simple wrapper over openssh. For a pure js implementation, look into forge
node-forge allow that. Nothing to say more. DOES NOT use openssl shell command internally.
https://github.com/digitalbazaar/forge#x509
there is a pure javascript library called "Jsrsasign" to generate CSR certificates.
const r = require("jsrsasign");
const kp = r.KEYUTIL.generateKeypair("RSA", 2048);
const csr = r.KJUR.asn1.csr.CSRUtil.newCSRPEM({
subject: {},
sbjpubkey: kp.pubKeyObj,
sigalg: "SHA256withRSA",
sbjprvkey: kp.prvKeyObj,
});
const privateKey = r.KEYUTIL.getPEM(kp.prvKeyObj, "PKCS1PRV");
for more docs visit http://kjur.github.io/jsrsasign/
None of the node libraries seem to support the options I need, so I use the openssl executable.
import { execSync } from 'child_process'
import fs from 'fs'
import tempy from 'tempy'
const extHeader = `authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = #alt_names
[alt_names]
`
const shell = cmd => execSync(cmd, { stdio: 'pipe' })
const writeCert = (extFile, outfile) => {
const cmd = [
`openssl`,
`x509`,
`-req`,
`-in ssl/my.csr`,
`-CA ssl/root-ca.pem`,
`-CAkey ssl/root-ca.key`,
`-CAserial ssl/root-ca.srl`,
`-out ssl/${outfile}`,
`-days 1825`,
`-sha256`,
`-extfile ${extFile}`,
`-passin pass:mypassphrase`
]
shell(cmd.join(' '))
}
const createCert = domains => {
const sans = domains.map((d, i) => `DNS.${i + 1} = ${d}`)
const ext = extHeader + sans.join('\n')
const extFile = tempy.file()
fs.writeFileSync(extFile, ext, 'utf-8')
writeCert(extFile, 'out.crt')
}
Dependencies:
openssl executable
yarn add tempy