adiroiban: @hynek when you have time, please take a look.

I did my best to get all the tests green.

I have not idea why `lint` pre-commit hook failed on the GitHub CI.

On my local system it fails, but is due to a strange Ubuntu python installation.
```
      File "/home/adi/.cache/pre-commit/repovai_r5c8/py_env-python3.8/lib/python3.8/site-packages/pip/_internal/exceptions.py", line 10, in <module>
        from pip._vendor.six import iteritems
    ModuleNotFoundError: No module named 'pip._vendor.six'
    
Check the log at /home/adi/.cache/pre-commit/pre-commit.log
ERROR: InvocationError for command /home/adi/chevah/pem/.tox/lint/bin/pre-commit run --all-files (exited with code 1)
```
hynek: You should be able to click into the checks and then open the tox run?

In this case both black and isort are failing.
adiroiban: Thanks.. I see now black and isort known_third_party ...not sure why they are failing.

I have executed `black` and `isort` on this branch before the commit and no file were modified.


adiroiban: @hynek can you please help with the command that I need to run before the commit so that black and isort is happy with my change?

I have no idea why pre-commit is not working ... it looks it creates an invalid virtual environment.. I have no idea from where it creates that environment... as that import is available in my dev environment

```
$ pre-commit
[INFO] Initializing environment for https://github.com/psf/black.
[INFO] Initializing environment for https://github.com/asottile/seed-isort-config.
[INFO] Initializing environment for https://github.com/pre-commit/mirrors-isort.
[INFO] Initializing environment for https://github.com/pre-commit/mirrors-isort:toml.
[INFO] Initializing environment for https://gitlab.com/pycqa/flake8.
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Installing environment for https://github.com/psf/black.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
An unexpected error has occurred: CalledProcessError: command: ('/home/adi/.cache/pre-commit/repokn6d0ckn/py_env-python3.8/bin/python', '/home/adi/.cache/pre-commit/repokn6d0ckn/py_env-python3.8/bin/pip', 'install', '.')
return code: 1
expected return code: 0
stdout: (none)
stderr:
    Traceback (most recent call last):
      File "/home/adi/.cache/pre-commit/repokn6d0ckn/py_env-python3.8/bin/pip", line 5, in <module>
        from pip._internal.cli.main import main
      File "/home/adi/.cache/pre-commit/repokn6d0ckn/py_env-python3.8/lib/python3.8/site-packages/pip/_internal/cli/main.py", line 10, in <module>
        from pip._internal.cli.autocompletion import autocomplete
      File "/home/adi/.cache/pre-commit/repokn6d0ckn/py_env-python3.8/lib/python3.8/site-packages/pip/_internal/cli/autocompletion.py", line 9, in <module>
        from pip._internal.cli.main_parser import create_main_parser
      File "/home/adi/.cache/pre-commit/repokn6d0ckn/py_env-python3.8/lib/python3.8/site-packages/pip/_internal/cli/main_parser.py", line 7, in <module>
        from pip._internal.cli import cmdoptions
      File "/home/adi/.cache/pre-commit/repokn6d0ckn/py_env-python3.8/lib/python3.8/site-packages/pip/_internal/cli/cmdoptions.py", line 24, in <module>
        from pip._internal.exceptions import CommandError
      File "/home/adi/.cache/pre-commit/repokn6d0ckn/py_env-python3.8/lib/python3.8/site-packages/pip/_internal/exceptions.py", line 10, in <module>
        from pip._vendor.six import iteritems
    ModuleNotFoundError: No module named 'pip._vendor.six'
    
Check the log at /home/adi/.cache/pre-commit/pre-commit.log

$ python
Python 3.8.2 (default, Mar 13 2020, 10:14:16) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pip._vendor.six import iteritems
>>> 
```
adiroiban: I managed to fix pre-commit execution... it was bug https://github.com/pre-commit/pre-commit/issues/1383 and upstream https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=956144

Working with pre-commit after the commit is ugly... I have no idea why isort was auto-updating the `pyproject.toml` file.

Pypy3 is still failing...but this looks like a pypy compile bug, and not something related to this repo.

I think that this is ready for review.
adiroiban: @hynek for pypy3, instead of building from source, maybe is easier to use https://github.com/pyenv/pyenv and get a precompiled version. This is used on Twisted for Travice-CI runs.

Let me know if you need help with that.
hynek: What do you mean by "building from source"? We are using GitHub Action's official pypy3 and we don't use Travis CI at all.

---

The error seems to come from a failed compilation of typed-ast which is required by a new coverage version and doesn't have a pypy3 wheel.

Given how simple `pem` is, I will just remove pypy from CI completely, please rebase from/merge master.

---

Also please revert the changes to contributing. :) We use pre-commit for a very good reason, I'm glad you could figure it out.
adiroiban: Hi @hynek 

Sorry for the late comeback :)

I have rebased the code and it's now ready for review.

Trying to make the builds green.

It would be nice to have a separate job for lint / mypy / docs.

I see that docs are build on Pythton 3.7 and lint and mypy on 3.8.

But now I know that I can run `tox -e lint` :)
hynek: > It would be nice to have a separate job for lint / mypy / docs.

Yeah, that the only downside of GitHub Actions, but you can just click them open to get the data. I hope this gets more ergonomic over time.
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index dc0e516..d6e191c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -29,6 +29,14 @@ Changes:
 - Added support for DSA private keys (``BEGIN DSA PRIVATE``).
   This is also the OpenSSH legacy PEM format.
   `#49 <https://github.com/hynek/pem/issues/49>`_
+- Added support for ``pem.SSHPublicKey``
+  (``---- BEGIN SSH2 PUBLIC KEY ----``).
+  as defined in `RFC4716 <https://tools.ietf.org/html/rfc4716>`_.
+  `#46 <https://github.com/hynek/pem/pull/46>`_
+- Added support for ``pem.SSHCOMPrivateKey``
+  (``---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----``).
+  which is the SSH.com / Tectia private key format (plain or encrypted).
+  `#46 <https://github.com/hynek/pem/pull/46>`_
 
 
 ----
diff --git a/docs/api.rst b/docs/api.rst
index a5b5271..15bd5b0 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -35,6 +35,8 @@ The following objects can be returned by the parsing functions.
 .. autoclass:: ECPrivateKey(PrivateKey)
 .. autoclass:: DSAPrivateKey(PrivateKey)
 .. autoclass:: OpenSSHPrivateKey(PrivateKey)
+.. autoclass:: SSHPublicKey(Key)
+.. autoclass:: SSHCOMPrivateKey(PrivateKey)
 .. autoclass:: DHParameters(AbstractPEMObject)
 .. autoclass:: CertificateRequest(AbstractPEMObject)
 .. autoclass:: CertificateRevocationList(AbstractPEMObject)
diff --git a/setup.py b/setup.py
index 7ac45ec..978f42a 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,6 @@ from setuptools import find_packages, setup
 
 
 ###############################################################################
-
 NAME = "pem"
 KEYWORDS = ["pyopenssl", "ssl", "tls", "pem", "cryptography", "twisted"]
 PROJECT_URLS = {
diff --git a/src/pem/__init__.py b/src/pem/__init__.py
index 4e91e5e..9e98a7e 100644
--- a/src/pem/__init__.py
+++ b/src/pem/__init__.py
@@ -14,6 +14,8 @@ from ._core import (
     PublicKey,
     RSAPrivateKey,
     RSAPublicKey,
+    SSHCOMPrivateKey,
+    SSHPublicKey,
     parse,
     parse_file,
 )
@@ -50,5 +52,7 @@ __all__ = [
     "RSAPublicKey",
     "parse",
     "parse_file",
+    "SSHCOMPrivateKey",
+    "SSHPublicKey",
     "twisted",
 ]
diff --git a/src/pem/_core.py b/src/pem/_core.py
index 7238c0d..ce57807 100644
--- a/src/pem/_core.py
+++ b/src/pem/_core.py
@@ -194,6 +194,25 @@ class OpenSSHPrivateKey(PrivateKey):
     """
 
 
+class SSHPublicKey(PublicKey):
+    """
+    A public key in SSH
+    `RFC 4716 <https://tools.ietf.org/html/rfc4716>`_ format.
+
+    The Secure Shell (SSH) Public Key File Format.
+
+    .. versionadded:: 21.1.0
+    """
+
+
+class SSHCOMPrivateKey(PrivateKey):
+    """
+    A private key in SSH.COM / Tectia format.
+
+    .. versionadded:: 21.1.0
+    """
+
+
 _PEM_TO_CLASS = {
     b"CERTIFICATE": Certificate,
     b"PRIVATE KEY": PrivateKey,
@@ -207,15 +226,19 @@ _PEM_TO_CLASS = {
     b"DH PARAMETERS": DHParameters,
     b"NEW CERTIFICATE REQUEST": CertificateRequest,
     b"CERTIFICATE REQUEST": CertificateRequest,
+    b"SSH2 PUBLIC KEY": SSHPublicKey,
+    b"SSH2 ENCRYPTED PRIVATE KEY": SSHCOMPrivateKey,
     b"X509 CRL": CertificateRevocationList,
 }  # type: Dict[bytes, Type[AbstractPEMObject]]
 
+# See https://tools.ietf.org/html/rfc1421
+# and https://tools.ietf.org/html/rfc4716 for space instead of fifth dash.
 _PEM_RE = re.compile(
-    b"-----BEGIN ("
+    b"----[- ]BEGIN ("
     + b"|".join(_PEM_TO_CLASS.keys())
-    + b""")-----\r?
+    + b""")[- ]----\r?
 .+?\r?
------END \\1-----\r?\n?""",
+----[- ]END \\1[- ]----\r?\n?""",
     re.DOTALL,
 )
 
@@ -223,7 +246,7 @@ _PEM_RE = re.compile(
 def parse(pem_str):
     # type: (bytes) -> List[AbstractPEMObject]
     """
-    Extract PEM objects from *pem_str*.
+    Extract PEM-like objects from *pem_str*.
 
     :param pem_str: String to parse.
     :type pem_str: bytes

Add support for RFC 4716 public and private keys
https://tools.ietf.org/html/rfc4716

Not sure if this is the right place to have this kind of support.... but since I saw support for OpenSSH.

Feel free to close this.

This is the SSH.com (Tectia) marker

```
—- BEGIN SSH2 PUBLIC KEY —-

Comment: "rsa-key-20160402"
AAAAB3NzaC1yc2EAAAABJQAAAgEAiL0jjDdFqK/kYThqKt7THrjABTPWvXmB3URI
pGKCP/jZlSuCUP3Oc+IxuFeXSIMvVIYeW2PZAjXQGTn60XzPHr+M0NoGcPAvzZf2
u57aX3YKaL93cZSBHR97H+XhcYdrm7ATwfjMDgfgj7+VTvW4nI46Z+qjxmYifc8u
VELolg1TDHWY789ggcdvy92oGjB0VUgMEywrOP+LS0DgG4dmkoUBWGP9dvYcPZDU
F4q0XY9ZHhvyPWEZ3o2vETTrEJr9QHYwgjmFfJn2VFNnD/4qeDDHOmSlDgEOfQcZ
Im+XUOn9eVsv//dAPSY/yMJXf8d0ZSm+VS29QShMjA4R+7yh5WhsIhouBRno2PpE
VVb37Xwe3V6U3o9UnQ3ADtL75DbrZ5beNWcmKzlJ7jVX5QzHSBAnePbBx/fyeP/f
144xPtJWB3jW/kXjtPyWjpzGndaPQ0WgXkbf8fvIuB3NJTTcZ7PeIKnLaMIzT5XN
CR+xobvdC8J9d6k84/q/laJKF3G8KbRGPNwnoVg1cwWFez+dzqo2ypcTtv/20yAm
z86EvuohZoWrtoWvkZLCoyxdqO93ymEjgHAn2bsIWyOODtXovxAJqPgk3dxM1f9P
AEQwc1bG+Z/Gc1Fd8DncgxyhKSQzLsfWroTnIn8wsnmhPJtaZWNuT5BJa8GhnzX0
9g6nhbk=
---- END SSH2 PUBLIC KEY ----

```

The marker for private keys is `---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ---`


diff --git a/tests/data.py b/tests/data.py
index bdc1bb5..92498b7 100644
--- a/tests/data.py
+++ b/tests/data.py
@@ -264,3 +264,30 @@ Qrb0riZLOXc966m9uXkBJE+Eimh+Jed/qfbwNuTZbxVz9rmsnbGHj8kvJT4c3J27
 NRrjPxY+c3X65vSaThscOQ0SHm5bRhX2YNRhgnZPznUnMXfE8yRLdgIUUS6kFIid
 HhSy7IHLTHWGoNdmwLo=
 -----END DSA PRIVATE KEY-----"""
+
+# Taken from https://tools.ietf.org/html/rfc4716#section-3.6.
+KEY_PEM_RFC4716_PUBLIC = br"""---- BEGIN SSH2 PUBLIC KEY ----
+Subject: me
+Comment: 1024-bit rsa, created by me@example.com Mon Jan 15 \
+08:31:24 2001
+AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
+596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
+soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=
+---- END SSH2 PUBLIC KEY ----
+"""
+
+# SSH.COM / Tectia private (encrypted or plain) key.
+# The non-encrypted key has the same armor.
+# puttygen -t rsa -O private-sshcom
+KEY_PEM_SSHCOM_PRIVATE = b"""\
+---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----
+Comment: "rsa-key-20210120"
+P2/56wAAAUwAAAA3aWYtbW9kbntzaWdue3JzYS1wa2NzMS1zaGExfSxlbmNyeXB0e3JzYS
+1wa2NzMXYyLW9hZXB9fQAAAARub25lAAAA/QAAAPkAAAAGJQAAAf0VIAK0MvdpUXEG6obL
+3F5n0UimJWvwhJIb5AGyd++EdYvimCOg9iM2E75dDj89Ap7S5l4IS40fZO/5UjzYQxitAA
+ACAMNoGQLXcI4xVX/5Xt22aUBP4ADaJnDKR4H9D7LVZ4lBDUP8RBTmowCv9p3Hz7KvVw3R
+TX8BNF72gEuSOvruUAUAAAD9Hs7Zn1KbFR29ujFEv+d50/7rjMU7Ox4tzDeTSE6PBhsAAA
+EA3m/0JWkf61807iZ7AV8umYJMmNQ35HadG53n9nitpFEAAAEA4OQI1Rrh8e1EZ5qJBV8o
+gGyxzt4OdoXzuOtxkbHUB3U=
+---- END SSH2 ENCRYPTED PRIVATE KEY ----
+"""
diff --git a/tests/test_core.py b/tests/test_core.py
index 6dfa848..e76aef1 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -29,7 +29,9 @@ from .data import (
     KEY_PEM_PKCS8_ENCRYPTED,
     KEY_PEM_PKCS8_UNENCRYPTED,
     KEY_PEM_PUBLIC,
+    KEY_PEM_RFC4716_PUBLIC,
     KEY_PEM_RSA_PUBLIC,
+    KEY_PEM_SSHCOM_PRIVATE,
 )
 
 
@@ -525,6 +527,7 @@ class TestParse(object):
 
         assert isinstance(key, pem.PublicKey)
         assert isinstance(key, pem.RSAPublicKey)
+        assert KEY_PEM_RSA_PUBLIC == key.as_bytes()
 
     def test_generic_public_key(self):
         """
@@ -533,6 +536,7 @@ class TestParse(object):
         key = pem.parse(KEY_PEM_PUBLIC)[0]
 
         assert isinstance(key, pem.PublicKey)
+        assert KEY_PEM_PUBLIC == key.as_bytes()
 
     def test_ec_private_key(self):
         """
@@ -541,6 +545,7 @@ class TestParse(object):
         key = pem.parse(KEY_PEM_EC_PRIVATE)[0]
 
         assert isinstance(key, pem.ECPrivateKey)
+        assert KEY_PEM_EC_PRIVATE == key.as_bytes()
 
     def test_openshh_private_key(self):
         """
@@ -549,6 +554,7 @@ class TestParse(object):
         (key,) = pem.parse(KEY_PEM_OPENSSH)
 
         assert isinstance(key, pem.OpenSSHPrivateKey)
+        assert KEY_PEM_OPENSSH == key.as_bytes()
 
     def test_dsa_private_key(self):
         """
@@ -558,3 +564,26 @@ class TestParse(object):
         (key,) = pem.parse(KEY_PEM_DSA_PRIVATE)
 
         assert isinstance(key, pem.DSAPrivateKey)
+        assert KEY_PEM_DSA_PRIVATE == key.as_bytes()
+
+    def test_rfc4716_public_key_(self):
+        """
+        Detects and loads public SSH keys in RFC4716 format.
+        """
+        (key,) = pem.parse(
+            b"PREAMBLE \n" + KEY_PEM_RFC4716_PUBLIC + b"\n TRAILING"
+        )
+
+        assert isinstance(key, pem.SSHPublicKey)
+        assert KEY_PEM_RFC4716_PUBLIC == key.as_bytes()
+
+    def test_sshcom_private(self):
+        """
+        Detects and loads public SSH keys in RFC4716 format.
+        """
+        (key,) = pem.parse(
+            b"PREAMBLE \n" + KEY_PEM_SSHCOM_PRIVATE + b"\n TRAILING"
+        )
+
+        assert isinstance(key, pem.SSHCOMPrivateKey)
+        assert KEY_PEM_SSHCOM_PRIVATE == key.as_bytes()

pip install -e .[dev]
parse_log_pytest
pytest
pytest
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
attrs==25.3.0
Automat==24.8.1
babel==2.17.0
certifi==2025.1.31
cffi==1.17.1
cfgv==3.4.0
charset-normalizer==3.4.1
constantly==23.10.4
coverage==7.8.0
cryptography==44.0.2
distlib==0.3.9
docutils==0.21.2
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
filelock==3.18.0
hyperlink==21.0.0
identify==2.6.9
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
incremental==24.7.2
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
Jinja2==3.1.6
MarkupSafe==3.0.2
nodeenv==1.9.1
packaging @ file:///croot/packaging_1734472117206/work
-e git+https://github.com/hynek/pem.git@129e3059d5339cd49e51c8a2464b931a6f2ed6a7#egg=pem
platformdirs==4.3.7
pluggy @ file:///croot/pluggy_1733169602837/work
pre_commit==4.2.0
pretend==1.0.9
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycparser==2.22
Pygments==2.19.1
pyOpenSSL==25.0.0
pytest @ file:///croot/pytest_1738938843180/work
PyYAML==6.0.2
requests==2.32.3
service-identity==24.2.0
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
Twisted==24.11.0
typing_extensions==4.13.0
urllib3==2.3.0
virtualenv==20.29.3
zipp==3.21.0
zope.interface==7.2

name: pem
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - attrs==25.3.0
      - automat==24.8.1
      - babel==2.17.0
      - certifi==2025.1.31
      - cffi==1.17.1
      - cfgv==3.4.0
      - charset-normalizer==3.4.1
      - constantly==23.10.4
      - coverage==7.8.0
      - cryptography==44.0.2
      - distlib==0.3.9
      - docutils==0.21.2
      - filelock==3.18.0
      - hyperlink==21.0.0
      - identify==2.6.9
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - incremental==24.7.2
      - jinja2==3.1.6
      - markupsafe==3.0.2
      - nodeenv==1.9.1
      - pem==21.1.0.dev0
      - platformdirs==4.3.7
      - pre-commit==4.2.0
      - pretend==1.0.9
      - pyasn1==0.6.1
      - pyasn1-modules==0.4.2
      - pycparser==2.22
      - pygments==2.19.1
      - pyopenssl==25.0.0
      - pyyaml==6.0.2
      - requests==2.32.3
      - service-identity==24.2.0
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - twisted==24.11.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
      - zipp==3.21.0
      - zope-interface==7.2
prefix: /opt/conda/envs/pem

tests/test_core.py::TestParse::test_rfc4716_public_key_ tests/test_core.py::TestParse::test_sshcom_private
tests/test_core.py::TestPEMObjects::test_cert_has_correct_repr tests/test_core.py::TestPEMObjects::test_cert_has_correct_str tests/test_core.py::TestPEMObjects::test_cert_req_has_correct_repr tests/test_core.py::TestPEMObjects::test_sha1_hexdigest[test] tests/test_core.py::TestPEMObjects::test_sha1_hexdigest[test\r] tests/test_core.py::TestPEMObjects::test_as_text tests/test_core.py::TestPEMObjects::test_cert_req_has_correct_str tests/test_core.py::TestPEMObjects::test_key_has_correct_repr tests/test_core.py::TestPEMObjects::test_key_has_correct_str tests/test_core.py::TestPEMObjects::test_rsa_private_key_has_correct_repr tests/test_core.py::TestPEMObjects::test_rsa_public_key_has_correct_repr tests/test_core.py::TestPEMObjects::test_rsa_key_has_correct_str tests/test_core.py::TestPEMObjects::test_dh_params_has_correct_repr tests/test_core.py::TestPEMObjects::test_dh_params_has_correct_str tests/test_core.py::TestPEMObjects::test_crl_has_correct_repr tests/test_core.py::TestPEMObjects::test_crl_has_correct_str tests/test_core.py::TestPEMObjects::test_certificate_unicode tests/test_core.py::TestPEMObjects::test_certificate_request_unicode tests/test_core.py::TestPEMObjects::test_key_unicode tests/test_core.py::TestPEMObjects::test_rsa_key_unicode tests/test_core.py::TestPEMObjects::test_dhparams_unicode_deprecated tests/test_core.py::TestPEMObjects::test_crl_unicode tests/test_core.py::TestPEMObjects::test_certs_equal tests/test_core.py::TestPEMObjects::test_cert_reqs_equal tests/test_core.py::TestPEMObjects::test_keys_equal tests/test_core.py::TestPEMObjects::test_rsa_keys_equal tests/test_core.py::TestPEMObjects::test_dh_params_equal tests/test_core.py::TestPEMObjects::test_crl_equal tests/test_core.py::TestPEMObjects::test_cert_contents_unequal tests/test_core.py::TestPEMObjects::test_cert_req_contents_unequal tests/test_core.py::TestPEMObjects::test_crl_unequal tests/test_core.py::TestPEMObjects::test_different_objects_unequal tests/test_core.py::TestPEMObjects::test_incompatible_types tests/test_core.py::TestParse::test_key_pkcs5_unencrypted tests/test_core.py::TestParse::test_key_pkcs5_encrypted tests/test_core.py::TestParse::test_key_pkcs8_unencrypted tests/test_core.py::TestParse::test_key_pkcs8_encrypted tests/test_core.py::TestParse::test_certificates tests/test_core.py::TestParse::test_certificate_no_new_line tests/test_core.py::TestParse::test_certificates_no_new_line tests/test_core.py::TestParse::test_dh tests/test_core.py::TestParse::test_crl tests/test_core.py::TestParse::test_file tests/test_core.py::TestParse::test_loads_certifi tests/test_core.py::TestParse::test_allows_lf tests/test_core.py::TestParse::test_rsa_public_key tests/test_core.py::TestParse::test_generic_public_key tests/test_core.py::TestParse::test_ec_private_key tests/test_core.py::TestParse::test_openshh_private_key tests/test_core.py::TestParse::test_dsa_private_key
src/pem/_core.py setup.py CHANGELOG.rst docs/api.rst src/pem/__init__.py
src/pem/_core.py setup.py CHANGELOG.rst docs/api.rst src/pem/__init__.py