Open-source multiplayer game server compatible with the RuneScape client https://www.openrs2.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
openrs2/common/src/main/java/dev/openrs2/common/crypto/Pkcs12KeyStore.kt

112 lines
4.0 KiB

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) {
exec(
"jarsigner",
"-keystore", path.toString(),
"-storetype", "pkcs12",
"-storepass", PASSWORD,
"-keypass", PASSWORD,
jar.toString(),
ALIAS
)
}
companion object {
private const val ALIAS = "openrs2"
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 {
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 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) {
val commandWithArgs = listOf(command, *args)
val status = ProcessBuilder(commandWithArgs)
.start()
.waitFor()
if (status != 0) {
throw IOException("$command returned non-zero status code $status")
}
}
}
}