Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Did jwk creation and resolution #8

Merged
merged 4 commits into from
Jan 4, 2024
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
6 changes: 6 additions & 0 deletions Sources/tbDEX/Dids/Did.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

protocol Did {
var uri: String { get }
var keyManager: KeyManager { get }
}
286 changes: 286 additions & 0 deletions Sources/tbDEX/Dids/DidDocument.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
import Foundation

/// Decentralized Identifier (DID) Document
///
/// A set of data describing the DID subject including mechanisms such as:
/// * cryptographic public keys - used to authenticate itself and prove association
/// with the DID
/// * services - means of communicating or interacting with the DID subject or associated
/// entities via one or more service endpoints. Examples include discovery services, agent
/// services, social networking services, file storage services, and verifiable credential
/// repository services.
///
/// A DID Document can be retrieved by _resolving_ a DID URI
struct DidDocument: Codable {

let context: String?

/// The DID URI for a particular DID subject is expressed using the id property in the DID document.
let id: String

/// A DID subject can have multiple identifiers for different purposes, or at
/// different times. The assertion that two or more DIDs (or other types of URI)
/// refer to the same DID subject can be made using the alsoKnownAs property.
var alsoKnownAs: [String]?

/// A DID controller is an entity that is authorized to make changes to a
/// DID document. The process of authorizing a DID controller is defined
/// by the DID method.
var controller: DidController?

/// Cryptographic public keys, which can be used to authenticate or authorize
/// interactions with the DID subject or associated parties.
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#verification-methods)
var verificationMethod: [DidVerificationMethod]?

/// Services are used in DID documents to express ways of communicating with
/// the DID subject or associated entities.
/// A service can be any type of service the DID subject wants to advertise.
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#services)
var service: [DidService]?

/// The assertionMethod verification relationship is used to specify how the
/// DID subject is expected to express claims, such as for the purposes of
/// issuing a Verifiable Credential
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#assertion)
var assertionMethod: [String]?

/// The authentication verification relationship is used to specify how the
/// DID subject is expected to be authenticated, for purposes such as logging
/// into a website or engaging in any sort of challenge-response protocol.
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#authentication)
var authentication: [String]?

/// The keyAgreement verification relationship is used to specify how an
/// entity can generate encryption material in order to transmit confidential
/// information intended for the DID subject, such as for the purposes of
/// establishing a secure communication channel with the recipient
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#key-agreement)
var keyAgreement: [String]?

/// The capabilityDelegation verification relationship is used to specify a
/// mechanism that might be used by the DID subject to delegate a
/// cryptographic capability to another party, such as delegating the
/// authority to access a specific HTTP API to a subordinate.
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#capability-delegation)
var capabilityDelegation: [String]?

/// The capabilityInvocation verification relationship is used to specify a
/// verification method that might be used by the DID subject to invoke a
/// cryptographic capability, such as the authorization to update the
/// DID Document
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#capability-invocation)
var capabilityInvocation: [String]?

init(
context: String? = nil,
id: String,
alsoKnownAs: [String]? = nil,
controller: DidController? = nil,
verificationMethod: [DidVerificationMethod]? = nil,
service: [DidService]? = nil,
assertionMethod: [String]? = nil,
authentication: [String]? = nil,
keyAgreement: [String]? = nil,
capabilityDelegation: [String]? = nil,
capabilityInvocation: [String]? = nil
) {
self.context = context
self.id = id
self.alsoKnownAs = alsoKnownAs
self.controller = controller
self.verificationMethod = verificationMethod
self.service = service
self.assertionMethod = assertionMethod
self.authentication = authentication
self.keyAgreement = keyAgreement
self.capabilityDelegation = capabilityDelegation
self.capabilityInvocation = capabilityInvocation
}

enum CodingKeys: String, CodingKey {
case context = "@context"
case id
case alsoKnownAs
case controller
case verificationMethod
case service
case assertionMethod
case authentication
case keyAgreement
case capabilityDelegation
case capabilityInvocation
}

/// Contains metadata about the DID document contained in the didDocument
/// property. This metadata typically does not change between invocations of
/// the resolve and resolveRepresentation functions unless the DID document
/// changes, as it represents metadata about the DID document.
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#dfn-diddocumentmetadata)
struct Metadata: Codable {

/// Timestamp of the Create operation. The value of the property MUST be a
/// string formatted as an XML Datetime normalized to UTC 00:00:00 and
/// without sub-second decimal precision. For example: 2020-12-20T19:17:47Z.
let created: String?

/// Timestamp of the last Update operation for the document version which was
/// resolved. The value of the property MUST follow the same formatting rules
/// as the created property. The updated property is omitted if an Update
/// operation has never been performed on the DID document. If an updated
/// property exists, it can be the same value as the created property
/// when the difference between the two timestamps is less than one second.
let updated: String?

/// If a DID has been deactivated, DID document metadata MUST include this
/// property with the boolean value true. If a DID has not been deactivated,
/// this property is OPTIONAL, but if included, MUST have the boolean value
/// false.
let deactivated: Bool?

/// Indicates the version of the last Update operation for the document version
/// which was resolved.
let versionId: String?

/// Indicates the timestamp of the next Update operation. The value of the
/// property MUST follow the same formatting rules as the created property.
let nextUpdate: String?

/// If the resolved document version is not the latest version of the document.
/// It indicates the timestamp of the next Update operation. The value of the
/// property MUST follow the same formatting rules as the created property.
let nextVersionId: String?

/// A DID method can define different forms of a DID that are logically
/// equivalent. An example is when a DID takes one form prior to registration
/// in a verifiable data registry and another form after such registration.
/// In this case, the DID method specification might need to express one or
/// more DIDs that are logically equivalent to the resolved DID as a property
/// of the DID document. This is the purpose of the equivalentId property.
let equivalentId: String?

/// The canonicalId property is identical to the equivalentId property except:
/// * It is associated with a single value rather than a set
/// * The DID is defined to be the canonical ID for the DID subject within
/// the scope of the containing DID document.
let canonicalId: String?

internal init(
created: String? = nil,
updated: String? = nil,
deactivated: Bool? = nil,
versionId: String? = nil,
nextUpdate: String? = nil,
nextVersionId: String? = nil,
equivalentId: String? = nil,
canonicalId: String? = nil
) {
self.created = created
self.updated = updated
self.deactivated = deactivated
self.versionId = versionId
self.nextUpdate = nextUpdate
self.nextVersionId = nextVersionId
self.equivalentId = equivalentId
self.canonicalId = canonicalId
}
}
}

/// DID Controller
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#did-controller)
/// This is necessary, as the controller can be either a String, or a set of strings.
/// Swift does not allow multiple types, and must handle both cases when encoding/decoding.
struct DidController: Codable {
var value: Either<String, [String]>

init(_ value: Either<String, [String]>) {
self.value = value
}

enum CodingKeys: CodingKey {
case value
}

enum Either<A, B> {
case left(A)
case right(B)
}

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let singleValue = try? container.decode(String.self) {
self.value = .left(singleValue)
} else if let arrayValue = try? container.decode([String].self) {
self.value = .right(arrayValue)
} else {
throw DecodingError.typeMismatch(
DidController.self,
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected either String or [String]"
)
)
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch value {
case .left(let singleValue):
try container.encode(singleValue)
case .right(let arrayValue):
try container.encode(arrayValue)
}
}
}

/// A DID document can express verification methods, such as cryptographic
/// public keys, which can be used to authenticate or authorize interactions
/// with the DID subject or associated parties. For example,
/// a cryptographic public key can be used as a verification method with
/// respect to a digital signature; in such usage, it verifies that the
/// signer could use the associated cryptographic private key
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#verification-methods)
struct DidVerificationMethod: Codable {
let id: String
let type: String
let controller: String
let publicKeyJwk: Jwk?
let publicKeyMultibase: String?

init(
id: String,
type: String,
controller: String,
publicKeyJwk: Jwk? = nil,
publicKeyMultibase: String? = nil
) {
self.id = id
self.type = type
self.controller = controller
self.publicKeyJwk = publicKeyJwk
self.publicKeyMultibase = publicKeyMultibase
}
}

/// Services are used in DID documents to express ways of communicating with
/// the DID subject or associated entities.
/// A service can be any type of service the DID subject wants to advertise.
///
/// [Specification Reference](https://www.w3.org/TR/did-core/#services)
struct DidService: Codable {
let id: String
let type: String
let serviceEndpoint: String
}
63 changes: 63 additions & 0 deletions Sources/tbDEX/Dids/DidJwk.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Foundation

struct DidJwk: Did {

struct Options {
let algorithm: Jwk.Algorithm
let curve: Jwk.Curve
}

let uri: String
let keyManager: KeyManager

init(keyManager: KeyManager, options: Options) throws {
let keyAlias = try keyManager.generatePrivateKey(algorithm: options.algorithm, curve: options.curve)
let publicKey = try keyManager.getPublicKey(keyAlias: keyAlias)
let publicKeyBase64Url = try JSONEncoder().encode(publicKey).base64UrlEncodedString()

self.uri = "did:jwk:\(publicKeyBase64Url)"
self.keyManager = keyManager
}

/// Resolves a `did:jwk` URI into a `DidResolution.Result`
/// - Parameter didUri: The DID URI to resolve
/// - Returns: `DidResolution.Result` containing the resolved DID Document.
static func resolve(didUri: String) -> DidResolution.Result {
let parsedDid: ParsedDid
do {
parsedDid = try ParsedDid(uri: didUri)
} catch {
return DidResolution.Result.invalidDid()
}

guard parsedDid.method == "jwk" else {
return DidResolution.Result.invalidDid()
}

let jwk: Jwk

do {
jwk = try JSONDecoder().decode(Jwk.self, from: try parsedDid.id.decodeBase64Url())
} catch {
return DidResolution.Result.invalidDid()
}

let verifiationMethod = DidVerificationMethod(
id: "\(didUri)#0",
type: "JsonWebKey2020",
controller: didUri,
publicKeyJwk: jwk
)

let didDocument = DidDocument(
id: didUri,
verificationMethod: [verifiationMethod],
assertionMethod: [verifiationMethod.id],
authentication: [verifiationMethod.id],
capabilityDelegation: [verifiationMethod.id],
capabilityInvocation: [verifiationMethod.id]
)

return DidResolution.Result(didDocument: didDocument)
}
}
Loading