-
Notifications
You must be signed in to change notification settings - Fork 91
/
Copy path6_create_vp.rs
232 lines (199 loc) · 10.6 KB
/
6_create_vp.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
//! This example shows how to create a Verifiable Presentation and validate it.
//! A Verifiable Presentation is the format in which a (collection of) Verifiable Credential(s) gets shared.
//! It is signed by the subject, to prove control over the Verifiable Credential with a nonce or timestamp.
//!
//! cargo run --release --example 6_create_vp
use examples::create_did;
use examples::MemStorage;
use identity_eddsa_verifier::EdDSAJwsVerifier;
use identity_iota::core::Object;
use identity_iota::credential::DecodedJwtCredential;
use identity_iota::credential::DecodedJwtPresentation;
use identity_iota::credential::Jwt;
use identity_iota::credential::JwtCredentialValidatorUtils;
use identity_iota::credential::JwtPresentationOptions;
use identity_iota::credential::JwtPresentationValidationOptions;
use identity_iota::credential::JwtPresentationValidator;
use identity_iota::credential::JwtPresentationValidatorUtils;
use identity_iota::credential::Presentation;
use identity_iota::credential::PresentationBuilder;
use identity_iota::did::CoreDID;
use identity_iota::document::verifiable::JwsVerificationOptions;
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;
use iota_sdk::client::Password;
use iota_sdk::types::block::address::Address;
use examples::random_stronghold_path;
use examples::API_ENDPOINT;
use identity_iota::core::json;
use identity_iota::core::Duration;
use identity_iota::core::FromJson;
use identity_iota::core::Timestamp;
use identity_iota::core::Url;
use identity_iota::credential::Credential;
use identity_iota::credential::CredentialBuilder;
use identity_iota::credential::FailFast;
use identity_iota::credential::JwtCredentialValidationOptions;
use identity_iota::credential::JwtCredentialValidator;
use identity_iota::credential::Subject;
use identity_iota::credential::SubjectHolderRelationship;
use identity_iota::did::DID;
use identity_iota::iota::IotaDocument;
use identity_iota::resolver::Resolver;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// ===========================================================================
// Step 1: Create identities for the issuer and the holder.
// ===========================================================================
// Create a new client to interact with the IOTA ledger.
let client: Client = Client::builder()
.with_primary_node(API_ENDPOINT, None)?
.finish()
.await?;
// Create an identity for the issuer with one verification method `key-1`.
let mut secret_manager_issuer: SecretManager = SecretManager::Stronghold(
StrongholdSecretManager::builder()
.password(Password::from("secure_password_1".to_owned()))
.build(random_stronghold_path())?,
);
let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new());
let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) =
create_did(&client, &mut secret_manager_issuer, &storage_issuer).await?;
// Create an identity for the holder, in this case also the subject.
let mut secret_manager_alice: SecretManager = SecretManager::Stronghold(
StrongholdSecretManager::builder()
.password(Password::from("secure_password_2".to_owned()))
.build(random_stronghold_path())?,
);
let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new());
let (_, alice_document, fragment_alice): (Address, IotaDocument, String) =
create_did(&client, &mut secret_manager_alice, &storage_alice).await?;
// ===========================================================================
// Step 2: Issuer creates and signs a Verifiable Credential.
// ===========================================================================
// Create a credential subject indicating the degree earned by Alice.
let subject: Subject = Subject::from_json_value(json!({
"id": alice_document.id().as_str(),
"name": "Alice",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts",
},
"GPA": "4.0",
}))?;
// Build credential using subject above and issuer.
let credential: Credential = CredentialBuilder::default()
.id(Url::parse("https://example.edu/credentials/3732")?)
.issuer(Url::parse(issuer_document.id().as_str())?)
.type_("UniversityDegreeCredential")
.subject(subject)
.build()?;
let credential_jwt: Jwt = issuer_document
.create_credential_jwt(
&credential,
&storage_issuer,
&fragment_issuer,
&JwsSignatureOptions::default(),
None,
)
.await?;
// Before sending this credential to the holder the issuer wants to validate that some properties
// of the credential satisfy their expectations.
// Validate the credential's signature using the issuer's DID Document, the credential's semantic structure,
// that the issuance date is not in the future and that the expiration date is not in the past:
JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default())
.validate::<_, Object>(
&credential_jwt,
&issuer_document,
&JwtCredentialValidationOptions::default(),
FailFast::FirstError,
)
.unwrap();
println!("VC successfully validated");
// ===========================================================================
// Step 3: Issuer sends the Verifiable Credential to the holder.
// ===========================================================================
println!("Sending credential (as JWT) to the holder: {credential:#}");
// ===========================================================================
// Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation.
// ===========================================================================
// A unique random challenge generated by the requester per presentation can mitigate replay attacks.
let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440";
// The verifier and holder also agree that the signature should have an expiry date
// 10 minutes from now.
let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap();
// ===========================================================================
// Step 5: Holder creates and signs a verifiable presentation from the issued credential.
// ===========================================================================
// Create an unsigned Presentation from the previously issued Verifiable Credential.
let presentation: Presentation<Jwt> =
PresentationBuilder::new(alice_document.id().to_url().into(), Default::default())
.credential(credential_jwt)
.build()?;
// Create a JWT verifiable presentation using the holder's verification method
// and include the requested challenge and expiry timestamp.
let presentation_jwt: Jwt = alice_document
.create_presentation_jwt(
&presentation,
&storage_alice,
&fragment_alice,
&JwsSignatureOptions::default().nonce(challenge.to_owned()),
&JwtPresentationOptions::default().expiration_date(expires),
)
.await?;
// ===========================================================================
// Step 6: Holder sends a verifiable presentation to the verifier.
// ===========================================================================
println!("Sending presentation (as JWT) to the verifier: {presentation:#}");
// ===========================================================================
// Step 7: Verifier receives the Verifiable Presentation and verifies it.
// ===========================================================================
// The verifier wants the following requirements to be satisfied:
// - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks)
// - JWT verification of the credentials.
// - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property
// - The issuance date must not be in the future.
let presentation_verifier_options: JwsVerificationOptions =
JwsVerificationOptions::default().nonce(challenge.to_owned());
// Resolve the holder's document.
let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?;
let holder: IotaDocument = client.resolve(&holder_did).await?;
// Validate presentation. Note that this doesn't validate the included credentials.
let presentation_validation_options =
JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options);
let presentation: DecodedJwtPresentation<Jwt> = JwtPresentationValidator::with_signature_verifier(
EdDSAJwsVerifier::default(),
)
.validate(&presentation_jwt, &holder, &presentation_validation_options)?;
// Concurrently resolve the issuers' documents.
let jwt_credentials: &Vec<Jwt> = &presentation.presentation.verifiable_credential;
let issuers: Vec<CoreDID> = jwt_credentials
.iter()
.map(JwtCredentialValidatorUtils::extract_issuer_from_jwt)
.collect::<Result<Vec<CoreDID>, _>>()?;
let issuers_documents: Vec<IotaDocument> = client.resolve_multiple(&issuers).await?;
// Validate the credentials in the presentation.
let credential_validator: JwtCredentialValidator<EdDSAJwsVerifier> =
JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default());
let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default()
.subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject);
for (index, jwt_vc) in jwt_credentials.iter().enumerate() {
// SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it.
let issuer_document: &IotaDocument = &issuers_documents[index];
let _decoded_credential: DecodedJwtCredential<Object> = credential_validator
.validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError)
.unwrap();
}
// Since no errors were thrown by `verify_presentation` we know that the validation was successful.
println!("VP successfully validated: {:#?}", presentation.presentation);
// Note that we did not declare a latest allowed issuance date for credentials. This is because we only want to check
// that the credentials do not have an issuance date in the future which is a default check.
Ok(())
}