How to Mimic Browser X509 Client Certificate Verification Without Access to Http Layer

How to mimic browser X509 client certificate verification without access to HTTP layer

Your app stack isn't able to control SSL cause it's terminated on the web server. All the verification stuff is done by the web server and then only SSL_* headers go to the app server (look for Apache's SSLOptions for example). If you can't control web server you probably cant even turn on client certificate requirement. And i can't find any options to turn this requirement on via heroku: https://devcenter.heroku.com/articles/ssl#customdomain-ssl

If you've got SSL_* headers somehow then, regardless of web-server-side verification, you can verify certificate (commonly in the SSL_CLIENT_CERT header) using openssl as an army knife. See this for example in ruby: OpenSSL verify certificate from own CA

Also keep in mind that certificate verification is the process of checking certificate sign and other properties e.g. formal checking. To authenticate user you have to link that verified certificate to user by DN or E field, for example.

How to access client X.509 certificate from Ruby web service (not Rails)?

Using an Apache web server listening on port 443 with SSL configured, as a reverse proxy to a Thin server running a Sinatra app, for example, I can add these to my Apache VirtualHost configuration:

RequestHeader set SSL-Client-M-Serial %{SSL_CLIENT_M_SERIAL}s
RequestHeader set SSL-Client-S-DN %{SSL_CLIENT_S_DN}s
RequestHeader set SSL-Client-I-DN %{SSL_CLIENT_I_DN}s

Then from my Sinatra code, I can find these values in the Rack environment, for example:

puts "#{request.env['HTTP_SSL_CLIENT_S_DN']}"

I don't have the full client certificate, which is OK since I'm having Apache verify the certificate.

Authenticate Web Browser with SSL certificate

You can authenticate a browser/user using SSL/TLS client-certificate authentication.

The client certificate must be requested by the server, so you'd need access to the server configuration (not just installing some PHP code on a shared server). This is done at the SSL/TLS layer (in fact, the mechanism is not specific to HTTPS): the server requests the client-certificate during the SSL/TLS handshake (sometimes via a renegotiated handshake). In Apache Httpd, this is typically done via SSLVerifyClient (although you'll need to specify other options too).

The server will then verify the certificate against the CAs you've configured it with (possibly your own, and possibly independent of the CAs used for the server certificate itself). (Alternatively, you could disable certificate verification at the server level in some cases, and have the PHP application do it, but this is a bit more advanced and you'd need to know what you're doing.)

You can access the client certificate from your application and obtains its Subject DN (or alternative names), to identify the client.

It's not clear whether you're after identifying a browser or a user. In the end, everything goes through the browser anyway, but client certificates tend to be allocated to users. Users would have to install that certificate into their browser.


EDIT: For further details, it would help if you could clarify your question and what you intend to do with this.

Is it possible to authenticate a web browser using an ssl certificate.

Say i store a private key in my application, is there any way to read
a key from a browser and try to authenticate based on that?

Firstly, strictly speaking, there's no such thing as an "SSL certificate", since multiple types of certificates can be used for SSL/TLS, and some of these same certificates can also be used for other purposes than SSL/TLS. Typically, "SSL certificate" means "X.509 certificate in the context of SSL/TLS".

Therefore, authenticating a web browser using an SSL certificate implies doing it at the SSL/TLS layer. (There have been attempts to implement message-level security using X.509 certificates at the HTTP layer, but they're not widely supported by browsers.)

Secondly, the private key is held by the remote party that you authenticate. The local party that authenticates the remote party doesn't see any private key. If you (as a server) want to authenticate a web browser, it's the browser that needs to have the private key, not your (presumably PHP) application. In this context, it's not quite clear why your (PHP?) application would have/need a private key if it's the browser that you want to authenticate.

What your verifying application may need (if it's not done by the server itself) is a CA certificate to be able to verify the client certificate it is presented with (or at least some form of trust anchors with which to verify the client certificate). There's no private key required here, just public keys and certificates, unless you want your application to be a CA too.

Indeed, you could have your application be a mini CA. It could make the browser generate a key-pair and send a certificate request to the server (there are mechanisms to have a web page make the browser do all that). Then the server would generate the certificate and make the browser import it back against its private key. Subsequently, the browser could use this certificate for authentication with that server (or other servers that would recognise these certificates).

Loading a X509 pem certificate with password. Objective C

My Own Solution:

Using OpenSSL command to convert .pem file intro pkcs12 certificate:

openssl pkcs12 -export -in "certificateFile" -inkey "KeyFile" -out "certificate.p12"

Then setting up some code found in this tutorial: https://vanjakom.wordpress.com/tag/nsurlconnection/

Basically:

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(@"Authentication challenge");

// load cert
NSString *path = [[NSBundle mainBundle] pathForResource:@"userA" ofType:@"p12"];
NSData *p12data = [NSData dataWithContentsOfFile:path];
CFDataRef inP12data = (__bridge CFDataRef)p12data;

SecIdentityRef myIdentity;
SecTrustRef myTrust;
OSStatus status = extractIdentityAndTrust(inP12data, &myIdentity, &myTrust);

SecCertificateRef myCertificate;
SecIdentityCopyCertificate(myIdentity, &myCertificate);
const void *certs[] = { myCertificate };
CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);

NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:(__bridge NSArray*)certsArray persistence:NSURLCredentialPersistencePermanent];

[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}

And

OSStatus extractIdentityAndTrust(CFDataRef inP12data, SecIdentityRef *identity, SecTrustRef *trust)
{
OSStatus securityError = errSecSuccess;

CFStringRef password = CFSTR("userA");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };

CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);

CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import(inP12data, options, &items);

if (securityError == 0) {
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
*identity = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust);
*trust = (SecTrustRef)tempTrust;
}

if (options) {
CFRelease(options);
}

return securityError;
}

"Code copied from the tutorial, did not copy my own modification for my own needs".

And it's solved

HTTP library for Ruby with HTTPS, SSL Client Certificate and Keep-Alive support?

Is there something that already works?

I believe the HTTP client library Faraday is focus of more Ruby community action is these days.

It comes with a :net_http_persistent adapter, which supports SSL client certificates. You can probably do something like this:

ssl_options = {
cert: OpenSSL::X509::Certificate.new(File.read('./certificate/client-2048.pem')),
key: OpenSSL::PKey::RSA.new(File.read('./certificate/client-2048.key'), 'mypassword')
}
conn = Faraday.new(url: 'https://example.com', ssl: ssl_options) do |faraday|
faraday.adapter = Faraday::Adapter::NetHttpPersistent
end

conn.get '/my-resource'

HTTParty

According to the specs:

... the resulting connection when providing PEM certificates when scheme is https

  • uses the provided PEM certificate
  • will verify the certificate

You can use the pem classmethod to provide a client certificate in PEM format.

REST Client

It's not as dead as all that -- Larry Gilbert (@L2G) is still merging in pull requests and keeping the lights on. He's nodded his general approval in the issue tracker. I suspect it's just not at the top of his priority queue at the moment.

The guy who sent in that pull request, @byroot, has been keeping his code up to date so you shouldn't need to do much at all while you wait.

How to show personal certificates in JSF?

As you said in your comments you follow this tutorial and you can configure your tomcat to use two way ssl, now you have the next problems for which I try to give you some guide lines:

(1) Only show the certificate I created in the example

The problem is probably that your browser is only showing the certificate which are issued by the self-signed CA loaded in your server trust store. This is you have in your server.xml your tomcat <Connector> like:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
truststoreFile=".../cacerts.jks" truststorePass="changeit"
keystoreFile="..../keystore.jks" keystorePass="changeit"
clientAuth="true" sslProtocol="TLS" />

So if you want that the browser show all client certificates issued by a specific CA like for example verisign (may be in your case dnie, fnmt?) you have to add the issuer CA certificate (and the intermediate certificates if necessary) in cacerts.jks. You can do it with java keytool (you can found it in JAVA_HOME/bin/keytool):

keytool -import -trustcacerts -alias yourCaAlias -file CA_certificate.crt -keystore cacerts.jks

(2) this happens everytime I access my application, and I only wish that this happens when I press a button.

This can be do it in different ways, a simple approach for example is configure two connectors in your tomcat server.xml file; one for your public access through http and another one for private access with https. In the public part you could have the log in page with your log in button and some stuff, when user click on log in button the webapp can redirect to the connector where you've configured your client authentication (https://yourserver.com/blabla...) so the browser prompt the user to select a valid certificate to authenticate, then in your webapp you could have configured one servlet which can perform the certificate validation and the required things for your webapp like this:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.security.cert.X509Certificate;

// Extend HttpServlet class
public class ServletCertificateSample extends HttpServlet {
private String message;

public void init() throws ServletException {
// Do required initialization
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

}

private X509Certificate getCertificate(HttpServletRequest request) throws ServletException
X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
if (null != certs && certs.length > 0) {
// cert found
// performs certificate operations...
}
throw new ServletException("No client certificate found");
}

public void destroy() {
// do nothing.
}
}

I hope that this puts you on the right direction,



Related Topics



Leave a reply



Submit