Provision device using x509 from softHSMv2 - node.js

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

Related

convert x509 ECDSA .pem cert and key to pkcs12

Is it possible to do the same as openssl does with chilkat as below:
I have tried few methods with chilkat but when I try to use it for signing a PDF file it fails.
However if I do convert it with openssl as below it passes
*** ECDSA
# Generate self-signed certificate with ECDSA using two common curves
openssl req -x509 -nodes -days 3650 -newkey ec:<(openssl ecparam -name prime256v1) -keyout ecdsakey.pem -out ecdsacert.pem
# print private and public key + curve name
openssl ec -in ecdsakey.pem -text -noout
# print certificate
openssl x509 -in ecdsacert.pem -text -noout
# generate container
openssl pkcs12 -export -inkey ecdsakey.pem -in ecdsacert.pem -out ecdsacred.p12
My certificate info looks as below:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
5a:ed:46:91:6c:d6:d4:e2:89:14:47:4c:39:62:e8:80:e4:17:e9:3b
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = IE, ST = Dublin, O = AID:Tech, OU = Blockchain, CN = ca
Validity
Not Before: Mar 31 21:10:00 2020 GMT
Not After : Mar 31 21:15:00 2021 GMT
Subject: OU = client, CN = john doe
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:91:f4:62:5b:79:31:59:41:d3:ff:59:8a:41:22:
06:13:34:5e:ce:0a:3f:16:ea:e7:91:fe:53:4f:a3:
ea:63:f7:90:aa:a3:66:72:98:97:01:2a:a6:33:b7:
c2:97:55:bf:83:b4:ca:b4:8e:6f:95:70:1f:da:f7:
f5:a4:00:77:ad
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Key Identifier:
5E:79:09:10:5D:64:BF:68:D7:29:AC:2A:BC:BB:39:2D:FF:12:D7:37
X509v3 Authority Key Identifier:
keyid:87:4B:D2:9C:83:32:05:97:CD:93:7A:25:B7:46:39:DF:AE:19:DE:79
1.2.3.4.5.6.7.8.1:
{"attrs":{"hf.Affiliation":"","hf.EnrollmentID":"john doe","hf.Type":"client"}}
Signature Algorithm: ecdsa-with-SHA256
30:44:02:20:3c:ff:16:5f:58:c9:4b:6f:d3:7e:75:b4:68:60:
07:a3:f7:8e:d8:0f:29:52:ee:86:8f:35:46:d0:a1:d0:f1:ea:
02:20:47:ff:19:02:7a:58:d4:6d:e4:67:4a:ca:c4:67:54:90:
48:8c:b0:70:29:77:97:bb:52:2f:80:7f:5a:e8:d2:0d
Here is method 1 that I tried:
const chilkat = require('#chilkat/ck-node12-macosx');
const os = require('os');
const fs = require('fs')
function chilkatExample() {
var cert = new chilkat.Cert();
var privKey = new chilkat.PrivateKey();
// Load any type of certificate (.cer, .p7b, .pem, etc.) by calling LoadFromFile.
var success = cert.LoadFromFile("static/johnDoeCert.pem");
if (success !== true) {
console.log(cert.LastErrorText);
return;
}
// Load the private key.
// (The various privKey methods that load from a file will automatically detect
// the format. It doesn't actually matter if you try to load a non-PKCS8 format private key
// by calling LoadPkcs8EncryptedFile -- internally Chilkat will detect the format and will load
// based on what it finds.)
success = privKey.LoadPkcs8EncryptedFile("static/privKey.pem","");
if (success !== true) {
console.log(privKey.LastErrorText);
return;
}
// Write the cert as PEM.
success = cert.ExportCertPemFile("qa_output/cert.pem");
// Or get the PEM string directly...
console.log(cert.ExportCertPem());
// Associate the private key with the cert object.
success = cert.SetPrivateKey(privKey);
if (success !== true) {
console.log(cert.LastErrorText);
return;
}
// Write the cert + private key to a .pfx file.
success = cert.ExportToPfxFile("static/myPfx.p12","", true);
if (success !== true) {
console.log(cert.LastErrorText);
return;
}
console.log("Success.");
}
chilkatExample();
And here is method 2 that I tried:
const os = require('os');
const fs = require('fs')
try {
const chilkat = require('#chilkat/ck-node12-macosx');
const CERT = new chilkat.Cert();
const encodedCert = fs.readFileSync('./static/johnDoeCert.pem', { encoding: 'base64' });
const loadedCert = CERT.LoadFromBase64(encodedCert);
if (!loadedCert) {
console.log('failed to load cert')
}
const certChain = CERT.GetCertChain();
if (!CERT.LastMethodSuccess){
console.log('failed to load cert chain')
}
const PRIVKEY = new chilkat.PrivateKey()
const encodedPrivKey = fs.readFileSync('./static/privKey.pem', 'utf8');
const loadedKEy = PRIVKEY.LoadPkcs8EncryptedFile('./static/privKey.pem', "");
if (!loadedKEy) {
console.log('failed to load privagte key')
}
const PFX = new chilkat.Pfx()
const loadedPFX = PFX.AddPrivateKey(PRIVKEY, certChain)
if (!loadedPFX) {
console.log('could not load PFX')
}
const writeP12 = PFX.ToFile("", "./static/johnDoe.p12")
if (!writeP12){
console.log('could not write PFX')
}
} catch (error) {
console.log(error)
}
Thank you!
It seems that `method 1 from above works fine, my problem was on another part of my code that is not related to this.
I did slight modifications to the code above and here is my final result:
const os = require('os');
const fs = require('fs')
const chilkat = require('#chilkat/ck-node12-macosx');
try {
const CERT = new chilkat.Cert();
const PRIVKEY = new chilkat.PrivateKey()
const loadedCert = CERT.LoadFromFile('./static/cert.pem');
if (!loadedCert) {
console.log('failed to load cert')
}
const loadedKEy = PRIVKEY.LoadPkcs8EncryptedFile('./static/privKey.pem', "");
if (!loadedKEy) {
console.log('failed to load privagte key')
}
// Associate the private key with the cert object.
const associatePrivKey = CERT.SetPrivateKey(PRIVKEY);
if (!associatePrivKey) {
console.log(CERT.LastErrorText);
return;
}
// Write the cert + private key to a .pfx file.
const exported = CERT.ExportToPfxFile("static/cert.p12", "password", false);
if (!exported) {
console.log(CERT.LastErrorText);
return;
}
console.log("Success.");
} catch (error) {
console.log(error)
}

How to verify JWT token with ES384 algorithm with Nodejs tools signed with JwtSecurityTokenHandler using CNG keys

I'm trying to verify JWT token with Node.js tools signed with JwtSecurityTokenHandler using CNG generated keys
I tried many Nood.js tools e.g. jsonwebtoken
jwt.verify(token, publickey,{ algorithms: ['ES384'], ...
But get wrong tag errors every time
["error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error"],"library":"asn1 encoding routines","function":"asn1_check_tlen","reason":"wrong tag","code":"ERR_OSSL_ASN1_WRONG_TAG"
The public and private keys generated with CNG
var key = CngKey.Create(CngAlgorithm.ECDsaP384, "keyName",
new CngKeyCreationParameters
{
KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey,
KeyUsage = CngKeyUsages.AllUsages,
ExportPolicy = CngExportPolicies.AllowPlaintextExport,
});
txtPrivateKey = Convert.ToBase64String(key.Export(CngKeyBlobFormat.EccPrivateBlob));
txtPublicKey = Convert.ToBase64String(key.Export(CngKeyBlobFormat.EccPublicBlob));
I tried with converting the keys, but still getting the same exception.
How can I generate a valid public key for Node.js tools using CNG and ES384 algorithm?
It seems I found the solution:
export private key in pkcs8 format
Convert.ToBase64String(key.Export(CngKeyBlobFormat.Pkcs8PrivateBlob));
save into pem and add BEGIN/END PRIVATE KEY
generate public key with openssl
openssl ec -in pkcs8.pem -pubout -out pkcs8genpubkey.pem

Creating JWT token with x5c header parameter using System.IdentityModel.Tokens.Jwt

My project is building an authentication service based on .NET Core and the System.IdentityModel.Tokens.Jwt nuget package. We want to create JWT tokens that include the public key certificate (or certificate chain) that can be used to verify the JWT digital signatures. This is possible with commercial identity providers (SaaS), and is supported in the JWT specification by means of a header parameter called "x5c". But I have so far been unable to get this to work using System.IdentityModel.Tokens.Jwt.
I am able to create a JWT token signed using a certificate. The certificate is self-signed and created using openssl (commands included underneath).
My test code in C# looks like this:
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
// more usings..
public static string GenerateJwtToken(int exampleAccountId, string x509CertFilePath, string x509CertFilePassword)
{
var tokenHandler = new JwtSecurityTokenHandler();
var signingCert = new X509Certificate2(x509CertFilePath, x509CertFilePassword);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, exampleAccountId.ToString()) }),
Expires = DateTime.UtcNow.AddDays(30),
Audience = "myapp:1",
Issuer = "self",
SigningCredentials = new X509SigningCredentials(signingCert, SecurityAlgorithms.RsaSha512Signature),
Claims = new Dictionary<string, object>()
{
["test1"] = "hello world",
["test2"] = new List<int> { 1, 2, 4, 9 }
}
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
The generated token header deserializes to this in jwt.io:
{
"alg": "RS512",
"kid": "193A49ED67F22850F4A95258FF07571A985BFCBE",
"x5t": "GTpJ7WfyKFD0qVJY_wdXGphb_L4",
"typ": "JWT"
}
Thing is, I would like to get the "x5c" header parameter output as well. The reason for this is that my project is trying to include the certificate with the public key to validate the token signature inside the token itself, and "x5c" is a good way to do this. But I just cannot get this to work.
I have tried adding x5c manually with AdditionalHeaderClaims on SecurityTokenDescriptor, but it just isn't being output in the token.
Does anybody know how to do this, or can you point me to some solid resources on the subject?
By the way, this is how I generated the certificate used (on Windows):
openssl genrsa -out private2048b.key 2048
openssl req -new -key private2048b.key -out myrequest2048.csr -config <path to openssl.cfg>
openssl x509 -req -days 3650 -in myrequest2048.csr -signkey private2048b.key -out public2048b.crt
openssl pkcs12 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in public2048b.crt -inkey private2048b.key -out mypkcs2048.pfx -name "Testtest"
The PFX is the file being read and used in the code.
Update for posterity
Using Abdulrahman Falyoun's answer, the final part of the code was updated to use token.Header.Add to manually add in the "x5c" header parameter, before serializing the JWT token. Token had to be cast as JwtSecurityToken.
This worked, and created a token that was valid (and had a signature that could immediatly be verified) in https://jwt.io :
// create JwtSecurityTokenHandler and SecurityTokenDescriptor instance before here..
var exportedCertificate = Convert.ToBase64String(signingCert.Export(X509ContentType.Cert, x509CertFilePassword));
// Add x5c header parameter containing the signing certificate:
var token = tokenHandler.CreateToken(tokenDescriptor) as JwtSecurityToken;
token.Header.Add(JwtHeaderParameterNames.X5c, new List<string> { exportedCertificate });
return tokenHandler.WriteToken(token);
What is x5c?
The "x5c" (X.509 certificate chain) Header Parameter contains the X.509 public key certificate or certificate chain [RFC5280] corresponding to the key used to digitally sign the JWS. The certificate or certificate chain is represented as a JSON array of certificate value strings. Each string in the array is a base64-encoded (not base64url-encoded) DER [ITU.X690.2008] PKIX certificate value. The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the one used to certify the previous one. The recipient MUST validate the certificate chain according to RFC 5280 [RFC5280] and consider the certificate or certificate chain to be invalid if any validation failure occurs. The use of this Header Parameter is OPTIONAL.
Note
From the security point of view - do not use the x5c certificate to validate the signature directly. In that case, anybody could just provide their own certificate and spoof any identity.
The purpose of the x5t / x5t#S256 header is to identify the signer - check you trust the certificate provided by x5c or x5t#S256 (or its issuer) under the specified iss, only then you should validate the signature.
so to build the X509 chain
X509Chain chain = new X509Chain()
bool success = chain.Build(cert);
if (!success) throw Error
Then for each chain.ChainElements value, take the Certificate property RawValue property (and base64 encode it).
finally, you got the string for x5c and should only provide it to the headers of jwt.
See the following links
Create JWK Set Containing Certificates
Generate x5c certificate chain from JWK
How to obtain JWKs and use them in JWT signing?
How to get x5c from RSACryptoServiceProvider
Hope it's useful.
#Edit
If the issue was to supply the x5c to the header, you have to add it using
token.Header.Add(name, value)

error:0409A06E:rsa routines data too large for key size

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.

Programmatically create certificate and certificate key in Node

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!

Resources