Skip to content

Commit d4982af

Browse files
committed
Simplify PIV zlib support for Net iD
1 parent 5f3b929 commit d4982af

File tree

2 files changed

+44
-60
lines changed

2 files changed

+44
-60
lines changed

tests/test_piv.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
Chuid,
1919
FascN,
2020
_do_check_key_support,
21-
_cxf_dictionary,
2221
decompress_certificate,
2322
)
2423

@@ -182,35 +181,35 @@ def test_gzip_decompression(self):
182181
result = decompress_certificate(compressed_data)
183182
assert result == original_data
184183

185-
def test_cxf_deflate_decompression(self):
186-
"""Test decompression of CXF deflate format (used by Pointsharp Net iD)."""
187-
original_data = b"Test certificate content for CXF format"
184+
def test_zlib_deflate_decompression(self):
185+
"""Test decompression of zlib deflate format (used by Pointsharp Net iD)."""
186+
original_data = b"Test certificate content for zlib format"
188187

189-
# CXF format: 0x01 0x00 + 2-byte little-endian length + zlib compressed data
190-
compressor = zlib.compressobj(wbits=zlib.MAX_WBITS, zdict=_cxf_dictionary)
188+
# zlib format: 0x01 0x00 + 2-byte little-endian length + zlib compressed data
189+
compressor = zlib.compressobj(wbits=zlib.MAX_WBITS)
191190
compressed = compressor.compress(original_data) + compressor.flush()
192191

193-
# Build CXF format: magic bytes + length + compressed data
192+
# Build zlib format: magic bytes + length + compressed data
194193
length_bytes = len(original_data).to_bytes(2, "little")
195-
cxf_data = b"\x01\x00" + length_bytes + compressed
194+
zlib_data = b"\x01\x00" + length_bytes + compressed
196195

197-
result = decompress_certificate(cxf_data)
196+
result = decompress_certificate(zlib_data)
198197
assert result == original_data
199198

200-
def test_cxf_deflate_wrong_length_raises(self):
201-
"""Test that CXF deflate with wrong expected length raises ValueError."""
199+
def test_zlib_deflate_wrong_length_raises(self):
200+
"""Test that zlib deflate with wrong expected length raises ValueError."""
202201
original_data = b"Test certificate content"
203202

204-
compressor = zlib.compressobj(wbits=zlib.MAX_WBITS, zdict=_cxf_dictionary)
203+
compressor = zlib.compressobj(wbits=zlib.MAX_WBITS)
205204
compressed = compressor.compress(original_data) + compressor.flush()
206205

207206
# Use wrong length (actual length + 10)
208207
wrong_length = len(original_data) + 10
209208
length_bytes = wrong_length.to_bytes(2, "little")
210-
cxf_data = b"\x01\x00" + length_bytes + compressed
209+
zlib_data = b"\x01\x00" + length_bytes + compressed
211210

212211
with pytest.raises(BadResponseError):
213-
decompress_certificate(cxf_data)
212+
decompress_certificate(zlib_data)
214213

215214
def test_invalid_data_raises_bad_response_error(self):
216215
"""Test that invalid/uncompressed data raises BadResponseError."""
@@ -227,12 +226,12 @@ def test_corrupted_gzip_raises_bad_response_error(self):
227226
with pytest.raises(BadResponseError):
228227
decompress_certificate(corrupted_gzip)
229228

230-
def test_cxf_format_fallback_to_gzip(self):
231-
"""Test that invalid CXF data falls back to gzip decompression."""
229+
def test_zlib_format_fallback_to_gzip(self):
230+
"""Test that invalid zlib data falls back to gzip decompression."""
232231
original_data = b"Fallback test data"
233232

234-
# Create data that starts with CXF magic but is actually gzip compressed
235-
# The CXF decompression will fail and it should fall back to gzip
233+
# Create data that starts with zlib magic but is actually gzip compressed
234+
# The zlib decompression will fail and it should fall back to gzip
236235
gzip_data = gzip.compress(original_data)
237236

238237
result = decompress_certificate(gzip_data)

yubikit/piv.py

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import re
3434
import warnings
3535
import zlib
36-
from base64 import b64decode
3736
from dataclasses import astuple, dataclass
3837
from datetime import date
3938
from enum import Enum, IntEnum, unique
@@ -657,53 +656,39 @@ def _parse_device_public_key(key_type, encoded):
657656
return ec.EllipticCurvePublicKey.from_encoded_point(curve(), data[0x86])
658657

659658

660-
# This data embeds pre-computed structures used by the zlib deflate algorithm
661-
# It's purpose is to avoid to store the full dictionary in the compressed
662-
# stream, thus reducing the size of the certificate
663-
# Source: https://datatracker.ietf.org/doc/html/draft-pritikin-comp-x509-00#appendix-A
664-
_cxf_dictionary = b64decode("""
665-
MIIBOTCCASOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAZMRcwFQYDVQQDEw5odHRw
666-
Oi8vd3d3LmNvbTAeFw0xMDA1MTExOTEzMDNaFw0xMTA1MTExOTEzMDNaMF8xEDAO
667-
BgkqhkiG9w0BCQEWAUAxCjAIBgNVBAMTASAxCzAJBgNVBAYTAlVTMQswCQYDVQQI
668-
EwJXSTELMAkGA1UEChMCb24xDDAKBgNVBAsTA291bjEKMAgGA1UEBRMBIDAfMA0G
669-
CSqGSIb3DQEBAQUAAw4AMAsCBG6G5ZUCAwEAAaNNMEswCQYDVR0TBAIwADAdBgNV
670-
HQ4EFgQUHSkK6busCxxK6PKpBlL9q8K1mcQwHwYDVR0jBBgwFoAUn7r/DVMuEpK9
671-
Rxq3nyiLml10+nQwDQYJKoZIhvcNAQEFBQADAQA=
672-
""")
673-
674-
675659
def decompress_certificate(cert_data: bytes) -> bytes:
676660
"""
677661
Decompress a compressed certificate using various methods.
678662
"""
679663
logger.debug("Certificate is compressed, decompressing...")
680664

681-
# CXF Deflate Method (as used by Pointsharp Net iD)
682-
if cert_data[:2] == b"\01\00":
683-
expected_length = int.from_bytes(cert_data[2:4], "little")
684-
decompressor = zlib.decompressobj(wbits=zlib.MAX_WBITS, zdict=_cxf_dictionary)
685-
try:
686-
cert_data = decompressor.decompress(cert_data[4:])
687-
if len(cert_data) != expected_length:
688-
logger.error(
689-
"Unexpected decompressed length, expected %d, got %d",
690-
expected_length,
691-
len(cert_data),
692-
)
693-
raise ValueError("Decompressed length does not match expected length")
694-
695-
logger.debug("Decompressed certificate with CXF deflate format")
696-
return cert_data
697-
except (zlib.error, ValueError):
698-
logger.warning("Failed to decompress with CXF format")
699-
700-
# Gzip Method (default)
701-
try:
702-
cert_data = gzip.decompress(cert_data)
703-
logger.debug("Decompressed certificate with basic gzip format")
704-
return cert_data
705-
except (zlib.error, gzip.BadGzipFile):
706-
logger.warning("Failed to decompressed with basic gzip format")
665+
match tuple(cert_data[:2]):
666+
case (0x1F, 0x8B): # Gzip (most commonly used)
667+
logger.debug("Decompressing certificate using gzip")
668+
try:
669+
return gzip.decompress(cert_data)
670+
except (zlib.error, gzip.BadGzipFile):
671+
logger.warning("Failed to decompressed with gzip")
672+
case (0x01, 0x00): # Net iD zlib format
673+
logger.debug("Decompressing certificate using zlib")
674+
expected_length = int.from_bytes(cert_data[2:4], "little")
675+
try:
676+
decompressed = zlib.decompress(cert_data[4:])
677+
if len(decompressed) != expected_length:
678+
logger.error(
679+
"Unexpected decompressed length, expected %d, got %d",
680+
expected_length,
681+
len(decompressed),
682+
)
683+
raise BadResponseError(
684+
"Decompressed length does not match expected length"
685+
)
686+
687+
return decompressed
688+
except (zlib.error, ValueError):
689+
logger.warning("Failed to decompress with zlib")
690+
case _:
691+
logger.warning("Unknown compression type")
707692

708693
raise BadResponseError("Failed to decompress certificate")
709694

0 commit comments

Comments
 (0)