Unclear validity behavior when signing company documents

Im about to implement GPG for signing documents in my company. Here is the Setup I came up with:

  • A personal key for each employee on a smartcard
  • A Company signing key that signs all the personal keys for validity.

During implementation, I’m struggling with the following points:

Distinguish between “good signature” and “valid signature”

In my understanding, a “good signature” is a cryptogaphically sound signature, a “valid signature” is one where I can trust the signers key actually belongs to the real person, is not expired, etc. See validity

I have the case where I have a signature where the validity of a key is “unknown” and I have no trust in the key. I have imported the public cert, but I have not signed the key with my private or the company key. Here is the ouput of the --edit-key output:

gpg: using pgp trust model
pub  rsa3072/F6475CA4D40800F1
     created: 2025-03-26  expires: never       usage: SC  
     trust: unknown       validity: unknown
sub  rsa3072/6F884B933496DEC5
     created: 2025-03-26  expires: never       usage: E   
[ unknown] (1). Employee A <mployee_a@company.com>

When I validate a signature made with this key, I get the expected (good, but not valid):

> $ gpg --verify "System_Integration_Test_Bench_00014.pdf.asc"
gpg: assuming signed data in 'System_Integration_Test_Bench_00014.pdf'
gpg: Signature made Mo 19 Mai 2025 11:15:51 CEST
gpg:                using RSA key 41BDF6C5822010ABC6652ED3F6475CA4D40800F1
gpg: using pgp trust model
gpg: Good signature from "Employee A <emplyee_a@company.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 41BD F6C5 8220 10AB C665  2ED3 F647 5CA4 D408 00F1
gpg: binary signature, digest algorithm SHA512, key algorithm rsa3072

However, if i get the machine-readable version, I get the info that the signature is valid (because of the [GNUPG:] VALIDSIG line):

> $ gpg --status-fd 1 --verify "System_Integration_Test_Bench_00014.pdf.asc"
gpg: assuming signed data in 'System_Integration_Test_Bench_00014.pdf'
[GNUPG:] NEWSIG
gpg: Signature made Mo 19 Mai 2025 11:15:51 CEST
gpg:                using RSA key 41BDF6C5822010ABC6652ED3F6475CA4D40800F1
[GNUPG:] KEY_CONSIDERED 41BDF6C5822010ABC6652ED3F6475CA4D40800F1 0
[GNUPG:] SIG_ID 0OytdFIocjafx9/5fsyKdWNrMkg 2025-05-19 1747646151
gpg: using pgp trust model
[GNUPG:] GOODSIG F6475CA4D40800F1 Employee A <emplyee_a@company.com>
gpg: Good signature from "Employee A <emplyee_a@company.com>" [unknown]
[GNUPG:] VALIDSIG 41BDF6C5822010ABC6652ED3F6475CA4D40800F1 2025-05-19 1747646151 0 4 0 1 10 00 41BDF6C5822010ABC6652ED3F6475CA4D40800F1
[GNUPG:] KEY_CONSIDERED 41BDF6C5822010ABC6652ED3F6475CA4D40800F1 0
[GNUPG:] TRUST_UNDEFINED 0 pgp
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 41BD F6C5 8220 10AB C665  2ED3 F647 5CA4 D408 00F1
gpg: binary signature, digest algorithm SHA512, key algorithm rsa3072
[GNUPG:] VERIFICATION_COMPLIANCE_MODE 23

To my understanding, this signature should not be valid. Please help me understand what makes this valid or where I get the concept of validity wrong.

Setting up a easy-to-use web of trust

Some of my employees are not tech-savvy, I want the setup an usage for them as easy as possible. I would like to

  • Get everybody a ready-to use key on a smartcard
  • Have the possibility of a backup
  • Make it easy for them to have all the keys of other employees in a valid way

For this, I thought of the following:

  • Have a offline (tails) environment to create the keys for a new employee
  • Sign the key with the company key (to show other employees that this is valid)
  • Sign the company key (to show the employees computer that the company key is valid)
    • I was told that it is good practice to not have the Cert key on the smartcard, but offline. This creates the need to sign the company key at this stage, because afterwards the Cert key is not available anymore.
  • Export the private keys to the smartcard and offer the public keys (with exported signatures) over a static html site that offers the export of the public key

However, Im facing the following problem that I cannot get the web of trust to work. Even though I signed the company key with my cert key (in a exportable way), exported it and import it on my working - environment, I do not get the key to be shown as valid. To verify that the certification of the keys made it with the export and import, here is my output of gpg --list-signatures on my working maschine:

pub   rsa4096 2022-06-21 [C]
      AF3295423E1B6B50C42006C59F8921B4BAEA00F2
uid           [ultimate] Me myself and I <me@company.com>
sig 3        9F8921B4BAEA00F2 2022-06-21  Me myself and I <me@company.com>
sig      N   E373B7E181E3C1B5 2025-05-16  company offline certification key <cert@company.com>
sub   rsa4096 2022-06-21 [S]
sig          9F8921B4BAEA00F2 2022-06-21  Me myself and I <me@company.com>
sub   rsa4096 2022-06-21 [E]
sig          9F8921B4BAEA00F2 2022-06-21  Me myself and I <me@company.com>
sub   rsa4096 2022-06-21 [A]
sig          9F8921B4BAEA00F2 2022-06-21  Me myself and I <me@company.com>


pub   rsa4096 2025-05-16 [C]
      4A0DA3776273007C32CCBAC1E373B7E181E3C1B5
uid           [ unknown] company offline certification key <cert@company.com>
sig 3        E373B7E181E3C1B5 2025-05-16  company offline certification key <cert@company.com>
sig      N   9F8921B4BAEA00F2 2025-05-16  Me myself and I <me@company.com>
sub   rsa4096 2025-05-16 [S]
sig          E373B7E181E3C1B5 2025-05-16  company offline certification key <cert@company.com>
sub   rsa4096 2025-05-16 [E]
sig          E373B7E181E3C1B5 2025-05-16  company offline certification key <cert@company.com>
sub   rsa4096 2025-05-16 [A]
sig          E373B7E181E3C1B5 2025-05-16  company offline certification key <cert@company.com>

Please not that the “company offline certification key” still has validity [ unknown] even though it is signed by me and my key is ultimately trusted. I have the feeling that my own imported signature from another company does not count, it is also not listed in the check command in the --edit-key command. Can I make this work? If not, having an offline Cert key does not seem feasible for me. Feel free for other suggestion/comments about this setup.

Thank you a lot for your help!

Dominik

Edit: I just found out that there is a --trusted-key option. I assume that this is an option that you have to add each call. Even tough this would be possible, I would prefer a solution that is cryptography-based / without needing an argument in every call.

Hi Dominik,

to answer your first question:
You’ve got the concept of validity right.

When looking at the output of --status-fd 1 you must know that parsing of several lines is necessary to get the full status of the signature. This is why use of gpgme is recommended. (Programmatic use of GnuPG (Using the GNU Privacy Guard)).

If you want to implement the API yourself, here is one important part of the documentation: git.gnupg.org Git - gnupg.git/blob - doc/DETAILS (taken from the git head of the stable version, you should check this against the version you are using).

This status indicates that the signature is cryptographically
[…]
Multiple status (VALIDSIG and the other appropriate *SIG status) are emitted for a valid signature.

TRUST_
[…]
For good signatures one of these status lines are emitted to
indicate the validity of the key used to create the signature.

Best Regards,
Bernhard

Towards the second question:

There is a third concept called “ownertrust”, it means how much do you trust a pubkey to certify other pubkeys. Please do two things:
a) check the output of your key with --edit-key to see the ownertrust, if it is not as you think it should be, use the interactive subcommand trust.
b) give the version of GnuPG you are using and the platform.

Thank you very much for the clarification!

Did I get i right that I have to parse the TRUST from another line to get the validity rather than the VALIDSIG line? Why am I getting the VALIDSIG line and what is the purpose of it, when the validity can be seen somewhere else? I don’t get the VALIDSIG line with other signatures that are not valid.

The ownertrust of my key is ultimate, at least if I read it correctly form the output of gpg --edit-key:

> $ gpg --edit-key AF3295423E1B6B50C42006C59F8921B4BAEA00F2                                                                  
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret subkeys are available.

gpg: using pgp trust model
pub  rsa4096/9F8921B4BAEA00F2
     created: 2022-06-21  expires: never       usage: C   
     trust: ultimate      validity: ultimate
ssb  rsa4096/2C556D21C4A6DEC0
     created: 2022-06-21  expires: never       usage: S   
     card-no: 0006 16038111
ssb  rsa4096/243435F10756C4CD
     created: 2022-06-21  expires: never       usage: E   
     card-no: 0006 16038111
ssb  rsa4096/A49A4495E84CC58D
     created: 2022-06-21  expires: never       usage: A   
     card-no: 0006 16038111
[ultimate] (1). Me myself and I <me@company.com>

My gpg version is 2.2.27 (Im running Ubuntu 22.04)

> $ gpg --version                                                                                                            
gpg (GnuPG) 2.2.27
libgcrypt 1.9.4
Copyright (C) 2021 Free Software Foundation, Inc.