A program to have an easy way to use digital signatures. Like certificates, only better and easier ;-).
Today's IT world thrives on many, many libraries that are created by volunteers and maintained and developed with a great deal of energy and dedication. But the users of these libraries are exposed to great danger. When one uses artifacts, one needs a method to check whether these artifacts are the ones that the creator created. Currently, there is no way to check if artifacts have been manipulated. There are several ways to perform such a check:
- Use of hash values
- Use of digital signatures
Hash values are easy to calculate and verify, but they only provide protection against erroneous changes, not against attacks. An attacker who can change the artifacts can also change the published hash values.
Digital signatures provide protection against such attacks because the attacker would need to have access to the signature's private key to forge it. They usually work with certificates, but these are difficult to handle:
- It is essential that the private key of a certificate be strongly protected. If it becomes known to unauthorized persons, they can create signatures for manipulated artifacts themselves.
- It is always necessary to check whether the certificate is valid and whether it has been revoked in the meantime. This is extremely tedious and error-prone.
- Certificates expire and must be renewed regularly.
This means a considerable organizational effort.
The current version still has a 0
in the version number.
So the interface may still change.
Any constructive feedback on this program is welcome.
The solution presented here provides a digital signature without the hassles associated with certificates. Artifacts are signed. To do this, a key pair is generated from a private and public key. The signatures are created with the private key. The public key is published so that it can be used to verify the signatures. After the signing process, the private key is deleted. It is not stored and therefore cannot be stolen and misused by attackers. However, it is still possible to verify the signature using the public key.
For a more detailed description of the concept behind this software refer to the concept.md document.
Now, how can you protect against an attacker dropping his forged artifact and generating the signature with the appropriate program?
When artifacts are published, they and the signatures file are stored. The public key used is made known to the recipients of the artifacts by another means. This allows them to always verify that the signatures file is the one issued by the publishing team.
This is illustrated below with an example.
The signing call looks like this:
filesigner sign {contextId} [-a|--algorithm {algorithm}] [-i|--include-file {pattern}] [-x|--exclude-file {pattern}] [-I|--include-dir {pattern}] [-X|--exclude-dir {pattern}] [-f|--from-file {file}] [-m|--name {name}] [-r|--recurse] [-s|--stdin] [files...]
The parts have the following meaning:
Part | Meaning |
---|---|
contextId |
An arbitrary text used to make the signature depend on a topic, also called a "domain separator". |
algorithm |
Specification of the signature method. Either ed25519 or ecdsap521 . If the type is not specified, ed25519 is used. |
exclude-dir |
Specification of directories to exclude. |
exclude-file |
Specification of files to exclude. |
from-file |
Read file names to process from the specified file. There is one file name per line. |
include-dir |
Specification of directories to include. |
include-file |
Specification of files to include. |
name |
The signatures file name is {name}-signatures.json . Default for the name is filesigner . |
recurse |
Descend also into subdirectories. |
stdin |
Read file names to process from the standard input. There is one file name per line. |
files |
A blank-separated list of files to sign. |
Please note the following information:
- The exclude/include options scan the current directory and the subdirectories if
--recurse
is specified. - All exclude/include options take one specification.
- Wildcards (
*
,?
) may be used in include/exclude options. - An include option excludes all objects that are not included.
- If both, files and includes are specified, they are combined.
- If both, files and excludes are specified, files that match an exclude specification are not processed.
- If wildcards are specified in the files list, they are treated as if they are values in
--include-file
options. - On Linux, wildcards need to be put in quotes (
'
) or double quotes ("
) or escaped by a \ (like e.g.--exclude-dir .\*
to exclude all directories starting with.
).
Important
The signatures file is always excluded and cannot be signed.
The call creates a signatures file1 which has the following format:
{
"format": 1,
"contextId": "project1711",
"publicKey": "bfdjDDJ44djrcjhRfFRtdz4HFJjjdZTzBSm37FrZjDgMzFjdv7zT",
"timestamp": "2024-03-05 15:48:51 +01:00",
"hostname": "BuildHost",
"signatureType": 1,
"fileSignatures": {
"common.go": "3sLc4CVCsMmfgmhtf4LBssGt9rtrZHmJhRrVB3QQ7M7LRdCvjGHh3rdHDH77mQgFC3Z9f9jmcDjdtRDFGS9QgC37r3QrHZSfzvcZ743",
"filesigner": "FsVSjJSbQTVLgfSJLvRS33G9bFHdMFSRFGb9FL94C9v37D7zrZSCBmRrFhDfQcHGTFhbhjFFZRhQVMJ4sGB3FVFSdfDgtfRgBzDLJ9G",
"filesigner.exe": "HMQm44ShmbLcfQSv94vcGsMHZLJ4VVZMsfgcbHDVDcbtQg4RTjBCBsm9b94rgDVLCgQdD4GHbBLzFM7RTGhQB94MQ9HQvMgRcQT7Q4h",
"filesigner_sbom.json": "bDvRzQG9dLDTQGVBHfrzJfJBTrCBDhrzbMVvsc7FbbzcFhM7FGtLzLftBCL9fVzRFrgMDMcCsmCTtTQc7j4fBmSGR7rfBSrs9tbbB4G",
"main.go": "QjgzMhsRSSsJMjBDhfTVm3BmBdSZzMZCTvbG7TssCrDVHG9mVrhGHjMVdvdthrrLrdr4jCJbZDfGstJsrdCSJR4gtSRMg9fSzbCrM9h",
"sign_command.go": "HcDcF7LmB4mSvvVJfTvbSSgCtcc7t3vCcjCjZgQc3jfGJ3MfdSS9FChQV37LjdBVhMChLsrdv9vQSJbmgSfD9HszhVJhSDdZL4TdM33",
"verify_command.go": "jQMcCcjRTVQmM7StcZbfVZmfJbstLv9FSFSDZrdrsVVBbHdJQ74BbcH4hsz4gL7V9cvTzgFSZDhVtBMhzbmDFRdR4Sg94ZSVDm7Qc7h"
},
"dataSignature": "cRSSgg3Cbhd3vGLgBf4MtFDCHb3SZrtzCJDrZmhHMzrHVVd3Vc94zhJLmD9g7CRdSRjhjQQhhszCmd3rH79LGT3t97tTg4Gt7bCHc7T"
}
This is a json
file whose structure and the meaning of the fields are described in file_format.md.
A detailed description of the various calculations and data formats can be found in technical_specification.md.
The possible return codes are the following:
Code | Meaning |
---|---|
0 |
Successful processing |
1 |
Error in the command line |
2 |
Warning while processing |
3 |
Error while processing |
The verification call looks like this:
filesigner verify [-m|--name {name}]
The parts have the following meaning:
Part | Meaning |
---|---|
name |
The signatures file name is {name}-signatures.json . Default for the name is filesigner . |
Important
More parameters are not permitted and will result in an error message.
The program reads the signatures file and checks whether the files named there exist and whether their signatures match the current content.
The return codes are the same as for signing.
OS | Program |
---|---|
Windows | filesigner.exe |
Linux | filesigner |
The Linux program can be executed on any Linux system.
Binary values are stored in a special Base32 encoding. What makes this encoding special, is that the alphabet used contains no vowels, no easily confusable characters such as '0' and 'O' or '1' and 'l' and no special characters. This means that the encoded values can be marked with a double click, no real words can be created by mistake and no characters can be confused when reading aloud.
Assume that signatures are to be created and checked for the artifacts filesigner
, filesigner.exe
, all Go
files and all files beginning with the word go
for version 1.7.11
of an application.
The signatures are created with the following call:
filesigner sign project1711 -if *.go -if filesign*
The program then generates the following output on the console:
2024-03-05 15:48:51 +01:00 15 I filesigner V0.81.2 (go1.23.2, 8 cpus)
2024-03-05 15:48:51 +01:00 24 I Context id : project1711
2024-03-05 15:48:51 +01:00 25 I Public key id : DLQB-J6MT-YMF1-PPRF-KQ6P-V9LG-QR
2024-03-05 15:48:51 +01:00 26 I Signature timestamp: 2024-03-05 15:48:51 +01:00
2024-03-05 15:48:51 +01:00 27 I Signature host name: Jetzt
2024-03-05 15:48:51 +01:00 21 I Signing succeeded for file 'common.go'
2024-03-05 15:48:51 +01:00 21 I Signing succeeded for file 'filesigner'
2024-03-05 15:48:51 +01:00 21 I Signing succeeded for file 'filesigner.exe'
2024-03-05 15:48:51 +01:00 21 I Signing succeeded for file 'filesigner_sbom.json'
2024-03-05 15:48:51 +01:00 21 I Signing succeeded for file 'main.go'
2024-03-05 15:48:51 +01:00 21 I Signing succeeded for file 'sign_command.go'
2024-03-05 15:48:51 +01:00 21 I Signing succeeded for file 'verify_command.go'
2024-03-05 15:48:51 +01:00 37 I Signatures for 7 files successfully created and written to 'filesigner-signatures.json'
The return code is 0.
To verify the signatures one needs a trusted place where the public key id, the signature timestamp and the signature host name are published. This may be a signed email, a website, a database, or whatever is deemed to be a secure trusted place.
Then the verifier runs the filesigner program with the following parameters:
filesigner verify
The program then generates the following output on the console:
2024-03-05 15:49:13 +01:00 15 I filesigner V0.81.2 (go1.23.2, 8 cpus)
2024-03-05 15:49:13 +01:00 51 I Reading signatures file 'filesigner-signatures.json'
2024-03-05 15:49:13 +01:00 24 I Context id : project1711
2024-03-05 15:49:13 +01:00 25 I Public key id : DLQB-J6MT-YMF1-PPRF-KQ6P-V9LG-QR
2024-03-05 15:49:13 +01:00 26 I Signature timestamp: 2024-03-05 15:48:51 +01:00
2024-03-05 15:49:13 +01:00 27 I Signature host name: Jetzt
2024-03-05 15:49:13 +01:00 21 I Verification succeeded for file 'common.go'
2024-03-05 15:49:13 +01:00 21 I Verification succeeded for file 'filesigner'
2024-03-05 15:49:13 +01:00 21 I Verification succeeded for file 'filesigner.exe'
2024-03-05 15:49:13 +01:00 21 I Verification succeeded for file 'filesigner_sbom.json'
2024-03-05 15:49:13 +01:00 21 I Verification succeeded for file 'main.go'
2024-03-05 15:49:13 +01:00 21 I Verification succeeded for file 'sign_command.go'
2024-03-05 15:49:13 +01:00 21 I Verification succeeded for file 'verify_command.go'
2024-03-05 15:49:13 +01:00 56 I Verification of 7 files successful
The return code is 0.
The verifying person checks, if the shown public key id, signature timestamp and signature host are the same as those stored in the trusted place. If this is not the case, the signature is deemed to be invalid and the files must not be trusted!
As another example, if the file filesigner
has been manipulated, the following output would appear:
2024-03-05 15:49:38 +01:00 15 I filesigner V0.81.2 (go1.23.2, 8 cpus)
2024-03-05 15:49:38 +01:00 51 I Reading signatures file 'filesigner-signatures.json'
2024-03-05 15:49:38 +01:00 24 I Context id : project1711
2024-03-05 15:49:38 +01:00 25 I Public key id : DLQB-J6MT-YMF1-PPRF-KQ6P-V9LG-QR
2024-03-05 15:49:38 +01:00 26 I Signature timestamp: 2024-03-05 15:48:51 +01:00
2024-03-05 15:49:38 +01:00 27 I Signature host name: Jetzt
2024-03-05 15:49:38 +01:00 21 I Verification succeeded for file 'common.go'
2024-03-05 15:49:38 +01:00 21 I Verification succeeded for file 'filesigner.exe'
2024-03-05 15:49:38 +01:00 21 I Verification succeeded for file 'filesigner_sbom.json'
2024-03-05 15:49:38 +01:00 21 I Verification succeeded for file 'main.go'
2024-03-05 15:49:38 +01:00 21 I Verification succeeded for file 'sign_command.go'
2024-03-05 15:49:38 +01:00 21 I Verification succeeded for file 'verify_command.go'
2024-03-05 15:49:38 +01:00 22 E File 'filesigner' has been modified
2024-03-05 15:49:38 +01:00 58 E Verification of 6 files successful and 1 file unsuccessful
The return code is 3.
If, for example, the signatures file has been manipulated the following output would appear:
2024-03-05 15:50:04 +01:00 15 I filesigner V0.81.2 (go1.23.2, 8 cpus)
2024-03-05 15:50:04 +01:00 51 I Reading signatures file 'filesigner-signatures.json'
2024-03-05 15:50:04 +01:00 54 E Signatures file has been modified
The return code is 3.
You must have Go installed to create the program.
This creates a directory that is specified in the GOPATH
environment variable.
Under Windows, this is the home directory, e.g. D:\Users\username\go
.
Under Linux this is ${HOME}/go
.
In that directory there is a subdirectory src
.
To create the program, the source code must be stored under %GOPATH%\src\filesigner
or ${HOME}/go/src/filesigner
.
Then one has to start the batch file gb.bat
or the shell script gb
, which builds the executables.
These scripts expect the UPX program to be in a specific location.
This location can be adapted to the local path.
If UPX is not available, no compression is performed.
As a result, the files filesigner
for Linux and filesigner.exe
for Windows are created.
Frank Schwab (Mail)
This source code is published under the Apache License V2.
Footnotes
-
The content of the file is just an illustration. It is not possible to verify the signatures of files in this repository with it. ↩