Ejemplo n.º 1
0
        /// <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);
        }
Ejemplo n.º 2
0
        /// <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());
        }
Ejemplo n.º 5
0
        /// <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));
        }
Ejemplo n.º 10
0
 public abstract Task <ValidationResult> VerifyProofAsync(JToken proof, ProofOptions options);
Ejemplo n.º 11
0
 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);