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/crypto/src/main/kotlin/org/openrs2/crypto/Pkcs12KeyStore.kt

110 lines
4.1 KiB

package org.openrs2.crypto
import jdk.security.jarsigner.JarSigner
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 org.openrs2.util.io.useAtomicOutputStream
import java.io.OutputStream
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
import java.util.jar.JarFile
public class Pkcs12KeyStore private constructor(privateKeyEntry: KeyStore.PrivateKeyEntry, signerName: String) {
private val signer = JarSigner.Builder(privateKeyEntry)
.signatureAlgorithm("SHA256withRSA")
.digestAlgorithm("SHA-256")
.signerName(signerName)
.build()
public fun signJar(input: Path, output: OutputStream) {
JarFile(input.toFile()).use { file ->
signer.sign(file, output)
}
}
public 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
private val MAX_CLOCK_SKEW = Period.ofDays(1)
private val VALIDITY_PERIOD = Period.ofYears(10)
private val SHA256_WITH_RSA = AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption)
private val SHA256 = AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256)
@JvmStatic
public fun open(path: Path, signerName: String): Pkcs12KeyStore {
val keyStore = KeyStore.getInstance("PKCS12")
if (Files.exists(path)) {
Files.newInputStream(path).use { input ->
keyStore.load(input, PASSWORD_CHARS)
}
} else {
keyStore.load(null)
}
val privateKeyEntry = if (keyStore.containsAlias(ALIAS)) {
keyStore.getEntry(ALIAS, PASSWORD_PARAMETER) as KeyStore.PrivateKeyEntry
} else {
val entry = createPrivateKeyEntry(signerName)
keyStore.setEntry(ALIAS, entry, PASSWORD_PARAMETER)
path.useAtomicOutputStream { output ->
keyStore.store(output, PASSWORD_CHARS)
}
entry
}
return Pkcs12KeyStore(privateKeyEntry, signerName)
}
private fun createPrivateKeyEntry(signerName: String): KeyStore.PrivateKeyEntry {
val (public, private) = Rsa.generateKeyPair(Rsa.JAR_KEY_LENGTH)
val dname = X500NameBuilder()
.addRDN(BCStyle.CN, signerName)
.build()
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))
}
}
}