Esempio n. 1
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 override object ReadJson(JsonReader reader, Type objectType, object existingValue,
                                        JsonSerializer serializer)
        {
            var item = JObject.Load(reader);

            var(_, messageType) = MessageUtils.ParseMessageType(item["@type"].ToObject <string>());

            IContentMessage message;

            switch (messageType)
            {
            case MessageTypes.ConnectionRequest:
                message = new ConnectionRequestMessage();
                break;

            case MessageTypes.ConnectionResponse:
                message = new ConnectionResponseMessage();
                break;

            case MessageTypes.CredentialOffer:
                message = new CredentialOfferMessage();
                break;

            case MessageTypes.CredentialRequest:
                message = new CredentialRequestMessage();
                break;

            case MessageTypes.Credential:
                message = new CredentialMessage();
                break;

            case MessageTypes.ProofRequest:
                message = new ProofRequestMessage();
                break;

            case MessageTypes.DisclosedProof:
                message = new ProofMessage();
                break;

            default: throw new TypeLoadException("Unsupported serialization type.");
            }

            serializer.Populate(item.CreateReader(), message);
            return(message);
        }
        /// <inheritdoc />
        public virtual async Task IssueCredentialAsync(IAgentContext agentContext, string issuerDid, string credentialId,
                                                       Dictionary <string, string> values)
        {
            var credential = await GetAsync(agentContext, credentialId);

            if (credential.State != CredentialState.Requested)
            {
                throw new AgentFrameworkException(ErrorCode.RecordInInvalidState,
                                                  $"Credential state was invalid. Expected '{CredentialState.Requested}', found '{credential.State}'");
            }

            var credentialCopy = credential.DeepCopy();

            if (values != null && values.Count > 0)
            {
                credential.ValuesJson = CredentialUtils.FormatCredentialValues(values);
            }

            var definitionRecord =
                await SchemaService.GetCredentialDefinitionAsync(agentContext.Wallet, credential.CredentialDefinitionId);

            var connection = await ConnectionService.GetAsync(agentContext, credential.ConnectionId);

            if (connection.State != ConnectionState.Connected)
            {
                throw new AgentFrameworkException(ErrorCode.RecordInInvalidState,
                                                  $"Connection state was invalid. Expected '{ConnectionState.Connected}', found '{connection.State}'");
            }

            string            revocationRegistryId = null;
            BlobStorageReader tailsReader          = null;

            if (definitionRecord.SupportsRevocation)
            {
                var revocationRecordSearch = await RecordService.SearchAsync <RevocationRegistryRecord>(
                    agentContext.Wallet, SearchQuery.Equal(nameof(RevocationRegistryRecord.CredentialDefinitionId), definitionRecord.Id), null, 5);

                var revocationRecord = revocationRecordSearch.Single(); // TODO: Credential definition can have multiple revocation registries

                revocationRegistryId = revocationRecord.Id;
                tailsReader          = await TailsService.OpenTailsAsync(revocationRecord.TailsFile);
            }

            var issuedCredential = await AnonCreds.IssuerCreateCredentialAsync(agentContext.Wallet, credential.OfferJson,
                                                                               credential.RequestJson, credential.ValuesJson, revocationRegistryId, tailsReader);

            if (definitionRecord.SupportsRevocation)
            {
                await LedgerService.SendRevocationRegistryEntryAsync(agentContext.Wallet, agentContext.Pool, issuerDid,
                                                                     revocationRegistryId,
                                                                     "CL_ACCUM", issuedCredential.RevocRegDeltaJson);

                credential.CredentialRevocationId = issuedCredential.RevocId;
            }

            var msg = new CredentialMessage
            {
                CredentialJson       = issuedCredential.CredentialJson,
                RevocationRegistryId = revocationRegistryId
            };

            await credential.TriggerAsync(CredentialTrigger.Issue);

            await RecordService.UpdateAsync(agentContext.Wallet, credential);

            try
            {
                await MessageService.SendAsync(agentContext.Wallet, msg, connection);
            }
            catch (Exception e)
            {
                await RecordService.UpdateAsync(agentContext.Wallet, credentialCopy);

                throw new AgentFrameworkException(ErrorCode.A2AMessageTransmissionError, "Failed to send credential request message", e);
            }
        }
        /// <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);
        }
        /// <inheritdoc />
        public virtual async Task <(CredentialMessage, CredentialRecord)> CreateCredentialAsync(IAgentContext agentContext, string issuerDid, string credentialId,
                                                                                                IEnumerable <CredentialPreviewAttribute> values)
        {
            var credential = await GetAsync(agentContext, credentialId);

            if (credential.State != CredentialState.Requested)
            {
                throw new AgentFrameworkException(ErrorCode.RecordInInvalidState,
                                                  $"Credential state was invalid. Expected '{CredentialState.Requested}', found '{credential.State}'");
            }

            if (values != null && values.Any())
            {
                credential.CredentialAttributesValues = values;
            }

            var definitionRecord =
                await SchemaService.GetCredentialDefinitionAsync(agentContext.Wallet, credential.CredentialDefinitionId);

            var connection = await ConnectionService.GetAsync(agentContext, credential.ConnectionId);

            if (connection.State != ConnectionState.Connected)
            {
                throw new AgentFrameworkException(ErrorCode.RecordInInvalidState,
                                                  $"Connection state was invalid. Expected '{ConnectionState.Connected}', found '{connection.State}'");
            }

            string            revocationRegistryId = null;
            BlobStorageReader tailsReader          = null;

            if (definitionRecord.SupportsRevocation)
            {
                var revocationRecordSearch = await RecordService.SearchAsync <RevocationRegistryRecord>(
                    agentContext.Wallet, SearchQuery.Equal(nameof(RevocationRegistryRecord.CredentialDefinitionId), definitionRecord.Id), null, 5);

                var revocationRecord = revocationRecordSearch.Single(); // TODO: Credential definition can have multiple revocation registries

                revocationRegistryId = revocationRecord.Id;
                tailsReader          = await TailsService.OpenTailsAsync(revocationRecord.TailsFile);
            }

            var issuedCredential = await AnonCreds.IssuerCreateCredentialAsync(agentContext.Wallet, credential.OfferJson,
                                                                               credential.RequestJson, CredentialUtils.FormatCredentialValues(credential.CredentialAttributesValues), revocationRegistryId, tailsReader);

            if (definitionRecord.SupportsRevocation)
            {
                var paymentInfo = await PaymentService.GetTransactionCostAsync(agentContext, TransactionTypes.REVOC_REG_ENTRY);

                await LedgerService.SendRevocationRegistryEntryAsync(context : agentContext,
                                                                     issuerDid : issuerDid,
                                                                     revocationRegistryDefinitionId : revocationRegistryId,
                                                                     revocationDefinitionType : "CL_ACCUM",
                                                                     value : issuedCredential.RevocRegDeltaJson,
                                                                     paymentInfo : paymentInfo);

                credential.CredentialRevocationId = issuedCredential.RevocId;

                if (paymentInfo != null)
                {
                    await RecordService.UpdateAsync(agentContext.Wallet, paymentInfo.PaymentAddress);
                }
            }

            await credential.TriggerAsync(CredentialTrigger.Issue);

            await RecordService.UpdateAsync(agentContext.Wallet, credential);

            var threadId = credential.GetTag(TagConstants.LastThreadId);

            var credentialMsg = new CredentialMessage
            {
                CredentialJson       = issuedCredential.CredentialJson,
                RevocationRegistryId = revocationRegistryId
            };

            credentialMsg.ThreadFrom(threadId);

            return(credentialMsg, credential);
        }
        /// <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);
        }