Closed tegefaulkes closed 1 year ago
Reposting here:
Regarding the encryption of the private key for web socket server.
In order to encrypt the private into an encrypted PEM file, we need to follow the PKCS#5 standard (version 2) to produce an encrypted PKCS#8 pem file.
For example, in openssl, it has a pkcs8 subcommand. https://www.openssl.org/docs/man1.0.2/man1/pkcs8.html
In this subcommand there is a -v2 alg
option. And in the comments it says:
The alg argument is the encryption algorithm to use, valid values include des, des3 and rc2. It is recommended that des3 is used.
So this will basically end using an algorithm specified in pkcs#5 v2 RFC. However it's not clear which algorithm this is, and that it's OID is going to be.
One way to solve this is to actually use the openssl pkcs8
command, and run it with a plaintext PKCS8 key. Using the -v2 des3
option, and you get back your PKCS8 encrypted PEM.
Subsequently you then just need to interrogate this file. Probably using another openssl
command.
Actually in the updated version https://www.openssl.org/docs/man1.1.1/man1/openssl-pkcs8.html, it turns out the -v2 alg
can be omitted, and the default cipher becomes AES256GCM.
So that means, we can just do the encryption using openssl pkcs8
command, then open up the file, and parse it, to get the algorithm identifier.
This identifier is going to be an OID. The OID is just a string that looks like 1.3.101.110
. But we just need to know which one is for this one.
Use the ASN1 parser to see if you can open up the encrypted file and just console.log out the version identifier string.
Once you get the confirmed OID, we can actually proceed with building this encrypted pem file.
To do this, use https://github.com/PeculiarVentures/asn1-schema/blob/master/packages/pkcs8/src/encrypted_private_key_info.ts.
Then construct the EncryptedPrivateKeyInfo
object, with the algorithm identifier. Because this identifier may not exist in any of asn1 libraries. The string can be defined in the utils
if we need to do this. It should go into the keys/utils
.
The next thing to do is to actually encrypt it. It is explained here.
https://datatracker.ietf.org/doc/html/rfc5208#section-6
We would reuse our own existing code:
const pkcs8 = new asn1Pkcs8.PrivateKeyInfo({
privateKeyAlgorithm: new asn1X509.AlgorithmIdentifier({
algorithm: x509.idEd25519,
}),
privateKey: new asn1Pkcs8.PrivateKey(
new asn1.OctetString(privateKey).toASN().toBER(),
),
});
That provides us the private key info object.
The encryption process involves the following two steps:
1. The private-key information is BER encoded, yielding an octet string. 2. The result of step 1 is encrypted with the secret key to give an octet string, the result of the encryption process.
But what we actually need is something like:
new asn1.OctetString(pkcs8).toASN().toBER()
That then produces an arraybuffer.
Then that array buffer has to go through the encryption process of AES-256-GCM, whatever is specified as part of the OID we discover through openssl pkcs8
.
The resulting function can be called privateKeyToPEMEncrypted
. Then to be symmetric privateKeyFromPEMEncrypted
.
Good thing is that our libsodium supports aes256gcm. So we should be able re-use symmetric algos. We currently have not exposed this from the sodium-native JS wrapper, so you'll need to have a look at that.
I just looked at https://github.com/sodium-friends/sodium-native/blob/master/binding.c, it doesn't export the aes256gcm from libsodium. So the only alternative right now is the peculiar webcrypto which would have it.
However we need to create a new feature issue to replace webcrypto with whatever is possible in libsodium.
Hopefully openssl here actually understands ed25519 keys. But I think your test would prove that it works. Also https://blog.pinterjann.is/ed25519-certificates.html
Producing an encrypted PEM would be useful extra feature to the keys domain that other things could use as well.
So in terms of following openssl aes256gcm, this is therefore a secure default.
We are going to need to fork the sodium-native wrapper later in the future.
@tegefaulkes you can do this after you've fixed up the PR, this can be a second PR as it adds extra functionality to the keys domain that is then used by the websockets domain.
See this comment https://github.com/MatrixAI/Polykey/issues/503#issuecomment-1445638011, this explains how we can do this.
The motivation for this whole thing is cause uWS doesn't seem to support in-memory key and certificate.
The uWS API is lacking alot of bindings that are actually available in the underlying code. So we might want to create our own uWS bindings in the future, and specifically bind to the uWS C code, then we can even just use Node's openssl instead.
Of course the whole thing is really about not relying on Node's version of websockets... so maybe boringssl can be used again. Not sure atm.
If we end up with needing an HTTP server, I think uWS does supply HTTP too.
That would mean something like js-ws
just like js-quic
. We're going lower and lower into the stack...
After switching to ws.js
we now load the certs and keys from memory. There is no need for an encrypted PEM format now.
I'm closing this as a wontfix.
I think there can be an issue created to enable export of our root key as encrypted PEM format. The above notes I wrote can still be useful if for whatever reason such a utility is needed in the future.
Recreated as a feature request for #550.
Specification
In #506 we found that the
uWebsockets
library needed to read SSL keys and certs from the file system. Given this we need to write the cert chain PEM and private key PEM files to the file system so we can provide them to the web socket server.To this end we need to generate a encrypted private key PEM format that the
uWebsocket
library can use. Ideally we can use the peculiarwebcrypto
,asn1-schema
andasn1
libraries to generate this format.Given the crypto ecosystem in JS and node and the level of required knowledge, this issue is pretty tricky to tackle.
Additional context
Tasks
uWebsockets
can load with a provided passphrase.ClientServer
to use this when starting theuWebsockets
server.