Replace shelling out to keytool with Bouncy Castle and the security API

Signed-off-by: Graham <gpe@openrs2.dev>
pull/93/head
Graham 4 years ago
parent cf132cb2dd
commit efa9c90f57
  1. 1
      common/build.gradle.kts
  2. 100
      common/src/main/java/dev/openrs2/common/crypto/Pkcs12KeyStore.kt
  3. 8
      common/src/main/java/dev/openrs2/common/crypto/Rsa.kt

@ -6,6 +6,7 @@ plugins {
dependencies { dependencies {
api("com.google.inject:guice:${Versions.guice}") api("com.google.inject:guice:${Versions.guice}")
api("io.netty:netty-buffer:${Versions.netty}") api("io.netty:netty-buffer:${Versions.netty}")
api("org.bouncycastle:bcpkix-jdk15on:${Versions.bouncyCastle}")
api("org.bouncycastle:bcprov-jdk15on:${Versions.bouncyCastle}") api("org.bouncycastle:bcprov-jdk15on:${Versions.bouncyCastle}")
testImplementation("com.google.jimfs:jimfs:${Versions.jimfs}") testImplementation("com.google.jimfs:jimfs:${Versions.jimfs}")

@ -1,8 +1,24 @@
package dev.openrs2.common.crypto package dev.openrs2.common.crypto
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.asn1.x500.X500NameBuilder
import org.bouncycastle.asn1.x500.style.BCStyle
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder
import org.bouncycastle.util.BigIntegers
import java.io.IOException import java.io.IOException
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyFactory
import java.security.KeyStore
import java.time.OffsetDateTime
import java.time.Period
import java.time.ZoneOffset
import java.util.Date
class Pkcs12KeyStore private constructor(private val path: Path) { class Pkcs12KeyStore private constructor(private val path: Path) {
fun signJar(jar: Path) { fun signJar(jar: Path) {
@ -10,8 +26,8 @@ class Pkcs12KeyStore private constructor(private val path: Path) {
"jarsigner", "jarsigner",
"-keystore", path.toString(), "-keystore", path.toString(),
"-storetype", "pkcs12", "-storetype", "pkcs12",
"-storepass", STORE_PASSWORD, "-storepass", PASSWORD,
"-keypass", KEY_PASSWORD, "-keypass", PASSWORD,
jar.toString(), jar.toString(),
ALIAS ALIAS
) )
@ -19,36 +35,68 @@ class Pkcs12KeyStore private constructor(private val path: Path) {
companion object { companion object {
private const val ALIAS = "openrs2" private const val ALIAS = "openrs2"
private const val STORE_PASSWORD = ALIAS
private const val KEY_PASSWORD = ALIAS private const val PASSWORD = ALIAS
private const val DNAME = "CN=OpenRS2" private val PASSWORD_CHARS = PASSWORD.toCharArray()
private const val VALIDITY_PERIOD = "3650" private val PASSWORD_PARAMETER = KeyStore.PasswordProtection(PASSWORD_CHARS)
private const val KEY_ALGORITHM = "RSA"
private const val KEY_SIZE = "2048" private const val SERIAL_LENGTH = 128
private const val SIGNATURE_ALGORITHM = "SHA256with$KEY_ALGORITHM"
// TODO(gpe): add support for overriding this
private val DNAME = X500NameBuilder()
.addRDN(BCStyle.CN, "OpenRS2")
.build()
private val MAX_CLOCK_SKEW = Period.ofDays(1)
private val VALIDITY_PERIOD = Period.ofYears(1)
private val SHA256_WITH_RSA = AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption)
private val SHA256 = AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256)
fun open(path: Path): Pkcs12KeyStore { fun open(path: Path): Pkcs12KeyStore {
if (!Files.exists(path)) { val keyStore = KeyStore.getInstance("PKCS12")
create(path) if (Files.exists(path)) {
Files.newInputStream(path).use { input ->
keyStore.load(input, PASSWORD_CHARS)
}
} else {
keyStore.load(null)
}
if (!keyStore.containsAlias(ALIAS)) {
keyStore.setEntry(ALIAS, createPrivateKeyEntry(), PASSWORD_PARAMETER)
Files.newOutputStream(path).use { output ->
keyStore.store(output, PASSWORD_CHARS)
}
} }
return Pkcs12KeyStore(path) return Pkcs12KeyStore(path)
} }
private fun create(path: Path) { private fun createPrivateKeyEntry(): KeyStore.PrivateKeyEntry {
exec( val (public, private) = Rsa.generateKeyPair(Rsa.JAR_KEY_LENGTH)
"keytool",
"-genkeypair", val serial = BigIntegers.createRandomBigInteger(SERIAL_LENGTH, secureRandom)
"-keystore", path.toString(),
"-storetype", "pkcs12", val start = OffsetDateTime.now(ZoneOffset.UTC).minus(MAX_CLOCK_SKEW)
"-storepass", STORE_PASSWORD, val end = start.plus(VALIDITY_PERIOD)
"-keypass", KEY_PASSWORD,
"-alias", ALIAS, val spki = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(public)
"-dname", DNAME, val signer = BcRSAContentSignerBuilder(SHA256_WITH_RSA, SHA256).build(private)
"-validity", VALIDITY_PERIOD,
"-keyalg", KEY_ALGORITHM, val certificate = X509v3CertificateBuilder(
"-keysize", KEY_SIZE, DNAME,
"-sigalg", SIGNATURE_ALGORITHM serial,
) Date.from(start.toInstant()),
Date.from(end.toInstant()),
DNAME,
spki
).build(signer)
val jcaPrivate = KeyFactory.getInstance("RSA").generatePrivate(private.toKeySpec())
val jcaCertificate = JcaX509CertificateConverter().getCertificate(certificate)
return KeyStore.PrivateKeyEntry(jcaPrivate, arrayOf(jcaCertificate))
} }
private fun exec(command: String, vararg args: String) { private fun exec(command: String, vararg args: String) {

@ -24,6 +24,8 @@ import java.io.IOException
import java.math.BigInteger import java.math.BigInteger
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.security.spec.KeySpec
import java.security.spec.RSAPrivateCrtKeySpec
val RSAPrivateCrtKeyParameters.publicKey val RSAPrivateCrtKeyParameters.publicKey
get() = RSAKeyParameters(false, modulus, publicExponent) get() = RSAKeyParameters(false, modulus, publicExponent)
@ -45,6 +47,10 @@ fun ByteBuf.rsaDecrypt(key: RSAKeyParameters): ByteBuf {
return Rsa.decrypt(toBigInteger(), key).toByteBuf() return Rsa.decrypt(toBigInteger(), key).toByteBuf()
} }
fun RSAPrivateCrtKeyParameters.toKeySpec(): KeySpec {
return RSAPrivateCrtKeySpec(modulus, publicExponent, exponent, p, q, dp, dq, qInv)
}
object Rsa { object Rsa {
private const val PUBLIC_KEY = "PUBLIC KEY" private const val PUBLIC_KEY = "PUBLIC KEY"
private const val PRIVATE_KEY = "PRIVATE KEY" private const val PRIVATE_KEY = "PRIVATE KEY"
@ -61,6 +67,8 @@ object Rsa {
*/ */
const val CLIENT_KEY_LENGTH = 1008 const val CLIENT_KEY_LENGTH = 1008
const val JAR_KEY_LENGTH = 2048
// 1 in 2^80 // 1 in 2^80
private const val CERTAINTY = 80 private const val CERTAINTY = 80

Loading…
Cancel
Save