mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'feature/passkeys_validation' into feature/Passkeys
This commit is contained in:
@@ -38,15 +38,15 @@ android {
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF") // bouncycastle need this
|
||||
// Bouncy castle bug https://github.com/bcgit/bc-java/issues/1685
|
||||
resources.pickFirsts.add('META-INF/versions/9/OSGI-INF/MANIFEST.MF')
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Crypto
|
||||
implementation 'org.bouncycastle:bcpkix-jdk18on:1.78.1'
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 202 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.encrypt
|
||||
|
||||
import com.kunzisoft.encrypt.HashManager.fingerprintToUrlSafeBase64
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class AppSignatureTest {
|
||||
|
||||
@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))
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.kunzisoft.asymmetric
|
||||
package com.kunzisoft.encrypt
|
||||
|
||||
import com.kunzisoft.encrypt.HashManager.fingerprintToUrlSafeBase64
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class SignatureTest {
|
||||
@@ -154,4 +156,22 @@ class SignatureTest {
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
package com.kunzisoft.asymmetric
|
||||
|
||||
import android.util.Log
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
||||
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters
|
||||
import org.bouncycastle.crypto.util.OpenSSHPrivateKeyUtil
|
||||
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
|
||||
|
||||
object Signature {
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
237
crypto/src/main/java/com/kunzisoft/encrypt/Signature.kt
Normal file
237
crypto/src/main/java/com/kunzisoft/encrypt/Signature.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.random
|
||||
|
||||
import java.security.SecureRandom
|
||||
|
||||
class KeePassDXRandom {
|
||||
|
||||
companion object {
|
||||
|
||||
private val internalSecureRandom: SecureRandom = SecureRandom()
|
||||
|
||||
fun generateCredentialId(): ByteArray {
|
||||
// see https://w3c.github.io/webauthn/#credential-id
|
||||
val size = 16
|
||||
val credentialId = ByteArray(size)
|
||||
internalSecureRandom.nextBytes(credentialId)
|
||||
return credentialId
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user