Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import kotlinx.io.bytestring.ByteString
import org.multipaz.crypto.AsymmetricKey
import org.multipaz.rpc.backend.BackendEnvironment
import org.multipaz.server.enrollment.ServerIdentity
import org.multipaz.server.enrollment.getServerIdentity
import org.multipaz.server.common.persistentServerKey
import org.multipaz.server.enrollment.getServerIdentityCertified

/**
* Various keys used by the Cloud Secure Area.
Expand All @@ -27,8 +27,8 @@ data class KeyMaterial(
fun create(backendEnvironment: Deferred<BackendEnvironment>): Deferred<KeyMaterial> {
return CoroutineScope(Dispatchers.Default).async {
withContext(backendEnvironment.await()) {
val attestationSigningKey = getServerIdentity(ServerIdentity.KEY_ATTESTATION)
val bindingKey = getServerIdentity(ServerIdentity.CLOUD_SECURE_AREA_BINDING)
val attestationSigningKey = getServerIdentityCertified(ServerIdentity.KEY_ATTESTATION)
val bindingKey = getServerIdentityCertified(ServerIdentity.CLOUD_SECURE_AREA_BINDING)
KeyMaterial(
attestationKey = attestationSigningKey,
cloudBindingKey = bindingKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import org.multipaz.openid4vci.request.wellKnownOpenidCredentialIssuer
import org.multipaz.openid4vci.util.CredentialId
import org.multipaz.provisioning.CredentialFormat
import org.multipaz.server.enrollment.ServerIdentity
import org.multipaz.server.enrollment.getServerIdentity
import org.multipaz.rpc.backend.BackendEnvironment
import org.multipaz.server.enrollment.getServerIdentityCertified

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

/**
* Initializes the factory and ensures that all the necessary resources are loaded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,19 @@ class EnrollmentImpl: Enrollment, RpcAuthInspector by serverAuth {
private class ServerIdentityRecord(
// Lazy deferred seems exotic, but that's what's needed here. We do not want to launch
// enrollment until ServerIdentityRecord is created and registered.
var signingKeyDeferred: Lazy<Deferred<AsymmetricKey.X509Certified>>,
var signingKeyDeferred: Lazy<Deferred<AsymmetricKey>>,
val requestId: String? = null,
val expiration: Instant? = null,
val responseChannel: Channel<AsymmetricKey.X509Certified>? = null
) {
companion object {
fun fromKey(key: AsymmetricKey): ServerIdentityRecord {
val cert = (key as AsymmetricKey.X509Certified).certChain.certificates.first()
return ServerIdentityRecord(
fun fromKey(key: AsymmetricKey): ServerIdentityRecord =
ServerIdentityRecord(
signingKeyDeferred = Eager(CompletableDeferred(key)),
expiration = cert.validityNotAfter - MIN_VALIDITY_DURATION
expiration = (key as? AsymmetricKey.X509Certified)
?.let { it.certChain.certificates.first().validityNotAfter - MIN_VALIDITY_DURATION }
?: Instant.DISTANT_FUTURE
)
}
}
}

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

private fun isValid(
cert: X509Cert,
cert: X509Cert?,
identity: ServerIdentity,
configuration: Configuration
): Boolean {
if (identity != ServerIdentity.VERIFIER) {
return true
}
if (cert == null) {
Logger.w(TAG, "Reader key must have certificate chain")
return false
}
// check that the certificate satisfies the requirements
if (!cert.keyUsage.contains(X509KeyUsage.DIGITAL_SIGNATURE)) {
Logger.w(TAG, "Reader certificate key usage is wrong")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,16 @@ enum class ServerIdentity(
* The private key alias and the certificate chain are then stored in the database, so they can
* be used in the future.
*/
suspend fun getServerIdentity(serverIdentity: ServerIdentity): AsymmetricKey.X509Certified =
suspend fun getServerIdentity(serverIdentity: ServerIdentity): AsymmetricKey =
EnrollmentImpl.getServerIdentity(serverIdentity).await()

/**
* Same as [getServerIdentity], but the returned key is required to have associated certificate
* chain.
*/
suspend fun getServerIdentityCertified(serverIdentity: ServerIdentity): AsymmetricKey.X509Certified =
getServerIdentity(serverIdentity) as AsymmetricKey.X509Certified

private class CachedIdentity(val signingKey: AsymmetricKey)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ import org.multipaz.sdjwt.SdJwtKb
import org.multipaz.server.common.baseUrl
import org.multipaz.server.common.getBaseUrl
import org.multipaz.server.enrollment.ServerIdentity
import org.multipaz.server.enrollment.getServerIdentity
import org.multipaz.server.enrollment.getServerIdentityCertified
import org.multipaz.storage.StorageTableSpec
import org.multipaz.storage.ephemeral.EphemeralStorage
import org.multipaz.trustmanagement.TrustManager
Expand Down Expand Up @@ -358,7 +358,7 @@ private suspend fun clientId(): String {
}

private suspend fun getReaderIdentity(): AsymmetricKey.X509Certified =
getServerIdentity(ServerIdentity.VERIFIER)
getServerIdentityCertified(ServerIdentity.VERIFIER)

private suspend fun handleGetAvailableRequests(
call: ApplicationCall,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import org.multipaz.securearea.SecureAreaRepository
import org.multipaz.server.common.getBaseUrl
import org.multipaz.server.common.getDomain
import org.multipaz.server.enrollment.ServerIdentity
import org.multipaz.server.enrollment.getServerIdentity
import org.multipaz.server.enrollment.getServerIdentityCertified
import org.multipaz.trustmanagement.TrustManagerInterface
import org.multipaz.util.Logger
import org.multipaz.verification.PresentmentRecord
Expand Down Expand Up @@ -414,7 +414,7 @@ private suspend fun getVerifierIdentityMap(): Map<String, VerifierIdentity> =
}
}
put("default", VerifierIdentity(
key = getServerIdentity(ServerIdentity.VERIFIER),
key = getServerIdentityCertified(ServerIdentity.VERIFIER),
clientId = getClientId()
))
}.also { verifierIdentities = it }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,12 @@ suspend fun validateJwt(
val kid = header["kid"]?.jsonPrimitive?.content
?: throw InvalidRequestException(
"$jwtName: either 'iss' and 'kid' or 'x5c' must be specified")
caPublicKey("$issuer#$kid", caName)
if (issuer == null || issuer == kid) {
// self-issued
caPublicKey(kid, caName)
} else {
caPublicKey("$issuer#$kid", caName)
}
}
}

Expand Down
Loading