Coverage Summary for Class: PemString (dev.suresh.cert)

Class Class, % Method, % Branch, % Line, % Instruction, %
PemString 0% (0/1) 0% (0/2) 0% (0/14) 0% (0/106)


 package dev.suresh.cert
 
 import java.security.KeyFactory
 import java.security.cert.CertificateFactory
 import java.security.cert.X509Certificate
 import java.security.spec.InvalidKeySpecException
 import java.security.spec.PKCS8EncodedKeySpec
 import java.util.Locale
 import javax.crypto.Cipher
 import javax.crypto.Cipher.DECRYPT_MODE
 import javax.crypto.EncryptedPrivateKeyInfo
 import javax.crypto.SecretKeyFactory
 import javax.crypto.spec.PBEKeySpec
 import kotlin.io.encoding.Base64
 
 /**
  * PEM X.509 certificates reader & writer
  *
  * @author Suresh
  */
 object PemFormat {
 
   /** Supported X.509 SAN OIDs */
   const val ALT_RFC822_NAME = 1
   const val ALT_DNS_NAME = 2
   const val ALT_IPA_NAME = 7
 
   /** PEM certificate pattern */
   private val CERT_PATTERN =
       """-+BEGIN\s+.*CERTIFICATE[^-]*-+(?:\s|\r|\n)+([a-z0-9+/=\r\n]+)-+END\s+.*CERTIFICATE[^-]*-+"""
           .toRegex(RegexOption.IGNORE_CASE)
 
   /** PEM private key pattern */
   private val KEY_PATTERN =
       """-+BEGIN\s+(?:(.*)\s+)?PRIVATE\s+KEY[^-]*-+(?:\s|\r|\n)+([a-z0-9+/=\r\n]+)-+END\s+.*PRIVATE\s+KEY[^-]*-+"""
           .toRegex(RegexOption.IGNORE_CASE)
 
   val certFactory = CertificateFactory.getInstance("X.509")
 
   /**
    * Checks if the given string is a PEM encoded certificate.
    *
    * @param data cert data
    * @return `true` if it's a `PEM` certificate.
    */
   fun isPem(data: String) = CERT_PATTERN.containsMatchIn(data)
 
   /**
    * Read all X.509 certificates from the given PEM encoded certificate.
    *
    * @param certChain PEM encoded cert(s)
    * @return list of [X509Certificate]
    */
   fun readCertChain(certChain: String) =
       try {
         CERT_PATTERN.findAll(certChain)
             .map {
               val base64Text = it.groupValues[1]
               val buffer = Base64.Mime.decode(base64Text.toByteArray(Charsets.US_ASCII))
               certFactory.generateCertificate(buffer.inputStream()) as X509Certificate
             }
             .toList()
       } catch (e: Exception) {
         throw IllegalStateException("Can't read the PEM certificate, cert data is invalid", e)
       }
 
   /** Read PKCS#8 encoded private key. */
   fun readPrivateKey(privateKey: String, password: String? = null) =
       KEY_PATTERN.findAll(privateKey)
           .map {
             val keyType = it.groupValues[1]
             val base64Key = it.groupValues[2]
             if (base64Key.lowercase(locale = Locale.US).startsWith("proc-type")) {
               throw InvalidKeySpecException(
                   "Password protected PKCS1 private keys are not supported"
               )
             }
 
             val encodedKey = Base64.Mime.decode(base64Key.toByteArray(Charsets.US_ASCII))
             val encKeySpec =
                 when (keyType) {
                   "ENCRYPTED" -> {
                     require(password != null) {
                       "Private key is encrypted, but no password was provided"
                     }
                     val encPrivateKeyInfo = EncryptedPrivateKeyInfo(encodedKey)
                     val keyFactory = SecretKeyFactory.getInstance(encPrivateKeyInfo.algName)
                     val secretKey = keyFactory.generateSecret(PBEKeySpec(password.toCharArray()))
 
                     val cipher = Cipher.getInstance(encPrivateKeyInfo.algName)
                     cipher.init(DECRYPT_MODE, secretKey, encPrivateKeyInfo.algParameters)
                     encPrivateKeyInfo.getKeySpec(cipher)
                   }
                   else -> PKCS8EncodedKeySpec(encodedKey)
                 }
 
             KeyFactory.getInstance("RSA").generatePrivate(encKeySpec)
           }
           .toList()
 
   /** Encodes the given [encoded] bytes to PEM format. */
   fun encodePem(pemString: PemString, encoded: ByteArray) =
       """-----BEGIN ${pemString.beginMarker}-----
       |${Base64.encode(encoded).chunked(64).joinToString("\n")}
       |-----END ${pemString.beginMarker}-----
       |"""
           .trimMargin()
 }
 
 /**
  * PEM string begin markers.
  *
  * [Pem.h](https://github.com/openssl/openssl/blob/master/include/openssl/pem.h#L35)
  */
 enum class PemString(val beginMarker: String) {
   X509("CERTIFICATE"),
   X509_REQ("CERTIFICATE REQUEST"),
   X509_CRL("X509 CRL"),
   PUBLIC("PUBLIC KEY"),
   // Openssl -traditional
   PKCS1("RSA PRIVATE KEY"),
   RSA_PUBLIC("RSA PUBLIC KEY"),
   DSA("DSA PRIVATE KEY"),
   DSA_PUBLIC("DSA PUBLIC KEY"),
   PKCS8("ENCRYPTED PRIVATE KEY"),
   // Unencrypted PKCS#8 private key
   PKCS8INF("PRIVATE KEY"),
   ECDSA_PUBLIC("ECDSA PUBLIC KEY"),
   EC_PARAMETERS("EC PARAMETERS"),
   EC_PRIVATE_KEY("EC PRIVATE KEY"),
 }