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:
@@ -107,7 +107,8 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
packaging {
|
packaging {
|
||||||
resources.excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF") // necessary for bcpkix-jdk18on in crypto
|
// Bouncy castle bug https://github.com/bcgit/bc-java/issues/1685
|
||||||
|
resources.pickFirsts.add('META-INF/versions/9/OSGI-INF/MANIFEST.MF')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,8 @@
|
|||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/KeepassDXStyle.Night"
|
android:theme="@style/KeepassDXStyle.Night"
|
||||||
tools:targetApi="tiramisu"
|
tools:targetApi="s"
|
||||||
android:enableOnBackInvokedCallback="true">
|
tools:ignore="CredentialDependency">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.backup.api_key"
|
android:name="com.google.android.backup.api_key"
|
||||||
android:value="${googleAndroidBackupAPIKey}" />
|
android:value="${googleAndroidBackupAPIKey}" />
|
||||||
@@ -198,7 +197,7 @@
|
|||||||
android:label="@string/keyboard_setting_label"
|
android:label="@string/keyboard_setting_label"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
package com.kunzisoft.keepass.credentialprovider.passkey.data
|
package com.kunzisoft.keepass.credentialprovider.passkey.data
|
||||||
|
|
||||||
import androidx.credentials.exceptions.GetCredentialUnknownException
|
import androidx.credentials.exceptions.GetCredentialUnknownException
|
||||||
import com.kunzisoft.asymmetric.Signature
|
import com.kunzisoft.encrypt.Signature
|
||||||
import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
|
import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.credentialprovider.passkey.data
|
package com.kunzisoft.keepass.credentialprovider.passkey.data
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.kunzisoft.encrypt.Base64Helper
|
import com.kunzisoft.encrypt.Base64Helper
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
@@ -66,15 +65,6 @@ class PublicKeyCredentialCreationOptions(
|
|||||||
excludeCredentials = emptyList()
|
excludeCredentials = emptyList()
|
||||||
authenticatorSelection = AuthenticatorSelectionCriteria("platform", "required")
|
authenticatorSelection = AuthenticatorSelectionCriteria("platform", "required")
|
||||||
attestation = json.optString("attestation", "none")
|
attestation = json.optString("attestation", "none")
|
||||||
|
|
||||||
Log.i(TAG, "challenge $challenge()")
|
|
||||||
Log.i(TAG, "rp $relyingPartyEntity")
|
|
||||||
Log.i(TAG, "user $userEntity")
|
|
||||||
Log.i(TAG, "pubKeyCredParams $pubKeyCredParams")
|
|
||||||
Log.i(TAG, "timeout $timeout")
|
|
||||||
Log.i(TAG, "excludeCredentials $excludeCredentials")
|
|
||||||
Log.i(TAG, "authenticatorSelection $authenticatorSelection")
|
|
||||||
Log.i(TAG, "attestation $attestation")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ import androidx.credentials.provider.CallingAppInfo
|
|||||||
import androidx.credentials.provider.PendingIntentHandler
|
import androidx.credentials.provider.PendingIntentHandler
|
||||||
import androidx.credentials.provider.ProviderCreateCredentialRequest
|
import androidx.credentials.provider.ProviderCreateCredentialRequest
|
||||||
import androidx.credentials.provider.ProviderGetCredentialRequest
|
import androidx.credentials.provider.ProviderGetCredentialRequest
|
||||||
import com.kunzisoft.asymmetric.Signature
|
|
||||||
import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
|
import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
|
||||||
import com.kunzisoft.encrypt.HashManager.getApplicationFingerprints
|
import com.kunzisoft.encrypt.HashManager.getApplicationFingerprints
|
||||||
|
import com.kunzisoft.encrypt.Signature
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AuthenticatorAssertionResponse
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.AuthenticatorAssertionResponse
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AuthenticatorAttestationResponse
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.AuthenticatorAttestationResponse
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.Cbor
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.Cbor
|
||||||
@@ -61,11 +61,11 @@ import com.kunzisoft.keepass.model.SearchInfo
|
|||||||
import com.kunzisoft.keepass.model.WebOrigin
|
import com.kunzisoft.keepass.model.WebOrigin
|
||||||
import com.kunzisoft.keepass.utils.StringUtil.toHexString
|
import com.kunzisoft.keepass.utils.StringUtil.toHexString
|
||||||
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
||||||
import com.kunzisoft.random.KeePassDXRandom
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
import java.security.SecureRandom
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.crypto.KeyGenerator
|
import javax.crypto.KeyGenerator
|
||||||
@@ -104,6 +104,8 @@ object PasskeyHelper {
|
|||||||
|
|
||||||
private const val MAX_DIFF_IN_SECONDS = 60
|
private const val MAX_DIFF_IN_SECONDS = 60
|
||||||
|
|
||||||
|
private val internalSecureRandom: SecureRandom = SecureRandom()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the Passkey response for one entry
|
* Build the Passkey response for one entry
|
||||||
*/
|
*/
|
||||||
@@ -379,6 +381,17 @@ object PasskeyHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a credential id randomly
|
||||||
|
*/
|
||||||
|
private fun generateCredentialId(): ByteArray {
|
||||||
|
// see https://w3c.github.io/webauthn/#credential-id
|
||||||
|
val size = 16
|
||||||
|
val credentialId = ByteArray(size)
|
||||||
|
internalSecureRandom.nextBytes(credentialId)
|
||||||
|
return credentialId
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method to create a passkey and the associated creation request parameters
|
* Utility method to create a passkey and the associated creation request parameters
|
||||||
* [intent] allows to retrieve the request
|
* [intent] allows to retrieve the request
|
||||||
@@ -402,7 +415,7 @@ object PasskeyHelper {
|
|||||||
val pubKeyCredParams = creationOptions.pubKeyCredParams
|
val pubKeyCredParams = creationOptions.pubKeyCredParams
|
||||||
val clientDataHash = creationOptions.clientDataHash
|
val clientDataHash = creationOptions.clientDataHash
|
||||||
|
|
||||||
val credentialId = KeePassDXRandom.generateCredentialId()
|
val credentialId = generateCredentialId()
|
||||||
|
|
||||||
val (keyPair, keyTypeId) = Signature.generateKeyPair(
|
val (keyPair, keyTypeId) = Signature.generateKeyPair(
|
||||||
pubKeyCredParams.map { params -> params.alg }
|
pubKeyCredParams.map { params -> params.alg }
|
||||||
@@ -470,7 +483,8 @@ object PasskeyHelper {
|
|||||||
response = AuthenticatorAttestationResponse(
|
response = AuthenticatorAttestationResponse(
|
||||||
requestOptions = publicKeyCredentialCreationParameters.publicKeyCredentialCreationOptions,
|
requestOptions = publicKeyCredentialCreationParameters.publicKeyCredentialCreationOptions,
|
||||||
credentialId = publicKeyCredentialCreationParameters.credentialId,
|
credentialId = publicKeyCredentialCreationParameters.credentialId,
|
||||||
credentialPublicKey = Cbor().encode(Signature.convertPublicKeyToMap(
|
credentialPublicKey = Cbor().encode(
|
||||||
|
Signature.convertPublicKeyToMap(
|
||||||
publicKeyIn = keyPair.public,
|
publicKeyIn = keyPair.public,
|
||||||
keyTypeId = keyTypeId
|
keyTypeId = keyTypeId
|
||||||
) ?: mapOf<Int, Any>()),
|
) ?: mapOf<Int, Any>()),
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import com.kunzisoft.keepass.R
|
|||||||
|
|
||||||
class DialogColorPreference @JvmOverloads constructor(context: Context,
|
class DialogColorPreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: ChromaPreferenceCompat(context, attrs, defStyleAttr, defStyleRes) {
|
: ChromaPreferenceCompat(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.R
|
|||||||
|
|
||||||
class DialogListExplanationPreference @JvmOverloads constructor(context: Context,
|
class DialogListExplanationPreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.R
|
|||||||
|
|
||||||
class DurationDialogPreference @JvmOverloads constructor(context: Context,
|
class DurationDialogPreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import java.util.*
|
|||||||
|
|
||||||
class IconPackListPreference @JvmOverloads constructor(context: Context,
|
class IconPackListPreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: ListPreference(context, attrs, defStyleAttr, defStyleRes) {
|
: ListPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
|||||||
|
|
||||||
open class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
|
open class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.utils.DataByte
|
|||||||
|
|
||||||
class InputKdfSizePreference @JvmOverloads constructor(context: Context,
|
class InputKdfSizePreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: InputKdfNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
|
: InputKdfNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.R
|
|||||||
|
|
||||||
open class InputListPreference @JvmOverloads constructor(context: Context,
|
open class InputListPreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.R
|
|||||||
|
|
||||||
open class InputNumberPreference @JvmOverloads constructor(context: Context,
|
open class InputNumberPreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.utils.DataByte
|
|||||||
|
|
||||||
open class InputSizePreference @JvmOverloads constructor(context: Context,
|
open class InputSizePreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: InputNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
|
: InputNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.R
|
|||||||
|
|
||||||
open class InputTextPreference @JvmOverloads constructor(context: Context,
|
open class InputTextPreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.kunzisoft.keepass.settings.preference
|
|
||||||
|
|
||||||
class PreferenceConstant {
|
|
||||||
companion object {
|
|
||||||
const val R_ATTR_DIALOG_PREFERENCE_STYLE: Int = 16842897;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.R
|
|||||||
|
|
||||||
open class TextPreference @JvmOverloads constructor(context: Context,
|
open class TextPreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.kunzisoft.keepass.view
|
package com.kunzisoft.keepass.view
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
|||||||
@@ -38,15 +38,15 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
packaging {
|
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 {
|
dependencies {
|
||||||
// Crypto
|
// 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"
|
androidTestImplementation "androidx.test:runner:$android_test_version"
|
||||||
testImplementation "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
|
import org.junit.Test
|
||||||
|
|
||||||
class SignatureTest {
|
class SignatureTest {
|
||||||
@@ -154,4 +156,22 @@ class SignatureTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,6 +33,11 @@ android {
|
|||||||
includeAndroidResources = true
|
includeAndroidResources = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
// Bouncy castle bug https://github.com/bcgit/bc-java/issues/1685
|
||||||
|
resources.pickFirsts.add('META-INF/versions/9/OSGI-INF/MANIFEST.MF')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -42,7 +47,7 @@ dependencies {
|
|||||||
implementation 'commons-io:commons-io:2.8.0'
|
implementation 'commons-io:commons-io:2.8.0'
|
||||||
implementation 'commons-codec:commons-codec:1.15'
|
implementation 'commons-codec:commons-codec:1.15'
|
||||||
|
|
||||||
implementation project(path: ':crypto')
|
api project(path: ':crypto')
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
androidTestImplementation "androidx.test:runner:$android_test_version"
|
androidTestImplementation "androidx.test:runner:$android_test_version"
|
||||||
|
|||||||
Reference in New Issue
Block a user