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
Related
I have an issue with the Windows Azure ACS and I can't quite determine if it's supposed to be that way, or if there's an error in my code.
I have a number of relying parties configured in the ACS and all of them are configured with HTTPS. Every service is configured in such a way that Token Encryption is required. For this, I've uploaded a certificate created using MakeCert.exe.
When the client communicates with the relying party, I add the public part of the certificate as the service certificate and I add the subject name as a DnsIdentity:
var identity = EndpointIdentity.CreateDnsIdentity( GetClientCertificateSubjectName() );
var serviceEndpointAddress = new EndpointAddress( new Uri( _serviceAddress ), identity );
// Creation of channel factory
if( channelFactory.Credentials != null ) {
channelFactory.Credentials.ServiceCertificate.DefaultCertificate = GetClientCertificate();
channelFactory.Credentials.ClientCertificate.Certificate = GetServiceIdentityCertificate();
}
Here's the thing: when I call the relying party over HTTPS, then I can skip the creation of the EndpointIdentity and then the relying party will give me a correct answer. I can also skip setting the ServiceCertificate.DefaultCertificate property or set a totally random certificate, and the relying party will still give me a correct answer.
When calling over HTTP, doing any of the above will result in the ACS erroring out with messages indicating that I haven't used the correct certificates. In short: when calling over HTTP, I can only communicate with the correct client certificate. I expected that this was the case for HTTPS as well.
I can imagine that the ChannelFactory<T> or the ACS is smart enough to detect that HTTPS is used and that the configured encryption is skipped, in favour of SSL encryption. Sadly, I can't find any documentation that supports this idea.
My question is: Is it normal to ignore the EndpointIdentity and certificates when calling a relying party over HTTPS? Or do I need additional configuration to make this work?
Thanks in advance!
The amount of information I gave turned out to be insufficient to properly answer the question. It turned out that it was all in the bindings we were creating. It creates a binding with the following piece of code:
public static Binding CreateServiceBinding( string acsCertificateEndpoint, string bindingNameSpace, bool useSsl ) {
var binding = new IssuedTokenWSTrustBinding( CreateAcsCertificateBinding(), new EndpointAddress( acsCertificateEndpoint ) );
if( useSsl ) {
binding.SecurityMode = SecurityMode.TransportWithMessageCredential;
}
if( !string.IsNullOrWhiteSpace( bindingNameSpace ) ) {
binding.Namespace = bindingNameSpace;
}
return binding;
}
public static CertificateWSTrustBinding CreateAcsCertificateBinding() {
return new CertificateWSTrustBinding( SecurityMode.TransportWithMessageCredential );
}
That results in the following:
If it is http communication, it goes through MutualCertificate authentication mode flow and it is applied on the message layer only. That is why client is mandated to present a client certificate. This binding element creates an asymmetric security binding element that is configured to require certificate-based client authentication as well as certificate-based server authentication.
If it is https communication, it goes through the CertificateOverTransport authentication mode flow and it is applied on transport layer only. That’s why even though client certificate is not presented, it works. This binding element expects the transport to provide server authentication as well as message protection (for example, HTTPS).
For more information on the security modes, check out the following links:
https://msdn.microsoft.com/en-us/library/ms733098%28v=vs.110%29.aspx
https://msdn.microsoft.com/en-us/library/ms731074%28v=vs.110%29.aspx
Hope this helps someone!
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
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.
I'm trying to figure out what iOS' policy is when verifying certificates using Security.Framework regarding revocation of certificates.
I cannot find information about this in the docs for iOS.
In the context of an iPad project I'm working on at the moment, there is reason to demand checking revocation status for some certs. Anyone ideas on how to force CRL / OCSP checking during cert verification using Security.Framework? Or do I need to "fall back" to OpenSSL to accomplish this?
It seems that also on Mac OS X 10.6 CRL / OCSP checks are done optionally and have to be turned on manually through Keychain Access.
Martijn
I have an answer to this question by Apple guys, I posted the full answer here:
Details on SSL/TLS certificate revocation mechanisms on iOS
To sum it up, there are several things to keep in mind for OCSP implementation on iOS:
OCSP policy cannot be configured at this moment
it works for the EV certificates only
high-level stuff, such as NSURLConnection or UIWebView use TLS security policy, which uses OCSP
SecTrustEvaluate is a blocking network operation
it works the "best attempt" - if OCSP server cannot be contacted, the trust evaluation will not fail
I just did this on iOS in GCDAsyncSocket.
For a given SecTrustRef trust;
do this
SecPolicyRef policy = SecPolicyCreateRevocation(kSecRevocationOCSPMethod)
SecTrustSetPolicies(trust, policy);
SecTrustResultType trustResultType = kSecTrustResultInvalid;
OSStatus status = SecTrustEvaluate(trust, &trustResultType);
if (status == errSecSuccess && trustResultType == kSecTrustResultProceed)
{
//good!
}
else
{
//not good
}
//edit to check the trustResultType
I was able to enable CRL checking for a SecTrustRef object on iOS 10:
SecTrustRef trust = ...; // from TLS challenge
CFArrayRef oldPolicies;
SecTrustCopyPolicies(trust, &oldPolicies);
SecPolicyRef revocationPolicy = SecPolicyCreateRevocation(kSecRevocationCRLMethod);
NSArray *newPolicies = [(__bridge NSArray *)oldPolicies arrayByAddingObject(__bridge id)revocationPolicy];
CFRelease(oldPolicies);
SecTrustSetPolicies(trust, (__bridge CFArrayRef)newPolicies);
SecTrustSetNetworkFetchAllowed(trust, true);
// Check the trust object
SecTrustResult result = kSecTrustResultInvalid;
SecTrustEvaluate(trust, &result);
// cert revoked -> kSecTrustResultRecoverableTrustFailure
Calling SecTrustSetNetworkFetchAllowed was key. Without that call, SecTrustEvaluate returned kSecTrustResultUnspecified instead.
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.