So I figured it out! Here's the answer for anyone else that might want it (or my future self)
First, it seems like the certificate needs to have the right usage and extensions. When I generated a certificate without these, it didn't work. Create a file called user-key.conf
with content similar to:
[ req ]
default_bits = 2048
default_md = sha256
distinguished_name = subject
req_extensions = req_ext
x509_extensions = req_ext
string_mask = utf8only
prompt = no
[ req_ext ]
basicConstraints = CA:FALSE
nsCertType = client, server
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign
extendedKeyUsage= serverAuth, clientAuth
nsComment = "Bruces User Cert"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
subjectAltName = URI:urn:opcua:user:bruce,IP: 127.0.0.1
[ subject ]
countryName = US
stateOrProvinceName = TX
localityName = Houston
organizationName = AL
commonName = bruce
Note, in this example, I am generating a self-signed certificate. I am not a security expert, so I don't know the implication of all these claims and I haven't tested to see which ones are absolutely necessary. Some information I found seemed to suggest that the commonName should correspond to the usernames in the OPC server, but this is the only detail that I learned in my research.
Next, use openssl with this configuration file to create a certificate and private key pair:
> openssl.exe req -x509 -days 365 -new -out bruce1.pem -keyout bruce1_key.pem -config user-key.conf
For some (probably a good, security related) reason, openssl forces you to specify a passphrase to protect the private key. So far, I have not figured out how to decrypt this within node.js, so I found that I can remove it with the following command:
> openssl.exe rsa -in bruce1_key.pem -out bruce1_key_nopass.pem
Then, at least with the OPC-UA server I was testing with (prosys OPC-UA simulation server), it was necessary to get the certificate in .der format:
> openssl.exe x509 -inform PEM -outform DER -in bruce1.pem -out bruce1.der
This .der file need to be loaded into the OPC-UA server. For prosys this was done by copying it into the .\prosys-opc-ua-simulation-server\USERS_PKI\CA\certs
folder.
Within node.js, the following code will connect using the generated certificate files:
const client = OPCUAClient.create(options);
await client.connect(endpointUrl);
let userIdentity = {
type: UserTokenType.Certificate,
certificateData: fs.readFileSync('./user-certificates/bruce1.pem'),
privateKey: fs.readFileSync('./user-certificates/bruce1_key_nopass.pem','utf8')
}
const session = await client.createSession(userIdentity);
This results in a successful connection to the prosys OPC-UA client. I am sure there are some future improvements that can be made, like doing the passphrase decryption within node.js.