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!
Related
I want to provision a device using x509 security. The x509 certificate and private key are saved on an HSM. The private key cannot leave the HSM.
Ideally I'd like to pass the PKCS11 URI for both objects when creating the x509 object for transport.
const deviceCert: X509 = {
cert: cert,
clientCertEngine: "pkcs11",
keyFile: 'pkcs11:object=privateKey;type=private?pin-value=1234',
};
const securityClient = new X509Security(this.registrationId, deviceCert);
this.provisioningClient = ProvisioningDeviceClient.create(
this.provisioningHost,
this.idScopeOperator,
new ProvisioningTransport(),
securityClient
);
I generated jwt key (RSA Key: private and public) using the following commands
openssl genrsa -out private.key 2048
openssl rsa -in private.key -pubout -out public.key
After that, I created a file called private2.key, I gave it the same private.key content (the only difference is that I removed all break lines except the first and last one)
Also, I created a file called public2.key, I gave it the same public.key content (the only difference is that I removed all break lines except the first and last one)
private.key:
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAy6fZ1JmG4BX02R2Odj/zso7hJwq7qsTZs6SVcNdiHeDxCRT5
UQvTO/qxSIips89iyDTahPjFaquTvmF+oTTOXqA80SYtE8JK2r2/QJ9te5je/7jB
8yxrRsp5heZ4uQv9yThygvOTItuHDN+w4Le2QReegunRDnbfTrsogwUmVAyam4qF
iBDx3zEoTp98H4lRYB4Mpekt0Z2fh8q4g1vBaJLSc+Vc8MXLXsuQTNZ0x61fV7p9
3ewJAHlSxNYRdEFuhNMjU7hp2DR6zQqqyEWslFyq0JW6pd7hHjuCSoBcW5e4lrEg
ZgLspbWbl0rlIOQFizy+IJumXKDmM90WHp2v9wIDAQABAoIBAQDDiSmw4qeJSAuK
2sIJ72VAr8amAbwmPlL4FLIXYfUm0u8a1TR8CGqMUCsfhXfK2PfzWivlOCX0QUDd
riYzCcyLNjauaYUmT4onc7/JgElSPnj99prhrGhj08vSMHMA2O6W4Mexy8Qd18FX
Lv9ZA4rN/KuI7o524NOPeEtxYORoFOslq02PBBuPxTToRs2f++px1HOkLQBt+Mkn
Lqh/gK+0w4CD3JDc/yM2jH4z8jZw8E33vmMGdj59mjhsVlgvOine0mvZnJRU9BWS
sgdtHwnbBIuQBkiXaMWgY83rrP/hSCCcCcKUNyUgxtlWwMcSZrM/gkt2tts7EkRD
e8evVvMhAoGBAO0leVSvjQ8NVqNQajV+o6Z58j2WVTOFG4qZWhuQA4O0oNT4Gcf4
w+DYLQKntmwQ9ShWciw/f0nJCTknCSo0TVmIl5yhJVtudWlIosMw6YerpK7VW+6x
J53ZkHn+6EGgBEYuJjHXFw+vY3VnOmsqO3yCON2oUtw2RA1/tLkiJqZzAoGBANvY
wa5e8dHpdqVKQX1YZFkA1O8y20lDNgGZNz/8qiY21kmMiVAXsTK9wWiF7Uys/jp0
btwWY3u/MHpzP/zz0OTcOJ5b9u+NVcSYzF6FrUGGpOrb9Oh124x2UCKL9exsz8xJ
av4PGfof8uhPGxRSav+DqbmrY+jqVuz1Dn3YnqttAoGAJPPF6DBCpqnJakFJi3Rk
Q7iUyov2UsTW+c3TgJ/8LDWlKgpO2h4lR4/n05YWkthBmzt9Ju/uAa1VxpYSk4T6
2Iy0My/ZBlo76V/sHMYuXXmde7C7VoI8ThhsrtXNkwxAHj9qrDF74nHN6algLPqz
sj8IZWGpJ689A21217I+m4kCgYA21VdpgHDcJFjdXSn8c4GD2XtCtfKP0V21BFwN
b52YrnDAI3dULLSbrUyCH3VSfItkVQoZhtQFV2hmAjzhgIaHro3IobNziFLuGBZR
NRJDl6umkHoDSPIblJ7kHviVoYYqs90lxOp7wmA5pRFh/jSFyncYwjDHNTu9Glok
9VSN+QKBgDdjjMY41JU21fv2FWvKCwHXJwRSFVT8HNlMC0H9k6x2SZAmhNBtfIlU
xY5I5Cnsmkvw2zNxhS2conLAElKqoVSUprv6BvjW+p80dXisctfMTpv/2YM/o0Fs
Iu1ySS+kMK9OAb2kqz9uL38srfN5zdp3FNLrMaE4uRBei8y51D1/
-----END RSA PRIVATE KEY-----
private2.key:
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAy6fZ1JmG4BX02R2Odj/zso7hJwq7qsTZs6SVcNdiHeDxCRT5UQvTO/qxSIips89iyDTahPjFaquTvmF+oTTOXqA80SYtE8JK2r2/QJ9te5je/7jB8yxrRsp5heZ4uQv9yThygvOTItuHDN+w4Le2QReegunRDnbfTrsogwUmVAyam4qFiBDx3zEoTp98H4lRYB4Mpekt0Z2fh8q4g1vBaJLSc+Vc8MXLXsuQTNZ0x61fV7p93ewJAHlSxNYRdEFuhNMjU7hp2DR6zQqqyEWslFyq0JW6pd7hHjuCSoBcW5e4lrEgZgLspbWbl0rlIOQFizy+IJumXKDmM90WHp2v9wIDAQABAoIBAQDDiSmw4qeJSAuK2sIJ72VAr8amAbwmPlL4FLIXYfUm0u8a1TR8CGqMUCsfhXfK2PfzWivlOCX0QUDdriYzCcyLNjauaYUmT4onc7/JgElSPnj99prhrGhj08vSMHMA2O6W4Mexy8Qd18FXLv9ZA4rN/KuI7o524NOPeEtxYORoFOslq02PBBuPxTToRs2f++px1HOkLQBt+MknLqh/gK+0w4CD3JDc/yM2jH4z8jZw8E33vmMGdj59mjhsVlgvOine0mvZnJRU9BWSsgdtHwnbBIuQBkiXaMWgY83rrP/hSCCcCcKUNyUgxtlWwMcSZrM/gkt2tts7EkRDe8evVvMhAoGBAO0leVSvjQ8NVqNQajV+o6Z58j2WVTOFG4qZWhuQA4O0oNT4Gcf4w+DYLQKntmwQ9ShWciw/f0nJCTknCSo0TVmIl5yhJVtudWlIosMw6YerpK7VW+6xJ53ZkHn+6EGgBEYuJjHXFw+vY3VnOmsqO3yCON2oUtw2RA1/tLkiJqZzAoGBANvYwa5e8dHpdqVKQX1YZFkA1O8y20lDNgGZNz/8qiY21kmMiVAXsTK9wWiF7Uys/jp0btwWY3u/MHpzP/zz0OTcOJ5b9u+NVcSYzF6FrUGGpOrb9Oh124x2UCKL9exsz8xJav4PGfof8uhPGxRSav+DqbmrY+jqVuz1Dn3YnqttAoGAJPPF6DBCpqnJakFJi3RkQ7iUyov2UsTW+c3TgJ/8LDWlKgpO2h4lR4/n05YWkthBmzt9Ju/uAa1VxpYSk4T62Iy0My/ZBlo76V/sHMYuXXmde7C7VoI8ThhsrtXNkwxAHj9qrDF74nHN6algLPqzsj8IZWGpJ689A21217I+m4kCgYA21VdpgHDcJFjdXSn8c4GD2XtCtfKP0V21BFwNb52YrnDAI3dULLSbrUyCH3VSfItkVQoZhtQFV2hmAjzhgIaHro3IobNziFLuGBZRNRJDl6umkHoDSPIblJ7kHviVoYYqs90lxOp7wmA5pRFh/jSFyncYwjDHNTu9Glok9VSN+QKBgDdjjMY41JU21fv2FWvKCwHXJwRSFVT8HNlMC0H9k6x2SZAmhNBtfIlUxY5I5Cnsmkvw2zNxhS2conLAElKqoVSUprv6BvjW+p80dXisctfMTpv/2YM/o0FsIu1ySS+kMK9OAb2kqz9uL38srfN5zdp3FNLrMaE4uRBei8y51D1/
-----END RSA PRIVATE KEY-----
public.key:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy6fZ1JmG4BX02R2Odj/z
so7hJwq7qsTZs6SVcNdiHeDxCRT5UQvTO/qxSIips89iyDTahPjFaquTvmF+oTTO
XqA80SYtE8JK2r2/QJ9te5je/7jB8yxrRsp5heZ4uQv9yThygvOTItuHDN+w4Le2
QReegunRDnbfTrsogwUmVAyam4qFiBDx3zEoTp98H4lRYB4Mpekt0Z2fh8q4g1vB
aJLSc+Vc8MXLXsuQTNZ0x61fV7p93ewJAHlSxNYRdEFuhNMjU7hp2DR6zQqqyEWs
lFyq0JW6pd7hHjuCSoBcW5e4lrEgZgLspbWbl0rlIOQFizy+IJumXKDmM90WHp2v
9wIDAQAB
-----END PUBLIC KEY-----
public2.key:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy6fZ1JmG4BX02R2Odj/zso7hJwq7qsTZs6SVcNdiHeDxCRT5UQvTO/qxSIips89iyDTahPjFaquTvmF+oTTOXqA80SYtE8JK2r2/QJ9te5je/7jB8yxrRsp5heZ4uQv9yThygvOTItuHDN+w4Le2QReegunRDnbfTrsogwUmVAyam4qFiBDx3zEoTp98H4lRYB4Mpekt0Z2fh8q4g1vBaJLSc+Vc8MXLXsuQTNZ0x61fV7p93ewJAHlSxNYRdEFuhNMjU7hp2DR6zQqqyEWslFyq0JW6pd7hHjuCSoBcW5e4lrEgZgLspbWbl0rlIOQFizy+IJumXKDmM90WHp2v9wIDAQAB
-----END PUBLIC KEY-----
When I generated jwt using private.key or private2.key and I verified it using public.key or public2.key I got a successful result
const fs = require('fs');
const jwt = require('jsonwebtoken');
const privateKey = fs.readFileSync('./private.key');
const publicKey = fs.readFileSync('./public.key');
const privateKey2 = fs.readFileSync('./private2.key');
const publicKey2 = fs.readFileSync('./public2.key');
const token = jwt.sign({foo: 'bar'}, privateKey, {algorithm: 'RS256'});
console.log(jwt.verify(token, publicKey)); // RESULT IS: { foo: 'bar', iat: 1580192807 }
console.log(jwt.verify(token, publicKey2)); // RESULT IS: { foo: 'bar', iat: 1580192807 }
const token2 = jwt.sign({foo: 'bar'}, privateKey2, {algorithm: 'RS256'});
console.log(jwt.verify(token2, publicKey)); // RESULT IS: { foo: 'bar', iat: 1580192807 }
console.log(jwt.verify(token2, publicKey2)); // RESULT IS: { foo: 'bar', iat: 1580192807 }
My question is: Scientifically is "remove all break lines except the first and last one from jwt key (RSA Key: private and public)" does not affect the jwt, is this way safe (please provide references that prove your answer)
So I can do this (with comfortable feel)
.env
JWT_PRIVATE_KEY=MIIEowIBAAKCAQEAy6fZ1JmG4BX02R2Odj/zso7hJwq7qsTZs6SVcNdiHeDxCRT5UQvTO/qxSIips89iyDTahPjFaquTvmF+oTTOXqA80SYtE8JK2r2/QJ9te5je/7jB8yxrRsp5heZ4uQv9yThygvOTItuHDN+w4Le2QReegunRDnbfTrsogwUmVAyam4qFiBDx3zEoTp98H4lRYB4Mpekt0Z2fh8q4g1vBaJLSc+Vc8MXLXsuQTNZ0x61fV7p93ewJAHlSxNYRdEFuhNMjU7hp2DR6zQqqyEWslFyq0JW6pd7hHjuCSoBcW5e4lrEgZgLspbWbl0rlIOQFizy+IJumXKDmM90WHp2v9wIDAQABAoIBAQDDiSmw4qeJSAuK2sIJ72VAr8amAbwmPlL4FLIXYfUm0u8a1TR8CGqMUCsfhXfK2PfzWivlOCX0QUDdriYzCcyLNjauaYUmT4onc7/JgElSPnj99prhrGhj08vSMHMA2O6W4Mexy8Qd18FXLv9ZA4rN/KuI7o524NOPeEtxYORoFOslq02PBBuPxTToRs2f++px1HOkLQBt+MknLqh/gK+0w4CD3JDc/yM2jH4z8jZw8E33vmMGdj59mjhsVlgvOine0mvZnJRU9BWSsgdtHwnbBIuQBkiXaMWgY83rrP/hSCCcCcKUNyUgxtlWwMcSZrM/gkt2tts7EkRDe8evVvMhAoGBAO0leVSvjQ8NVqNQajV+o6Z58j2WVTOFG4qZWhuQA4O0oNT4Gcf4w+DYLQKntmwQ9ShWciw/f0nJCTknCSo0TVmIl5yhJVtudWlIosMw6YerpK7VW+6xJ53ZkHn+6EGgBEYuJjHXFw+vY3VnOmsqO3yCON2oUtw2RA1/tLkiJqZzAoGBANvYwa5e8dHpdqVKQX1YZFkA1O8y20lDNgGZNz/8qiY21kmMiVAXsTK9wWiF7Uys/jp0btwWY3u/MHpzP/zz0OTcOJ5b9u+NVcSYzF6FrUGGpOrb9Oh124x2UCKL9exsz8xJav4PGfof8uhPGxRSav+DqbmrY+jqVuz1Dn3YnqttAoGAJPPF6DBCpqnJakFJi3RkQ7iUyov2UsTW+c3TgJ/8LDWlKgpO2h4lR4/n05YWkthBmzt9Ju/uAa1VxpYSk4T62Iy0My/ZBlo76V/sHMYuXXmde7C7VoI8ThhsrtXNkwxAHj9qrDF74nHN6algLPqzsj8IZWGpJ689A21217I+m4kCgYA21VdpgHDcJFjdXSn8c4GD2XtCtfKP0V21BFwNb52YrnDAI3dULLSbrUyCH3VSfItkVQoZhtQFV2hmAjzhgIaHro3IobNziFLuGBZRNRJDl6umkHoDSPIblJ7kHviVoYYqs90lxOp7wmA5pRFh/jSFyncYwjDHNTu9Glok9VSN+QKBgDdjjMY41JU21fv2FWvKCwHXJwRSFVT8HNlMC0H9k6x2SZAmhNBtfIlUxY5I5Cnsmkvw2zNxhS2conLAElKqoVSUprv6BvjW+p80dXisctfMTpv/2YM/o0FsIu1ySS+kMK9OAb2kqz9uL38srfN5zdp3FNLrMaE4uRBei8y51D1/
JWT_PUBLIC_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy6fZ1JmG4BX02R2Odj/zso7hJwq7qsTZs6SVcNdiHeDxCRT5UQvTO/qxSIips89iyDTahPjFaquTvmF+oTTOXqA80SYtE8JK2r2/QJ9te5je/7jB8yxrRsp5heZ4uQv9yThygvOTItuHDN+w4Le2QReegunRDnbfTrsogwUmVAyam4qFiBDx3zEoTp98H4lRYB4Mpekt0Z2fh8q4g1vBaJLSc+Vc8MXLXsuQTNZ0x61fV7p93ewJAHlSxNYRdEFuhNMjU7hp2DR6zQqqyEWslFyq0JW6pd7hHjuCSoBcW5e4lrEgZgLspbWbl0rlIOQFizy+IJumXKDmM90WHp2v9wIDAQAB
index.js
require('dotenv').config();
const jwt = require('jsonwebtoken');
const privateKey = `-----BEGIN RSA PRIVATE KEY-----\n${process.env.JWT_PRIVATE_KEY}\n-----END RSA PRIVATE KEY-----`;
const publicKey = `-----BEGIN PUBLIC KEY-----\n${process.env.JWT_PUBLIC_KEY}\n-----END PUBLIC KEY-----`;
const token = jwt.sign({foo: 'bar'}, privateKey, {algorithm: 'RS256'});
console.log(jwt.verify(token, publicKey)); // RESULT IS: { foo: 'bar', iat: 1580192822 }
It depends.
Theoretically, the answer is no - you can't always remove the line breaks. This is simply because RFC 1421 defines situations in which you must include the line breaks:
Text lines, delimited by the character pair <CR><LF>, must be no more than 1000 characters long.
The above statement is in Section 4.3 of RFC 1421, the document where the PEM format is defined.
Practically, however, the answer is generally yes: a number of common implementations are quite lenient when parsing objects in PEM format and allow for excessively long text lengths. Specifically, OpenSSL and the Golang PEM implementation allow this (or, at least lengths longer than 1000 characters).
Try running this command on one of your certificates with the line breaks removed - it succeeds:
openssl x509 -text -noout -in certificate.crt
What should you do?
Don't concern yourself with this problem and instead store your certificates external to your application. They should be configuration values and stored externally from your application. Even better, delegate this problem to a technology that is designed to deal with it - Azure has Key Vault, AWS has Certificate Manager and KMS.
I'm generating a SAML response and it needs to be encrypted and signed with public and private keys. I generated private.pem and public.pem in the terminal with the commands
openssl genrsa -out private.pem 2048
openssl rsa -in ./private.pem -pubout -out public.pem
Then in nodeJS.
encrypt: function(message) {
return new Promise(function (resolve, reject) {
var publicKey = require("fs").readFileSync(__dirname + "/public.pem", "utf8");
var encrypted = require("crypto").publicEncrypt(publicKey, new Buffer(message));
resolve(encrypted.toString("base64"));
});
},
Once I call the message encrypt(xml), I get the following error
{
library: 'rsa routines',
function: 'RSA_padding_add_PKCS1_OAEP_mgf1',
reason: 'data too large for key size',
code: 'ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE'
}
Objective:
I've to sign the message as per the demo here samltools.com (Mode: SignMessage), my SAML message looks like this. (see SAML Response section).
Sign the message
Base64Encode the message
The problem here is that you cannot directly encrypted with RSA, a piece of data which is larger than the key size.
Surprising I know, it surprised me too.
In reality very little payload data is encrypted directly with RSA or even elliptic curves.
You should be using RSA Diffie-Hellman to generate a shared secret.
Signature of the file, is really signature of the hash of the file.
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.
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.