Skip to content

Commit

Permalink
backwards compatible; minimal breakage
Browse files Browse the repository at this point in the history
  • Loading branch information
jvanasco committed Jan 15, 2025
1 parent 4c384c8 commit db02561
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 279 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ on:

jobs:
build:
name: test-${{ matrix.python }}-${{ matrix.build-type }}
name: test-${{ matrix.python }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
build-type: ["noopenssl", "legacy"]
steps:
- uses: actions/checkout@v3
- name: Setup Python
Expand All @@ -36,7 +35,7 @@ jobs:
- name: Install Poetry & Tox
run: pip install poetry>1.0.0 tox>3.3.0
- name: Run tox
run: tox -e github-${{ matrix.build-type }}
run: tox
# This job runs our tests like external parties such as packagers.
external:
runs-on: ubuntu-latest
Expand Down
32 changes: 17 additions & 15 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
Changelog
=========

This PR
----------
* Deprecated pyOpenSSL in favor of Cryptography.
* Deprecated the following for removal in the next scheduled release:
`josepy.util.ComparableX509`
* The following functions now accept a `josepy.util.ComparableX509` OR a
`cryptography.x509.Certificate`:
- `josepy.json_util.encode_cert`
* The following functions now accept a `josepy.util.ComparableX509` OR a
`cryptography.x509.CertificateSigningRequest`:
- `josepy.json_util.encode_csr`
* Added the following functions:
- `josepy.json_util.decode_cert_cryptography`
- `josepy.json_util.decode_csr_cryptography`
* x5c headers are now raw Cryptography objects


1.15.0 (master)
---------------
* Added support for Python 3.13.
* Dropped support for Python 3.7.
* Support for Python 3.8 has been deprecated and will be removed in the next
scheduled release.
* Deprecated pyOpenSSL in favor of Cryptography and removed the required
dependency. The underlying storage format of the `josepy.util.ComparableX509`
has been switched to `cryptography.x509` objects, and the
`josepy.util.ComparableX509.wrapped` attribute will now be either a
`cryptography.x509.Certificate` or `cryptography.x509.CertificateSigningRequest`
- objects from the `opensssl.crypto` package will be automatically transcoded
to their Cryptography counterparts on initialization. A new convenience
attribute, `josepy.util.ComparableX509.wrapped_legacy` will return an
`opensssl.crypto` object for affected projects that are unable to immediately
migrate code to the Cryptography objects. This is offered as a minimally
breaking change to aid in migration to Cryptography. Affected projects should
either pin to `1.14.0` or utilize the new attribute in a "hotfix" release.
Please note, due to the removal of `X509_V_FLAG_NOTIFY_POLICY` in pyOpenSSL
`23.2.0`, projects migrating to the new backend may experience a version
conflict during the code transition.

1.14.0 (2023-11-01)
-------------------
Expand Down
3 changes: 1 addition & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 1 addition & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ python = "^3.8"
cryptography = ">=42.0.0"
# Connection.set_tlsext_host_name (>=0.13)
# incompatibilities with cryptography; X509_V_FLAG_NOTIFY_POLICY removed (>=23.2.0)
pyopenssl = {version = ">=23.2.0", optional=true}
pyopenssl = ">=23.2.0"
# >=4.3.0 is needed for Python 3.10 support
sphinx = {version = ">=4.3.0", optional = true}
sphinx-rtd-theme = {version = ">=1.0", optional = true}
Expand Down Expand Up @@ -75,17 +75,10 @@ docs = [
"sphinx",
"sphinx-rtd-theme",
]
legacy = [
"pyopenssl",
]

[tool.poetry.scripts]
jws = "josepy.jws:CLI.run"

# pyopenssl is only needed for testing
[tool.poetry.group.dev.dependencies]
pyopenssl = ">=23.2.0"

# Black tooling configuration
[tool.black]
line-length = 100
Expand Down
2 changes: 2 additions & 0 deletions src/josepy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
TypedJSONObjectWithFields,
decode_b64jose,
decode_cert,
decode_cert_cryptography,
decode_csr,
decode_csr_cryptography,
decode_hex16,
encode_b64jose,
encode_cert,
Expand Down
72 changes: 61 additions & 11 deletions src/josepy/json_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Optional,
Type,
TypeVar,
Union,
)

from cryptography import x509
Expand Down Expand Up @@ -427,43 +428,79 @@ def decode_hex16(value: str, size: Optional[int] = None, minimum: bool = False)
raise errors.DeserializationError(error)


def encode_cert(cert: util.ComparableX509) -> str:
def encode_cert(cert: Union[util.ComparableX509, x509.Certificate]) -> str:
"""Encode certificate as JOSE Base-64 DER.
:type cert: `x509.Certificate` wrapped in `.ComparableX509`
:type cert: `cryptography.x509.Certificate`
:rtype: unicode
"""
if isinstance(cert._wrapped_new, x509.CertificateSigningRequest):
if isinstance(cert, util.ComparableX509):
# DEPRECATED; remove in next release
util.warn_deprecated(
"""`josepy.json_util.encode_cert` has deprecated support for"""
"""util.ComparableX509 objects. Please use """
"""`cryptography.x509.Certificate` objects instead."""
)
if isinstance(cert.wrapped_new, x509.CertificateSigningRequest):
raise ValueError("Error input is actually a certificate request.")
return encode_b64jose(cert.wrapped_new.public_bytes(Encoding.DER))
if isinstance(cert, x509.CertificateSigningRequest):
raise ValueError("Error input is actually a certificate request.")

return encode_b64jose(cert._wrapped_new.public_bytes(Encoding.DER))
return encode_b64jose(cert.public_bytes(Encoding.DER))


def decode_cert(b64der: str) -> util.ComparableX509:
"""Decode JOSE Base-64 DER-encoded certificate.
:param unicode b64der:
:rtype: `x509.Certificate` wrapped in `.ComparableX509`
:rtype: `cryptography.x509.Certificate` wrapped in `.ComparableX509`
"""
util.warn_deprecated(
"""`josepy.json_util.decode_cert` has been deprecated and will be removed"""
"""in a future release. please use `josepy.json_util.decode_cert_cryptography`"""
"""instead."""
)
try:
return util.ComparableX509(x509.load_der_x509_certificate(decode_b64jose(b64der)))
except Exception as error:
raise errors.DeserializationError(error)


def encode_csr(csr: util.ComparableX509) -> str:
def decode_cert_cryptography(b64der: str) -> x509.Certificate:
"""Decode JOSE Base-64 DER-encoded certificate.
:param unicode b64der:
:rtype: `cryptography.x509.Certificate`
"""
try:
return x509.load_der_x509_certificate(decode_b64jose(b64der))
except Exception as error:
raise errors.DeserializationError(error)


def encode_csr(csr: Union[util.ComparableX509, x509.CertificateSigningRequest]) -> str:
"""Encode CSR as JOSE Base-64 DER.
:type csr: `x509.CertificateSigningRequest` wrapped in `.ComparableX509`
:type csr: `cryptography.x509.CertificateSigningRequest`
:rtype: unicode
"""
if isinstance(csr._wrapped_new, x509.Certificate):
if isinstance(csr, util.ComparableX509):
# DEPRECATED; remove in next release
util.warn_deprecated(
"""`josepy.json_util.encode_csr` has deprecated support for"""
"""util.ComparableX509 objects. Please use """
"""`cryptography.x509.CertificateSigningRequest` objects instead."""
)
if isinstance(csr.wrapped_new, x509.Certificate):
raise ValueError("Error input is actually a certificate.")
return encode_b64jose(csr.wrapped_new.public_bytes(Encoding.DER))
if isinstance(csr, x509.Certificate):
raise ValueError("Error input is actually a certificate.")

return encode_b64jose(csr._wrapped_new.public_bytes(Encoding.DER))
return encode_b64jose(csr.public_bytes(Encoding.DER))


def decode_csr(b64der: str) -> util.ComparableX509:
Expand All @@ -479,6 +516,19 @@ def decode_csr(b64der: str) -> util.ComparableX509:
raise errors.DeserializationError(error)


def decode_csr_cryptography(b64der: str) -> x509.CertificateSigningRequest:
"""Decode JOSE Base-64 DER-encoded CSR.
:param unicode b64der:
:rtype: `cryptography.x509.CertificateSigningRequest`
"""
try:
return x509.load_der_x509_csr(decode_b64jose(b64der))
except Exception as error:
raise errors.DeserializationError(error)


GenericTypedJSONObjectWithFields = TypeVar(
"GenericTypedJSONObjectWithFields", bound="TypedJSONObjectWithFields"
)
Expand Down
21 changes: 15 additions & 6 deletions src/josepy/jws.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Optional,
Tuple,
Type,
Union,
cast,
)

Expand Down Expand Up @@ -81,7 +82,10 @@ class Header(json_util.JSONObjectWithFields):
)
kid: Optional[str] = json_util.field("kid", omitempty=True)
x5u: Optional[bytes] = json_util.field("x5u", omitempty=True)
x5c: Tuple[util.ComparableX509, ...] = json_util.field("x5c", omitempty=True, default=())
x5c: Union[
Tuple[util.ComparableX509, ...],
Tuple[x509.Certificate, ...],
] = json_util.field("x5c", omitempty=True, default=())
x5t: Optional[bytes] = json_util.field("x5t", decoder=json_util.decode_b64jose, omitempty=True)
x5tS256: Optional[bytes] = json_util.field(
"x5t#S256", decoder=json_util.decode_b64jose, omitempty=True
Expand Down Expand Up @@ -139,15 +143,20 @@ def crit(unused_value: Any) -> Any:

@x5c.encoder # type: ignore
def x5c(value):
return [base64.b64encode(cert._wrapped_new.public_bytes(Encoding.DER)) for cert in value]
# DEPRECATE `cert.wrapped_new`
return [
(
base64.b64encode(cert.wrapped_new.public_bytes(Encoding.DER))
if isinstance(cert, util.ComparableX509)
else base64.b64encode(cert.public_bytes(Encoding.DER))
)
for cert in value
]

@x5c.decoder # type: ignore
def x5c(value):
try:
return tuple(
util.ComparableX509(x509.load_der_x509_certificate(base64.b64decode(cert)))
for cert in value
)
return tuple(x509.load_der_x509_certificate(base64.b64decode(cert)) for cert in value)
except Exception as error:
raise errors.DeserializationError(error)

Expand Down
Loading

0 comments on commit db02561

Please sign in to comment.