Skip to content

Commit

Permalink
util-security: Introduce X509CrlFile for Loading CRLs
Browse files Browse the repository at this point in the history
Summary: Problem / Solution

CRL (Certificate Revocation List) files should be as easy to load as X.509v3
certificate files. This commit adds support to util-security for loading CRL
files via `X509CrlFile`.

JIRA Issues: CSL-5819

Differential Revision: https://phabricator.twitter.biz/D127700
  • Loading branch information
ryanoneill authored and jenkins committed Jan 13, 2018
1 parent 073c2d3 commit 32d8cc8
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 9 deletions.
6 changes: 6 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ Note that ``PHAB_ID=#`` and ``RB_ID=#`` correspond to associated messages in com

Unreleased

New Features:

* util-security: Added `c.t.util.security.X509CrlFile` for reading
Certificate Revocation List PEM formatted `X509CRL` files.
``PHAB_ID=D127700``

17.12.0 2017-12-08

API Changes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,6 @@ class X509CertificateFile(file: File) {
private[this] def logException(ex: Throwable): Unit =
log.warning(s"X509Certificate (${file.getName}) failed to load: ${ex.getMessage}.")

private[this] def generateX509Certificate(decodedMessage: Array[Byte]): X509Certificate = {
val certFactory = CertificateFactory.getInstance("X.509")
val certificate = certFactory
.generateCertificate(new ByteArrayInputStream(decodedMessage))
.asInstanceOf[X509Certificate]
certificate.checkValidity()
certificate
}

/**
* Attempts to read the contents of the X.509 Certificate from the file.
*/
Expand Down Expand Up @@ -57,4 +48,13 @@ private object X509CertificateFile {
private val MessageType: String = "CERTIFICATE"

private val log = Logger.get("com.twitter.util.security")

private def generateX509Certificate(decodedMessage: Array[Byte]): X509Certificate = {
val certFactory = CertificateFactory.getInstance("X.509")
val certificate = certFactory
.generateCertificate(new ByteArrayInputStream(decodedMessage))
.asInstanceOf[X509Certificate]
certificate.checkValidity()
certificate
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.twitter.util.security

import com.twitter.logging.Logger
import com.twitter.util.Try
import com.twitter.util.security.X509CrlFile._
import java.io.{ByteArrayInputStream, File}
import java.security.cert.{CertificateFactory, X509CRL}

/**
* A representation of an X.509 Certificate Revocation List (CRL)
* PEM-encoded and stored in a file.
*
* @example
* -----BEGIN X509 CRL-----
* base64encodedbytes
* -----END X509 CRL-----
*/
class X509CrlFile(file: File) {

private[this] def logException(ex: Throwable): Unit =
log.warning(s"X509Crl (${file.getName}) failed to load: ${ex.getMessage}.")

def readX509Crl(): Try[X509CRL] = {
val pemFile = new PemFile(file)
pemFile
.readMessage(MessageType)
.map(generateX509Crl)
.onFailure(logException)
}

}

private object X509CrlFile {
private val MessageType: String = "X509 CRL"

private val log = Logger.get("com.twitter.util.security")

private def generateX509Crl(decodedMessage: Array[Byte]): X509CRL = {
val certFactory = CertificateFactory.getInstance("X.509")
certFactory
.generateCRL(new ByteArrayInputStream(decodedMessage))
.asInstanceOf[X509CRL]
}
}
14 changes: 14 additions & 0 deletions util-security/src/test/resources/crl/csl-intermediate-garbage.crl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-----BEGIN X509 CRL-----
MIIDKTCCARECAQEwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVTMRMwEQYD
VQQIDApDYWxpZm9ybmlhMRAwDgYDVQQKDAdUd2l0dGVyMR8wHQYDVQQLDBZDb3Jl
IFN5c3RlbXMgTGlicmFyaWVzMRwwGgYDVQQDDBNDU0wgSW50ZXJtZWRpYXRlIENB
MSAwHgYJKoZIhvcNAQkBFhFyeWFub0B0d2l0dGVyLmNvbRcNMTgwMTEyMTkzMTQ0
1jNNCn+9h0GfQniDxxE8Vd7zolky7uO5lLQHzLEuqXrCein9TqKTgE4X54XrJrZH
BMMKGYomoPqLhoocqHMSyJs0a0PYlbvL6VIFRpWdaczeZZrP5/wAsbUs6bNsprvZ
81Nx7XFPTo746aRyMFXUOOJQMfS7hu38sxSBk04+jTY/+3dn+VrGIcRCXB5i2HjZ
kdkHcBXPib/Hl5fKIWAIa0FDJ6Kq6+6iLqX+WjW35sQW3V81ZAi34vTagtPj9TcO
b9SdxkkHO0VBGXzKd0aBrdQNKe/yrNNAz7l1/RbUp8CrIG58GDoXs+wr3yubSUwU
WNoMe+STHFIQD6o1AQkmbXFqaRO71L/sTbTQzzlraqugo9bv1T4RZOIbENexNX50
nF/B4RRNM38+YgcXEc7TegCh866WAsiIfO4ejtveGNigQqYbDfmsZknDlSqthRxi
Rdhk0MG4Vuo/wBo5/F5JIm3viNj2SLE2LxEKpFAEneEXhkWKhCDmRjas0F/5
-----END X509 CRL-----
19 changes: 19 additions & 0 deletions util-security/src/test/resources/crl/csl-intermediate.crl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN X509 CRL-----
MIIDKTCCARECAQEwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVTMRMwEQYD
VQQIDApDYWxpZm9ybmlhMRAwDgYDVQQKDAdUd2l0dGVyMR8wHQYDVQQLDBZDb3Jl
IFN5c3RlbXMgTGlicmFyaWVzMRwwGgYDVQQDDBNDU0wgSW50ZXJtZWRpYXRlIENB
MSAwHgYJKoZIhvcNAQkBFhFyeWFub0B0d2l0dGVyLmNvbRcNMTgwMTEyMTkzMTQ0
WhcNMTgwMjExMTkzMTQ0WjAVMBMCAhAMFw0xODAxMTIxOTMxMjFaoDAwLjAfBgNV
HSMEGDAWgBRaEYAYDZMHjVdH+tBvLkOSFMKF8TALBgNVHRQEBAICEAEwDQYJKoZI
hvcNAQELBQADggIBAKzuybkOzirP09GhJpZ86gdkL/uB1TF8SlMLeFEZCr/Ng5sm
1jNNCn+9h0GfQniDxxE8Vd7zolky7uO5lLQHzLEuqXrCein9TqKTgE4X54XrJrZH
BMMKGYomoPqLhoocqHMSyJs0a0PYlbvL6VIFRpWdaczeZZrP5/wAsbUs6bNsprvZ
81Nx7XFPTo746aRyMFXUOOJQMfS7hu38sxSBk04+jTY/+3dn+VrGIcRCXB5i2HjZ
p82AdrUQQjnsOyIlUgGOjv6ayzMro7c/FWCfLqqbD6sViuAoXWU2ODKjDqeNlMhq
vVakF8iDSNLYXVbxOdnTk+/yw1h1SFmBnpF64xBGqWp4bda3mvwBzhD/pezQa2M7
kdkHcBXPib/Hl5fKIWAIa0FDJ6Kq6+6iLqX+WjW35sQW3V81ZAi34vTagtPj9TcO
b9SdxkkHO0VBGXzKd0aBrdQNKe/yrNNAz7l1/RbUp8CrIG58GDoXs+wr3yubSUwU
WNoMe+STHFIQD6o1AQkmbXFqaRO71L/sTbTQzzlraqugo9bv1T4RZOIbENexNX50
nF/B4RRNM38+YgcXEc7TegCh866WAsiIfO4ejtveGNigQqYbDfmsZknDlSqthRxi
Rdhk0MG4Vuo/wBo5/F5JIm3viNj2SLE2LxEKpFAEneEXhkWKhCDmRjas0F/5
-----END X509 CRL-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.twitter.util.security

import com.twitter.io.TempFile
import com.twitter.util.Try
import java.io.File
import java.security.cert.{CRLException, X509CRL}
import org.scalatest.FunSuite

class X509CrlFileTest extends FunSuite {

private[this] val assertLogMessage =
PemFileTestUtils.assertLogMessage("X509Crl") _

private[this] def assertCrlException(tryCrl: Try[X509CRL]): Unit =
PemFileTestUtils.assertException[CRLException, X509CRL](tryCrl)

private[this] val readCrlFromFile: File => Try[X509CRL] =
(tempFile) => {
val crlFile = new X509CrlFile(tempFile)
crlFile.readX509Crl()
}

test("File path doesn't exist") {
PemFileTestUtils.testFileDoesntExist("X509Crl", readCrlFromFile)
}

test("File path isn't a file") {
PemFileTestUtils.testFilePathIsntFile("X509Crl", readCrlFromFile)
}

test("File isn't a crl") {
PemFileTestUtils.testEmptyFile[InvalidPemFormatException, X509CRL](
"X509Crl",
readCrlFromFile
)
}

test("File is garbage") {
val handler = PemFileTestUtils.newHandler()
// Lines were manually deleted from a real crl file
val tempFile = TempFile.fromResourcePath("/crl/csl-intermediate-garbage.crl")
// deleteOnExit is handled by TempFile

val crlFile = new X509CrlFile(tempFile)
val tryCrl = crlFile.readX509Crl()

assertLogMessage(handler.get, tempFile.getName, "Incomplete BER/DER data.")
assertCrlException(tryCrl)
}

test("File is a Certificate Revocation List") {
val tempFile = TempFile.fromResourcePath("/crl/csl-intermediate.crl")
// deleteOnExit is handled by TempFile

val crlFile = new X509CrlFile(tempFile)
val tryCrl = crlFile.readX509Crl()

assert(tryCrl.isReturn)
val crl = tryCrl.get()

val principalInfo = new X500PrincipalInfo(crl.getIssuerX500Principal)
assert(principalInfo.organizationalUnitName.isDefined)
assert(principalInfo.organizationalUnitName.get == "Core Systems Libraries")
}

}

0 comments on commit 32d8cc8

Please sign in to comment.