Code Signing using REST API, with manual Approval
This tutorial is an introduction to how the code signing procedure works using the REST API.
In the following we will: 1. compile some code and hash it. 2. request that the hash be signed using a secret key on the keyserver. 3. approve the signing of the hash. 4. retrieve the final signature and verify it.
Prerequisites
While most Linux distributions should work, this tutorial is based on Ubuntu 24.04 with the following prerequisites:
- jq, for JSON command line processing
- cURL, for HTTP requests
- openssl, for command line cryptographic operations
- g++, c++ compiler, (optional, if you want to use the code compilation example in this tutorial)
You may choose to install all by using the following command
Additionally set environment variables for servers used
# Initial tutorial setup
KEYSERVER=https://keyserver.<UNIQUE-ID>.cryptera.com:8200
AUTHSERVER=https://authserver.<UNIQUE-ID>.cryptera.com:8300/oauth2/token
Available accounts
The demo system comes pre-installed with a number of users configured:
- adminuser - This user has access to all operations, including Admin operations, through the UI
- fw-admin - This user has access to all operations, through the API
- fw-client - Able to create pipeline requests and signing for all keys, through the API
- fw-clientkey# - Able to create pipeline requests and signing for the specified key #, eg. orderer-clientkey1, through the API
- approver-fw - Able to approve signing requests for all keys, through the UI
- approver-fw# - Able to approve signing requests for the specified key #, eg. approver-fw2, through the UI
Available Keys
The list of available keys is available here: https://keyclient.
- key1 is an ECDSA key with SHA256 and requires 0 approvers
- key2 is an ECDSA key with SHA384 and requires 1 approver
- key3 is an ECDSA key with SHA512 and requires 2 approvers
- key4 is an RSA key with SHA256 and requires 1 approver
Authorize and retrieve certificate
First we want to authorize as a developer with the keyserver. This is done by retrieving an OAuth token. To authorize we need to convert our username and password to base64. We can then request our access token. Note that it is only valid for 5 minutes.
export AUTHSERVER_CREDENTIAL=$(echo -n fw-admin:<PASSWORD> | base64)
export TOKEN=$(\
curl -v --tlsv1.3 -X POST -H "Authorization: Basic $AUTHSERVER_CREDENTIAL" \
-d "grant_type=client_credentials" $AUTHSERVER \
| jq -r .access_token\
)
For this demo we will choose key2. We also store the corresponding hash-algorithm as a variable for convenience reasons, as we will use it as a flag for OpenSSL.
To fetch the public certificate corresponding to key2 we use the following API call, and then save it in certificate.pem
.
export KEYID="key2"
export HASH_ALGORITHM="-sha384"
export CERT=(curl -v --tlsv1.3 -X 'GET' \
''$KEYSERVER'/api/keys/'$KEYID'' \
-H "Authorization: Bearer $TOKEN" \
-H 'accept: application/json' \
| jq -r .certificate \
)
echo -n $CERT | awk 'BEGIN {FS=" "; print "-----BEGIN CERTIFICATE-----"} {for (i=3; i<NF-1; i++){ print $i}} END {print "-----END CERTIFICATE-----"}' > certificate.pem
Compile some code and hash it
First we are going to create a file with a small C program, and compile it using g++:
echo '#include <stdio.h>
int main() {
printf("Hello World!\n");
return 0;
}' > hello.cpp
g++ hello.cpp -o hello.bin
We then hash the binary using SHA384 (as this is compatible with key2). The hash of the binary is what will be sent to the server for signing
Create an operation
First we request an operation using the following API call.
export OPERATION_ID=$(curl -sS --tlsv1.3 -X 'POST' \
''$KEYSERVER'/api/operations' \
-H "Authorization: Bearer $TOKEN" \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"validms": 60000,
"description": "Building of firmware for XYZ development board",
"new-keyusagerestrictions": [
{
"keyid": "'$KEYID'",
"maxusagecount": 1
}
]
}' | jq -r .id)
echo $OPERATION_ID
This now needs to be approved by an approver.
Approve the operation (via GUI)
Normally an approver does not have access to the API, which will usually be available only to the developers. Instead an approver can access the keyserver to approve an operation using the operation id.
First log in using credentials that can approve operations using the key in question.
- Since we are using key2, log in with username
approver-fw2
and password. - Go to 'https://keyclient.<UNIQUE-ID>.cryptera.com:8100/approvaladmin'
- Find the approval request matching to operation-id from previous step
- If the operation exists click "View"
- Inspect the approval request
- Click "Approve" to approve the operation
In this way the developer can now request a signature on the approved operation-id
Approve the operation (via API)
Since we have authenticated as an admin, with approval rights we can also approve our operation using the API. As mentioned this is not the usual way it is done. In terms of security it is desirable to require multiple people to create a valid signature. Typically it would be a developer requesting a piece of software to be signed, and a manager or coworker can then check that the software indeed looks legitimate, and approve the signing.
To authenticate via the API we use the following.
curl -sS --tlsv1.3 -X 'PUT' \
''$KEYSERVER'/api/approvals/'$OPERATION_ID'' \
-H "Authorization: Bearer $TOKEN" \
-H 'accept: application/json' \
Create a signorder
Now that the operation is approved we can create our signature.
export RESULT=$(\
curl -sS --tlsv1.3 -X 'POST' \
''$KEYSERVER'/api/signorders' \
-H "Authorization: Bearer $TOKEN" \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"operationid": "'$OPERATION_ID'",
"keyid": "'$KEYID'",
"inputdata": "'$HASH_TO_SIGN'",
"inputformat": "hex",
"inputpadding": "none",
"signatureformat": "asn1",
"description": "Version 3.4.5 of XYZ controller for the ABC product. International version."
}' \
| jq -r .result \
)
Verify the signature with OpenSSL
Using the public certificate we can verify that the signature is valid. First we grab the public key from the certificate to use with openssl:
We then write the signature received in the result to a file in binary format for OpenSSL to be able to verify it. The xxd command ensures that the format of the signature complies with OpenSSL
You now have all you need to verify, the public key (pubkey.pem
), the binary (hello.bin
) and the signature hello.bin.sig
of the hash of hello.bin
retrieved from KeyServer