Skip to content

Commit

Permalink
fix use Concat instead of DER for signature in CWT proof signer; add …
Browse files Browse the repository at this point in the history
…extensive logging for DefaultOpenId4VciManager
  • Loading branch information
vkanellopoulos committed Jun 10, 2024
1 parent 63f34f6 commit 3195c91
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 77 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ espresso-contrib = "3.5.1"
espresso-core = "3.5.1"
eudi-document-manager = "0.3.0-SNAPSHOT"
eudi-iso18013-data-transfer = "0.2.0-SNAPSHOT"
eudi-lib-jvm-openid4vci-kt = "0.3.0-SNAPSHOT"
eudi-lib-jvm-openid4vci-kt = "0.3.0"
eudi-lib-jvm-siop-openid4vp-kt = "0.4.2"
gradle-plugin = "7.4.0"
identity-credential = "20231002"
Expand Down
6 changes: 6 additions & 0 deletions wallet-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ android {
}
}

testOptions {
unitTests.all {
useJUnitPlatform()
}
}

// publishing {
// singleVariant('release') {
// withSourcesJar()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ internal class CWTProofSigner(
*/
fun sign(signingInput: ByteArray): ByteArray {
return doSign(issuanceRequest, signingInput, supportedProofAlgorithm.signAlgorithmName)
.derToConcat(supportedProofAlgorithm)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_VIEW
import android.net.Uri
import android.util.Log
import com.nimbusds.jose.jwk.Curve
import eu.europa.ec.eudi.openid4vci.*
import eu.europa.ec.eudi.wallet.document.AddDocumentResult
Expand All @@ -34,6 +35,7 @@ import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.C
import eu.europa.ec.eudi.wallet.issue.openid4vci.IssueEvent.Companion.failure
import eu.europa.ec.eudi.wallet.issue.openid4vci.ProofSigner.UserAuthStatus
import kotlinx.coroutines.*
import org.bouncycastle.util.encoders.Hex
import java.net.URI
import java.util.*
import java.util.concurrent.Executor
Expand Down Expand Up @@ -92,6 +94,7 @@ internal class DefaultOpenId4VciManager(
doIssueDocumentByOffer(offer, config, listener)

} catch (e: Throwable) {
Log.e(TAG, "issueDocumentByDocType failed", e)
listener(failure(e))
coroutineScope.cancel("issueDocumentByDocType failed", e)
}
Expand All @@ -109,6 +112,7 @@ internal class DefaultOpenId4VciManager(
try {
doIssueDocumentByOffer(offer, config, listener)
} catch (e: Throwable) {
Log.e(TAG, "issueDocumentByOffer failed", e)
listener(failure(e))
coroutineScope.cancel("issueDocumentByOffer failed", e)
}
Expand All @@ -125,18 +129,20 @@ internal class DefaultOpenId4VciManager(
clearStateThen {
launch(onIssueEvent.wrap(executor)) { coroutineScope, listener ->
try {
val offer = offerUriCache[offerUri]
?: CredentialOfferRequestResolver().resolve(offerUri).getOrThrow()
.let {
DefaultOffer(
it, Compose(
MsoMdocFormatFilter,
ProofTypeFilter(config.proofTypes)
)
val offer = offerUriCache[offerUri].also {
Log.d(TAG, "OfferUri $offerUri cache hit")
} ?: CredentialOfferRequestResolver().resolve(offerUri).getOrThrow()
.let {
DefaultOffer(
it, Compose(
MsoMdocFormatFilter,
ProofTypeFilter(config.proofTypes)
)
}
)
}.also { offerUriCache[offerUri] = it }
doIssueDocumentByOffer(offer, config, listener)
} catch (e: Throwable) {
Log.e(TAG, "issueDocumentByOfferUri failed", e)
listener(failure(e))
coroutineScope.cancel("issueDocumentByOffer failed", e)
}
Expand All @@ -155,10 +161,12 @@ internal class DefaultOpenId4VciManager(
val offer =
DefaultOffer(credentialOffer, Compose(MsoMdocFormatFilter, ProofTypeFilter(config.proofTypes)))
offerUriCache[offerUri] = offer
Log.d(TAG, "OfferUri $offerUri resolved")
callback(OfferResult.Success(offer))
coroutineScope.cancel("resolveDocumentOffer succeeded")
} catch (e: Throwable) {
offerUriCache.remove(offerUri)
Log.e(TAG, "OfferUri $offerUri resolution failed", e)
callback(OfferResult.Failure(e))
coroutineScope.cancel("resolveDocumentOffer failed", e)
}
Expand Down Expand Up @@ -209,6 +217,9 @@ internal class DefaultOpenId4VciManager(
val issuanceRequest = documentManager
.createIssuanceRequest(item, config.useStrongBoxIfSupported)
.getOrThrow()

Log.d(TAG, "Issuing document: ${issuanceRequest.documentId} for ${issuanceRequest.docType}")

doIssueCredential(
authorizedRequest,
item.configurationIdentifier,
Expand Down Expand Up @@ -302,15 +313,19 @@ internal class DefaultOpenId4VciManager(
addedDocuments: MutableSet<DocumentId>,
onEvent: OpenId4VciManager.OnResult<IssueEvent>
) {
Log.d(TAG, "doRequestSingleNoProof for ${issuanceRequest.documentId}")
when (val outcome = authRequest.requestSingle(payload).getOrThrow()) {
is SubmittedRequest.InvalidProof -> doRequestSingleWithProof(
authRequest.handleInvalidProof(outcome.cNonce),
payload,
credentialConfiguration,
issuanceRequest,
addedDocuments,
onEvent
)
is SubmittedRequest.InvalidProof -> {
Log.d(TAG, "doRequestSingleNoProof invalid proof")
doRequestSingleWithProof(
authRequest.handleInvalidProof(outcome.cNonce),
payload,
credentialConfiguration,
issuanceRequest,
addedDocuments,
onEvent
)
}

is SubmittedRequest.Failed -> onEvent(IssueEvent.DocumentFailed(issuanceRequest, outcome.error))
is SubmittedRequest.Success -> storeIssuedCredential(
Expand Down Expand Up @@ -341,7 +356,9 @@ internal class DefaultOpenId4VciManager(
addedDocuments: MutableSet<DocumentId>,
onEvent: OpenId4VciManager.OnResult<IssueEvent>
) {
Log.d(TAG, "doRequestSingleWithProof for ${issuanceRequest.documentId}")
val proofSigner = ProofSigner(issuanceRequest, credentialConfiguration, config.proofTypes).getOrThrow()
Log.d(TAG, "doRequestSingleWithProof proofSigner: ${proofSigner::class.java.name}")
try {
when (val outcome = authRequest.requestSingle(payload, proofSigner.popSigner).getOrThrow()) {
is SubmittedRequest.Failed -> onEvent(IssueEvent.DocumentFailed(issuanceRequest, outcome.error))
Expand All @@ -363,8 +380,10 @@ internal class DefaultOpenId4VciManager(
} catch (e: Throwable) {
when (val status = proofSigner.userAuthStatus) {
is UserAuthStatus.Required -> {
Log.d(TAG, "doRequestSingleWithProof userAuthStatus: $status")
val event = object : IssueEvent.DocumentRequiresUserAuth(issuanceRequest, status.cryptoObject) {
override fun resume() {
Log.d(TAG, "doRequestSingleWithProof resume from user auth")
runBlocking {
doRequestSingleWithProof(
authRequest,
Expand All @@ -378,6 +397,7 @@ internal class DefaultOpenId4VciManager(
}

override fun cancel() {
Log.e(TAG, "doRequestSingleWithProof cancel from user auth")
onEvent(IssueEvent.DocumentFailed(issuanceRequest, e.cause ?: e))
}
}
Expand Down Expand Up @@ -412,6 +432,10 @@ internal class DefaultOpenId4VciManager(

is IssuedCredential.Issued -> {
val cbor = Base64.getUrlDecoder().decode(issuedCredential.credential)

Log.d(TAG, "storeIssuedCredential for ${issuanceRequest.documentId}")
Log.d(TAG, "storeIssuedCredential cbor: ${Hex.toHexString(cbor)}")

when (val addResult = documentManager.addDocument(issuanceRequest, cbor)) {
is AddDocumentResult.Failure -> {
documentManager.deleteDocumentById(issuanceRequest.documentId)
Expand All @@ -433,11 +457,49 @@ internal class DefaultOpenId4VciManager(
* @receiver The [OpenId4VciManager.OnResult].
* @return The wrapped [OpenId4VciManager.OnResult].
*/
private fun <R : OpenId4VciManager.OnResult<V>, V> R.wrap(executor: Executor?): OpenId4VciManager.OnResult<V> {
private inline fun <R : OpenId4VciManager.OnResult<V>, reified V> R.wrap(executor: Executor?): OpenId4VciManager.OnResult<V> {
return OpenId4VciManager.OnResult { result: V ->
(executor ?: context.mainExecutor()).execute {
this@wrap.onResult(result)
}
}.logResult()
}

private inline fun <R : OpenId4VciManager.OnResult<V>, reified V> R.logResult(): OpenId4VciManager.OnResult<V> {
return OpenId4VciManager.OnResult { result: V ->
when (result) {
is IssueEvent.DocumentIssued -> Log.d(
TAG,
"${IssueEvent.DocumentIssued::class.java.name} for ${result.documentId}"
)

is IssueEvent.DocumentFailed -> Log.e(
TAG,
IssueEvent.DocumentFailed::class.java.simpleName,
result.cause
)

is IssueEvent.DocumentRequiresUserAuth -> Log.d(
TAG,
IssueEvent.DocumentRequiresUserAuth::class.java.simpleName
)

is IssueEvent.Started -> Log.d(
TAG,
"${IssueEvent.Started::class.java.name} for ${result.total} documents"
)

is IssueEvent.Finished -> Log.d(
TAG,
"${IssueEvent.Finished::class.java.name} for ${result.issuedDocuments}"
)

is IssueEvent.Failure -> Log.e(TAG, IssueEvent.Failure::class.java.simpleName, result.cause)
is OfferResult.Failure -> Log.e(TAG, OfferResult.Failure::class.java.simpleName, result.error)
is OfferResult.Success -> Log.d(TAG, "${OfferResult.Success::class.java.name} for ${result.offer}")
else -> Log.d(TAG, V::class.java.simpleName)
}
this.onResult(result)
}
}

Expand All @@ -461,7 +523,7 @@ internal class DefaultOpenId4VciManager(
}

companion object {
private const val TAG = "DefaultOpenId4VciManage"
internal const val TAG = "DefaultOpenId4VciManage"

/**
* Converts the [OpenId4VciManager.Config] to [OpenId4VCIConfig].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ internal class JWSProofSigner(
)
}
return doSign(issuanceRequest, signingInput, supportedProofAlgorithm.signAlgorithmName).let { signature ->
Base64URL.encode(signature.derToJose(header.algorithm))
Base64URL.encode(signature.derToConcat(supportedProofAlgorithm))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package eu.europa.ec.eudi.wallet.issue.openid4vci

import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.crypto.impl.ECDSA
import eu.europa.ec.eudi.openid4vci.*
import eu.europa.ec.eudi.wallet.document.Algorithm
import eu.europa.ec.eudi.wallet.issue.openid4vci.SupportedProofType.ProofAlgorithm.Cose
Expand Down Expand Up @@ -153,6 +154,7 @@ internal sealed interface SupportedProofType {
* Proof algorithm for the supported proof type.
* @property name the name of the proof algorithm
* @property signAlgorithmName the name of the sign algorithm that it is used in the [eu.europa.ec.eudi.wallet.document.IssuanceRequest.signWithAuthKey]
* @property signatureByteArrayLength the length of the signature byte array to be use when converting the signature from DER to Concat format
* method
*/
sealed interface ProofAlgorithm {
Expand All @@ -161,6 +163,8 @@ internal sealed interface SupportedProofType {
@get:Algorithm
val signAlgorithmName: String

val signatureByteArrayLength: Int

/**
* Proof algorithm for signing JWT with JWS.
* @property algorithm the JWS algorithm
Expand All @@ -170,9 +174,11 @@ internal sealed interface SupportedProofType {
data class Jws(
val algorithm: JWSAlgorithm,
@Algorithm override val signAlgorithmName: String,
override val name: String = algorithm.name
override val name: String = algorithm.name,
override val signatureByteArrayLength: Int = ECDSA.getSignatureByteArrayLength(algorithm)
) : ProofAlgorithm {


/**
* Companion object for [Jws] instances.
* @property ES256 the ES256 proof algorithm
Expand All @@ -194,15 +200,17 @@ internal sealed interface SupportedProofType {
val coseAlgorithm: CoseAlgorithm,
val coseCurve: CoseCurve,
@Algorithm override val signAlgorithmName: String,
override val name: String = "${coseAlgorithm.name()}_${coseCurve.name()}"
override val name: String = "${coseAlgorithm.name()}_${coseCurve.name()}",
override val signatureByteArrayLength: Int
) : ProofAlgorithm {

/**
* Companion object for [Cose] instances.
* @property ES256_P_256 the ES256_P_256 proof algorithm for COSE ES256 with P-256 curve
*/
companion object {
val ES256_P_256 = Cose(CoseAlgorithm.ES256, CoseCurve.P_256, Algorithm.SHA256withECDSA)
val ES256_P_256 =
Cose(CoseAlgorithm.ES256, CoseCurve.P_256, Algorithm.SHA256withECDSA, signatureByteArrayLength = 64)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package eu.europa.ec.eudi.wallet.issue.openid4vci

import android.content.Intent
import android.net.Uri
import android.util.Log
import eu.europa.ec.eudi.wallet.issue.openid4vci.DefaultOpenId4VciManager.Companion.TAG
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CancellationException
import java.io.Closeable
Expand Down Expand Up @@ -50,13 +52,21 @@ internal class SuspendedAuthorization(
* @throws Throwable if the uri is invalid
*/
fun resumeFromUri(uri: Uri) {
Log.d(TAG, "resumeFromUri: $uri")
try {
uri.getQueryParameter("code")?.let { authorizationCode ->
uri.getQueryParameter("state")?.let { serverState ->
continuation.resume(Result.success(Response(authorizationCode, serverState)))
} ?: continuation.resumeWith(Result.failure(IllegalStateException("No server state found")))
} ?: continuation.resumeWith(Result.failure(IllegalStateException("No authorization code found")))
} ?: "No server state found".let { msg ->
Log.e(TAG, "resumeFromUri: msg")
continuation.resumeWith(Result.failure(IllegalStateException(msg)))
}
} ?: "No authorization code found".let { msg ->
Log.e(TAG, "resumeFromUri: msg")
continuation.resumeWith(Result.failure(IllegalStateException(msg)))
}
} catch (e: Throwable) {
Log.e(TAG, "resumeFromUri exception", e)
continuation.resumeWith(Result.failure(e))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ internal val PublicKey.pem: String
wr.toString()
}

/**
* Converts a signature in DER format to a concatenated format.
* @receiver the [ByteArray]
* @param signatureLength the length of the signature
* @return the concatenated signature
*/
@JvmSynthetic
internal fun ByteArray.derToConcat(signatureLength: Int) =
ECDSA.transcodeSignatureToConcat(this, signatureLength)

/**
* Converts a signature in DER format to a concatenated format.
* @receiver the [ByteArray]
* @param algorithm the supported proof algorithm
* @return the concatenated signature
*/
@JvmSynthetic
internal fun ByteArray.derToConcat(algorithm: SupportedProofType.ProofAlgorithm) =
derToConcat(algorithm.signatureByteArrayLength)

/**
* Converts the [ByteArray] to a JOSE signature.
* @receiver the [ByteArray]
Expand All @@ -76,5 +96,7 @@ internal val PublicKey.pem: String
@JvmSynthetic
internal fun ByteArray.derToJose(algorithm: JWSAlgorithm = JWSAlgorithm.ES256): ByteArray {
val len = ECDSA.getSignatureByteArrayLength(algorithm)
return ECDSA.transcodeSignatureToConcat(this, len)
}
return derToConcat(len)
}


Loading

0 comments on commit 3195c91

Please sign in to comment.