Ejemplo n.º 1
0
        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());
        }
Ejemplo n.º 3
0
        /// <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));
        }
Ejemplo n.º 4
0
        /// <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);
        }
Ejemplo n.º 5
0
        /// <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));
        }
Ejemplo n.º 8
0
        /// <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);
        }