From e3477da7373342a6356f5ce1b4661e6c10b1c27a Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 29 Jun 2024 03:44:55 +0200 Subject: [PATCH] Send signatures and verify signature of patches file before loading it --- build.gradle.kts | 5 +- configuration.example.toml | 6 +- configuration.toml | 6 +- gradle/libs.versions.toml | 8 +- .../revanced/api/command/StartAPICommand.kt | 1 + .../api/configuration/Dependencies.kt | 2 + .../app/revanced/api/configuration/Logging.kt | 16 +++ .../repository/BackendRepository.kt | 8 +- .../repository/ConfigurationRepository.kt | 70 +++++++++++- .../repository/GitHubBackendRepository.kt | 6 +- .../api/configuration/routes/PatchesRoute.kt | 30 +++++ .../api/configuration/schema/APISchema.kt | 35 +++--- .../configuration/services/PatchesService.kt | 104 ++++++++++++------ .../services/SignatureService.kt | 72 ++++++++++++ src/main/resources/logback.xml | 3 +- 15 files changed, 298 insertions(+), 74 deletions(-) create mode 100644 src/main/kotlin/app/revanced/api/configuration/Logging.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7a421557..de068fe8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,9 +65,10 @@ dependencies { implementation(libs.ktor.server.rate.limit) implementation(libs.ktor.server.host.common) implementation(libs.ktor.server.jetty) + implementation(libs.ktor.server.call.logging) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.koin.ktor) - implementation("io.bkbn:kompendium-core:latest.release") + implementation(libs.kompendium.core) implementation(libs.h2) implementation(libs.logback.classic) implementation(libs.exposed.core) @@ -82,6 +83,8 @@ dependencies { implementation(libs.revanced.patcher) implementation(libs.revanced.library) implementation(libs.caffeine) + implementation(libs.bouncy.castle.provider) + implementation(libs.bouncy.castle.pgp) } // The maven-publish plugin is necessary to make signing work. diff --git a/configuration.example.toml b/configuration.example.toml index e969f57d..e4881aad 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -1,8 +1,6 @@ organization = "revanced" -patches-repository = "revanced-patches" -integrations-repositories = [ - "revanced-integrations" -] +patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "patches.asc" } +integrations = { repository = "revanced-integrations", asset-regex = "apk$", signature-asset-regex = "asc$", public-key-file = "integrations.asc" } contributors-repositories = [ "revanced-patcher", "revanced-patches", diff --git a/configuration.toml b/configuration.toml index 044b6e4f..28155952 100644 --- a/configuration.toml +++ b/configuration.toml @@ -1,8 +1,6 @@ organization = "revanced" -patches-repository = "revanced-patches" -integrations-repositories = [ - "revanced-integrations" -] +patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "key.asc" } +integrations = { repository = "revanced-integrations", asset-regex = "apk$", signature-asset-regex = "asc$", public-key-file = "key.asc" } contributors-repositories = [ "revanced-patcher", "revanced-patches", diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3d38a951..cf646231 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,5 @@ [versions] +kompendium-core = "latest.release" kotlin = "2.0.0" logback = "1.4.14" exposed = "0.41.1" @@ -7,13 +8,15 @@ koin = "3.5.3" dotenv = "6.4.1" ktor = "2.3.7" ktoml = "0.5.1" -picocli = "4.7.5" +picocli = "4.7.6" datetime = "0.5.0" revanced-patcher = "19.3.1" revanced-library = "2.3.0" caffeine = "3.1.8" +bouncy-castle = "1.78.1" [libraries] +kompendium-core = { module = "io.bkbn:kompendium-core", version.ref = "kompendium-core" } ktor-client-core = { module = "io.ktor:ktor-client-core" } ktor-client-cio = { module = "io.ktor:ktor-client-cio" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp" } @@ -29,6 +32,7 @@ ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers" } ktor-server-rate-limit = { module = "io.ktor:ktor-server-rate-limit" } ktor-server-host-common = { module = "io.ktor:ktor-server-host-common" } ktor-server-jetty = { module = "io.ktor:ktor-server-jetty" } +ktor-server-call-logging = { module = "io.ktor:ktor-server-call-logging" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" } koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" } h2 = { module = "com.h2database:h2", version.ref = "h2" } @@ -45,6 +49,8 @@ kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version. revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" } caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } +bouncy-castle-provider = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncy-castle" } +bouncy-castle-pgp = { module = "org.bouncycastle:bcpg-jdk18on", version.ref = "bouncy-castle" } [plugins] serilization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index a220addb..66faa070 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -39,6 +39,7 @@ internal object StartAPICommand : Runnable { configureSerialization() configureSecurity() configureOpenAPI() + configureLogging() configureRouting() }.start(wait = true) } diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 0fbed12d..24ebb542 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -4,6 +4,7 @@ import app.revanced.api.configuration.repository.AnnouncementRepository import app.revanced.api.configuration.repository.BackendRepository import app.revanced.api.configuration.repository.ConfigurationRepository import app.revanced.api.configuration.repository.GitHubBackendRepository +import app.revanced.api.configuration.services.* import app.revanced.api.configuration.services.AnnouncementService import app.revanced.api.configuration.services.ApiService import app.revanced.api.configuration.services.AuthService @@ -130,6 +131,7 @@ fun Application.configureDependencies( ) } singleOf(::AnnouncementService) + singleOf(::SignatureService) singleOf(::PatchesService) singleOf(::ApiService) } diff --git a/src/main/kotlin/app/revanced/api/configuration/Logging.kt b/src/main/kotlin/app/revanced/api/configuration/Logging.kt new file mode 100644 index 00000000..48b8eb7d --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/Logging.kt @@ -0,0 +1,16 @@ +package app.revanced.api.configuration + +import io.ktor.server.application.* +import io.ktor.server.plugins.callloging.* +import io.ktor.server.request.* + +internal fun Application.configureLogging() { + install(CallLogging) { + format { call -> + val status = call.response.status() + val httpMethod = call.request.httpMethod.value + val uri = call.request.uri + "$status $httpMethod $uri" + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt index b48ee81a..1ed0469a 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt @@ -4,7 +4,7 @@ import io.ktor.client.* import kotlinx.datetime.LocalDateTime /** - * The backend of the application used to get data for the API. + * The backend of the API used to get data. * * @param client The HTTP client to use for requests. */ @@ -97,12 +97,18 @@ abstract class BackendRepository internal constructor( val createdAt: LocalDateTime, val assets: Set, ) { + companion object { + fun Set.first(assetRegex: Regex) = first { assetRegex.containsMatchIn(it.name) } + } + /** * An asset of a release. * + * @property name The name of the asset. * @property downloadUrl The URL to download the asset. */ class BackendAsset( + val name: String, val downloadUrl: String, ) } diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index a7a37023..eaf70be6 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -1,18 +1,78 @@ package app.revanced.api.configuration.repository +import app.revanced.api.configuration.services.PatchesService +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.io.File +/** + * The repository storing the configuration for the API. + * + * @property organization The API backends organization name where the repositories for the patches and integrations are. + * @property patches The source of the patches. + * @property integrations The source of the integrations. + * @property contributorsRepositoryNames The names of the repositories to get contributors from. + * @property apiVersion The version to use for the API. + * @property host The host of the API to configure CORS. + */ @Serializable internal class ConfigurationRepository( val organization: String, - @SerialName("patches-repository") - val patchesRepository: String, - @SerialName("integrations-repositories") - val integrationsRepositoryNames: Set, + val patches: AssetConfiguration, + val integrations: AssetConfiguration, @SerialName("contributors-repositories") val contributorsRepositoryNames: Set, @SerialName("api-version") val apiVersion: Int = 1, val host: String, -) +) { + /** + * An asset configuration. + * + * [PatchesService] uses [BackendRepository] to get assets from its releases. + * A release contains multiple assets. + * + * This configuration is used in [ConfigurationRepository] + * to determine which release assets from repositories to get and to verify them. + * + * @property repository The repository in which releases are made to get an asset. + * @property assetRegex The regex matching the asset name. + * @property signatureAssetRegex The regex matching the signature asset name to verify the asset. + * @property publicKeyFile The public key file to verify the signature of the asset. + */ + @Serializable + internal class AssetConfiguration( + val repository: String, + @Serializable(with = RegexSerializer::class) + @SerialName("asset-regex") + val assetRegex: Regex, + @Serializable(with = RegexSerializer::class) + @SerialName("signature-asset-regex") + val signatureAssetRegex: Regex, + @Serializable(with = FileSerializer::class) + @SerialName("public-key-file") + val publicKeyFile: File, + ) +} + +private object RegexSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Regex", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Regex) = encoder.encodeString(value.pattern) + + override fun deserialize(decoder: Decoder) = Regex(decoder.decodeString()) +} + +private object FileSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("File", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: File) = encoder.encodeString(value.path) + + override fun deserialize(decoder: Decoder) = File(decoder.decodeString()) +} diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt index 8084fb0d..2664afea 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt @@ -35,7 +35,10 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { releaseNote = release.body, createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC), assets = release.assets.map { - BackendAsset(downloadUrl = it.browserDownloadUrl) + BackendAsset( + name = it.name, + downloadUrl = it.browserDownloadUrl, + ) }.toSet(), ) } @@ -156,6 +159,7 @@ class GitHubOrganization { ) { @Serializable class GitHubAsset( + val name: String, val browserDownloadUrl: String, ) } diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt index ab11b324..8419e6a5 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt @@ -1,6 +1,8 @@ package app.revanced.api.configuration.routes +import app.revanced.api.configuration.installCache import app.revanced.api.configuration.installNotarizedRoute +import app.revanced.api.configuration.schema.APIAssetPublicKeys import app.revanced.api.configuration.schema.APIRelease import app.revanced.api.configuration.schema.APIReleaseVersion import app.revanced.api.configuration.services.PatchesService @@ -10,6 +12,7 @@ import io.ktor.server.application.* import io.ktor.server.plugins.ratelimit.* import io.ktor.server.response.* import io.ktor.server.routing.* +import kotlin.time.Duration.Companion.days import org.koin.ktor.ext.get as koinGet internal fun Route.patchesRoute() = route("patches") { @@ -42,6 +45,18 @@ internal fun Route.patchesRoute() = route("patches") { } } } + + rateLimit(RateLimitName("strong")) { + route("keys") { + installCache(356.days) + + installPatchesPublicKeyRouteDocumentation() + + get { + call.respond(patchesService.publicKeys()) + } + } + } } fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRoute { @@ -88,3 +103,18 @@ fun Route.installLatestPatchesListRouteDocumentation() = installNotarizedRoute { } } } + +fun Route.installPatchesPublicKeyRouteDocumentation() = installNotarizedRoute { + tags = setOf("Patches") + + get = GetInfo.builder { + description("Get the public keys for verifying patches and integrations assets") + summary("Get patches and integrations public keys") + response { + description("The public keys") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index 1dad95fa..1230ce12 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -1,14 +1,13 @@ package app.revanced.api.configuration.schema import kotlinx.datetime.LocalDateTime -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable class APIRelease( val version: String, val createdAt: LocalDateTime, - val changelog: String, + val description: String, val assets: Set, ) @@ -49,23 +48,15 @@ class APIContributable( @Serializable class APIAsset( val downloadUrl: String, -) { - val type = when { - downloadUrl.endsWith(".jar") -> Type.PATCHES - downloadUrl.endsWith(".apk") -> Type.INTEGRATIONS - else -> Type.UNKNOWN - } - - enum class Type { - @SerialName("patches") - PATCHES, - - @SerialName("integrations") - INTEGRATIONS, - - @SerialName("unknown") - UNKNOWN, - } + val signatureDownloadUrl: String, + // TODO: Remove this eventually when integrations are merged into patches. + val type: APIAssetType, +) + +@Serializable +enum class APIAssetType { + PATCHES, + INTEGRATION, } @Serializable @@ -113,3 +104,9 @@ class APIRateLimit( val remaining: Int, val reset: LocalDateTime, ) + +@Serializable +class APIAssetPublicKeys( + val patchesPublicKey: String, + val integrationsPublicKey: String, +) diff --git a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt index 591e4fe7..abc107de 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt @@ -1,87 +1,119 @@ package app.revanced.api.configuration.services import app.revanced.api.configuration.repository.BackendRepository +import app.revanced.api.configuration.repository.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.Companion.first import app.revanced.api.configuration.repository.ConfigurationRepository -import app.revanced.api.configuration.schema.APIAsset -import app.revanced.api.configuration.schema.APIRelease -import app.revanced.api.configuration.schema.APIReleaseVersion +import app.revanced.api.configuration.schema.* import app.revanced.library.PatchUtils import app.revanced.patcher.PatchBundleLoader import com.github.benmanes.caffeine.cache.Caffeine -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.withContext +import io.ktor.util.* import java.io.ByteArrayOutputStream import java.net.URL internal class PatchesService( + private val signatureService: SignatureService, private val backendRepository: BackendRepository, private val configurationRepository: ConfigurationRepository, ) { - private val patchesListCache = Caffeine - .newBuilder() - .maximumSize(1) - .build() - suspend fun latestRelease(): APIRelease { val patchesRelease = backendRepository.release( configurationRepository.organization, - configurationRepository.patchesRepository, + configurationRepository.patches.repository, ) - val integrationsReleases = withContext(Dispatchers.Default) { - configurationRepository.integrationsRepositoryNames.map { - async { backendRepository.release(configurationRepository.organization, it) } - } - }.awaitAll() - val assets = (patchesRelease.assets + integrationsReleases.flatMap { it.assets }) - .map { APIAsset(it.downloadUrl) } - .filter { it.type != APIAsset.Type.UNKNOWN } - .toSet() + val integrationsRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.integrations.repository, + ) + + fun ConfigurationRepository.AssetConfiguration.asset( + release: BackendRepository.BackendOrganization.BackendRepository.BackendRelease, + assetType: APIAssetType, + ) = APIAsset( + release.assets.first(assetRegex).downloadUrl, + release.assets.first(signatureAssetRegex).downloadUrl, + assetType, + ) + + val patchesAsset = configurationRepository.patches.asset( + patchesRelease, + APIAssetType.PATCHES, + ) + val integrationsAsset = configurationRepository.integrations.asset( + integrationsRelease, + APIAssetType.INTEGRATION, + ) return APIRelease( patchesRelease.tag, patchesRelease.createdAt, patchesRelease.releaseNote, - assets, + setOf(patchesAsset, integrationsAsset), ) } suspend fun latestVersion(): APIReleaseVersion { val patchesRelease = backendRepository.release( configurationRepository.organization, - configurationRepository.patchesRepository, + configurationRepository.patches.repository, ) return APIReleaseVersion(patchesRelease.tag) } + private val patchesListCache = Caffeine + .newBuilder() + .maximumSize(1) + .build() + suspend fun list(): ByteArray { val patchesRelease = backendRepository.release( configurationRepository.organization, - configurationRepository.patchesRepository, + configurationRepository.patches.repository, ) - return patchesListCache.getIfPresent(patchesRelease.tag) ?: run { - val downloadUrl = patchesRelease.assets - .map { APIAsset(it.downloadUrl) } - .find { it.type == APIAsset.Type.PATCHES } - ?.downloadUrl + return patchesListCache.get(patchesRelease.tag) { + val patchesDownloadUrl = patchesRelease.assets + .first(configurationRepository.patches.assetRegex).downloadUrl + + val signatureDownloadUrl = patchesRelease.assets + .first(configurationRepository.patches.signatureAssetRegex).downloadUrl - val patches = kotlin.io.path.createTempFile().toFile().apply { - outputStream().use { URL(downloadUrl).openStream().copyTo(it) } - }.let { file -> - PatchBundleLoader.Jar(file).also { file.delete() } + val patchesFile = kotlin.io.path.createTempFile().toFile().apply { + outputStream().use { URL(patchesDownloadUrl).openStream().copyTo(it) } } + val patches = if ( + signatureService.verify( + patchesFile, + signatureDownloadUrl, + configurationRepository.patches.publicKeyFile, + ) + ) { + PatchBundleLoader.Jar(patchesFile) + } else { + // Use an empty set of patches if the signature is invalid. + emptySet() + } + + patchesFile.delete() + ByteArrayOutputStream().use { stream -> PatchUtils.Json.serialize(patches, outputStream = stream) stream.toByteArray() - }.also { - patchesListCache.put(patchesRelease.tag, it) } } } + + fun publicKeys(): APIAssetPublicKeys { + fun publicKeyBase64(getAssetConfiguration: ConfigurationRepository.() -> ConfigurationRepository.AssetConfiguration) = + configurationRepository.getAssetConfiguration().publicKeyFile.readBytes().encodeBase64() + + return APIAssetPublicKeys( + publicKeyBase64 { patches }, + publicKeyBase64 { integrations }, + ) + } } diff --git a/src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt b/src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt new file mode 100644 index 00000000..89ea40e7 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt @@ -0,0 +1,72 @@ +package app.revanced.api.configuration.services + +import com.github.benmanes.caffeine.cache.Caffeine +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator +import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider +import java.io.File +import java.io.InputStream +import java.net.URL +import java.security.MessageDigest +import java.security.Security + +internal class SignatureService { + private val signatureCache = Caffeine + .newBuilder() + .maximumSize(2) // Assuming this is enough for patches and integrations. + .build() // Hash -> Verified. + + fun verify( + file: File, + signatureDownloadUrl: String, + publicKeyFile: File, + ): Boolean { + val fileBytes = file.readBytes() + + return signatureCache.get(MessageDigest.getInstance("SHA-256").digest(fileBytes)) { + verify( + fileBytes = fileBytes, + signatureInputStream = URL(signatureDownloadUrl).openStream(), + publicKeyInputStream = publicKeyFile.inputStream(), + ) + } + } + + private fun verify( + fileBytes: ByteArray, + signatureInputStream: InputStream, + publicKeyInputStream: InputStream, + ) = getSignature(signatureInputStream).apply { + init(BcPGPContentVerifierBuilderProvider(), getPublicKey(publicKeyInputStream)) + update(fileBytes) + }.verify() + + private fun getPublicKey(publicKeyInputStream: InputStream): PGPPublicKey { + val decoderStream = PGPUtil.getDecoderStream(publicKeyInputStream) + + PGPPublicKeyRingCollection(decoderStream, BcKeyFingerprintCalculator()).forEach { keyRing -> + keyRing.publicKeys.forEach { publicKey -> + if (publicKey.isEncryptionKey) { + return publicKey + } + } + } + + throw IllegalArgumentException("Can't find encryption key in key ring.") + } + + private fun getSignature(inputStream: InputStream): PGPSignature { + val decoderStream = PGPUtil.getDecoderStream(inputStream) + val pgpObjectFactory = PGPObjectFactory(decoderStream, BcKeyFingerprintCalculator()) + val signatureList = pgpObjectFactory.nextObject() as PGPSignatureList + + return signatureList.first() + } + + private companion object { + init { + Security.addProvider(BouncyCastleProvider()) + } + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index de52ac6e..6f0c78a3 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -4,8 +4,7 @@ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + -