/// <summary> /// Verifies the capability chain, if any, attached to the given capability. /// </summary> /// <param name="capability"></param> /// <returns></returns> public static async Task <JToken> VerifyCapabilityChain(JToken capability, PurposeOptions purposeOptions, JsonLdProcessorOptions options = null) { /* Verification process is: * 1. Fetch capability if only its ID was passed. * 1.1. Ensure `capabilityAction` is allowed. * 2. Get the capability delegation chain for the capability. * 3. Validate the capability delegation chain. * 4. Verify the root capability: * 4.1. Check the expected target, if one was specified. * 4.2. Ensure that the caveats are met on the root capability. * 4.3. Ensure root capability is expected and has no invocation target. * 5. If `excludeGivenCapability` is not true, then we need to verify the * capability delegation proof on `capability`, so add it to the chain to * get processed below. * 6. For each capability `cap` in the chain, verify the capability delegation * proof on `cap`. This will validate everything else for `cap` including * that caveats are met. * * Note: We start verifying a capability from its root of trust (the * beginning or head of the capability chain) as this approach limits an * attacker's ability to waste our time and effort traversing from the tail * to the head. */ // 1. Fetch capability if only its ID was passed. capability = await FetchInSecurityContextAsync(capability, false, options); //capability = new CapabilityDelegation(cap as JObject); // TODO: Add allowed action validation // 2. Get the capability delegation chain for the capability. var capabilityChain = GetCapabilityChain(capability); // 3. Validate the capability delegation chain. ValidateCapabilityChain(capability, capabilityChain); // 4. Verify root capability (note: it must *always* be dereferenced since // it does not need to have a delegation proof to vouch for its authenticity // ... dereferencing it prevents adversaries from submitting an invalid // root capability that is accepted): var isRoot = !capabilityChain.Any(); var rootCapability = isRoot ? capability : capabilityChain.First(); capabilityChain = capabilityChain.Skip(1).ToArray(); rootCapability = await Utils.FetchInSecurityContextAsync(rootCapability, isRoot, null); // 4.1. Check the expected target, if one was specified. if (purposeOptions.ExpectedTarget != null) { var target = GetTarget(rootCapability); if (target != purposeOptions.ExpectedTarget) { throw new Exception($"Expected target ({purposeOptions.ExpectedTarget}) does not match " + $"root capability target({target})."); } } // 4.2. Ensure that the caveats are met on the root capability. // TODO: Add caveats // 4.3. Ensure root capability is expected and has no invocation target. // run root capability checks (note that these will only be run once // because the `verifiedParentCapability` parameter stops recursion // from happening below) // ensure that the invocation target matches the root capability or, // if `expectedRootCapability` is present, that it matches that if (purposeOptions.ExpectedRootCapability != null) { if (purposeOptions.ExpectedRootCapability != rootCapability["id"].Value <string>()) { throw new Exception($"Expected root capability ({purposeOptions.ExpectedRootCapability}) does not " + $"match actual root capability({rootCapability["id"]})."); } } else if (GetTarget(rootCapability) != rootCapability["id"].Value <string>()) { throw new Exception("The root capability must not specify a different " + "invocation target."); } // root capability now verified var verifiedParentCapability = rootCapability; // if verifying a delegation proof and we're at the root, exit early if (isRoot) { return(verifiedParentCapability); } // create a document loader that will use properly embedded capabilities var documentLoader = new CachingDocumentLoader(Array.Empty <IDocumentResolver>()); var next = capabilityChain; while (next.Any()) { // the only capability that may be embedded (if the zcap is valid) is // the last one in the chain, if it is embedded, add it to `dlMap` and // recurse into its chain and loop to collect all embedded zcaps var cap = next.Last(); if (!(cap is JObject)) { break; } if (cap["@context"] == null) { // the capabilities in the chain are already in the security context // if no context has been specified cap["@context"] = Constants.SECURITY_CONTEXT_V2_URL; } // Transforms the `capability` into the security context (the native // context this code uses) so we can process it cleanly and then // verifies the capability delegation proof on `capability`. This allows // capabilities to be expressed using custom contexts. cap = await FetchInSecurityContextAsync(cap, false, options); documentLoader.AddCached(cap["id"].Value <string>(), cap as JObject); next = GetCapabilityChain(cap); } options.DocumentLoader = documentLoader.Load; // 5. If `excludeGivenCapability` is not true, then we need to verify the // capability delegation proof on `capability`, so add it to the chain to // get processed below. If an `inspectCapabilityChain` handler has been // provided, the verify results are required on all capabilities. // TODO: Add support for `excludeGivenCapability` // 6. For each capability `cap` in the chain, verify the capability // delegation proof on `cap`. This will validate everything else for // `cap` including that caveats are met. // note that `verifiedParentCapability` will prevent repetitive checking // of the same segments of the chain (once a parent is verified, its chain // is not checked again when checking its children) for (int i = 0; i < capabilityChain.Count(); i++) { var cap = capabilityChain.ElementAt(i); cap = await FetchInSecurityContextAsync(cap, false, options); var verifyResult = LdSignatures.VerifyAsync(cap, new ProofOptions { Suite = purposeOptions.Suite, Purpose = new CapabilityDelegationProofPurpose { Options = new PurposeOptions { ExpectedTarget = purposeOptions.ExpectedTarget, ExpectedRootCapability = purposeOptions.ExpectedRootCapability }, VerifiedParentCapability = verifiedParentCapability }, DocumentLoader = documentLoader, CompactProof = false }); } return(null); }
public CapabilityInvocation(PurposeOptions options) : base("capabilityInvocation") { Options = options; }