Initially, I was going to put out this big announcement about how I had successfully ported my cryptonark script from Perl to Groovy. Due to the Sun JDK not supporting SSLv2, the final product did not wind up being as feature complete as the Perl version, so as a PCI compliance tool, the groovy version won't cut it. However, I did learn some interesting things about java and groovy along the way and the script itself is still pretty useful for someone who wants to know which ssl ciphers are supported on a particular host.
Below are some of the things I've learned about ssl, groovy, and java while writing this script.
- As previously mentioned, the sun jdk does not support SSLv2—it was disabled intentionally some time ago. It does support SSLv2 Hello's but this is not the same thing as an SSLv2 connection. Apparently IBM's JDK does support SSLv2 but I have not tested this script on anything other than Apple's Java 1.5.0_19 implementation under OS X 10.5.
- Finding Groovy example code illustrating ssl connectivity is extremely hard to do. Finding Java examples is a lot easier but most of the Java examples utilize inner classes, which groovy hates.
- I still need to figure out a way to format output better for console applications with groovy. The spacing that I put in front of the values was my low-tech way of lining everything up nicely. Does anyone have any pointers here?
- Anonymous Ciphers come in all encryption strengths, so there apparently really is no good reason to buy a 128bit-only (or stronger) ssl certificate from the CAs.
- Groovy's CliBuilder rocks. If you want to create useful command line application interfaces, use CliBuilder
- Colorization of groovy or java code within an xterm seems to be impossible, which is too bad because Perl and Ruby make it so easy.
The script will be available in binary form for download soon on the Downloads page. To reiterate, do not use it as a PCI Compliance auditing tool but consider it as one of those useful tools that may make your day go a little easier. Running it with no arguments will allow you to see the usage info. The host
argument is required. Port number is set to 443 unless overridden with the port
argument. In the source code below, both the SSLv2Hello test and the TLS test are commented out because the results are redundant. Uncomment them if you wish to test with SSLv2Hello packets and TLS Hello packets as well.
#!/usr/bin/env groovy
// Usage: ./whichCiphers.groovy --host $host --port $port
import javax.net.ssl.*
/* This section below sets up the cipher map.
Yes...the spacing is intentional. It's the
only way I currently know to format the output
nicely */
def ciphers = [
'TLS_RSA_WITH_AES_128_CBC_SHA': ' High-Grade, 128-bit',
'TLS_RSA_WITH_AES_256_CBC_SHA': ' High-Grade, 256-bit',
'TLS_DHE_RSA_WITH_AES_128_CBC_SHA': 'High-Grade, 128-bit',
'TLS_DHE_RSA_WITH_AES_256_CBC_SHA': 'High-Grade, 256-bit',
'TLS_DHE_DSS_WITH_AES_128_CBC_SHA': 'High-Grade, 128-bit',
'TLS_DHE_DSS_WITH_AES_256_CBC_SHA': 'High Grade, 256-bit',
'TLS_DH_anon_WITH_AES_128_CBC_SHA': 'Anonymous Cipher, 128-bit',
'TLS_DH_anon_WITH_AES_256_CBC_SHA': 'Anonymous Cipher, 256-bit',
'SSL_RSA_WITH_3DES_EDE_CBC_SHA': ' High-Grade, 168-bit',
'SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA': 'High-Grade, 168-bit',
'SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA': 'High-Grade, 168-bit',
'SSL_RSA_WITH_RC4_128_MD5': ' Medium-Grade, 128-bit',
'SSL_RSA_WITH_RC4_128_SHA': ' Medium-Grade, 128-bit',
'SSL_RSA_WITH_DES_CBC_SHA': ' Low-Grade, 56-bit',
'SSL_DHE_RSA_WITH_DES_CBC_SHA': ' Low-Grade, 56-bit',
'SSL_DHE_DSS_WITH_DES_CBC_SHA': ' Low-Grade, 56-bit',
'SSL_RSA_EXPORT_WITH_RC4_40_MD5': 'Export-Grade, 40-bit',
'SSL_RSA_EXPORT_WITH_DES40_CBC_SHA': 'Export-Grade, 40-bit',
'SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA': 'Export-Grade, 40-bit',
'SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA': 'Export-Grade, 40-bit',
'SSL_RSA_WITH_NULL_MD5': ' Null Cipher, 0-bit',
'SSL_RSA_WITH_NULL_SHA': ' Null Cipher, 0-bit',
'SSL_DH_anon_WITH_RC4_128_MD5': ' Anonymous Cipher, 128-bit',
'SSL_DH_anon_WITH_3DES_EDE_CBC_SHA': 'Anonymous Cipher, 168-bit',
'SSL_DH_anon_WITH_DES_CBC_SHA': ' Anonymous Cipher, 56-bit',
'SSL_DH_anon_EXPORT_WITH_RC4_40_MD5': 'Anonymous Cipher, 40-bit',
'SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA': 'Anonymous Cipher, 40-bit'
]
def cli = new CliBuilder( usage: 'whichCiphers.groovy [-h "hostname"] [-p "port"] ' )
cli.h( longOpt:'host', args:1, required:true , type:GString, 'The hostname or site you want to test - REQUIRED' )
cli.p( longOpt:'port', args:1, required:false, type:GString, 'The ssl port--usually 443' )
def opt = cli.parse(args)
if (!opt) return
if (!opt.h) return
if (opt.h) host = opt.h
def port = 443
if (opt.p) port = Integer.parseInt(opt.p)
class listener implements HandshakeCompletedListener {
void handshakeCompleted(HandshakeCompletedEvent e ) {
}
}
/* Right now I'm just running through each one of these but
it should probably become a command line option instead
because the SSLv2Hello, SSLv3, and TLSv1 tests report back
the same results
SSLv2Hello test commented out as results are redundant.
Uncomment if you want to test with an SSLv2 Hello */
/*
println "Testing with an SSLv2Hello...."
ciphers.each{
try{
def factory = SSLSocketFactory.getDefault()
def socket = factory.createSocket("$host", port)
socket.setEnabledCipherSuites(it.key)
socket.setEnabledProtocols( "SSLv2Hello", "SSLv3" )
socket.addHandshakeCompletedListener( new listener( ))
socket.startHandshake()
println " ${it.key}\t${it.value} works!"
socket.close()
}catch(SSLHandshakeException ex) {
}
}
*/
println "Testing with SSLv3...."
ciphers.each{
try{
def factory = SSLSocketFactory.getDefault()
def socket = factory.createSocket("$host", port)
socket.setEnabledCipherSuites(it.key)
socket.setEnabledProtocols( "SSLv3" )
socket.addHandshakeCompletedListener( new listener( ))
socket.startHandshake()
println " ${it.key}\t${it.value} works!"
socket.close()
}catch(SSLHandshakeException ex) {
}
}
/* TLSv1 test commented out as results are redundant.
Uncomment to test using TLS Hello's */
/*
println "Testing with TLSv1..."
ciphers.each{
try{
def factory = SSLSocketFactory.getDefault()
def socket = factory.createSocket("$host", port)
socket.setEnabledCipherSuites(it.key)
socket.setEnabledProtocols( "TLSv1" )
socket.addHandshakeCompletedListener( new listener( ))
socket.startHandshake()
println " ${it.key}\t${it.value} works!"
}catch(SSLHandshakeException ex) {
}
}
*/