Digest signing with detached signature example¶
This example shows how to create a product which digest signing with detached signature. Also example signing operation is performed and the result is also verified.
This example uses the reference python client and the UI to perform different actions.
The example is divided to following stages:
- How to get authentication token
- Adding certificate profile
- Creating product
- Signing digest
- Verifying digest
Authentication¶
In order to use the reference python client a valid JWT token is needed. How to obtain one is explained in more detail in the authentication chapter. New token must be requested if the current token expires. ( roughly 1 hour of validity)
Here we use the token as an environment variable.
(venv) $ export TOKEN=<insert here the retrieved token>
Add profiles¶
Digest signing with detached signature is using a Certificate authority which is used to issue signing certificates and a Signing End-Entity issued under that CA.
The example profile were taken as base and modified for this example
(venv) $ ./signing-tool.py -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1/ profile \
add -F endentity.yaml -N "Digest signing cert for detached signature" -T END
{
"id": "41fcea27-d2cb-4400-8c9c-74a5c577e66b",
"profile_name": "Digest signing cert for detached signature",
"profile_type": 1,
"profile_yaml": "IyBDb25maWcgZmlsZSBmb3IgdGhlIEVuZCBlbnRpdHkgcHJvZmlsZS4gRG8gbm90IHVzZSBUQUJTCi0tLQpOYW1lOiBVU0IgZGV0YWNoZWQgc2lnbmF0dXJlIGVudGl0eSBwcm9maWxlCkROOgogICMgT3JnYW5pemF0aW9uOiBUZXN0IENvbXAKICAjIE9yZ2FuaXphdGlvbmFsVW5pdDogUiZECiAgIyBDb3VudHJ5OiBGSQogICMgUHJvdmluY2U6CiAgIy0gU29tZXRoaW5nCiAgIyBMb2NhbGl0eToKICAjIFN0cmVldEFkZHJlc3M6CiAgIyBQb3N0YWxDb2RlOgogICMgU2VyaWFsTnVtYmVyCiAgIyBDb21tb25OYW1lOiBJTUcKIyBFbmRFbnRpdHkgaXMgMSwgU3ViIENBIHByb2ZpbGUgMiAsIFJvb3QgcHJvZmlsZSAzClByb2ZpbGVUeXBlOiAxCiMgU2lnbmF0dXJlQWxnb3JpdGhtcwojICBTSEExV2l0aFJTQSA9IDMKIyAgU0hBMjU2V2l0aFJTQSA9IDQKIyAgU0hBMzg0V2l0aFJTQSA9IDUKIyAgU0hBNTEyV2l0aFJTQSA9IDYKIyAgRFNBV2l0aFNIQTEgPSA3CiMgIERTQVdpdGhTSEEyNTYgPSA4CiMgIEVDRFNBV2l0aFNIQTEgPSA5CiMgIEVDRFNBV2l0aFNIQTI1NiA9IDEwCiMgIEVDRFNBV2l0aFNIQTM4NCA9IDExCiMgIEVDRFNBV2l0aFNIQTUxMiA9IDEyCiMgIFNIQTI1NldpdGhSU0FQU1MgPSAxMwojICBTSEEzODRXaXRoUlNBUFNTID0gMTQKIyAgU0hBNTEyV2l0aFJTQVBTUyA9IDE1ClNpZ25hdHVyZUFsZ29yaXRobTogNAojIEtleUFsZ29yaXRobXMKIyAgIFJTQUFsZ29yaXRobSA9IDEKIyAgIEVDRFNBQWxnb3JpdGhtID0gMgpLZXlBbGdvcml0aG06IDEKIyBWYWxpZGl0eSBvZiB0aGUgY2VydGlmaWNhdGUgaW4gZHVyYXRpb24gKG1heCAyOTAgeWVhcnMpIG9yIHdpdGggYWJzb2x1dGUgZGF0ZSBpbiBSRkMzMzM5IGUuZy4gOTk5OS0xMi0zMVQyMzo1OTo1OVouCiMgRXhhbXBsZSBpbiBhYnNvbHV0ZSBkYXRlICI5OTk5LTEyLTMxVDIzOjU5OjU5WiIuCiMgRXhhbXBsZSBpbiBkdXJhdGlvbiAiMWgxMG0xMHMiLiBPbmx5IGgsIG0gYW5kL29yIHMgYXJlIGFjY2VwdGVkLgpWYWxpZGl0eVBlcmlvZDogIjg3NjAwMGgiICMgMTAwIFllYXJzCkJhc2ljQ29uc3RyYWludHM6CiAgVXNlOiB0cnVlCiAgSXNDQTogZmFsc2UKQWRkU0tJOiB0cnVlCkFkZEFLSTogdHJ1ZQojIEtleVVzYWdlcwojICBLZXlVc2FnZURpZ2l0YWxTaWduYXR1cmUgPSAxCiMgIEtleVVzYWdlQ29udGVudENvbW1pdG1lbnQgPSAyCiMgIEtleVVzYWdlS2V5RW5jaXBoZXJtZW50ID0gNAojICBLZXlVc2FnZURhdGFFbmNpcGhlcm1lbnQgPSA4CiMgIEtleVVzYWdlS2V5QWdyZWVtZW50ID0gMTYKIyAgS2V5VXNhZ2VDZXJ0U2lnbiA9IDMyCiMgIEtleVVzYWdlQ1JMU2lnbiA9IDY0CiMgIEtleVVzYWdlRW5jaXBoZXJPbmx5ID0gMTI4CiMgIEtleVVzYWdlRGVjaXBoZXJPbmx5ID0gMjU2CktleVVzYWdlOgpFeHRlbmRlZEtleVVzYWdlOgogICMgRXh0ZW5kZWRLZXlVc2FnZXMKICAjICBFeHRLZXlVc2FnZUFueSA9IDAKICAjICBFeHRLZXlVc2FnZVNlcnZlckF1dGggPSAxCiAgIyAgRXh0S2V5VXNhZ2VDbGllbnRBdXRoID0gMgogICMgIEV4dEtleVVzYWdlQ29kZVNpZ25pbmcgPSAzCiAgIyAgRXh0S2V5VXNhZ2VFbWFpbFByb3RlY3Rpb24gPSA0CiAgIyAgRXh0S2V5VXNhZ2VJUFNFQ0VuZFN5c3RlbSA9IDUKICAjICBFeHRLZXlVc2FnZUlQU0VDVHVubmVsID0gNgogICMgIEV4dEtleVVzYWdlSVBTRUNVc2VyID0gNwogICMgIEV4dEtleVVzYWdlVGltZVN0YW1waW5nID0gOAogICMgIEV4dEtleVVzYWdlT0NTUFNpZ25pbmcgPSA5CiAgIyAgRXh0S2V5VXNhZ2VNaWNyb3NvZnRTZXJ2ZXJHYXRlZENyeXB0byA9IDEwCiAgIyAgRXh0S2V5VXNhZ2VOZXRzY2FwZVNlcnZlckdhdGVkQ3J5cHRvID0gMTEKICAjICBFeHRLZXlVc2FnZU1pY3Jvc29mdENvbW1lcmNpYWxDb2RlU2lnbmluZyA9IDEyCiAgIyAgRXh0S2V5VXNhZ2VNaWNyb3NvZnRLZXJuZWxDb2RlU2lnbmluZyA9IDEzCkV4dHJhRXh0ZW5zaW9uczogdHJ1ZQpTQU5Vc2FnZTogdHJ1ZQojIENSTERpc3RyaWJ1dGlvblBvaW50cy4gTGlzdCBvZiBVUkkgc3RyaW5ncy4KI0NSTERpc3RyaWJ1dGlvblBvaW50czoKCiMgUG9saWN5SWRlbnRpZmllcnMuIExpc3Qgb2YgQVNOMSBwb2xpY3kgT0lEUwojUG9saWN5SWRlbnRpZmllcnM6CiMgIC0gMS4xMC4xMjMuNDMyLjQuNQojICAtIDIuMTAuMTIzLjQzMi40LjY1CgojIEVuZm9yY2VVbmlxdWVETiBlbmFibGVzIHRoZSBjaGVja2luZyBpZiBhIGNlcnRpZmljYXRlIGhhcyBiZWVuIGlzc3VlZCB3aXRoIHRoZSBzYW1lIHN1YmplY3QgRE4gZnJvbSB0aGUgQ0EKIyBWYWx1ZXMgdHJ1ZSBvciBmYWxzZQojRW5mb3JjZVVuaXF1ZUROOiB0cnVlCg=="
}
Profile added. Profile ID: 41fcea27-d2cb-4400-8c9c-74a5c577e66b
(venv) $ ./signing-tool.py -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1/ profile getall
{
"count": 1,
"items": [
{
"id": "41fcea27-d2cb-4400-8c9c-74a5c577e66b",
"profile_name": "Digest signing cert for detached signature",
"profile_type": 1
}
],
"next": "/cas/profiles/?page=1",
"pages": 1,
"prev": "/cas/profiles/?page=1"
}
Create product¶
Here is the used product template. $PRODUCTNAME was changed to "Detached Demo".
The product template was updated with the profile ids obtained from the previous commands. The value ENDSIGPROFILEID were changed with their corresponding profile id.
- ENDSIGPROFILEID=41fcea27-d2cb-4400-8c9c-74a5c577e66b
In the approval rule it was decided that entities in the $WRITERGROUP were able to make signing requests. And entities in the $APPROVERGROUP group would be able to approve those. Autoapproval was not used.
$WRITERGROUP and $APPROVERGROUP were replaced with the corresponding Group Object ID from Microsoft Entra or from Google.
The template was saved as product.json.
More information about rules can be found from approval rules.
Product was created using the python tool
(venv) $ ./signing-tool.py -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1/ product add -T product.json
Product:
{
"ca_info": [
{
"cert_override_payload": null,
"certificate_type": "ENDENTITY",
"cn": "Externally issued End-Entity certificate for Detached demo",
"crl_distribution_points": null,
"crl_expiry": null,
"crl_issue_interval": null,
"csr": null,
"description": "End-Entity certificate for signing for Detached demo",
"external_request_id": null,
"external_root": true,
"id": null,
"issuer_ca_id": null,
"key_override_id": null,
"key_type": "RSA2048",
"leafs": [],
"originating_id": null,
"policy_identifiers": null,
"product_id": null,
"profile_id": "41fcea27-d2cb-4400-8c9c-74a5c577e66b",
"state": null,
"use_case": "CodeSigning"
}
],
"description": "Product for Digest signing with detached signature based product",
"enabled": true,
"external_request_id": null,
"id": null,
"name": "Detached demo",
"product_config_items": null,
"product_operations": [
{
"approval_rule": {
"allowed_groups": [
"writersforsaas@testing.laavat.fi"
],
"approval_groups": [
"approversforsaas@testing.laavat.fi"
],
"blanket_groups": [],
"description": "Rule used for testing for Detached demo",
"name": "Test rule"
},
"ca_use_case": null,
"description": "Generate detached signature with CodeSigning key for Detached demo",
"id": null,
"name": "Generate detached signature with CodeSigning key",
"operation_type": "DetachedSignature",
"profile_id": null,
"token": null
}
],
"product_type": "RnD",
"rnd_keys": [],
"state": null
}
Is product ok(Y/N): y
{
"ca_info": [],
"description": "Product for Digest signing with detached signature based product",
"enabled": null,
"external_request_id": null,
"id": "6e1cbe3d-9033-455c-8e38-51d13bf66a9c",
"name": "Detached demo",
"product_config_items": null,
"product_operations": [],
"product_type": null,
"rnd_keys": [],
"state": 2
}
Product Add request sent. Request ID: 6e1cbe3d-9033-455c-8e38-51d13bf66a9c state: ApprovalRequired
Approve product¶
The product was approved using the GUI.
Then with the python tool the state was checked ( state is 16 for ready product) and the product ID and operation ID was obtained.
- productID = 6e1cbe3d-9033-455c-8e38-51d13bf66a9c82
- operation ID for digest signing with detached signature = 6609abf8-23c8-43cf-9f7b-b3076746b57e
(venv) $ ./signing-tool.py -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1/ product getall
{
"count": 1,
"items": [
{
"description": "Product for Digest signing with detached signature based product",
"id": "6e1cbe3d-9033-455c-8e38-51d13bf66a9c",
"name": "Detached demo",
"state": 16
}
],
"next": "/products/?page=1",
"pages": 1,
"prev": "/products/?page=1"
}
(venv) $ ./signing-tool.py -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1/ product \
get -I 6e1cbe3d-9033-455c-8e38-51d13bf66a9c
{
"ca_info": [
{
"cert_override_payload": null,
"certificate_type": "ENDENTITY",
"cn": "Externally issued End-Entity certificate for Detached demo",
"crl_distribution_points": null,
"crl_expiry": null,
"crl_issue_interval": null,
"csr": "MIICijCCAXICAQAwRTFDMEEGA1UEAxM6RXh0ZXJuYWxseSBpc3N1ZWQgRW5kIGVudGl0eSBjZXJ0aWZpY2F0ZSBmb3IgRGV0YWNoZWQgZGVtbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL8+XzEXTRYQOTnlHF1fPNG9f8vAP3RyYqiYb0Ntm0Qx6BadZZ0YVC99mL+h/cFpRUaTJTDdcAUuCRm4/El33tn7I1fsp1yQuvknVL6NO312ygcXlugRg82rLGghRvhLXJQhOrgfB3d5TgLnEDbkF4bko0I6WZjLRyRjCvZK2TYzUHHFmlKmpc7zB2fYos6x6aH81a0gnYBWXmxjfJjyFPXYYJAeBS1SwP+mo5wciX4+6Le6ZYclHIxeymCHkaIrMIp0zr98htEZvQ+FsQuuCTzILt0CQtFxCxev7UmdCnCcS0Bm+z5ykzi3nQAk+ZYqgAYuZPW45/WOGCkbfWGOn5cCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBFJ4BVUiAeDvnSTLBKEzA7OnnNpQxYVO90e8IWMtqwsY7JvUMdeh2F05j8kGznJS2Q8nwW/tVuBpX7o1c3WU/2wufuCIF/SaYLOHWczOp1wnjT+g4u7bD7QTibVrmCDF0unWFqZz7WEzx0fMWCU4ZSx3NUoHqaRNzLT6Lt//EXyHCwGBusMmtoiaMIq8wbmD1GmSx8R+qflIMdFt5uHesyVAMx2HY7nZ4cKQDo/iyr6M5K7HTIIEYwVd247YveFPTih2sLnQX0nGpl88tOTqgvCcDmWeYYiADeen2bKb/ANwGLK9PdIIR9x2ddHUchdqp9ynCEY970oI/XpdFyZBYc",
"description": "End-Entity certificate for signing for Detached demo",
"external_request_id": null,
"external_root": null,
"id": "09c20a06-f069-4cfe-a413-89c63cf24e98",
"issuer_ca_id": null,
"key_override_id": null,
"key_type": "RSA2048",
"leafs": [],
"originating_id": null,
"policy_identifiers": null,
"product_id": null,
"profile_id": "41fcea27-d2cb-4400-8c9c-74a5c577e66b",
"state": 256,
"use_case": "CodeSigning"
}
],
"description": "Product for Digest signing with detached signature based product",
"enabled": null,
"external_request_id": "0e89388e-96f6-29b3-d601-0f70a647d610",
"id": "6e1cbe3d-9033-455c-8e38-51d13bf66a9c",
"name": "Detached demo",
"product_config_items": [],
"product_operations": [
{
"approval_rule": {
"allowed_groups": [
"writersforsaas@testing.laavat.fi"
],
"approval_groups": [
"approversforsaas@testing.laavat.fi"
],
"blanket_groups": [
""
],
"description": "Rule used for testing for Detached demo",
"name": "Test rule"
},
"ca_use_case": null,
"description": "Generate detached signature with CodeSigning key for Detached demo",
"id": "6609abf8-23c8-43cf-9f7b-b3076746b57e",
"name": "Generate detached signature with CodeSigning key",
"operation_type": "DetachedSignature",
"profile_id": "00000000-0000-0000-0000-000000000000",
"token": null
}
],
"product_type": "RnD",
"rnd_keys": [],
"state": 16
}
Issue certificate using OpenSSL¶
We need to get the CA ID from the created product. That datastructure contains the CSR. In this example jq tool is used to extract the id.
(venv) $ ./signing-tool.py -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1 product get \
-I 6e1cbe3d-9033-455c-8e38-51d13bf66a9c | jq -r .ca_info[0].id
09c20a06-f069-4cfe-a413-89c63cf24e98
Get the CSR to a file named 09c20a06-f069-4cfe-a413-89c63cf24e98.csr
(venv) $ ./signing-tool.py -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1 ca getcsr \
--caid 09c20a06-f069-4cfe-a413-89c63cf24e98 \
-O /tmp/09c20a06-f069-4cfe-a413-89c63cf24e98.csr
Payload written to file: /tmp/09c20a06-f069-4cfe-a413-89c63cf24e98.csr
Then the signing certificate was issued using openssl. The LAAVAT can also be used to create the PKI hierarchy.
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt -subj "/C=US/ST=State/L=City/O=Organization/OU=Unit/CN=RootCA"
openssl x509 -req -inform der -in /tmp/09c20a06-f069-4cfe-a413-89c63cf24e98.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out /tmp/09c20a06-f069-4cfe-a413-89c63cf24e98.crt -days 365 -sha256
openssl verify -CAfile rootCA.crt /tmp/09c20a06-f069-4cfe-a413-89c63cf24e98.crt
cat /tmp/09c20a06-f069-4cfe-a413-89c63cf24e98.crt > chain.pem
cat rootCA.crt >> chain.pem
The chain is uploaded to the product to complete the configuration. After this the certificate is ready.
(venv) $ ./signing-tool.py -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1 ca patch \
--caid 09c20a06-f069-4cfe-a413-89c63cf24e98 \
-F chain.pem
None
Chain upload
Digest signing using detached signature¶
In this example we create a dummy file which we will use. In real life the correct target file is used:
dd if=/dev/urandom of="/tmp/update.bin" bs="1k" count=1 iflag=fullblock status=none
Calculate the s256 digest from the file
sha256sum < /tmp/update.bin | cut -d" " -f1 | xxd -r -p | base64 -w 0
Hm8KIFv4XZ48PHBjtU4RKphRDRutRsIVVR7df7q6io0=
Create an image signing request with the digest in Base64 format and the product id and the operation id. Hash algorithm SHA256 is used.
- Digest is Hm8KIFv4XZ48PHBjtU4RKphRDRutRsIVVR7df7q6io0=
- ProductID is 6e1cbe3d-9033-455c-8e38-51d13bf66a9c for this example
- OperationID for the detached signature is 6609abf8-23c8-43cf-9f7b-b3076746b57e. This is obtained from the product info
(venv) $ ./signing-tool.py -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1 imagesigning add DigestSigning -p Hm8KIFv4XZ48PHBjtU4RKphRDRutRsIVVR7df7q6io0= \
-P 6e1cbe3d-9033-455c-8e38-51d13bf66a9c --operid 6609abf8-23c8-43cf-9f7b-b3076746b57e -H SHA256
{
"call_back_url": null,
"description": "digest signing request",
"id": "b6a02618-43d4-4d23-8c79-3f571f4e9f3f",
"id_product": "6e1cbe3d-9033-455c-8e38-51d13bf66a9c",
"id_product_operation": "6609abf8-23c8-43cf-9f7b-b3076746b57e",
"name": "Digest signing request",
"payload": {
"id": null,
"metadata": [
{
"name": "Version",
"value": "1.0"
}
],
"modified_sha256": null,
"name": "Signing payload",
"original_sha256": null,
"s3_url": null,
"service_provided_parameters": null
},
"state": 1
}
Image signing request sent. Request ID: b6a02618-43d4-4d23-8c79-3f571f4e9f3f state: Created
The operation created ID "b6a02618-43d4-4d23-8c79-3f571f4e9f3f" which is then used when querying/downloading the signed content.
Approve signing request¶
Request was approved from the GUI.
Download signed content¶
After approval the request is processed and it can be queried. If the state is 16 then the signing is complete and the content is downloaded.
(venv) $ ./signing-tool.py -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1/ imagesigning get \
-I b6a02618-43d4-4d23-8c79-3f571f4e9f3f -O /tmp/out.signed
{
"call_back_url": null,
"description": "digest signing request",
"id": "b6a02618-43d4-4d23-8c79-3f571f4e9f3f",
"id_product": "6e1cbe3d-9033-455c-8e38-51d13bf66a9c",
"id_product_operation": "6609abf8-23c8-43cf-9f7b-b3076746b57e",
"name": "Digest signing request",
"payload": {
"id": "13b31717-b640-4ec1-bd4c-0837e5d656c5",
"metadata": [
{
"name": "Version",
"value": "1.0"
}
],
"modified_sha256": null,
"name": "Signing payload",
"original_sha256": null,
"s3_url": "redacted",
"service_provided_parameters": []
},
"state": 16
}
Downloading signed binary to: /tmp/out.signed
Verify the digest¶
Get the public key of the signing certificate which we issued before. It is possible also to get the certificate from the system securely. More info can be found from client usage.
openssl x509 -in /tmp/09c20a06-f069-4cfe-a413-89c63cf24e98.crt -noout -pubkey > publickey.pem
Verify the signature using the Signing certificates public key and signature received against the target binary.
openssl dgst -sha256 -verify publickey.pem -signature /tmp/test.bin /tmp/update.bin
Verified OK