A Python module for generating, parsing and handling OpenSSH keys and certificates
Project description
sshkey-tools
Python package for managing OpenSSH keypairs and certificates (protocol.CERTKEYS). Supported functionality includes:
Notice
The DSA algorithm has been deprecated and is removed in pyca/cryptography 41.x, meaning version 0.9. of this package will be the last to support DSA keys and certificates* for SSH. If there is any demand to reintroduce DSA support, please open an issue regarding this and we'll look into it.
For now, 0.9. will be restricted to version <41.1 of the cryptography package* and 0.10 will have its DSA support removed. We've introduced a deprecation notice in version 0.9.3.
Background
The DSA algorithm is considered deprecated and will be removed in a future version. If possible, use RSA, (ECDSA) or ED25519 as a first-hand choice.
Notice from OpenSSH:
OpenSSH 7.0 and greater similarly disable the ssh-dss (DSA) public key algorithm. It too is weak and we recommend against its use. It can be re-enabled using the HostKeyAlgorithms configuration option: sshd_config(5) HostKeyAlgorithms
ECDSA has some flaws, especially when using short nonces or re-using nonces, it can still be used but exercise some caution in regards to nonces/re-signing identical data multiple times.
Features
SSH Keys
- Supports RSA, ECDSA and ED25519 keys
- Import existing keys from file, string, byte data or pyca/cryptography class
- Generate new keys
- Get public key from private keys
- Sign bytestrings with private keys
- Export to file, string or bytes
- Generate fingerprint
OpenSSH Certificates
- Supports RSA, ECDSA and ED25519 certificates
- Import existing certificates from file, string or bytes
- Verify certificate signature against internal or separate public key
- Create new certificates from CA private key and subject public key
- Create new certificates using old certificate as template
- Sign certificates
- Export certificates to file, string or bytes
Roadmap
See issues for planned features and fixes
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
You can find the full documentation at scheiblingco.github.io/sshkey-tools/
Building the documentation
pdoc3 src/sshkey_tools/ -o docs --html
cp -rf docs/sshkey_tools/* docs/
rm -rf docs/sshkey_tools
SSH Keypairs (generating, loading, exporting)
# Import the certificate classes
from sshkey_tools.keys import (
RsaPrivateKey,
EcdsaPrivateKey,
Ed25519PrivateKey,
EcdsaCurves
)
#
## Generating keys
#
# For all keys except ED25519, the key size/curve can be manually specified
# Generate RSA (default is 4096 bits)
rsa_priv = RsaPrivateKey.generate()
rsa_priv = RsaPrivateKey.generate(2048)
# Generate DSA keys (since SSH only supports 1024-bit keys, this is the default)
# DEPRECATED
# dsa_priv = DsaPrivateKey.generate()
# Generate ECDSA keys (The default curve is P521)
ecdsa_priv = EcdsaPrivateKey.generate()
ecdsa_priv = EcdsaPrivateKey.generate(EcdsaCurves.P256)
# Generate ED25519 keys (fixed key size)
ed25519_priv = Ed25519PrivateKey.generate()
#
## Loading keys
#
# Keys can be loaded either via the specific class:
rsa_priv = RsaPrivateKey.from_file("/path/to/key", "OptionalSecurePassword")
# or via the general class, in case the type is not known in advance
rsa_priv = PrivateKey.from_file("/path/to/key", "OptionalSecurePassword")
# The import functions are .from_file(), .from_string() and .from_class() and are valid for both PublicKey and PrivateKey-classes
rsa_priv = PrivateKey.from_string("-----BEGIN OPENSSH PRIVATE KEY...........END -----", "OptionalSecurePassword")
rsa_priv = PrivateKey.from_class(pyca_cryptography_class)
# The different keys can also be loaded from their numbers, e.g. RSA Pubkey:
rsa_priv = PublicKey.from_numbers(65537, 123123123....1)
#
## Key functionality
#
# The public key for any loaded or generated private key is available in the .public_key attribute
ed25519_pub = ed25519_priv.public_key
# The private keys can be exported using to_bytes, to_string or to_file
rsa_priv.to_bytes("OptionalSecurePassword")
rsa_priv.to_string("OptionalSecurePassword", "utf-8")
rsa_priv.to_file("/path/to/file", "OptionalSecurePassword", "utf-8")
# The public keys also have .to_string() and .to_file(), but .to_bytes() is divided into .serialize() and .raw_bytes()
# The comment can be set before export by changing the public_key.comment-attribute
rsa_priv.public_key.comment = "Comment@Comment"
# This will return the serialized public key as found in an OpenSSH keyfile
rsa_priv.public_key.serialize()
b"ssh-rsa AAAA......... Comment@Comment"
# This will return the raw bytes of the key (base64-decoded middle portion)
rsa_priv.public_key.raw_bytes()
b"\0xc\0a\........"
SSH Key Signatures
The loaded private key objects can be used to sign bytestrings, and the public keys can be used to verify signatures on those
from sshkey_tools.keys import RsaPrivateKey, RsaPublicKey
from sshkey_tools.fields import RsaAlgs
signable_data = b'This is a message that will be signed'
privkey = RsaPrivateKey.generate()
pubkey = RsaPrivateKey.public_key
# Sign the data
signature = privkey.sign(signable_data)
# When using an RSA key for the signature, you can specify the hashing algorithm
# The default algorithm is SHA512
signature = privkey.sign(signable_data, RsaAlgs.SHA512)
# Verify the signature (Throws exception if invalid)
pubkey.verify(signable_data, signature)
OpenSSH Certificates
Introduction
Certificates are a way to handle access management/PAM for OpenSSH with the ability to dynamically grant access during a specific time, to specific servers and/or with specific attributes. There are a couple of upsides to using certificates instead of public/private keys, mainly:
- Additional Security: Certificate authentication for OpenSSH is built as an extension of public key authentication, enabling additional features on top of key-based access control.
- Short-term access: The user has to request a certificate for their keypair, which together with the private key grants access to the server. Without the certificate the user can't connect to the server - giving you control over how, when and from where the user can connect.
- Hostkey Verification: Certificiates can be issued for the OpenSSH Server, adding the CA public key to the clients enables you to establish servers as trusted without the hostkey warning.
- RBAC: Control which servers or users (principals) a keypair has access to, and specify the required principals for access to certain functionality on the server side.
- Logging: Key ID and Serial fields for tracking of issued certificates
- CRL: Revoke certificates prematurely if they are compromised
Structure
The original OpenSSH certificate format is a block of parameters, encoded and packed to a bytestring. In this package, the fields have been divided into three parts. For a more detailed information about the format, see PROTOCOL.certkeys.
Certificate Header
Attribute | Type(Length) | Key | Example Value | Description |
---|---|---|---|---|
Public Key/Certificate type | string(fixed) | pubkey_type | ssh-rsa-sha2-512-cert-v01@openssh.com | The private key (and certificate) type, derived from the public key for which the certificate is created (Automatically set upon creation) |
Subject public key | bytestring(variable) | public_key | \x00\x00\x00.......... | The public key for which the certificate is created (Automatically set upon creation) |
Nonce | string | nonce(variable, typically 16 or 32 bytes) | abcdefghijklmnopqrstuvwxyz | A random string included to make attacks that depend on inducing collisions in the signature hash infeasible. (Default is automatically set, can be changed with Certificate.header.nonce = "abcdefg..." |
Certificate Fields
Attribute | Type(Length) | Key | Example Value | Description |
---|---|---|---|---|
Serial | Integer(64-bit) | serial | 1234567890 | An optional certificate serial number set by the CA to provide an abbreviated way to refer to certificates from that CA. If a CA does not wish to number its certificates, it must set this field to zero. |
Certificate type | Integer(1 or 2) | cert_type | 1 | The type of the certificate, 1 for user certificates, 2 for host certificates |
Key ID | string(variable) | key_id | someuser@somehost | Free-form text field that is filled in by the CA at the time of signing; the intention is that the contents of this field are used to identify the identity principal in log messages. |
Valid Principals | List(string(variable)) | principals | ['some-user', 'some-group', production-webservers'] | These principals list the names for which this certificate is valid hostnames for SSH_CERT_TYPE_HOST certificates and usernames for SH_CERT_TYPE_USER certificates. As a special case, a zero-length "valid principals" field means the certificate is valid for any principal of the specified type. |
Valid After | Timestamp | valid_after | datetime.now() | Timestamp for the start of the validity period for the certificate |
Valid Before | Timestamp | valid_before | datetime.now()+timedelta(hours=8) or 1658322031 | Timestamp for the end of the validity period for the certificate. Needs to be larger than valid_after, can be a string (ex. 2d, 2w, 1h4m, 99d) or forever (MAX_INT64) |
Critical Options | Dict(string, string) | critical_options | [] | Zero or more of the available critical options (see below) |
Extensions | Dict(string, string)/List/Tuple/Set | extensions | [] | Zero or more of the available extensions (see below) |
Critical Options
Name | Format | Description |
---|---|---|
force-command | string | Specifies a command that is executed (replacing any the user specified on the ssh command-line) whenever this key is used for authentication. |
source-address | string | Comma-separated list of source addresses from which this certificate is accepted for authentication. Addresses are specified in CIDR format (nn.nn.nn.nn/nn or hhhh::hhhh/nn). If this option is not present, then certificates may be presented from any source address. |
verify-required | empty | Flag indicating that signatures made with this certificate must assert FIDO user verification (e.g. PIN or biometric). This option only makes sense for the U2F/FIDO security key types that support this feature in their signature formats. |
Extensions
Name | Format | Description |
---|---|---|
no-touch-required | empty | Flag indicating that signatures made with this certificate need not assert FIDO user presence. This option only makes sense for the U2F/FIDO security key types that support this feature in their signature formats. |
permit-X11-forwarding | empty | Flag indicating that X11 forwarding should be permitted. X11 forwarding will be refused if this option is absent. |
permit-agent-forwarding | empty | Flag indicating that agent forwarding should be allowed. Agent forwarding must not be permitted unless this option is present. |
permit-port-forwarding | empty | Flag indicating that port-forwarding should be allowed. If this option is not present, then no port forwarding will be allowed. |
permit-pty | empty | Flag indicating that PTY allocation should be permitted. In the absence of this option PTY allocation will be disabled. |
permit-user-rc | empty | Flag indicating that execution of ~/.ssh/rc should be permitted. Execution of this script will not be permitted if this option is not present. |
Certificate Body
Attribute | Type(Length) | Key | Example Value | Description |
---|---|---|---|---|
Reserved | string(0) | reserved | "" | Reserved for future use, must be empty (automatically set upon signing) |
CA Public Key | bytestring(variable) | ca_pubkey | \x00\x00\x00.......... | The public key of the CA that issued this certificate (automatically set upon signing) |
Signature | bytestring(variable) | signature | \x00\x00\x00.......... | The signature of the certificate, created by the CA (automatically set upon signing) |
Creating, signing and verifying certificates
# Every certificate needs two parts, the subject (user or host) public key and the CA Private key
from sshkey_tools.cert import SSHCertificate, CertificateFields, Ed25519Certificate
from sshkey_tools.keys import Ed25519PrivateKey
from datetime import datetime, timedelta
subject_pubkey = Ed25519PrivateKey.generate().public_key
ca_privkey = Ed25519PrivateKey.generate()
# There are multiple ways to create a certificate, either by creating the certificate body field object first and then creating the certificate, or creating the certificate and setting the fields one by one
# Create certificate body fields
cert_fields = CertificateFields(
serial=1234567890,
cert_type=1,
key_id="someuser@somehost",
principals=["some-user", "some-group", "production-webservers"],
valid_after=datetime.now(),
valid_before=datetime.now() + timedelta(hours=8),
critical_options=[],
extensions=[
"permit-pty",
"permit-X11-forwarding",
"permit-agent-forwarding",
],
)
# Create certificate from existing fields
certificate = SSHCertificate(
subject_pubkey=subject_pubkey,
ca_privkey=ca_privkey,
fields=cert_fields,
)
# Start with a blank certificate by calling the general class
certificate = SSHCertificate.create(
subject_pubkey=subject_pubkey,
ca_privkey=ca_privkey
)
# You can also call the specialized classes directly, for the general class the .create-function needs to be used
certificate = Ed25519Certificate(
subject_pubkey=subject_pubkey,
ca_privkey=ca_privkey
)
# Manually set the fields
certificate.fields.serial = 1234567890
certificate.fields.cert_type = 1
certificate.fields.key_id = "someuser@somehost"
certificate.fields.principals = ["some-user", "some-group", "production-webservers"]
certificate.fields.valid_after = datetime.now()
certificate.fields.valid_before = datetime.now() + timedelta(hours=8)
certificate.fields.critical_options = []
certificate.fields.extensions = [
"allow-pty",
"permit-X11-forwarding",
"permit-agent-forwarding",
]
# Check if the certificate is ready to be signed
certificate.can_sign()
# Sign the certificate
certificate.sign()
# Verify the certificate against the included public key (insecure, but useful for testing)
certificate.verify()
# Verify the certificate against a public key that is not included in the certificate
certificate.verify(ca_privkey.public_key)
# Raise an exception if the certificate is invalid
certificate.verify(ca_privkey.public_key, True)
# Export the certificate to file/string
certificate.to_file('filename-cert.pub')
cert_str = certificate.to_string()
Loading, re-creating and verifying existing certificates
from sshkey_tools.cert import SSHCertificate, CertificateFields, Ed25519Certificate
from sshkey_tools.keys import PublicKey, PrivateKey
from datetime import datetime, timedelta
# Load a certificate from file or string
# This will return the correct certificate type based on the contents of the certificate
certificate = SSHCertificate.from_file('filename-cert.pub')
certificate = SSHCertificate.from_string(cert_str)
type(certificate) # sshkey_tools.cert.Ed25519Certificate
# Verify the certificate signature against the included public key (insecure, but useful for testing)
certificate.verify()
# Verify the certificate signature against a public key
pubkey = PublicKey.from_file('filename-pubkey.pub')
certificate.verify(pubkey)
# Raise an exception if the certificate is invalid
certificate.verify(pubkey, True)
# Use the loaded certificate as a template to create a new one
new_ca = PrivateKey.from_file('filename-ca')
certificate.replace_ca(new_ca)
certificate.sign()
Changelog
0.9.1
- Updated documentation
- Fix for bug where exception would occur when trying to export a key without a comment set
0.9
- Adjustments to certificate field handling for easier usage/syntax autocompletion
- Updated testing
- Removed method for changing RSA hash method (now default SHA512)
0.8.2
- Fixed bug where an RSA certificate would send the RSA alg to the sign() function of another key type
0.8.1
- Changed versioning for out-of-github installation/packaging
- Moved documentation to HTML (PDOC3)
- Added verification of certificate signature
- Added option to choose RSA hashing algorithm for signing
- Removed test files
- Added documentation deployment CD for GH pages
0.8
- Initial public release
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
File details
Details for the file sshkey-tools-0.11.3.tar.gz
.
File metadata
- Download URL: sshkey-tools-0.11.3.tar.gz
- Upload date:
- Size: 46.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.11.9
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f611a9879410a999b6027d91248ee840003941899d03d52693772db1bad676cf |
|
MD5 | 29533eca23c5c0204155ff2462457946 |
|
BLAKE2b-256 | b7c8a793d37f72d80336016a62f8f0b408c2bf30bd89a8c632f519d09c056c81 |
File details
Details for the file sshkey_tools-0.11.3-py3-none-any.whl
.
File metadata
- Download URL: sshkey_tools-0.11.3-py3-none-any.whl
- Upload date:
- Size: 42.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.11.9
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | a05986de3241feda356a0ef5a6e976460ad71354a4f28ae39d85c7827659e8af |
|
MD5 | 083fd17c1803ef457e2252fb848d3786 |
|
BLAKE2b-256 | 1eb870118b5ff3ec748e9821aedd1978abf08fdba4d89a01eaf0184e6fff40b2 |