Guardtime KSI c SDK
|
For simplicity reasons, the error handling in this tutorial is mostly omitted. In practice almost all the functions in the SDK return a status code which value should be checked to be KSI_OK, which means all went well.
For preparation see Basics Tutorial.
Usually if a signature needs to be verified, it has been serialized into a binary format which is stored in a file or database. We won't cover reading the binary data, as it may vary on different integrations. Let's assume the signature is copied into a buffer called raw
and it's length is stored in raw_len
. To parse the signature we need to call KSI_Signature_parse:
After a successful call to KSI_Signature_parse the buffer raw
can be freed by the caller as it is not referenced by the signature.
For the most basic verification needs we can just call KSI_verifySignature and check that the return code is KSI_OK:
In the above example we didn't verify the signature against the original document. However we can do so by calling KSI_Signature_verifyDocument. The document is pointed to by doc
and its length is given in doc_len:
Alternatively, if we already have a document hash available in hsh
, we can simply call KSI_verifyDataHash:
Abovemnetioned examples in this chapter used the general verification policy implicitly (see next chapter for details) and relied on a pre-configured publications file and extender in KSI context. If this is all we need and we are not interested in the details of the verification result (e.g. in case of a failure), then that's all we need to know about the verification!
If however we want more control over the verification process, we can choose the verification policy explicitly and provide relevant input data in the verification context. All of this is explained in the following chapters.
Signatures are verified according to one or more policies (even in the simple examples of the previous chapter). A verification policy is a set of ordered rules that verify relevant signature properties. Verifying a signature according to a policy results in one of three possible outcomes:
The SDK provides the following predefined policies for verification:
Note: all of the policies perform internal verification as a prerequisite to the specific verification and a policy will never result in a success if internal verification fails.
To perform verification according to a policy, we first need to set up a verification context. To do this, we first call KSI_VerificationContext_init to initialize our context variable with default values. Then, by using our favorite API method, we obtain the signature that we want to verify and assign it directly into the verification context. Let's assume that our signature contains a publication record and we have a recent enough publication file to possibly contain the same publication. We obtain the publications file by using our API method of choice and assign it directly into the verification context. For now we have set up all the required information to perform the verification, so we can verify the signature by calling KSI_SignatureVerifier_verify. We get two kinds of information from this function. First, the functions returns a status code that indicates verification completeness. Under normal circumstances the return code should be KSI_OK, meaning that the verification process was completed without any errors (e.g. invalid parameters, out of memory errors, extender errors, etc). Second, the function creates a verification result that contains the actual result of the verification according to our chosen policy. As mentioned before, the result can be a success or failure if there was enough data to verify the signature or inconclusive if there was not enough data (e.g. no publication file configured) Note: KSI_OK alone as a positive status code does not indicate a successful verification result (although it is a prerequisite), so we must inspect the verification result for details:
The key to conclusive verification is having sufficient information set up in the verification context without assuming too much from the signature itself. For most cases this means that we have to set up a publications file in the verification context and configure an extender (see Basics Tutorial). In some cases (see example below) we need to allow extending as well. If we want to verify the signature against a specific publication, we can do so by setting up the publication string in the verification context. If we want to verify the signature against a document, the document hash can be stored in the verification context. Additionally, the verification context can be used for enabling/disabling extending for publication-based verification.
Let's continue with another example where we want to verify the signature against a specific publication. We don't really need any of the other verification policies, so we can use the predefined policy KSI_VERIFICATION_POLICY_USER_PUBLICATION_BASED. For conclusive results we need to set up the publication string in the verification context:
If we want to rely on the key-based policy, we must set up the publications file as shown in the above examples. The only time we don't need a publication file or string is when we perform calendar-based verification. However we do need a valid extender for the online verification. The corresponding verification calls:
For allowing extending of a signature for publication based policies, we have to enable it in the verification context. By default the extending is not allowed, which in some situations is what we want, but in other situations can lead to inconclusive verification results if a suitable publication is not found. Note: if extending is allowed, a valid extender should also be configured (see Basics Tutorial).
For verifying the document, we need to set up the document hash in the verification context. The document hash, if set up, will be verified as part of all predefined policies, but for the sake of a simple example we will choose the internal policy:
As mentioned before, the prerequisite of a conclusive verification result is that KSI_SignatureVerifier_verify returns KSI_OK. If the return code is other than KSI_OK, e.g. KSI_INVALID_ARGUMENT or KSI_OUT_OF_MEMORY, the verification process was not completed and it is not possible to say if the signature is valid or incorrect. If however KSI_OK is returned, we must evaluate the result
object, which is created by KSI_SignatureVerifier_verify. Only then can we say if the verification was a success or failure.
As the final step we need to free all the allocated resources. As an owner of all the resources that we set in the verification context (signature, document hash, publication file/string), we are responsible for freeing them. The verification context contains temporary data that is allocated during verification and this must be freed by calling KSI_VerificationContext_clean. After we are done inspecting the verification result, we must free it with KSI_PolicyVerificationResult_free.
Note that the KSI context may be reused as much as needed (within a single thread) and must not be created every time. It is also important to point out that the context, if freed, must be freed last.
The above described new policy-based verification replaces the old verification functionality. In particular, the following outdated interfaces have been removed from SDK:
When replacing the usage of deprecated verification functionality, we need to choose a policy that matches that of the deprecated functionality and then set up the relevant data in the verification context. A straightforward replacement exists for KSI_Signature_verify - just use KSI_verifySignature as shown in chapter 3. We will show how to replace the remaining functionalities. Note: for brevity, all error handling has been removed in the following examples.
Depending on the chosen policy we need to set up relevant data in the verification context:
The verification result is an output parameter of KSI_SignatureVerifier_verify, so the KSI_Signature_getVerificationResult interface is now obsolete. A typical verification result inspection could look like this:
As always, used resources need to be freed. See previous chapters on how to free the context and result.
If the predefined policies do not meet our needs of verification, we can build our own policies. For this we need to put rules (implemented as verification functions) in some order that meets our verification needs. We can reuse predefined rules or define our own rules for this purpose. Let's create a policy based on our own rules, defined in the rules array customRules
. Let's name our policy "CustomPolicy".
Each element in customRules
array consists of two parts: rule type and a pointer. In our first example, we use the basic rule type KSI_RULE_TYPE_BASIC, which means that the second part - pointer - is a pointer to a verifying function. When a policy is verified by KSI_SignatureVerifier_verify, it goes through this array and checks the rule type. If the rule type is KSI_RULE_TYPE_BASIC, it calls the verifying function and examines the verification result of this function. If the function returns KSI_OK and verification result is KSI_VER_RES_OK, it continues with the next rule in the array and does so until it encounters the final empty rule. In this case the verification is successful. If at some point any of the functions does not return KSI_OK or the verification result is not KSI_VER_RES_OK, the verification fails and no more rules are processed. There is however one exception when the next rule is processed and we will see this in one of the following examples. For now, let's examine the typical verifying function:
Important points to remember about a verifying function:
should
be KSI_OK, indicating that the function had enough input data and was able to reach a conclusion about the correctness of the data.Time to move on to some more complex rules. Let's say we want to provide alternative, equally conclusive paths to a verification result. Path A means that we would have to go through a set of three rules, path B requires verification of a different set of four rules and path C consists of yet another set of two rules. Finally, after succeeding at either path A, B, or C, we still want to run some final rule before deciding on the result of the policy. We can write it down like this:
We introduced two new rule types here: KSI_RULE_TYPE_COMPOSITE_OR and KSI_RULE_TYPE_COMPOSITE_AND. Both are composite rule types, which means that the second part of the rule - the pointer - is not a function pointer (as was the case with the basic rule type), but instead a pointer to another array of rules. The array of rules can contain both basic and composite rules, meaning that composite rules can be nested. As you would expect from any array of rules, the composite rule is also also verified in a linear fashion until a rule fails or until all rules including the last one are successful.
The result of the composite rule, whether success or failure, is interpreted according to the rule type. If an OR-type rule is successfully verified, further rules in the rule array are skipped and the whole rule of which the OR-type rule is part of, is considered successfully verified. In our example, if pathARules
verifies successfully, the subsequent rules pathBRules
and pathCRules
are skipped and the rule chooseABCRule
is considered successful. The analogy to an OR-statement continues, but with a slightly different definition of failure - if an OR-type rule result is inconclusive (KSI_VER_RES_NA), we are allowed to verify the the next rule in the array. However, if an OR-type rule result fails with KSI_VER_RES_FAIL, subsequent rules are not verified and the result of array of rules is a failure. So in our example, if pathARules
results in KSI_VER_RES_NA, the rule pathBRules
is verified. If this rule result is also inconclusive, the rule pathCRules
is verified. If however any of those rules fail with KSI_VER_RES_FAIL, the rule chooseABCRule
has also failed.
In our example the rule chooseABCRule
itself is a composite AND-type rule, which means that its result must be successful for the verification to continue. So for a successful result of our complexPolicy
, both chooseABCRule
and VerifyingFunction10
must verify successfully. If an AND-type rule fails, the whole rule array of which it is part of, fails as well (no further rules are verified).
Let's summarize how rule results are interpreted:
For an additional level of customization we can chain policies to each other to allow automatic fallback to a different verification policy if the original policy fails. For our own policies that we have created with KSI_Policy_create, we can set the fallback policy pointer by calling KSI_Policy_setFallback. If we want to use one of the predefined policies as the original policy, we first need to clone it by calling KSI_Policy_clone. When we have created or cloned a policy, we also need to free it after use by calling KSI_Policy_free.