I've got a Node.js server that runs on sub.domain.com, using SSL. It's been working perfectly for months on desktop browsers, but I just noticed that it doesn't work on mobile browsers.
I've done a bit of research and there's a lot of people suggesting that there is something wrong with my certificate chain. I've changed my code to look like there's but still no luck.
Here's my code:
var httpsOptions = {
ca: [fs.readFileSync("certrequest.csr")],
key: fs.readFileSync("privatekey.pem"),
cert: fs.readFileSync("certificate.pem")
};
var app = http.createServer(httpsOptions, function(req, res) {
log.cnsl.write("HTTP Request received from " + req.connection.remoteAddress);
//Do stuff
});
I'm running this command to view some debug information (my server runs on port 5673):
openssl s_client -connect sub.domain.com:5673 -showcerts | grep "^ "
Below is the important part of that output
depth=0 O = *.domain.com, OU = Domain Control Validated, CN = *.domain.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 O = *.domain.com, OU = Domain Control Validated, CN = *.domain.com
verify error:num=27:certificate not trusted
verify return:1
depth=0 O = *.domain.com, OU = Domain Control Validated, CN = *domain.com
verify error:num=21:unable to verify the first certificate
verify return:1
It sounds rather weird that you've put your certificate request file "certrequest.csr" as the CA.
The CA field should contain the certificate chain from your personal certificate to th root certificate. In my configuration, it contains 2 entries. One as the root certificate itself and the second one as the intermediate one because my issuer offers multiple levels of certifications.
By the way, your certification company most certainly provides you with such informations in their FAQ for example.
As an example, here is an extract of my configuration :
var httpsOptions = {
key:fs.readFileSync('/etc/ssl/private/ssl-main.key'),
cert:fs.readFileSync('/etc/ssl/private/ssl-main.crt'),
ca:[fs.readFileSync('/etc/ssl/private/ca.pem'),
fs.readFileSync('/etc/ssl/private/sub.class2.server.ca.pem')]
};
Anyway, this does not explain why It works for non mobile browsers. My only guess is that they my embed themselves a part of the chain while the mobiles wont't for disk space reasons.
Hope this helps.
Related
I have a problem when I am making a request with Axios in node getting Error: ca md too weak.
I am sending .pfx file and password for the pfx certificate file.
I can easily access API using Postman and sending pfx certificate and password, but making a request with Axios in node js (v. 18.0) I get Error: ca md too weak.
I don't want to downgrade Node version and using: process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; also doesn't help.
Also to connect to the server I have to use VPN and this error pops up even if I haven't turned on VPN.
Is it possible to bypass this check, so I can send a request as I do in the Postman?
Here is a code example:
agent = new https.Agent({
pfx: fs.readFileSync((process.cwd() + "\\src\\sources\\KIISWSClient.pfx")),
passphrase: 'password',
rejectUnauthorized: false,
});
userKiisData = axios.post('https://ws-kiis.hlk.hr/AdeoMembersPublicService.svc/WSPublic/BasicData', {
Username: "myusername",
Role: "role"
}, {
auth: {
username: "user",
password: "mypassword"
},
httpsAgent: agent
}
return userKiisData
Here is the error:
Error: ca md too weak
at configSecureContext (node:internal/tls/secure-context:278:15)
at Object.createSecureContext (node:_tls_common:113:3)
at Object.connect (node:_tls_wrap:1622:48)
at Agent.createConnection (node:https:142:22)
at Agent.createSocket (node:_http_agent:343:26)
at Agent.addRequest (node:_http_agent:294:10)
at new ClientRequest (node:_http_client:311:16)
at Object.request (node:https:352:10)
TLDR: jump to end if you don't care about understanding
rejectUnauthorized directs nodejs to accept (some) invalid certs used by the server instead of aborting the connection, but this error is occurring on your own client cert/chain not that of the server. (That's why it can occur even without the VPN you need for an actual connection.) The "CA_MD_TOO_WEAK" check occurs in modern versions of OpenSSL (1.1.0 up) but can vary depending on how the OpenSSL was built, thus for nodejs it also depends whether your nodejs was built to use its own embedded OpenSSL (usual on Windows) or one already provided on the system where it is installed (usual on Linux).
You can look at the signature algorithms(s) used in your cert chain with openssl if you have (or get) it on this machine or you (may, can, and do) copy the pfx to a machine where ditto. First do
openssl pkcs12 -in yourpfxfile -nokeys >temp0
and look at temp0; it should contain one or more blocks each consisting of a subject= line, an issuer= line, a -----BEGIN CERTIFICATE----- line, several lines of base64, and an -----END CERTIFICATE----- line. (Likely there are also Bag Attributes: lines optionally followed by indented lines; ignore those.)
If there is only one block and it has the same value for subject and issuer, then your error should not have happened, because the cert is selfsigned and OpenSSL should not be checking signature strength on selfsigned cert. Otherwise, do
openssl x509 -in temp0 -noout -text -certopt no_pubkey,no_sigdump,no_extensions
and you should get a few lines of output that includes signatureAlgorithm: x where x is of the form {hash}withRSAEncryption ecdsa_with_{hash} dsa-with-{hash} or dsaWith{hash}. If {hash} is MD5, the signature on your certificate is weak and doesn't provide the security it claims -- although in your situation, if the server accepts it in spite of this weakness, it's probably not your problem.
If the first (leaf) cert is not selfsigned (subject not same as issuer) and its signature algorithm does not use MD5, either
the OpenSSL used by your nodejs is set to a higher than usual 'security level' -- this is not likely for a nodejs that has embedded OpenSSL but more plausible for a system-provided one, especially on security-focussed systems like RedHat 8; or
the problem is with a cert other than the first one (i.e. a CA cert), but this shouldn't occur if your cert(s) are from a properly-run CA because such a CA should never have a higher CA cert signed with a weaker key or signature algorithm than the leaf cert, except the selfsigned root whose signature doesn't matter, and isn't checked so it wouldn't cause your error. However, if you want, break each block other than the first into a separate file, and repeat the openssl x509 procedure above on each except if the last one has subject and issuer the same don't check it because it is the selfsigned root cert.
Anyway, workaround: add to your https-Agent options ciphers: "DEFAULT:#SECLEVEL=0". This should turn off several checks designed to prevent insecure SSL/TLS connections, including the one relevant here; as a result, if you use this code to process data of any importance, it may be at higher risk depending on what the server does.
I want to creat a Certificate Request with the Certreq.exe Command.
To start a new request I need the mandatory inf file.
There is my problem, I need a inf file which creates, except the normal Variables (CN, O, OU, Provider, length ...) exact the same as if I would create the Cert Crequest over the IIS GUI.
My Question therefor, is there a way to find out what the "standard" key arguments that MS uses are or can I get this from an already create certificate (I know the Cert Details, there arent all infos need)?
Thanks
If you want to get information about existing IIS SSL certificate you can do that by using command
certutil -v -store my
This will show (probably all) information that you need to make inf file for certreq like Subject, SubjectAlternativeName, extensions, exportable flag and CSP name.
Inf file would then look like (taken from here)
[Version]
Signature="$Windows NT$"
[NewRequest]
;Change to your,country code, company name and common name
Subject = "C=US, O=Example Co, CN=something.example.com"
KeySpec = 1
KeyLength = 2048
Exportable = TRUE
MachineKeySet = TRUE
SMIME = False
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = PKCS10
KeyUsage = 0xa0
[EnhancedKeyUsageExtension]
OID=1.3.6.1.5.5.7.3.1 ; this is for Server Authentication / Token Signing
To generate the request you would then run command
certreq -new request.inf request.csr
and send request.csr to a CA to issuing a certificate. CA may use all information in your certificate request but does not have to, i.e. it might change extensions like enhanced key usage and add bot Client Authentication and Server Authentication.
I'm new to security so some of the terms might be used incorrectly:
When I create a socket connection using SSL_connect, the server should send back the entire certificate chain so that the authenticity of the server can be verified.
For this to happen, the server needs to be configured accordingly.
If the server doesn't send back the entire certificate chain and the intermediate certificate isn't in the client certificate store, the authenticity can't be verified. This results in the behavior experienced here in which FireFox regards a website as unsafe.
I've also read that some browsers are able to attain the intermediate certificate automatically. However, OpenSSL doesn't behavior like this, at least by default.
I've also been told that some (maybe all) intermediate certificates have been installed in Windows certificate stores since some Windows update was rolled out a few years ago.
I would like to view the certificates returned by the server to verify that I'm getting back the entire certificate chain. Here's what I've tried:
I'm using SSLv3_method.
SSL_CTX_set_verify is set using SSL_VERIFY_NONE.
After SSL_connect I use SSL_get_peer_cert_chain to get access to the certificate chain.
Consider:
STACK_OF(X509)* certificateChain = SSL_get_peer_cert_chain(ssl);
while (char* stackCertificate = sk_pop(certificateChain))
{
X509* certificate = (X509*)stackCertificate;
}
Is this the correct way to get the certificate chain? Is my understanding of the situation correct? Is there perhaps a better way to do this?
Thank you for your time and contribution.
The following code snippet is based off code in s_client:
SSL* ssl = ...;
STACK_OF(X509)* certCollection = SSL_get_peer_cert_chain(ssl);
for (size_t i = 0; i < sk_X509_num(certCollection); i++)
{
X509* cert = sk_X509_value(certCollection, i);
...
}
As far as I understand, an SSL session must have been created otherwise SSL_get_peer_cert_chain will return null. Additionally I haven't found any evidence to contradict the list I noted in my question.
Perhaps an easier alternative would be to use the command line tool (downloaded from here):
openssl s_client -connect {server}:{port} -ssl3
This question already has answers here:
SSL and man-in-the-middle misunderstanding
(5 answers)
Closed 8 years ago.
I am using OpenSSL to connect over HTTPS to one of my servers. However I cannot seem to get server verification to work on the client side. From what I understand, not verifying the certificate leaves me open to Man In the Middle attacks, but the certificate verification is basically looking for the ip address and domain name within the certificate to match. (I am saying a lot of things wrong just to get some full detailed responses :) )
So if it's my server, I know its domain name and ip address, and I am using SSL, should I be worried? Then again, couldn't the man in the middle decrypt my ssl data, insert malicious code, re-encrypt, and then forward me my server's certificate anyway?
Lastly, if MITM attacks are an issue, what if I check the certificate with another library first to verify, and then use OpenSSL just not with verification?
Are there any other attacks that could happen?
but the certificate verification is basically looking for the ip address and domain name within the certificate to match
A certificate binds a public key to an entity (an identity like a person or organization). The binding occurs via a signature from an authority. Validation ensures the signature is present, and the entity presenting the certificate is who they say they are.
The way you identify the peer is through DNS names. If DNS is compromised, or the hostname checks are omitted, then the system crumbles.
So you need to trust both the certification authority and DNS. DNS does not provide authenticity assurances (or more correctly, clients don't use the security mechanisms), so you should consider DNS as untrusted input.
However I cannot seem to get server verification to work on the client side.
With OpenSSL, you need to do three things in the client. First, you need to ensure the server presents a certificate. Second, you need to verify the chain. Third, you need to perform hostname matching because OpenSSL doe not do it as part of chain verification.
Server Certificate
You need to verify the server has a certificate because some of the protocols and cipher suites don't require a certificate. You can do that with:
X509* cert = SSL_get_peer_certificate(ssl);
if(cert) { X509_free(cert); }
if(NULL == cert) handleFailure();
Chain Verification
This applies if you have a custom verify callback, but it works with both the standard verification built into OpenSSL and a custom verify callback. To get the verify result from chain verification, perform:
long res = SSL_get_verify_result(ssl);
if(!(X509_V_OK == res)) handleFailure();
Hostname Verification
OpenSSL prior to 1.0.2 does not verify hostnames. You will have to extract the hostnames from the server's certificate and ensure its the site you visited. If you want to borrow the code, take a look at libcurl and the verification procedure in source file ssluse.c.
If you want to perform the manual verification against a spec, see RFC 6125, Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS).
For completeness, here's how to fetch the DNS names present in the Subject Alternate Names (SAN) of a certificate. You can get the X509* from a function like SSL_get_peer_certificate.
void print_san_name(X509* const cert)
{
GENERAL_NAMES* names = NULL;
unsigned char* utf8 = NULL;
do
{
if(!cert) break; /* failed */
names = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0 );
if(!names) break;
int i = 0, count = sk_GENERAL_NAME_num(names);
if(!count) break; /* failed */
for( i = 0; i < count; ++i )
{
GENERAL_NAME* entry = sk_GENERAL_NAME_value(names, i);
if(!entry) continue;
if(GEN_DNS == entry->type)
{
int len1 = 0, len2 = -1;
len1 = ASN1_STRING_to_UTF8(&utf8, entry->d.dNSName);
if(!utf8) continue;
len2 = (int)strlen((const char*)utf8);
/* If there's a problem with string lengths, then */
/* we skip the candidate and move on to the next. */
/* Another policy would be to fail since it probably */
/* indicates the client is under attack. */
if(len1 != len2) {
fprintf(stderr, "Strlen and ASN1_STRING size do not match
"(embedded null?): %d vs %d\n", len2, len1);
/* Potential problem with the DNS name. Skip it */
/* TODO: test against IDNs */
OPENSSL_free(utf8), utf8 = NULL;
continue;
}
/* Perform matching here */
fprintf(stdout, " SAN: %s\n", utf8);
OPENSSL_free(utf8), utf8 = NULL;
}
else
{
fprintf(stderr, " Unknown GENERAL_NAME type: %d\n", entry->type);
}
}
} while (0);
if(names)
GENERAL_NAMES_free(names);
if(utf8)
OPENSSL_free(utf8);
}
There's a sample program on the OpenSSL wiki at TLS Client. It covers the Server Certificate and Chain Verification. You will have to provide the code for Hostname Verification.
From what I understand, not verifying the certificate leaves me open to Man In the Middle attacks
If you don't perform validation, you might as well save the cycles and choose an anonymous scheme like ADH.
So if it's my server, I know its domain name and ip address, and I am using SSL, should I be worried? ... if MITM attacks are an issue, what if I check the certificate with another library first to verify, and then use OpenSSL just not with verification? ... Are there any other attacks that could happen?
There's a lot to it, and there's no single answer. Since you are building clients that know the server a priori, consider moving to a pinning scheme so you can forgo trusting CAs and DNS. See for example, OWASP's Certificate and Public Key Pinning.
Also, read Peter Guttman's Engineering Security. A significant portion of the book discusses the systemic defects in PKI, SSL, TLS and human behavior; and how you can improve your security posture.
Basically there are two verification's for Https:
i) Authenticating certificates provided by the sever/client against each others trust stores.
For this you have to implement keystore/truststore for server/client instances and add your certificates to keystore and trusted certificates to trust store. This will accomplish certificate verification.
FYI: Keystore and truststore
ii) On other hand HTTPS will do hostname verification to prevent man in the middle attacks. Basically this verification method will return "true" if ip specified in the https url matches with CommonName presented by the certificate. If you know which ip's to trust you can override this verify method.
FYI: Hostname verification
This is an extension question to:
how-do-i-create-a-self-signed-certificate-for-code-signing-on-windows
To take this further, if I have created a CA cert, and have create a set of SPC certs, how do I go about creating revocation lists and distributing them? (note: I have no knowledge about how CRLs work, how they are distributed etc) If I were to GUESS how it all worked, I would hope that the CA cert defined some HTTP address where CRLs could be downloaded, and windows would contact that address the first time a cert chain was queried, and every time the current CRL expires... Then all I would have to do is create a signed web address that distributes certificate serial numbers...?
EDIT: SELF ANSWERED
For anyone else who is interested, Bouncy Castle is a Java+C# library providing a massive set of PKI Crypto APIs, including certificate generation.
Their sample code (in their downloads) demonstrates how to generate a chained set of CA, Intermediate, and 'Personal' certificates.
What it doesnt show, is how to correctly assign an HTTP based CRL - you can do so with this code:
GeneralName gn = new GeneralName(new DerIA5String("http://localhost/revocationlist.crl"), 6);
GeneralNames gns = new GeneralNames(gn);
DistributionPointName dpn = new DistributionPointName(gns);
DistributionPoint distp = new DistributionPoint(dpn, null, null);
DerSequence seq = new DerSequence(distp);
v3CertGen.AddExtension(X509Extensions.CrlDistributionPoints, false, seq);
As of crypto-147 the supplied code has changed to
GeneralName gn = new GeneralName(GeneralName.uniformResourceIdentifier, new DERIA5String(crlUrl));
GeneralNames gns = new GeneralNames(gn);
DistributionPointName dpn = new DistributionPointName(gns);
DistributionPoint distp = new DistributionPoint(dpn, null, null);
DERSequence seq = new DERSequence(distp);
certGen.addExtension(Extension.cRLDistributionPoints, false, seq);
Suppose you have a CA certificate and some set of certificates, signed by that CA certificate. Then you can create a CRL, which would (potentially) contain IDs of revoked certificates, which were previously signed using CA certificate. Indeed you add the URL of the CRL to the CA certificate itself via the corresponding certificate extension (CRLDistributionPoint).
As you have not specified, what tools or libraries you use to generate the certificates, I can't say how the extension can be added.
PS: I'd recommend that you learn about technology before trying to use it. Especially when it comes to implementing security. Otherwise you will end up in situation, worse than the one of Comodo, whose sub-CAs have issued fake certs for google, yahoo and more just recently.