Add crypto-hwsecurity library

This commit is contained in:
Tad Fisher 2022-10-09 15:10:10 -07:00
parent a244a0f3b8
commit 4b7457c7f7
No known key found for this signature in database
GPG key ID: 3A7425F7E7B22251
13 changed files with 517 additions and 4 deletions

View file

@ -0,0 +1,12 @@
package app.passwordstore.crypto
import app.passwordstore.crypto.errors.DeviceHandlerException
import com.github.michaelbull.result.Result
public interface DeviceHandler<Key, EncryptedSessionKey, DecryptedSessionKey> {
public suspend fun pairWithPublicKey(publicKey: Key): Result<Key, DeviceHandlerException>
public suspend fun decryptSessionKey(
encryptedSessionKey: EncryptedSessionKey
): Result<DecryptedSessionKey, DeviceHandlerException>
}

View file

@ -6,7 +6,7 @@ public sealed class CryptoException(message: String? = null, cause: Throwable? =
Exception(message, cause)
/** Sealed exception types for [KeyManager]. */
public sealed class KeyManagerException(message: String? = null) : CryptoException(message)
public sealed class KeyManagerException(message: String? = null, cause: Throwable? = null) : CryptoException(message, cause)
/** Store contains no keys. */
public object NoKeysAvailableException : KeyManagerException("No keys were found")
@ -19,8 +19,8 @@ public object KeyDirectoryUnavailableException :
public object KeyDeletionFailedException : KeyManagerException("Couldn't delete the key file")
/** Failed to parse the key as a known type. */
public object InvalidKeyException :
KeyManagerException("Given key cannot be parsed as a known key type")
public class InvalidKeyException(cause: Throwable? = null) :
KeyManagerException("Given key cannot be parsed as a known key type", cause)
/** No key matching `keyId` could be found. */
public class KeyNotFoundException(keyId: String) :
@ -30,6 +30,9 @@ public class KeyNotFoundException(keyId: String) :
public class KeyAlreadyExistsException(keyId: String) :
KeyManagerException("Pre-existing key was found for $keyId")
public class NoSecretKeyException(keyId: String) :
KeyManagerException("No secret keys found for $keyId")
/** Sealed exception types for [app.passwordstore.crypto.CryptoHandler]. */
public sealed class CryptoHandlerException(message: String? = null, cause: Throwable? = null) :
CryptoException(message, cause)
@ -42,3 +45,33 @@ public class NoKeysProvided(message: String?) : CryptoHandlerException(message,
/** An unexpected error that cannot be mapped to a known type. */
public class UnknownError(cause: Throwable) : CryptoHandlerException(null, cause)
public class KeySpecific(public val key: Any, cause: Throwable?) : CryptoHandlerException(key.toString(), cause)
/** Wrapper containing possibly multiple child exceptions via [suppressedExceptions]. */
public class MultipleKeySpecific(
message: String?,
public val errors: List<KeySpecific>
) : CryptoHandlerException(message) {
init {
for (error in errors) {
addSuppressed(error)
}
}
}
/** Sealed exception types for [app.passwordstore.crypto.DeviceHandler]. */
public sealed class DeviceHandlerException(message: String? = null, cause: Throwable? = null) :
CryptoHandlerException(message, cause)
/** The device crypto operation was canceled by the user. */
public class DeviceOperationCanceled(message: String) : DeviceHandlerException(message, null)
/** The device crypto operation failed. */
public class DeviceOperationFailed(message: String?, cause: Throwable? = null) : DeviceHandlerException(message, cause)
/** The device's key fingerprint doesn't match the fingerprint we are trying to pair it to. */
public class DeviceFingerprintMismatch(
public val publicFingerprint: String,
public val deviceFingerprint: String,
) : DeviceHandlerException()

View file

@ -0,0 +1,64 @@
public final class app/passwordstore/crypto/DeviceIdentifier {
public static final synthetic fun box-impl ([B)Lapp/passwordstore/crypto/DeviceIdentifier;
public static fun constructor-impl ([B)[B
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl ([BLjava/lang/Object;)Z
public static final fun equals-impl0 ([B[B)Z
public static final fun getManufacturer-impl ([B)I
public static final fun getOpenPgpVersion-impl ([B)Ljava/lang/String;
public static final fun getSerialNumber-impl ([B)[B
public fun hashCode ()I
public static fun hashCode-impl ([B)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl ([B)Ljava/lang/String;
public final synthetic fun unbox-impl ()[B
}
public final class app/passwordstore/crypto/DeviceIdentifierKt {
public static final fun getManufacturerName-0zlKB64 ([B)Ljava/lang/String;
}
public final class app/passwordstore/crypto/DeviceKeyInfo {
public fun <init> (Lorg/pgpainless/algorithm/PublicKeyAlgorithm;Lorg/pgpainless/key/OpenPgpFingerprint;)V
public final fun component1 ()Lorg/pgpainless/algorithm/PublicKeyAlgorithm;
public final fun component2 ()Lorg/pgpainless/key/OpenPgpFingerprint;
public final fun copy (Lorg/pgpainless/algorithm/PublicKeyAlgorithm;Lorg/pgpainless/key/OpenPgpFingerprint;)Lapp/passwordstore/crypto/DeviceKeyInfo;
public static synthetic fun copy$default (Lapp/passwordstore/crypto/DeviceKeyInfo;Lorg/pgpainless/algorithm/PublicKeyAlgorithm;Lorg/pgpainless/key/OpenPgpFingerprint;ILjava/lang/Object;)Lapp/passwordstore/crypto/DeviceKeyInfo;
public fun equals (Ljava/lang/Object;)Z
public final fun getAlgorithm ()Lorg/pgpainless/algorithm/PublicKeyAlgorithm;
public final fun getFingerprint ()Lorg/pgpainless/key/OpenPgpFingerprint;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class app/passwordstore/crypto/HWSecurityDevice {
public synthetic fun <init> ([BLjava/lang/String;Lapp/passwordstore/crypto/DeviceKeyInfo;Lapp/passwordstore/crypto/DeviceKeyInfo;Lapp/passwordstore/crypto/DeviceKeyInfo;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAuthKeyInfo ()Lapp/passwordstore/crypto/DeviceKeyInfo;
public final fun getEncryptKeyInfo ()Lapp/passwordstore/crypto/DeviceKeyInfo;
public final fun getId-z5xZLwU ()[B
public final fun getName ()Ljava/lang/String;
public final fun getSignKeyInfo ()Lapp/passwordstore/crypto/DeviceKeyInfo;
}
public final class app/passwordstore/crypto/HWSecurityDeviceHandler : app/passwordstore/crypto/DeviceHandler {
public fun <init> (Lapp/passwordstore/crypto/HWSecurityManager;Landroidx/fragment/app/FragmentManager;)V
public fun decryptSessionKey (Lapp/passwordstore/crypto/PGPEncryptedSessionKey;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public synthetic fun decryptSessionKey (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public synthetic fun pairWithPublicKey (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun pairWithPublicKey-P2gA-3I ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/passwordstore/crypto/HWSecurityException : org/pgpainless/decryption_verification/HardwareSecurity$HardwareSecurityException {
public fun <init> (Ljava/lang/String;)V
public fun getMessage ()Ljava/lang/String;
}
public final class app/passwordstore/crypto/HWSecurityManager {
public fun <init> (Landroid/app/Application;)V
public final fun decryptSessionKey (Landroidx/fragment/app/FragmentManager;Lapp/passwordstore/crypto/PGPEncryptedSessionKey;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun init (Z)V
public static synthetic fun init$default (Lapp/passwordstore/crypto/HWSecurityManager;ZILjava/lang/Object;)V
public final fun isHardwareAvailable ()Z
public final fun readDevice (Landroidx/fragment/app/FragmentManager;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

View file

@ -0,0 +1,27 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
plugins {
id("com.github.android-password-store.android-library")
id("com.github.android-password-store.kotlin-android")
id("com.github.android-password-store.kotlin-library")
}
android {
namespace = "app.passwordstore.crypto.hwsecurity"
}
dependencies {
implementation(projects.cryptoPgpainless)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.annotation)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.material)
implementation(libs.aps.hwsecurity.openpgp)
implementation(libs.aps.hwsecurity.ui)
implementation(libs.dagger.hilt.android)
implementation(libs.kotlin.coroutines.android)
implementation(libs.thirdparty.kotlinResult)
}

View file

@ -0,0 +1 @@
<manifest />

View file

@ -0,0 +1,52 @@
@file:Suppress("MagicNumber")
package app.passwordstore.crypto
@JvmInline
public value class DeviceIdentifier(
private val aid: ByteArray
) {
init {
require(aid.size == 16) { "Invalid device application identifier" }
}
public val openPgpVersion: String get() = "${aid[6]}.${aid[7]}"
public val manufacturer: Int
get() = ((aid[8].toInt() and 0xff) shl 8) or (aid[9].toInt() and 0xff)
public val serialNumber: ByteArray get() = aid.sliceArray(10..13)
}
// https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=scd/app-openpgp.c;hb=HEAD#l292
public val DeviceIdentifier.manufacturerName: String get() = when (manufacturer) {
0x0001 -> "PPC Card Systems"
0x0002 -> "Prism"
0x0003 -> "OpenFortress"
0x0004 -> "Wewid"
0x0005 -> "ZeitControl"
0x0006 -> "Yubico"
0x0007 -> "OpenKMS"
0x0008 -> "LogoEmail"
0x0009 -> "Fidesmo"
0x000A -> "VivoKey"
0x000B -> "Feitian Technologies"
0x000D -> "Dangerous Things"
0x000E -> "Excelsecu"
0x000F -> "Nitrokey"
0x002A -> "Magrathea"
0x0042 -> "GnuPG e.V."
0x1337 -> "Warsaw Hackerspace"
0x2342 -> "warpzone"
0x4354 -> "Confidential Technologies"
0x5343 -> "SSE Carte à puce"
0x5443 -> "TIF-IT e.V."
0x63AF -> "Trustica"
0xBA53 -> "c-base e.V."
0xBD0E -> "Paranoidlabs"
0xCA05 -> "Atos CardOS"
0xF1D0 -> "CanoKeys"
0xF517 -> "FSIJ"
0xF5EC -> "F-Secure"
0x0000, 0xFFFF -> "test card"
else -> "unknown"
}

View file

@ -0,0 +1,26 @@
package app.passwordstore.crypto
import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.key.OpenPgpFingerprint
public data class DeviceKeyInfo(
public val algorithm: PublicKeyAlgorithm,
public val fingerprint: OpenPgpFingerprint
) {
override fun toString(): String = "${algorithm.displayName()} ${fingerprint.prettyPrint()}"
}
@Suppress("DEPRECATION")
private fun PublicKeyAlgorithm.displayName(): String = when (this) {
PublicKeyAlgorithm.RSA_GENERAL -> "RSA"
PublicKeyAlgorithm.RSA_ENCRYPT -> "RSA (encrypt-only, deprecated)"
PublicKeyAlgorithm.RSA_SIGN -> "RSA (sign-only, deprecated)"
PublicKeyAlgorithm.ELGAMAL_ENCRYPT -> "ElGamal"
PublicKeyAlgorithm.DSA -> "DSA"
PublicKeyAlgorithm.EC -> "EC (deprecated)"
PublicKeyAlgorithm.ECDH -> "ECDH"
PublicKeyAlgorithm.ECDSA -> "ECDSA"
PublicKeyAlgorithm.ELGAMAL_GENERAL -> "ElGamal (general, deprecated)"
PublicKeyAlgorithm.DIFFIE_HELLMAN -> "Diffie-Hellman"
PublicKeyAlgorithm.EDDSA -> "EDDSA"
}

View file

@ -0,0 +1,46 @@
package app.passwordstore.crypto
import de.cotech.hw.openpgp.OpenPgpSecurityKey
import de.cotech.hw.openpgp.internal.openpgp.EcKeyFormat
import de.cotech.hw.openpgp.internal.openpgp.KeyFormat
import de.cotech.hw.openpgp.internal.openpgp.RsaKeyFormat
import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.key.OpenPgpFingerprint
public class HWSecurityDevice(
public val id: DeviceIdentifier,
public val name: String,
public val encryptKeyInfo: DeviceKeyInfo?,
public val signKeyInfo: DeviceKeyInfo?,
public val authKeyInfo: DeviceKeyInfo?,
)
internal fun OpenPgpSecurityKey.toDevice(): HWSecurityDevice =
with (openPgpAppletConnection.openPgpCapabilities) {
HWSecurityDevice(
id = DeviceIdentifier(aid),
name = securityKeyName,
encryptKeyInfo = keyInfo(encryptKeyFormat, fingerprintEncrypt),
signKeyInfo = keyInfo(signKeyFormat, fingerprintSign),
authKeyInfo = keyInfo(authKeyFormat, fingerprintAuth)
)
}
internal fun keyInfo(
format: KeyFormat?,
fingerprint: ByteArray?
): DeviceKeyInfo? {
if (format == null || fingerprint == null) return null
return DeviceKeyInfo(format.toKeyAlgorithm(), OpenPgpFingerprint.parseFromBinary(fingerprint))
}
internal fun KeyFormat.toKeyAlgorithm(): PublicKeyAlgorithm = when (this) {
is RsaKeyFormat -> PublicKeyAlgorithm.RSA_GENERAL
is EcKeyFormat -> when (val id = algorithmId()) {
PublicKeyAlgorithm.ECDH.algorithmId -> PublicKeyAlgorithm.ECDH
PublicKeyAlgorithm.ECDSA.algorithmId -> PublicKeyAlgorithm.ECDSA
PublicKeyAlgorithm.EDDSA.algorithmId -> PublicKeyAlgorithm.EDDSA
else -> throw IllegalArgumentException("Unknown EC algorithm ID: $id")
}
else -> throw IllegalArgumentException("Unknown key format")
}

View file

@ -0,0 +1,52 @@
package app.passwordstore.crypto
import androidx.fragment.app.FragmentManager
import app.passwordstore.crypto.errors.DeviceFingerprintMismatch
import app.passwordstore.crypto.errors.DeviceHandlerException
import app.passwordstore.crypto.errors.DeviceOperationFailed
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError
import com.github.michaelbull.result.runCatching
import org.bouncycastle.openpgp.PGPSessionKey
public class HWSecurityDeviceHandler(
private val deviceManager: HWSecurityManager,
private val fragmentManager: FragmentManager,
) : DeviceHandler<PGPKey, PGPEncryptedSessionKey, PGPSessionKey> {
override suspend fun pairWithPublicKey(
publicKey: PGPKey
): Result<PGPKey, DeviceHandlerException> = runCatching {
val publicFingerprint = KeyUtils.tryGetEncryptionKeyFingerprint(publicKey)
?: throw DeviceOperationFailed("Failed to get encryption key fingerprint")
val device = deviceManager.readDevice(fragmentManager)
if (publicFingerprint != device.encryptKeyInfo?.fingerprint) {
throw DeviceFingerprintMismatch(
publicFingerprint.toString(),
device.encryptKeyInfo?.fingerprint?.toString() ?: "Missing encryption key"
)
}
KeyUtils.tryCreateStubKey(
publicKey,
device.id.serialNumber,
listOfNotNull(
device.encryptKeyInfo.fingerprint,
device.signKeyInfo?.fingerprint,
device.authKeyInfo?.fingerprint
)
) ?: throw DeviceOperationFailed("Failed to create stub secret key")
}.mapError { error ->
when (error) {
is DeviceHandlerException -> error
else -> DeviceOperationFailed("Failed to pair device", error)
}
}
override suspend fun decryptSessionKey(
encryptedSessionKey: PGPEncryptedSessionKey
): Result<PGPSessionKey, DeviceHandlerException> = runCatching {
deviceManager.decryptSessionKey(fragmentManager, encryptedSessionKey)
}.mapError { error ->
DeviceOperationFailed("Failed to decrypt session key", error)
}
}

View file

@ -0,0 +1,182 @@
package app.passwordstore.crypto
import android.app.Application
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import de.cotech.hw.SecurityKeyManager
import de.cotech.hw.SecurityKeyManagerConfig
import de.cotech.hw.openpgp.OpenPgpSecurityKey
import de.cotech.hw.openpgp.OpenPgpSecurityKeyDialogFragment
import de.cotech.hw.openpgp.internal.operations.PsoDecryptOp
import de.cotech.hw.secrets.ByteSecret
import de.cotech.hw.secrets.PinProvider
import de.cotech.hw.ui.SecurityKeyDialogInterface
import de.cotech.hw.ui.SecurityKeyDialogInterface.SecurityKeyDialogCallback
import de.cotech.hw.ui.SecurityKeyDialogOptions
import de.cotech.hw.ui.SecurityKeyDialogOptions.PinMode
import java.io.IOException
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.completeWith
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.bouncycastle.bcpg.ECDHPublicBCPGKey
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags
import org.bouncycastle.openpgp.PGPSessionKey
import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.decryption_verification.HardwareSecurity.HardwareSecurityException
@Singleton
public class HWSecurityManager @Inject constructor(
private val application: Application,
) {
private val securityKeyManager: SecurityKeyManager by lazy {
SecurityKeyManager.getInstance()
}
public fun init(
enableLogging: Boolean = false
) {
securityKeyManager.init(
application,
SecurityKeyManagerConfig.Builder()
.setEnableDebugLogging(enableLogging)
.build()
)
}
public fun isHardwareAvailable(): Boolean {
return securityKeyManager.isNfcHardwareAvailable || securityKeyManager.isUsbHostModeAvailable
}
private suspend fun <T : Any> withOpenDevice(
fragmentManager: FragmentManager,
pinMode: PinMode,
block: suspend (OpenPgpSecurityKey, PinProvider?) -> T
): T = withContext(Dispatchers.Main) {
val fragment = OpenPgpSecurityKeyDialogFragment.newInstance(
SecurityKeyDialogOptions.builder()
.setPinMode(pinMode)
.setFormFactor(SecurityKeyDialogOptions.FormFactor.SECURITY_KEY)
.setPreventScreenshots(false) // TODO
.build()
)
val deferred = CompletableDeferred<T>()
fragment.setSecurityKeyDialogCallback(object : SecurityKeyDialogCallback<OpenPgpSecurityKey> {
private var result: Result<T> = Result.failure(CancellationException())
override fun onSecurityKeyDialogDiscovered(
dialogInterface: SecurityKeyDialogInterface,
securityKey: OpenPgpSecurityKey,
pinProvider: PinProvider?
) {
fragment.lifecycleScope.launch {
fragment.repeatOnLifecycle(Lifecycle.State.CREATED) {
runCatching {
fragment.postProgressMessage("Decrypting password entry")
result = Result.success(block(securityKey, pinProvider))
fragment.successAndDismiss()
}.onFailure { e ->
when (e) {
is IOException -> fragment.postError(e)
else -> {
result = Result.failure(e)
fragment.dismiss()
}
}
}
}
}
}
override fun onSecurityKeyDialogCancel() {
deferred.cancel()
}
override fun onSecurityKeyDialogDismiss() {
deferred.completeWith(result)
}
})
fragment.show(fragmentManager)
val value = deferred.await()
// HWSecurity doesn't clean up fast enough for LeakCanary's liking.
securityKeyManager.clearConnectedSecurityKeys()
value
}
public suspend fun readDevice(
fragmentManager: FragmentManager
): HWSecurityDevice = withOpenDevice(fragmentManager, PinMode.NO_PIN_INPUT) { securityKey, _ ->
securityKey.toDevice()
}
public suspend fun decryptSessionKey(
fragmentManager: FragmentManager,
encryptedSessionKey: PGPEncryptedSessionKey
): PGPSessionKey = withOpenDevice(fragmentManager, PinMode.PIN_INPUT) { securityKey, pinProvider ->
val pin = pinProvider?.getPin(securityKey.openPgpInstanceAid)
?: throw HWSecurityException("PIN required for decryption")
val contents = withContext(Dispatchers.IO) {
when (val a = encryptedSessionKey.algorithm) {
PublicKeyAlgorithm.RSA_GENERAL ->
decryptSessionKeyRsa(encryptedSessionKey, securityKey, pin)
PublicKeyAlgorithm.ECDH ->
decryptSessionKeyEcdh(encryptedSessionKey, securityKey, pin)
else -> throw HWSecurityException("Unsupported encryption algorithm: ${a.name}")
}
}
PGPSessionKey(encryptedSessionKey.algorithm.algorithmId, contents)
}
}
public class HWSecurityException(override val message: String) : HardwareSecurityException()
private fun decryptSessionKeyRsa(
encryptedSessionKey: PGPEncryptedSessionKey,
securityKey: OpenPgpSecurityKey,
pin: ByteSecret,
): ByteArray {
return PsoDecryptOp
.create(securityKey.openPgpAppletConnection)
.verifyAndDecryptSessionKey(pin, encryptedSessionKey.contents, 0, null)
}
@Suppress("MagicNumber")
private fun decryptSessionKeyEcdh(
encryptedSessionKey: PGPEncryptedSessionKey,
securityKey: OpenPgpSecurityKey,
pin: ByteSecret,
): ByteArray {
val key = encryptedSessionKey.publicKey.publicKeyPacket.key.run {
this as? ECDHPublicBCPGKey
?: throw HWSecurityException("Expected ECDHPublicBCPGKey but got ${this::class.simpleName}")
}
val symmetricKeySize = when (val id = key.symmetricKeyAlgorithm.toInt()) {
SymmetricKeyAlgorithmTags.AES_128 -> 128
SymmetricKeyAlgorithmTags.AES_192 -> 192
SymmetricKeyAlgorithmTags.AES_256 -> 256
else -> throw HWSecurityException("Unexpected symmetric key algorithm: $id")
}
return PsoDecryptOp
.create(securityKey.openPgpAppletConnection)
.verifyAndDecryptSessionKey(
pin,
encryptedSessionKey.contents,
symmetricKeySize,
byteArrayOf()
)
}

View file

@ -15,7 +15,7 @@ dependencies {
implementation(libs.dagger.hilt.core)
implementation(libs.kotlin.coroutines.core)
implementation(libs.thirdparty.kotlinResult)
implementation(libs.thirdparty.pgpainless)
api(libs.thirdparty.pgpainless)
testImplementation(libs.bundles.testDependencies)
testImplementation(libs.kotlin.coroutines.test)
testImplementation(libs.testing.testparameterinjector)

View file

@ -0,0 +1,16 @@
package app.passwordstore.crypto
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSessionKey
import org.pgpainless.algorithm.PublicKeyAlgorithm
public class PGPEncryptedSessionKey(
public val publicKey: PGPPublicKey,
public val algorithm: PublicKeyAlgorithm,
public val contents: ByteArray
)
public fun PGPSessionKey(
algorithm: PublicKeyAlgorithm,
sessionKey: ByteArray
): PGPSessionKey = PGPSessionKey(algorithm.algorithmId, sessionKey)

View file

@ -217,6 +217,8 @@ include("coroutine-utils-testing")
include("crypto-common")
include("crypto-hwsecurity")
include("crypto-pgpainless")
include("format-common")