Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/draft 14 #98

Merged
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
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ the [EUDI Wallet Reference Implementation project description](https://github.co
## Overview

This is a Swift library, that supports
the [OpenId4VCI (draft 13)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) protocol.
the [OpenId4VCI (draft 14)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) protocol.
In particular, the library focuses on the wallet's role in the protocol to:
- Resolve credential issuer metadata
- Resolve metadata of the authorization server protecting issuance services
Expand Down Expand Up @@ -202,9 +202,9 @@ let payload: IssuanceRequestPayload = .configurationBased(
credentialConfigurationIdentifier: ...
)

let requestOutcome = try await issuer.requestSingle(
let requestOutcome = try await issuer.request(
proofRequest: ...,
bindingKey: ...,
bindingKeys: ..., // SigningKeyProxy array
requestPayload: payload,
responseEncryptionSpecProvider: {
Issuer.createResponseEncryptionSpec($0)
Expand All @@ -229,18 +229,21 @@ Specification defines ([section 6.2](https://openid.github.io/OpenID4VCI/openid-
if `authorization_details` parameter is used in authorization endpoint. Current version of library is not parsing/utilizing this response attribute.

### Credential Request
Current version of the library implements integrations with issuer's [Crednetial Endpoint](https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-wg-draft.html#name-credential-endpoint),
[Batch Crednetial Endpoint](https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-wg-draft.html#name-batch-credential-endpoint) and
Current version of the library implements integrations with issuer's [Crednetial Endpoint](https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-wg-draft.html#name-credential-endpoint) and
[Deferred Crednetial Endpoint](https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-wg-draft.html#name-deferred-credential-endpoin)
endpoints.

**NOTE:** Attribute `credential_identifier` of a credential request (single or batch) is not yet supported.
**NOTE:** Attribute `credential_identifier` of a credential request is not yet supported.

#### Credential Format Profiles
OpenId4VCI specification defines several extension points to accommodate the differences across Credential formats. The current version of the library fully supports **ISO mDL** profile and gives some initial support for **IETF SD-JWT VC** profile.

#### Proof Types
OpenId4VCI specification (draft 12) defines two types of proofs that can be included in a credential issuance request, JWT proof type and CWT proof type. Current version of the library supports only JWT proof types
OpenId4VCI specification (draft 14) defines proofs that can be included in a credential issuance request, JWT proof type in particular. The current version of the library supports JWT proof types.

#### Notes

Examples for all of the above are located in the test target of the library.

## How to contribute

Expand Down
10 changes: 5 additions & 5 deletions Sources/Entities/AccessManagement/AuthorizationDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
import Foundation

public struct AuthorizationType: Codable {
public let type: String
public init(type: String) {
self.type = type
}
public let type: String

public init(type: String) {
self.type = type
}
}

public struct AuthorizationDetail: Codable {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation

public enum AuthorizationDetailsInTokenRequest {
case doNotInclude
case include(filter: (CredentialConfigurationIdentifier) -> Bool)
}

31 changes: 31 additions & 0 deletions Sources/Entities/BatchCredentialIssuance.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation

public struct BatchCredentialIssuance: Codable {
public let batchSize: Int

enum CodingKeys: String, CodingKey {
case batchSize = "batch_size"
}

public init(batchSize: Int) throws {
if batchSize <= 0 {
throw ValidationError.invalidBatchSize(batchSize)
}
self.batchSize = batchSize
}
}
49 changes: 49 additions & 0 deletions Sources/Entities/Credential/Credential.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2023 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import SwiftyJSON

public enum Credential: Codable {
case string(String)
case json(JSON)

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringValue = try? container.decode(String.self) {
self = .string(stringValue)
} else if let jsonObject = try? container.decode(JSON.self) {
self = .json(jsonObject)
} else {
throw DecodingError.typeMismatch(
Credential.self,
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Invalid Credential Type"
)
)
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let value):
try container.encode(value)
case .json(let jsonValue):
try container.encode(jsonValue)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
* limitations under the License.
*/
import Foundation
import SwiftyJSON

public enum IssuedCredential {
public enum IssuedCredential: Codable {
case issued(
format: String,
credential: String,
notificationId: String
format: String?,
credential: Credential,
notificationId: String?,
additionalInfo: JSON?
)
case deferred(transactionId: TransactionId)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,19 @@ public struct CredentialIssuerMetadata: Decodable, Equatable {
public let credentialIssuerIdentifier: CredentialIssuerId
public let authorizationServers: [URL]?
public let credentialEndpoint: CredentialIssuerEndpoint
public let batchCredentialEndpoint: CredentialIssuerEndpoint?
public let deferredCredentialEndpoint: CredentialIssuerEndpoint?
public let notificationEndpoint: CredentialIssuerEndpoint?
public let credentialResponseEncryption: CredentialResponseEncryption
public let credentialsSupported: [CredentialConfigurationIdentifier: CredentialSupported]
public let credentialIdentifiersSupported: Bool?
public let signedMetadata: String?
public let display: [Display]
public let batchCredentialIssuance: BatchCredentialIssuance?

public enum CodingKeys: String, CodingKey {
case credentialIssuerIdentifier = "credential_issuer"
case authorizationServers = "authorization_servers"
case credentialEndpoint = "credential_endpoint"
case batchCredentialEndpoint = "batch_credential_endpoint"
case deferredCredentialEndpoint = "deferred_credential_endpoint"
case notificationEndpoint = "notification_endpoint"
case credentialResponseEncryptionAlgorithmsSupported = "credential_response_encryption_alg_values_supported"
Expand All @@ -45,26 +44,26 @@ public struct CredentialIssuerMetadata: Decodable, Equatable {
case credentialResponseEncryption = "credential_response_encryption"
case signedMetadata = "signed_metadata"
case credentialIdentifiersSupported = "credential_identifiers_supported"
case batchCredentialIssuance = "batch_credential_issuance"
}

public init(
credentialIssuerIdentifier: CredentialIssuerId,
authorizationServers: [URL],
credentialEndpoint: CredentialIssuerEndpoint,
batchCredentialEndpoint: CredentialIssuerEndpoint?,
deferredCredentialEndpoint: CredentialIssuerEndpoint?,
notificationEndpoint: CredentialIssuerEndpoint?,
credentialResponseEncryption: CredentialResponseEncryption = .notRequired,
credentialConfigurationsSupported: [CredentialConfigurationIdentifier: CredentialSupported],
signedMetadata: String?,
display: [Display]?,
credentialIdentifiersSupported: Bool? = nil
credentialIdentifiersSupported: Bool? = nil,
batchCredentialIssuance: BatchCredentialIssuance? = nil
) {
self.credentialIssuerIdentifier = credentialIssuerIdentifier
self.authorizationServers = authorizationServers

self.credentialEndpoint = credentialEndpoint
self.batchCredentialEndpoint = batchCredentialEndpoint
self.deferredCredentialEndpoint = deferredCredentialEndpoint
self.notificationEndpoint = notificationEndpoint

Expand All @@ -75,14 +74,14 @@ public struct CredentialIssuerMetadata: Decodable, Equatable {
self.display = display ?? []

self.credentialIdentifiersSupported = credentialIdentifiersSupported
self.batchCredentialIssuance = batchCredentialIssuance
}

public init(deferredCredentialEndpoint: CredentialIssuerEndpoint?) throws {
try self.init(
credentialIssuerIdentifier: .init(Constants.url),
authorizationServers: [],
credentialEndpoint: .init(string: Constants.url),
batchCredentialEndpoint: nil,
deferredCredentialEndpoint: deferredCredentialEndpoint,
notificationEndpoint: nil,
credentialConfigurationsSupported: [:],
Expand All @@ -102,7 +101,6 @@ public struct CredentialIssuerMetadata: Decodable, Equatable {
authorizationServers = servers ?? [credentialIssuerIdentifier.url]

credentialEndpoint = try container.decode(CredentialIssuerEndpoint.self, forKey: .credentialEndpoint)
batchCredentialEndpoint = try container.decodeIfPresent(CredentialIssuerEndpoint.self, forKey: .batchCredentialEndpoint)
deferredCredentialEndpoint = try container.decodeIfPresent(CredentialIssuerEndpoint.self, forKey: .deferredCredentialEndpoint)
notificationEndpoint = try container.decodeIfPresent(CredentialIssuerEndpoint.self, forKey: .notificationEndpoint)

Expand Down Expand Up @@ -147,6 +145,8 @@ public struct CredentialIssuerMetadata: Decodable, Equatable {
signedMetadata = try? container.decodeIfPresent(String.self, forKey: .signedMetadata)

credentialIdentifiersSupported = try? container.decodeIfPresent(Bool.self, forKey: .credentialIdentifiersSupported) ?? false

batchCredentialIssuance = try? container.decodeIfPresent(BatchCredentialIssuance.self, forKey: .batchCredentialIssuance)
}

public static func == (lhs: CredentialIssuerMetadata, rhs: CredentialIssuerMetadata) -> Bool {
Expand Down
25 changes: 4 additions & 21 deletions Sources/Entities/CredentialIssuer/ProofType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
*/
import Foundation

public enum ProofType: Codable {
public enum ProofType: Decodable {
case jwt
case cwt
case ldpVp
case unsupported

private enum CodingKeys: String, CodingKey {
case rawValue
Expand All @@ -31,38 +31,21 @@ public enum ProofType: Codable {
switch rawValue {
case "JWT", "jwt":
self = .jwt
case "CWT", "cwt":
self = .cwt
case "LDP_VP", "ldp_vp":
self = .ldpVp
default:
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid proof type")
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()

switch self {
case .jwt:
try container.encode("JWT")
case .cwt:
try container.encode("CWT")
case .ldpVp:
try container.encode("LDP_VP")
self = .unsupported
}
}

public init(type: String) throws {
switch type {
case "JWT", "jwt":
self = .jwt
case "CWT", "cwt":
self = .cwt
case "LDP_VP", "ldp_vp":
self = .ldpVp
default:
throw ValidationError.error(reason: "Invalid proof type: \(type)")
self = .unsupported
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public extension CredentialSupported {
func toIssuanceRequest(
requester: IssuanceRequesterType,
claimSet: ClaimSet? = nil,
proof: Proof? = nil,
proofs: [Proof] = [],
credentialIdentifier: CredentialIdentifier? = nil,
responseEncryptionSpecProvider: (_ issuerResponseEncryptionMetadata: CredentialResponseEncryption) -> IssuanceResponseEncryptionSpec?
) throws -> CredentialIssuanceRequest {
Expand Down Expand Up @@ -75,7 +75,7 @@ public extension CredentialSupported {
return try credentialConfiguration.toIssuanceRequest(
responseEncryptionSpec: issuerEncryption.notRequired ? nil : responseEncryptionSpec,
claimSet: claimSet,
proof: proof
proofs: proofs
)

case .sdJwtVc(let credentialConfiguration):
Expand All @@ -102,7 +102,7 @@ public extension CredentialSupported {
return try credentialConfiguration.toIssuanceRequest(
responseEncryptionSpec: issuerEncryption.notRequired ? nil : responseEncryptionSpec,
claimSet: claimSet,
proof: proof
proofs: proofs
)
default:
throw ValidationError.error(reason: "Unsupported profile for issuance request")
Expand Down
3 changes: 0 additions & 3 deletions Sources/Entities/Errors/CredentialIssuanceError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import Foundation
public enum CredentialIssuanceError: Error, LocalizedError {
case pushedAuthorizationRequestFailed(error: String, errorDescription: String?)
case accessTokenRequestFailed(error: String, errorDescription: String?)
case issuerDoesNotSupportBatchIssuance
case responseUnparsable(String)
case invalidIssuanceRequest(String)
case cryptographicSuiteNotSupported(String)
Expand Down Expand Up @@ -47,8 +46,6 @@ public enum CredentialIssuanceError: Error, LocalizedError {
.issuanceRequestFailed(_, let errorDescription),
.invalidProof(_, _, let errorDescription):
return errorDescription
case .issuerDoesNotSupportBatchIssuance:
return "Issuer does not support batch issuance"
case .responseUnparsable(let details):
return "Response is unparsable. Details: \(details)"
case .invalidIssuanceRequest(let details):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ public enum CredentialIssuerMetadataValidationError: Error {
/// - Parameter reason: The reason for the invalidity.
case invalidCredentialEndpoint(reason: String)

/// The batch credential endpoint is invalid.
/// - Parameter reason: The reason for the invalidity.
case invalidBatchCredentialEndpoint(reason: String)

/// The deferred credential endpoint is invalid.
/// - Parameter reason: The reason for the invalidity.
case invalidDeferredCredentialEndpoint(reason: String)
Expand Down
Loading