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 {
api("com.google.inject:guice:${Versions.guice}")
api("io.netty:netty-buffer:${Versions.netty}")
api("org.bouncycastle:bcpkix-jdk15on:${Versions.bouncyCastle}")
api("org.bouncycastle:bcprov-jdk15on:${Versions.bouncyCastle}")
testImplementation("com.google.jimfs:jimfs:${Versions.jimfs}")

@ -1,8 +1,24 @@
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.nio.file.Files
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) {
fun signJar(jar: Path) {
@ -10,8 +26,8 @@ class Pkcs12KeyStore private constructor(private val path: Path) {
"jarsigner",
"-keystore", path.toString(),
"-storetype", "pkcs12",
"-storepass", STORE_PASSWORD,
"-keypass", KEY_PASSWORD,
"-storepass", PASSWORD,
"-keypass", PASSWORD,
jar.toString(),
ALIAS
)
@ -19,36 +35,68 @@ class Pkcs12KeyStore private constructor(private val path: Path) {
companion object {
private const val ALIAS = "openrs2"
private const val STORE_PASSWORD = ALIAS
private const val KEY_PASSWORD = ALIAS
private const val DNAME = "CN=OpenRS2"
private const val VALIDITY_PERIOD = "3650"
private const val KEY_ALGORITHM = "RSA"
private const val KEY_SIZE = "2048"
private const val SIGNATURE_ALGORITHM = "SHA256with$KEY_ALGORITHM"
private const val PASSWORD = ALIAS
private val PASSWORD_CHARS = PASSWORD.toCharArray()
private val PASSWORD_PARAMETER = KeyStore.PasswordProtection(PASSWORD_CHARS)
private const val SERIAL_LENGTH = 128
// 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 {
if (!Files.exists(path)) {
create(path)
val keyStore = KeyStore.getInstance("PKCS12")
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)
}
private fun create(path: Path) {
exec(
"keytool",
"-genkeypair",
"-keystore", path.toString(),
"-storetype", "pkcs12",
"-storepass", STORE_PASSWORD,
"-keypass", KEY_PASSWORD,
"-alias", ALIAS,
"-dname", DNAME,
"-validity", VALIDITY_PERIOD,
"-keyalg", KEY_ALGORITHM,
"-keysize", KEY_SIZE,
"-sigalg", SIGNATURE_ALGORITHM
)
private fun createPrivateKeyEntry(): KeyStore.PrivateKeyEntry {
val (public, private) = Rsa.generateKeyPair(Rsa.JAR_KEY_LENGTH)
val serial = BigIntegers.createRandomBigInteger(SERIAL_LENGTH, secureRandom)
val start = OffsetDateTime.now(ZoneOffset.UTC).minus(MAX_CLOCK_SKEW)
val end = start.plus(VALIDITY_PERIOD)
val spki = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(public)
val signer = BcRSAContentSignerBuilder(SHA256_WITH_RSA, SHA256).build(private)
val certificate = X509v3CertificateBuilder(
DNAME,
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) {

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

Loading…
Cancel
Save