public void CanFormatStringCredentialValues() { var attributeValues = new List <CredentialPreviewAttribute> { new CredentialPreviewAttribute("first_name", "Test"), new CredentialPreviewAttribute("last_name", "holder") }; var expectedResult = new { first_name = new { raw = "Test", encoded = CredentialUtils.GetEncoded("Test") }, last_name = new { raw = "holder", encoded = CredentialUtils.GetEncoded("holder") } }.ToJson(); var formatedCredentialUtils = CredentialUtils.FormatCredentialValues(attributeValues); var expectedResultObj = JObject.Parse(expectedResult); var formatedCredObj = JObject.Parse(formatedCredentialUtils); Assert.Equal(expectedResultObj, formatedCredObj); }
private async Task <(IssuerCreateCredentialResult, RevocationRegistryRecord)> IssueCredentialSafeAsync( IAgentContext agentContext, DefinitionRecord definitionRecord, CredentialRecord credentialRecord) { BlobStorageReader tailsReader = null; RevocationRegistryRecord revocationRecord = null; if (definitionRecord.SupportsRevocation) { revocationRecord = await RecordService.GetAsync <RevocationRegistryRecord>(agentContext.Wallet, definitionRecord.CurrentRevocationRegistryId); tailsReader = await TailsService.OpenTailsAsync(revocationRecord.TailsFile); } try { return(await AnonCreds.IssuerCreateCredentialAsync( agentContext.Wallet, credentialRecord.OfferJson, credentialRecord.RequestJson, CredentialUtils.FormatCredentialValues(credentialRecord.CredentialAttributesValues), definitionRecord.CurrentRevocationRegistryId, tailsReader), revocationRecord); } catch (RevocationRegistryFullException) { if (!definitionRecord.RevocationAutoScale) { throw; } } var registryIndex = definitionRecord.CurrentRevocationRegistryId.Split(':').LastOrDefault()?.Split('-').FirstOrDefault(); string registryTag; if (int.TryParse(registryIndex, out var currentIndex)) { registryTag = $"{currentIndex + 1}-{definitionRecord.MaxCredentialCount}"; } else { registryTag = $"1-{definitionRecord.MaxCredentialCount}"; } var(_, nextRevocationRecord) = await SchemaService.CreateRevocationRegistryAsync(agentContext, registryTag, definitionRecord); definitionRecord.CurrentRevocationRegistryId = nextRevocationRecord.Id; await RecordService.UpdateAsync(agentContext.Wallet, definitionRecord); tailsReader = await TailsService.OpenTailsAsync(nextRevocationRecord.TailsFile); return(await AnonCreds.IssuerCreateCredentialAsync( agentContext.Wallet, credentialRecord.OfferJson, credentialRecord.RequestJson, CredentialUtils.FormatCredentialValues(credentialRecord.CredentialAttributesValues), nextRevocationRecord.Id, tailsReader), nextRevocationRecord); }
/// <inheritdoc /> public virtual async Task <CredentialOfferMessage> CreateOfferAsync(Wallet wallet, DefaultCreateOfferConfiguration config) { Logger.LogInformation(LoggingEvents.CreateCredentialOffer, "DefinitionId {0}, ConnectionId {1}, IssuerDid {2}", config.CredentialDefinitionId, config.ConnectionId, config.IssuerDid); var connection = await ConnectionService.GetAsync(wallet, config.ConnectionId); var offerJson = await AnonCreds.IssuerCreateCredentialOfferAsync(wallet, config.CredentialDefinitionId); var nonce = JObject.Parse(offerJson)["nonce"].ToObject <string>(); // Write offer record to local wallet var credentialRecord = new CredentialRecord { Id = Guid.NewGuid().ToString(), CredentialDefinitionId = config.CredentialDefinitionId, OfferJson = offerJson, ValuesJson = CredentialUtils.FormatCredentialValues(config.CredentialAttributeValues), State = CredentialState.Offered, ConnectionId = connection.GetId(), }; credentialRecord.Tags.Add(TagConstants.Nonce, nonce); credentialRecord.Tags.Add(TagConstants.Role, TagConstants.Issuer); credentialRecord.Tags.Add(TagConstants.ConnectionId, connection.GetId()); if (!string.IsNullOrEmpty(config.IssuerDid)) { credentialRecord.Tags.Add(TagConstants.IssuerDid, config.IssuerDid); } if (config.Tags != null) { foreach (var tag in config.Tags) { if (!credentialRecord.Tags.Keys.Contains(tag.Key)) { credentialRecord.Tags.Add(tag.Key, tag.Value); } } } await RecordService.AddAsync(wallet, credentialRecord); var credentialOffer = await MessageSerializer.PackSealedAsync <CredentialOfferMessage>( new CredentialOfferDetails { OfferJson = offerJson }, wallet, connection.MyVk, connection.TheirVk); credentialOffer.Type = MessageUtils.FormatDidMessageType(connection.TheirDid, MessageTypes.CredentialOffer); return(credentialOffer); }
/// <inheritdoc /> public virtual async Task AcceptOfferAsync(IAgentContext agentContext, string credentialId, Dictionary <string, string> attributeValues = null) { var credential = await GetAsync(agentContext, credentialId); if (credential.State != CredentialState.Offered) { throw new AgentFrameworkException(ErrorCode.RecordInInvalidState, $"Credential state was invalid. Expected '{CredentialState.Offered}', found '{credential.State}'"); } var credentialCopy = credential.DeepCopy(); var connection = await ConnectionService.GetAsync(agentContext, credential.ConnectionId); var definition = await LedgerService.LookupDefinitionAsync(agentContext.Pool, credential.CredentialDefinitionId); var provisioning = await ProvisioningService.GetProvisioningAsync(agentContext.Wallet); var request = await AnonCreds.ProverCreateCredentialReqAsync(agentContext.Wallet, connection.MyDid, credential.OfferJson, definition.ObjectJson, provisioning.MasterSecretId); // Update local credential record with new info credential.CredentialRequestMetadataJson = request.CredentialRequestMetadataJson; await credential.TriggerAsync(CredentialTrigger.Request); await RecordService.UpdateAsync(agentContext.Wallet, credential); var msg = new CredentialRequestMessage { OfferJson = credential.OfferJson, CredentialRequestJson = request.CredentialRequestJson, CredentialValuesJson = CredentialUtils.FormatCredentialValues(attributeValues) }; 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); } }
public void CanFormatCredentialValues() { Dictionary <string, string> attributeValues = new Dictionary <string, string>() { { "first_name", "Test" }, { "last_name", "holder" } }; var expectedResult = "{\n \"first_name\" : {\n \"raw\" : \"Test\",\n \"encoded\" : \"1234567890\"\n },\n \"last_name\" : {\n \"raw\" : \"holder\",\n \"encoded\" : \"1234567890\"\n }\n}"; var formatedCredentialUtils = CredentialUtils.FormatCredentialValues(attributeValues); var expectedResultObj = JObject.Parse(expectedResult); var formatedCredObj = JObject.Parse(formatedCredentialUtils); Assert.Equal(expectedResultObj, formatedCredObj); }
public void CanFormatStringCredentialValues() { var attributeValues = new List <CredentialPreviewAttribute> { new CredentialPreviewAttribute("first_name", "Test"), new CredentialPreviewAttribute("last_name", "holder") }; var expectedResult = "{\n \"first_name\" : {\n \"raw\" : \"Test\",\n \"encoded\" : \"1234567890\"\n },\n \"last_name\" : {\n \"raw\" : \"holder\",\n \"encoded\" : \"1234567890\"\n }\n}"; var formatedCredentialUtils = CredentialUtils.FormatCredentialValues(attributeValues); var expectedResultObj = JObject.Parse(expectedResult); var formatedCredObj = JObject.Parse(formatedCredentialUtils); Assert.Equal(expectedResultObj, formatedCredObj); }
/// <inheritdoc /> public virtual async Task AcceptOfferAsync(Wallet wallet, Pool pool, string credentialId, Dictionary <string, string> attributeValues = null) { var credential = await RecordService.GetAsync <CredentialRecord>(wallet, credentialId); var connection = await ConnectionService.GetAsync(wallet, credential.ConnectionId); var definition = await LedgerService.LookupDefinitionAsync(pool, connection.MyDid, credential.CredentialDefinitionId); var provisioning = await ProvisioningService.GetProvisioningAsync(wallet); var request = await AnonCreds.ProverCreateCredentialReqAsync(wallet, connection.MyDid, credential.OfferJson, definition.ObjectJson, provisioning.MasterSecretId); // Update local credential record with new info credential.CredentialRequestMetadataJson = request.CredentialRequestMetadataJson; var details = new CredentialRequestDetails { OfferJson = credential.OfferJson, CredentialRequestJson = request.CredentialRequestJson, CredentialValuesJson = CredentialUtils.FormatCredentialValues(attributeValues) }; var requestMessage = await MessageSerializer.PackSealedAsync <CredentialRequestMessage>(details, wallet, connection.MyVk, connection.TheirVk); requestMessage.Type = MessageUtils.FormatDidMessageType(connection.TheirDid, MessageTypes.CredentialRequest); await credential.TriggerAsync(CredentialTrigger.Request); await RecordService.UpdateAsync(wallet, credential); //TODO we need roll back, i.e if we fail to send the A2A message the credential record should revert to Offer phase //so the user can resend await RouterService.ForwardAsync(new ForwardEnvelopeMessage { Content = requestMessage.ToJson(), Type = MessageUtils.FormatDidMessageType(connection.TheirDid, MessageTypes.Forward) }, connection.Endpoint); }
public async Task <(CredentialIssueMessage, CredentialRecord)> CreateCredentialAsync(IAgentContext agentContext, 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); var provisioning = await ProvisioningService.GetProvisioningAsync(agentContext.Wallet); 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( wallet : agentContext.Wallet, pool : await agentContext.Pool, issuerDid : provisioning.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 CredentialIssueMessage { Credentials = new [] { new Attachment { Id = "libindy-cred-0", MimeType = CredentialMimeTypes.ApplicationJsonMimeType, Data = new AttachmentContent { Base64 = issuedCredential.CredentialJson .GetUTF8Bytes() .ToBase64String() } } } }; credentialMsg.ThreadFrom(threadId); return(credentialMsg, credential); }
/// <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 <(CredentialOfferMessage, string)> CreateOfferAsync(IAgentContext agentContext, OfferConfiguration config, string connectionId = null) { Logger.LogInformation(LoggingEvents.CreateCredentialOffer, "DefinitionId {0}, ConnectionId {1}, IssuerDid {2}", config.CredentialDefinitionId, config.IssuerDid); if (!config.MultiPartyOffer && !string.IsNullOrEmpty(connectionId)) { var connection = await ConnectionService.GetAsync(agentContext, connectionId); if (connection.State != ConnectionState.Connected) { throw new AgentFrameworkException(ErrorCode.RecordInInvalidState, $"Connection state was invalid. Expected '{ConnectionState.Connected}', found '{connection.State}'"); } } var offerJson = await AnonCreds.IssuerCreateCredentialOfferAsync(agentContext.Wallet, config.CredentialDefinitionId); var nonce = JObject.Parse(offerJson)["nonce"].ToObject <string>(); // Write offer record to local wallet var credentialRecord = new CredentialRecord { Id = Guid.NewGuid().ToString(), CredentialDefinitionId = config.CredentialDefinitionId, OfferJson = offerJson, MultiPartyOffer = config.MultiPartyOffer, ValuesJson = CredentialUtils.FormatCredentialValues(config.CredentialAttributeValues), State = CredentialState.Offered, }; if (!config.MultiPartyOffer) { credentialRecord.ConnectionId = connectionId; } credentialRecord.SetTag(TagConstants.Nonce, nonce); credentialRecord.SetTag(TagConstants.Role, TagConstants.Issuer); if (!string.IsNullOrEmpty(config.IssuerDid)) { credentialRecord.Tags.Add(TagConstants.IssuerDid, config.IssuerDid); } if (config.Tags != null) { foreach (var tag in config.Tags) { if (!credentialRecord.Tags.Keys.Contains(tag.Key)) { credentialRecord.Tags.Add(tag.Key, tag.Value); } } } await RecordService.AddAsync(agentContext.Wallet, credentialRecord); return(new CredentialOfferMessage { OfferJson = offerJson }, 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) { await LedgerService.SendRevocationRegistryEntryAsync(agentContext.Wallet, await agentContext.Pool, issuerDid, revocationRegistryId, "CL_ACCUM", issuedCredential.RevocRegDeltaJson); credential.CredentialRevocationId = issuedCredential.RevocId; } 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 IssueCredentialAsync(Pool pool, Wallet wallet, string issuerDid, string credentialId, Dictionary <string, string> values) { var credentialRecord = await RecordService.GetAsync <CredentialRecord>(wallet, credentialId); if (values != null && values.Count > 0) { credentialRecord.ValuesJson = CredentialUtils.FormatCredentialValues(values); } var definitionRecord = await SchemaService.GetCredentialDefinitionAsync(wallet, credentialRecord.CredentialDefinitionId); var connection = await ConnectionService.GetAsync(wallet, credentialRecord.ConnectionId); if (credentialRecord.State != CredentialState.Requested) { throw new Exception( $"Credential sate was invalid. Expected '{CredentialState.Requested}', found '{credentialRecord.State}'"); } string revocationRegistryId = null; BlobStorageReader tailsReader = null; if (definitionRecord.SupportsRevocation) { var revocationRecordSearch = await RecordService.SearchAsync <RevocationRegistryRecord>( wallet, new SearchRecordQuery { { TagConstants.CredentialDefinitionId, definitionRecord.DefinitionId } }, null, 1); var revocationRecord = revocationRecordSearch.First(); revocationRegistryId = revocationRecord.RevocationRegistryId; tailsReader = await TailsService.OpenTailsAsync(revocationRecord.TailsFile); } var issuedCredential = await AnonCreds.IssuerCreateCredentialAsync(wallet, credentialRecord.OfferJson, credentialRecord.RequestJson, credentialRecord.ValuesJson, revocationRegistryId, tailsReader); if (definitionRecord.SupportsRevocation) { await LedgerService.SendRevocationRegistryEntryAsync(wallet, pool, issuerDid, revocationRegistryId, "CL_ACCUM", issuedCredential.RevocRegDeltaJson); credentialRecord.CredentialRevocationId = issuedCredential.RevocId; } var credentialDetails = new CredentialDetails { CredentialJson = issuedCredential.CredentialJson, RevocationRegistryId = revocationRegistryId }; await credentialRecord.TriggerAsync(CredentialTrigger.Issue); await RecordService.UpdateAsync(wallet, credentialRecord); var credential = await MessageSerializer.PackSealedAsync <CredentialMessage>(credentialDetails, wallet, connection.MyVk, connection.TheirVk); credential.Type = MessageUtils.FormatDidMessageType(connection.TheirDid, MessageTypes.Credential); await RouterService.ForwardAsync(new ForwardEnvelopeMessage { Content = credential.ToJson(), Type = MessageUtils.FormatDidMessageType(connection.TheirDid, MessageTypes.Forward) }, connection.Endpoint); }