Skip to content

sureshg/certkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

59 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

certkit

GitHub Workflow Status Kotlin release OpenJDK Version Test Report

Lightweight X.509 certificate toolkit for Kotlin/JVM. Build self-signed certs, CSRs, CRLs, and work with PEM/DER encoding, all using JDK standard libraries. No BouncyCastle, no Guava.

Features

  • Self-signed certificates β€” X.509v3 with SAN, Basic Constraints, key identifiers (EC keys)
  • CSR creation β€” PKCS#10 Certificate Signing Requests with SAN support
  • CRL support β€” parse, build, and check certificate revocation lists
  • PEM read/write β€” load and encode certificates, private keys, public keys
  • DER DSL β€” type-safe Kotlin DSL for building ASN.1 DER structures
  • Private key formats β€” PKCS#8, PKCS#8 encrypted, PKCS#1 (RSA, DSA, EC)
  • TLS scanning β€” connect to any host and capture the certificate chain

πŸš€ Quick Start

Requires JDK 21+

dependencies {
    implementation("dev.suresh.certkit:certkit:1.0.0-SNAPSHOT")
}

Self-Signed Certificate

val keyPair = newEcKeyPair()
val today = Clock.System.todayIn(TimeZone.UTC)

val cert = Cert.buildSelfSigned(
    keyPair = keyPair,
    serialNumber = 1,
    issuer = X500Principal("CN=My CA,O=TestOrg"),
    subject = X500Principal("CN=My CA,O=TestOrg"),
    notBefore = today,
    notAfter = today + DatePeriod(days = 30),
    sans = listOf(San.Dns("localhost"), San.Dns("*.local"), San.Ip("127.0.0.1")),
)

println(cert.pem)

An Instant overload is also available for precise control over validity times.

Certificate Extensions

cert.commonName          // "My CA"
cert.subjectAltNames     // ["localhost", "*.local", "127.0.0.1"]
cert.expiryDateUtc       // 2026-04-07T23:59:59
cert.isExpired           // false
cert.expiresIn           // 30d 0h ...
cert.isCA                // true
cert.selfSigned          // true
cert.signedBy(caCert)    // true

PEM

// Load
val privateKey = Pem.loadPrivateKey(Path("server.key"), keyPassword = "secret")
val publicKey = Pem.loadPublicKey(Path("server.pub"))
val certs = Pem.readCertificateChain(Path("chain.crt"))
val keyStore = Pem.loadKeyStore(Path("server.crt"), Path("server.key"))
val trustStore = Pem.loadTrustStore(Path("ca.crt"))

// Encode β€” .pem extension on all major types
publicKey.pem                              // -----BEGIN PUBLIC KEY-----
privateKey.pem                             // -----BEGIN PRIVATE KEY-----
certificate.pem                            // -----BEGIN CERTIFICATE-----
csr.pem                                    // -----BEGIN CERTIFICATE REQUEST-----
crl.pem                                    // -----BEGIN X509 CRL-----

// PKCS#8 export (encrypted & unencrypted)
privateKey.toPkcs8Pem()                    // -----BEGIN PRIVATE KEY-----
privateKey.toPkcs8Pem(password = "secret") // -----BEGIN ENCRYPTED PRIVATE KEY-----

// PKCS#1 export (RSA only)
rsaPrivateKey.toPkcs1Pem()                 // -----BEGIN RSA PRIVATE KEY-----

KeyStore

// Parse a Base64-encoded PKCS#12 keystore into PEM components
val bundle = parseKeyStore(base64Data, storePass = "changeit")
// Export with encrypted PKCS#8 key
val encrypted = parseKeyStore(base64Data, "changeit", format = KeyFormat.Pkcs8(keyPass = "secret"))
// Export with PKCS#1 key (RSA only, no encryption)
val pkcs1 = parseKeyStore(base64Data, "changeit", format = KeyFormat.Pkcs1)

// PemBundle fields
bundle.key        // PEM-encoded private key
bundle.cert       // PEM-encoded leaf certificate
bundle.certChain  // PEM-encoded CA certificate chain
bundle.keyPass    // key password

CSR

val csr = Csr.create(
    x500Name = "CN=app.example.com,O=Acme",
    algorithmName = "SHA256withRSA",
    keyPair = keyPair,
    sans = listOf(San.Dns("app.example.com"), San.Ip("10.0.0.1")),
)
println(csr.pem)

CRL

val crl = Crl.build(
    keyPair = caKeyPair,
    issuer = X500Principal("CN=My CA,O=Acme"),
    thisUpdate = Clock.System.now(),
    nextUpdate = Clock.System.now() + 30.days,
    revokedSerials = listOf(42L, 99L),
)

cert.isRevokedBy(crl)          // check revocation
Crl.distributionPoints(cert)   // extract CRL URLs

DER DSL

Type-safe Kotlin DSL for building ASN.1 DER-encoded structures:

val encoded: ByteArray = seq {
    integer(1L)
    boolean(true)
    seq {
        oid("2.5.29.14")
        octetString(byteArrayOf(0x01, 0x02))
    }
    utcTime(Clock.System.now())
    explicitTag(0) { integer(2L) }
}

All standard ASN.1 types are supported: integer, boolean, bitString, octetString, oid, utcTime, nullValue, tag, implicitTag, explicitTag, seq, and set.

TLS

// Scan remote server certificates
val chain = scanCertificates("github.com")
chain.forEach { println("${it.commonName} β€” expires ${it.expiryDateUtc}") }

Supported Types

Category Formats
Private keys PKCS#8, PKCS#8 encrypted, PKCS#1 (RSA, DSA, EC)
Public keys X.509/SPKI, PKCS#1 RSA
Certificates X.509v3 (PEM & DER)
CRLs X.509 CRL (PEM & DER)
Key algorithms RSA, EC (secp256r1, secp384r1, …), DSA
Cert builder EC keys (SHA256withECDSA)

πŸ”§ Build & Test

./amper build                 # Build
./amper test                  # Test
./amper publish mavenLocal    # Publish to local Maven repository

Credits

Thanks to the Airlift project. The crypto and DER/PEM logic is adapted from its security module, rewritten as idiomatic Kotlin without the Guava dependency.

License

Apache 2.0 β€” see LICENSE for details.

About

πŸ” Kotlin/JVM toolkit for X.509 certs, PEM/DER encoding, CSR, CRL and TLS scanning

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors