1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
'''
this are linter checks for package signatures
'''
import base64
import logging
import datetime
from parabola_repolint.linter import LinterIssue, LinterCheckBase, LinterCheckType
class SigningKeyExpiry(LinterCheckBase):
'''
for the list of signing keys and subkeys in parabola.gpg that are used to sign
packages in the repos, check whether they are expired, or are about to expire.
This check reports an issue for any expired signing key in the keyring, as well
as any key that is going to expire within the next 90 days, indicating that the
key should be extended and the keyring rebuilt to avoid user-facing issues on
system updates.
'''
name = 'signing_key_expiry'
check_type = LinterCheckType.SIGNING_KEY
header = 'signing keys expired or about to expire'
# pylint: disable=no-self-use
def check(self, key):
''' run the check '''
if not key['packages']:
return
if not key['expires']:
return
expires = datetime.datetime.utcfromtimestamp(int(key['expires']))
time = expires.strftime("%Y-%m-%d")
reason = None
if expires < datetime.datetime.now():
reason = 'expired %s' % time
elif expires <= datetime.datetime.now() + datetime.timedelta(days=90):
reason = 'expires %s' % time
if reason is not None:
if 'master_key' in key:
reason += ' (subkey of %s)' % key['master_key']
raise LinterIssue('%s: %s (signed %i)', key['keyid'], reason, len(key['packages']))
class MasterKeyExpiry(LinterCheckBase):
'''
for the list of master keys in parabola.gpg, check whether they are expired, or
are about to expire. This check reports an issue for any expired master key in
the keyring, as well as any key that is going to expire within the next 90
days, indicating that the key should be extended and the keyring rebuilt to
avoid user-facing issues on system updates.
'''
name = 'master_key_expiry'
check_type = LinterCheckType.MASTER_KEY
header = 'master keys expired or about to expire'
# pylint: disable=no-self-use
def check(self, key):
''' run the check '''
if not key['expires']:
return
expires = datetime.datetime.utcfromtimestamp(int(key['expires']))
time = expires.strftime("%Y-%m-%d")
reason = None
if expires < datetime.datetime.now():
reason = 'expired %s' % time
elif expires <= datetime.datetime.now() + datetime.timedelta(days=90):
reason = 'expires %s' % time
if reason is not None:
raise LinterIssue('%s: %s', key['keyid'], reason)
class PkgEntrySignatureMismatch(LinterCheckBase):
'''
for the list of entries in the repo.db's, check whether the signature stored in
the repository matches the signature stored in the detached signature file of
the corresponding built package. This check reports an issue whenever the
signature in the repo.db reports a different key then what was used to produce
the built package signature.
'''
name = 'pkgentry_signature_mismatch'
check_type = LinterCheckType.PKGENTRY
header = 'repo.db entries with mismatched signing keys'
# pylint: disable=no-self-use
def check(self, pkgentry):
''' run the check '''
if not pkgentry.pkgfile:
return
key1 = pkgentry.pkgfile.siginfo['key_id'].upper()
key2 = base64.b64decode(pkgentry.pgpsig)
# quick & dirty gpg packet parser
# https://tools.ietf.org/search/rfc4880
header = key2[0]
assert header & 0x80 == 0x80
assert header & 0x40 == 0x00
assert header & 0x03 == 0x01
assert header & 0x1C == 0x08
version = key2[3]
assert version == 0x04
hashed_len = key2[7] * 256 + key2[8]
unhashed_len = key2[9 + hashed_len] * 256 + key2[10 + hashed_len]
data = key2[11 + hashed_len:11 + hashed_len + unhashed_len]
assert data[1] == 0x10 # issuer
key2 = data[2:10].hex().upper()
if key1 != key2:
raise LinterIssue('%s: %s != %s', pkgentry, key1, key2)
class PkgFileInvalidSignature(LinterCheckBase):
'''
this check validates the package signature against the pacman keyrings. It
reports an issue whenever a package is signed by an unknown key, that is not
part of the keyring, or by a key that has expired.
'''
name = 'pkgfile_invalid_signature'
check_type = LinterCheckType.PKGFILE
header = 'packages with invalid signatures'
def check(self, package):
''' run the check '''
verify = package.siginfo
key = self._cache.key_cache.get(verify['key_id'], None)
if key is None:
raise LinterIssue('%s: no public key: %s', package, verify['key_id'])
if key['expires']:
expires = datetime.datetime.utcfromtimestamp(int(key['expires']))
if expires < datetime.datetime.now():
raise LinterIssue('%s: signing key expired (%s)', package, verify['key_id'])
if not verify['valid']:
# if this is triggered, add more cases here.
logging.warning('%s: unknown gpg invalidity: %s', package, verify)
raise LinterIssue('%s: invalid signature', package)
|