fix: First validation pass

This commit is contained in:
J-Jamet
2025-09-02 11:49:40 +02:00
parent 5e4ee167fc
commit d36f675da7
101 changed files with 6028 additions and 1133 deletions

View File

@@ -36,12 +36,17 @@ android {
kotlinOptions {
jvmTarget = "17"
}
packaging {
resources.excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF") // bouncycastle need this
}
}
dependencies {
// Crypto
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.81'
//androidTestImplementation 'org.testng:testng:6.9.6'
androidTestImplementation "androidx.test:runner:$android_test_version"
testImplementation "androidx.test:runner:$android_test_version"
}

View File

@@ -0,0 +1,177 @@
package com.kunzisoft.encrypt
import com.kunzisoft.encrypt.HashManager.fingerprintToUrlSafeBase64
import org.junit.Assert
import org.junit.Test
class SignatureTest {
// All private keys are for testing only.
// DO NOT USE THEM
// region ES256
private val es256PemInKeePassXC =
"""
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgaIrmuL+0IpvMpZ4O
8+CpXEzVNoyNkhquyRqD8CtVWDmhRANCAARyucecj8E9YvcAZHEYgElcLjwLMWmM
vQ2BDZPVL4pLG1oBZer1mPEEQV7LzwGYvTzV/eb9GlXPwj/4la/bpVp1
-----END PRIVATE KEY-----
""".trimIndent().trim()
private val es256PemInKeePassDX = """
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgaIrmuL+0IpvMpZ4O
8+CpXEzVNoyNkhquyRqD8CtVWDmgCgYIKoZIzj0DAQehRANCAARyucecj8E9YvcA
ZHEYgElcLjwLMWmMvQ2BDZPVL4pLG1oBZer1mPEEQV7LzwGYvTzV/eb9GlXPwj/4
la/bpVp1
-----END PRIVATE KEY-----
""".trimIndent().trim()
@Test
fun testEC256KeyConversionKeypassXCIn() {
val privateKey = Signature.createPrivateKey(es256PemInKeePassXC)
val pemOut = Signature.convertPrivateKeyToPem(privateKey)
assert(pemOut == es256PemInKeePassDX)
}
@Test
fun testEC256KeyConversionKeePassDXIn() {
val privateKey = Signature.createPrivateKey(es256PemInKeePassDX)
val pemOut = Signature.convertPrivateKeyToPem(privateKey)
assert(pemOut == es256PemInKeePassDX)
}
@Test
fun testEC256KeyGenAndConversion() {
val (keyPair, keyTypeId) = Signature.generateKeyPair(listOf(Signature.ES256_ALGORITHM))!!
val privateKeyPem = Signature.convertPrivateKeyToPem(keyPair.private)
assert(keyTypeId == Signature.ES256_ALGORITHM)
assert(privateKeyPem.contains("-----BEGIN PRIVATE KEY-----", true))
assert( privateKeyPem.contains("-----BEGIN EC PRIVATE KEY-----", true).not())
}
// endregion
// region RSA
private val rsa256PemIn = """
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCaunVJEhLHl7/f
NZufOmj4MY/1J/YHgMAZYFBORQVm58psUjCU7jIww+BK5aRGShdumRzbxr1Yqyh6
yvWbv2u9l6cOdtQKFtXDsWtP9tMBqqhODhG30gE3rEt5l2k1CSzSO9sGghUxlb2i
Q9fiSQ4HmiEc+cXbsSbeYsWwGYNYNhPdJ7vwsZsXzmD0RPpcxy0uJatASjWx3lXF
+eprpcZUr0NLlGIob6VfCt0q2ZfoeEHcEcp4qQ9nI+hOLPFzp/x1TFLX8wKvmwRh
ifwyAauVIaDZaQvAeoBuV2hSBl596ujiJt2Kd3pbQ4SjD1MXudbVvGkofgSZNR0f
ai6f6POFAgMBAAECggEAFtSIVb3K85RahU7dpYLy1hxKB3xb+wNuVNA3STU59NMi
tRTzgiYbVcKxJ5v2v0BTcMg6z9rlOV4X3PZxgwedmB32UlYKN2rjI7rcALKEs+xA
ZTQCPUNJVrOfd1N1/JNb/7FBQhaTlftoPbcQ9Zyd61U8qY/ZN+9NsuaUEMXS8YLe
cqlwJjRcWh3PuTQ+qeVw5l6lgK4XEyDbh/Aj9DGgwVsAkwGdXpuQRBQr8UClO/he
2iOwkn4LJ5nnXwByMpEct03+eUj0kxlijunYbBnKJfRv6tz+ZcpZoc1EqeWbrTB0
eKf+R6N8MHgJSemVVGZvgsUYbfMqkJA/LNOyIpQ/wQKBgQDJhowHVDtze+FQrunR
NchOXgZNWTFf3ZITxxnWnTgumtKdg3MkxeKEBCzAqefb6n6zi2rQzP/PAZAKT/we
YP24hwUVeFePH9/Llf5QuCOWGtkZbNRFSCHWcbfRQAL4vfPJk79bhwCoC5wQ5uk1
atCA+dln6b3wDXq2bvBs6Rj7bQKBgQDEjZIMMgYoEq6yKCFK+11BFo3sj3rmbCcE
tu29mXBfromgzfL0NLoqUAB5OsYKO1nl7eQp2QVIgdLLs8BwTKkel4gosK9B5T4C
umFG0yGIOJz7twA5joLuZAFsoPazj6yHUXaFJaye2P5KwVCL9ws5V2WKgnsF/hKe
QWwSIjtxeQKBgAbyR1NdWOtDIuIYFWErvGrPHOJ/p48JYSajX0Whh7U7ivT4+fgT
hhpM1ooRkTdoXtOrg5QM7OhiwmdImIUnjLdWmBtEWahKTfmDgw+fOULMTB1vPeXh
daEhrFdfIHsYeRXCrP7nqWMhe1Ct1O4Nb4BynEbTrMNgg5FUQ59NbZoFAoGAXNNb
YSUS4UQJexwWtRnHbeDgABO3ADGdr81QtBVOC/IbD3WUQx7PuQH1Z0uJkfV7vGpA
Mj9LDnY5fniS7rZVvJvl8wmWi3FfetxY6qD1mibahMplcclLLpjOT2YpfJ3i5jlj
1vf28UIbvmRTzPZMN7V9wA9lWGwokNLm3h2Ko0kCgYBq0NEd+VMkuIXuGz6j2IXC
qjKf187RZAn2B7otXoumCze+uxm4N0PyOYb1fNGHeE8/RjNQO7VmzZg/dMrk9ZJh
ueHJgOLTbDdlQCUacSipHGmWMN9E+EjgBRiqmPZzV6dq/kGc2FUSGB22wY8gckEX
AmqgkPgYHZ/VzFPTrp97IQ==
-----END PRIVATE KEY-----
""".trimIndent().trim()
@Test
fun testRS256KeyConversion() {
val privateKey = Signature.createPrivateKey(rsa256PemIn)
val pemOut = Signature.convertPrivateKeyToPem(privateKey)
assert(pemOut == rsa256PemIn)
}
@Test
fun testRS256KeyGenAndConversion() {
val (keyPair, keyTypeId) = Signature.generateKeyPair(listOf(Signature.RS256_ALGORITHM))!!
val privateKeyPem = Signature.convertPrivateKeyToPem(keyPair.private)
assert(keyTypeId == Signature.RS256_ALGORITHM)
assert(privateKeyPem.contains("-----BEGIN PRIVATE KEY-----", true))
}
// endregion
// region ED25519
private val ed25519PemInShort = """
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEILBoCo4+IXxIuwN36/oaEsPgbe6WYJcV9YW+xnprDF4H
-----END PRIVATE KEY-----
""".trimIndent()
private val ed25519PemInLong = """
-----BEGIN PRIVATE KEY-----
MFECAQEwBQYDK2VwBCIEIESP8edVGbqoR/pKNmy7j7FV8Y68zrIi/5VEuAJ281K6
gSEAyJU1wQNaJUeyxPcWjN7xZKZUhCRoIFS/MQvbdd4QE7Q=
-----END PRIVATE KEY-----
""".trimIndent()
private val ed25519PemOut = """
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIESP8edVGbqoR/pKNmy7j7FV8Y68zrIi/5VEuAJ281K6
-----END PRIVATE KEY-----
""".trimIndent()
@Test
fun testEd25519KeyConverionShortIn() {
val privateKey = Signature.createPrivateKey(ed25519PemInShort)
val pemOut = Signature.convertPrivateKeyToPem(privateKey)
assert(pemOut == ed25519PemInShort)
}
@Test
fun testEd25519KeyConverionLongIn() {
val privateKey = Signature.createPrivateKey(ed25519PemInLong)
val pemOut = Signature.convertPrivateKeyToPem(privateKey)
assert(pemOut == ed25519PemOut)
}
@Test
fun testEd25519KeyGenAndConversion() {
val (keyPair, keyTypeId) = Signature.generateKeyPair(listOf(Signature.ED_DSA_ALGORITHM))!!
val privateKeyPem = Signature.convertPrivateKeyToPem(keyPair.private)
assert(keyTypeId == Signature.ED_DSA_ALGORITHM)
assert(privateKeyPem.contains("-----BEGIN PRIVATE KEY-----", true))
assert(privateKeyPem.contains("-----BEGIN EC PRIVATE KEY-----", true).not())
}
// endregion
@Test
fun testSingleSignature() {
// Generate random input
val fingerprint = "A7:5C:63:72:A0:B6:7D:B0:16:86:B4:7D:F6:8C:91:51:6E:E1:62:29:EE:C4:C0:C6:7D:35:5E:32:20:7C:66:17"
val expected = "p1xjcqC2fbAWhrR99oyRUW7hYinuxMDGfTVeMiB8Zhc"
Assert.assertEquals("Check fingerprint app", expected, fingerprintToUrlSafeBase64(fingerprint))
}
@Test
fun testMultipleSignature() {
// Generate random input
val fingerprint = "A7:5C:63:72:A0:B6:7D:B0:16:86:B4:7D:F6:8C:91:51:6E:E1:62:29:EE:C4:C0:C6:7D:35:5E:32:20:7C:66:17##SIG##DB:25:8A:A6:19:08:9B:D1:3D:BA:71:9E:5A:DA:EC:FF:7F:12:C8:8F:67:AD:68:3C:1F:BC:F2:28:B3:88:BD:91"
val expected = "p1xjcqC2fbAWhrR99oyRUW7hYinuxMDGfTVeMiB8Zhc"
Assert.assertEquals("Check fingerprint app", expected, fingerprintToUrlSafeBase64(fingerprint))
}
}

View File

@@ -0,0 +1,23 @@
package com.kunzisoft.encrypt
import android.util.Base64
class Base64Helper {
companion object {
fun b64Decode(encodedString: String): ByteArray {
return Base64.decode(
encodedString,
Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE
)
}
fun b64Encode(data: ByteArray): String {
return Base64.encodeToString(
data,
Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE
)
}
}
}

View File

@@ -19,6 +19,11 @@
*/
package com.kunzisoft.encrypt
import android.content.pm.Signature
import android.content.pm.SigningInfo
import android.os.Build
import android.util.AndroidException
import android.util.Log
import org.bouncycastle.crypto.engines.ChaCha7539Engine
import org.bouncycastle.crypto.engines.Salsa20Engine
import org.bouncycastle.crypto.params.KeyParameter
@@ -26,9 +31,14 @@ import org.bouncycastle.crypto.params.ParametersWithIV
import java.io.IOException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.Locale
object HashManager {
private val TAG = HashManager::class.simpleName
fun getHash256(): MessageDigest {
val messageDigest: MessageDigest
try {
@@ -107,4 +117,115 @@ object HashManager {
return StreamCipher(cipher)
}
const val SIGNATURE_DELIMITER = "##SIG##"
/**
* Converts a Signature object into its SHA-256 fingerprint string.
* The fingerprint is typically represented as uppercase hex characters separated by colons.
*/
private fun signatureToSha256Fingerprint(signature: Signature): String? {
return try {
val certificateFactory = CertificateFactory.getInstance("X.509")
val x509Certificate = certificateFactory.generateCertificate(
signature.toByteArray().inputStream()
) as X509Certificate
val messageDigest = MessageDigest.getInstance("SHA-256")
val digest = messageDigest.digest(x509Certificate.encoded)
// Format as colon-separated HEX uppercase string
digest.joinToString(separator = ":") { byte -> "%02X".format(byte) }
.uppercase(Locale.US)
} catch (e: Exception) {
Log.e("SigningInfoUtil", "Error converting signature to SHA-256 fingerprint", e)
null
}
}
/**
* Retrieves all relevant SHA-256 signature fingerprints for a given package.
*
* @param signingInfo The SigningInfo object to retrieve the strings signatures
* @return A List of SHA-256 fingerprint strings, or null if an error occurs or no signatures are found.
*/
fun getAllFingerprints(signingInfo: SigningInfo?): List<String>? {
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
throw AndroidException("API level ${Build.VERSION.SDK_INT} not supported")
val signatures = mutableSetOf<String>()
if (signingInfo != null) {
// Includes past and current keys if rotation occurred. This is generally preferred.
signingInfo.signingCertificateHistory?.forEach { signature ->
signatureToSha256Fingerprint(signature)?.let { signatures.add(it) }
}
// If only one signer and history is empty (e.g. new app), this might be needed.
// Or if multiple signers are explicitly used for the APK content.
if (signingInfo.hasMultipleSigners()) {
signingInfo.apkContentsSigners?.forEach { signature ->
signatureToSha256Fingerprint(signature)?.let { signatures.add(it) }
}
} else { // Fallback for single signer if history was somehow null/empty
signingInfo.signingCertificateHistory?.firstOrNull()?.let {
signatureToSha256Fingerprint(it)?.let { fp -> signatures.add(fp) }
}
}
}
return if (signatures.isEmpty()) null else signatures.toList()
} catch (e: Exception) {
Log.e(TAG, "Error getting signatures", e)
return null
}
}
/**
* Combines a list of signature into a single string for database storage.
*
* @return A single string with fingerprints joined by a ##SIG## delimiter,
* or null if the input list is null or empty.
*/
fun SigningInfo.getApplicationFingerprints(): String? {
val fingerprints = getAllFingerprints(this)
if (fingerprints.isNullOrEmpty()) {
return null
}
return fingerprints.joinToString(SIGNATURE_DELIMITER)
}
/**
* Transforms a colon-separated hex fingerprint string into a URL-safe,
* padding-removed Base64 string, mimicking the Python behavior:
* base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', '')
*
* Only check the first footprint if there are several delimited by ##SIG##.
*
* @param fingerprint The colon-separated hex fingerprint string (e.g., "91:F7:CB:...").
* @return The Android App Origin string.
* @throws IllegalArgumentException if the hex string (after removing colons) has an odd length
* or contains non-hex characters.
*/
fun fingerprintToUrlSafeBase64(fingerprint: String): String {
val firstFingerprint = fingerprint.split(SIGNATURE_DELIMITER).firstOrNull()?.trim()
if (firstFingerprint.isNullOrEmpty()) {
throw IllegalArgumentException("Invalid fingerprint $fingerprint")
}
val hexStringNoColons = firstFingerprint.replace(":", "")
if (hexStringNoColons.length % 2 != 0) {
throw IllegalArgumentException("Hex string must have an even number of characters: $hexStringNoColons")
}
if (hexStringNoColons.length != 64) {
throw IllegalArgumentException("Expected a 64-character hex string for a SHA-256 hash, but got ${hexStringNoColons.length} characters.")
}
val hashBytes = ByteArray(hexStringNoColons.length / 2)
for (i in hashBytes.indices) {
try {
val index = i * 2
val byteValue = hexStringNoColons.substring(index, index + 2).toInt(16)
hashBytes[i] = byteValue.toByte()
} catch (e: NumberFormatException) {
throw IllegalArgumentException("Invalid hex character in fingerprint: $hexStringNoColons", e)
}
}
return Base64Helper.b64Encode(hashBytes)
}
}

View File

@@ -0,0 +1,237 @@
package com.kunzisoft.encrypt
import android.util.Log
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPrivateKey
import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPublicKey
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.PEMParser
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator
import org.bouncycastle.util.BigIntegers
import org.bouncycastle.util.io.pem.PemWriter
import java.io.StringReader
import java.io.StringWriter
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.PrivateKey
import java.security.PublicKey
import java.security.Security
import java.security.Signature
import java.security.spec.ECGenParameterSpec
class Signature {
companion object {
// see at https://www.iana.org/assignments/cose/cose.xhtml
const val ES256_ALGORITHM: Long = -7
const val RS256_ALGORITHM: Long = -257
private const val RS256_KEY_SIZE_IN_BITS = 2048
const val ED_DSA_ALGORITHM: Long = -8
init {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider())
}
fun sign(privateKeyPem: String, message: ByteArray): ByteArray? {
val privateKey = createPrivateKey(privateKeyPem)
val algorithmKey = privateKey.algorithm
val algorithmSignature = when (algorithmKey) {
"EC" -> "SHA256withECDSA"
"ECDSA" -> "SHA256withECDSA"
"RSA" -> "SHA256withRSA"
"Ed25519" -> "Ed25519"
else -> null
}
if (algorithmSignature == null) {
Log.e(this::class.java.simpleName, "sign: the algorithm $algorithmKey is unknown")
return null
}
val sig = Signature.getInstance(algorithmSignature, BouncyCastleProvider.PROVIDER_NAME)
sig.initSign(privateKey)
sig.update(message)
return sig.sign()
}
fun createPrivateKey(privateKeyPem: String): PrivateKey {
val targetReader = StringReader(privateKeyPem)
val pemParser = PEMParser(targetReader)
val privateKeyInfo = pemParser.readObject() as PrivateKeyInfo
val privateKey = JcaPEMKeyConverter().getPrivateKey(privateKeyInfo)
pemParser.close()
targetReader.close()
return privateKey
}
fun convertPrivateKeyToPem(privateKey: PrivateKey): String {
var useV1Info = false
if (privateKey is BCEdDSAPrivateKey) {
// to generate PEM, which are compatible to KeepassXC
useV1Info = true
}
System.setProperty(
"org.bouncycastle.pkcs8.v1_info_only",
useV1Info.toString().lowercase()
)
val noOutputEncryption = null
val pemObjectGenerator = JcaPKCS8Generator(privateKey, noOutputEncryption)
val writer = StringWriter()
val pemWriter = PemWriter(writer)
pemWriter.writeObject(pemObjectGenerator)
pemWriter.close()
val privateKeyInPem = writer.toString().trim()
writer.close()
return privateKeyInPem
}
fun generateKeyPair(keyTypeIdList: List<Long>): Pair<KeyPair, Long>? {
for (typeId in keyTypeIdList) {
if (typeId == ES256_ALGORITHM) {
val es256CurveNameBC = "secp256r1"
val spec = ECGenParameterSpec(es256CurveNameBC)
val keyPairGen =
KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME)
keyPairGen.initialize(spec)
val keyPair = keyPairGen.genKeyPair()
return Pair(keyPair, ES256_ALGORITHM)
} else if (typeId == RS256_ALGORITHM) {
val keyPairGen =
KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME)
keyPairGen.initialize(RS256_KEY_SIZE_IN_BITS)
val keyPair = keyPairGen.genKeyPair()
return Pair(keyPair, RS256_ALGORITHM)
} else if (typeId == ED_DSA_ALGORITHM) {
val keyPairGen =
KeyPairGenerator.getInstance("Ed25519", BouncyCastleProvider.PROVIDER_NAME)
val keyPair = keyPairGen.genKeyPair()
return Pair(keyPair, ED_DSA_ALGORITHM)
}
}
Log.e(this::class.java.simpleName, "generateKeyPair: no known key type id found")
return null
}
fun convertPublicKey(publicKeyIn: PublicKey, keyTypeId: Long): ByteArray? {
if (keyTypeId == ES256_ALGORITHM) {
if (publicKeyIn is BCECPublicKey) {
publicKeyIn.setPointFormat("UNCOMPRESSED")
return publicKeyIn.encoded
}
} else if (keyTypeId == RS256_ALGORITHM) {
return publicKeyIn.encoded
} else if (keyTypeId == ED_DSA_ALGORITHM) {
return publicKeyIn.encoded
}
Log.e(this::class.java.simpleName, "convertPublicKey: unknown key type id found")
return null
}
fun convertPublicKeyToMap(publicKeyIn: PublicKey, keyTypeId: Long): Map<Int, Any>? {
// https://www.iana.org/assignments/cose/cose.xhtml#key-common-parameters
val keyTypeLabel = 1
val algorithmLabel = 3
if (keyTypeId == ES256_ALGORITHM) {
if (publicKeyIn !is BCECPublicKey) {
Log.e(
this::class.java.simpleName,
"publicKey object has wrong type for keyTypeId $ES256_ALGORITHM: ${publicKeyIn.javaClass.canonicalName}"
)
return null
}
// constants see at https://w3c.github.io/webauthn/#example-bdbd14cc
val publicKeyMap = mutableMapOf<Int, Any>()
val es256KeyTypeId = 2
val es256EllipticCurveP256Id = 1
publicKeyMap[keyTypeLabel] = es256KeyTypeId
publicKeyMap[algorithmLabel] = ES256_ALGORITHM
publicKeyMap[-1] = es256EllipticCurveP256Id
val ecPoint = publicKeyIn.q
publicKeyMap[-2] = ecPoint.xCoord.encoded
publicKeyMap[-3] = ecPoint.yCoord.encoded
return publicKeyMap
} else if (keyTypeId == RS256_ALGORITHM) {
if (publicKeyIn !is BCRSAPublicKey) {
Log.e(
this::class.java.simpleName,
"publicKey object has wrong type for keyTypeId $RS256_ALGORITHM: ${publicKeyIn.javaClass.canonicalName}"
)
return null
}
// constants see at https://w3c.github.io/webauthn/#example-8dfabc00
val rs256KeySizeInBytes = RS256_KEY_SIZE_IN_BITS / 8
val rs256KeyTypeId = 3
val rs256ExponentSizeInBytes = 3
val publicKeyMap = mutableMapOf<Int, Any>()
publicKeyMap[keyTypeLabel] = rs256KeyTypeId
publicKeyMap[algorithmLabel] = RS256_ALGORITHM
publicKeyMap[-1] =
BigIntegers.asUnsignedByteArray(rs256KeySizeInBytes, publicKeyIn.modulus)
publicKeyMap[-2] =
BigIntegers.asUnsignedByteArray(
rs256ExponentSizeInBytes,
publicKeyIn.publicExponent
)
return publicKeyMap
} else if (keyTypeId == ED_DSA_ALGORITHM) {
if (publicKeyIn !is BCEdDSAPublicKey) {
Log.e(
this::class.java.simpleName,
"publicKey object has wrong type for keyTypeId $ED_DSA_ALGORITHM: ${publicKeyIn.javaClass.canonicalName}"
)
return null
}
val publicKeyMap = mutableMapOf<Int, Any>()
// https://www.rfc-editor.org/rfc/rfc9053#name-key-object-parameters
val octetKeyPairId = 1
val curveLabel = -1
val ed25519CurveId = 6
val publicKeyLabel = -2
publicKeyMap[keyTypeLabel] = octetKeyPairId
publicKeyMap[algorithmLabel] = ED_DSA_ALGORITHM
publicKeyMap[curveLabel] = ed25519CurveId
val length = Ed25519PublicKeyParameters.KEY_SIZE
publicKeyMap[publicKeyLabel] = BigIntegers.asUnsignedByteArray(
length,
BigIntegers.fromUnsignedByteArray(publicKeyIn.pointEncoding)
)
return publicKeyMap
}
Log.e(this::class.java.simpleName, "convertPublicKeyToMap: no known key type id found")
return null
}
}
}