1"""
2<Module Name>
3 constants.py
4
5<Author>
6 Santiago Torres-Arias <santiago@nyu.edu>
7
8<Started>
9 Nov 15, 2017
10
11<Copyright>
12 See LICENSE for licensing information.
13
14<Purpose>
15 aggregates all the constant definitions and lookup structures for signature
16 handling
17"""
18
19from __future__ import annotations
20
21import functools
22import logging
23import os
24import shlex
25import subprocess
26
27log = logging.getLogger(__name__)
28
29GPG_TIMEOUT = 10
30
31
32@functools.lru_cache(maxsize=3)
33def is_available_gnupg(gnupg: str, timeout: int | None = None) -> bool:
34 """Returns whether gnupg points to a gpg binary."""
35 if timeout is None:
36 timeout = GPG_TIMEOUT
37
38 gpg_version_cmd = shlex.split(f"{gnupg} --version")
39 try:
40 subprocess.run( # noqa: S603
41 gpg_version_cmd,
42 capture_output=True,
43 timeout=timeout,
44 check=True,
45 )
46 return True
47 except (OSError, subprocess.TimeoutExpired):
48 return False
49
50
51GPG_ENV_COMMAND = os.environ.get("GNUPG")
52GPG2_COMMAND = "gpg2"
53GPG1_COMMAND = "gpg"
54
55
56def gpg_command() -> str:
57 """Returns command to run GPG, or ``""``` if not found)."""
58 # By default, we allow providing GPG client through the environment
59 # assuming gpg2 as default value and test if exists. Otherwise, we assume gpg
60 # exists.
61 if GPG_ENV_COMMAND:
62 if is_available_gnupg(GPG_ENV_COMMAND):
63 return GPG_ENV_COMMAND
64 elif is_available_gnupg(GPG2_COMMAND):
65 return GPG2_COMMAND
66 elif is_available_gnupg(GPG1_COMMAND):
67 return GPG1_COMMAND
68 return ""
69
70
71def have_gpg() -> bool:
72 """Returns True if a gpg_command is available."""
73 return bool(gpg_command())
74
75
76def gpg_version_command() -> list[str]:
77 """Returns the command to get the current GPG version."""
78 return shlex.split(f"{gpg_command()} --version")
79
80
81FULLY_SUPPORTED_MIN_VERSION = "2.1.0"
82NO_GPG_MSG = (
83 f"GPG support requires a GPG client. 'gpg2' or 'gpg' with version "
84 f"{FULLY_SUPPORTED_MIN_VERSION} or newer is fully supported."
85)
86
87
88def gpg_sign_command(keyarg: str, homearg: str) -> list[str]:
89 """Returns the command to use GPG to sign STDIN."""
90 return shlex.split(
91 f"{gpg_command()} --detach-sign --digest-algo SHA256 {keyarg} {homearg}"
92 )
93
94
95def gpg_export_pubkey_command(homearg: str, keyid: str) -> list[str]:
96 """Returns the GPG command to export a public key."""
97 return shlex.split(f"{gpg_command()} {homearg} --export {keyid}")
98
99
100# See RFC4880 section 4.3. Packet Tags for a list of all packet types The
101# relevant packets defined below are described in sections 5.2 (signature),
102# 5.5.1.1 (primary pubkey) and 5.5.1.2 (pub subkey), 5.12 (user id) and 5.13
103# (user attribute)
104PACKET_TYPE_SIGNATURE = 0x02
105PACKET_TYPE_PRIMARY_KEY = 0x06
106PACKET_TYPE_USER_ID = 0x0D
107PACKET_TYPE_USER_ATTR = 0x11
108PACKET_TYPE_SUB_KEY = 0x0E
109
110
111# See sections 5.2.3 (signature) and 5.5.2 (public key) of RFC4880
112SUPPORTED_SIGNATURE_PACKET_VERSIONS = {0x04}
113SUPPORTED_PUBKEY_PACKET_VERSIONS = {0x04}
114
115# The constants for hash algorithms are taken from section 9.4 of RFC4880.
116SHA1 = 0x02
117SHA256 = 0x08
118SHA512 = 0x0A
119
120# See section 5.2.1 of RFC4880
121SIGNATURE_TYPE_BINARY = 0x00
122SIGNATURE_TYPE_SUB_KEY_BINDING = 0x18
123SIGNATURE_TYPE_CERTIFICATES = {0x10, 0x11, 0x12, 0x13}
124
125# See section 5.2.3.4 (Signature Creation Time) of RFC4880
126SIG_CREATION_SUBPACKET = 0x02
127# See section 5.2.3.5. (Issuer) of RFC4880
128PARTIAL_KEYID_SUBPACKET = 0x10
129# See section 5.2.3.6 (Key Expiration Time) of RFC4880
130KEY_EXPIRATION_SUBPACKET = 0x09
131# See section 5.2.3.19 (Primary User ID) of RFC4880
132PRIMARY_USERID_SUBPACKET = 0x19
133# See section 5.2.3.28. (Issuer Fingerprint) of rfc4880bis-06
134FULL_KEYID_SUBPACKET = 0x21
135
136GPG_HASH_ALGORITHM_STRING = "pgp+SHA2"