mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-06 13:22:58 +00:00
Make all module_utils and plugin_utils private (#887)
* Add leading underscore. Remove deprecated module utils. * Document module and plugin utils as private. Add changelog fragment. * Convert relative to absolute imports. * Remove unnecessary imports.
This commit is contained in:
127
plugins/module_utils/_crypto/pem.py
Normal file
127
plugins/module_utils/_crypto/pem.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# Copyright (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time.
|
||||
# Do not use this from other collections or standalone plugins/modules!
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import typing as t
|
||||
|
||||
|
||||
PEM_START = "-----BEGIN "
|
||||
PEM_END_START = "-----END "
|
||||
PEM_END = "-----"
|
||||
PKCS8_PRIVATEKEY_NAMES = ("PRIVATE KEY", "ENCRYPTED PRIVATE KEY")
|
||||
PKCS1_PRIVATEKEY_SUFFIX = " PRIVATE KEY"
|
||||
|
||||
|
||||
def identify_pem_format(content: bytes, encoding: str = "utf-8") -> bool:
|
||||
"""Given the contents of a binary file, tests whether this could be a PEM file."""
|
||||
try:
|
||||
first_pem = extract_first_pem(content.decode(encoding))
|
||||
if first_pem is None:
|
||||
return False
|
||||
lines = first_pem.splitlines(False)
|
||||
if (
|
||||
lines[0].startswith(PEM_START)
|
||||
and lines[0].endswith(PEM_END)
|
||||
and len(lines[0]) > len(PEM_START) + len(PEM_END)
|
||||
):
|
||||
return True
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def identify_private_key_format(
|
||||
content: bytes, encoding: str = "utf-8"
|
||||
) -> t.Literal["raw", "pkcs1", "pkcs8", "unknown-pem"]:
|
||||
"""Given the contents of a private key file, identifies its format."""
|
||||
# See https://github.com/openssl/openssl/blob/master/crypto/pem/pem_pkey.c#L40-L85
|
||||
# (PEM_read_bio_PrivateKey)
|
||||
# and https://github.com/openssl/openssl/blob/master/include/openssl/pem.h#L46-L47
|
||||
# (PEM_STRING_PKCS8, PEM_STRING_PKCS8INF)
|
||||
try:
|
||||
first_pem = extract_first_pem(content.decode(encoding))
|
||||
if first_pem is None:
|
||||
return "raw"
|
||||
lines = first_pem.splitlines(False)
|
||||
if (
|
||||
lines[0].startswith(PEM_START)
|
||||
and lines[0].endswith(PEM_END)
|
||||
and len(lines[0]) > len(PEM_START) + len(PEM_END)
|
||||
):
|
||||
name = lines[0][len(PEM_START) : -len(PEM_END)]
|
||||
if name in PKCS8_PRIVATEKEY_NAMES:
|
||||
return "pkcs8"
|
||||
if len(name) > len(PKCS1_PRIVATEKEY_SUFFIX) and name.endswith(
|
||||
PKCS1_PRIVATEKEY_SUFFIX
|
||||
):
|
||||
return "pkcs1"
|
||||
return "unknown-pem"
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
return "raw"
|
||||
|
||||
|
||||
def split_pem_list(text: str, keep_inbetween: bool = False) -> list[str]:
|
||||
"""
|
||||
Split concatenated PEM objects into a list of strings, where each is one PEM object.
|
||||
"""
|
||||
result = []
|
||||
current: list[str] | None = [] if keep_inbetween else None
|
||||
for line in text.splitlines(True):
|
||||
if line.strip():
|
||||
if not keep_inbetween and line.startswith("-----BEGIN "):
|
||||
current = []
|
||||
if current is not None:
|
||||
current.append(line)
|
||||
if line.startswith("-----END "):
|
||||
result.append("".join(current))
|
||||
current = [] if keep_inbetween else None
|
||||
return result
|
||||
|
||||
|
||||
def extract_first_pem(text: str) -> str | None:
|
||||
"""
|
||||
Given one PEM or multiple concatenated PEM objects, return only the first one, or None if there is none.
|
||||
"""
|
||||
all_pems = split_pem_list(text)
|
||||
if not all_pems:
|
||||
return None
|
||||
return all_pems[0]
|
||||
|
||||
|
||||
def _extract_type(line: str, start: str = PEM_START) -> str | None:
|
||||
if not line.startswith(start):
|
||||
return None
|
||||
if not line.endswith(PEM_END):
|
||||
return None
|
||||
return line[len(start) : -len(PEM_END)]
|
||||
|
||||
|
||||
def extract_pem(content: str, strict: bool = False) -> tuple[str, str]:
|
||||
lines = content.splitlines()
|
||||
if len(lines) < 3:
|
||||
raise ValueError(f"PEM must have at least 3 lines, have only {len(lines)}")
|
||||
header_type = _extract_type(lines[0])
|
||||
if header_type is None:
|
||||
raise ValueError(
|
||||
f"First line is not of format {PEM_START}...{PEM_END}: {lines[0]!r}"
|
||||
)
|
||||
footer_type = _extract_type(lines[-1], start=PEM_END_START)
|
||||
if strict:
|
||||
if header_type != footer_type:
|
||||
raise ValueError(
|
||||
f"Header type ({header_type}) is different from footer type ({footer_type})"
|
||||
)
|
||||
for idx, line in enumerate(lines[1:-2]):
|
||||
if len(line) != 64:
|
||||
raise ValueError(f"Line {idx} has length {len(line)} instead of 64")
|
||||
if not (0 < len(lines[-2]) <= 64):
|
||||
raise ValueError(
|
||||
f"Last line has length {len(lines[-2])}, should be in (0, 64]"
|
||||
)
|
||||
return header_type, "".join(lines[1:-1])
|
||||
Reference in New Issue
Block a user