Skip to content

Commit fab70c0

Browse files
authored
Enable usage of JWK keys in config (instead of X509-certified) where possible. (#1820)
Fixes #1817 Signed-off-by: Peter Sorotokin <sorotokin@gmail.com>
1 parent c29d65e commit fab70c0

7 files changed

Lines changed: 38 additions & 22 deletions

File tree

multipaz-csa-server/src/main/java/org/multipaz/csa/server/KeyMaterial.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import kotlinx.io.bytestring.ByteString
99
import org.multipaz.crypto.AsymmetricKey
1010
import org.multipaz.rpc.backend.BackendEnvironment
1111
import org.multipaz.server.enrollment.ServerIdentity
12-
import org.multipaz.server.enrollment.getServerIdentity
1312
import org.multipaz.server.common.persistentServerKey
13+
import org.multipaz.server.enrollment.getServerIdentityCertified
1414

1515
/**
1616
* Various keys used by the Cloud Secure Area.
@@ -27,8 +27,8 @@ data class KeyMaterial(
2727
fun create(backendEnvironment: Deferred<BackendEnvironment>): Deferred<KeyMaterial> {
2828
return CoroutineScope(Dispatchers.Default).async {
2929
withContext(backendEnvironment.await()) {
30-
val attestationSigningKey = getServerIdentity(ServerIdentity.KEY_ATTESTATION)
31-
val bindingKey = getServerIdentity(ServerIdentity.CLOUD_SECURE_AREA_BINDING)
30+
val attestationSigningKey = getServerIdentityCertified(ServerIdentity.KEY_ATTESTATION)
31+
val bindingKey = getServerIdentityCertified(ServerIdentity.CLOUD_SECURE_AREA_BINDING)
3232
KeyMaterial(
3333
attestationKey = attestationSigningKey,
3434
cloudBindingKey = bindingKey,

multipaz-openid4vci/src/main/java/org/multipaz/openid4vci/credential/CredentialFactory.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import org.multipaz.openid4vci.request.wellKnownOpenidCredentialIssuer
88
import org.multipaz.openid4vci.util.CredentialId
99
import org.multipaz.provisioning.CredentialFormat
1010
import org.multipaz.server.enrollment.ServerIdentity
11-
import org.multipaz.server.enrollment.getServerIdentity
1211
import org.multipaz.rpc.backend.BackendEnvironment
12+
import org.multipaz.server.enrollment.getServerIdentityCertified
1313

1414
/**
1515
* Factory for credentials of a particular type.
@@ -58,8 +58,8 @@ interface CredentialFactory {
5858
* Only X509-certified keys are supported
5959
*/
6060
suspend fun getSigningKey(): AsymmetricKey.X509Certified =
61-
getServerIdentity(ServerIdentity.CREDENTIAL_SIGNING)
62-
// the key that is used to sign the credential
61+
// the key that is used to sign the credential
62+
getServerIdentityCertified(ServerIdentity.CREDENTIAL_SIGNING)
6363

6464
/**
6565
* Initializes the factory and ensures that all the necessary resources are loaded

multipaz-server/src/main/java/org/multipaz/server/enrollment/EnrollmentImpl.kt

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,19 +158,19 @@ class EnrollmentImpl: Enrollment, RpcAuthInspector by serverAuth {
158158
private class ServerIdentityRecord(
159159
// Lazy deferred seems exotic, but that's what's needed here. We do not want to launch
160160
// enrollment until ServerIdentityRecord is created and registered.
161-
var signingKeyDeferred: Lazy<Deferred<AsymmetricKey.X509Certified>>,
161+
var signingKeyDeferred: Lazy<Deferred<AsymmetricKey>>,
162162
val requestId: String? = null,
163163
val expiration: Instant? = null,
164164
val responseChannel: Channel<AsymmetricKey.X509Certified>? = null
165165
) {
166166
companion object {
167-
fun fromKey(key: AsymmetricKey): ServerIdentityRecord {
168-
val cert = (key as AsymmetricKey.X509Certified).certChain.certificates.first()
169-
return ServerIdentityRecord(
167+
fun fromKey(key: AsymmetricKey): ServerIdentityRecord =
168+
ServerIdentityRecord(
170169
signingKeyDeferred = Eager(CompletableDeferred(key)),
171-
expiration = cert.validityNotAfter - MIN_VALIDITY_DURATION
170+
expiration = (key as? AsymmetricKey.X509Certified)
171+
?.let { it.certChain.certificates.first().validityNotAfter - MIN_VALIDITY_DURATION }
172+
?: Instant.DISTANT_FUTURE
172173
)
173-
}
174174
}
175175
}
176176

@@ -200,7 +200,7 @@ class EnrollmentImpl: Enrollment, RpcAuthInspector by serverAuth {
200200
*/
201201
suspend fun getServerIdentity(
202202
serverIdentity: ServerIdentity,
203-
): Deferred<AsymmetricKey.X509Certified> {
203+
): Deferred<AsymmetricKey> {
204204
val record = enrollmentsMap[serverIdentity]
205205
val validRecord = if (record != null &&
206206
(record.expiration == null || record.expiration > Clock.System.now())) {
@@ -234,9 +234,9 @@ class EnrollmentImpl: Enrollment, RpcAuthInspector by serverAuth {
234234
Json.parseToJsonElement(it).jsonObject[keyName]?.let { keyJson ->
235235
val secureAreaRepository =
236236
backendEnvironment.getInterface(SecureAreaRepository::class)
237-
val loadedKey = AsymmetricKey.parse(keyJson, secureAreaRepository) as AsymmetricKey.X509Certified
237+
val loadedKey = AsymmetricKey.parse(keyJson, secureAreaRepository)
238238
return ServerIdentityRecord.fromKey(loadedKey).also {
239-
val cert = loadedKey.certChain.certificates.first()
239+
val cert = (loadedKey as? AsymmetricKey.X509Certified)?.certChain?.certificates?.first()
240240
// If configuration is wrong, it has to be re-configured correctly
241241
if(!isValid(cert, serverIdentity, configuration)) {
242242
val message = "Configuration error: certificate for 'server_identities.${serverIdentity.jsonName}' is not generated correctly"
@@ -346,13 +346,17 @@ class EnrollmentImpl: Enrollment, RpcAuthInspector by serverAuth {
346346
}
347347

348348
private fun isValid(
349-
cert: X509Cert,
349+
cert: X509Cert?,
350350
identity: ServerIdentity,
351351
configuration: Configuration
352352
): Boolean {
353353
if (identity != ServerIdentity.VERIFIER) {
354354
return true
355355
}
356+
if (cert == null) {
357+
Logger.w(TAG, "Reader key must have certificate chain")
358+
return false
359+
}
356360
// check that the certificate satisfies the requirements
357361
if (!cert.keyUsage.contains(X509KeyUsage.DIGITAL_SIGNATURE)) {
358362
Logger.w(TAG, "Reader certificate key usage is wrong")

multipaz-server/src/main/java/org/multipaz/server/enrollment/ServerIdentity.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,16 @@ enum class ServerIdentity(
172172
* The private key alias and the certificate chain are then stored in the database, so they can
173173
* be used in the future.
174174
*/
175-
suspend fun getServerIdentity(serverIdentity: ServerIdentity): AsymmetricKey.X509Certified =
175+
suspend fun getServerIdentity(serverIdentity: ServerIdentity): AsymmetricKey =
176176
EnrollmentImpl.getServerIdentity(serverIdentity).await()
177177

178+
/**
179+
* Same as [getServerIdentity], but the returned key is required to have associated certificate
180+
* chain.
181+
*/
182+
suspend fun getServerIdentityCertified(serverIdentity: ServerIdentity): AsymmetricKey.X509Certified =
183+
getServerIdentity(serverIdentity) as AsymmetricKey.X509Certified
184+
178185
private class CachedIdentity(val signingKey: AsymmetricKey)
179186

180187
/**

multipaz-verifier-server/src/main/java/org/multipaz/verifier/request/verifier.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ import org.multipaz.sdjwt.SdJwtKb
8383
import org.multipaz.server.common.baseUrl
8484
import org.multipaz.server.common.getBaseUrl
8585
import org.multipaz.server.enrollment.ServerIdentity
86-
import org.multipaz.server.enrollment.getServerIdentity
86+
import org.multipaz.server.enrollment.getServerIdentityCertified
8787
import org.multipaz.storage.StorageTableSpec
8888
import org.multipaz.storage.ephemeral.EphemeralStorage
8989
import org.multipaz.trustmanagement.TrustManager
@@ -358,7 +358,7 @@ private suspend fun clientId(): String {
358358
}
359359

360360
private suspend fun getReaderIdentity(): AsymmetricKey.X509Certified =
361-
getServerIdentity(ServerIdentity.VERIFIER)
361+
getServerIdentityCertified(ServerIdentity.VERIFIER)
362362

363363
private suspend fun handleGetAvailableRequests(
364364
call: ApplicationCall,

multipaz-verifier/src/main/java/org/multipaz/verifier/request/verifyCredentials.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import org.multipaz.securearea.SecureAreaRepository
4545
import org.multipaz.server.common.getBaseUrl
4646
import org.multipaz.server.common.getDomain
4747
import org.multipaz.server.enrollment.ServerIdentity
48-
import org.multipaz.server.enrollment.getServerIdentity
48+
import org.multipaz.server.enrollment.getServerIdentityCertified
4949
import org.multipaz.trustmanagement.TrustManagerInterface
5050
import org.multipaz.util.Logger
5151
import org.multipaz.verification.PresentmentRecord
@@ -414,7 +414,7 @@ private suspend fun getVerifierIdentityMap(): Map<String, VerifierIdentity> =
414414
}
415415
}
416416
put("default", VerifierIdentity(
417-
key = getServerIdentity(ServerIdentity.VERIFIER),
417+
key = getServerIdentityCertified(ServerIdentity.VERIFIER),
418418
clientId = getClientId()
419419
))
420420
}.also { verifierIdentities = it }

multipaz/src/commonMain/kotlin/org/multipaz/webtoken/validateJwt.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,12 @@ suspend fun validateJwt(
189189
val kid = header["kid"]?.jsonPrimitive?.content
190190
?: throw InvalidRequestException(
191191
"$jwtName: either 'iss' and 'kid' or 'x5c' must be specified")
192-
caPublicKey("$issuer#$kid", caName)
192+
if (issuer == null || issuer == kid) {
193+
// self-issued
194+
caPublicKey(kid, caName)
195+
} else {
196+
caPublicKey("$issuer#$kid", caName)
197+
}
193198
}
194199
}
195200

0 commit comments

Comments
 (0)