A Python module for generating, parsing and handling OpenSSH keys and certificates
Project description
sshkey-tools
Python and CLI tools for managing OpenSSH keypairs and certificates
Installation
With pip
pip3 install sshkey-tools
# or
pip3 install -e git+https://github.com/scheiblingco/sshkey-tools.git
From source
git clone https://github.com/scheiblingco/sshkey-tools
cd sshkey-tools
pip3 install ./
Documentation
scheiblingco.github.io/sshkey-tools/
Basic usage
SSH Keypairs
Generate keys
from sshkey_tools.keys import (
RSAPrivateKey,
DSAPrivateKey,
ECDSAPrivateKey,
ED25519PrivateKey,
EcdsaCurves
)
# RSA
# By default, RSA is generated with a 4096-bit keysize
rsa_private = RSAPrivateKey.generate()
# You can also specify the key size
rsa_private = RSAPrivateKey.generate(bits)
# DSA
# Since OpenSSH only supports 1024-bit keys, this is the default
dsa_private = DSAPrivateKey.generate()
# ECDSA
# The default curve is P521
ecdsa_private = ECDSAPrivateKey.generate()
# You can also manually specify a curve
ecdsa_private = ECDSAPrivateKey.generate(EcdsaCurves.P256)
# ED25519
# The ED25519 keys are always a fixed size
ed25519_private = ED25519PrivateKey.generate()
# Public keys
# The public key for any given private key is in the public_key parameter
rsa_pub = rsa_private.public_key
Load keys
You can load keys either directly with the specific key classes (RSAPrivateKey, DSAPrivateKey, etc.) or the general PrivateKey class
from sshkey_tools.keys import (
PrivateKey,
PublicKey,
RSAPrivateKey,
RSAPublicKey
)
# Load a private key with a specific class
rsa_private = RSAPrivateKey.from_file('path/to/rsa_key')
# Load a private key with the general class
rsa_private = PrivateKey.from_file('path/to/rsa_key')
print(type(rsa_private))
"<class 'sshkey_tools.keys.RSAPrivateKey'>"
# Public keys can be loaded in the same way
rsa_pub = RSAPublicKey.from_file('path/to/rsa_key.pub')
rsa_pub = PublicKey.from_file('path/to/rsa_key.pub')
print(type(rsa_private))
"<class 'sshkey_tools.keys.RSAPrivateKey'>"
# Public key objects are automatically created for any given private key
# negating the need to load them separately
rsa_pub = rsa_private.public_key
# Load a key from a pyca/cryptography class privkey_pyca/pubkey_pyca
rsa_private = PrivateKey.from_class(privkey_pyca)
rsa_public = PublicKey.from_class(pubkey_pyca)
# You can also load private and public keys from strings or bytes (file contents)
with open('path/to/rsa_key', 'r', 'utf-8') as file:
rsa_private = PrivateKey.from_string(file.read())
with open('path/to/rsa_key', 'rb') as file:
rsa_private = PrivateKey.from_bytes(file.read())
# RSA, DSA and ECDSA keys can be loaded from the public/private numbers and/or parameters
rsa_public = RSAPublicKey.from_numbers(
e=65537,
n=12.........811
)
rsa_private = RSAPrivateKey.from_numbers(
e=65537,
n=12......811,
d=17......122
)
SSH Certificates
Attributes
| Attribute | Type | Key | Example Value | Description |
|---|---|---|---|---|
| Certificate Type | Integer (1/2) | cert_type | 1 | The type of certificate, 1 for User and 2 for Host. Can also be defined as sshkey_tools.fields.CERT_TYPE.USER or sshkey_tools.fields.CERT_TYPE.HOST |
| Serial | Integer | serial | 11223344 | The serial number for the certificate, a 64-bit integer |
| Key ID | String | key_id | someuser@somehost | The key identifier, can be set to any string, for example username, email or other unique identifier |
| Principals | List | principals | ['zone-webservers', 'server-01'] | The principals for which the certificate is valid, this needs to correspond to the allowed principals on the OpenSSH Server-side. Only valid for User certificates |
| Valid After | Integer | valid_after | datetime.now() | The datetime object or unix timestamp for when the certificate validity starts |
| Valid Before | Integer | valid_before | datetime.now() + timedelta(hours=12) | The datetime object or unix timestamp for when the certificate validity ends |
| Critical Options | Dict | critical_options | {'source-address': '1.2.3.4/8'} | Options set on the certificate that the OpenSSH server cannot choose to ignore (critical). Only valid on user certificates. Valid options are force-command (for limiting the user to a certain shell, e.g. sftp-internal), source-address (to limit the source IPs the user can connect from) and verify-required (to require the user to touch a hardware key before usage) |
| Extensions | Dict/Set/List/Tuple | extensions | {'permit-X11-forwarding', 'permit-port-forwarding'} | Extensions that the certificate holder is allowed to use. Valid options are no-touch-required, permit-X11-forwarding, permit-agent-forwarding, permit-port-forwarding, permit-pty, permit-user-rc |
Certificate creation
The basis for a certificate is the public key for the subject (User/Host), and bases the format of the certificate on that.
from datetime import datetime, timedelta
from cryptography.hazmat.primitives import (
serialization as crypto_serialization,
hashes as crypto_hashes
)
from cryptography.hazmat.primitives.asymmetric import padding as crypto_padding
from sshkey_tools.keys import PublicKey, RSAPrivateKey, RsaAlgs
from sshkey_tools.cert import SSHCertificate
from sshkey_tools.exceptions import SignatureNotPossibleException
user_pubkey = PublicKey.from_file('path/to/user_key.pub')
ca_privkey = PrivateKey.from_file('path/to/ca_key')
# You can create a certificate with a dict of pre-set options
cert_opts = {
'cert_type': 1,
'serial': 12345,
'key_id': "my.user@mycompany.com",
'principals': [
'webservers-dev',
'webservers-prod',
'servername01'
],
'valid_after': datetime.now(),
'valid_before': datetime.now() + timedelta(hours=12),
'critical_options': {},
'extensions': [
'permit-pty',
'permit-user-rc',
'permit-port-forwarding'
]
}
# Create a signable certificate from a PublicKey class
certificate = SSHCertificate.from_public_class(
user_pubkey,
ca_privkey,
**cert_opts
)
# You can also create the certificate in steps
certificate = SSHCertificate.from_public_class(
user_pubkey
)
# Set the CA private key used to sign the certificate
certificate.set_ca(ca_privkey)
# Set or update the options one-by-one
for key, value in cert_opts.items():
certificate.set_opt(key, value)
# Via a dict
certificate.set_opts(**cert_opts)
# Or via parameters
certificate.set_opts(
cert_type=1,
serial=12345,
key_id='my.user@mycompany.com',
principals=['zone-webservers'],
valid_after=datetime.now(),
valid_before=datetime.now() + timedelta(hours=12),
critical_options={},
extensions={}
)
# Check if the certificate is ready to be signed
# Will return True or an exception
certificate.can_sign()
# Catch exceptions
try:
certificate.can_sign()
except SignatureNotPossibleException:
...
# Sign the certificate
certificate.sign()
# For certificates signed by an RSA key, you can choose the hashing algorithm
# to be used for creating the hash of the certificate data before signing
certificate.sign(
hash_alg=RsaAlgs.SHA512
)
# If you want to verify the signature after creation,
# you can do so with the verify()-method
#
# Please note that a public key should always be provided
# to this function if the certificate was not just created,
# since an attacker very well could have replaced CA public key
# and signature with their own
#
# The method will return None if successful, and InvalidSignatureException
# if the signature does not match the data
certificate.verify()
certificate.verify(ca_privkey.public_key)
# If you prefer to verify manually, you can use the CA public key object
# from sshkey_tools or the key object from pyca/cryptography
# PublicKey
ca_pubkey = PublicKey.from_file('path/to/ca_pubkey')
ca_pubkey.verify(
certificate.get_signable_data(),
certificate.signature.value,
RsaAlgs.SHA256.value[1]
)
# pyca/cryptography RSAPrivateKey
with open('path/to/ca_pubkey', 'rb') as file:
crypto_ca_pubkey = crypto_serialization.load_ssh_public_key(file.read())
crypto_ca_pubkey.verify(
certificate.get_signable_data(),
certificate.signature.value,
crypto_padding.PKCS1v15(),
crypto_hashes.SHA256()
)
# You now have an OpenSSH Certificate
# Export it to file, string or bytes
certificate.to_file('path/to/user_key-cert.pub')
cert_string = certificate.to_string()
cert_bytes = certificate.to_bytes()
Load an existing certificate
Certificates can be loaded from file, a string/bytestring with file contents or the base64-decoded byte data of the certificate
from sshkey_tools.keys import PublicKey, PrivateKey
from sshkey_tools.cert import SSHCertificate, RSACertificate
# Load an existing certificate
certificate = SSHCertificate.from_file('path/to/user_key-cert.pub')
# or
certificate = RSACertificate.from_file('path/to/user_key-cert.pub')
# Verify the certificate with a CA public key
ca_pubkey = PublicKey.from_file('path/to/ca_key.pub')
certificate.verify(ca_pubkey)
# Create a new certificate with duplicate values from existing certificate
# You can use existing or previously issued certificates as templates
# for creating new ones
certificate = SSHCertificate.from_file('path/to/user_key-cert.pub')
ca_privkey = PrivateKey.from_file('path/to/ca_privkey')
certificate.set_ca(ca_privkey)
certificate.sign()
certificate.to_file('path/to/user_key-cert2.pub')
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sshkey-tools-0.8.2.tar.gz.
File metadata
- Download URL: sshkey-tools-0.8.2.tar.gz
- Upload date:
- Size: 37.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.10.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e81631a4a00df3dc55cca2205116660d4b733099206aa1f728f29dd646b6aa37
|
|
| MD5 |
dc333cdc008f15591ee2ba5bc37baf12
|
|
| BLAKE2b-256 |
e7b4c941013b44b764c8b6b16733f4535893204dfc6d82db26783df0ce4798a4
|
File details
Details for the file sshkey_tools-0.8.2-py3-none-any.whl.
File metadata
- Download URL: sshkey_tools-0.8.2-py3-none-any.whl
- Upload date:
- Size: 37.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.10.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f23e5fe0cb444930f48303b93ac1ce39d3ec5934d62c090c37ece61b29348ddc
|
|
| MD5 |
56440a805deaea31bbccef43fbbb459c
|
|
| BLAKE2b-256 |
1167f424d703a7323dfd36edab2dfde3c1e66f4d8f1d4b1f69f45cbca9e7cc57
|