Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Public key authentication

In this chapter, we will authenticate to the SSH server using a private key. With this authentication method, we send a public key to the server and we cryptographically prove that we own the corresponding private key. However, our private key is not transmitted, so it stays secure even if we connect to an untrusted server. The server then decides whether to accept the public key, for example by consulting a list of public keys stored in the ~/.ssh/authorized_keys file on the server.

Get the private key

Makiko supports several types of public/private keys:

  • RSA is the original public key cryptosystem based on integer factorization, which is still widely deployed. The theoretical algorithm is sound, but practical implementations of RSA have a long history of being subtly flawed, so using RSA is discouraged.
  • Elliptic curve cryptography is a more modern class of public key algorithms that are based on elliptic curves. Makiko supports:
    • ECDSA is a standardized signature algorithm scheme based on elliptic curves. Makiko supports ECDSA with two curves, NIST P-256 and NIST P-384. However, there have been suspicions that these curves may contain a backdoor, because they are generated using parameters that have not been fully explained.
    • EdDSA is a different signature algorithm scheme. Makiko supports the algorithm Ed25519, which uses Curve25519. This algorithm is fast and is considered very secure.

In Makiko, public keys are represented as the enum Pubkey and private keys as the enum Privkey. You can always obtain the public key from the private key by calling Privkey::pubkey().

File formats for private keys

To authenticate with public key authentication, we need to obtain the private key. Makiko can read private keys in the following formats:

  • PKCS#1: Legacy format for RSA keys, uses ASN.1 and encodes the key using the DER encoding.
  • PKCS#8: A newer format that can encode keys from different public key algorithms. This format is common when working with TLS and other cryptography applications. It is also based on ASN.1 and uses DER encoding.
  • OpenSSH: A format for private keys that is used by OpenSSH. The key is not encoded using DER but with the same encoding that is used in the SSH protocol.

Private keys in these formats may also be encrypted. Makiko supports decrypting private keys in PKCS#8 and OpenSSH formats; encrypted keys in the PKCS#1 format are not supported.

All these formats are binary. To make them easier to use, they are usually stored in PEM format, which encodes the binary data in a textual form. PEM files can be easily recognized by starting with -----BEGIN <tag>-----, followed by base64-encoded data and ending with -----END <tag>-----, where <tag> is a string that determines the format of the binary data:

  • -----BEGIN PRIVATE KEY----- is a private key in PKCS#8 format,
  • -----BEGIN RSA PRIVATE KEY----- is a private key in PKCS#1 format,
  • -----BEGIN OPENSSH PRIVATE KEY----- is a private key in OpenSSH format, and
  • -----BEGIN ENCRYPTED PRIVATE KEY----- is an encrypted private key in PKCS#8 format.

PEM files can also store other types of cryptographic material such as certificates (-----BEGIN CERTIFICATE-----).

Decode the private key

In Makiko, the makiko::keys module contains functions for decoding from all these formats, both binary and PEM. For the common case of reading an unencrypted private key from PEM, you can use the function decode_pem_privkey_nopass, which automatically detects the format of the key from the PEM tag. This function returns an enum DecodedPrivkeyNopass, which can take one of these variants:

  • Privkey if we successfully decoded the private key.
  • Pubkey if the private key was encrypted, but we could at least decode the public key. This is supported only by the OpenSSH format.
  • Encrypted if the file was encrypted and we could not decode anything.

We will just use the DecodedPrivkeyNopass::privkey() convenience method to get the private key, if it is available. However, in a real application, we may want to prompt the user for the password if the key is encrypted.

// Decode our private key from PEM.
let privkey = makiko::keys::decode_pem_privkey_nopass(PRIVKEY_PEM)
    .expect("Could not decode a private key from PEM")
    .privkey().cloned()
    .expect("Private key is encrypted");

In this tutorial, we simply hard-coded the private key, but in practice, you would usually read the key from a file or from configuration.

const PRIVKEY_PEM: &[u8] = br#"
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDyVJsRfh+NmkQKg2Dh6rPVodiQ3nC+dVoGMoMtYcbMJQAAAJBPdwHAT3cB
wAAAAAtzc2gtZWQyNTUxOQAAACDyVJsRfh+NmkQKg2Dh6rPVodiQ3nC+dVoGMoMtYcbMJQ
AAAEA5ct+xfc9qlJ4I2Jee8HIrAhN55yxmtUmvKpjT7q6QXPJUmxF+H42aRAqDYOHqs9Wh
2JDecL51WgYygy1hxswlAAAABmVkd2FyZAECAwQFBgc=
-----END OPENSSH PRIVATE KEY-----
"#;

Never hard-code private keys or other credentials into your program in practice!

Authenticate

Once we have the private key, we need to select the signing algorithm that we will use for authentication. For ECDSA and EdDSA keys, the algorithm is determined by the key type, but for RSA keys, multiple algorithms are applicable.

The RSA algorithms differ in the hash function that is used during signing: the original algorithm “ssh-rsa” uses the SHA-1 hash algorithm, which was found to be insecure, so the protocol was extended with new algorithms that replace the hash function with SHA-2 (Makiko supports “rsa-sha2-256” and “rsa-sha2-512”). These new algorithms work with the same keys as the old “ssh-rsa” algorithm, the only difference is in the mechanism that the client uses to prove to the server that it knows the private key.

All public key algorithms supported in Makiko are listed in the makiko::pubkey module. In this chapter, we know that the private key that we decoded in the previous section is an Ed25519 key, so there is just a single applicable algorithm, “ssh-ed25519”. In the next chapter, we will see how to select the algorithm more robustly.

// Select an algorithm for public key authentication.
let pubkey_algo = &makiko::pubkey::SSH_ED25519;

We can now call the Client::auth_pubkey() method to authenticate with username “edward” and the private key:

// Try to authenticate with the private key
let auth_res = client.auth_pubkey("edward".into(), privkey, pubkey_algo).await
    .expect("Error when trying to authenticate");

The method returns an AuthPubkeyResult, which is either a Success or Failure:

// Deal with the possible outcomes of public key authentication.
match auth_res {
    makiko::AuthPubkeyResult::Success => {
        println!("We have successfully authenticated using a private key");
    },
    makiko::AuthPubkeyResult::Failure(failure) => {
        panic!("The server rejected authentication: {:?}", failure);
    }
}

Full code for this tutorial can be found in examples/tutorial_3.rs. The program will print a message if the authentication was successful, or an error if it failed. If you don’t use the example server for this tutorial, you may need to change the code to use a different username and private key.

Next: Public key algorithm