diff --git a/.ci/aptPackagesToInstall.txt b/.ci/aptPackagesToInstall.txt
new file mode 100644
index 0000000..e69de29
diff --git a/.ci/pythonPackagesToInstallFromGit.txt b/.ci/pythonPackagesToInstallFromGit.txt
new file mode 100644
index 0000000..e69de29
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c9162b9
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = tab
+indent_size = 4
+insert_final_newline = true
+end_of_line = lf
+
+[*.{yml,yaml}]
+indent_style = space
+indent_size = 2
diff --git a/.github/.templateMarker b/.github/.templateMarker
new file mode 100644
index 0000000..5e3a3e0
--- /dev/null
+++ b/.github/.templateMarker
@@ -0,0 +1 @@
+KOLANICH/python_project_boilerplate.py
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..89ff339
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ allow:
+ - dependency-type: "all"
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
new file mode 100644
index 0000000..7fe33b3
--- /dev/null
+++ b/.github/workflows/CI.yml
@@ -0,0 +1,15 @@
+name: CI
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ build:
+ runs-on: ubuntu-22.04
+ steps:
+ - name: typical python workflow
+ uses: KOLANICH-GHActions/typical-python-workflow@master
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..71fb1b6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+__pycache__
+*.py[co]
+/*.egg-info
+*.srctrlbm
+*.srctrldb
+build
+dist
+.eggs
+monkeytype.sqlite3
+/.ipynb_checkpoints
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..67a1858
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,51 @@
+image: registry.gitlab.com/kolanich-subgroups/docker-images/fixed_python:latest
+
+variables:
+ DOCKER_DRIVER: overlay2
+ SAST_ANALYZER_IMAGE_TAG: latest
+ SAST_DISABLE_DIND: "true"
+ SAST_CONFIDENCE_LEVEL: 5
+ CODECLIMATE_VERSION: latest
+
+include:
+ - template: SAST.gitlab-ci.yml
+ - template: Code-Quality.gitlab-ci.yml
+ - template: License-Management.gitlab-ci.yml
+
+build:
+ tags:
+ - shared
+ - linux
+ stage: build
+ variables:
+ GIT_DEPTH: "1"
+ PYTHONUSERBASE: ${CI_PROJECT_DIR}/python_user_packages
+
+ before_script:
+ - export PATH="$PATH:$PYTHONUSERBASE/bin" # don't move into `variables`
+ - apt-get update
+ # todo:
+ #- apt-get -y install
+ #- pip3 install --upgrade
+ #- python3 ./fix_python_modules_paths.py
+
+ script:
+ - python3 -m build -nw bdist_wheel
+ - mv ./dist/*.whl ./dist/securesystemslib_KOLANICH-0.CI-py3-none-any.whl
+ - pip3 install --upgrade ./dist/*.whl
+ - coverage run --source=securesystemslib_KOLANICH -m --branch pytest --junitxml=./rspec.xml ./tests/test.py
+ - coverage report -m
+ - coverage xml
+
+ coverage: "/^TOTAL(?:\\s+\\d+){4}\\s+(\\d+%).+/"
+
+ cache:
+ paths:
+ - $PYTHONUSERBASE
+
+ artifacts:
+ paths:
+ - dist
+ reports:
+ junit: ./rspec.xml
+ cobertura: ./coverage.xml
diff --git a/Code_Of_Conduct.md b/Code_Of_Conduct.md
new file mode 100644
index 0000000..bcaa2bf
--- /dev/null
+++ b/Code_Of_Conduct.md
@@ -0,0 +1 @@
+No codes of conduct!
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..5779168
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Santiago Torres
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..20f0fa8
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include UNLICENSE
+include *.md
+include tests
+include .editorconfig
diff --git a/ReadMe.md b/ReadMe.md
new file mode 100644
index 0000000..f8419da
--- /dev/null
+++ b/ReadMe.md
@@ -0,0 +1,17 @@
+securesystemslib_KOLANICH.py
+============================
+~~[wheel (GitLab)](https://gitlab.com/KOLANICH-libs/securesystemslib_KOLANICH.py/-/jobs/artifacts/master/raw/dist/securesystemslib_KOLANICH-0.CI-py3-none-any.whl?job=build)~~
+[wheel (GHA via `nightly.link`)](https://nightly.link/KOLANICH-libs/securesystemslib_KOLANICH.py/workflows/CI/master/securesystemslib_KOLANICH-0.CI-py3-none-any.whl)
+~~![GitLab Build Status](https://gitlab.com/KOLANICH-libs/securesystemslib_KOLANICH.py/badges/master/pipeline.svg)~~
+~~![GitLab Coverage](https://gitlab.com/KOLANICH-libs/securesystemslib_KOLANICH.py/badges/master/coverage.svg)~~
+[![GitHub Actions](https://github.com/KOLANICH-libs/securesystemslib_KOLANICH.py/workflows/CI/badge.svg)](https://github.com/KOLANICH-libs/securesystemslib_KOLANICH.py/actions/)
+[![Libraries.io Status](https://img.shields.io/librariesio/github/KOLANICH-libs/securesystemslib_KOLANICH.py.svg)](https://libraries.io/github/KOLANICH-libs/securesystemslib_KOLANICH.py)
+[![Code style: antiflash](https://img.shields.io/badge/code%20style-antiflash-FFF.svg)](https://github.com/KOLANICH-tools/antiflash.py)
+
+My additions to [`securesystemslib`](https://github.com/secure-systems-lab/securesystemslib).
+
+* https://github.com/secure-systems-lab/securesystemslib/pull/452 - implements a method to generate a `securesystemslib` `dict` for a `ed25519` key - used internally.
+* https://github.com/secure-systems-lab/securesystemslib/pull/451 - implements import of SSH keys. `from securesystemslib_KOLANICH.convert.ssh import import_ssh_key`
+* https://github.com/secure-systems-lab/securesystemslib/pull/453 - monkey-patches inconsistent keyids for ECDSA keys - just `import securesystemslib_KOLANICH`
+
+The most of this lib is licensed under `Unlicense`, but some files with portions copied from `securesystemslib` are licensed under `MIT`.
diff --git a/UNLICENSE b/UNLICENSE
new file mode 100644
index 0000000..efb9808
--- /dev/null
+++ b/UNLICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..8947d97
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,39 @@
+[build-system]
+requires = ["setuptools>=61.2.0", "setuptools_scm[toml]>=3.4.3"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "securesystemslib_KOLANICH"
+readme = "ReadMe.md"
+description = "A temporary workaround for non-merging my PRs into `securesystemslib`"
+authors = [{name = "KOLANICH"}]
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Environment :: Other Environment",
+ "Intended Audience :: Developers",
+ "License :: Public Domain",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+]
+keywords = ["securesystemslib_KOLANICH"]
+license = {text = "MIT AND Unlicense"}
+requires-python = ">=3.4"
+dynamic = ["version"]
+dependencies = [
+ "securesystemslib",
+ "cryptography",
+]
+
+[project.urls]
+Homepage = "https://github.com/KOLANICH-libs/securesystemslib_KOLANICH.py"
+
+[tool.setuptools]
+zip-safe = true
+
+[tool.setuptools.packages.find]
+include = ["securesystemslib_KOLANICH", "securesystemslib_KOLANICH.*"]
+
+[tool.setuptools_scm]
diff --git a/securesystemslib_KOLANICH/__init__.py b/securesystemslib_KOLANICH/__init__.py
new file mode 100644
index 0000000..9864a21
--- /dev/null
+++ b/securesystemslib_KOLANICH/__init__.py
@@ -0,0 +1 @@
+__license__ = "Unlicense"
diff --git a/securesystemslib_KOLANICH/convert/__init__.py b/securesystemslib_KOLANICH/convert/__init__.py
new file mode 100644
index 0000000..9864a21
--- /dev/null
+++ b/securesystemslib_KOLANICH/convert/__init__.py
@@ -0,0 +1 @@
+__license__ = "Unlicense"
diff --git a/securesystemslib_KOLANICH/convert/hazmat.py b/securesystemslib_KOLANICH/convert/hazmat.py
new file mode 100644
index 0000000..6695e4e
--- /dev/null
+++ b/securesystemslib_KOLANICH/convert/hazmat.py
@@ -0,0 +1,133 @@
+"""
+This module contains functions to convert `cryptography.hazmat` keys into `securesystemlib` key dicts.
+"""
+
+
+import typing
+
+from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey, _EllipticCurvePublicKey
+from cryptography.hazmat.backends.openssl.ed25519 import _Ed25519PrivateKey, _Ed25519PublicKey
+from cryptography.hazmat.backends.openssl.rsa import _RSAPrivateKey, _RSAPublicKey
+from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat
+from securesystemslib.keys import import_ecdsakey_from_pem, import_rsakey_from_pem
+
+from ..keys import format_ed25519_dict
+
+
+__license__ = "Unlicense"
+
+
+def _hazmat_key_to_pem(
+ key: typing.Union[
+ _RSAPrivateKey,
+ _EllipticCurvePrivateKey,
+ ]
+) -> str:
+ """The approach already used in this lib is to convert keys into PEM and then to parse from it."""
+
+ if key.__class__.__name__.endswith("PrivateKey"):
+ serialized = key.private_bytes(
+ Encoding.PEM,
+ PrivateFormat.TraditionalOpenSSL,
+ NoEncryption(),
+ )
+ elif key.__class__.__name__.endswith("PublicKey"):
+ serialized = key.public_bytes(
+ Encoding.PEM,
+ PublicFormat.SubjectPublicKeyInfo,
+ )
+ else:
+ raise TypeError(key)
+
+ return serialized.decode("utf-8")
+
+
+def _import_hazmat_ed25519_private_key(
+ key: _Ed25519PrivateKey,
+) -> dict:
+ """Imports hazmat ed25519 private key"""
+
+ pub = key.public_key().public_bytes(
+ Encoding.Raw,
+ PublicFormat.Raw,
+ )
+ sec = key.private_bytes(
+ Encoding.Raw,
+ PrivateFormat.Raw,
+ NoEncryption(),
+ )
+ return format_ed25519_dict(
+ pub,
+ sec,
+ )
+
+
+def _import_hazmat_ed25519_public_key(
+ key: _Ed25519PublicKey,
+) -> dict:
+ """Imports hazmat ed25519 public key"""
+
+ pub = key.public_bytes(
+ Encoding.Raw,
+ PublicFormat.Raw,
+ )
+ return format_ed25519_dict(
+ pub,
+ None,
+ )
+
+
+def _import_rsa_key(
+ key: _RSAPrivateKey,
+) -> dict:
+ """Imports hazmat RSA key"""
+
+ return import_rsakey_from_pem(_hazmat_key_to_pem(key))
+
+
+def _import_ecdsa_key(
+ key: _EllipticCurvePrivateKey,
+) -> dict:
+ """Imports hazmat ECDSA key"""
+
+ return import_ecdsakey_from_pem(_hazmat_key_to_pem(key))
+
+
+_typeMapping = {
+ _Ed25519PrivateKey: _import_hazmat_ed25519_private_key,
+ _Ed25519PublicKey: _import_hazmat_ed25519_public_key,
+ _RSAPrivateKey: _import_rsa_key,
+ _RSAPublicKey: _import_rsa_key,
+ _EllipticCurvePrivateKey: _import_ecdsa_key,
+ _EllipticCurvePublicKey: _import_ecdsa_key,
+}
+
+
+def import_hazmat_key(key: typing.Union[_RSAPrivateKey, _EllipticCurvePrivateKey, _Ed25519PrivateKey]) -> dict:
+ """
+
+ Converts a `cryptography.hazmat` key into a dictionary conformant to 'securesystemslib.formats.KEY_SCHEMA'.
+
+
+ key:
+ A key of the classes from `cryptography.hazmat` module. Currently only keys of `openssl` backend are implemented.
+
+
+ securesystemslib.exceptions.FormatError, if 'key_value' does not conform to
+ 'securesystemslib.formats.KEYVAL_SCHEMA', or if the private key is not
+ present in 'key_value' if requested by the caller via 'private'.
+ NotImplementedError, if we cannot convert a key of this type.
+
+
+ None.
+
+
+ A 'securesystemslib.formats.KEY_SCHEMA' dictionary."""
+
+ key_type = type(key)
+ try:
+ mapper = _typeMapping[key_type]
+ except KeyError as ex:
+ raise NotImplementedError(key_type) from ex
+ else:
+ return mapper(key)
diff --git a/securesystemslib_KOLANICH/convert/ssh.py b/securesystemslib_KOLANICH/convert/ssh.py
new file mode 100644
index 0000000..951e5ac
--- /dev/null
+++ b/securesystemslib_KOLANICH/convert/ssh.py
@@ -0,0 +1,47 @@
+"""
+This module contains functions to import OpenSSH keys.
+"""
+
+import re
+import typing
+
+from cryptography.hazmat.primitives.serialization.ssh import load_ssh_private_key, load_ssh_public_key
+
+from .hazmat import import_hazmat_key
+
+__license__ = "Unlicense"
+
+openssh_text_format_marker_re = re.compile(b"^-{2,}BEGIN OPENSSH PRIVATE KEY-{2,}$")
+
+
+def import_ssh_key(key: typing.Union[str, bytes], password: typing.Optional[bytes] = None):
+ """
+
+ Imports either a public or a private key in OpenSSH format
+
+
+ key:
+ A string in OpenSSH format, usually Base64-encoded.
+
+
+ securesystemslib.exceptions.FormatError, if the arguments are improperly
+ formatted.
+
+ securesystemslib.exceptions.UnsupportedAlgorithmError, if 'pem' specifies
+ an unsupported key type.
+
+
+ None.
+
+
+ A dictionary containing the keys, conforming to 'securesystemslib.formats.KEY_SCHEMA'.
+ """
+
+ if isinstance(key, str):
+ key = key.encode("utf-8")
+
+ first_line = key.split(b"\n", 1)[0]
+ if openssh_text_format_marker_re.match(first_line):
+ return import_hazmat_key(load_ssh_private_key(key, password))
+
+ return import_hazmat_key(load_ssh_public_key(key))
diff --git a/securesystemslib_KOLANICH/keys.py b/securesystemslib_KOLANICH/keys.py
new file mode 100644
index 0000000..4d1ac67
--- /dev/null
+++ b/securesystemslib_KOLANICH/keys.py
@@ -0,0 +1,64 @@
+import binascii
+
+from securesystemslib import formats, settings
+from securesystemslib.keys import _get_keyid
+
+
+__license__ = "MIT"
+
+
+def format_ed25519_dict(public: bytes, private: bytes, scheme="ed25519"):
+ """
+
+ Formats a ed25519 private key dict.
+
+
+ public:
+ Bytes of public key.
+
+ private:
+ Bytes of private key.
+
+ scheme:
+ The signature scheme used by the generated Ed25519 key.
+
+
+ None.
+
+
+ None.
+
+
+ A dictionary containing the ED25519 keys and other identifying information.
+ Conforms to 'securesystemslib.formats.ED25519KEY_SCHEMA'.
+ """
+
+ assert private is None or len(private) == 32 # nosec assert_used
+ assert len(public) == 32 # nosec assert_used
+
+ # Are the arguments properly formatted? If not, raise an
+ # 'securesystemslib.exceptions.FormatError' exceptions.
+ formats.ED25519_SIG_SCHEMA.check_match(scheme)
+
+ # Begin building the Ed25519 key dictionary.
+ ed25519_key = {}
+ keytype = "ed25519"
+
+ # Generate the keyid of the Ed25519 key. 'key_value' corresponds to the
+ # 'keyval' entry of the 'Ed25519KEY_SCHEMA' dictionary. The private key
+ # information is not included in the generation of the 'keyid' identifier.
+ key_value = {"public": binascii.hexlify(public).decode(), "private": ""}
+ keyid = _get_keyid(keytype, scheme, key_value)
+
+ if private is not None:
+ # Build the 'ed25519_key' dictionary. Update 'key_value' with the Ed25519
+ # private key prior to adding 'key_value' to 'ed25519_key'.
+ key_value["private"] = binascii.hexlify(private).decode()
+
+ ed25519_key["keytype"] = keytype
+ ed25519_key["scheme"] = scheme
+ ed25519_key["keyid"] = keyid
+ ed25519_key["keyid_hash_algorithms"] = settings.HASH_ALGORITHMS
+ ed25519_key["keyval"] = key_value
+
+ return ed25519_key
diff --git a/tests/data/ssh/ecdsa b/tests/data/ssh/ecdsa
new file mode 100644
index 0000000..5ca9f76
--- /dev/null
+++ b/tests/data/ssh/ecdsa
@@ -0,0 +1,9 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
+1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRIz8F5gDezWgzdlRn5Upj7B1NoK43R
+wP1d/oiCtNKSYDdql9ds3d+5zzEhnhWvdjGOn1sKwAFlbkz+aueExAkvAAAAqGkmJORpJi
+TkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEjPwXmAN7NaDN2V
+GflSmPsHU2grjdHA/V3+iIK00pJgN2qX12zd37nPMSGeFa92MY6fWwrAAWVuTP5q54TECS
+8AAAAhAMfqco7t7Cdz3rXsPcUwOINw4CKTqyTpOCFTzjULEB6AAAAACWVjZHNhIGtleQEC
+AwQFBg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/tests/data/ssh/ecdsa.pub b/tests/data/ssh/ecdsa.pub
new file mode 100644
index 0000000..c154e3e
--- /dev/null
+++ b/tests/data/ssh/ecdsa.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEjPwXmAN7NaDN2VGflSmPsHU2grjdHA/V3+iIK00pJgN2qX12zd37nPMSGeFa92MY6fWwrAAWVuTP5q54TECS8= ecdsa key
diff --git a/tests/data/ssh/ed25519 b/tests/data/ssh/ed25519
new file mode 100644
index 0000000..42367f7
--- /dev/null
+++ b/tests/data/ssh/ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAUMmZQY7F/6Pvhj+LQn6HLke1TI0L1qvVImeBCxKjFuAAAAJAh8wZwIfMG
+cAAAAAtzc2gtZWQyNTUxOQAAACAUMmZQY7F/6Pvhj+LQn6HLke1TI0L1qvVImeBCxKjFuA
+AAAED6uVRyKJZMvslMIFpm4nbqMcRlzza7VwJobiRcbLNwSBQyZlBjsX/o++GP4tCfocuR
+7VMjQvWq9UiZ4ELEqMW4AAAAC2VkMjU1MTkga2V5AQI=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/tests/data/ssh/ed25519.pub b/tests/data/ssh/ed25519.pub
new file mode 100644
index 0000000..b9a878d
--- /dev/null
+++ b/tests/data/ssh/ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBQyZlBjsX/o++GP4tCfocuR7VMjQvWq9UiZ4ELEqMW4 ed25519 key
diff --git a/tests/data/ssh/generate.sh b/tests/data/ssh/generate.sh
new file mode 100755
index 0000000..4988329
--- /dev/null
+++ b/tests/data/ssh/generate.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env sh
+
+types="ecdsa ed25519 rsa" # dsa
+
+for t in $types; do
+ yes | ssh-keygen -t $t -C "$t key" -N "" -f ./$t;
+done
diff --git a/tests/data/ssh/rsa b/tests/data/ssh/rsa
new file mode 100644
index 0000000..a523263
--- /dev/null
+++ b/tests/data/ssh/rsa
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+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
+fyozEzAWaCa90AAAAHcnNhIGtleQECAwQ=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/tests/data/ssh/rsa.pub b/tests/data/ssh/rsa.pub
new file mode 100644
index 0000000..b260788
--- /dev/null
+++ b/tests/data/ssh/rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzzjkzJGZjENWuFFpjhhK7y+YpMMST18lCK28dlG3QbqbpVJggC+3DcpD4GfFVHTEUpJWplatF4nlpHrzQTc/8RhJzVg+FY3hZXRdxX5J5zJ5vW1aTQdduj0AGMQ0RRaFQo3o5fGMQdrthDUW1/9ybGF0sv4DlTH0rtR8yCvXpl0LmaCIn0MireG8SqsaxdYDb8KdbOHL8Ezo2UmqZg0+svwkphEFoFzqQYVNAhgG4+/5Yotl2PBxZrSUWEUApqvWSS5BeyT/cTnf/8XDGV64H2Qmw9nZhRTIan6GgTgzPOhaX3yUxXS4ZI5rnWRQJuOk/J6FVKsJFNVA+/BAsxjcbUpms+PUtrXe+mHMLcb9FvQAiR9oteNMWUM3X1H8hVRZ20ETTzZVXM44mbSJ+ZvBEcpsGo9RmaMnvsc+4QI9ak/wEojPGN1msNXh/R8RWnhG+sCkbM9Btf2wf1RUyxtpkpYksbcDVyQEl+Rr6x2M+sJH9KZCzS9OFL8hZHKsCAks= rsa key
diff --git a/tests/tests.py b/tests/tests.py
new file mode 100644
index 0000000..e90b8e2
--- /dev/null
+++ b/tests/tests.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+"""
+
+test_openssh.py
+
+
+KOLANICH
+
+
+Oct 31, 2022
+
+
+See LICENSE for licensing information.
+
+
+Test OpenSSH-related functions.
+
+"""
+
+import itertools
+import re
+import sys
+import unittest
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).parent.parent))
+
+from collections import OrderedDict
+
+dict = OrderedDict
+
+import unittest
+from pathlib import Path
+
+from securesystemslib.keys import create_signature, verify_signature
+
+import securesystemslib_KOLANICH
+from securesystemslib_KOLANICH import *
+from securesystemslib_KOLANICH.convert.ssh import import_ssh_key
+
+this_dir = Path(__file__).resolve().absolute().parent
+keys_dir = this_dir / "data" / "ssh"
+
+
+__license__ = "Unlicense"
+
+# pylint: disable=wrong-import-position
+
+TEST_DATA = b"test"
+
+
+class TestOpenSSH(unittest.TestCase):
+ def test_openssh_import_and_sign_and_verify(self):
+ files = sorted(set(keys_dir.glob("*.pub")))
+ for pub_f in files:
+ sec_f = pub_f.parent / pub_f.stem
+ with self.subTest(pub_f=pub_f, sec_f=sec_f):
+ pub = import_ssh_key(pub_f.read_text(), None)
+ sec = import_ssh_key(sec_f.read_text(), None)
+ signature = create_signature(sec, TEST_DATA)
+ self.assertTrue(verify_signature(pub, signature, TEST_DATA))
+
+
+if __name__ == "__main__":
+ unittest.main()