pull/86237/merge
David Shrewsbury 13 hours ago committed by GitHub
commit ea9cd7a7d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -94,8 +94,8 @@ stages:
test: rhel/9.6@3.9
- name: RHEL 9.6 py312
test: rhel/9.6@3.12
- name: RHEL 10.0
test: rhel/10.0
- name: RHEL 10.1
test: rhel/10.1
- name: FreeBSD 13.5
test: freebsd/13.5
- name: FreeBSD 14.3
@ -110,8 +110,8 @@ stages:
test: macos/15.3
- name: RHEL 9.6
test: rhel/9.6
- name: RHEL 10.0
test: rhel/10.0
- name: RHEL 10.1
test: rhel/10.1
groups:
- 3
- 4
@ -125,8 +125,8 @@ stages:
test: fedora/42
- name: RHEL 9.6
test: rhel/9.6
- name: RHEL 10.0
test: rhel/10.0
- name: RHEL 10.1
test: rhel/10.1
- name: Ubuntu 24.04
test: ubuntu/24.04
groups:

@ -0,0 +1,5 @@
bugfixes:
- rpm_key - Use librpm library API instead of gpg utility to support version 6 PGP keys (https://github.com/ansible/ansible/issues/86157).
minor_changes:
- ansible-test - Replace RHEL 10.0 with RHEL 10.1 as a remote platform for testing.

@ -85,15 +85,308 @@ EXAMPLES = """
RETURN = r"""#"""
import ctypes
import ctypes.util
import hashlib
import re
import os.path
import tempfile
import typing as t
# import module snippets
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.common.text.converters import to_native
# Type alias for ctypes pointer to uint8 array (packet data)
# Using Any here because ctypes._Pointer is private, but documenting the actual type
PktPointer = t.Any # Actually: ctypes.POINTER(ctypes.c_uint8)
class LibRPM:
"""Wrapper for librpm PGP key functions"""
def __init__(self) -> None:
# Load the librpm library
lib_path = ctypes.util.find_library('rpm')
if not lib_path:
raise ImportError("Error: Could not find librpm library")
try:
self.lib = ctypes.CDLL(lib_path)
except OSError:
raise ImportError(f"Error: Could not load librpm library from {lib_path}")
try:
self.libc = ctypes.CDLL(None)
except OSError:
raise ImportError("Error: Could not load libC library")
# Constants
self.PGPTAG_PUBLIC_KEY = 6
self.PGPTAG_PUBLIC_SUBKEY = 14
# pgpArmor pgpParsePkts(const char *armor, uint8_t **pkt, size_t *pktlen)
self.lib.pgpParsePkts.argtypes = [
ctypes.c_char_p,
ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8)),
ctypes.POINTER(ctypes.c_size_t)
]
self.lib.pgpParsePkts.restype = ctypes.c_int
# void free(void *ptr)
self.libc.free.argtypes = [ctypes.c_void_p]
self.libc.free.restype = None
def _parse_armor(self, armor: str) -> tuple[t.Optional[PktPointer], int]:
"""
Parse ASCII armored PGP data using pgpParsePkts().
Returns (pkt, pktlen) tuple or (None, 0) on error.
"""
pkt = ctypes.POINTER(ctypes.c_uint8)()
pktlen = ctypes.c_size_t()
armor_bytes = armor.encode('utf-8')
result = self.lib.pgpParsePkts(armor_bytes, ctypes.byref(pkt), ctypes.byref(pktlen))
if result < 0 or not pkt:
return None, 0
return pkt, pktlen.value
def _parse_packet_header(self, pkt: PktPointer, offset: int, pktlen: int) -> tuple[t.Optional[int], int, int]:
"""
Parse a PGP packet header to get tag and packet length.
Returns (tag, body_length, header_length) or (None, 0, 0) on error.
Per RFC 9580 - Section 4.2: Packet Headers
https://www.rfc-editor.org/rfc/rfc9580.html#name-packet-headers
"""
if offset >= pktlen:
return None, 0, 0
tag_byte = pkt[offset]
# Check if it's a new format packet (bit 6 set)
if tag_byte & 0x40:
# New format
tag = tag_byte & 0x3f # bits 0-5 are packet type ID
offset += 1
if offset >= pktlen:
return None, 0, 0
first_len_byte = pkt[offset]
if first_len_byte < 192:
# One-octet length
return tag, first_len_byte, 2
elif first_len_byte < 224:
# Two-octet length
if offset + 1 >= pktlen:
return None, 0, 0
length = ((first_len_byte - 192) << 8) + pkt[offset + 1] + 192
return tag, length, 3
elif first_len_byte == 255:
# Five-octet length
if offset + 4 >= pktlen:
return None, 0, 0
length = (pkt[offset + 1] << 24) | (pkt[offset + 2] << 16) | \
(pkt[offset + 3] << 8) | pkt[offset + 4]
return tag, length, 6
else:
# Partial body length (not supported here)
return None, 0, 0
else:
# Old format
tag = (tag_byte >> 2) & 0x0f
length_type = tag_byte & 0x03
if length_type == 0:
# One-octet length
if offset + 1 >= pktlen:
return None, 0, 0
return tag, pkt[offset + 1], 2
elif length_type == 1:
# Two-octet length
if offset + 2 >= pktlen:
return None, 0, 0
length = (pkt[offset + 1] << 8) | pkt[offset + 2]
return tag, length, 3
elif length_type == 2:
# Four-octet length
if offset + 4 >= pktlen:
return None, 0, 0
length = (pkt[offset + 1] << 24) | (pkt[offset + 2] << 16) | \
(pkt[offset + 3] << 8) | pkt[offset + 4]
return tag, length, 5
else:
# Indeterminate length (not supported)
return None, 0, 0
def _find_key_packets(self, pkt: PktPointer, pktlen: int) -> list[tuple[int, int]]:
"""
Walk the packet stream and find all PGPTAG_PUBLIC_KEY and PGPTAG_PUBLIC_SUBKEY packets.
Returns list of (offset, total_packet_length) tuples.
"""
key_packets: list[tuple[int, int]] = []
offset = 0
while offset < pktlen:
tag, body_len, header_len = self._parse_packet_header(pkt, offset, pktlen)
if tag is None:
break
if tag in (self.PGPTAG_PUBLIC_KEY, self.PGPTAG_PUBLIC_SUBKEY):
# Found a key packet
total_len = header_len + body_len
key_packets.append((offset, total_len))
# Move to next packet
offset += header_len + body_len
return key_packets
def _get_key_version(self, pkt: PktPointer, offset: int, pktlen: int) -> t.Optional[int]:
"""
Get the version byte from a key packet.
Returns version number (4 or 6) or None on error.
"""
tag, dummy, header_len = self._parse_packet_header(pkt, offset, pktlen)
if tag is None:
return None
# Extract packet body (skip the packet header)
body_offset = offset + header_len
if body_offset >= pktlen:
return None
# First byte of body is the version
return pkt[body_offset]
def _compute_v4_fingerprint(self, pkt: PktPointer, offset: int, pktlen: int) -> t.Optional[str]:
"""
Compute V4 fingerprint from packet data.
For V4 keys, fingerprint = SHA-1(0x99 || 2-byte-length || packet_body)
Per RFC 4880 Section 12.2
"""
tag, body_len, header_len = self._parse_packet_header(pkt, offset, pktlen)
if tag is None:
return None
# Extract packet body (skip the packet header)
body_offset = offset + header_len
if body_offset + body_len > pktlen:
return None
# Check if it's a V4 key (first byte of body should be 0x04)
if pkt[body_offset] != 0x04:
return None
# Build the data for fingerprint: 0x99 || 2-byte length || body
fp_data = bytearray()
fp_data.append(0x99) # V4 public key packet tag
fp_data.append((body_len >> 8) & 0xFF) # Length high byte
fp_data.append(body_len & 0xFF) # Length low byte
# Append the packet body
for i in range(body_len):
fp_data.append(pkt[body_offset + i])
# Compute SHA-1 hash
fingerprint = hashlib.sha1(fp_data).digest()
return fingerprint.hex().upper()
def _compute_v6_fingerprint(self, pkt: PktPointer, offset: int, pktlen: int) -> t.Optional[str]:
"""
Compute V6 fingerprint from packet data.
For V6 keys, fingerprint = SHA-256(0x9B || 4-byte-length || packet_body)
Per RFC 9580 Section 5.5.4
"""
tag, body_len, header_len = self._parse_packet_header(pkt, offset, pktlen)
if tag is None:
return None
# Extract packet body (skip the packet header)
body_offset = offset + header_len
if body_offset + body_len > pktlen:
return None
# Check if it's a V6 key (first byte of body should be 0x06)
if pkt[body_offset] != 0x06:
return None
# Build the data for fingerprint: 0x9B || 4-byte length || body
fp_data = bytearray()
fp_data.append(0x9B) # V6 public key packet tag
fp_data.append((body_len >> 24) & 0xFF) # Length byte 1 (MSB)
fp_data.append((body_len >> 16) & 0xFF) # Length byte 2
fp_data.append((body_len >> 8) & 0xFF) # Length byte 3
fp_data.append(body_len & 0xFF) # Length byte 4 (LSB)
# Append the packet body
for i in range(body_len):
fp_data.append(pkt[body_offset + i])
# Compute SHA-256 hash
fingerprint = hashlib.sha256(fp_data).digest()
return fingerprint.hex().upper()
def _identify_keys(self, armor: str) -> list[dict[str, str]]:
"""Return a list of dicts with key ID and fingerprint for the primary key and each subkey"""
key_info: list[dict[str, str]] = []
pkt, pktlen = self._parse_armor(armor)
if not pkt:
raise Exception("Unable to parse PGP key armor")
# Find all key packets in the stream and compute their fingerprints.
key_packets = self._find_key_packets(pkt, pktlen)
for offset, dummy in key_packets:
# Detect key version
version = self._get_key_version(pkt, offset, pktlen)
if version == 0x04:
# V4 key
computed_fp = self._compute_v4_fingerprint(pkt, offset, pktlen)
if computed_fp:
# V4: Key ID is the last 8 bytes (16 hex chars) of the fingerprint
keyid_from_fp = computed_fp[-16:]
key_info.append({'keyid': keyid_from_fp, 'fingerprint': computed_fp})
elif version == 0x06:
# V6 key
computed_fp = self._compute_v6_fingerprint(pkt, offset, pktlen)
if computed_fp:
# V6: Key ID is the first 8 bytes (16 hex chars) of the fingerprint
keyid_from_fp = computed_fp[:16]
key_info.append({'keyid': keyid_from_fp, 'fingerprint': computed_fp})
self.libc.free(pkt)
return key_info
def get_key_ids_from_armor(self, armor: str) -> list[str]:
"""
Get the key IDs from the primary PGP key, and all subkeys of that key, from the ASCII armored key.
'armor' is expected to be a single ASCII armored PGP key (v4 or v6). The primary key should be the
first item in the results, followed by its subkeys.
"""
return [key['keyid'] for key in self._identify_keys(armor)]
def get_fingerprints_from_armor(self, armor: str) -> list[str]:
"""
Get the fingerprints from the primary PGP key, and all subkeys of that key, from the ASCII armored key.
'armor' is expected to be a single ASCII armored PGP key (v4 or v6). The primary key should be the
first item in the results, followed by its subkeys.
"""
return [key['fingerprint'] for key in self._identify_keys(armor)]
def is_pubkey(string):
"""Verifies if string is a pubkey"""
@ -120,9 +413,7 @@ class RpmKey(object):
fingerprint = [fingerprint]
fingerprints = set(f.replace(' ', '').upper() for f in fingerprint)
self.gpg = self.module.get_bin_path('gpg')
if not self.gpg:
self.gpg = self.module.get_bin_path('gpg2', required=True)
self.librpm = LibRPM()
if '://' in key:
keyfile = self.fetch_key(key)
@ -187,40 +478,18 @@ class RpmKey(object):
return ret
def getkeyid(self, keyfile):
stdout, stderr = self.execute_command([self.gpg, '--no-tty', '--batch', '--with-colons', '--fixed-list-mode', keyfile])
for line in stdout.splitlines():
line = line.strip()
if line.startswith('pub:'):
return line.split(':')[4]
self.module.fail_json(msg="Unexpected gpg output")
with open(keyfile, "r") as key_fd:
key_ids = self.librpm.get_key_ids_from_armor(key_fd.read())
if not key_ids:
self.module.fail_json(msg="Failed to get keyid")
return key_ids[0]
def getfingerprints(self, keyfile):
stdout, stderr = self.execute_command([
self.gpg, '--no-tty', '--batch', '--with-colons',
'--fixed-list-mode', '--import', '--import-options', 'show-only',
'--dry-run', keyfile
])
fingerprints = set()
for line in stdout.splitlines():
line = line.strip()
if line.startswith('fpr:'):
# As mentioned here,
#
# https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob_plain;f=doc/DETAILS
#
# The description of the `fpr` field says
#
# "fpr :: Fingerprint (fingerprint is in field 10)"
#
fingerprints.add(line.split(':')[9])
if fingerprints:
return fingerprints
self.module.fail_json(msg="Unexpected gpg output")
with open(keyfile, "r") as key_fd:
fingerprints = self.librpm.get_fingerprints_from_armor(key_fd.read())
if not fingerprints:
self.module.fail_json(msg="Failed to get fingerprint")
return frozenset(fingerprints)
def is_keyid(self, keystr):
"""Verifies if a key, as provided by the user is a keyid"""
@ -233,15 +502,39 @@ class RpmKey(object):
return stdout, stderr
def is_key_imported(self, keyid):
"""
Uses 'rpm' CLI to output the ASCII armor of all imported keys, then gets the key ID
for each to determine if the supplied key is among them.
"""
cmd = self.rpm + ' -q gpg-pubkey'
rc, stdout, stderr = self.module.run_command(cmd)
if rc != 0: # No key is installed on system
return False
cmd += ' --qf "%{description}" | ' + self.gpg + ' --no-tty --batch --with-colons --fixed-list-mode -'
cmd += ' --qf "%{description}"'
stdout, stderr = self.execute_command(cmd)
# Split the content into individual key blocks
key_blocks = []
current_block = []
in_key_block = False
for line in stdout.splitlines():
if keyid in line.split(':')[4]:
if line.strip() == '-----BEGIN PGP PUBLIC KEY BLOCK-----':
in_key_block = True
current_block = [line]
elif line.strip() == '-----END PGP PUBLIC KEY BLOCK-----':
current_block.append(line)
key_blocks.append('\n'.join(current_block))
current_block = []
in_key_block = False
elif in_key_block:
current_block.append(line)
for armor_string in key_blocks:
key_ids = self.librpm.get_key_ids_from_armor(armor_string)
if keyid in key_ids:
return True
return False
def import_key(self, keyfile):

@ -0,0 +1,521 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: 3F88 FFB5 8C58 7323 B818 9B81 DF71 002F FA1B 4101 CDDA 432E BF34 21D5 67E2 5C5E
Comment: <info@redhat.com>
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
EhYbIicz
-----END PGP PUBLIC KEY BLOCK-----

@ -19,8 +19,10 @@
always:
# This will fail if the tests leave no keys in the key store
- name: Remove all GPG keys from key ring
shell: rpm -q gpg-pubkey | xargs rpm -e
ignore_errors: yes
- name: Restore the previously installed GPG keys
command: rpm --import {{ (remote_tmp_dir + '/pubkeys') | quote }}

@ -102,21 +102,17 @@
assert:
that: checksig.stdout is contains 'digests signatures OK'
- name: Get keyid
shell: rpm -q gpg-pubkey | head -n 1 | xargs rpm -q --qf %{version}
register: key_id
- name: Remove test key using keyid
rpm_key:
state: absent
key: "{{ key_id.stdout }}"
key: "{{ test_key_keyid }}"
register: remove_keyid
failed_when: not remove_keyid.changed
- name: Remove test key using keyid (idempotent)
rpm_key:
state: absent
key: "{{ key_id.stdout }}"
key: "{{ test_key_keyid }}"
register: key_id_idempotence
- name: Verify idempotent (key_id)
@ -227,3 +223,56 @@
that:
- result is success
- result is not changed
# Test PGPv6 keys
- name: Test PGPv6 Keys
block:
# Reset to test PGPv6 keys validation
- name: Remove all keys from key ring
shell: rpm -q gpg-pubkey | xargs rpm -e
- name: Copy v6 key to remote
copy:
src: "files/{{ test_v6_key_file }}"
dest: "{{ remote_tmp_dir }}/{{ test_v6_key_file }}"
mode: "0644"
- name: Import v6 key
rpm_key:
state: present
key: "{{ remote_tmp_dir }}/{{ test_v6_key_file }}"
fingerprint: "{{ invalid_fingerprint }}"
register: result
failed_when: result is success
- name: Verify invalid fingerprint failure
assert:
that:
- result is success
- result is not changed
- result.msg is contains 'does not match any key fingerprints'
- name: Import v6 key
rpm_key:
state: present
key: "{{ remote_tmp_dir }}/{{ test_v6_key_file }}"
fingerprint: "{{ test_v6_key_fingerprint }}"
- name: Import v6 key (idempotent)
rpm_key:
state: present
key: "{{ remote_tmp_dir }}/{{ test_v6_key_file }}"
fingerprint: "{{ test_v6_key_fingerprint }}"
register: key_idempotence
- name: Verify idempotence
assert:
that: key_idempotence is not changed
- name: Delete v6 key
rpm_key:
state: absent
key: "{{ test_v6_key_keyid }}"
when: ansible_facts['distribution'] == "RedHat" and ansible_facts['distribution_version'] is version('10.1', '>=')

@ -1,4 +1,5 @@
test_key_url: https://ci-files.testing.ansible.com/test/integration/targets/rpm_key/RPM-GPG-KEY-EPEL-10
test_key_keyid: 33D98517E37ED158
test_key_path: "{{ remote_tmp_dir + '/RPM-GPG-KEY-EPEL-10' }}"
test_rpm_url: https://ci-files.testing.ansible.com/test/integration/targets/rpm_key/scitokens-cpp-devel-1.1.3-2.el10_1.x86_64.rpm
test_rpm_path: "{{ remote_tmp_dir + '/test.rpm' }}"
@ -6,3 +7,6 @@ sub_key_url: https://ci-files.testing.ansible.com/test/integration/targets/rpm_k
invalid_fingerprint: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
primary_fingerprint: 66D1 5FDD 8728 7219 C8E1 5478 D200 CD70 2853 E6D0
sub_key_fingerprint: E617 DCD4 065C 2AFC 0B2C F7A7 BA8B C08C 0F69 1F94
test_v6_key_file: "test-v6-key.cert"
test_v6_key_fingerprint: "3F88FFB58C587323B8189B81DF71002FFA1B4101CDDA432EBF3421D567E25C5E"
test_v6_key_keyid: "3F88FFB58C587323"

@ -8,7 +8,7 @@ freebsd python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
macos/15.3 python=3.13 python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
macos python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
rhel/9.6 python=3.9,3.12 become=sudo provider=aws arch=x86_64
rhel/10.0 python=3.12 become=sudo provider=aws arch=x86_64
rhel/10.1 python=3.12 become=sudo provider=aws arch=x86_64
rhel become=sudo provider=aws arch=x86_64
ubuntu/22.04 python=3.10 become=sudo provider=aws arch=x86_64
ubuntu/24.04 python=3.12 become=sudo provider=aws arch=x86_64

Loading…
Cancel
Save