How can I use a key.pem file in Netsuite to sign a HTTP request with Suitescript? - api

I am trying to sign a https request and for that I need to encrypt a digest. From the api I generated both a certificate.pem and a privateKey.pem. I uploaded them both in Netsuite in the Certficate and Key part of the company set up.
My question is mainly how do I now get the privateKey from the file to use with the crypto module?
Here is what I have so far.
"payload" is the data I want to encrypt for my digest and is just a string.
var sKey = keyControl.loadKey('custkey2');
var hmacObj = crypto.createHmac({
algorithm: crypto.HashAlg.SHA256,
key: sKey
});
var updatedHmac = hmacObj.update({
input: payload,
inputEncoding:encode.Encoding.UTF_8
});
var reencoded = encode.convert({
string: updatedHmac,
inputEncoding: encode.Encoding.UTF_8,
outputEncoding: encode.Encoding.BASE_64
});
But when ever I run that in my Suitelet I get an error coming from the "create Hmac".
any help would be more than appreciated thank you.

SS2.0 module N/https/clientCertificate holds the answer. Instead of using https.post() use clientCertificate.post() which can send SSL requests with a digital certificate.
Example that works for me:
/* 1st create certificate in NetSuite UI (Setup > Pereferences > Certificates) */
const certId = 'custcertificate_xy';
/* 2nd use certificates id inside request call */
const response = clientCertificate.post({
url: url,
body: body,
certId: certId,
headers: headers
});
Please note that for some reason NetSuite wanted me to have certificate (*.pem) file in following format:
-----BEGIN PRIVATE KEY-----
{{private key}}
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
{{certificate}}
-----END CERTIFICATE-----

Related

converting .pem keys to .der compiles but results in errors

Calling my hyper based API now ported to HTTPS, with Python's requests I'm getting
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)' on every request.
As per the docs for tokio_rustls:
Certificate has to be DER-encoded X.509
PrivateKey has to be DER-encoded ASN.1 in either PKCS#8 or PKCS#1 format.
The keys I used in my PKEY and CERT variables are my certbot generated .pem keys converted to .der format using those commands:
openssl x509 -outform der -in /etc/letsencrypt/live/mysite.com/cert.pem -out /etc/letsencrypt/live/mysite.com/cert.der
openssl pkcs8 -topk8 -inform PEM -outform DER -in /etc/letsencrypt/live/mysite.com/privkey.pem -out /etc/letsencrypt/live/mysite.com/privkey.der -nocrypt
And loaded up with include_bytes!() macro.
Well it compiles, polls... and just throws this error on every request Bad connection: cannot decrypt peer's message whilst the caller gets the SSLError mentioned in the beginning.
Here is the script used for the API:
fn tls_acceptor_impl(cert_der: &[u8], key_der: &[u8]) -> tokio_rustls::TlsAcceptor {
let key = PrivateKey(cert_der.into());
let cert = Certificate(key_der.into());
Arc::new(
ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(vec![cert], key)
.unwrap(),
)
.into()
}
fn tls_acceptor() -> tokio_rustls::TlsAcceptor {
tls_acceptor_impl(PKEY, CERT)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = SocketAddr::from(...);
let mut listener = tls_listener::builder(tls_acceptor())
.max_handshakes(10)
.listen(AddrIncoming::bind(&addr).unwrap());
let (tx, mut rx) = mpsc::channel::<tokio_rustls::TlsAcceptor>(1);
let http = Http::new();
loop {
tokio::select! {
conn = listener.accept() => {
match conn.expect("Tls listener stream should be infinite") {
Ok(conn) => {
let http = http.clone();
// let tx = tx.clone();
// let counter = counter.clone();
tokio::spawn(async move {
// let svc = service_fn(move |request| handle_request(tx.clone(), counter.clone(), request));
if let Err(err) = http.serve_connection(conn, service_fn(my_query_handler)).await {
eprintln!("Application error: {}", err);
}
});
},
Err(e) => {
eprintln!("Bad connection: {}", e);
}
}
},
message = rx.recv() => {
// Certificate is loaded on another task; we don't want to block the listener loop
let acceptor = message.expect("Channel should not be closed");
}
}
}
How can I make any sense of the errors, when the certificate keys work on Web (as they are the apache2 server's keys)? I've tried various other encodings, that are against the docs, and all fail in the same way.
I'm not familiar enough with rust, but I know that proper configuration of a TLS server (no matter which language) requires the server certificate, the key matching the certificate and all intermediate CA needed to build the trust chain from server certificate to the root CA on the clients machine. These intermediate CA are not provided in your code. That's why the Python code fails to validate the certificate.
What you need is probably something like this:
ServerConfig::builder()
....
.with_single_cert(vec![cert, intermediate_cert], key)
Where intermediate_cert is the internal representation of the Let’s Encrypt R3 CA, which you can find here.

Not able to verify the validity of X509Certificate using Apples App attest root certificate

All I am able to do is validate the generated X509Certificate using its method checkValidity(), but as per the steps mentioned in https://developer.apple.com/documentation/devicecheck/validating_apps_that_connect_to_your_server, we have to validate the X509Certificate using Apple App attest root certificate which is
-----BEGIN CERTIFICATE-----
MIICITCCAaegAwIBAgIQC/O+DvHN0uD7jG5yH2IXmDAKBggqhkjOPQQDAzBSMSYw
JAYDVQQDDB1BcHBsZSBBcHAgQXR0ZXN0YXRpb24gUm9vdCBDQTETMBEGA1UECgwK
QXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTAeFw0yMDAzMTgxODMyNTNa
Fw00NTAzMTUwMDAwMDBaMFIxJjAkBgNVBAMMHUFwcGxlIEFwcCBBdHRlc3RhdGlv
biBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9y
bmlhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAERTHhmLW07ATaFQIEVwTtT4dyctdh
NbJhFs/Ii2FdCgAHGbpphY3+d8qjuDngIN3WVhQUBHAoMeQ/cLiP1sOUtgjqK9au
Yen1mMEvRq9Sk3Jm5X8U62H+xTD3FE9TgS41o0IwQDAPBgNVHRMBAf8EBTADAQH/
MB0GA1UdDgQWBBSskRBTM72+aEH/pwyp5frq5eWKoTAOBgNVHQ8BAf8EBAMCAQYw
CgYIKoZIzj0EAwMDaAAwZQIwQgFGnByvsiVbpTKwSga0kP0e8EeDS4+sQmTvb7vn
53O5+FRXgeLhpJ06ysC5PrOyAjEAp5U4xDgEgllF7En3VcE3iexZZtKeYnpqtijV
oyFraWVIyd/dganmrduC1bmTBGwD
-----END CERTIFICATE-----
You can have a look at my code:
String decodedCredCert = "
-----BEGIN CERTIFICATE----MIIC4jCCAmmgAwIBAgIGAX66NktmMAoGCCqGSM49BAMCME8xIzAhBgNVBAMMGkFwcGxlIEFwcCBBdHRlc3RhdGlvbiBDQSAxMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIyMDIwMTExMzM0N1oXDTIyMDIwNDExMzM0N1owgZExSTBHBgNVBAMMQGMwYTY1ZmRjYjE1MDJjNTNmNzUyMzM4NzZlM2ViZTE3NGVlMzYyODkwZDVmZGE3N2RmZTJhMmE2NWQwOTQ1MGYxGjAYBgNVBAsMEUFBQSBDZXJ0aWZpY2F0aW9uMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExooI0SVHKxhLJu+1zDz1VmCQU7SDdoYTBP5FPsl3sn2sYc1De9tzkKCbb/txsvZXDU6cn+HH5Lt3w4+s+wkkmqOB7TCB6jAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIE8DB6BgkqhkiG92NkCAUEbTBrpAMCAQq/iTADAgEBv4kxAwIBAL+JMgMCAQG/iTMDAgEBv4k0IgQgV0EyREI2MzdKVy5jb20uZ2wubWljaGFlbEtvcnMucWGlBgQEc2tzIL+JNgMCAQW/iTcDAgEAv4k5AwIBAL+JOgMCAQAwGQYJKoZIhvdjZAgHBAwwCr+KeAYEBDE1LjIwMwYJKoZIhvdjZAgCBCYwJKEiBCCDM1heaSkW2MvNzjxqor1xYX1U/tdYlXz9a1CiCT/VBjAKBggqhkjOPQQDAgNnADBkAjA4Ek+QKVFVunbBBBbjQ4qfni7vhXVABvbV/ru8CjsSc/fkuKsR4mKmmWjNoFq2vnACMAv5YFRC6rt1fv9vD1duDxv608DOGZtC95H7LOI65RHn0IYiq1iMLfBF
MPUPwJACRw==
-----END CERTIFICATE----- ";
X509Certificate cert1 = getParentCertificate(decodedCredCert);
System.out.println(cert1);
cert1.checkValidity();
where my
This app attest step is to verify the certificate chain. You will get 2 certificates in attestation request i.e. under x5c[0], x5c[1]. These are leaf and intermediate certificates.
To verify the certificate chain, x5c[0] certificate should be signed by x5c[1] and x5c[1] certificate should be signed by Apple App attest root certificate.
Sample code for this
CertificateFactory cf = CertificateFactory.getInstance(AppConstants.X_509);
byte[] credCertByte = Base64.getDecoder().decode(x5c[0]);
X509Certificate credCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(credCertByte));
byte[] caCertByte = Base64.getDecoder().decode(x5c[1]);
X509Certificate caCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(caCertByte));
X509Certificate appleAppAttestationRootCaCert = (X509Certificate) cf
.generateCertificate(APPLE_APP_ATTEST_CERT);
credCert.verify(caCert.getPublicKey());
caCert.verify(appleAppAttestationRootCaCert.getPublicKey());
This is the code for NodeJS/Typescript:
import fs from 'fs';
import crypto from 'crypto'
const appleAppAttestationRootCaCert = fs.readFileSync(__dirname + '/../assets/AppleRootCA-G3.pem').toString();
const credCert = new crypto.X509Certificate(Buffer.from(x5c[0], 'base64'))
const caCert = new crypto.X509Certificate(Buffer.from(x5c[1], 'base64'))
const appleCert = new crypto.X509Certificate(appleAppAttestationRootCaCert)
const valid = credCert.verify(caCert.publicKey)
console.log("Valid:", valid)
caCert.verify(appleCert.publicKey);
console.log("Valid with Apple Key:", valid)

How to create a JWKs key from a CRT encoded in PEM format

I have a CRT file:
Example:
-----BEGIN CERTIFICATE-----
MIIDijCCAvOgAwIBAgIJAKRvtQxONVZoMA0GCSqGSIb3DQEBBAUAMIGLMQswCQYD
aXJlbGVzcyBOZXR3b3JrczEMMAoGA1UECxMDVEFDMSMwIQYDVQQDExpteXNlcnZl
ci5hcnViYW5ldHdvcmtzLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
zRwqc9prVXycGhHcsAjGPzC2MKU4DhXSr86Z89Jk8/cXEJBJ0C/NgdAqqDgxneUh
nVyxGxODa7BNGAWSagdCsKLrbkchr479E3xLfgdc3UzAJITLGCXGiQ66NwQDyM5I
YWxpZm9ybmlhMRIwEAYDVQQHEwlTdW5ueXZhbGUxIDAeBgNVBAoTF0FydWJhIFdp
cmVsZXNzIE5ldHdvcmtzMQwwCgYDVQQLEwNUQUMxIzAhBgNVBAMTGm15c2VydmVy
LmFydWJhbmV0d29ya3MuY29tggkApG+1DE41VmgwDAYDVR0TBAUwAwEB/zANBgkq
hkiG9w0BAQQFAAOBgQBp71WeF6dKvqUSO1JFsVhBeUesbEgx9+tx6eP328uL0oSC
fQ6EaiXZVbrQt+PMqG0F80+4wxVXug9EW5Ob9M/opaCGI+cgtpLCwSf6CjsmAcUc
b6EjG/l4HW2BztYJfx15pk51M49TYS7okDKWYRT10y65xcyQdfUKvfDC1k5P9Q==
-----END CERTIFICATE-----
I know that this CRT is encoded in PEM. But i want to create a JWK key from this file how should i go about doing this ?
You can use the below code to convert your PEM to JWK.
const jwk = require('jwk');
const myKeyJWK = jwk.readCertificate('crykey-my-key');

Adding extension in CSR for generating an intermediate certificate

I am generating a Certificate Signing Request for an intermediate certificate. I want to make the certificate a certificate authority (CA), so I want to add the basic constraints extension in CSR. I am currently using the following code
exts = sk_X509_EXTENSION_new_null();
add_ext(exts, x509_req, NID_basic_constraints, "critical,CA:TRUE");
X509_REQ_add_extensions(x509_req, exts);
sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
The add extension function looks like this
int add_ext(STACK_OF(X509_EXTENSION) *sk, X509_REQ* req, int nid, char *value)
{
X509_EXTENSION *ex;
X509V3_CTX ctx;
X509V3_set_ctx_nodb(&ctx);
X509V3_set_ctx(&ctx, NULL, NULL, req, NULL, 0);
ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
if (!ex)
{
log("X509V3_EXT_conf_nid generated error", cspsdk::Logging::LOG_LEVEL_INFO);
return 0;
}
sk_X509_EXTENSION_push(sk, ex);
return 1;
}
The problem is that after getting signed, the certificate has the CA value of basic constraints extension set to false. I am at a loss here. Can anybody point out the issue.
Your issuer can choose to override the constraints like CA: False even though you requested for CA: True. You need to contact them unless you are self-signing your certs.
openssl x509 -in your-signed-cert.pem -text -noout
Please check if the output contains "CA:True".

How do I use frisby.js with SSL client certificates?

I have a question about the use of Frisby.js with a REST service that uses SSL client certificates.
If I use the Request package to talk to the service, I can do something like this:
request.get(URL, {
agentOptions: {
// Or use `pfx` property replacing `cert` and `key` when using private key, certificate and CA certs in PFX or PKCS12 format:
pfx: fs.readFileSync(pfxFilePath),
passphrase: 'password',
ca: fs.readFileSync(caFilePath)
}
});
Since Frisby uses request, I assume I can do something similar in a Frisby test, but I can’t seem to get the syntax correct.
Can you suggest something that might work? Thanks...
The .get() and .post() methods can take an additional struct with contents similar to the options parameter of https.request(). You will need to supply values for (some of): pfx, key, cert, ca and passphrase.
Something like this:
var fs = require('fs');
var frisby = require('frisby');
frisbee.create( 'Test name' )
.get(
'https://localhost/rest/endpoint',
{
key: fs.readFileSync( "/path/to/certificates/certificate.key" ),
cert: fs.readFileSync( "/path/to/certificates/certificate.crt" ),
ca: fs.readFileSync( "/path/to/certificates/ca.crt" ),
passphrase: "your pass phrase"
}
)
.expectStatus( 200 )
.toss();