diff --git a/bindings/wasm/docs/api-reference.md b/bindings/wasm/docs/api-reference.md index 631b6a8194..7e69a84c31 100644 --- a/bindings/wasm/docs/api-reference.md +++ b/bindings/wasm/docs/api-reference.md @@ -37,8 +37,7 @@ It can be placed in an origin's .well-known directory to prove See: https://identity.foundation/.well-known/resources/did-configuration/#did-configuration-resource

Note:

DomainLinkageValidator
@@ -465,7 +464,7 @@ A method-agnostic DID Document. * [._shallowCloneInternal()](#CoreDocument+_shallowCloneInternal) ⇒ [CoreDocument](#CoreDocument) * [._strongCountInternal()](#CoreDocument+_strongCountInternal) ⇒ number * [.toJSON()](#CoreDocument+toJSON) ⇒ any - * [.generateMethod(storage, keyType, alg, fragment, scope)](#CoreDocument+generateMethod) ⇒ Promise.<(string\|null)> + * [.generateMethod(storage, keyType, alg, fragment, scope)](#CoreDocument+generateMethod) ⇒ Promise.<string> * [.purgeMethod(storage, id)](#CoreDocument+purgeMethod) ⇒ Promise.<void> * [.createJws(storage, fragment, payload, options)](#CoreDocument+createJws) ⇒ [Promise.<Jws>](#Jws) * [.createCredentialJwt(storage, fragment, credential, options)](#CoreDocument+createCredentialJwt) ⇒ [Promise.<Jwt>](#Jwt) @@ -834,7 +833,7 @@ Serializes to a plain JS representation. **Kind**: instance method of [CoreDocument](#CoreDocument) -### coreDocument.generateMethod(storage, keyType, alg, fragment, scope) ⇒ Promise.<(string\|null)> +### coreDocument.generateMethod(storage, keyType, alg, fragment, scope) ⇒ Promise.<string> Generate new key material in the given `storage` and insert a new verification method with the corresponding public key material into the DID document. @@ -888,7 +887,7 @@ See [RFC7515 section 3.1](https://www.rfc-editor.org/rfc/rfc7515#section-3.1). ### coreDocument.createCredentialJwt(storage, fragment, credential, options) ⇒ [Promise.<Jwt>](#Jwt) -Produces a JWS where the payload is produced from the given `credential` +Produces a JWT where the payload is produced from the given `credential` in accordance with [VC-JWT version 1.1.](https://w3c.github.io/vc-jwt/#version-1.1). The `kid` in the protected header is the `id` of the method identified by `fragment` and the JWS signature will be @@ -906,6 +905,12 @@ produced by the corresponding private key backed by the `storage` in accordance ### coreDocument.createPresentationJwt(storage, fragment, presentation, signature_options, presentation_options) ⇒ [Promise.<Jwt>](#Jwt) +Produces a JWT where the payload is produced from the given presentation. +in accordance with [VC-JWT version 1.1](https://w3c.github.io/vc-jwt/#version-1.1). + +The `kid` in the protected header is the `id` of the method identified by `fragment` and the JWS signature will be +produced by the corresponding private key backed by the `storage` in accordance with the passed `options`. + **Kind**: instance method of [CoreDocument](#CoreDocument) | Param | Type | @@ -1561,9 +1566,10 @@ It does not imply anything about a potentially present proof property on the pre * [DecodedJwtPresentation](#DecodedJwtPresentation) * [.presentation()](#DecodedJwtPresentation+presentation) ⇒ [JwtPresentation](#JwtPresentation) * [.protectedHeader()](#DecodedJwtPresentation+protectedHeader) ⇒ [JwsHeader](#JwsHeader) - * [.intoCredential()](#DecodedJwtPresentation+intoCredential) ⇒ [JwtPresentation](#JwtPresentation) + * [.intoPresentation()](#DecodedJwtPresentation+intoPresentation) ⇒ [JwtPresentation](#JwtPresentation) * [.expirationDate()](#DecodedJwtPresentation+expirationDate) ⇒ [Timestamp](#Timestamp) \| undefined * [.issuanceDate()](#DecodedJwtPresentation+issuanceDate) ⇒ [Timestamp](#Timestamp) \| undefined + * [.audience()](#DecodedJwtPresentation+audience) ⇒ string \| undefined * [.credentials()](#DecodedJwtPresentation+credentials) ⇒ [Array.<DecodedJwtCredential>](#DecodedJwtCredential) @@ -1576,9 +1582,9 @@ It does not imply anything about a potentially present proof property on the pre Returns a copy of the protected header parsed from the decoded JWS. **Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - + -### decodedJwtPresentation.intoCredential() ⇒ [JwtPresentation](#JwtPresentation) +### decodedJwtPresentation.intoPresentation() ⇒ [JwtPresentation](#JwtPresentation) Consumes the object and returns the decoded presentation. ### Warning @@ -1594,7 +1600,13 @@ The expiration date parsed from the JWT claims. ### decodedJwtPresentation.issuanceDate() ⇒ [Timestamp](#Timestamp) \| undefined -The issuance dated parsed from the JWT claims. +The issuance date parsed from the JWT claims. + +**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) + + +### decodedJwtPresentation.audience() ⇒ string \| undefined +The `aud` property parsed from JWT claims. **Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) @@ -1611,16 +1623,15 @@ It can be placed in an origin's `.well-known` directory to prove linkage between See: Note: -- Only [Linked Data Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#linked-data-proof-format) - is supported. +- Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) **Kind**: global class * [DomainLinkageConfiguration](#DomainLinkageConfiguration) * [new DomainLinkageConfiguration(linked_dids)](#new_DomainLinkageConfiguration_new) * _instance_ - * [.linkedDids()](#DomainLinkageConfiguration+linkedDids) ⇒ [Array.<Credential>](#Credential) - * [.issuers()](#DomainLinkageConfiguration+issuers) ⇒ Array.<string> + * [.linkedDids()](#DomainLinkageConfiguration+linkedDids) ⇒ [Array.<Jwt>](#Jwt) + * [.issuers()](#DomainLinkageConfiguration+issuers) ⇒ [Array.<CoreDID>](#CoreDID) * [.toJSON()](#DomainLinkageConfiguration+toJSON) ⇒ any * [.clone()](#DomainLinkageConfiguration+clone) ⇒ [DomainLinkageConfiguration](#DomainLinkageConfiguration) * _static_ @@ -1634,17 +1645,17 @@ Constructs a new `DomainLinkageConfiguration`. | Param | Type | | --- | --- | -| linked_dids | [Array.<Credential>](#Credential) | +| linked_dids | [Array.<Jwt>](#Jwt) | -### domainLinkageConfiguration.linkedDids() ⇒ [Array.<Credential>](#Credential) +### domainLinkageConfiguration.linkedDids() ⇒ [Array.<Jwt>](#Jwt) List of the Domain Linkage Credentials. **Kind**: instance method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) -### domainLinkageConfiguration.issuers() ⇒ Array.<string> +### domainLinkageConfiguration.issuers() ⇒ [Array.<CoreDID>](#CoreDID) List of the issuers of the Domain Linkage Credentials. **Kind**: instance method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) @@ -1679,20 +1690,32 @@ A validator for a Domain Linkage Configuration and Credentials. **Kind**: global class * [DomainLinkageValidator](#DomainLinkageValidator) - * [.validateLinkage(issuer, configuration, domain, options)](#DomainLinkageValidator.validateLinkage) - * [.validateCredential(issuer, credential, domain, options)](#DomainLinkageValidator.validateCredential) + * [new DomainLinkageValidator(signatureVerifier)](#new_DomainLinkageValidator_new) + * [.validateLinkage(issuer, configuration, domain, options)](#DomainLinkageValidator+validateLinkage) + * [.validateCredential(issuer, credentialJwt, domain, options)](#DomainLinkageValidator+validateCredential) + + + +### new DomainLinkageValidator(signatureVerifier) +Creates a new `DomainLinkageValidator`. If a `signatureVerifier` is provided it will be used when +verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` +algorithm will be used. + + +| Param | Type | +| --- | --- | +| signatureVerifier | IJwsVerifier \| undefined | - + -### DomainLinkageValidator.validateLinkage(issuer, configuration, domain, options) +### domainLinkageValidator.validateLinkage(issuer, configuration, domain, options) Validates the linkage between a domain and a DID. [`DomainLinkageConfiguration`] is validated according to [DID Configuration Resource Verification](https://identity.foundation/.well-known/resources/did-configuration/#did-configuration-resource-verification). Linkage is valid if no error is thrown. # Note: -- Only [Linked Data Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#linked-data-proof-format) - is supported. +- Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) - Only the Credential issued by `issuer` is verified. # Errors @@ -1700,29 +1723,29 @@ Linkage is valid if no error is thrown. - `configuration` includes multiple credentials issued by `issuer`. - Validation of the matched Domain Linkage Credential fails. -**Kind**: static method of [DomainLinkageValidator](#DomainLinkageValidator) +**Kind**: instance method of [DomainLinkageValidator](#DomainLinkageValidator) | Param | Type | | --- | --- | | issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | | configuration | [DomainLinkageConfiguration](#DomainLinkageConfiguration) | | domain | string | -| options | [CredentialValidationOptions](#CredentialValidationOptions) | +| options | [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) | - + -### DomainLinkageValidator.validateCredential(issuer, credential, domain, options) +### domainLinkageValidator.validateCredential(issuer, credentialJwt, domain, options) Validates a [Domain Linkage Credential](https://identity.foundation/.well-known/resources/did-configuration/#domain-linkage-credential). Error will be thrown in case the validation fails. -**Kind**: static method of [DomainLinkageValidator](#DomainLinkageValidator) +**Kind**: instance method of [DomainLinkageValidator](#DomainLinkageValidator) | Param | Type | | --- | --- | | issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| credential | [Credential](#Credential) | +| credentialJwt | [Jwt](#Jwt) | | domain | string | -| options | [CredentialValidationOptions](#CredentialValidationOptions) | +| options | [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) | @@ -2137,7 +2160,7 @@ Deserializes an instance from a JSON object. * [._strongCountInternal()](#IotaDocument+_strongCountInternal) ⇒ number * [.toJSON()](#IotaDocument+toJSON) ⇒ any * [.toCoreDocument()](#IotaDocument+toCoreDocument) ⇒ [CoreDocument](#CoreDocument) - * [.generateMethod(storage, keyType, alg, fragment, scope)](#IotaDocument+generateMethod) ⇒ Promise.<(string\|null)> + * [.generateMethod(storage, keyType, alg, fragment, scope)](#IotaDocument+generateMethod) ⇒ Promise.<string> * [.purgeMethod(storage, id)](#IotaDocument+purgeMethod) ⇒ Promise.<void> * [.createJwt(storage, fragment, payload, options)](#IotaDocument+createJwt) ⇒ [Promise.<Jws>](#Jws) * [.createCredentialJwt(storage, fragment, credential, options)](#IotaDocument+createCredentialJwt) ⇒ [Promise.<Jwt>](#Jwt) @@ -2579,7 +2602,7 @@ Transforms the `IotaDocument` to its `CoreDocument` representation. **Kind**: instance method of [IotaDocument](#IotaDocument) -### iotaDocument.generateMethod(storage, keyType, alg, fragment, scope) ⇒ Promise.<(string\|null)> +### iotaDocument.generateMethod(storage, keyType, alg, fragment, scope) ⇒ Promise.<string> Generate new key material in the given `storage` and insert a new verification method with the corresponding public key material into the DID document. @@ -2651,7 +2674,7 @@ produced by the corresponding private key backed by the `storage` in accordance ### iotaDocument.createPresentationJwt(storage, fragment, presentation, signature_options, presentation_options) ⇒ [Promise.<Jwt>](#Jwt) -Produces a JWT where the payload is produced from the given `presentation` +Produces a JWT where the payload is produced from the given presentation. in accordance with [VC-JWT version 1.1](https://w3c.github.io/vc-jwt/#version-1.1). The `kid` in the protected header is the `id` of the method identified by `fragment` and the JWS signature will be @@ -3684,7 +3707,12 @@ A wrapper around a JSON Web Token (JWK). * [Jwt](#Jwt) * [new Jwt(jwt_string)](#new_Jwt_new) - * [.toString()](#Jwt+toString) ⇒ string + * _instance_ + * [.toString()](#Jwt+toString) ⇒ string + * [.toJSON()](#Jwt+toJSON) ⇒ any + * [.clone()](#Jwt+clone) ⇒ [Jwt](#Jwt) + * _static_ + * [.fromJSON(json)](#Jwt.fromJSON) ⇒ [Jwt](#Jwt) @@ -3702,6 +3730,29 @@ Creates a new `Jwt` from the given string. Returns a clone of the JWT string. **Kind**: instance method of [Jwt](#Jwt) + + +### jwt.toJSON() ⇒ any +Serializes this to a JSON object. + +**Kind**: instance method of [Jwt](#Jwt) + + +### jwt.clone() ⇒ [Jwt](#Jwt) +Deep clones the object. + +**Kind**: instance method of [Jwt](#Jwt) + + +### Jwt.fromJSON(json) ⇒ [Jwt](#Jwt) +Deserializes an instance from a JSON object. + +**Kind**: static method of [Jwt](#Jwt) + +| Param | Type | +| --- | --- | +| json | any | + ## JwtCredentialValidationOptions @@ -3763,7 +3814,7 @@ A type for decoding and validating `Credentials`. **Kind**: global class * [JwtCredentialValidator](#JwtCredentialValidator) - * [new JwtCredentialValidator(signature_verifier)](#new_JwtCredentialValidator_new) + * [new JwtCredentialValidator(signatureVerifier)](#new_JwtCredentialValidator_new) * _instance_ * [.validate(credential_jwt, issuer, options, fail_fast)](#JwtCredentialValidator+validate) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) * [.verifySignature(credential, trustedIssuers, options)](#JwtCredentialValidator+verifySignature) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) @@ -3776,15 +3827,15 @@ A type for decoding and validating `Credentials`. -### new JwtCredentialValidator(signature_verifier) -Creates a new `JwtCredentialValidator`. If a `signature_verifier` is provided it will be used when +### new JwtCredentialValidator(signatureVerifier) +Creates a new `JwtCredentialValidator`. If a `signatureVerifier` is provided it will be used when verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` algorithm will be used. | Param | Type | | --- | --- | -| signature_verifier | IJwsVerifier \| undefined | +| signatureVerifier | IJwsVerifier \| undefined | @@ -3973,7 +4024,7 @@ Returns a copy of the URIs defining the type of the presentation. ### jwtPresentation.verifiableCredential() ⇒ [Array.<Jwt>](#Jwt) -Returns a copy of the [Credential](#Credential)(s) expressing the claims of the presentation. +Returns the JWT credentials expressing the claims of the presentation. **Kind**: instance method of [JwtPresentation](#JwtPresentation) @@ -3997,7 +4048,7 @@ Returns a copy of the terms-of-use specified by the presentation holder ### jwtPresentation.proof() ⇒ Map.<string, any> \| undefined -Returns a copy of the proof property. +Optional proof that can be verified by users in addition to JWS. **Kind**: instance method of [JwtPresentation](#JwtPresentation) @@ -4161,7 +4212,7 @@ Deserializes an instance from a JSON object. * [JwtPresentationValidator](#JwtPresentationValidator) * [new JwtPresentationValidator(signature_verifier)](#new_JwtPresentationValidator_new) * _instance_ - * [.validate(presentation_jwt, holder, issuers, options, fail_fast)](#JwtPresentationValidator+validate) ⇒ [DecodedJwtPresentation](#DecodedJwtPresentation) + * [.validate(presentation_jwt, holder, issuers, validation_options, fail_fast)](#JwtPresentationValidator+validate) ⇒ [DecodedJwtPresentation](#DecodedJwtPresentation) * _static_ * [.checkStructure(presentation)](#JwtPresentationValidator.checkStructure) * [.extractDids(presentation)](#JwtPresentationValidator.extractDids) ⇒ JwtPresentationDids @@ -4180,7 +4231,34 @@ algorithm will be used. -### jwtPresentationValidator.validate(presentation_jwt, holder, issuers, options, fail_fast) ⇒ [DecodedJwtPresentation](#DecodedJwtPresentation) +### jwtPresentationValidator.validate(presentation_jwt, holder, issuers, validation_options, fail_fast) ⇒ [DecodedJwtPresentation](#DecodedJwtPresentation) +Validates a `JwtPresentation`. + +The following properties are validated according to `options`: +- the JWT can be decoded into semantically valid presentation. +- the expiration and issuance date contained in the JWT claims. +- the holder's signature. +- the relationship between the holder and the credential subjects. +- the signatures and some properties of the constituent credentials (see `CredentialValidator`). + +Validation is done with respect to the properties set in `options`. + +# Warning +The lack of an error returned from this method is in of itself not enough to conclude that the presentation can be +trusted. This section contains more information on additional checks that should be carried out before and after +calling this method. + +## The state of the supplied DID Documents. +The caller must ensure that the DID Documents in `holder` and `issuers` are up-to-date. + +## Properties that are not validated + There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as: +`credentialStatus`, `type`, `credentialSchema`, `refreshService`, **and more**. +These should be manually checked after validation, according to your requirements. + +# Errors +An error is returned whenever a validated condition is not satisfied or when decoding fails. + **Kind**: instance method of [JwtPresentationValidator](#JwtPresentationValidator) | Param | Type | @@ -4188,12 +4266,14 @@ algorithm will be used. | presentation_jwt | [Jwt](#Jwt) | | holder | [CoreDocument](#CoreDocument) \| IToCoreDocument | | issuers | Array.<(CoreDocument\|IToCoreDocument)> | -| options | [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) | +| validation_options | [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) | | fail_fast | number | ### JwtPresentationValidator.checkStructure(presentation) +Validates the semantic structure of the `JwtPresentation`. + **Kind**: static method of [JwtPresentationValidator](#JwtPresentationValidator) | Param | Type | @@ -4203,6 +4283,14 @@ algorithm will be used. ### JwtPresentationValidator.extractDids(presentation) ⇒ JwtPresentationDids +Attempt to extract the holder of the presentation and the issuers of the included +credentials. + +# Errors: +* If deserialization/decoding of the presentation or any of the constituent credentials +fails. +* If the holder or any of the issuers can't be parsed as DIDs. + **Kind**: static method of [JwtPresentationValidator](#JwtPresentationValidator) | Param | Type | diff --git a/bindings/wasm/examples/src/1_advanced/6_domain_linkage.ts b/bindings/wasm/examples/src/1_advanced/6_domain_linkage.ts index 651c42f2fc..e93e2bdfa7 100644 --- a/bindings/wasm/examples/src/1_advanced/6_domain_linkage.ts +++ b/bindings/wasm/examples/src/1_advanced/6_domain_linkage.ts @@ -4,6 +4,7 @@ import { Client, MnemonicSecretManager } from "@iota/client-wasm/node"; import { Bip39 } from "@iota/crypto.js"; import { + CoreDID, Credential, CredentialValidationOptions, DIDUrl, @@ -13,12 +14,17 @@ import { IotaDID, IotaDocument, IotaIdentityClient, + JwkMemStore, + JwsSignatureOptions, + JwtCredentialValidationOptions, + KeyIdMemStore, LinkedDomainService, ProofOptions, + Storage, Timestamp, } from "@iota/identity-wasm/node"; import { IAliasOutput, IRent, TransactionHelper } from "@iota/iota.js"; -import { API_ENDPOINT, createDid } from "../util"; +import { API_ENDPOINT, createDid, createDidStorage } from "../util"; /** * Demonstrates how to link a domain and a DID and verify the linkage. @@ -35,8 +41,10 @@ export async function domainLinkage() { mnemonic: Bip39.randomMnemonic(), }; + const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); + // Creates a new wallet and identity (see "0_create_did" example). - let { document, keypair } = await createDid(client, secretManager); + let { document, fragment } = await createDidStorage(client, secretManager, storage); const did: IotaDID = document.id(); // ===================================================== @@ -74,19 +82,19 @@ export async function domainLinkage() { }); // Sign the credential. - domainLinkageCredential = document.signCredential( + const credentialJwt = await document.createCredentialJwt( + storage, + fragment, domainLinkageCredential, - keypair.private(), - "#key-1", - ProofOptions.default(), + new JwsSignatureOptions(), ); // Create the DID Configuration Resource which wraps the Domain Linkage credential. - let configurationResource: DomainLinkageConfiguration = new DomainLinkageConfiguration([domainLinkageCredential]); + let configurationResource: DomainLinkageConfiguration = new DomainLinkageConfiguration([credentialJwt]); // The DID Configuration resource can be made available on `https://foo.example.com/.well-known/did-configuration.json`. let configurationResourceJson = configurationResource.toJSON(); - console.log("Configuration Resource:", JSON.stringify(configurationResource.toJSON(), null, 2)); + console.log("Configuration Resource:", JSON.stringify(configurationResourceJson, null, 2)); // Now the DID Document links to the Domains through the service, and the Foo domain links to the DID // through the DID Configuration resource. A bidirectional linkage is established. @@ -114,16 +122,16 @@ export async function domainLinkage() { // Retrieve the issuers of the Domain Linkage Credentials which correspond to the possibly linked DIDs. // Note that in this example only the first entry in the credential is validated. - let issuers: Array = fetchedConfigurationResource.issuers(); - const issuerDocument: IotaDocument = await didClient.resolveDid(IotaDID.parse(issuers[0])); + let issuers: Array = fetchedConfigurationResource.issuers(); + const issuerDocument: IotaDocument = await didClient.resolveDid(IotaDID.parse(issuers[0].toString())); // Validate the linkage between the Domain Linkage Credential in the configuration and the provided issuer DID. // Validation succeeds when no error is thrown. - DomainLinkageValidator.validateLinkage( + new DomainLinkageValidator().validateLinkage( issuerDocument, fetchedConfigurationResource, domainFoo, - CredentialValidationOptions.default(), + JwtCredentialValidationOptions.default(), ); // ===================================================== @@ -153,12 +161,14 @@ export async function domainLinkage() { // Validate the linkage between the Domain Linkage Credential in the configuration and the provided issuer DID. // Validation succeeds when no error is thrown. - DomainLinkageValidator.validateLinkage( + new DomainLinkageValidator().validateLinkage( didDocument, fetchedConfigurationResource, domains[0], - CredentialValidationOptions.default(), + JwtCredentialValidationOptions.default(), ); + + console.log("Successfully validated Domain Linkage!"); } async function publishDocument( diff --git a/bindings/wasm/examples/src/util.ts b/bindings/wasm/examples/src/util.ts index d6a38d687d..367958187a 100644 --- a/bindings/wasm/examples/src/util.ts +++ b/bindings/wasm/examples/src/util.ts @@ -3,9 +3,12 @@ import { IotaDID, IotaDocument, IotaIdentityClient, + JwkMemStore, + JwsAlgorithm, KeyPair, KeyType, MethodScope, + Storage, VerificationMethod, } from "@iota/identity-wasm/node"; import { AddressTypes, Bech32Helper, IAliasOutput } from "@iota/iota.js"; @@ -15,6 +18,53 @@ export const FAUCET_ENDPOINT = "http://localhost:8091/api/enqueue"; /** Creates a DID Document and publishes it in a new Alias Output. +Its functionality is equivalent to the "create DID" example +and exists for convenient calling from the other examples. */ +export async function createDidStorage(client: Client, secretManager: SecretManager, storage: Storage): Promise<{ + address: AddressTypes; + document: IotaDocument; + fragment: string; +}> { + const didClient = new IotaIdentityClient(client); + const networkHrp: string = await didClient.getNetworkHrp(); + + const walletAddressBech32 = (await client.generateAddresses(secretManager, { + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + }))[0]; + console.log("Wallet address Bech32:", walletAddressBech32); + + await ensureAddressHasFunds(client, walletAddressBech32); + + const address = Bech32Helper.addressFromBech32(walletAddressBech32, networkHrp); + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + const document = new IotaDocument(networkHrp); + + const fragment = await document.generateMethod( + storage, + JwkMemStore.ed25519KeyType(), + JwsAlgorithm.EdDSA, + "#jwk", + MethodScope.AssertionMethod(), + ); + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + const aliasOutput: IAliasOutput = await didClient.newDidOutput(address, document); + + // Publish the Alias Output and get the published DID document. + const published = await didClient.publishDidOutput(secretManager, aliasOutput); + + return { address, document: published, fragment }; +} + +/** Creates a DID Document and publishes it in a new Alias Output. + Its functionality is equivalent to the "create DID" example and exists for convenient calling from the other examples. */ export async function createDid(client: Client, secretManager: SecretManager): Promise<{ diff --git a/bindings/wasm/src/credential/domain_linkage_configuration.rs b/bindings/wasm/src/credential/domain_linkage_configuration.rs index 6df4810471..77cfb59e8a 100644 --- a/bindings/wasm/src/credential/domain_linkage_configuration.rs +++ b/bindings/wasm/src/credential/domain_linkage_configuration.rs @@ -1,24 +1,26 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::common::ArrayString; -use crate::credential::ArrayCredential; -use crate::credential::WasmCredential; +use crate::credential::ArrayCoreDID; +use crate::credential::WasmJwt; +use crate::did::WasmCoreDID; use crate::error::Result; use crate::error::WasmResult; -use identity_iota::credential::Credential; + use identity_iota::credential::DomainLinkageConfiguration; +use identity_iota::credential::Jwt; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; +use super::ArrayJwt; + /// DID Configuration Resource which contains Domain Linkage Credentials. /// It can be placed in an origin's `.well-known` directory to prove linkage between the origin and a DID. /// See: /// /// Note: -/// - Only [Linked Data Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#linked-data-proof-format) -/// is supported. +/// - Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) #[wasm_bindgen(js_name = DomainLinkageConfiguration, inspectable)] pub struct WasmDomainLinkageConfiguration(pub(crate) DomainLinkageConfiguration); @@ -26,35 +28,39 @@ pub struct WasmDomainLinkageConfiguration(pub(crate) DomainLinkageConfiguration) impl WasmDomainLinkageConfiguration { /// Constructs a new `DomainLinkageConfiguration`. #[wasm_bindgen(constructor)] - pub fn new(linked_dids: ArrayCredential) -> Result { - let wasm_credentials: Vec = linked_dids.into_serde().wasm_result()?; + pub fn new(linked_dids: &ArrayJwt) -> Result { + let wasm_credentials: Vec = linked_dids.into_serde().wasm_result()?; Ok(Self(DomainLinkageConfiguration::new(wasm_credentials))) } /// List of the Domain Linkage Credentials. #[wasm_bindgen(js_name = linkedDids)] - pub fn linked_dids(&self) -> ArrayCredential { + pub fn linked_dids(&self) -> ArrayJwt { self .0 .linked_dids() .iter() .cloned() - .map(WasmCredential::from) + .map(WasmJwt::from) .map(JsValue::from) .collect::() - .unchecked_into::() + .unchecked_into::() } /// List of the issuers of the Domain Linkage Credentials. #[wasm_bindgen] - pub fn issuers(&self) -> ArrayString { - self - .0 - .issuers() - .map(|url| url.to_string()) - .map(JsValue::from) - .collect::() - .unchecked_into::() + pub fn issuers(&self) -> Result { + Ok( + self + .0 + .issuers() + .wasm_result()? + .into_iter() + .map(WasmCoreDID::from) + .map(JsValue::from) + .collect::() + .unchecked_into::(), + ) } } diff --git a/bindings/wasm/src/credential/domain_linkage_credential_builder.rs b/bindings/wasm/src/credential/domain_linkage_credential_builder.rs index bf9e9fc6dc..562d7977f5 100644 --- a/bindings/wasm/src/credential/domain_linkage_credential_builder.rs +++ b/bindings/wasm/src/credential/domain_linkage_credential_builder.rs @@ -5,7 +5,7 @@ use crate::error::WasmResult; use identity_iota::core::Timestamp; use identity_iota::core::Url; use identity_iota::credential::DomainLinkageCredentialBuilder; -use identity_iota::credential::Issuer; +use identity_iota::did::CoreDID; use proc_typescript::typescript; use wasm_bindgen::prelude::*; @@ -49,8 +49,8 @@ extern "C" { #[typescript(name = "IDomainLinkageCredential", readonly, optional)] struct IDomainLinkageCredentialHelper { /// A reference to the issuer of the `Credential`. - #[typescript(optional = false, type = "string | CoreDID | IotaDID | Issuer")] - issuer: Option, + #[typescript(optional = false, type = "CoreDID | IotaDID")] + issuer: Option, /// A timestamp of when the `Credential` becomes valid. Defaults to the current datetime. #[typescript(name = "issuanceDate", type = "Timestamp")] issuance_date: Option, diff --git a/bindings/wasm/src/credential/domain_linkage_validator.rs b/bindings/wasm/src/credential/domain_linkage_validator.rs index 8733f67f07..c553f830de 100644 --- a/bindings/wasm/src/credential/domain_linkage_validator.rs +++ b/bindings/wasm/src/credential/domain_linkage_validator.rs @@ -3,30 +3,45 @@ use crate::common::ImportedDocumentLock; use crate::credential::WasmDomainLinkageConfiguration; +use crate::credential::WasmJwtCredentialValidationOptions; use crate::did::IToCoreDocument; use crate::error::Result; use crate::error::WasmResult; +use crate::verification::IJwsVerifier; +use crate::verification::WasmJwsVerifier; use identity_iota::core::Url; use identity_iota::credential::DomainLinkageValidator; use wasm_bindgen::prelude::wasm_bindgen; -use super::WasmCredential; -use super::WasmCredentialValidationOptions; +use super::WasmJwt; /// A validator for a Domain Linkage Configuration and Credentials. #[wasm_bindgen(js_name = DomainLinkageValidator)] -pub struct WasmDomainLinkageValidator; +pub struct WasmDomainLinkageValidator { + validator: DomainLinkageValidator, +} #[wasm_bindgen(js_class = DomainLinkageValidator)] impl WasmDomainLinkageValidator { + /// Creates a new `DomainLinkageValidator`. If a `signatureVerifier` is provided it will be used when + /// verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` + /// algorithm will be used. + #[wasm_bindgen(constructor)] + #[allow(non_snake_case)] + pub fn new(signatureVerifier: Option) -> WasmDomainLinkageValidator { + let signature_verifier = WasmJwsVerifier::new(signatureVerifier); + WasmDomainLinkageValidator { + validator: DomainLinkageValidator::with_signature_verifier(signature_verifier), + } + } + /// Validates the linkage between a domain and a DID. /// [`DomainLinkageConfiguration`] is validated according to [DID Configuration Resource Verification](https://identity.foundation/.well-known/resources/did-configuration/#did-configuration-resource-verification). /// /// Linkage is valid if no error is thrown. /// /// # Note: - /// - Only [Linked Data Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#linked-data-proof-format) - /// is supported. + /// - Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) /// - Only the Credential issued by `issuer` is verified. /// /// # Errors @@ -35,29 +50,38 @@ impl WasmDomainLinkageValidator { /// - Validation of the matched Domain Linkage Credential fails. #[wasm_bindgen(js_name = validateLinkage)] pub fn validate_linkage( + &self, issuer: &IToCoreDocument, configuration: &WasmDomainLinkageConfiguration, domain: &str, - options: &WasmCredentialValidationOptions, + options: &WasmJwtCredentialValidationOptions, ) -> Result<()> { let domain = Url::parse(domain).wasm_result()?; let doc = ImportedDocumentLock::from(issuer); let doc_guard = doc.blocking_read(); - DomainLinkageValidator::validate_linkage(&doc_guard, &configuration.0, &domain, &options.0).wasm_result() + self + .validator + .validate_linkage(&doc_guard, &configuration.0, &domain, &options.0) + .wasm_result() } /// Validates a [Domain Linkage Credential](https://identity.foundation/.well-known/resources/did-configuration/#domain-linkage-credential). /// Error will be thrown in case the validation fails. #[wasm_bindgen(js_name = validateCredential)] + #[allow(non_snake_case)] pub fn validate_credential( + &self, issuer: &IToCoreDocument, - credential: &WasmCredential, + credentialJwt: &WasmJwt, domain: &str, - options: &WasmCredentialValidationOptions, + options: &WasmJwtCredentialValidationOptions, ) -> Result<()> { let domain = Url::parse(domain).wasm_result()?; let doc = ImportedDocumentLock::from(issuer); let doc_guard = doc.blocking_read(); - DomainLinkageValidator::validate_credential(&doc_guard, &credential.0, &domain, &options.0).wasm_result() + self + .validator + .validate_credential(&doc_guard, &credentialJwt.0, &domain, &options.0) + .wasm_result() } } diff --git a/bindings/wasm/src/credential/jwt.rs b/bindings/wasm/src/credential/jwt.rs index efa7b732e8..270963fe5e 100644 --- a/bindings/wasm/src/credential/jwt.rs +++ b/bindings/wasm/src/credential/jwt.rs @@ -25,3 +25,18 @@ impl WasmJwt { self.0.as_str().to_owned() } } + +impl_wasm_json!(WasmJwt, Jwt); +impl_wasm_clone!(WasmJwt, Jwt); + +impl From for WasmJwt { + fn from(value: Jwt) -> Self { + WasmJwt(value) + } +} + +impl From for Jwt { + fn from(value: WasmJwt) -> Self { + value.0 + } +} diff --git a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator.rs b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator.rs index 866da4a76b..c4d402c1f9 100644 --- a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator.rs +++ b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator.rs @@ -36,12 +36,13 @@ pub struct WasmJwtCredentialValidator(JwtCredentialValidator); #[wasm_bindgen(js_class = JwtCredentialValidator)] impl WasmJwtCredentialValidator { - /// Creates a new `JwtCredentialValidator`. If a `signature_verifier` is provided it will be used when + /// Creates a new `JwtCredentialValidator`. If a `signatureVerifier` is provided it will be used when /// verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` /// algorithm will be used. #[wasm_bindgen(constructor)] - pub fn new(signature_verifier: Option) -> WasmJwtCredentialValidator { - let signature_verifier = WasmJwsVerifier::new(signature_verifier); + #[allow(non_snake_case)] + pub fn new(signatureVerifier: Option) -> WasmJwtCredentialValidator { + let signature_verifier = WasmJwsVerifier::new(signatureVerifier); WasmJwtCredentialValidator(JwtCredentialValidator::with_signature_verifier(signature_verifier)) } diff --git a/bindings/wasm/src/did/wasm_core_document.rs b/bindings/wasm/src/did/wasm_core_document.rs index 6b0150692b..2bd30c52e4 100644 --- a/bindings/wasm/src/did/wasm_core_document.rs +++ b/bindings/wasm/src/did/wasm_core_document.rs @@ -11,10 +11,11 @@ use crate::common::ArrayString; use crate::common::ArrayVerificationMethod; use crate::common::MapStringAny; use crate::common::OptionOneOrManyString; -use crate::common::PromiseOptionString; +use crate::common::PromiseString; use crate::common::PromiseVoid; use crate::common::UDIDUrlQuery; use crate::common::UOneOrManyNumber; +use crate::credential::ArrayCoreDID; use crate::credential::WasmCredential; use crate::credential::WasmJws; use crate::credential::WasmJwt; @@ -640,7 +641,7 @@ impl WasmCoreDocument { alg: WasmJwsAlgorithm, fragment: Option, scope: WasmMethodScope, - ) -> Result { + ) -> Result { let alg: JwsAlgorithm = alg.into_serde().wasm_result()?; let document_lock_clone: Rc = self.0.clone(); let storage_clone: Rc = storage.0.clone(); @@ -780,12 +781,8 @@ extern "C" { #[wasm_bindgen(typescript_type = "ICoreDocument")] pub type ICoreDocument; - #[wasm_bindgen(typescript_type = "CoreDID[]")] - pub type ArrayCoreDID; - #[wasm_bindgen(typescript_type = "CoreDID | CoreDID[] | null")] pub type OptionOneOrManyCoreDID; - } #[derive(Deserialize)] diff --git a/bindings/wasm/src/iota/iota_document.rs b/bindings/wasm/src/iota/iota_document.rs index ff965ef8b0..e680e651b4 100644 --- a/bindings/wasm/src/iota/iota_document.rs +++ b/bindings/wasm/src/iota/iota_document.rs @@ -40,7 +40,7 @@ use crate::common::ArrayVerificationMethod; use crate::common::MapStringAny; use crate::common::OptionOneOrManyString; use crate::common::OptionTimestamp; -use crate::common::PromiseOptionString; +use crate::common::PromiseString; use crate::common::PromiseVoid; use crate::common::UDIDUrlQuery; use crate::common::UOneOrManyNumber; @@ -758,7 +758,7 @@ impl WasmIotaDocument { alg: WasmJwsAlgorithm, fragment: Option, scope: WasmMethodScope, - ) -> Result { + ) -> Result { let alg: JwsAlgorithm = alg.into_serde().wasm_result()?; let document_lock_clone: Rc = self.0.clone(); let storage_clone: Rc = storage.0.clone(); diff --git a/examples/1_advanced/7_domain_linkage.rs b/examples/1_advanced/7_domain_linkage.rs index 1e43b74a6b..bb95a88a97 100644 --- a/examples/1_advanced/7_domain_linkage.rs +++ b/examples/1_advanced/7_domain_linkage.rs @@ -1,8 +1,9 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use examples::create_did; +use examples::create_did_storage; use examples::random_stronghold_path; +use examples::MemStorage; use examples::API_ENDPOINT; use identity_iota::core::Duration; use identity_iota::core::FromJson; @@ -11,16 +12,15 @@ use identity_iota::core::OrderedSet; use identity_iota::core::Timestamp; use identity_iota::core::ToJson; use identity_iota::core::Url; +use identity_iota::credential::vc_jwt_validation::CredentialValidationOptions; use identity_iota::credential::Credential; -use identity_iota::credential::CredentialValidationOptions; use identity_iota::credential::DomainLinkageConfiguration; use identity_iota::credential::DomainLinkageCredentialBuilder; use identity_iota::credential::DomainLinkageValidationError; use identity_iota::credential::DomainLinkageValidator; -use identity_iota::credential::Issuer; +use identity_iota::credential::Jwt; use identity_iota::credential::LinkedDomainService; -use identity_iota::crypto::KeyPair; -use identity_iota::crypto::ProofOptions; +use identity_iota::did::CoreDID; use identity_iota::did::DIDUrl; use identity_iota::did::DID; use identity_iota::iota::IotaClientExt; @@ -28,6 +28,10 @@ use identity_iota::iota::IotaDID; use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwsSignatureOptions; +use identity_iota::storage::KeyIdMemstore; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; use iota_sdk::client::secret::SecretManager; use iota_sdk::client::Client; @@ -52,8 +56,9 @@ async fn main() -> anyhow::Result<()> { ); // Create a DID for the entity that will issue the Domain Linkage Credential. - let (_, mut did_document, keypair): (Address, IotaDocument, KeyPair) = - create_did(&client, &mut secret_manager).await?; + let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let (_, mut did_document, fragment): (Address, IotaDocument, String) = + create_did_storage(&client, &mut secret_manager, &storage).await?; let did: IotaDID = did_document.id().clone(); // ===================================================== @@ -88,25 +93,26 @@ async fn main() -> anyhow::Result<()> { // and can be made available on the domain. // Create the Domain Linkage Credential. - let mut domain_linkage_credential: Credential = DomainLinkageCredentialBuilder::new() - .issuer(Issuer::Url(updated_did_document.id().to_url().into())) + let domain_linkage_credential: Credential = DomainLinkageCredentialBuilder::new() + .issuer(updated_did_document.id().clone().into()) .origin(domain_1.clone()) .issuance_date(Timestamp::now_utc()) // Expires after a year. .expiration_date(Timestamp::now_utc().checked_add(Duration::days(365)).unwrap()) .build()?; - // Sign the credential. - updated_did_document.sign_data( - &mut domain_linkage_credential, - keypair.private(), - "#key-1", - ProofOptions::default(), - )?; + let jwt: Jwt = updated_did_document + .sign_credential( + &domain_linkage_credential, + &storage, + &fragment, + &JwsSignatureOptions::default(), + ) + .await + .unwrap(); // Create the DID Configuration Resource which wraps the Domain Linkage credential. - let configuration_resource: DomainLinkageConfiguration = - DomainLinkageConfiguration::new(vec![domain_linkage_credential]); + let configuration_resource: DomainLinkageConfiguration = DomainLinkageConfiguration::new(vec![jwt]); println!("Configuration Resource >>: {configuration_resource:#}"); // The DID Configuration resource can be made available on `https://foo.example.com/.well-known/did-configuration.json`. @@ -134,7 +140,9 @@ async fn main() -> anyhow::Result<()> { // Fetch the DID Configuration resource // let configuration_resource: DomainLinkageConfiguration = - // DomainLinkageConfiguration::fetch_configuration(domain_foo).await.unwrap(); + // DomainLinkageConfiguration::fetch_configuration(domain_foo.clone()) + // .await + // .unwrap(); // But since the DID Configuration // resource isn't available online in this example, we will simply deserialize the JSON. @@ -142,14 +150,14 @@ async fn main() -> anyhow::Result<()> { DomainLinkageConfiguration::from_json(&configuration_resource_json)?; // Retrieve the issuers of the Domain Linkage Credentials which correspond to the possibly linked DIDs. - let linked_dids: Vec<&Url> = configuration_resource.issuers().collect(); + let linked_dids: Vec = configuration_resource.issuers()?; assert_eq!(linked_dids.len(), 1); // Resolve the DID Document of the DID that issued the credential. let issuer_did_document: IotaDocument = resolver.resolve(&did).await.unwrap(); // Validate the linkage between the Domain Linkage Credential in the configuration and the provided issuer DID. - let validation_result: Result<(), DomainLinkageValidationError> = DomainLinkageValidator::validate_linkage( + let validation_result: Result<(), DomainLinkageValidationError> = DomainLinkageValidator::new().validate_linkage( &issuer_did_document, &configuration_resource, &domain_foo, @@ -179,7 +187,7 @@ async fn main() -> anyhow::Result<()> { // Fetch the DID Configuration resource // let configuration_resource: DomainLinkageConfiguration = - // DomainLinkageConfiguration::fetch_configuration(domain_foo).await.unwrap(); + // DomainLinkageConfiguration::fetch_configuration(domain_foo.clone()).await?; // But since the DID Configuration // resource isn't available online in this example, we will simply deserialize the JSON. @@ -187,7 +195,7 @@ async fn main() -> anyhow::Result<()> { DomainLinkageConfiguration::from_json(&configuration_resource_json)?; // Validate the linkage. - let validation_result: Result<(), DomainLinkageValidationError> = DomainLinkageValidator::validate_linkage( + let validation_result: Result<(), DomainLinkageValidationError> = DomainLinkageValidator::new().validate_linkage( &did_document, &configuration_resource, &domain_foo, diff --git a/examples/Cargo.toml b/examples/Cargo.toml index ee9b518e31..fa320d6dc1 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] anyhow = "1.0.62" -identity_iota = { path = "../identity_iota" } +identity_iota = { path = "../identity_iota", features = ["memstore"] } primitive-types = "0.12.1" rand = "0.8.5" tokio = { version = "1.20.1", default-features = false, features = ["rt"] } diff --git a/examples/utils/utils.rs b/examples/utils/utils.rs index 3ac61a0a03..58d53fa04f 100644 --- a/examples/utils/utils.rs +++ b/examples/utils/utils.rs @@ -12,9 +12,14 @@ use identity_iota::iota::IotaClientExt; use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; +use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::KeyIdMemstore; +use identity_iota::storage::Storage; use identity_iota::verification::MethodScope; use identity_iota::verification::VerificationMethod; +use identity_iota::verification::jws::JwsAlgorithm; use iota_sdk::client::node_api::indexer::query_parameters::QueryParameter; use iota_sdk::client::secret::SecretManager; use iota_sdk::client::Client; @@ -25,6 +30,32 @@ use rand::distributions::DistString; pub static API_ENDPOINT: &str = "http://localhost:14265"; pub static FAUCET_ENDPOINT: &str = "http://localhost:8091/api/enqueue"; +pub type MemStorage = Storage; + +/// Creates a DID Document and publishes it in a new Alias Output. +/// +/// Its functionality is equivalent to the "create DID" example +/// and exists for convenient calling from the other examples. +pub async fn create_did_storage( + client: &Client, + secret_manager: &mut SecretManager, + storage: &MemStorage, +) -> anyhow::Result<(Address, IotaDocument, String)> { + let address: Address = get_address_with_funds(client, secret_manager, FAUCET_ENDPOINT) + .await + .context("failed to get address with funds")?; + + let network_name: NetworkName = client.network_name().await?; + + let (document, fragment): (IotaDocument, String) = create_did_document_storage(&network_name, storage).await?; + + let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; + + let document: IotaDocument = client.publish_did_output(secret_manager, alias_output).await?; + + Ok((address, document, fragment)) +} + /// Creates a DID Document and publishes it in a new Alias Output. /// /// Its functionality is equivalent to the "create DID" example @@ -48,6 +79,29 @@ pub async fn create_did( Ok((address, document, key_pair)) } +/// Creates an example DID document with the given `network_name`. +/// +/// Its functionality is equivalent to the "create DID" example +/// and exists for convenient calling from the other examples. +pub async fn create_did_document_storage( + network_name: &NetworkName, + storage: &MemStorage, +) -> anyhow::Result<(IotaDocument, String)> { + let mut document: IotaDocument = IotaDocument::new(network_name); + + let fragment: String = document + .generate_method( + storage, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA, + None, + MethodScope::VerificationMethod, + ) + .await?; + + Ok((document, fragment)) +} + /// Creates an example DID document with the given `network_name`. /// /// Its functionality is equivalent to the "create DID" example diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index 3b8d530eeb..06d1547be0 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -31,6 +31,7 @@ thiserror.workspace = true url = { version = "2.2", default-features = false } [dev-dependencies] +iota-crypto = { version = "0.20", default-features = false, features = ["ed25519", "std", "random"] } proptest = { version = "1.0.0", default-features = false, features = ["std"] } serde_json.workspace = true tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread", "macros"] } diff --git a/identity_credential/src/credential/domain_linkage_configuration.rs b/identity_credential/src/credential/domain_linkage_configuration.rs index 87a069b5df..d37ba9f946 100644 --- a/identity_credential/src/credential/domain_linkage_configuration.rs +++ b/identity_credential/src/credential/domain_linkage_configuration.rs @@ -1,17 +1,22 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::credential::Credential; use crate::error::Result; +use crate::validator::vc_jwt_validation::CredentialValidator; +use crate::validator::vc_jwt_validation::ValidationError; use identity_core::common::Context; +use identity_core::common::Object; use identity_core::common::Url; use identity_core::convert::FmtJson; +use identity_did::CoreDID; use serde::Deserialize; use std::fmt::Display; use std::fmt::Formatter; use crate::Error::DomainLinkageError; +use super::Jwt; + lazy_static! { static ref WELL_KNOWN_CONTEXT: Context = Context::Url(Url::parse("https://identity.foundation/.well-known/did-configuration/v1").unwrap()); @@ -22,8 +27,7 @@ lazy_static! { /// See: /// /// Note: -/// - Only [Linked Data Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#linked-data-proof-format) -/// is supported. +/// - Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(try_from = "__DomainLinkageConfiguration")] pub struct DomainLinkageConfiguration(__DomainLinkageConfiguration); @@ -34,8 +38,8 @@ struct __DomainLinkageConfiguration { /// Fixed context. #[serde(rename = "@context")] context: Context, - /// Linked credentials. - linked_dids: Vec, + /// Linked JWT credentials. + linked_dids: Vec, } impl __DomainLinkageConfiguration { @@ -68,7 +72,7 @@ impl Display for DomainLinkageConfiguration { impl DomainLinkageConfiguration { /// Creates a new DID Configuration Resource. - pub fn new(linked_dids: Vec) -> Self { + pub fn new(linked_dids: Vec) -> Self { Self(__DomainLinkageConfiguration { context: Self::well_known_context().clone(), linked_dids, @@ -84,17 +88,22 @@ impl DomainLinkageConfiguration { } /// List of Domain Linkage Credentials. - pub fn linked_dids(&self) -> &Vec { + pub fn linked_dids(&self) -> &Vec { &self.0.linked_dids } /// List of the issuers of the Domain Linkage Credentials. - pub fn issuers(&self) -> impl Iterator { - self.0.linked_dids.iter().map(|linked_did| linked_did.issuer.url()) + pub fn issuers(&self) -> std::result::Result, ValidationError> { + self + .0 + .linked_dids + .iter() + .map(CredentialValidator::extract_issuer_from_jwt::) + .collect() } /// List of domain Linkage Credentials. - pub fn linked_dids_mut(&mut self) -> &mut Vec { + pub fn linked_dids_mut(&mut self) -> &mut Vec { &mut self.0.linked_dids } } @@ -167,20 +176,20 @@ mod tests { #[test] fn test_from_json_valid() { - const JSON1: &str = include_str!("../../tests/fixtures/dn-config-valid.json"); + const JSON1: &str = include_str!("../../tests/fixtures/domain-config-valid.json"); DomainLinkageConfiguration::from_json(JSON1).unwrap(); } #[test] fn test_from_json_invalid_context() { - const JSON1: &str = include_str!("../../tests/fixtures/dn-config-invalid-context.json"); + const JSON1: &str = include_str!("../../tests/fixtures/domain-config-invalid-context.json"); let deserialization_result: Result = DomainLinkageConfiguration::from_json(JSON1); assert!(deserialization_result.is_err()); } #[test] fn test_from_json_extra_property() { - const JSON1: &str = include_str!("../../tests/fixtures/dn-config-extra-property.json"); + const JSON1: &str = include_str!("../../tests/fixtures/domain-config-extra-property.json"); let deserialization_result: Result = DomainLinkageConfiguration::from_json(JSON1); assert!(deserialization_result.is_err()); } diff --git a/identity_credential/src/credential/domain_linkage_credential_builder.rs b/identity_credential/src/credential/domain_linkage_credential_builder.rs index d202934326..c9c0090646 100644 --- a/identity_credential/src/credential/domain_linkage_credential_builder.rs +++ b/identity_credential/src/credential/domain_linkage_credential_builder.rs @@ -11,8 +11,10 @@ use identity_core::common::Object; use identity_core::common::OneOrMany; use identity_core::common::Timestamp; use identity_core::common::Url; +use identity_did::CoreDID; +use identity_did::DID; -/// Convenient builder to create a spec compliant Linked Data Domain Linkage Credential. +/// Convenient builder to create a spec compliant Domain Linkage Credential. /// /// See: /// @@ -32,16 +34,12 @@ impl DomainLinkageCredentialBuilder { Self::default() } - /// Sets the value of the `issuer`, only the URL is used, other properties are ignored. + /// Sets the value of the `issuer`. /// - /// The issuer will also be set as the `credentialSubject`. + /// The issuer will also be set as `credentialSubject.id`. #[must_use] - pub fn issuer(mut self, value: Issuer) -> Self { - let issuer: Url = match value { - Issuer::Url(url) => url, - Issuer::Obj(data) => data.id, - }; - self.issuer = Some(issuer); + pub fn issuer(mut self, did: CoreDID) -> Self { + self.issuer = Some(did.into_url().into()); self } @@ -60,6 +58,8 @@ impl DomainLinkageCredentialBuilder { } /// Sets the origin in `credentialSubject`. + /// + /// Must be a domain origin. #[must_use] pub fn origin(mut self, value: Url) -> Self { self.origin = Some(value); @@ -69,6 +69,12 @@ impl DomainLinkageCredentialBuilder { /// Returns a new `Credential` based on the `DomainLinkageCredentialBuilder` configuration. pub fn build(self) -> Result> { let origin: Url = self.origin.ok_or(Error::MissingOrigin)?; + if origin.domain().is_none() { + return Err(Error::DomainLinkageError( + "origin must be a domain with http(s) scheme".into(), + )); + } + let mut properties: Object = Object::new(); properties.insert("origin".into(), origin.into_string().into()); let issuer: Url = self.issuer.ok_or(Error::MissingIssuer)?; @@ -103,22 +109,35 @@ impl DomainLinkageCredentialBuilder { mod tests { use crate::credential::domain_linkage_credential_builder::DomainLinkageCredentialBuilder; use crate::credential::Credential; - use crate::credential::Issuer; use crate::error::Result; use crate::Error; use identity_core::common::Timestamp; use identity_core::common::Url; + use identity_did::CoreDID; #[test] fn test_builder_with_all_fields_set_succeeds() { - let issuer = Issuer::Url(Url::parse("did:example:issuer").unwrap()); - let _credential: Credential = DomainLinkageCredentialBuilder::new() + let issuer: CoreDID = "did:example:issuer".parse().unwrap(); + assert!(DomainLinkageCredentialBuilder::new() .issuance_date(Timestamp::now_utc()) .expiration_date(Timestamp::now_utc()) .issuer(issuer) .origin(Url::parse("http://www.example.com").unwrap()) .build() - .unwrap(); + .is_ok()); + } + + #[test] + fn test_builder_origin_is_not_a_domain() { + let issuer: CoreDID = "did:example:issuer".parse().unwrap(); + let err: Error = DomainLinkageCredentialBuilder::new() + .issuance_date(Timestamp::now_utc()) + .expiration_date(Timestamp::now_utc()) + .issuer(issuer) + .origin(Url::parse("did:example:origin").unwrap()) + .build() + .unwrap_err(); + assert!(matches!(err, Error::DomainLinkageError(_))); } #[test] @@ -134,7 +153,7 @@ mod tests { #[test] fn test_builder_no_origin() { - let issuer = Issuer::Url(Url::parse("did:example:issuer").unwrap()); + let issuer: CoreDID = "did:example:issuer".parse().unwrap(); let credential: Result = DomainLinkageCredentialBuilder::new() .issuance_date(Timestamp::now_utc()) .expiration_date(Timestamp::now_utc()) @@ -146,7 +165,7 @@ mod tests { #[test] fn test_builder_no_expiration_date() { - let issuer = Issuer::Url(Url::parse("did:example:issuer").unwrap()); + let issuer: CoreDID = "did:example:issuer".parse().unwrap(); let credential: Result = DomainLinkageCredentialBuilder::new() .issuance_date(Timestamp::now_utc()) .issuer(issuer) diff --git a/identity_credential/src/credential/mod.rs b/identity_credential/src/credential/mod.rs index d0c6f76a0c..242fd7a240 100644 --- a/identity_credential/src/credential/mod.rs +++ b/identity_credential/src/credential/mod.rs @@ -7,7 +7,10 @@ mod builder; mod credential; +// TODO: Feature-gate Domain Linkage Types behind its own flag and require flag = ["validator"]. +#[cfg(feature = "validator")] mod domain_linkage_configuration; +#[cfg(feature = "validator")] mod domain_linkage_credential_builder; mod evidence; mod issuer; @@ -25,7 +28,9 @@ mod subject; pub use self::builder::CredentialBuilder; pub use self::credential::Credential; +#[cfg(feature = "validator")] pub use self::domain_linkage_configuration::DomainLinkageConfiguration; +#[cfg(feature = "validator")] pub use self::domain_linkage_credential_builder::DomainLinkageCredentialBuilder; pub use self::evidence::Evidence; pub use self::issuer::Issuer; diff --git a/identity_credential/src/validator/domain_linkage_validator.rs b/identity_credential/src/validator/domain_linkage_validator.rs index 3bd31a85db..5ef759ab2d 100644 --- a/identity_credential/src/validator/domain_linkage_validator.rs +++ b/identity_credential/src/validator/domain_linkage_validator.rs @@ -3,24 +3,56 @@ use crate::credential::Credential; use crate::credential::DomainLinkageConfiguration; +use crate::credential::Jwt; use crate::validator::errors::DomainLinkageValidationError; use crate::validator::errors::DomainLinkageValidationErrorCause; -use crate::validator::CredentialValidationOptions; -use crate::validator::CredentialValidator; +use crate::validator::vc_jwt_validation::CredentialValidationOptions; +use crate::validator::vc_jwt_validation::CredentialValidator; use crate::validator::FailFast; use identity_core::common::OneOrMany; use identity_core::common::Url; use identity_did::CoreDID; -use identity_did::DID; use identity_document::document::CoreDocument; -use serde::Serialize; +use identity_verification::jws::EdDSAJwsVerifier; +use identity_verification::jws::JwsVerifier; + +use super::vc_jwt_validation::DecodedJwtCredential; type DomainLinkageValidationResult = Result<(), DomainLinkageValidationError>; /// A validator for a Domain Linkage Configuration and Credentials. -pub struct DomainLinkageValidator {} + +#[derive(Debug, Clone)] +pub struct DomainLinkageValidator { + validator: CredentialValidator, +} impl DomainLinkageValidator { + /// Creates a new [`DomainLinkageValidator`] capable of verifying a Domain Linkage Credential issued as a JWS + /// using the [`EdDSA`](::identity_verification::jose::jws::JwsAlgorithm::EdDSA) algorithm. + /// + /// See [`DomainLinkageValidator::with_signature_verifier`](DomainLinkageValidator::with_signature_verifier) + /// which enables you to supply a custom signature verifier if other JWS algorithms are of interest. + pub fn new() -> Self { + Self { + validator: CredentialValidator::new(), + } + } +} + +impl DomainLinkageValidator +where + V: JwsVerifier, +{ + /// Create a new [`DomainLinkageValidator`] that delegates cryptographic signature verification to the given + /// `signature_verifier`. If you are only interested in `EdDSA` signatures (with `Ed25519`) then the default + /// constructor can be used. See [`DomainLinkageValidator::new`](DomainLinkageValidator::new). + pub fn with_signature_verifier(signature_verifier: V) -> Self { + Self { + validator: CredentialValidator::with_signature_verifier(signature_verifier), + } + } + /// Validates the linkage between a domain and a DID. /// [`DomainLinkageConfiguration`] is validated according to [DID Configuration Resource Verification](https://identity.foundation/.well-known/resources/did-configuration/#did-configuration-resource-verification). /// @@ -31,8 +63,8 @@ impl DomainLinkageValidator { /// * `validation_options`: Further validation options to be applied on the Domain Linkage Credential. /// /// # Note: - /// - Only [Linked Data Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#linked-data-proof-format) - /// is supported. + /// - Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) + /// is supported. /// - Only the Credential issued by `issuer` is verified. /// /// # Errors @@ -40,32 +72,45 @@ impl DomainLinkageValidator { /// - `configuration` includes multiple credentials issued by `issuer`. /// - Validation of the matched Domain Linkage Credential fails. pub fn validate_linkage>( + &self, issuer: &DOC, configuration: &DomainLinkageConfiguration, domain: &Url, validation_options: &CredentialValidationOptions, ) -> DomainLinkageValidationResult { - let mut matched_credentials = configuration - .linked_dids() - .iter() - .filter(|credential| credential.issuer.url().as_str() == CoreDocument::id(issuer.as_ref()).as_str()); + let issuers: Vec = configuration.issuers().map_err(|err| DomainLinkageValidationError { + cause: DomainLinkageValidationErrorCause::InvalidJwt, + source: Some(err.into()), + })?; - match matched_credentials.next() { - None => Err(DomainLinkageValidationError { + // Multiple credentials for the same issuer are an error. + if issuers.iter().filter(|iss| *iss == issuer.as_ref().id()).count() > 1 { + return Err(DomainLinkageValidationError { cause: DomainLinkageValidationErrorCause::InvalidStructure, source: None, - }), - Some(credential) => { - if matched_credentials.next().is_some() { - Err(DomainLinkageValidationError { - cause: DomainLinkageValidationErrorCause::InvalidStructure, - source: None, - }) - } else { - Self::validate_credential(issuer, credential, domain, validation_options) - } - } - } + }); + }; + + // Find the index of the issuer in the JWT credentials if present. + let (jwt_index, _): (usize, _) = issuers + .iter() + .enumerate() + .find(|(_index, iss)| *iss == issuer.as_ref().id()) + .ok_or_else(|| DomainLinkageValidationError { + cause: DomainLinkageValidationErrorCause::InvalidIssuer, + source: None, + })?; + + // Validate the credential at the corresponding index. + let credential: &Jwt = configuration + .linked_dids() + .get(jwt_index) + .ok_or_else(|| DomainLinkageValidationError { + cause: DomainLinkageValidationErrorCause::InvalidIssuer, + source: None, + })?; + + self.validate_credential(issuer, credential, domain, validation_options) } /// Validates a [Domain Linkage Credential](https://identity.foundation/.well-known/resources/did-configuration/#domain-linkage-credential). @@ -73,25 +118,29 @@ impl DomainLinkageValidator { /// *`issuer`: issuer of the credential. /// *`credential`: domain linkage Credential to be verified. /// *`domain`: the domain hosting the credential. - pub fn validate_credential>( + pub fn validate_credential>( + &self, issuer: &DOC, - credential: &Credential, + credential: &Jwt, domain: &Url, validation_options: &CredentialValidationOptions, ) -> DomainLinkageValidationResult { + let decoded_credential: DecodedJwtCredential = self + .validator + .validate(credential, issuer, validation_options, FailFast::AllErrors) + .map_err(|err| DomainLinkageValidationError { + cause: DomainLinkageValidationErrorCause::CredentialValidationError, + source: Some(Box::new(err)), + })?; + + let credential: &Credential = &decoded_credential.credential; + let issuer_did: CoreDID = CoreDID::parse(credential.issuer.url().as_str()).map_err(|err| DomainLinkageValidationError { cause: DomainLinkageValidationErrorCause::InvalidIssuer, source: Some(Box::new(err)), })?; - CredentialValidator::validate(credential, issuer, validation_options, FailFast::AllErrors).map_err(|err| { - DomainLinkageValidationError { - cause: DomainLinkageValidationErrorCause::CredentialValidationError, - source: Some(Box::new(err)), - } - })?; - if credential.id.is_some() { return Err(DomainLinkageValidationError { cause: DomainLinkageValidationErrorCause::ImpermissibleIdProperty, @@ -171,51 +220,69 @@ impl DomainLinkageValidator { } } +impl Default for DomainLinkageValidator { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use crate::credential::Credential; use crate::credential::DomainLinkageConfiguration; use crate::credential::DomainLinkageCredentialBuilder; - use crate::credential::Issuer; - use crate::credential::Subject; + use crate::credential::Jws; + use crate::credential::Jwt; use crate::validator::domain_linkage_validator::DomainLinkageValidationResult; use crate::validator::domain_linkage_validator::DomainLinkageValidator; use crate::validator::errors::DomainLinkageValidationErrorCause; - use crate::validator::test_utils::generate_document_with_keys; - use crate::validator::CredentialValidationOptions; + use crate::validator::test_utils::generate_jwk_document_with_keys; + use crate::validator::vc_jwt_validation::CredentialValidationOptions; + use crypto::signatures::ed25519::SecretKey; use identity_core::common::Duration; use identity_core::common::Object; use identity_core::common::OneOrMany; use identity_core::common::OrderedSet; use identity_core::common::Timestamp; use identity_core::common::Url; - use identity_core::crypto::KeyPair; - use identity_core::crypto::ProofOptions; - use identity_did::DID; + use identity_did::CoreDID; use identity_document::document::CoreDocument; + use identity_verification::jws::CharSet; + use identity_verification::jws::CompactJwsEncoder; + use identity_verification::jws::CompactJwsEncodingOptions; + use identity_verification::jws::JwsAlgorithm; + use identity_verification::jws::JwsHeader; + use identity_verification::MethodData; + use identity_verification::VerificationMethod; #[test] pub(crate) fn test_valid_credential() { - let (mut credential, document, keypair) = create_valid_credential(); - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let credential: Credential = create_domain_linkage_credential(document.id()); + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); + assert!(validation_result.is_ok()); } #[test] pub(crate) fn test_invalid_credential_signature() { - let (mut credential, document, keypair) = create_valid_credential(); - sign_credential(&mut credential, &document, keypair); - credential.expiration_date = Some(Timestamp::now_utc().checked_add(Duration::days(10)).unwrap()); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + let (document, _secret_key, fragment) = generate_jwk_document_with_keys(); + let credential: Credential = create_domain_linkage_credential(document.id()); + let other_secret_key: SecretKey = SecretKey::generate().unwrap(); + // Sign with `other_secret_key` to produce an invalid signature. + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &other_secret_key); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); @@ -227,15 +294,18 @@ mod tests { #[test] pub(crate) fn test_invalid_id_property() { - let (mut credential, document, keypair) = create_valid_credential(); + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let mut credential: Credential = create_domain_linkage_credential(document.id()); credential.id = Some(Url::parse("http://random.credential.id").unwrap()); - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); + assert!(matches!( validation_result.unwrap_err().cause, DomainLinkageValidationErrorCause::ImpermissibleIdProperty @@ -244,15 +314,18 @@ mod tests { #[test] pub(crate) fn test_domain_linkage_type_missing() { - let (mut credential, document, keypair) = create_valid_credential(); + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let mut credential: Credential = create_domain_linkage_credential(document.id()); credential.types = OneOrMany::One(Credential::::base_type().to_owned()); - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); + assert!(matches!( validation_result.unwrap_err().cause, DomainLinkageValidationErrorCause::InvalidTypeProperty @@ -261,37 +334,44 @@ mod tests { #[test] pub(crate) fn test_extra_type() { - let (mut credential, document, keypair) = create_valid_credential(); + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let mut credential: Credential = create_domain_linkage_credential(document.id()); credential.types = OneOrMany::Many(vec![ Credential::::base_type().to_owned(), DomainLinkageConfiguration::domain_linkage_type().to_owned(), "not-allowed-type".to_owned(), ]); - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); + assert!(validation_result.is_ok()); } #[test] pub(crate) fn test_origin_mismatch() { - let (mut credential, document, keypair) = create_valid_credential(); + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let mut credential: Credential = create_domain_linkage_credential(document.id()); + let mut properties: Object = Object::new(); properties.insert("origin".into(), "http://www.example-1.com".into()); if let OneOrMany::One(ref mut subject) = credential.credential_subject { subject.properties = properties; } - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); + assert!(matches!( validation_result.unwrap_err().cause, DomainLinkageValidationErrorCause::OriginMismatch @@ -300,18 +380,22 @@ mod tests { #[test] pub(crate) fn test_empty_origin() { - let (mut credential, document, keypair) = create_valid_credential(); + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let mut credential: Credential = create_domain_linkage_credential(document.id()); + let properties: Object = Object::new(); if let OneOrMany::One(ref mut subject) = credential.credential_subject { subject.properties = properties; } - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); + assert!(matches!( validation_result.unwrap_err().cause, DomainLinkageValidationErrorCause::InvalidSubjectOrigin @@ -320,55 +404,43 @@ mod tests { #[test] pub(crate) fn test_origin_without_scheme() { - let (mut credential, document, keypair) = create_valid_credential(); + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let mut credential: Credential = create_domain_linkage_credential(document.id()); + let mut properties: Object = Object::new(); properties.insert("origin".into(), "foo.example.com".into()); if let OneOrMany::One(ref mut subject) = credential.credential_subject { subject.properties = properties; } - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( - &document, - &credential, - &url_foo(), - &CredentialValidationOptions::default(), - ); - assert!(validation_result.is_ok()); - } + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); - #[test] - pub(crate) fn test_multiple_subjects() { - let (mut credential, document, keypair) = create_valid_credential(); - let mut subjects: Vec = credential.credential_subject.clone().to_vec(); - subjects.push(subjects.get(0).unwrap().clone()); - let subjects = OneOrMany::Many(subjects); - credential.credential_subject = subjects; - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); - assert!(matches!( - validation_result.unwrap_err().cause, - DomainLinkageValidationErrorCause::MultipleCredentialSubjects - )); + + assert!(validation_result.is_ok()); } #[test] pub(crate) fn test_no_subject_id() { - let (mut credential, document, keypair) = create_valid_credential(); + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let mut credential: Credential = create_domain_linkage_credential(document.id()); + if let OneOrMany::One(ref mut subject) = credential.credential_subject { subject.id = None; } - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); + assert!(matches!( validation_result.unwrap_err().cause, DomainLinkageValidationErrorCause::MissingSubjectId @@ -377,17 +449,22 @@ mod tests { #[test] pub(crate) fn test_invalid_subject_id() { - let (mut credential, document, keypair) = create_valid_credential(); + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let mut credential: Credential = create_domain_linkage_credential(document.id()); + if let OneOrMany::One(ref mut subject) = credential.credential_subject { subject.id = Some(Url::parse("http://invalid.did").unwrap()); } - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); + assert!(matches!( validation_result.unwrap_err().cause, DomainLinkageValidationErrorCause::InvalidSubjectId @@ -396,17 +473,21 @@ mod tests { #[test] pub(crate) fn test_issuer_subject_mismatch() { - let (mut credential, document, keypair) = create_valid_credential(); + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let mut credential: Credential = create_domain_linkage_credential(document.id()); + if let OneOrMany::One(ref mut subject) = credential.credential_subject { subject.id = Some(Url::parse("did:abc:xyz").unwrap()); } - sign_credential(&mut credential, &document, keypair); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_credential( + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_credential( &document, - &credential, + &jwt, &url_foo(), &CredentialValidationOptions::default(), ); + assert!(matches!( validation_result.unwrap_err().cause, DomainLinkageValidationErrorCause::IssuerSubjectMismatch @@ -415,11 +496,13 @@ mod tests { #[test] pub(crate) fn test_multiple_credentials_for_same_did() { - let (mut credential, document, keypair) = create_valid_credential(); - sign_credential(&mut credential, &document, keypair); - let configuration: DomainLinkageConfiguration = - DomainLinkageConfiguration::new(vec![credential.clone(), credential]); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_linkage( + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let credential: Credential = create_domain_linkage_credential(document.id()); + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let configuration: DomainLinkageConfiguration = DomainLinkageConfiguration::new(vec![jwt.clone(), jwt]); + + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_linkage( &document, &configuration, &url_foo(), @@ -433,15 +516,18 @@ mod tests { #[test] pub(crate) fn test_valid_configuration() { - let (mut credential, document, keypair) = create_valid_credential(); - sign_credential(&mut credential, &document, keypair); - let configuration: DomainLinkageConfiguration = DomainLinkageConfiguration::new(vec![credential]); - let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::validate_linkage( + let (document, secret_key, fragment) = generate_jwk_document_with_keys(); + let credential: Credential = create_domain_linkage_credential(document.id()); + let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key); + + let configuration: DomainLinkageConfiguration = DomainLinkageConfiguration::new(vec![jwt]); + let validation_result: DomainLinkageValidationResult = DomainLinkageValidator::new().validate_linkage( &document, &configuration, &url_foo(), &CredentialValidationOptions::default(), ); + assert!(validation_result.is_ok()); } @@ -449,29 +535,53 @@ mod tests { Url::parse("https://foo.example.com").unwrap() } - fn create_valid_credential() -> (Credential, CoreDocument, KeyPair) { - let (doc, keypair) = generate_document_with_keys(); - let domain_1: Url = url_foo(); + fn create_domain_linkage_credential(did: &CoreDID) -> Credential { + let domain: Url = url_foo(); let mut domains: OrderedSet = OrderedSet::new(); - domains.append(domain_1.clone()); + domains.append(domain.clone()); let credential: Credential = DomainLinkageCredentialBuilder::new() - .issuer(Issuer::Url(doc.id().to_url().into())) - .origin(domain_1) + .issuer(did.clone()) + .origin(domain) .issuance_date(Timestamp::now_utc()) .expiration_date(Timestamp::now_utc().checked_add(Duration::days(365)).unwrap()) .build() .unwrap(); - (credential, doc, keypair) + credential } - fn sign_credential(credential: &mut Credential, document: &CoreDocument, keypair: KeyPair) { - document - .signer(keypair.private()) - .options(ProofOptions::default()) - .method(document.methods(None).get(0).unwrap().id()) - .sign(credential) - .unwrap(); + fn sign_credential_jwt( + credential: &Credential, + document: &CoreDocument, + fragment: &str, + secret_key: &SecretKey, + ) -> Jwt { + let payload: String = credential.serialize_jwt().unwrap(); + Jwt::new(sign_bytes(document, fragment, payload.as_ref(), secret_key).into()) + } + + fn sign_bytes(document: &CoreDocument, fragment: &str, payload: &[u8], secret_key: &SecretKey) -> Jws { + let method: &VerificationMethod = document.resolve_method(fragment, None).unwrap(); + let MethodData::PublicKeyJwk(ref jwk) = method.data() else { panic!("not a jwk"); }; + let alg: JwsAlgorithm = jwk.alg().unwrap_or("").parse().unwrap(); + + let header: JwsHeader = { + let mut header = JwsHeader::new(); + header.set_alg(alg); + header.set_kid(method.id().to_string()); + header + }; + + let encoding_options: CompactJwsEncodingOptions = CompactJwsEncodingOptions::NonDetached { + charset_requirements: CharSet::Default, + }; + + let jws_encoder: CompactJwsEncoder<'_> = + CompactJwsEncoder::new_with_options(payload, &header, encoding_options).unwrap(); + + let signature: [u8; 64] = secret_key.sign(jws_encoder.signing_input()).to_bytes(); + + Jws::new(jws_encoder.into_jws(&signature)) } } diff --git a/identity_credential/src/validator/errors.rs b/identity_credential/src/validator/errors.rs index 01a190b0a6..ecfc8a7fdc 100644 --- a/identity_credential/src/validator/errors.rs +++ b/identity_credential/src/validator/errors.rs @@ -169,6 +169,8 @@ impl From for &str { pub enum DomainLinkageValidationErrorCause { #[error("invalid credential")] CredentialValidationError, + #[error("invalid JWT")] + InvalidJwt, #[error("the expiration date is missing")] MissingExpirationDate, #[error("id property is not allowed")] diff --git a/identity_credential/src/validator/test_utils.rs b/identity_credential/src/validator/test_utils.rs index 689a5c66f2..432ee98743 100644 --- a/identity_credential/src/validator/test_utils.rs +++ b/identity_credential/src/validator/test_utils.rs @@ -1,6 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use crypto::signatures::ed25519::PublicKey; +use crypto::signatures::ed25519::SecretKey; use identity_core::common::Object; use identity_core::common::Timestamp; use identity_core::common::Url; @@ -12,12 +14,43 @@ use identity_core::utils::BaseEncoding; use identity_did::CoreDID; use identity_did::DID; use identity_document::document::CoreDocument; +use identity_verification::jwk::EdCurve; +use identity_verification::jwk::Jwk; +use identity_verification::jwk::JwkParamsOkp; +use identity_verification::jws::JwsAlgorithm; +use identity_verification::jwu; use identity_verification::VerificationMethod; use crate::credential::Credential; use crate::credential::CredentialBuilder; use crate::credential::Subject; +pub(crate) fn encode_public_ed25519_jwk(public_key: &PublicKey) -> Jwk { + let x = jwu::encode_b64(public_key.as_ref()); + let mut params = JwkParamsOkp::new(); + params.x = x; + params.d = None; + params.crv = EdCurve::Ed25519.name().to_owned(); + let mut jwk = Jwk::from_params(params); + jwk.set_alg(JwsAlgorithm::EdDSA.name()); + jwk +} + +pub(super) fn generate_jwk_document_with_keys() -> (CoreDocument, SecretKey, String) { + let secret: SecretKey = SecretKey::generate().unwrap(); + let public: PublicKey = secret.public_key(); + let jwk: Jwk = encode_public_ed25519_jwk(&public); + + let did: CoreDID = CoreDID::parse(format!("did:example:{}", BaseEncoding::encode_base58(&public))).unwrap(); + let fragment: String = "#jwk".to_owned(); + let document: CoreDocument = CoreDocument::builder(Object::new()) + .id(did.clone()) + .verification_method(VerificationMethod::new_from_jwk(did, jwk, Some(&fragment)).unwrap()) + .build() + .unwrap(); + (document, secret, fragment) +} + pub(super) fn generate_document_with_keys() -> (CoreDocument, KeyPair) { let keypair: KeyPair = KeyPair::new(KeyType::Ed25519).unwrap(); let did: CoreDID = CoreDID::parse(format!("did:example:{}", BaseEncoding::encode_base58(keypair.public()))).unwrap(); diff --git a/identity_credential/tests/fixtures/dn-config-extra-property.json b/identity_credential/tests/fixtures/dn-config-extra-property.json deleted file mode 100644 index 4c613ddd8b..0000000000 --- a/identity_credential/tests/fixtures/dn-config-extra-property.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "@context": "https://identity.foundation/.well-known/did-configuration/v1", - "linked_dids": [ - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://identity.foundation/.well-known/did-configuration/v1" - ], - "issuer": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM", - "issuanceDate": "2020-12-04T14:08:28-06:00", - "expirationDate": "2025-12-04T14:08:28-06:00", - "type": [ - "VerifiableCredential", - "DomainLinkageCredential" - ], - "credentialSubject": { - "id": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM", - "origin": "https://identity.foundation" - }, - "proof": { - "type": "Ed25519Signature2018", - "created": "2020-12-04T20:08:28.540Z", - "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..D0eDhglCMEjxDV9f_SNxsuU-r3ZB9GR4vaM9TYbyV7yzs1WfdUyYO8rFZdedHbwQafYy8YOpJ1iJlkSmB4JaDQ", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM#z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM" - } - } - ], - "other": "some value" -} \ No newline at end of file diff --git a/identity_credential/tests/fixtures/dn-config-invalid-context.json b/identity_credential/tests/fixtures/dn-config-invalid-context.json deleted file mode 100644 index d66f1a6e53..0000000000 --- a/identity_credential/tests/fixtures/dn-config-invalid-context.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@context": "https://identity.foundation/.well-known/did-configuration/v0", - "linked_dids": [ - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://identity.foundation/.well-known/did-configuration/v1" - ], - "issuer": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM", - "issuanceDate": "2020-12-04T14:08:28-06:00", - "expirationDate": "2025-12-04T14:08:28-06:00", - "type": [ - "VerifiableCredential", - "DomainLinkageCredential" - ], - "credentialSubject": { - "id": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM", - "origin": "https://identity.foundation" - }, - "proof": { - "type": "Ed25519Signature2018", - "created": "2020-12-04T20:08:28.540Z", - "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..D0eDhglCMEjxDV9f_SNxsuU-r3ZB9GR4vaM9TYbyV7yzs1WfdUyYO8rFZdedHbwQafYy8YOpJ1iJlkSmB4JaDQ", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM#z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM" - } - } - ] -} \ No newline at end of file diff --git a/identity_credential/tests/fixtures/dn-config-valid.json b/identity_credential/tests/fixtures/dn-config-valid.json deleted file mode 100644 index 848bf9e8a1..0000000000 --- a/identity_credential/tests/fixtures/dn-config-valid.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@context": "https://identity.foundation/.well-known/did-configuration/v1", - "linked_dids": [ - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://identity.foundation/.well-known/did-configuration/v1" - ], - "issuer": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM", - "issuanceDate": "2020-12-04T14:08:28-06:00", - "expirationDate": "2025-12-04T14:08:28-06:00", - "type": [ - "VerifiableCredential", - "DomainLinkageCredential" - ], - "credentialSubject": { - "id": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM", - "origin": "https://identity.foundation" - }, - "proof": { - "type": "Ed25519Signature2018", - "created": "2020-12-04T20:08:28.540Z", - "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..D0eDhglCMEjxDV9f_SNxsuU-r3ZB9GR4vaM9TYbyV7yzs1WfdUyYO8rFZdedHbwQafYy8YOpJ1iJlkSmB4JaDQ", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:key:z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM#z6MkoTHsgNNrby8JzCNQ1iRLyW5QQ6R8Xuu6AA8igGrMVPUM" - } - } - ] -} \ No newline at end of file diff --git a/identity_credential/tests/fixtures/domain-config-extra-property.json b/identity_credential/tests/fixtures/domain-config-extra-property.json new file mode 100644 index 0000000000..e6a56b5481 --- /dev/null +++ b/identity_credential/tests/fixtures/domain-config-extra-property.json @@ -0,0 +1,7 @@ +{ + "@context": "https://identity.foundation/.well-known/did-configuration/v1", + "linked_dids": [ + "eyJraWQiOiJkaWQ6ZXhhbXBsZTpEUnZvYXpzR05yNmFzWlNWUHVzWDIyN3ZYVnYzTDU4b1loZkFlUU12b3JieCNqd2siLCJhbGciOiJFZERTQSJ9.eyJleHAiOjE3MTc4MzY0NjcsImlzcyI6ImRpZDpleGFtcGxlOkRSdm9henNHTnI2YXNaU1ZQdXNYMjI3dlhWdjNMNThvWWhmQWVRTXZvcmJ4IiwibmJmIjoxNjg2MzAwNDY3LCJzdWIiOiJkaWQ6ZXhhbXBsZTpEUnZvYXpzR05yNmFzWlNWUHVzWDIyN3ZYVnYzTDU4b1loZkFlUU12b3JieCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2RpZC1jb25maWd1cmF0aW9uL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJEb21haW5MaW5rYWdlQ3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJvcmlnaW4iOiJodHRwczovL2Zvby5leGFtcGxlLmNvbS8ifX19.-XQpvYM0ZzQbn9cBaJKzgPu1XXMRS6KDe663JHbNvvD6ALD5yYJRTeh5ABzjHA3eBQ7D6yj5C3uSaXPe_A2hCw" + ], + "other": "some value" +} diff --git a/identity_credential/tests/fixtures/domain-config-invalid-context.json b/identity_credential/tests/fixtures/domain-config-invalid-context.json new file mode 100644 index 0000000000..05cb121416 --- /dev/null +++ b/identity_credential/tests/fixtures/domain-config-invalid-context.json @@ -0,0 +1,6 @@ +{ + "@context": "https://identity.foundation/.well-known/did-configuration/v0", + "linked_dids": [ + "eyJraWQiOiJkaWQ6ZXhhbXBsZTpEUnZvYXpzR05yNmFzWlNWUHVzWDIyN3ZYVnYzTDU4b1loZkFlUU12b3JieCNqd2siLCJhbGciOiJFZERTQSJ9.eyJleHAiOjE3MTc4MzY0NjcsImlzcyI6ImRpZDpleGFtcGxlOkRSdm9henNHTnI2YXNaU1ZQdXNYMjI3dlhWdjNMNThvWWhmQWVRTXZvcmJ4IiwibmJmIjoxNjg2MzAwNDY3LCJzdWIiOiJkaWQ6ZXhhbXBsZTpEUnZvYXpzR05yNmFzWlNWUHVzWDIyN3ZYVnYzTDU4b1loZkFlUU12b3JieCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2RpZC1jb25maWd1cmF0aW9uL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJEb21haW5MaW5rYWdlQ3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJvcmlnaW4iOiJodHRwczovL2Zvby5leGFtcGxlLmNvbS8ifX19.-XQpvYM0ZzQbn9cBaJKzgPu1XXMRS6KDe663JHbNvvD6ALD5yYJRTeh5ABzjHA3eBQ7D6yj5C3uSaXPe_A2hCw" + ] +} diff --git a/identity_credential/tests/fixtures/domain-config-valid.json b/identity_credential/tests/fixtures/domain-config-valid.json new file mode 100644 index 0000000000..46eb12ae49 --- /dev/null +++ b/identity_credential/tests/fixtures/domain-config-valid.json @@ -0,0 +1,6 @@ +{ + "@context": "https://identity.foundation/.well-known/did-configuration/v1", + "linked_dids": [ + "eyJraWQiOiJkaWQ6ZXhhbXBsZTpEUnZvYXpzR05yNmFzWlNWUHVzWDIyN3ZYVnYzTDU4b1loZkFlUU12b3JieCNqd2siLCJhbGciOiJFZERTQSJ9.eyJleHAiOjE3MTc4MzY0NjcsImlzcyI6ImRpZDpleGFtcGxlOkRSdm9henNHTnI2YXNaU1ZQdXNYMjI3dlhWdjNMNThvWWhmQWVRTXZvcmJ4IiwibmJmIjoxNjg2MzAwNDY3LCJzdWIiOiJkaWQ6ZXhhbXBsZTpEUnZvYXpzR05yNmFzWlNWUHVzWDIyN3ZYVnYzTDU4b1loZkFlUU12b3JieCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2RpZC1jb25maWd1cmF0aW9uL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJEb21haW5MaW5rYWdlQ3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJvcmlnaW4iOiJodHRwczovL2Zvby5leGFtcGxlLmNvbS8ifX19.-XQpvYM0ZzQbn9cBaJKzgPu1XXMRS6KDe663JHbNvvD6ALD5yYJRTeh5ABzjHA3eBQ7D6yj5C3uSaXPe_A2hCw" + ] +} diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 36e086cf9b..02d4c13aff 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -63,6 +63,9 @@ domain-linkage-fetch = ["identity_credential/domain-linkage-fetch"] # Enables JWS verification with the EdDSA algorithm eddsa = ["identity_verification/eddsa"] +# Exposes in-memory implementations of the storage traits intended exclusively for testing. +memstore = ["identity_storage/memstore"] + [package.metadata.docs.rs] # To build locally: # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --workspace --open diff --git a/identity_jose/src/jws/custom_verification/jws_verifier.rs b/identity_jose/src/jws/custom_verification/jws_verifier.rs index 0559840496..126cedb356 100644 --- a/identity_jose/src/jws/custom_verification/jws_verifier.rs +++ b/identity_jose/src/jws/custom_verification/jws_verifier.rs @@ -82,12 +82,14 @@ mod eddsa_verifier { use crate::jwk::EdCurve; use crate::jwk::JwkParamsOkp; use crate::jws::SignatureVerificationErrorKind; + /// An implementor of [`JwsVerifier`] that can handle the /// [`JwsAlgorithm::EdDSA`](crate::jws::JwsAlgorithm::EdDSA) algorithm. /// /// See [`Self::verify`](EdDSAJwsVerifier::verify). /// /// NOTE: This type can only be constructed when the `eddsa` feature is enabled. + #[derive(Debug)] #[non_exhaustive] pub struct EdDSAJwsVerifier; diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index eefd03fcbb..d980fe9362 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -35,10 +35,10 @@ proptest = { version = "1.0.0", default-features = false, features = ["std"] } tokio = { version = "1.23.0", default-features = false, features = ["macros", "sync", "rt"] } [features] +default = ["iota-document", "memstore"] # Exposes in-memory implementations of the storage traits intended exclusively for testing. memstore = ["dep:tokio", "dep:rand", "dep:iota-crypto", "identity_verification/eddsa"] # Enables `Send` + `Sync` bounds for the storage traits. send-sync-storage = [] # Implements the JwkStorageDocumentExt trait for IotaDocument iota-document = ["dep:identity_iota_core"] -default = ["iota-document", "memstore"]