Analyzing SSL Certificates in Scala with Akka

This example shows how to use Akka to get SSL certificate information (i.e. public keys) from a large list of domains.

To do this, we’ll need to create a buil.sbt that pulls in Akka, a CSV reader, and a solr client (this is where we’ll store the output).

libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.2.1"
libraryDependencies += "commons-net" % "commons-net" % "3.3"
libraryDependencies += "com.github.tototoshi" %% "scala-csv" % "1.3.0"
resolvers += "amateras-repo" at "http://amateras.sourceforge.jp/mvn/"
libraryDependencies += "jp.sf.amateras.solr.scala" %% "solr-scala-client" % "0.0.12"

For this script, we’ll spin up a bunch of worker actors, and a master actor that knows when the script completes. Each time an actor processes a domain, it sends a message to the master, so it can do reference counting.

To do this, we’ll need a message class (the first message goes from the orchestration function to the actors, and the second from the actors to the ‘master’ actor).

sealed trait Message
case class SSLCheck(domain: String) extends Message
case class Success() extends Message

Now we can set up the actor system:

object SSLChecker {
  def main(args: Array[String]) {
    val system = ActorSystem("SSLChecker")
    val nrOfWorkers = 100

    val sslRouter = system.actorOf(
      Props(new CertificateChecker())
        .withRouter(
          RoundRobinRouter(nrOfInstances = nrOfWorkers)
        ), name = "ssl")

    val f = new File(args(0))
    val reader = CSVReader.open(f)
    val domains = reader.iterator.map(
      (x: Seq[String]) => {
        try {
          x(1).trim
        } catch {
          case e: Throwable => {
            ""
          }
        }
      }
    ).toList

    system.actorOf(
      Props(
        new Master(domains.length)
      ),
      name = "master")

    domains.foreach((domain) => {
      sslRouter ! SSLCheck(domain)
    })
  }
}

The actual work is done within an actor. This gets a connection to Solr, where it writes the results:

class CertificateChecker() extends Actor {
  val client = new SolrClient("http://localhost:8983/solr/ssl_certificates")

  def receive = {
    case SSLCheck(domain) => {
      try {
        val testUrl = "https://www." + domain
        println(testUrl)
        val url = new URL(testUrl)
        val newConn = url.openConnection

        newConn.setConnectTimeout(500)
        newConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)");

        val conn: HttpsURLConnectionImpl = newConn match {
          case httpsConn: HttpsURLConnectionImpl => httpsConn
          case conn => {
            println(conn.getClass)
            ???
          }
        }

        conn.connect()

        conn.getContent
        val certs = conn.getServerCertificateChain
        var certIndex = 0
        for (cert <- certs) {
          val result =
            List(
              domain,
              certIndex,
              conn.getCipherSuite,
              cert.hashCode,
              cert.getPublicKey().getAlgorithm,
              cert.getPublicKey().getFormat,
              cert.getSigAlgName,
              cert.getSigAlgOID,
              cert.getIssuerDN,
              cert.getSerialNumber,
              cert.getSubjectDN,
              cert.getVersion,
              cert.getNotAfter,
              cert.getNotBefore,
              cert.getPublicKey.getFormat,
              cert.getPublicKey.getAlgorithm,
              cert.getIssuerDN.getName
            )

          certIndex = certIndex + 1

          val level = certIndex match {
            case 1 => "root"
            case 2 => "intermediate"
            case 3 => "client"
            case _ => "unknown"
          }

          client
            .add(
              Map(
                "domain"->domain,
                "level"->level,
                "cipherSuite"->conn.getCipherSuite,
                "hashCode"->cert.hashCode,
                "publicKeyAlgorithm"->cert.getPublicKey().getAlgorithm,
                "publicKeyFormat"->cert.getPublicKey().getFormat,
                "signatureAlgorithmName"->cert.getSigAlgName,
                "signatureAlgorithmOID"->cert.getSigAlgOID,
                "issuerDN"->cert.getIssuerDN,
                "serialNumber"->cert.getSerialNumber,
                "subjectDN"->cert.getSubjectDN,
                "subjectName"->cert.getSubjectDN.getName,
                "version"->cert.getVersion,
                "expirationDate"->cert.getNotAfter,
                "effectiveDate"->cert.getNotBefore,
                "publicKeyFormat"->cert.getPublicKey.getFormat,
                "publicKeyAlgorithm"->cert.getPublicKey.getAlgorithm,
                "issuerDNName"->cert.getIssuerDN.getName
              ))
        }

        client.commit

        context.system.actorSelection("/user/master") ! Success()
      }
      catch {
        case e: Throwable => {
          println("Error: " + e.getMessage)
          context.system.actorSelection("/user/master") ! Success()
        }
      }
    }
    case _ => {
      println("Unknown message")
    }
  }
}

Finally, the master actor just tracks the results and shuts down on completion:

class Master(totalDomains: Int) extends Actor {
  var counter = 0
  def receive = {
    case Success() => {
      counter += 1
      if(counter == totalDomains) {
        context.system.shutdown
      }
    }
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *