private async Task <string> BuildRevocationStatesAsync(IAgentContext agentContext, IEnumerable <CredentialInfo> credentialObjects, ProofRequest proofRequest, RequestedCredentials requestedCredentials) { var allCredentials = new List <RequestedAttribute>(); allCredentials.AddRange(requestedCredentials.RequestedAttributes.Values); allCredentials.AddRange(requestedCredentials.RequestedPredicates.Values); var result = new Dictionary <string, Dictionary <string, JObject> >(); foreach (var requestedCredential in allCredentials) { // ReSharper disable once PossibleMultipleEnumeration var credential = credentialObjects.First(x => x.Referent == requestedCredential.CredentialId); if (credential.RevocationRegistryId == null || (proofRequest.NonRevoked == null)) { continue; } var registryDefinition = await LedgerService.LookupRevocationRegistryDefinitionAsync( agentContext : agentContext, registryId : credential.RevocationRegistryId); var delta = await LedgerService.LookupRevocationRegistryDeltaAsync( agentContext : agentContext, revocationRegistryId : credential.RevocationRegistryId, // Ledger will not return correct revocation state if the 'from' field // is other than 0 from : 0, //proofRequest.NonRevoked.From, to : proofRequest.NonRevoked.To); var tailsfile = await TailsService.EnsureTailsExistsAsync(agentContext, credential.RevocationRegistryId); var tailsReader = await TailsService.OpenTailsAsync(tailsfile); var state = await AnonCreds.CreateRevocationStateAsync( blobStorageReader : tailsReader, revRegDef : registryDefinition.ObjectJson, revRegDelta : delta.ObjectJson, timestamp : (long)delta.Timestamp, credRevId : credential.CredentialRevocationId); if (!result.ContainsKey(credential.RevocationRegistryId)) { result.Add(credential.RevocationRegistryId, new Dictionary <string, JObject>()); } requestedCredential.Timestamp = (long)delta.Timestamp; if (!result[credential.RevocationRegistryId].ContainsKey($"{delta.Timestamp}")) { result[credential.RevocationRegistryId].Add($"{delta.Timestamp}", JObject.Parse(state)); } } return(result.ToJson()); }
private async Task <string> BuildRevocationStatesAsync(Pool pool, IEnumerable <CredentialInfo> credentialObjects, RequestedCredentials requestedCredentials) { var allCredentials = new List <RequestedAttribute>(); allCredentials.AddRange(requestedCredentials.RequestedAttributes.Values); allCredentials.AddRange(requestedCredentials.RequestedPredicates.Values); var result = new Dictionary <string, Dictionary <string, JObject> >(); foreach (var requestedCredential in allCredentials) { // ReSharper disable once PossibleMultipleEnumeration var credential = credentialObjects.First(x => x.Referent == requestedCredential.CredentialId); if (credential.RevocationRegistryId == null) { continue; } var timestamp = requestedCredential.Timestamp ?? throw new Exception( "Timestamp must be provided for credential that supports revocation"); if (result.ContainsKey(credential.RevocationRegistryId) && result[credential.RevocationRegistryId].ContainsKey($"{timestamp}")) { continue; } var registryDefinition = await LedgerService.LookupRevocationRegistryDefinitionAsync(pool, credential.RevocationRegistryId); var delta = await LedgerService.LookupRevocationRegistryDeltaAsync(pool, credential.RevocationRegistryId, -1, timestamp); var tailsfile = await TailsService.EnsureTailsExistsAsync(pool, credential.RevocationRegistryId); var tailsReader = await TailsService.OpenTailsAsync(tailsfile); var state = await AnonCreds.CreateRevocationStateAsync(tailsReader, registryDefinition.ObjectJson, delta.ObjectJson, (long)delta.Timestamp, credential.CredentialRevocationId); if (!result.ContainsKey(credential.RevocationRegistryId)) { result.Add(credential.RevocationRegistryId, new Dictionary <string, JObject>()); } result[credential.RevocationRegistryId].Add($"{timestamp}", JObject.Parse(state)); // TODO: Revocation state should provide the state between a certain period // that can be requested in the proof request in the 'non_revocation' field. } return(result.ToJson()); }
/// <inheritdoc /> public virtual async Task <string> EnsureTailsExistsAsync(IAgentContext agentContext, string revocationRegistryId) { var revocationRegistry = await LedgerService.LookupRevocationRegistryDefinitionAsync(agentContext, revocationRegistryId); var tailsUri = JObject.Parse(revocationRegistry.ObjectJson)["value"]["tailsLocation"].ToObject <string>(); var tailsFileName = JObject.Parse(revocationRegistry.ObjectJson)["value"]["tailsHash"].ToObject <string>(); var tailsfile = Path.Combine(AgentOptions.RevocationRegistryDirectory, tailsFileName); if (!Directory.Exists(AgentOptions.RevocationRegistryDirectory)) { Directory.CreateDirectory(AgentOptions.RevocationRegistryDirectory); } try { if (!File.Exists(tailsfile)) { File.WriteAllBytes( path: tailsfile, bytes: await HttpClient.GetByteArrayAsync(new Uri(tailsUri))); } } catch (Exception e) { throw new AriesFrameworkException( errorCode: ErrorCode.RevocationRegistryUnavailable, message: $"Unable to retrieve revocation registry from the specified URL '{tailsUri}'. Error: {e.Message}"); } return(Path.GetFileName(tailsfile)); }
/// <inheritdoc /> public virtual async Task <string> ProcessCredentialAsync(IAgentContext agentContext, CredentialIssueMessage credential, ConnectionRecord connection) { var credentialAttachment = credential.Credentials.FirstOrDefault(x => x.Id == "libindy-cred-0") ?? throw new ArgumentException("Credential attachment not found"); var credentialJson = credentialAttachment.Data.Base64.GetBytesFromBase64().GetUTF8String(); var credentialJobj = JObject.Parse(credentialJson); var definitionId = credentialJobj["cred_def_id"].ToObject <string>(); var revRegId = credentialJobj["rev_reg_id"]?.ToObject <string>(); var credentialRecord = await Policy.Handle <AriesFrameworkException>() .RetryAsync(3, async(ex, retry) => { await Task.Delay((int)Math.Pow(retry, 2) * 100); }) .ExecuteAsync(() => this.GetByThreadIdAsync(agentContext, credential.GetThreadId())); if (credentialRecord.State != CredentialState.Requested) { throw new AriesFrameworkException(ErrorCode.RecordInInvalidState, $"Credential state was invalid. Expected '{CredentialState.Requested}', found '{credentialRecord.State}'"); } var credentialDefinition = await LedgerService.LookupDefinitionAsync(agentContext, definitionId); string revocationRegistryDefinitionJson = null; if (!string.IsNullOrEmpty(revRegId)) { // If credential supports revocation, lookup registry definition var revocationRegistry = await LedgerService.LookupRevocationRegistryDefinitionAsync(agentContext, revRegId); revocationRegistryDefinitionJson = revocationRegistry.ObjectJson; credentialRecord.RevocationRegistryId = revRegId; } var credentialId = await AnonCreds.ProverStoreCredentialAsync( wallet : agentContext.Wallet, credId : credentialRecord.Id, credReqMetadataJson : credentialRecord.CredentialRequestMetadataJson, credJson : credentialJson, credDefJson : credentialDefinition.ObjectJson, revRegDefJson : revocationRegistryDefinitionJson); credentialRecord.CredentialId = credentialId; await credentialRecord.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(agentContext.Wallet, credentialRecord); EventAggregator.Publish(new ServiceMessageProcessingEvent { RecordId = credentialRecord.Id, MessageType = credential.Type, ThreadId = credential.GetThreadId() }); return(credentialRecord.Id); }
/// <inheritdoc /> public virtual async Task ProcessCredentialAsync(Pool pool, Wallet wallet, CredentialMessage credential) { var(didOrKey, _) = MessageUtils.ParseMessageType(credential.Type); var connectionSearch = await ConnectionService.ListAsync(wallet, new SearchRecordQuery { { TagConstants.MyDid, didOrKey } }); if (!connectionSearch.Any()) { throw new Exception($"Can't find connection record for type {credential.Type}"); } var connection = connectionSearch.First(); var(details, _) = await MessageSerializer.UnpackSealedAsync <CredentialDetails>(credential.Content, wallet, connection.MyVk); var offer = JObject.Parse(details.CredentialJson); var definitionId = offer["cred_def_id"].ToObject <string>(); var schemaId = offer["schema_id"].ToObject <string>(); var revRegId = offer["rev_reg_id"]?.ToObject <string>(); var credentialSearch = await RecordService.SearchAsync <CredentialRecord>(wallet, new SearchRecordQuery { { TagConstants.SchemaId, schemaId }, { TagConstants.DefinitionId, definitionId }, { TagConstants.ConnectionId, connection.GetId() } }, null, 1); var credentialRecord = credentialSearch.Single(); // TODO: Should throw or resolve conflict gracefully if multiple credential records are found var credentialDefinition = await LedgerService.LookupDefinitionAsync(pool, connection.MyDid, definitionId); string revocationRegistryDefinitionJson = null; if (!string.IsNullOrEmpty(revRegId)) { // If credential supports revocation, lookup registry definition var revocationRegistry = await LedgerService.LookupRevocationRegistryDefinitionAsync(pool, connection.MyDid, revRegId); revocationRegistryDefinitionJson = revocationRegistry.ObjectJson; } var credentialId = await AnonCreds.ProverStoreCredentialAsync(wallet, null, credentialRecord.CredentialRequestMetadataJson, details.CredentialJson, credentialDefinition.ObjectJson, revocationRegistryDefinitionJson); credentialRecord.CredentialId = credentialId; await credentialRecord.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(wallet, credentialRecord); }
/// <inheritdoc /> public virtual async Task <string> ProcessCredentialAsync(IAgentContext agentContext, CredentialMessage credential, ConnectionRecord connection) { var offer = JObject.Parse(credential.CredentialJson); var definitionId = offer["cred_def_id"].ToObject <string>(); var revRegId = offer["rev_reg_id"]?.ToObject <string>(); var credentialRecord = await this.GetByThreadIdAsync(agentContext, credential.GetThreadId()); if (credentialRecord.State != CredentialState.Requested) { throw new AgentFrameworkException(ErrorCode.RecordInInvalidState, $"Credential state was invalid. Expected '{CredentialState.Requested}', found '{credentialRecord.State}'"); } var credentialDefinition = await LedgerService.LookupDefinitionAsync(await agentContext.Pool, definitionId); string revocationRegistryDefinitionJson = null; if (!string.IsNullOrEmpty(revRegId)) { // If credential supports revocation, lookup registry definition var revocationRegistry = await LedgerService.LookupRevocationRegistryDefinitionAsync(await agentContext.Pool, revRegId); revocationRegistryDefinitionJson = revocationRegistry.ObjectJson; } var credentialId = await AnonCreds.ProverStoreCredentialAsync(agentContext.Wallet, null, credentialRecord.CredentialRequestMetadataJson, credential.CredentialJson, credentialDefinition.ObjectJson, revocationRegistryDefinitionJson); credentialRecord.CredentialId = credentialId; credentialRecord.CredentialAttributesValues = null; await credentialRecord.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(agentContext.Wallet, credentialRecord); EventAggregator.Publish(new ServiceMessageProcessingEvent { RecordId = credentialRecord.Id, MessageType = credential.Type, ThreadId = credential.GetThreadId() }); return(credentialRecord.Id); }
/// <inheritdoc /> public virtual async Task <string> EnsureTailsExistsAsync(Pool pool, string revocationRegistryId) { var revocationRegistry = await LedgerService.LookupRevocationRegistryDefinitionAsync(pool, revocationRegistryId); var tailsUri = JObject.Parse(revocationRegistry.ObjectJson)["value"]["tailsLocation"].ToObject <string>(); var tailsfile = Path.Combine(EnvironmentUtils.GetTailsPath(), new Uri(tailsUri).Segments.Last()); if (!File.Exists(tailsfile)) { File.WriteAllBytes( path: tailsfile, bytes: await HttpClient.GetByteArrayAsync(tailsUri)); } return(Path.GetFileName(tailsfile)); }
/// <inheritdoc /> public virtual async Task <string> EnsureTailsExistsAsync(IAgentContext agentContext, string revocationRegistryId) { var revocationRegistry = await LedgerService.LookupRevocationRegistryDefinitionAsync(agentContext, revocationRegistryId); var tailsUri = JObject.Parse(revocationRegistry.ObjectJson)["value"]["tailsLocation"].ToObject <string>(); var tailsFileName = JObject.Parse(revocationRegistry.ObjectJson)["value"]["tailsHash"].ToObject <string>(); var tailsfile = Path.Combine(AgentOptions.RevocationRegistryDirectory, tailsFileName); var hash = Multibase.Base58.Decode(tailsFileName); if (!Directory.Exists(AgentOptions.RevocationRegistryDirectory)) { Directory.CreateDirectory(AgentOptions.RevocationRegistryDirectory); } try { if (!File.Exists(tailsfile)) { var bytes = await HttpClient.GetByteArrayAsync(new Uri(tailsUri)); // Check hash using var sha256 = SHA256.Create(); var computedHash = sha256.ComputeHash(bytes); if (!computedHash.SequenceEqual(hash)) { throw new Exception("Tails file hash didn't match"); } File.WriteAllBytes( path: tailsfile, bytes: bytes); } } catch (Exception e) { throw new AriesFrameworkException( errorCode: ErrorCode.RevocationRegistryUnavailable, message: $"Unable to retrieve revocation registry from the specified URL '{tailsUri}'. Error: {e.Message}"); } return(Path.GetFileName(tailsfile)); }
/// <inheritdoc /> public virtual async Task <string> ProcessCredentialAsync(IAgentContext agentContext, CredentialMessage credential, ConnectionRecord connection) { var offer = JObject.Parse(credential.CredentialJson); var definitionId = offer["cred_def_id"].ToObject <string>(); var schemaId = offer["schema_id"].ToObject <string>(); var revRegId = offer["rev_reg_id"]?.ToObject <string>(); // TODO: Replace this with thread lookup // Currently, this is unable to process multiple offers and requests reliably var credentialSearch = await RecordService.SearchAsync <CredentialRecord>(agentContext.Wallet, SearchQuery.And( SearchQuery.Equal(nameof(CredentialRecord.SchemaId), schemaId), SearchQuery.Equal(nameof(CredentialRecord.CredentialDefinitionId), definitionId), SearchQuery.Equal(nameof(CredentialRecord.ConnectionId), connection.Id), SearchQuery.Equal(nameof(CredentialRecord.State), CredentialState.Requested.ToString("G")) ), null, 5); if (credentialSearch.Count == 0) { throw new AgentFrameworkException(ErrorCode.RecordNotFound, "Credential record not found"); } var credentialRecord = credentialSearch.Single(); // TODO: Should resolve if multiple credential records are found if (credentialRecord.State != CredentialState.Requested) { throw new AgentFrameworkException(ErrorCode.RecordInInvalidState, $"Credential state was invalid. Expected '{CredentialState.Requested}', found '{credentialRecord.State}'"); } var credentialDefinition = await LedgerService.LookupDefinitionAsync(agentContext.Pool, definitionId); string revocationRegistryDefinitionJson = null; if (!string.IsNullOrEmpty(revRegId)) { // If credential supports revocation, lookup registry definition var revocationRegistry = await LedgerService.LookupRevocationRegistryDefinitionAsync(agentContext.Pool, revRegId); revocationRegistryDefinitionJson = revocationRegistry.ObjectJson; } var credentialId = await AnonCreds.ProverStoreCredentialAsync(agentContext.Wallet, null, credentialRecord.CredentialRequestMetadataJson, credential.CredentialJson, credentialDefinition.ObjectJson, revocationRegistryDefinitionJson); credentialRecord.CredentialId = credentialId; await credentialRecord.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(agentContext.Wallet, credentialRecord); EventAggregator.Publish(new ServiceMessageProcessingEvent { RecordId = credentialRecord.Id, MessageType = credential.Type, }); return(credentialRecord.Id); }