|
|
@ -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) { |
|
|
|