mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Add support for ed25519
This commit is contained in:
@@ -1,12 +1,19 @@
|
|||||||
package com.kunzisoft.asymmetric
|
package com.kunzisoft.asymmetric
|
||||||
|
|
||||||
|
import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPrivateKey
|
||||||
|
import org.bouncycastle.jcajce.provider.asymmetric.edec.BCXDHPrivateKey
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileWriter
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
class SignatureTest {
|
class SignatureTest {
|
||||||
|
|
||||||
// All private keys are for testing only.
|
// All private keys are for testing only.
|
||||||
// DO NOT USE THEM
|
// DO NOT USE THEM
|
||||||
|
|
||||||
|
|
||||||
|
// region ES256
|
||||||
private val es256PemInKeePassXC =
|
private val es256PemInKeePassXC =
|
||||||
"""
|
"""
|
||||||
-----BEGIN PRIVATE KEY-----
|
-----BEGIN PRIVATE KEY-----
|
||||||
@@ -48,10 +55,14 @@ class SignatureTest {
|
|||||||
|
|
||||||
assert(keyTypeId == Signature.ES256_ALGORITHM)
|
assert(keyTypeId == Signature.ES256_ALGORITHM)
|
||||||
assert(privateKeyPem.contains("-----BEGIN PRIVATE KEY-----", true))
|
assert(privateKeyPem.contains("-----BEGIN PRIVATE KEY-----", true))
|
||||||
assert(privateKeyPem.contains("-----BEGIN EC PRIVATE KEY-----", true).not())
|
assert( privateKeyPem.contains("-----BEGIN EC PRIVATE KEY-----", true).not())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region RSA
|
||||||
|
|
||||||
private val rsa256PemIn = """
|
private val rsa256PemIn = """
|
||||||
-----BEGIN PRIVATE KEY-----
|
-----BEGIN PRIVATE KEY-----
|
||||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCaunVJEhLHl7/f
|
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCaunVJEhLHl7/f
|
||||||
@@ -99,4 +110,58 @@ class SignatureTest {
|
|||||||
assert(keyTypeId == Signature.RS256_ALGORITHM)
|
assert(keyTypeId == Signature.RS256_ALGORITHM)
|
||||||
assert(privateKeyPem.contains("-----BEGIN PRIVATE KEY-----", true))
|
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
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,11 @@ package com.kunzisoft.asymmetric
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
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.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.jcajce.provider.asymmetric.rsa.BCRSAPublicKey
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.bouncycastle.openssl.PEMParser
|
import org.bouncycastle.openssl.PEMParser
|
||||||
@@ -27,6 +31,8 @@ object Signature {
|
|||||||
const val RS256_ALGORITHM: Long = -257
|
const val RS256_ALGORITHM: Long = -257
|
||||||
private const val RS256_KEY_SIZE_IN_BITS = 2048
|
private const val RS256_KEY_SIZE_IN_BITS = 2048
|
||||||
|
|
||||||
|
const val ED_DSA_ALGORITHM: Long = -8
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
Security.addProvider(BouncyCastleProvider())
|
Security.addProvider(BouncyCastleProvider())
|
||||||
@@ -39,10 +45,11 @@ object Signature {
|
|||||||
"EC" -> "SHA256withECDSA"
|
"EC" -> "SHA256withECDSA"
|
||||||
"ECDSA" -> "SHA256withECDSA"
|
"ECDSA" -> "SHA256withECDSA"
|
||||||
"RSA" -> "SHA256withRSA"
|
"RSA" -> "SHA256withRSA"
|
||||||
|
"Ed25519" -> "Ed25519"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (algorithmSignature == null) {
|
if (algorithmSignature == null) {
|
||||||
Log.e(this::class.java.simpleName, "sign: privateKeyPem has an unknown algorithm")
|
Log.e(this::class.java.simpleName, "sign: the algorithm $algorithmKey is unknown")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val sig = Signature.getInstance(algorithmSignature, BouncyCastleProvider.PROVIDER_NAME)
|
val sig = Signature.getInstance(algorithmSignature, BouncyCastleProvider.PROVIDER_NAME)
|
||||||
@@ -62,6 +69,13 @@ object Signature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun convertPrivateKeyToPem(privateKey: PrivateKey): String {
|
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 noOutputEncryption = null
|
||||||
val pemObjectGenerator = JcaPKCS8Generator(privateKey, noOutputEncryption)
|
val pemObjectGenerator = JcaPKCS8Generator(privateKey, noOutputEncryption)
|
||||||
|
|
||||||
@@ -86,13 +100,19 @@ object Signature {
|
|||||||
keyPairGen.initialize(spec)
|
keyPairGen.initialize(spec)
|
||||||
val keyPair = keyPairGen.genKeyPair()
|
val keyPair = keyPairGen.genKeyPair()
|
||||||
return Pair(keyPair, ES256_ALGORITHM)
|
return Pair(keyPair, ES256_ALGORITHM)
|
||||||
} else if (typeId == RS256_ALGORITHM) {
|
|
||||||
|
|
||||||
|
} else if (typeId == RS256_ALGORITHM) {
|
||||||
val keyPairGen =
|
val keyPairGen =
|
||||||
KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME)
|
KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME)
|
||||||
keyPairGen.initialize(RS256_KEY_SIZE_IN_BITS)
|
keyPairGen.initialize(RS256_KEY_SIZE_IN_BITS)
|
||||||
val keyPair = keyPairGen.genKeyPair()
|
val keyPair = keyPairGen.genKeyPair()
|
||||||
return Pair(keyPair, RS256_ALGORITHM)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +128,19 @@ object Signature {
|
|||||||
}
|
}
|
||||||
} else if (keyTypeId == RS256_ALGORITHM) {
|
} else if (keyTypeId == RS256_ALGORITHM) {
|
||||||
return publicKeyIn.encoded
|
return publicKeyIn.encoded
|
||||||
|
} else if (keyTypeId == ED_DSA_ALGORITHM) {
|
||||||
|
return publicKeyIn.encoded
|
||||||
}
|
}
|
||||||
Log.e(this::class.java.simpleName, "convertPublicKey: unknown key type id found")
|
Log.e(this::class.java.simpleName, "convertPublicKey: unknown key type id found")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun convertPublicKeyToMap(publicKeyIn: PublicKey, keyTypeId: Long): Map<Int, Any>? {
|
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 (keyTypeId == ES256_ALGORITHM) {
|
||||||
if (publicKeyIn !is BCECPublicKey) {
|
if (publicKeyIn !is BCECPublicKey) {
|
||||||
Log.e(
|
Log.e(
|
||||||
@@ -128,8 +155,9 @@ object Signature {
|
|||||||
val es256KeyTypeId = 2
|
val es256KeyTypeId = 2
|
||||||
val es256EllipticCurveP256Id = 1
|
val es256EllipticCurveP256Id = 1
|
||||||
|
|
||||||
publicKeyMap[1] = es256KeyTypeId
|
publicKeyMap[keyTypeLabel] = es256KeyTypeId
|
||||||
publicKeyMap[3] = ES256_ALGORITHM
|
publicKeyMap[algorithmLabel] = ES256_ALGORITHM
|
||||||
|
|
||||||
publicKeyMap[-1] = es256EllipticCurveP256Id
|
publicKeyMap[-1] = es256EllipticCurveP256Id
|
||||||
|
|
||||||
val ecPoint = publicKeyIn.q
|
val ecPoint = publicKeyIn.q
|
||||||
@@ -154,8 +182,8 @@ object Signature {
|
|||||||
val rs256ExponentSizeInBytes = 3
|
val rs256ExponentSizeInBytes = 3
|
||||||
|
|
||||||
val publicKeyMap = mutableMapOf<Int, Any>()
|
val publicKeyMap = mutableMapOf<Int, Any>()
|
||||||
publicKeyMap[1] = rs256KeyTypeId
|
publicKeyMap[keyTypeLabel] = rs256KeyTypeId
|
||||||
publicKeyMap[3] = RS256_ALGORITHM
|
publicKeyMap[algorithmLabel] = RS256_ALGORITHM
|
||||||
publicKeyMap[-1] =
|
publicKeyMap[-1] =
|
||||||
BigIntegers.asUnsignedByteArray(rs256KeySizeInBytes, publicKeyIn.modulus)
|
BigIntegers.asUnsignedByteArray(rs256KeySizeInBytes, publicKeyIn.modulus)
|
||||||
publicKeyMap[-2] =
|
publicKeyMap[-2] =
|
||||||
@@ -164,6 +192,36 @@ object Signature {
|
|||||||
publicKeyIn.publicExponent
|
publicKeyIn.publicExponent
|
||||||
)
|
)
|
||||||
return publicKeyMap
|
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")
|
Log.e(this::class.java.simpleName, "convertPublicKeyToMap: no known key type id found")
|
||||||
|
|||||||
Reference in New Issue
Block a user