Using a Microsoft Windows Certificate Store in node.js - node.js

I am trying to create a node https server. The idea is that the server will receive the thumbprint of a certificate in the store, pull it out of the store and use it for SSL/TLS connection. I have done examples for self-signed certificates and it has worked fine.
I have the code to look through the certificate store and find the correct certificate based on its thumbprint
The code I have so far is below.
const ca = require('win-ca');
const forge = require('node-forge');
const pki = forge.pki;
const ssh = forge.ssh;
const thumbprint = process.env.THUMBPRINT;
...
for (let cert of ca.all()) {
console.log(forge.pki.certificateToPem(cert));
console.log(' ----------- \r');
const md = forge.md.sha1.create();
md.update(forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes());
const hex = md.digest().toHex();
console.log(hex);
console.log('\r');
if (thumbprint === hex) {
foundCert = cert;
console.log('found\r');
break;
}
}
console.log('**********************');
console.log('\r');
//}
cnt++;
}
if (foundCert) {
console.log(forge.pki.certificateToPem(foundCert));
const pm = forge.pki.certificateToPem(foundCert);
console.log(foundCert);
}
In windows ASP.NET Core, one only needs the the certificate for https. For node.js, all the examples use a private key. I don't have the private key, but I have access to the machine's certificate store. Can I use the certificate from the store without the private key? There seems to be a piece I am missing.
Thanks

Related

Azure "JsonWebTokenError: invalid algorithm"

Azure Static Web App (SWA) with integrated API. One of the step at backend API is to validate the Bearer Token with public key submitted in request headers:
const jwt = require("jsonwebtoken"); // v 8.5.1
async function getMSPublicKey(misc) // misc contains kid and tenantId, confirmed in F12 request header
{
var vurl = "https://login.microsoftonline.com/" + misc.tenantId + "/v2.0/.well-known/openid-configuration";
const x1 = await fetch(vurl);
const x2 = await x1.json();
const x3 = await fetch(x2.jwks_uri);
const k = await x3.json();
return pkey = k.keys.find( k => k.kid === misc.kid).x5c[0]; // public key in the entry matching kid
}
var vmisc = JSON.parse(ac.req.headers["misc"]);
var publickey = "-----BEGIN CERTIFICATE-----\n" + await getMSPublicKey(vmisc) + "\n-----END CERTIFICATE-----";
// next line is reported in AppTraces, Message = JsonWebTokenError: invalid algorithm
var payload = jwt.verify(theToken, publickey, { algorithms: ['RS256'] });
// theToken is validated ok at jwt.io
It only occurs when deployed to Azure cloud, local Azure Static Web Apps emulator is all ok.
Update, Guess this is something about Azure cloud, particularly security. similar result on another package Jose, error only on Azure cloud.
Update: found culprit My original code was sending the token in under Authorization name. Azure log shows its read-in length is always 372 vs. 1239 tested in local emulator. After renaming it to something else like mytoken, all good! This is undocumented, reminder to everyone: avoid sensitive/reserved words.
This ought to be painless and work the same with less code on your end, it handles rotation, re-fetching of the public keys, as well as implements a complete applicable JWK selection algorithm for all known JWS algorithms. Also does not depend on a brittle x5c[0] JWK parameter.
const jose = require('jose')
const JWKS = jose.createRemoteJWKSet(new URL(`https://login.microsoftonline.com/${misc.tenantId}/discovery/v2.0/keys`))
// JWKS you keep around for subsequent verifications.
const { payload, protectedHeader } = await jose.jwtVerify(jwt, JWKS)
Please check if the below steps help to work around:
Replace the CERTIFICATE keyword with PUBLIC KEY if you're using the public key or PRIVATE KEY if you're using the Private Key or RSA PRIVATE KEY if you are using RSA Private Key.
Also, the problem again occurs in the way we format the Public Key which requires begin and end lines, and line breaks at every 64 characters.
Refer here for more information.

Playing around with certificates in .net5

I am trying to learn about certificates, the need for that is from an idea I had a few days ago to simplify the ordering and delivery of SSL certificates to my collegues. So I startet to investigate "how hard can it be"?
And to large entusiasm, the entire process was "not so hard", but then I came to "generating pfx certificates", with private key...
The code is from a console test app:
Creating keys and storing it.
int keysize = 2048;
CngKeyCreationParameters ckcParams = new CngKeyCreationParameters()
{
ExportPolicy = CngExportPolicies.AllowPlaintextExport,
KeyCreationOptions = CngKeyCreationOptions.None,
KeyUsage = CngKeyUsages.AllUsages,
};
ckcParams.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(KeySize), CngPropertyOptions.None));
CngKey myCngKey = CngKey.Create(CngAlgorithm.Rsa, KeyName, ckcParams);
byte[] privatePlainTextBlob = myCngKey.Export(CngKeyBlobFormat.Pkcs8PrivateBlob);
string privateblob_string = Convert.ToBase64String(privatePlainTextBlob);
// Now I can save the Pkcs8PrivateBlob "somewhere"
Later I can pick up this up and create a Certificate Request and send it to a certificate service by API.
byte[] cngbytes = Convert.FromBase64String( privateblob_string );
CngKey importedkey = CngKey.Import(cngbytes, CngKeyBlobFormat.Pkcs8PrivateBlob, CngProvider.MicrosoftSoftwareKeyStorageProvider);
RSACng rsa = new RSACng(importedkey);
request = new CertificateRequest(
new X500DistinguishedName(order.CertName),
rsa,
HashAlgorithmName.SHA512,
RSASignaturePadding.Pkcs1);
etc....
Now I can download the issued certificate in various formats from the API.
So far so good. Now I want to create PFX file and KEY file.
Creating PFX:
// My issued certificate from provider
byte[] PublicKeyStr = System.Text.Encoding.ASCII.GetBytes(CER_STRING);
// pfx
var certificate = new X509Certificate2(PublicKeyStr, string.Empty, X509KeyStorageFlags.Exportable);
byte[] certificateData = certificate.Export(X509ContentType.Pfx, "password");
// Now I have the pfx, but no private key
I am obviously is not capable of solving this. I have been on a journey into Bouncy Castle, with no luck (btw: where is their documentation?).
I have noticed that .net5 has a metode that I thought (again) might solve this.
X509Certificate2.CreateFromPem(ReadOnlySpan certpem, ReadOnlySpan keypem)
But then I need to get the keypem "private key pem".
My question is simple: Have I totally misunderstod or is the any way to add the needed private key to the pfx file with the information I have stored from the CngKey?
ANY suggestions, ideas, help, tips will be very welcomed. It is simply so frustrating to be so close and just fail miserably.
You need to associate the public certificate with the private key before exporting it as a PFX.
// Key
byte[] cngbytes = Convert.FromBase64String( privateblob_string );
CngKey importedkey = CngKey.Import(cngbytes, CngKeyBlobFormat.Pkcs8PrivateBlob, CngProvider.MicrosoftSoftwareKeyStorageProvider);
RSACng rsa = new RSACng(importedkey);
// Cert
byte[] PublicKeyStr = System.Text.Encoding.ASCII.GetBytes(CER_STRING);
var certificate = new X509Certificate2(PublicKeyStr);
// Together:
X509Certificate2 certWithKey = certificate.CopyWithPrivateKey(rsa);
// PFX:
byte[] pfx = certWithKey.Export(X509ContentType.Pfx, pwd);

Using node.js to verify a X509 certificate with CA cert

I am looking for a node.js way to verify a client certificate in X509 format with a CA certificate which was given to me (none of those are created/managed by me, my software only has to verify what is beeing sent to it).
I have found several modules for this job, however I am having issues with each of them:
X509 is able to do it using x509.verify(cert, CABundlePath, cb), however it needs to read the certificates from FS, and I am having them in memory already. This is cumbersome as it will be done with each web request which reaches my app.
It seems like PKI.js is able to do it, however their examples don't work for me but complain about missing files, so I can't even try it out.
I tried node-forge, but while I am unsure if I use it correctly (they don't have any API documentation) its throwing a forge.pki.BadCertificate error from forge.pki.verifyCertificateChain(caStore, [ cer ], cb).
When trying pem, using a simple pem.verifySigningChain(cer, [ ca ], cb) would throw some error complaining about loading a file from /var/.... Even if it would work, I would avoid using this lib as its relying on the openssl command line tool, which I would like to avoid
Now I feel pretty stupid because I failed to get this simple task done with any of the above modules. Could someone point me to a simple solution which will allow me to verify the signature/validity of a X509 certificate using a given CA certificate? :s
[edit] Basically I would need openssl verify -verbose -CAfile ca-crt.pem client1-crt.pem in Node.js but without dependencies to the openssl command line tool and without temporarily saving the certs to disk.
[edit2] Would it be possible to just use https://nodejs.org/api/crypto.html#crypto_verify_verify_object_signature_signatureformat?
I finally managed to do it using node-forge. Heres a working code example:
let pki = require('node-forge').pki;
let fs = require('fs');
let caCert;
let caStore;
try {
caCert = fs.readFileSync('path/to/ca-cert.pem').toString();
caStore = pki.createCaStore([ caCert ]);
} catch (e) {
log.error('Failed to load CA certificate (' + e + ')');
return....;
}
try {
pki.verifyCertificateChain(caStore, [ cert ]);
} catch (e) {
return handleResponse(new Error('Failed to verify certificate (' + e.message || e + ')'));
}
Both certificates shall be given in base64 encoded PEM format/js string.
verifyCertificateChain checks the certifitate validity (notBefore/notAfter) as well as verifies the given CA chain.
I am not 100% sure if this is the best approach, or if this library is doing a good job, since their source code of verifyCertificateChain is full of #TODOs, so maybe this is not ready for production?
But at least I have a somewhat working solution. Probably it would be better to create a node module which wraps the libssl c calls, but thats just a lot of effort for this small task.
You can also do like this if you want to check the using the client certificates from the http request directly :
// retrieve certificates from the request ( in der format )
clientCert = req.connection.getPeerCertificate(true).raw.toString('base64'))
Method to convert the der certificate to pem and verify against the castore.
const caCert = fs....
const ca = pki.certificateFromPem(caCert)
const caStore = pki.createCaStore([ ca ])
const verify = (clientCert, next) => {
try {
const derKey = forge.util.decode64(clientCert)
const asnObj = forge.asn1.fromDer(derKey)
const asn1Cert = pki.certificateFromAsn1(asnObj)
const pemCert = pki.certificateToPem(asn1Cert)
const client = pki.certificateFromPem(pemCert)
return pki.verifyCertificateChain(caStore, [ client ], cb)
} catch (err) {
next(new Error(err))
}
}
I did not find a better way to verify the client "der" certificate from the request.
fas3r
This works for me:
const fs = require('fs'), pki = require('node-forge').pki
var ca = pki.certificateFromPem(fs.readFileSync('ca.pem', 'ascii'))
var client = pki.certificateFromPem(fs.readFileSync('client.pem', 'ascii'))
try {
if (!ca.verify(client)) throw 'verify failed'
} catch (err) {
console.log(err)
}
try/catch was required, because .verify threw an error (instead of returning false) in my case.

Azure app services does it have a certificate store?

I have a project that communicates with a client server, the client server sends a self-signed certificate and I should trust him in order the continue the connection.
private bool VerifyServerCertificate(LdapConnection ldapConnection, X509Certificate certificate)
{
foreach (var cert in CertificateFileNames)
{
var certPath = Directory.GetFiles($#"{AppDomain.CurrentDomain.BaseDirectory}", $"{cert}",System.IO.SearchOption.AllDirectories);
X509Store store = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
try
{
if (File.Exists(certPath.FirstOrDefault()))
{
var certToAdd = new X509Certificate2(X509Certificate.CreateFromCertFile(certPath.FirstOrDefault()));
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, certToAdd.SubjectName.Name, true);
if (certs.Count.Equals(0))
{
store.Open(OpenFlags.ReadWrite);
store.Add(new X509Certificate2(X509Certificate.CreateFromCertFile(cert)));
store.Close();
}
}
}
catch
{
throw new Exception("Cannot install the certificate.");
}
}
X509Certificate2 certificate2 = new X509Certificate2(certificate);
return certificate2.Verify();
}
This works fine but the project needs to be deployed as a Azure app service. So my question is, the app service environment does it have a "Trusted People" storage ?
The method receives de X509certificate from the connection and verify against the trusted people storage so I need to install the certificates before the validation.
Thanks.

IdentityServer4 invalid_token "The issuer is invalid" on Azure, working on localhost

Help please, I'm building a .NET Core API with ionic front end. I want to use ASPNET Core Identity so I was more or less following this example
https://identityserver4.readthedocs.io/en/release/quickstarts/6_aspnet_identity.html
here is what I have in Startup.cs
// Adds IdentityServer
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients(Configuration))
.AddAspNetIdentity<ApplicationUser>();
and
app.UseIdentity();
app.UseIdentityServer();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = API_address,
RequireHttpsMetadata = false,
ApiName = "myAPIs"
});
and in my Config.cs file for in memory configurations I have
public class Config
{
// scopes define the resources in your system
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
}
// scopes define the API resources in your system
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource(
"myAPIs", // Api resource name
"My API Set #1", // Display name
new[] { JwtClaimTypes.Name, JwtClaimTypes.Role }) // Claims to be included in access token
};
}
// client want to access resources (aka scopes)
public static IEnumerable<Client> GetClients(IConfigurationRoot configuration)
{
return new List<Client>
{
new Client
{
ClientId = "myClient",
ClientName = "My Custom Client",
AllowedCorsOrigins = new List<string>
{
"whateverINeedHere"
},
AccessTokenLifetime = 60 * 60 * 24,
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
RequireClientSecret = false,
AccessTokenType = AccessTokenType.Jwt,
AllowedScopes =
{
"myAPIs"
}
}
};
}
}
Now the problem is that when I test this locally, everything works just fine.
I hit the /connect/token endpoint, I get a token response, hit the controller that needs token authorization and my claims are there. But when I deploy it to Azure, when I want to use the token (issued from that environment) I get 401 Unauthorized with response header invalid_token "The issuer is invalid". I've Googled, but people get invalid tokens with signature problems, not issuer. I've never used identity server before and to me this looks like it's some configuration problem. I have compared tokens I get from identity server on jwt.io, they look exactly the same, only difference being the issuer localhost -> myAPIAddress.
Can someone point me to the right direction?
This smells like it could be the temporary signing credentials. I also ran into problems when deploying to Azure when my cert wasn't loading.
I suggest you create a self signed cert and add it to azure using the following instructions. (Note this can be done in the new portal).
https://azure.microsoft.com/en-us/blog/using-certificates-in-azure-websites-applications/
REMEMBER: Make sure you add the WEBSITE_LOAD_CERTIFICATES application setting!
Also for your benefit, here's the code I use to load the cert in my startup.cs. I keep a copy of the cert in the repository so I can load it from disk as a fallback (when I'm on my dev machine).
X509Certificate2 cert = null;
using (X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
// Replace below with your cert's thumbprint
"A9781679661914B7539BE020EE9C4F6880579F42",
false);
// Get the first cert with the thumbprint
if (certCollection.Count > 0)
{
cert = certCollection[0];
// Use certificate
Log.Logger.Information($"Successfully loaded cert from registry: {cert.FriendlyName}");
}
}
// Fallback to local file for development
if (cert == null)
{
cert = new X509Certificate2(Path.Combine(_env.ContentRootPath, "myauth.pfx"), "mypassword");
Log.Logger.Information($"Falling back to cert from file. Successfully loaded : {cert.FriendlyName}");
}
services.AddIdentityServer()
.AddSigningCredential(cert)
Could be you've got an SSL/TLS issue between client and IdentityServer, are you able to view logged exceptions from IdentityServer itself? You may see something like:
"... Could not establish trust relationship for the SSL/TLS..."
If you're running IdentityServer on HTTPS you need to make sure you've got its domain/sub-domain in your certificate.
Either way, IdentityServer logs lots of useful info so turn on logging and check out what it says, that should point you in the right direction.

Resources