/// <summary> /// Cryptographically signs the provided document by adding a `proof` section, /// based on the provided suite and proof purpose. /// </summary> /// <param name="document"></param> /// <param name="options"></param> /// <returns></returns> public static async Task <JToken> SignAsync(JToken document, ProofOptions options) { if (options.Purpose is null) { throw new Exception("Proof purpose is required."); } if (options.Suite is null) { throw new Exception("Suite is required."); } options.AdditonalData["originalDocument"] = document; var documentCopy = document.DeepClone(); documentCopy = options.CompactProof ? JsonLdProcessor.Compact(documentCopy, Constants.SECURITY_CONTEXT_V2_URL, options.GetProcessorOptions()) : document.DeepClone(); documentCopy.Remove("proof"); // create the new proof (suites MUST output a proof using the security-v2 // `@context`) options.Input = documentCopy; var proof = await options.Suite.CreateProofAsync(options); // TODO: Check compaction again proof.Proof.Remove("@context"); var result = proof.UpdatedDocument ?? document.DeepClone(); result["proof"] = proof.Proof; return(result); }
/// <summary> /// Verifies the linked data signature on the provided document. /// </summary> /// <param name="document"></param> /// <param name="options"></param> /// <returns></returns> public static async Task <ValidationResult> VerifyAsync(JToken document, ProofOptions options) { if (options.Purpose is null) { throw new Exception("Purpose is required."); } if (options.Suite is null) { throw new Exception("Suite is required."); } options.AdditonalData["originalDocument"] = document.DeepClone(); // shallow copy to allow for removal of proof set prior to canonize var input = document.Type == JTokenType.String ? await options.DocumentLoader.LoadAsync(document.ToString()) : document.DeepClone(); var(proof, doc) = GetProof(input, options); var result = await options.Suite.VerifyProofAsync(proof, new ProofOptions { Suite = options.Suite, Purpose = options.Purpose, CompactProof = options.CompactProof, AdditonalData = options.AdditonalData, DocumentLoader = options.DocumentLoader, Input = doc }); return(result); }
public override async Task <ValidationResult> VerifyProofAsync(JToken proof, ProofOptions options) { var verifyData = CreateVerifyData(proof as JObject, options); var verificationMethod = GetVerificationMethod(proof as JObject, options); await VerifyAsync(verifyData, proof, verificationMethod, options); // Validate proof purpose options.Purpose.Options.VerificationMethod = new VerificationMethod(verificationMethod); return(await options.Purpose.ValidateAsync(proof, options)); }
protected virtual IVerifyData CreateVerifyData(JObject proof, ProofOptions options) { var processorOptions = options.GetProcessorOptions(); var c14nProofOptions = Helpers.CanonizeProof(proof, processorOptions); var c14nDocument = Helpers.Canonize(options.Input, processorOptions); var sha256 = SHA256.Create(); return((ByteArray)sha256.ComputeHash(Encoding.UTF8.GetBytes(c14nProofOptions)) .Concat(sha256.ComputeHash(Encoding.UTF8.GetBytes(c14nDocument))) .ToArray()); }
/// <summary> /// Get a proof from a signed document /// </summary> /// <param name="document"></param> /// <param name="options"></param> /// <returns></returns> public static (JToken proof, JToken document) GetProof(JToken document, ProofOptions options) { var documentCopy = options.CompactProof ? JsonLdProcessor.Compact( input: document, context: Constants.SECURITY_CONTEXT_V2_URL, options: options.GetProcessorOptions()) : document.DeepClone(); var proof = documentCopy["proof"].DeepClone(); document.Remove("proof"); if (proof == null) { throw new Exception("No matching proofs found in the given document."); } proof["@context"] = Constants.SECURITY_CONTEXT_V2_URL; return(proof, document); }
/// <summary> /// Create proof /// </summary> /// <param name="options"></param> /// <returns></returns> public override async Task <ProofResult> CreateProofAsync(ProofOptions options) { if (VerificationMethod == null) { throw new ArgumentNullException(nameof(VerificationMethod), "VerificationMethod must be specified."); } if (TypeName == null) { throw new ArgumentNullException(nameof(TypeName), "TypeName must be specified."); } var proof = InitialProof != null ? JsonLdProcessor.Compact(InitialProof, Constants.SECURITY_CONTEXT_V2_URL, options.GetProcessorOptions()) : new JObject { { "@context", Constants.SECURITY_CONTEXT_V2_URL } }; proof["type"] = TypeName; proof["created"] = Date.HasValue ? Date.Value.ToString("s") : DateTime.Now.ToString("s"); proof["verificationMethod"] = VerificationMethod; // allow purpose to update the proof; the `proof` is in the // SECURITY_CONTEXT_URL `@context` -- therefore the `purpose` must // ensure any added fields are also represented in that same `@context` proof = await options.Purpose.UpdateAsync(proof, options); // create data to sign var verifyData = CreateVerifyData(proof, options); // sign data proof = await SignAsync(verifyData, proof, options); return(new ProofResult { Proof = proof }); }
protected VerificationMethod GetVerificationMethod(JObject proof, ProofOptions options) { var verificationMethod = proof["verificationMethod"] ?? throw new Exception("No 'verificationMethod' found in proof."); var verificationMethodId = verificationMethod.Type switch { JTokenType.String => verificationMethod.ToString(), JTokenType.Object => verificationMethod["id"]?.ToString() ?? throw new Exception("Verification Method found, but it's 'id' property was empty"), _ => throw new Exception($"Invalid verification method type: {verificationMethod.Type}") }; var processorOptions = options.GetProcessorOptions(); processorOptions.ExpandContext = Constants.SECURITY_CONTEXT_V2_URL; var result = JsonLdProcessor.Frame( verificationMethodId, new JObject { { "@context", Constants.SECURITY_CONTEXT_V2_URL }, { "@embed", "@always" }, { "id", verificationMethod } }, processorOptions); if (result == null || result["id"] == null) { throw new Exception($"Verification method {verificationMethod} not found."); } if (result["revoked"] != null) { throw new Exception("The verification method has been revoked."); } return(new VerificationMethod(result)); }
protected override Task VerifyAsync(IVerifyData payload, JToken proof, JToken verificationMethod, ProofOptions options) { var verifyData = payload as ByteArray ?? throw new ArgumentException("Invalid data type"); if (proof["jws"] == null || !proof["jws"].ToString().Contains("..")) { throw new Exception("The proof does not include a valid 'jws' property."); } var parts = proof["jws"].ToString().Split(".."); var(encodedHeader, encodedSignature) = (parts.First(), parts.Last()); var header = JObject.Parse(Encoding.UTF8.GetString(Helpers.FromBase64String(encodedHeader))); if (header["alg"]?.ToString() != Algorithm) { throw new Exception($"Invalid JWS header parameters for ${TypeName}."); } var signature = Helpers.FromBase64String(encodedSignature); var data = (ByteArray)Encoding.ASCII.GetBytes($"{encodedHeader}.") .Concat(verifyData.Data) .ToArray(); var signer = GetSigner(verificationMethod); var valid = signer.Verify(signature, data); if (!valid) { throw new Exception("Invalid signature"); } return(Task.CompletedTask); }
/// <inheritdoc /> protected override Task <JObject> SignAsync(IVerifyData payload, JObject proof, ProofOptions options) { var verifyData = payload as ByteArray ?? throw new ArgumentException("Invalid data type"); // JWS header var header = new JObject { { "alg", Algorithm }, { "b64", false }, { "crit", JArray.Parse("[\"b64\"]") } }; /* +-------+-----------------------------------------------------------+ | "b64" | JWS Signing Input Formula | +-------+-----------------------------------------------------------+ | true | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || | | | BASE64URL(JWS Payload)) | | | | | false | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.') || | | | JWS Payload | +-------+-----------------------------------------------------------+ */ // create JWS data and sign var encodedHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header))); var data = (ByteArray)Encoding.ASCII.GetBytes($"{encodedHeader}.") .Concat(verifyData.Data) .ToArray(); var signature = Signer.Sign(data); // create detached content signature var encodedSignature = Convert.ToBase64String(signature); proof["jws"] = $"{encodedHeader}..{encodedSignature}"; return(Task.FromResult(proof)); }
public abstract Task <ValidationResult> VerifyProofAsync(JToken proof, ProofOptions options);
public abstract Task <ProofResult> CreateProofAsync(ProofOptions options);
/// <summary> /// Verifies the verification data for the current proof /// </summary> /// <param name="verifyData"></param> /// <param name="proof"></param> /// <param name="verificationMethod"></param> /// <param name="options"></param> /// <returns></returns> protected abstract Task VerifyAsync(IVerifyData verifyData, JToken proof, JToken verificationMethod, ProofOptions options);
/// <summary> /// Signs the verification data for the current proof /// </summary> /// <param name="verifyData"></param> /// <param name="proof"></param> /// <param name="options"></param> /// <returns></returns> protected abstract Task <JObject> SignAsync(IVerifyData verifyData, JObject proof, ProofOptions options);