├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── certs_per_second.rs ├── examples ├── into-ssh-pubkey.rs ├── new-fido-sshkey.rs ├── sign-cert-with-file.rs ├── sign-cert-with-yubikey.rs ├── sign-file-with-file.rs ├── ssh-cert-info.rs ├── ssh-pkey-info.rs ├── ssh-pubkey-from-cert.rs ├── ssh-pubkey-info.rs ├── yk-fingerprint.rs └── yk-provision.rs ├── src ├── error.rs ├── fido │ ├── generate │ │ ├── ctap2_hid.rs │ │ ├── mod.rs │ │ └── mozilla.rs │ ├── mod.rs │ ├── parsing.rs │ ├── signing │ │ ├── ctap2_hid.rs │ │ ├── mod.rs │ │ └── mozilla.rs │ ├── utils │ │ ├── ctap2_hid.rs │ │ ├── mod.rs │ │ └── mozilla.rs │ └── verification.rs ├── lib.rs ├── ssh │ ├── allowed_signer.rs │ ├── cert.rs │ ├── keytype.rs │ ├── mod.rs │ ├── privkey.rs │ ├── pubkey.rs │ ├── reader.rs │ ├── signature.rs │ └── writer.rs ├── utils.rs ├── x509 │ └── mod.rs └── yubikey │ ├── mod.rs │ ├── piv │ ├── management.rs │ ├── mod.rs │ └── ssh.rs │ └── verification.rs └── tests ├── allowed-signer.rs ├── allowed-signers.rs ├── allowed_signers ├── good_allowed_signers └── test_keys ├── cert-conformance.rs ├── cert-creation-parse-rsa.rs ├── cert-creation-parse.rs ├── cert-creation.rs ├── cert-ecdsa.rs ├── cert-ed25519.rs ├── cert-offensive.rs ├── cert-options.rs ├── cert-rsa.rs ├── cert-sk-ecdsa.rs ├── cert-sk-ed25519.rs ├── certs ├── ecdsa384_signed_by_ecdsa384-cert.pub ├── ed25519_signed_by_ecdsa_sk-cert.pub ├── ed25519_signed_by_ed25519-cert.pub ├── ed25519_sk_signed_by_ecdsa384-cert.pub └── ed25519_sk_signed_by_ed25519_sk-cert.pub ├── fido-lite.rs ├── keys ├── public │ ├── k1.pub │ ├── k2.pub │ ├── rsa-sha2-256-4096.pub │ ├── rsa-sha2-512-4096.pub │ ├── rsa-sha2-512-8192.pub │ └── test.pub ├── sk │ ├── ecdsa │ ├── ecdsa.pub │ ├── ed25519 │ └── ed25519.pub └── unencrypted │ ├── ecdsa_256_1 │ ├── ecdsa_384_1 │ ├── ed25519_1 │ ├── ed25519_2 │ ├── rsa-2048 │ ├── rsa-sha2-256-4096 │ ├── rsa-sha2-512-4096 │ ├── rsa-sha2-512-8192 │ └── rsa_2048_1 ├── messages └── Test ├── privkey.rs ├── privkey_encrypted.rs ├── pubkey.rs ├── reader.rs ├── signature-bad.rs ├── signature-creation-rsa.rs ├── signature-creation.rs ├── signature.rs ├── signatures ├── ecdsa_256_1_Test.sig ├── ecdsa_384_1_Test.sig ├── ed25519_1_Test.sig ├── ed25519_2_Test.sig ├── rsa-sha2-256-4096_Test.sig ├── rsa-sha2-512-4096_Test.sig ├── rsa-sha2-512-8192_Test.sig ├── rsa_2048_1_Test.sig ├── sk_ecdsa_Test.sig └── sk_ed25519_Test.sig ├── signatures_bad ├── ecdsa_256_1_bitflip_Test.sig └── ed25519_1_empty-namespace_Test.sig ├── sk-privkey.rs ├── sk-pubkey.rs ├── writer.rs └── yubikey-lite.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: macOS + Ubuntu 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | macos-build-test-all: 14 | runs-on: macos-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Build 19 | run: cargo build --features="all" --examples --verbose 20 | - name: Run tests 21 | run: cargo test --features="all" --verbose 22 | 23 | macos-build-test-experimental: 24 | runs-on: macos-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Build 29 | run: cargo build --no-default-features --features="experimental" --examples --verbose 30 | - name: Run tests 31 | run: cargo test --no-default-features --features="experimental" --verbose 32 | 33 | ubuntu-build-test-no-yubikey: 34 | runs-on: ubuntu-latest 35 | 36 | steps: 37 | - uses: actions/checkout@v2 38 | - name: Build 39 | run: cargo build --no-default-features --features="encrypted-keys,rsa-signing,x509-support" --examples --verbose 40 | - name: Run tests 41 | run: cargo test --no-default-features --features="encrypted-keys,rsa-signing,x509-support" --verbose 42 | 43 | macos-build-check-ctap2: 44 | runs-on: macos-latest 45 | 46 | steps: 47 | - uses: actions/checkout@v2 48 | - name: Build 49 | run: cargo build --no-default-features --features="all-but-fido, fido-support" --examples --verbose 50 | - name: Run tests 51 | run: cargo test --no-default-features --features="all-but-fido, fido-support" --verbose 52 | 53 | macos-build-check-mozilla: 54 | runs-on: macos-latest 55 | 56 | steps: 57 | - uses: actions/checkout@v2 58 | - name: Build 59 | run: cargo build --no-default-features --features="all-but-fido, fido-support-mozilla" --examples --verbose 60 | - name: Run tests 61 | run: cargo test --no-default-features --features="all-but-fido, fido-support-mozilla" --verbose -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | /target 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sshcerts" 3 | version = "0.14.0" 4 | authors = ["Mitchell Grenier "] 5 | edition = "2021" 6 | license-file = "LICENSE" 7 | description = "A library for parsing, verifying, and creating SSH Certificates" 8 | repository = "https://github.com/obelisk/sshcerts" 9 | homepage = "https://github.com/obelisk/sshcerts" 10 | keywords = ["ssh", "yubikey", "certs", "certificates"] 11 | categories = ["authentication"] 12 | 13 | [features] 14 | default = ["all"] 15 | 16 | all = [ 17 | "encrypted-keys", 18 | "rsa-signing", 19 | "x509-support", 20 | "yubikey-support", 21 | "fido-support-mozilla", 22 | ] 23 | 24 | all-but-fido = [ 25 | "encrypted-keys", 26 | "rsa-signing", 27 | "x509-support", 28 | "yubikey-support", 29 | ] 30 | 31 | experimental = [ 32 | "encrypted-keys", 33 | "rsa-signing", 34 | "x509-support", 35 | "yubikey-support", 36 | "fido-support-mozilla", 37 | ] 38 | 39 | encrypted-keys = ["aes", "bcrypt-pbkdf", "ctr"] 40 | 41 | wasm_experimental = ["ring/wasm32_unknown_unknown_js"] 42 | 43 | # Full FIDO support pulls in ctap-hid-fido2 with USB dependencies. Use fido-lite 44 | # if you need to work with fido data, without needing to generate it. 45 | fido-support = ["ctap-hid-fido2", "fido-lite"] 46 | fido-support-mozilla = ["authenticator", "fido-lite"] 47 | fido-lite = ["minicbor", "x509-parser"] 48 | rsa-signing = ["simple_asn1", "num-bigint"] 49 | x509-support = ["der-parser", "x509", "x509-parser"] 50 | yubikey-support = ["rcgen", "yubikey", "yubikey-lite"] 51 | yubikey-lite = ["x509-support"] 52 | 53 | [dependencies] 54 | base64 = "0.13" 55 | chrono = "0.4" 56 | ring = "0.17" 57 | zeroize = { version = "1", features = ["zeroize_derive"] } 58 | 59 | # Dependencies for rsa-signing 60 | simple_asn1 = { version = "0.5", optional = true } 61 | num-bigint = { version = "0.4", optional = true } 62 | 63 | # Dependencies for yubikey-support 64 | yubikey = { version = "0.7", features = ["untested"], optional = true } 65 | lexical-core = { version = ">0.7.4", optional = true } 66 | rcgen = { version = "0.11", optional = true } 67 | x509 = { version = "0.2", optional = true } 68 | x509-parser = { version = "0.15", features = ["verify"], optional = true } 69 | der-parser = { version = "5", optional = true } 70 | 71 | # Dependencies for encrypted-keys 72 | aes = { version = "0.7", features = ["ctr"], optional = true } 73 | bcrypt-pbkdf = { version = "0.6", optional = true } 74 | ctr = { version = "0.8", optional = true } 75 | 76 | # Dependencies for fido-support-* 77 | minicbor = { version = "0.13", optional = true } 78 | 79 | # Dependencies for fido-support-mozilla 80 | authenticator = { version = "0.4.0-alpha.24", default-features = false, features = [ 81 | "crypto_openssl", 82 | ], optional = true } 83 | # authenticator = { path = "../authenticator-rs", default-features = false, features = [ 84 | # "crypto_openssl", 85 | # ], optional = true } 86 | 87 | 88 | # Dependencies for fido-support 89 | ctap-hid-fido2 = { version = "3", optional = true } 90 | #ctap-hid-fido2 = {git = "https://github.com/gebogebogebo/ctap-hid-fido2", branch="master", optional = true} 91 | #ctap-hid-fido2 = {git = "https://github.com/obelisk/ctap-hid-fido2", branch="device_by_path", optional = true} 92 | #ctap-hid-fido2 = {path = "../ctap-hid-fido2", optional = true} 93 | 94 | [dev-dependencies] 95 | env_logger = "0.8.2" 96 | hex = "0.4.2" 97 | clap = "3.0.5" 98 | criterion = "0.3" 99 | 100 | [[bench]] 101 | name = "certs_per_second" 102 | harness = false 103 | required-features = ["yubikey-support"] 104 | 105 | [[example]] 106 | name = "yk-fingerprint" 107 | required-features = ["yubikey-support"] 108 | 109 | [[example]] 110 | name = "yk-provision" 111 | required-features = ["yubikey-support"] 112 | 113 | [[example]] 114 | name = "sign-cert-with-yubikey" 115 | required-features = ["yubikey-support"] 116 | 117 | [[example]] 118 | name = "ssh-pkey-info" 119 | required-features = ["encrypted-keys"] 120 | 121 | [[example]] 122 | name = "into-ssh-pubkey" 123 | required-features = ["x509-support"] 124 | 125 | [[example]] 126 | name = "new-fido-sshkey" 127 | required-features = ["fido-support-mozilla"] 128 | 129 | [[test]] 130 | name = "privkey-encrypted" 131 | path = "tests/privkey_encrypted.rs" 132 | required-features = ["encrypted-keys"] 133 | 134 | [[test]] 135 | name = "cert-creation-parse-rsa" 136 | path = "tests/cert-creation-parse-rsa.rs" 137 | required-features = ["encrypted-keys", "rsa-signing"] 138 | 139 | [[test]] 140 | name = "fido-lite" 141 | path = "tests/fido-lite.rs" 142 | required-features = ["fido-support-mozilla"] 143 | 144 | [[test]] 145 | name = "yubikey-lite" 146 | path = "tests/yubikey-lite.rs" 147 | required-features = ["yubikey-lite"] 148 | 149 | [[test]] 150 | name = "signature-creation-rsa" 151 | path = "tests/signature-creation-rsa.rs" 152 | required-features = ["rsa-signing"] 153 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Mitchell Grenier 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sshcerts 2 | sshcerts (formerly rustica-keys) is a library for parsing, creating, and signing OpenSSH certificates. It was originally based on `rust-sshkeys` by @dnaeon but has been significantly expanded to offer a greater writer API, certificate signature validation, issuing new certificates, parsing encrypted private keys, and more. 3 | 4 | This library contains other optional functionality for Yubikey key management. The Yubikey management module can be used to provision slot with keys that can never leave the device and SSH module for SSH signatures backed by a Yubikey. To enable this functionality use the feature `yubikey-support`. 5 | 6 | This library attempts to keep as few dependencies as possible so many features are gated behind features. For example, RSA certificates can be read in and verified but not created unless the `rsa-signing` feature is used. 7 | 8 | Support for encrypted private keys in available with the `encrypted-keys` features. 9 | 10 | Finally there is the x509 module for doing some strange things like getting the SSH public key from an x509 certificate. This is automatically included when using the Yubikey feature but can be enabled separately with `x509-support` 11 | 12 | ## Builds 13 | ![macOS and Ubuntu Builds](https://github.com/obelisk/sshcerts/workflows/macOS%20+%20Ubuntu/badge.svg) 14 | 15 | ## API Stability 16 | The API for this crate should not be considered stable and expect breaking changes between versions. 17 | 18 | ## Security Warning 19 | No review has been done. I built it because I thought people could find it useful. Be wary about using this in production without doing a thorough code review. 20 | 21 | ## Yubikey Benchmarks 22 | Yubikeys are not fast HSMs so running infra generating many certificates per second is going to be a challenge. I have benchmarked several kinds with both 256 and 384 bit ECDSA keys. The results are below: 23 | 24 | | | ECDSA256 | ECDSA384 | Notes | 25 | |---|---|---|---| 26 | |4C FIPS <4.4.5>| - | - | Requires pin on sign| 27 | |Nano 4C <4.3.7>| 9.14 | 6.35 || 28 | |Nano 5C <5.2.4>| 10.58 | 7.10 || 29 | |5 NFC <5.2.7>| 10.93 | 7.20 || 30 | |5Ci <5.2.4>| 10.94 | 7.22 || 31 | 32 | This shows 5s are about 15% faster than 4s but between 5s is mostly a wash. Loading the same key on multiple Yubikeys would provide a multiplicative speed up (equal to number of keys) but if you need that many signatures per second, Yubikeys are probably not the way to go. 33 | 34 | ## Licence 35 | This software is provided under the MIT licence so you may use it basically however you wish so long as all distributions and derivatives (source and binary) include the copyright from the `LICENSE`. -------------------------------------------------------------------------------- /benches/certs_per_second.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | use sshcerts::yubikey::{RetiredSlotId, SlotId, Yubikey}; 4 | 5 | fn generate_certs(n: u64) -> () { 6 | let data = [0; 32]; 7 | let mut yk = Yubikey::new().unwrap(); 8 | for _ in 0..n { 9 | yk.ssh_cert_signer(&data, &SlotId::Retired(RetiredSlotId::R19)) 10 | .unwrap(); 11 | } 12 | } 13 | 14 | pub fn criterion_benchmark(c: &mut Criterion) { 15 | c.bench_function("generate 3 signatures", |b| b.iter(|| generate_certs(5))); 16 | } 17 | 18 | criterion_group!(benches, criterion_benchmark); 19 | criterion_main!(benches); 20 | -------------------------------------------------------------------------------- /examples/into-ssh-pubkey.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | 4 | use sshcerts::x509::der_encoding_to_ssh_public_key; 5 | 6 | fn help() { 7 | println!("Read a PEM/DER encoded public key and convert it to SSH format"); 8 | println!("Usage: into-ssh-pubkey "); 9 | } 10 | 11 | fn main() { 12 | let args: Vec = env::args().collect(); 13 | 14 | if args.len() != 2 { 15 | return help(); 16 | } 17 | 18 | let contents = match fs::read(&args[1]) { 19 | Ok(c) => c, 20 | Err(e) => { 21 | println!("Error {} opening file: {}", e, &args[1]); 22 | return help(); 23 | } 24 | }; 25 | 26 | match der_encoding_to_ssh_public_key(&contents) { 27 | Ok(public_key) => println!("{}", public_key), 28 | Err(e) => println!("Error: {}", e), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/new-fido-sshkey.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use clap::{Arg, Command}; 4 | 5 | use sshcerts::fido::generate::generate_new_ssh_key; 6 | 7 | use std::fs::File; 8 | 9 | fn main() { 10 | env_logger::init(); 11 | let matches = Command::new("new-fido-sshkey") 12 | .version(env!("CARGO_PKG_VERSION")) 13 | .author("Mitchell Grenier ") 14 | .about("Generate a new SSH Key backed by a hardware token") 15 | .arg( 16 | Arg::new("pin") 17 | .help("If using an SK key handle, what PIN to use with the key (not always needed)") 18 | .long("pin") 19 | .short('p') 20 | .required(false) 21 | .takes_value(true) 22 | ) 23 | .arg( 24 | Arg::new("device") 25 | .help("Manually specify a device to use. If not provided, one will be chosen randomly or by user tap selection (when implemented)") 26 | .long("device") 27 | .short('d') 28 | .required(false) 29 | .takes_value(true) 30 | ) 31 | .arg( 32 | Arg::new("out") 33 | .help("Path to write the resultant private key handle to") 34 | .long("out") 35 | .short('o') 36 | .required(false) 37 | .takes_value(true) 38 | ) 39 | .get_matches(); 40 | 41 | let pin = if let Some(pin) = matches.value_of("pin") { 42 | Some(pin.to_owned()) 43 | } else { 44 | None 45 | }; 46 | 47 | let device_path = if let Some(dev) = matches.value_of("device") { 48 | Some(dev.to_owned()) 49 | } else { 50 | None 51 | }; 52 | 53 | match generate_new_ssh_key("ssh:test_sk_key", "new-fido-sshkey", pin, device_path) { 54 | Ok(key) => { 55 | println!("{:#}", key.private_key.pubkey); 56 | 57 | if let Some(out) = matches.value_of("out") { 58 | let mut out = File::create(out).unwrap(); 59 | key.private_key.write(&mut out).unwrap(); 60 | } else { 61 | let mut buf = std::io::BufWriter::new(Vec::new()); 62 | key.private_key.write(&mut buf).unwrap(); 63 | let serialized = String::from_utf8(buf.into_inner().unwrap()).unwrap(); 64 | println!("Your new private key handle:\n{}", serialized); 65 | } 66 | } 67 | Err(e) => { 68 | println!("Failed to generate new SSH Key: {}", e.to_string()); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/sign-cert-with-file.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use clap::{Arg, Command}; 4 | 5 | use sshcerts::*; 6 | 7 | fn main() { 8 | env_logger::init(); 9 | let matches = Command::new("sign-cert-with-file") 10 | .version(env!("CARGO_PKG_VERSION")) 11 | .author("Mitchell Grenier ") 12 | .about("Sign an OpenSSH private key with another OpenSSH private key") 13 | .arg( 14 | Arg::new("sign") 15 | .help("The private key file you want to use as the signing authority") 16 | .long("signing_key") 17 | .short('s') 18 | .required(true) 19 | .takes_value(true), 20 | ) 21 | .arg( 22 | Arg::new("pin") 23 | .help("If using an SK key handle, what PIN to use with the key (not always needed)") 24 | .long("pin") 25 | .short('p') 26 | .required(false) 27 | .takes_value(true), 28 | ) 29 | .arg( 30 | Arg::new("principal") 31 | .help("Add this principal to the certificate") 32 | .long("principal") 33 | .short('n') 34 | .default_value("ubuntu") 35 | .takes_value(true), 36 | ) 37 | .arg( 38 | Arg::new("file") 39 | .help("The key to sign with the CA into an SSH certificate") 40 | .long("file") 41 | .short('f') 42 | .required(true) 43 | .takes_value(true), 44 | ) 45 | .get_matches(); 46 | 47 | let ssh_pubkey = PublicKey::from_path(matches.value_of("file").unwrap()).unwrap(); 48 | let mut ca_private_key = PrivateKey::from_path(matches.value_of("sign").unwrap()).unwrap(); 49 | 50 | if let Some(pin) = matches.value_of("pin") { 51 | ca_private_key.set_pin(pin); 52 | } 53 | 54 | let user_cert = Certificate::builder(&ssh_pubkey, CertType::User, &ca_private_key.pubkey) 55 | .unwrap() 56 | .serial(0x0) 57 | .key_id("key_id") 58 | .principal(matches.value_of("principal").unwrap()) 59 | .valid_after(0) 60 | .valid_before(0xFFFFFFFFFFFFFFFF) 61 | .set_extensions(Certificate::standard_extensions()) 62 | .sign(&ca_private_key); 63 | 64 | println!("{}", user_cert.unwrap()); 65 | } 66 | -------------------------------------------------------------------------------- /examples/sign-cert-with-yubikey.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use clap::{Arg, Command}; 4 | 5 | use sshcerts::ssh::SSHCertificateSigner; 6 | use sshcerts::yubikey::piv::{SlotId, Yubikey}; 7 | use sshcerts::*; 8 | 9 | use std::convert::TryFrom; 10 | 11 | fn slot_parser(slot: &str) -> Option { 12 | // If first character is R, then we need to parse the nice 13 | // notation 14 | if (slot.len() == 2 || slot.len() == 3) && slot.starts_with('R') { 15 | let slot_value = slot[1..].parse::(); 16 | match slot_value { 17 | Ok(v) if v <= 20 => Some(SlotId::try_from(0x81_u8 + v).unwrap()), 18 | _ => None, 19 | } 20 | } else if slot.len() == 4 && slot.starts_with("0x") { 21 | let slot_value = hex::decode(&slot[2..]).unwrap()[0]; 22 | Some(SlotId::try_from(slot_value).unwrap()) 23 | } else { 24 | None 25 | } 26 | } 27 | 28 | fn slot_validator(slot: &str) -> Result<(), String> { 29 | match slot_parser(slot) { 30 | Some(_) => Ok(()), 31 | None => Err(String::from( 32 | "Provided slot was not valid. Should be R1 - R20 or a raw hex identifier", 33 | )), 34 | } 35 | } 36 | 37 | struct YubikeySigner { 38 | slot: SlotId, 39 | } 40 | 41 | impl SSHCertificateSigner for YubikeySigner { 42 | fn sign(&self, buffer: &[u8]) -> Option> { 43 | let mut yk = Yubikey::new().unwrap(); 44 | match yk.ssh_cert_signer(buffer, &self.slot) { 45 | Ok(sig) => Some(sig), 46 | Err(_) => None, 47 | } 48 | } 49 | } 50 | 51 | fn main() { 52 | env_logger::init(); 53 | let matches = Command::new("sign-cert-with-yubikey") 54 | .version(env!("CARGO_PKG_VERSION")) 55 | .author("Mitchell Grenier ") 56 | .about("Sign an OpenSSH private key with a Yubikey") 57 | .arg( 58 | Arg::new("slot") 59 | .help("Numerical value for the slot on the yubikey to use for CA") 60 | .long("slot") 61 | .short('s') 62 | .required(true) 63 | .validator(slot_validator) 64 | .takes_value(true), 65 | ) 66 | .arg( 67 | Arg::new("principal") 68 | .help("Add this principal to the certificate") 69 | .long("principal") 70 | .short('n') 71 | .default_value("ubuntu") 72 | .takes_value(true), 73 | ) 74 | .arg( 75 | Arg::new("key") 76 | .help("The key to sign with the Yubikey into an SSH certificate") 77 | .long("key") 78 | .short('f') 79 | .required(true) 80 | .takes_value(true), 81 | ) 82 | .get_matches(); 83 | 84 | let slot = slot_parser(matches.value_of("slot").unwrap()).unwrap(); 85 | let mut yk = Yubikey::new().unwrap(); 86 | let yk_pubkey = yk.ssh_cert_fetch_pubkey(&slot).unwrap(); 87 | let ssh_pubkey = PublicKey::from_path(matches.value_of("key").unwrap()).unwrap(); 88 | 89 | let yk_signer = YubikeySigner { slot }; 90 | 91 | let user_cert = Certificate::builder(&ssh_pubkey, CertType::User, &yk_pubkey) 92 | .unwrap() 93 | .serial(0xFEFEFEFEFEFEFEFE) 94 | .key_id("key_id") 95 | .principal(matches.value_of("principal").unwrap()) 96 | .valid_after(0) 97 | .valid_before(0xFFFFFFFFFFFFFFFF) 98 | .set_extensions(Certificate::standard_extensions()) 99 | .sign(&yk_signer); 100 | 101 | println!("{}", user_cert.unwrap()); 102 | } 103 | -------------------------------------------------------------------------------- /examples/sign-file-with-file.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use clap::{Arg, Command}; 4 | 5 | use sshcerts::{ssh::VerifiedSshSignature, *}; 6 | 7 | fn main() { 8 | env_logger::init(); 9 | let matches = Command::new("sign-file-with-file") 10 | .version(env!("CARGO_PKG_VERSION")) 11 | .author("Mitchell Grenier ") 12 | .about("Sign a file with an OpenSSH private key") 13 | .arg( 14 | Arg::new("sign") 15 | .help("The private key file you want to use to sign the file") 16 | .long("signing_key") 17 | .short('s') 18 | .required(true) 19 | .takes_value(true), 20 | ) 21 | .arg( 22 | Arg::new("pin") 23 | .help("If using an SK key handle, what PIN to use with the key (not always needed)") 24 | .long("pin") 25 | .short('p') 26 | .required(false) 27 | .takes_value(true), 28 | ) 29 | .arg( 30 | Arg::new("file") 31 | .help("The file to sign with the provided key") 32 | .long("file") 33 | .short('f') 34 | .required(true) 35 | .takes_value(true), 36 | ) 37 | .arg( 38 | Arg::new("namespace") 39 | .help("The signing namespace you'd like the signature to be in") 40 | .long("namespace") 41 | .short('n') 42 | .default_value("file") 43 | .takes_value(true), 44 | ) 45 | .get_matches(); 46 | 47 | let mut private_key = PrivateKey::from_path(matches.value_of("sign").unwrap()).unwrap(); 48 | 49 | if let Some(pin) = matches.value_of("pin") { 50 | private_key.set_pin(pin); 51 | } 52 | 53 | let namespace = matches.value_of("namespace").unwrap(); 54 | 55 | let contents = std::fs::read(matches.value_of("file").unwrap()).unwrap(); 56 | 57 | let signature = 58 | VerifiedSshSignature::new_with_private_key(&contents, namespace, private_key, None) 59 | .unwrap(); 60 | 61 | println!("{}", signature); 62 | } 63 | -------------------------------------------------------------------------------- /examples/ssh-cert-info.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | 4 | use sshcerts::ssh::Certificate; 5 | 6 | fn help() { 7 | println!("An SSH Cert Parser based on the sshcerts library"); 8 | println!("Usage: ssh-cert-info "); 9 | } 10 | 11 | fn main() { 12 | let args: Vec = env::args().collect(); 13 | 14 | if args.len() != 2 { 15 | return help(); 16 | } 17 | 18 | let contents = match fs::read_to_string(&args[1]) { 19 | Ok(c) => c, 20 | Err(e) => { 21 | println!("Error {} opening file: {}", e, &args[1]); 22 | return help(); 23 | } 24 | }; 25 | 26 | for (i, line) in contents.split('\n').into_iter().enumerate() { 27 | if line.is_empty() { 28 | break; 29 | } 30 | 31 | match Certificate::from_string(line) { 32 | Ok(c) => println!("{:#}", c), 33 | Err(e) => { 34 | println!("Line {}: Certificate not valid: {}", i, e); 35 | } 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/ssh-pkey-info.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use sshcerts::ssh::PrivateKey; 4 | 5 | fn help() { 6 | println!("An SSH Private Key reader based on the sshcerts library"); 7 | println!("Usage: ssh-pkey-info "); 8 | } 9 | 10 | fn main() -> Result<(), String> { 11 | let args: Vec = env::args().collect(); 12 | 13 | if args.len() < 2 { 14 | help(); 15 | return Ok(()); 16 | } 17 | 18 | let path = &args[1]; 19 | 20 | let passphrase = if args.len() == 3 { 21 | Some(args[2].clone()) 22 | } else { 23 | None 24 | }; 25 | 26 | match PrivateKey::from_path_with_passphrase(path, passphrase) { 27 | Ok(c) => { 28 | println!("{:#}", c); 29 | Ok(()) 30 | } 31 | Err(e) => Err(format!("{}: Private key at {} not valid", e, &args[1])), 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/ssh-pubkey-from-cert.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | 4 | use sshcerts::ssh::Certificate; 5 | 6 | fn help() { 7 | println!("An SSH Cert Parser based on the sshcerts library"); 8 | println!("Usage: ssh-cert-info "); 9 | } 10 | 11 | fn main() { 12 | let args: Vec = env::args().collect(); 13 | 14 | if args.len() != 2 { 15 | return help(); 16 | } 17 | 18 | let contents = match fs::read_to_string(&args[1]) { 19 | Ok(c) => c, 20 | Err(e) => { 21 | println!("Error {} opening file: {}", e, &args[1]); 22 | return help(); 23 | } 24 | }; 25 | 26 | for (i, line) in contents.split('\n').into_iter().enumerate() { 27 | if line.is_empty() { 28 | break; 29 | } 30 | 31 | let cert = match Certificate::from_string(line) { 32 | Ok(c) => c, 33 | Err(e) => { 34 | println!("Line {}: Certificate not valid: {}", i, e); 35 | continue; 36 | } 37 | }; 38 | println!("{}", cert.key); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/ssh-pubkey-info.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use sshcerts::ssh::PublicKey; 4 | 5 | fn help() { 6 | println!("An SSH Private Key reader based on the sshcerts library"); 7 | println!("Usage: ssh-pkey-info "); 8 | } 9 | 10 | fn main() -> Result<(), String> { 11 | let args: Vec = env::args().collect(); 12 | 13 | if args.len() < 2 { 14 | help(); 15 | return Ok(()); 16 | } 17 | 18 | let path = &args[1]; 19 | 20 | match PublicKey::from_path(path) { 21 | Ok(c) => { 22 | println!( 23 | "256 SHA256:{} {}", 24 | c.fingerprint().hash, 25 | c.comment.unwrap_or_else(|| "no comment".to_string()) 26 | ); 27 | Ok(()) 28 | } 29 | Err(e) => Err(format!("{}: Private key at {} not valid", e, &args[1])), 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/yk-fingerprint.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use sshcerts::yubikey::piv::Yubikey; 4 | use sshcerts::yubikey::piv::{RetiredSlotId, SlotId}; 5 | 6 | use std::convert::TryFrom; 7 | 8 | fn help() { 9 | println!("Print the SSH key fingerprint for all Yubikey slots"); 10 | println!("Usage: yk-fingerprint"); 11 | } 12 | 13 | fn main() { 14 | if env::args().len() > 1 { 15 | return help(); 16 | } 17 | let mut yk = Yubikey::new().unwrap(); 18 | 19 | println!("Normal Slots:"); 20 | for slot in [0x9a, 0x9c, 0x9e, 0x9d, 0x9e, 0xf9] 21 | .iter() 22 | .map(|x| *x as u8) 23 | { 24 | let slot = SlotId::try_from(slot).unwrap(); 25 | match (yk.fetch_subject(&slot), yk.ssh_cert_fetch_pubkey(&slot)) { 26 | (Ok(subj), Ok(cert)) => { 27 | let attest = yk.fetch_attestation(&slot); 28 | println!( 29 | "\t{:?}:\t[Fingerprint: {}] [Attest: {}] Subject: [{}]", 30 | &slot, 31 | cert.fingerprint().hash, 32 | if attest.is_ok() { "Yes" } else { "No " }, 33 | subj 34 | ) 35 | } 36 | _ => println!("\t{:?}:\tNo cert found", slot), 37 | } 38 | } 39 | 40 | println!("Retired Slots:"); 41 | for slot in 0x82..0x96_u8 { 42 | let slot = SlotId::Retired(RetiredSlotId::try_from(slot).unwrap()); 43 | match (yk.fetch_subject(&slot), yk.ssh_cert_fetch_pubkey(&slot)) { 44 | (Ok(subj), Ok(cert)) => { 45 | let attest = yk.fetch_attestation(&slot); 46 | println!( 47 | "\t{:?}:\t[Fingerprint: {}] [Attest: {}] Subject: [{}]", 48 | slot, 49 | cert.fingerprint().hash, 50 | if attest.is_ok() { "Yes" } else { "No " }, 51 | subj, 52 | ) 53 | } 54 | _ => println!("\t{:?}:\tNo cert found", slot), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/yk-provision.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use clap::{Arg, Command}; 4 | 5 | use sshcerts::yubikey::piv::Yubikey; 6 | use sshcerts::yubikey::piv::{AlgorithmId, PinPolicy, RetiredSlotId, SlotId, TouchPolicy}; 7 | 8 | use std::convert::TryFrom; 9 | 10 | fn provision_new_key( 11 | slot: SlotId, 12 | subject: &str, 13 | pin: &str, 14 | mgm_key: &[u8], 15 | alg: &str, 16 | secure: bool, 17 | ) { 18 | let alg = match alg { 19 | "p256" => AlgorithmId::EccP256, 20 | _ => AlgorithmId::EccP384, 21 | }; 22 | 23 | println!( 24 | "Provisioning new {:?} key called [{}] in slot: {:?}", 25 | alg, subject, slot 26 | ); 27 | 28 | let policy = if secure { 29 | println!("You're creating a secure key that will require touch to use. Touch Yubikey to continue..."); 30 | TouchPolicy::Always 31 | } else { 32 | TouchPolicy::Never 33 | }; 34 | 35 | let mut yk = Yubikey::new().unwrap(); 36 | yk.unlock(pin.as_bytes(), mgm_key).unwrap(); 37 | match yk.provision(&slot, subject, alg, policy, PinPolicy::Never) { 38 | Ok(pk) => { 39 | println!("New hardware backed SSH Public Key: {}", pk); 40 | } 41 | Err(e) => panic!("Could not provision device with new key: {:?}", e), 42 | } 43 | } 44 | 45 | fn slot_parser(slot: &str) -> Option { 46 | // If first character is R, then we need to parse the nice 47 | // notation 48 | if (slot.len() == 2 || slot.len() == 3) && slot.starts_with('R') { 49 | let slot_value = slot[1..].parse::(); 50 | match slot_value { 51 | Ok(v) if v <= 20 => Some(SlotId::try_from(0x81_u8 + v).unwrap()), 52 | _ => None, 53 | } 54 | } else if slot.len() == 4 && slot.starts_with("0x") { 55 | let slot_value = hex::decode(&slot[2..]).unwrap()[0]; 56 | Some(SlotId::try_from(slot_value).unwrap()) 57 | } else { 58 | None 59 | } 60 | } 61 | 62 | fn slot_validator(slot: &str) -> Result<(), String> { 63 | match slot_parser(slot) { 64 | Some(_) => Ok(()), 65 | None => Err(String::from( 66 | "Provided slot was not valid. Should be R1 - R20 or a raw hex identifier", 67 | )), 68 | } 69 | } 70 | 71 | fn main() { 72 | env_logger::init(); 73 | let matches = Command::new("yk-provision") 74 | .version(env!("CARGO_PKG_VERSION")) 75 | .author("Mitchell Grenier ") 76 | .about("A tool to provision a new key on a yubikey") 77 | .arg( 78 | Arg::new("slot") 79 | .help("Numerical value for the slot on the yubikey to use for your private key") 80 | .long("slot") 81 | .short('s') 82 | .required(true) 83 | .validator(slot_validator) 84 | .takes_value(true), 85 | ) 86 | .arg( 87 | Arg::new("pin") 88 | .help("Provision this slot with a new private key. The pin number must be passed as parameter here") 89 | .default_value("123456") 90 | .long("pin") 91 | .short('p') 92 | .takes_value(true), 93 | ) 94 | .arg( 95 | Arg::new("subject") 96 | .help("They subject you would like to store in the certificate for later identification") 97 | .default_value("ykProvisioned") 98 | .long("subject") 99 | .short('j') 100 | .takes_value(true), 101 | ) 102 | .arg( 103 | Arg::new("management-key") 104 | .help("Provision this slot with a new private key. The pin number must be passed as parameter here") 105 | .default_value("010203040506070801020304050607080102030405060708") 106 | .long("mgmkey") 107 | .short('m') 108 | .takes_value(true), 109 | ) 110 | .arg( 111 | Arg::new("type") 112 | .help("Specify the type of key you want to provision (p256, p384)") 113 | .long("type") 114 | .short('t') 115 | .possible_value("p256") 116 | .possible_value("p384") 117 | .takes_value(true), 118 | ) 119 | .arg( 120 | Arg::new("require-touch") 121 | .help("Newly provisioned key requires touch for signing operations (touch cached for 15 seconds)") 122 | .long("require-touch") 123 | .short('r') 124 | ) 125 | .get_matches(); 126 | 127 | let slot = match matches.value_of("slot") { 128 | // We unwrap here because we have already run the validator above 129 | Some(x) => slot_parser(x).unwrap(), 130 | None => SlotId::Retired(RetiredSlotId::R17), 131 | }; 132 | 133 | let secure = matches.is_present("require-touch"); 134 | 135 | provision_new_key( 136 | slot, 137 | matches.value_of("subject").unwrap(), 138 | matches.value_of("pin").unwrap(), 139 | &hex::decode(matches.value_of("management-key").unwrap()).unwrap(), 140 | matches.value_of("type").unwrap_or("p384"), 141 | secure, 142 | ); 143 | } 144 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, io, string}; 2 | use crate::ssh::AllowedSignerParsingError; 3 | 4 | /// A type to represent the different kinds of errors. 5 | #[derive(Debug)] 6 | pub enum Error { 7 | /// There was an error reading from or writing to an external source 8 | Io(io::Error), 9 | /// Data was improperly encoded as base64 10 | Decode(base64::DecodeError), 11 | /// Data was not a valid UTF8 string. 12 | Utf8Error(string::FromUtf8Error), 13 | /// A certificate type that doesn't exist was requested. Should be either 1 or 2 14 | InvalidCertType(u32), 15 | /// The format of a certificate or data was incorrect/invalid 16 | InvalidFormat, 17 | /// The stream ended unexpectedly 18 | UnexpectedEof, 19 | /// The provided data was not a certificate 20 | NotCertificate, 21 | /// The requested signature or key was incompatible with what was previously specified 22 | /// or an x509 certificate contains a public key that is not compatible with SSH. 23 | KeyTypeMismatch, 24 | /// The certificate is not signed correctly and invalid 25 | InvalidSignature, 26 | /// A parsing error for one allowed signer 27 | InvalidAllowedSigner(AllowedSignerParsingError), 28 | /// A parsing error for a collection/file of allowed signers 29 | InvalidAllowedSigners(AllowedSignerParsingError, usize), 30 | /// A cryptographic operation failed. 31 | SigningError, 32 | /// An encrypted private key was provided with no decryption key 33 | EncryptedPrivateKey, 34 | /// An encrypted private key was supplied but the encryption method is not supported 35 | EncryptedPrivateKeyNotSupported, 36 | /// The key type is unknown 37 | UnknownKeyType(String), 38 | /// The curve in an ECC public/private key/signature is unknown 39 | UnknownCurve(String), 40 | /// A generic parsing error which occurs whenever data sent does not match the 41 | /// expected format 42 | ParsingError, 43 | /// An error occured in the yubikey module 44 | #[cfg(feature = "yubikey-support")] 45 | YubikeyPIVError(crate::yubikey::piv::Error), 46 | /// An error occured in the FIDO module 47 | #[cfg(any(feature = "fido-support", feature = "fido-lite"))] 48 | FidoError(crate::fido::Error), 49 | /// This occurs when you try to use a feature that could technically work 50 | /// but is currently unimplemented. 51 | Unsupported, 52 | } 53 | 54 | impl fmt::Display for Error { 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | match self { 57 | Error::Io(ref err) => err.fmt(f), 58 | Error::Decode(ref err) => err.fmt(f), 59 | Error::Utf8Error(ref err) => err.fmt(f), 60 | Error::InvalidFormat => write!(f, "Invalid format"), 61 | Error::InvalidCertType(v) => write!(f, "Invalid certificate type with value {}", v), 62 | Error::UnexpectedEof => write!(f, "Unexpected EOF reached while reading data"), 63 | Error::NotCertificate => write!(f, "Not a certificate"), 64 | Error::KeyTypeMismatch => write!(f, "Key type mismatch"), 65 | Error::InvalidSignature => write!(f, "Data is improperly signed"), 66 | Error::InvalidAllowedSigner(ref v) => write!(f, "Invalid allowed signer format: {}", v), 67 | Error::InvalidAllowedSigners(ref v, line) => write!(f, "Invalid allowed signer format on line {}: {}", line, v), 68 | Error::SigningError => write!(f, "Could not sign data"), 69 | Error::EncryptedPrivateKey => write!(f, "Encountered encrypted private key with no decryption key"), 70 | Error::EncryptedPrivateKeyNotSupported => write!(f, "This method of private key encryption is not supported or sshcerts was not compiled with encrypted private key support"), 71 | Error::UnknownKeyType(ref v) => write!(f, "Unknown key type {}", v), 72 | Error::UnknownCurve(ref v) => write!(f, "Unknown curve {}", v), 73 | Error::ParsingError => write!(f, "Could not parse the data provided"), 74 | #[cfg(feature = "yubikey-support")] 75 | Error::YubikeyPIVError(ref e) => write!(f, "{}", e), 76 | #[cfg(any(feature = "fido-support", feature = "fido-lite"))] 77 | Error::FidoError(ref e) => write!(f, "{}", e), 78 | Error::Unsupported => write!(f, "Functionality either not implemented or cannot be technically supported"), 79 | } 80 | } 81 | } 82 | 83 | impl std::error::Error for Error { 84 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 85 | match self { 86 | Error::Io(ref e) => e.source(), 87 | Error::Decode(ref e) => e.source(), 88 | Error::Utf8Error(ref e) => e.source(), 89 | _ => None, 90 | } 91 | } 92 | } 93 | 94 | impl From for Error { 95 | fn from(error: io::Error) -> Self { 96 | Error::Io(error) 97 | } 98 | } 99 | 100 | impl From for Error { 101 | fn from(error: base64::DecodeError) -> Error { 102 | Error::Decode(error) 103 | } 104 | } 105 | 106 | impl From for Error { 107 | fn from(error: string::FromUtf8Error) -> Error { 108 | Error::Utf8Error(error) 109 | } 110 | } 111 | 112 | impl From for Error { 113 | fn from(_: ring::error::Unspecified) -> Error { 114 | Error::InvalidSignature 115 | } 116 | } 117 | 118 | #[cfg(feature = "rsa-signing")] 119 | impl From for Error { 120 | fn from(_e: simple_asn1::ASN1EncodeErr) -> Self { 121 | Error::InvalidFormat 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/fido/generate/ctap2_hid.rs: -------------------------------------------------------------------------------- 1 | use ctap_hid_fido2::{ 2 | fidokey::make_credential::{CredentialSupportedKeyType, MakeCredentialArgsBuilder}, 3 | verifier, Cfg, FidoKeyHid, HidParam, 4 | }; 5 | use ring::digest; 6 | 7 | use crate::{ 8 | error::Error, 9 | fido::{ 10 | generate::{FIDOSSHKey, U2FAttestation}, 11 | AuthData, 12 | }, 13 | ssh::{Ed25519SkPrivateKey, KeyType, PrivateKeyKind}, 14 | PrivateKey, 15 | }; 16 | 17 | use crate::fido::Error as FidoError; 18 | 19 | /// Generate a new SSH key on a FIDO/U2F device 20 | pub fn generate_new_ssh_key( 21 | application: &str, 22 | comment: &str, 23 | pin: Option, 24 | device_path: Option, 25 | ) -> Result { 26 | let device = if let Some(path) = &device_path { 27 | FidoKeyHid::new(&[HidParam::Path(path.to_string())], &Cfg::init()) 28 | } else { 29 | let fido_devices: Vec = ctap_hid_fido2::get_fidokey_devices() 30 | .into_iter() 31 | .map(|x| x.param) 32 | .collect(); 33 | FidoKeyHid::new(&fido_devices, &Cfg::init()) 34 | }; 35 | 36 | let challenge = verifier::create_challenge(); 37 | let args = MakeCredentialArgsBuilder::new(&application, &challenge) 38 | .key_type(CredentialSupportedKeyType::Ed25519); 39 | 40 | let args = if let Some(pin) = &pin { 41 | args.pin(pin) 42 | } else { 43 | args.without_pin_and_uv() 44 | }; 45 | 46 | let device = device.map_err(|e| Error::FidoError(FidoError::Unknown(e.to_string())))?; 47 | let att = device 48 | .make_credential_with_args(&args.build()) 49 | .map_err(|e| Error::FidoError(FidoError::Unknown(e.to_string())))?; 50 | 51 | let mut ret = 0x0; 52 | if att.flags_user_present_result { 53 | ret = ret | 0x01; 54 | } 55 | if att.flags_user_verified_result { 56 | ret = ret | 0x04; 57 | } 58 | if att.flags_attested_credential_data_included { 59 | ret = ret | 0x40; 60 | } 61 | if att.flags_extension_data_included { 62 | ret = ret | 0x80; 63 | } 64 | 65 | let key_type = KeyType::from_name("sk-ssh-ed25519@openssh.com")?; 66 | let kind = PrivateKeyKind::Ed25519Sk(Ed25519SkPrivateKey { 67 | flags: ret, 68 | handle: att.credential_descriptor.id.clone(), 69 | reserved: vec![], 70 | pin, 71 | device_path, 72 | }); 73 | 74 | let auth_data = AuthData::parse(&att.auth_data)?; 75 | 76 | let private_key = PrivateKey { 77 | key_type, 78 | kind, 79 | pubkey: auth_data.ssh_public_key(application)?, 80 | magic: 0x0, 81 | comment: comment.to_string(), 82 | }; 83 | 84 | let intermediate = if att.attstmt_x5c.is_empty() { 85 | vec![] 86 | } else { 87 | att.attstmt_x5c[0].clone() 88 | }; 89 | 90 | // Take a SHA256 of the challenge because that's what's part of 91 | // the signed data 92 | let challenge = digest::digest(&digest::SHA256, &challenge) 93 | .as_ref() 94 | .to_vec(); 95 | 96 | let attestation = U2FAttestation { 97 | auth_data: att.auth_data, 98 | auth_data_sig: att.attstmt_sig, 99 | intermediate, 100 | challenge, 101 | alg: att.attstmt_alg, 102 | }; 103 | 104 | let _ = attestation.verify()?; 105 | 106 | Ok(FIDOSSHKey { 107 | private_key, 108 | attestation, 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /src/fido/generate/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::PrivateKey; 2 | 3 | #[cfg(any(feature = "fido-support"))] 4 | mod ctap2_hid; 5 | #[cfg(any(feature = "fido-support"))] 6 | pub use ctap2_hid::generate_new_ssh_key; 7 | 8 | #[cfg(any(feature = "fido-support-mozilla"))] 9 | mod mozilla; 10 | #[cfg(any(feature = "fido-support-mozilla"))] 11 | pub use mozilla::generate_new_ssh_key; 12 | 13 | use super::verification::{verify_auth_data, ValidAttestation}; 14 | 15 | /// The attestation data, signature, and chain for a generated SSH key 16 | #[derive(Debug)] 17 | pub struct U2FAttestation { 18 | /// A blob that contains all public information that we can also verify with 19 | /// the attestation chain 20 | pub auth_data: Vec, 21 | /// The signature over the hash of the auth data 22 | pub auth_data_sig: Vec, 23 | /// The certificate that generated the signature over the auth data 24 | pub intermediate: Vec, 25 | /// The challenge that generated and is included in the signature 26 | pub challenge: Vec, 27 | /// The algorithm that was used to generate the signature (COSE value) 28 | pub alg: i32, 29 | } 30 | 31 | /// A generated SSH key that was generated with a FIDO/U2F key 32 | #[derive(Debug)] 33 | pub struct FIDOSSHKey { 34 | /// Private key handle to the new SSH Key on the hardware token 35 | pub private_key: PrivateKey, 36 | /// The U2F attestation data 37 | pub attestation: U2FAttestation, 38 | } 39 | 40 | impl U2FAttestation { 41 | /// Verify the attestation data, signature, and chain are valid 42 | pub fn verify(&self) -> Result { 43 | verify_auth_data( 44 | &self.auth_data, 45 | &self.auth_data_sig, 46 | &self.challenge, 47 | self.alg, 48 | &self.intermediate, 49 | None, 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/fido/generate/mozilla.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::Error, 3 | fido::{ 4 | generate::{FIDOSSHKey, U2FAttestation}, 5 | AuthData, 6 | }, 7 | ssh::{Ed25519SkPrivateKey, KeyType, PrivateKeyKind}, 8 | PrivateKey, 9 | }; 10 | 11 | use ring::digest; 12 | use ring::rand::{SecureRandom, SystemRandom}; 13 | 14 | use crate::fido::Error as FidoError; 15 | 16 | use authenticator::ctap2::attestation::AttestationStatement; 17 | use authenticator::ctap2::server::PublicKeyCredentialParameters; 18 | use authenticator::ctap2::server::RelyingParty; 19 | use authenticator::ctap2::server::ResidentKeyRequirement; 20 | use authenticator::ctap2::server::UserVerificationRequirement; 21 | use authenticator::statecallback::StateCallback; 22 | use authenticator::Pin; 23 | use authenticator::StatusUpdate; 24 | use authenticator::{ 25 | authenticatorservice::AuthenticatorService, ctap2::server::AuthenticationExtensionsClientInputs, 26 | }; 27 | use authenticator::{ 28 | authenticatorservice::RegisterArgs, ctap2::server::PublicKeyCredentialUserEntity, 29 | }; 30 | use authenticator::{crypto::COSEAlgorithm, StatusPinUv}; 31 | 32 | use std::{ 33 | sync::mpsc::{channel, RecvError}, 34 | thread, 35 | }; 36 | 37 | /// Generate a new SSH key on a FIDO/U2F device 38 | pub fn generate_new_ssh_key( 39 | application: &str, 40 | comment: &str, 41 | pin: Option, 42 | _: Option, 43 | ) -> Result { 44 | let mut manager = match AuthenticatorService::new() { 45 | Ok(m) => m, 46 | Err(e) => return Err(Error::FidoError(FidoError::Unknown(e.to_string()))), 47 | }; 48 | manager.add_u2f_usb_hid_platform_transports(); 49 | 50 | // This forms the challenge 51 | let mut client_data = [0u8; 32]; 52 | // Fill it with random data because we don't support taking in 53 | // challenge data at this point. 54 | SystemRandom::new().fill(&mut client_data).unwrap(); 55 | 56 | // Hash the data because that is what will actually be signed 57 | let client_data_digest = digest::digest(&digest::SHA256, &client_data); 58 | let mut client_data_hash = [0u8; 32]; 59 | client_data_hash.copy_from_slice(client_data_digest.as_ref()); 60 | 61 | let origin = application.to_string(); 62 | let ctap_args = RegisterArgs { 63 | client_data_hash, 64 | relying_party: RelyingParty { 65 | id: origin.clone(), 66 | name: None, 67 | }, 68 | origin, 69 | user: PublicKeyCredentialUserEntity { 70 | id: application.as_bytes().to_vec(), 71 | name: Some(application.to_string()), 72 | display_name: None, 73 | }, 74 | pub_cred_params: vec![PublicKeyCredentialParameters { 75 | alg: COSEAlgorithm::EDDSA, 76 | }], 77 | exclude_list: vec![], 78 | user_verification_req: UserVerificationRequirement::Discouraged, 79 | resident_key_req: ResidentKeyRequirement::Discouraged, 80 | extensions: AuthenticationExtensionsClientInputs::default(), 81 | pin: pin.as_ref().map(|x| Pin::new(x)), 82 | use_ctap1_fallback: false, 83 | }; 84 | 85 | let (status_tx, status_rx) = channel::(); 86 | 87 | let (register_tx, register_rx) = channel(); 88 | let (error_tx, error_rx) = channel::(); 89 | let callback = StateCallback::new(Box::new(move |rv| { 90 | let _ = register_tx.send(rv); 91 | })); 92 | 93 | if let Err(e) = manager.register(15_000, ctap_args, status_tx.clone(), callback) { 94 | return Err(Error::FidoError(FidoError::Unknown(e.to_string()))); 95 | }; 96 | 97 | thread::spawn(move || loop { 98 | let msg = status_rx.recv(); 99 | match msg { 100 | Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(_))) => { 101 | let _ = error_tx.send(FidoError::PinRequired); 102 | return; 103 | } 104 | Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => { 105 | let _ = error_tx.send(FidoError::KeyLocked); 106 | return; 107 | } 108 | Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => { 109 | let _ = error_tx.send(FidoError::KeyBlocked); 110 | return; 111 | } 112 | Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(_sender, attempts))) => { 113 | let _ = error_tx.send(FidoError::InvalidPin(attempts)); 114 | return; 115 | } 116 | Ok(_) => (), 117 | Err(RecvError) => { 118 | return; 119 | } 120 | } 121 | }); 122 | 123 | let register_result = register_rx 124 | .recv() 125 | .map_err(|e| Error::FidoError(FidoError::Unknown(e.to_string())))?; 126 | let attestation_object = match register_result { 127 | Ok(attestation) => attestation, 128 | Err(e) => { 129 | if let Ok(error) = error_rx.recv() { 130 | return Err(Error::FidoError(error)); 131 | } else { 132 | return Err(Error::FidoError(FidoError::Unknown(e.to_string()))); 133 | } 134 | } 135 | }; 136 | 137 | let raw_auth_data = attestation_object.att_obj.auth_data.to_vec(); 138 | 139 | let auth_data = AuthData::parse(&raw_auth_data)?; 140 | 141 | let key_type = KeyType::from_name("sk-ssh-ed25519@openssh.com")?; 142 | let kind = PrivateKeyKind::Ed25519Sk(Ed25519SkPrivateKey { 143 | flags: auth_data.flags, 144 | handle: auth_data.credential_id.clone(), 145 | reserved: vec![], 146 | pin, 147 | device_path: None, 148 | }); 149 | 150 | let private_key = PrivateKey { 151 | key_type, 152 | kind, 153 | pubkey: auth_data.ssh_public_key(application)?, 154 | magic: 0x0, 155 | comment: comment.to_string(), 156 | }; 157 | 158 | let (auth_data_sig, intermediate_certs, alg) = match attestation_object.att_obj.att_stmt { 159 | AttestationStatement::Packed(packed) => ( 160 | packed.sig.0.to_vec(), 161 | packed.attestation_cert, 162 | packed.alg as i32, 163 | ), 164 | _ => { 165 | return Err(Error::FidoError(FidoError::Unknown( 166 | "Wrong attestation format".to_owned(), 167 | ))) 168 | } 169 | }; 170 | 171 | let intermediate = if intermediate_certs.is_empty() { 172 | vec![] 173 | } else { 174 | intermediate_certs[0].0.clone() 175 | }; 176 | 177 | let attestation = U2FAttestation { 178 | auth_data: raw_auth_data, 179 | auth_data_sig, 180 | intermediate, 181 | challenge: client_data_hash.into(), 182 | alg, 183 | }; 184 | 185 | let _ = attestation.verify()?; 186 | 187 | Ok(FIDOSSHKey { 188 | private_key, 189 | attestation, 190 | }) 191 | } 192 | -------------------------------------------------------------------------------- /src/fido/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "fido-support", feature = "fido-support-mozilla"))] 2 | /// For generating new SSH keys on FIDO devices 3 | pub mod generate; 4 | 5 | /// For handling FIDO related errors 6 | #[derive(Debug)] 7 | pub enum Error { 8 | /// An operation errored because the incorrect pin was provided 9 | InvalidPin(Option), 10 | /// An operation failed because a pin was required but not provided 11 | PinRequired, 12 | /// The key is temporarily locked because the incorrect pin was provided too many times 13 | KeyLocked, 14 | /// The key is permanently locked because the incorrect pin was provided too many times 15 | KeyBlocked, 16 | /// A CBOR formatting error occured 17 | CborFormat(String), 18 | /// An unknown error occured 19 | Unknown(String), 20 | } 21 | 22 | impl std::fmt::Display for Error { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | let msg = match self { 25 | Self::InvalidPin(attempts) => { 26 | if let Some(attempts) = attempts { 27 | format!("Invalid pin: {} attempts remaining", attempts) 28 | } else { 29 | "Invalid pin".to_owned() 30 | } 31 | } 32 | Self::PinRequired => String::from("Pin required for operation"), 33 | Self::KeyLocked => String::from("Key locked"), 34 | Self::KeyBlocked => String::from("Key blocked"), 35 | Self::CborFormat(s) => s.to_string(), 36 | Self::Unknown(s) => s.to_string(), 37 | }; 38 | write!(f, "s{}", msg) 39 | } 40 | } 41 | 42 | #[cfg(any( 43 | feature = "fido-support", 44 | feature = "fido-support-mozilla", 45 | feature = "fido-lite" 46 | ))] 47 | /// For parsing FIDO related data 48 | pub mod parsing; 49 | 50 | /// Contains utility functions for dealing with FIDO keys 51 | #[cfg(any(feature = "fido-support", feature = "fido-support-mozilla"))] 52 | mod utils; 53 | #[cfg(any(feature = "fido-support", feature = "fido-support-mozilla"))] 54 | pub use utils::*; 55 | 56 | #[cfg(any( 57 | feature = "fido-support", 58 | feature = "fido-support-mozilla", 59 | feature = "fido-lite" 60 | ))] 61 | pub use parsing::{AuthData, CoseKey}; 62 | 63 | /// For handling signing operations with FIDO keys 64 | #[cfg(any(feature = "fido-support", feature = "fido-support-mozilla"))] 65 | pub mod signing; 66 | 67 | #[cfg(any( 68 | feature = "fido-support", 69 | feature = "fido-support-mozilla", 70 | feature = "fido-lite" 71 | ))] 72 | /// For code relating to the verification of FIDO certificate chains and 73 | /// certificate parsing 74 | pub mod verification; 75 | 76 | #[cfg(any(feature = "fido-support", feature = "fido-support-mozilla"))] 77 | pub use generate::FIDOSSHKey; 78 | -------------------------------------------------------------------------------- /src/fido/signing/ctap2_hid.rs: -------------------------------------------------------------------------------- 1 | use crate::ssh::PrivateKeyKind; 2 | use crate::ssh::PublicKeyKind; 3 | use crate::utils::format_signature_for_ssh; 4 | use crate::PrivateKey; 5 | 6 | use ctap_hid_fido2::{fidokey::GetAssertionArgsBuilder, Cfg, FidoKeyHid, HidParam}; 7 | 8 | /// Sign data with a SK type private key 9 | pub fn sign_with_private_key(private_key: &PrivateKey, challenge: &[u8]) -> Option> { 10 | let (handle, pin, device_path): (&Vec, _, _) = match &private_key.kind { 11 | PrivateKeyKind::EcdsaSk(key) => ( 12 | key.handle.as_ref(), 13 | key.pin.as_ref(), 14 | key.device_path.as_ref(), 15 | ), 16 | PrivateKeyKind::Ed25519Sk(key) => ( 17 | key.handle.as_ref(), 18 | key.pin.as_ref(), 19 | key.device_path.as_ref(), 20 | ), 21 | _ => return None, 22 | }; 23 | 24 | // It should be safe to unwrap here because we've already determined 25 | // the PrivateKey is of SK type 26 | let sk_application = match &private_key.pubkey.kind { 27 | PublicKeyKind::Ed25519(key) => key.sk_application.as_ref(), 28 | PublicKeyKind::Ecdsa(key) => key.sk_application.as_ref(), 29 | _ => return None, 30 | }; 31 | 32 | let sk_application = match sk_application { 33 | Some(sk) => sk, 34 | None => return None, 35 | }; 36 | 37 | let device = if let Some(path) = &device_path { 38 | FidoKeyHid::new(&[HidParam::Path(path.to_string())], &Cfg::init()) 39 | } else { 40 | let fido_devices: Vec = ctap_hid_fido2::get_fidokey_devices() 41 | .into_iter() 42 | .map(|x| x.param) 43 | .collect(); 44 | FidoKeyHid::new(&fido_devices, &Cfg::init()) 45 | }; 46 | 47 | let device = device.ok()?; 48 | 49 | let args = GetAssertionArgsBuilder::new(&sk_application, &challenge).credential_id(handle); 50 | 51 | let args = if let Some(pin) = pin { 52 | args.pin(pin) 53 | } else { 54 | args.without_pin_and_uv() 55 | }; 56 | 57 | let mut assert = device.get_assertion_with_args(&args.build()).ok()?; 58 | let assert = assert.pop()?; 59 | 60 | let signature = &assert.signature; 61 | let mut format = format_signature_for_ssh(&private_key.pubkey, &signature)?; 62 | format.push(assert.flags.as_u8()); 63 | format.extend_from_slice(&assert.sign_count.to_be_bytes()); 64 | 65 | Some(format) 66 | } 67 | -------------------------------------------------------------------------------- /src/fido/signing/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "fido-support"))] 2 | mod ctap2_hid; 3 | #[cfg(any(feature = "fido-support"))] 4 | pub use ctap2_hid::sign_with_private_key; 5 | 6 | #[cfg(any(feature = "fido-support-mozilla"))] 7 | mod mozilla; 8 | #[cfg(any(feature = "fido-support-mozilla"))] 9 | pub use mozilla::sign_with_private_key; 10 | -------------------------------------------------------------------------------- /src/fido/signing/mozilla.rs: -------------------------------------------------------------------------------- 1 | use crate::ssh::PrivateKeyKind; 2 | use crate::ssh::PublicKeyKind; 3 | use crate::utils::format_signature_for_ssh; 4 | use crate::PrivateKey; 5 | 6 | use ring::digest; 7 | 8 | use authenticator::{ 9 | authenticatorservice::{AuthenticatorService, SignArgs}, 10 | ctap2::server::*, 11 | errors::{AuthenticatorError, PinError}, 12 | statecallback::StateCallback, 13 | Pin, StatusUpdate, 14 | }; 15 | use std::sync::mpsc::channel; 16 | 17 | /// Sign data with a SK type private key 18 | pub fn sign_with_private_key(private_key: &PrivateKey, challenge: &[u8]) -> Option> { 19 | let (handle, pin): (&[u8], _) = match &private_key.kind { 20 | PrivateKeyKind::EcdsaSk(key) => (key.handle.as_ref(), key.pin.as_ref()), 21 | PrivateKeyKind::Ed25519Sk(key) => (key.handle.as_ref(), key.pin.as_ref()), 22 | _ => return None, 23 | }; 24 | 25 | let pin: Option = pin.map(|x| Pin::new(x)); 26 | 27 | // It should be safe to unwrap here because we've already determined 28 | // the PrivateKey is of SK type 29 | let sk_application = match &private_key.pubkey.kind { 30 | PublicKeyKind::Ed25519(key) => key.sk_application.as_ref().unwrap(), 31 | PublicKeyKind::Ecdsa(key) => key.sk_application.as_ref().unwrap(), 32 | _ => return None, 33 | }; 34 | 35 | let allow_list = vec![PublicKeyCredentialDescriptor { 36 | id: handle.to_vec(), 37 | transports: vec![Transport::USB], 38 | }]; 39 | 40 | let chall_bytes: [u8; 32] = digest::digest(&digest::SHA256, challenge) 41 | .as_ref() 42 | .try_into() 43 | .unwrap(); // This should be safe as SHA256 will always be 32 bytes 44 | 45 | let ctap_args = SignArgs { 46 | client_data_hash: chall_bytes, 47 | origin: format!(""), 48 | relying_party_id: sk_application.clone(), 49 | allow_list, 50 | extensions: AuthenticationExtensionsClientInputs::default(), 51 | pin, 52 | user_presence_req: true, 53 | use_ctap1_fallback: false, 54 | user_verification_req: UserVerificationRequirement::Discouraged, 55 | }; 56 | 57 | let mut manager = match AuthenticatorService::new() { 58 | Ok(m) => m, 59 | Err(_) => return None, 60 | }; 61 | manager.add_u2f_usb_hid_platform_transports(); 62 | 63 | let (sign_tx, sign_rx) = channel(); 64 | let callback = StateCallback::new(Box::new(move |rv| { 65 | sign_tx.send(rv).unwrap(); 66 | })); 67 | 68 | let (status_tx, _status_rx) = channel::(); 69 | if let Err(e) = manager.sign( 70 | 15_000, 71 | ctap_args.clone().into(), 72 | status_tx.clone(), 73 | callback, 74 | ) { 75 | panic!("Couldn't sign: {:?}", e); 76 | } 77 | let sign_result = sign_rx 78 | .recv() 79 | .expect("Problem receiving, unable to continue"); 80 | 81 | let assertion = match sign_result { 82 | Ok(assertion_object) => assertion_object.assertion, 83 | Err(AuthenticatorError::PinError(PinError::PinRequired)) => { 84 | println!("PIN needed but not provided!"); 85 | return None; 86 | } 87 | Err(e) => { 88 | println!("Some other error: {}", e); 89 | return None; 90 | } 91 | }; 92 | let mut format = format_signature_for_ssh(&private_key.pubkey, &assertion.signature)?; 93 | format.push(assertion.auth_data.flags.bits()); 94 | format.extend_from_slice(&assertion.auth_data.counter.to_be_bytes()); 95 | Some(format) 96 | } 97 | -------------------------------------------------------------------------------- /src/fido/utils/ctap2_hid.rs: -------------------------------------------------------------------------------- 1 | use super::FidoDeviceDescriptor; 2 | use crate::error::Error; 3 | use crate::fido::Error as FidoError; 4 | 5 | #[cfg(feature = "fido-support")] 6 | /// For listing all connected FIDO2 devices. The pathes returned 7 | /// in the descriptors can be used in private keys to route calls 8 | /// to the correct device. 9 | pub fn list_fido_devices() -> Vec { 10 | use ctap_hid_fido2::HidParam; 11 | 12 | ctap_hid_fido2::get_fidokey_devices() 13 | .into_iter() 14 | .filter_map(|x| match x.param { 15 | HidParam::Path(p) => Some(FidoDeviceDescriptor { 16 | path: p, 17 | product_string: x.product_string, 18 | }), 19 | _ => None, 20 | }) 21 | .collect() 22 | } 23 | 24 | #[cfg(feature = "fido-support")] 25 | /// Determine if the given device path requires a pin 26 | pub fn device_requires_pin(path: &str) -> Result { 27 | use ctap_hid_fido2::{fidokey::get_info::InfoOption, Cfg, FidoKeyHid, HidParam}; 28 | 29 | let device = match FidoKeyHid::new(&[HidParam::Path(path.to_string())], &Cfg::init()) { 30 | Ok(dev) => dev, 31 | Err(e) => return Err(Error::FidoError(FidoError::Unknown(e.to_string()))), 32 | }; 33 | 34 | match device.enable_info_option(&InfoOption::ClientPin) { 35 | Ok(Some(result)) => Ok(result), 36 | Ok(None) => { 37 | return Err(Error::FidoError(FidoError::Unknown( 38 | "Could not get pin status".to_owned(), 39 | ))) 40 | } 41 | Err(e) => return Err(Error::FidoError(FidoError::Unknown(e.to_string()))), 42 | } 43 | } 44 | 45 | #[cfg(feature = "fido-support")] 46 | /// Determine if the given device path requires a pin 47 | pub fn device_pin_retries(path: &str) -> Result { 48 | use ctap_hid_fido2::{Cfg, FidoKeyHid, HidParam}; 49 | 50 | let device = match FidoKeyHid::new(&[HidParam::Path(path.to_string())], &Cfg::init()) { 51 | Ok(dev) => dev, 52 | Err(e) => return Err(Error::FidoError(FidoError::Unknown(e.to_string()))), 53 | }; 54 | 55 | device 56 | .get_pin_retries() 57 | .map_err(|e| Error::FidoError(FidoError::Unknown(e.to_string()))) 58 | } 59 | -------------------------------------------------------------------------------- /src/fido/utils/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "fido-support"))] 2 | mod ctap2_hid; 3 | #[cfg(any(feature = "fido-support"))] 4 | pub use ctap2_hid::*; 5 | 6 | #[cfg(any(feature = "fido-support-mozilla"))] 7 | mod mozilla; 8 | #[cfg(any(feature = "fido-support-mozilla"))] 9 | pub use mozilla::*; 10 | 11 | /// Defines a FIDO device with name and path. The path can be 12 | /// used in PrivateKey to route the request to a particular device. 13 | /// 14 | /// These paths are only valid while a device is connected continuously. 15 | /// Disconnected and reconnecting will result in a new path and a key 16 | /// must be updated accordingly. 17 | #[derive(Clone, Debug, PartialEq, PartialOrd)] 18 | pub struct FidoDeviceDescriptor { 19 | /// Product name that the device reports 20 | pub product_string: String, 21 | /// Path to be used for connecting to this particular device 22 | pub path: String, 23 | } 24 | -------------------------------------------------------------------------------- /src/fido/utils/mozilla.rs: -------------------------------------------------------------------------------- 1 | use super::FidoDeviceDescriptor; 2 | use crate::error::Error; 3 | use crate::fido::Error as FidoError; 4 | 5 | /// List the connected FIDO devices. This returns a empty 6 | /// vector as Mozilla does not support listing devices. 7 | pub fn list_fido_devices() -> Vec { 8 | vec![] 9 | } 10 | 11 | /// Determine if the given device path requires a pin 12 | pub fn device_requires_pin(_: &str) -> Result { 13 | Err(Error::FidoError(FidoError::Unknown( 14 | "Not currently supported directly fetching key PIN status".to_owned(), 15 | ))) 16 | } 17 | 18 | /// Determine how many pin retries are left on a device 19 | pub fn device_pin_retries(_: &str) -> Result { 20 | return Err(Error::FidoError(FidoError::Unknown( 21 | "Not currently supported directly fetching key PIN status".to_owned(), 22 | ))); 23 | } 24 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! The 'sshcerts` crate provides types and methods for parsing 3 | //! OpenSSH keys, and parsing, verifying, and creating SSH certificates. 4 | //! 5 | //! The following OpenSSH key types are supported. 6 | //! 7 | //! - RSA 8 | //! - ECDSA 9 | //! - ED25519 10 | //! 11 | //! The following OpenSSH certificate types are supported. 12 | //! 13 | //! - ssh-rsa-cert-v01@openssh.com 14 | //! - ecdsa-sha2-nistp256-cert-v01@openssh.com 15 | //! - ecdsa-sha2-nistp384-cert-v01@openssh.com 16 | //! - ssh-ed25519-cert-v01@openssh.com 17 | //! 18 | //! ### Why no ecdsa-sha2-nistp521-cert-v01@openssh.com? 19 | //! That curve is not supported on a standard yubikey nor in `ring`. This 20 | //! means I cannot implement any signing or verification routines. If this 21 | //! changes, I will update this crate with support. 22 | //! 23 | //! The crate also provides functionality for provision key slots on 24 | //! Yubikeys to handle signing operations. This is provided in the 25 | //! optional `yubikey` submodule 26 | //! 27 | 28 | #![deny( 29 | anonymous_parameters, 30 | missing_debug_implementations, 31 | missing_docs, 32 | nonstandard_style, 33 | rust_2018_idioms, 34 | single_use_lifetimes, 35 | trivial_casts, 36 | trivial_numeric_casts, 37 | unreachable_pub, 38 | unused_extern_crates, 39 | unused_qualifications, 40 | warnings 41 | )] 42 | 43 | /// The `sshcerts` error enum 44 | pub mod error; 45 | 46 | type Result = std::result::Result; 47 | 48 | pub use ssh::{CertType, Certificate, PrivateKey, PublicKey}; 49 | 50 | /// Functions or structs for dealing with SSH Certificates. 51 | /// Parsing, and creating certs happens here. 52 | pub mod ssh; 53 | 54 | /// Utility functions for dealing with SSH certificates, signatures 55 | /// or conversions 56 | pub mod utils; 57 | 58 | /// Functions for dealing with Yubikey signing. 59 | /// Also contains an SSH submodule containing helper functions to generate 60 | /// SSH encoded versions of it's normal functions. 61 | #[cfg(any(feature = "yubikey-lite", feature = "yubikey-support"))] 62 | pub mod yubikey; 63 | 64 | /// Contains some helper functions for pulling SSH public keys from x509 65 | /// certificates and CSRs. Is enabled whenever yubikey_support is enabled 66 | /// because some functionality is currently shared. 67 | #[cfg(any(feature = "x509-support", feature = "yubikey-support"))] 68 | pub mod x509; 69 | 70 | /// For dealing with FIDO/U2F tokens such as generating new SSH keys 71 | #[cfg(any(feature = "fido-lite", feature = "fido-support"))] 72 | pub mod fido; 73 | -------------------------------------------------------------------------------- /src/ssh/keytype.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::{error::Error, Result}; 4 | 5 | use zeroize::Zeroize; 6 | 7 | /// A type which represents the various kinds of keys. 8 | #[derive(Debug, PartialEq, Eq, Clone)] 9 | pub enum KeyTypeKind { 10 | /// Represents an RSA key type. 11 | Rsa, 12 | 13 | /// Represents an ED25519 key type. 14 | Ed25519, 15 | 16 | /// Represents an ECDSA key type. 17 | Ecdsa, 18 | 19 | /// Represents an RSA certificate key type. 20 | RsaCert, 21 | 22 | /// Represents an ED25519 certificate key type. 23 | Ed25519Cert, 24 | 25 | /// Represents an ECDSA certificate key type. 26 | EcdsaCert, 27 | } 28 | 29 | /// `KeyType` represents the type of an OpenSSH key. 30 | #[derive(Debug, PartialEq, Eq, Clone)] 31 | pub struct KeyType { 32 | /// Name of the key type. 33 | pub name: &'static str, 34 | 35 | /// Short name of the key type. 36 | pub short_name: &'static str, 37 | 38 | /// Indicates whether the key type represents a certificate or not. 39 | pub is_cert: bool, 40 | 41 | /// Indicates whether the key type represents a hardware key handle or not. 42 | pub is_sk: bool, 43 | 44 | /// Kind of the key type. 45 | pub kind: KeyTypeKind, 46 | 47 | /// The cert-less equivalent to a certified key type. 48 | pub plain: &'static str, 49 | } 50 | 51 | /// Represents the different kinds of supported curves. 52 | #[derive(Debug, PartialEq, Eq, Clone, Zeroize)] 53 | pub enum CurveKind { 54 | /// Represents a NIST P-256 curve. 55 | Nistp256, 56 | 57 | /// Represents a NIST P-384 curve. 58 | Nistp384, 59 | 60 | /// Represents a NIST P-521 curve. 61 | Nistp521, 62 | } 63 | 64 | /// A type which represents a cryptographic curve. 65 | #[derive(Debug, PartialEq, Eq, Clone, Zeroize)] 66 | pub struct Curve { 67 | /// The curve kind. 68 | pub kind: CurveKind, 69 | 70 | #[zeroize(skip)] 71 | /// Curve identifier. 72 | pub identifier: &'static str, 73 | } 74 | 75 | impl Curve { 76 | /// Creates a new `Curve` from the given identifier. 77 | /// 78 | /// # Example 79 | /// ```rust 80 | /// # use sshcerts::ssh::{Curve, CurveKind}; 81 | /// let curve = Curve::from_identifier("nistp256").unwrap(); 82 | /// assert_eq!(curve.kind, CurveKind::Nistp256); 83 | /// ``` 84 | pub fn from_identifier(id: &str) -> Result { 85 | let curve = match id { 86 | "nistp256" => Curve { 87 | kind: CurveKind::Nistp256, 88 | identifier: "nistp256", 89 | }, 90 | "nistp384" => Curve { 91 | kind: CurveKind::Nistp384, 92 | identifier: "nistp384", 93 | }, 94 | "nistp521" => Curve { 95 | kind: CurveKind::Nistp521, 96 | identifier: "nistp521", 97 | }, 98 | _ => return Err(Error::UnknownCurve(id.to_string())), 99 | }; 100 | 101 | Ok(curve) 102 | } 103 | } 104 | 105 | impl KeyType { 106 | /// Creates a new `KeyType` from a given name. 107 | /// 108 | /// # Example 109 | /// ```rust 110 | /// # use sshcerts::ssh::{KeyType, KeyTypeKind}; 111 | /// let kt = KeyType::from_name("ssh-rsa").unwrap(); 112 | /// assert_eq!(kt.kind, KeyTypeKind::Rsa); 113 | /// ``` 114 | pub fn from_name(name: &str) -> Result { 115 | let kt = match name { 116 | "ssh-rsa" => KeyType { 117 | name: "ssh-rsa", 118 | plain: "ssh-rsa", 119 | short_name: "RSA", 120 | is_cert: false, 121 | is_sk: false, 122 | kind: KeyTypeKind::Rsa, 123 | }, 124 | "rsa-sha2-256" => KeyType { 125 | name: "rsa-sha2-256", 126 | plain: "ssh-rsa", 127 | short_name: "RSA", 128 | is_cert: false, 129 | is_sk: false, 130 | kind: KeyTypeKind::Rsa, 131 | }, 132 | "rsa-sha2-512" => KeyType { 133 | name: "rsa-sha2-512", 134 | plain: "ssh-rsa", 135 | short_name: "RSA", 136 | is_cert: false, 137 | is_sk: false, 138 | kind: KeyTypeKind::Rsa, 139 | }, 140 | "ssh-rsa-cert-v01@openssh.com" => KeyType { 141 | name: "ssh-rsa-cert-v01@openssh.com", 142 | plain: "ssh-rsa", 143 | short_name: "RSA-CERT", 144 | is_cert: true, 145 | is_sk: false, 146 | kind: KeyTypeKind::RsaCert, 147 | }, 148 | "ecdsa-sha2-nistp256" => KeyType { 149 | name: "ecdsa-sha2-nistp256", 150 | plain: "ecdsa-sha2-nistp256", 151 | short_name: "ECDSA", 152 | is_cert: false, 153 | is_sk: false, 154 | kind: KeyTypeKind::Ecdsa, 155 | }, 156 | "ecdsa-sha2-nistp384" => KeyType { 157 | name: "ecdsa-sha2-nistp384", 158 | plain: "ecdsa-sha2-nistp384", 159 | short_name: "ECDSA", 160 | is_cert: false, 161 | is_sk: false, 162 | kind: KeyTypeKind::Ecdsa, 163 | }, 164 | "ecdsa-sha2-nistp521" => KeyType { 165 | name: "ecdsa-sha2-nistp521", 166 | plain: "ecdsa-sha2-nistp521", 167 | short_name: "ECDSA", 168 | is_cert: false, 169 | is_sk: false, 170 | kind: KeyTypeKind::Ecdsa, 171 | }, 172 | "ecdsa-sha2-nistp256-cert-v01@openssh.com" => KeyType { 173 | name: "ecdsa-sha2-nistp256-cert-v01@openssh.com", 174 | plain: "ecdsa-sha2-nistp256", 175 | short_name: "ECDSA-CERT", 176 | is_cert: true, 177 | is_sk: false, 178 | kind: KeyTypeKind::EcdsaCert, 179 | }, 180 | "ecdsa-sha2-nistp384-cert-v01@openssh.com" => KeyType { 181 | name: "ecdsa-sha2-nistp384-cert-v01@openssh.com", 182 | plain: "ecdsa-sha2-nistp384", 183 | short_name: "ECDSA-CERT", 184 | is_cert: true, 185 | is_sk: false, 186 | kind: KeyTypeKind::EcdsaCert, 187 | }, 188 | "ecdsa-sha2-nistp521-cert-v01@openssh.com" => KeyType { 189 | name: "ecdsa-sha2-nistp521-cert-v01@openssh.com", 190 | plain: "ecdsa-sha2-nistp521", 191 | short_name: "ECDSA-CERT", 192 | is_cert: true, 193 | is_sk: false, 194 | kind: KeyTypeKind::EcdsaCert, 195 | }, 196 | "ssh-ed25519" => KeyType { 197 | name: "ssh-ed25519", 198 | plain: "ssh-ed25519", 199 | short_name: "ED25519", 200 | is_cert: false, 201 | is_sk: false, 202 | kind: KeyTypeKind::Ed25519, 203 | }, 204 | "ssh-ed25519-cert-v01@openssh.com" => KeyType { 205 | name: "ssh-ed25519-cert-v01@openssh.com", 206 | plain: "ssh-ed25519", 207 | short_name: "ED25519-CERT", 208 | is_cert: true, 209 | is_sk: false, 210 | kind: KeyTypeKind::Ed25519Cert, 211 | }, 212 | // SK Types 213 | "sk-ssh-ed25519@openssh.com" => KeyType { 214 | name: "sk-ssh-ed25519@openssh.com", 215 | plain: "sk-ssh-ed25519@openssh.com", 216 | short_name: "ED25519-SK", 217 | is_cert: false, 218 | is_sk: true, 219 | kind: KeyTypeKind::Ed25519, 220 | }, 221 | "sk-ssh-ed25519-cert-v01@openssh.com" => KeyType { 222 | name: "sk-ssh-ed25519-cert-v01@openssh.com", 223 | plain: "sk-ssh-ed25519@openssh.com", 224 | short_name: "ED25519-SK-CERT", 225 | is_cert: true, 226 | is_sk: true, 227 | kind: KeyTypeKind::Ed25519, 228 | }, 229 | "sk-ecdsa-sha2-nistp256@openssh.com" => KeyType { 230 | name: "sk-ecdsa-sha2-nistp256@openssh.com", 231 | plain: "sk-ecdsa-sha2-nistp256@openssh.com", 232 | short_name: "ECDSA-SK", 233 | is_cert: false, 234 | is_sk: true, 235 | kind: KeyTypeKind::Ecdsa, 236 | }, 237 | "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com" => KeyType { 238 | name: "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com", 239 | plain: "sk-ecdsa-sha2-nistp256@openssh.com", 240 | short_name: "ECDSA-SK", 241 | is_cert: true, 242 | is_sk: true, 243 | kind: KeyTypeKind::Ecdsa, 244 | }, 245 | _ => return Err(Error::UnknownKeyType(name.to_string())), 246 | }; 247 | Ok(kt) 248 | } 249 | 250 | /// The SK types change the convention of plain vs name. So this provides 251 | /// an easy way to convert a name into the certificate version of it. 252 | pub fn as_cert_name(&self) -> String { 253 | if !self.is_sk { 254 | format!("{}-cert-v01@openssh.com", &self.plain) 255 | } else { 256 | format!( 257 | "{}-cert-v01@openssh.com", 258 | &self.plain[..self.plain.len() - 12] 259 | ) 260 | } 261 | } 262 | 263 | /// A function that just wraps access to the member to match the cert one 264 | /// above. 265 | pub fn as_pubkey_name(&self) -> String { 266 | (&self.plain).to_string() 267 | } 268 | } 269 | 270 | impl fmt::Display for KeyType { 271 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 272 | write!(f, "{}", self.name) 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/ssh/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017 Marin Atanasov Nikolov 3 | All rights reserved. 4 | */ 5 | 6 | //! This module is a heavily modified version of the `sshkeys` crate 7 | //! that adds certificate verification, and many other things to 8 | //! support that. The original licence for the code is in the source 9 | //! code provided 10 | 11 | mod allowed_signer; 12 | mod cert; 13 | mod keytype; 14 | mod privkey; 15 | mod pubkey; 16 | mod reader; 17 | mod signature; 18 | mod writer; 19 | 20 | /// Types that implement this trait can be used to sign SSH certificates using 21 | /// the Certificate::sign function. 22 | pub trait SSHCertificateSigner { 23 | /// This function is called when signing an SSH certificate. 24 | fn sign(&self, buffer: &[u8]) -> Option>; 25 | } 26 | 27 | pub use self::allowed_signer::{AllowedSigner, AllowedSigners, AllowedSignerParsingError}; 28 | pub use self::cert::{CertType, Certificate}; 29 | pub use self::keytype::{Curve, CurveKind, KeyType, KeyTypeKind}; 30 | pub use self::privkey::{ 31 | EcdsaPrivateKey, EcdsaSkPrivateKey, Ed25519PrivateKey, Ed25519SkPrivateKey, PrivateKey, 32 | PrivateKeyKind, RsaPrivateKey, 33 | }; 34 | pub use self::pubkey::{ 35 | EcdsaPublicKey, Ed25519PublicKey, Fingerprint, FingerprintKind, PublicKey, PublicKeyKind, 36 | RsaPublicKey, 37 | }; 38 | pub use self::reader::Reader; 39 | pub use self::signature::{HashAlgorithm, SshSignature, VerifiedSshSignature}; 40 | pub use self::writer::Writer; 41 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::ssh::{PublicKey, PublicKeyKind, Writer}; 2 | 3 | /// Takes an ASN1 encoded ECDSA signature and attempts to 4 | /// parse it into it's R and S constituent parts 5 | pub fn asn_der_to_r_s(buf: &[u8]) -> Option<(&[u8], &[u8])> { 6 | if buf.len() < 4 || buf[0] != 0x30 || buf[2] != 0x2 { 7 | return None; 8 | } 9 | let buf = &buf[3..]; 10 | let r_length = buf[0] as usize; 11 | if buf.len() < r_length + 2 { 12 | return None; 13 | } 14 | let buf = &buf[1..]; 15 | let r = &buf[..r_length]; 16 | let buf = &buf[r_length..]; 17 | 18 | // Make sure we have the minimum of expected bytes 19 | if buf.len() < 3 { 20 | return None 21 | } 22 | 23 | if buf[0] != 0x2 { 24 | return None; 25 | } 26 | let s_length = buf[1] as usize; 27 | let s = &buf[2..]; 28 | 29 | if s.len() != s_length { 30 | return None; 31 | } 32 | 33 | Some((r, s)) 34 | } 35 | 36 | /// Most signature systems generate ECDSA signatures encoded in ASN1 format. 37 | /// This function will take an ASN1 encoded ECDSA signature and return 38 | /// an SSH Signature blob 39 | pub fn signature_convert_asn1_ecdsa_to_ssh(signature: &[u8]) -> Option> { 40 | let (r, s) = match asn_der_to_r_s(signature) { 41 | Some((r, s)) => (r, s), 42 | None => return None, 43 | }; 44 | let mut sig_encoding: Vec = Vec::new(); 45 | sig_encoding.extend_from_slice(&(r.len() as u32).to_be_bytes()); 46 | sig_encoding.extend_from_slice(r); 47 | sig_encoding.extend_from_slice(&(s.len() as u32).to_be_bytes()); 48 | sig_encoding.extend_from_slice(s); 49 | 50 | Some(sig_encoding) 51 | } 52 | 53 | /// Some protocols require the signature be in SSH format. This function takes 54 | /// a DER encoded signature and formats it for SSH compatibility. 55 | pub fn format_signature_for_ssh(public_key: &PublicKey, signature: &[u8]) -> Option> { 56 | let mut writer = Writer::new(); 57 | 58 | match public_key.kind { 59 | PublicKeyKind::Ecdsa(_) => { 60 | writer.write_string(public_key.key_type.name); 61 | if let Some(signature) = signature_convert_asn1_ecdsa_to_ssh(signature) { 62 | writer.write_bytes(&signature); 63 | } else { 64 | return None; 65 | } 66 | } 67 | PublicKeyKind::Rsa(_) => { 68 | writer.write_string("rsa-sha2-512"); 69 | writer.write_bytes(signature); 70 | } 71 | PublicKeyKind::Ed25519(_) => { 72 | writer.write_string(public_key.key_type.name); 73 | writer.write_bytes(signature); 74 | } 75 | }; 76 | 77 | Some(writer.into_bytes()) 78 | } 79 | -------------------------------------------------------------------------------- /src/x509/mod.rs: -------------------------------------------------------------------------------- 1 | use x509_parser::prelude::FromDer; 2 | 3 | use crate::error::Error; 4 | use crate::ssh::{Curve, EcdsaPublicKey, KeyType, PublicKey, PublicKeyKind}; 5 | 6 | use der_parser::der::parse_der_sequence; 7 | use der_parser::error::BerError; 8 | 9 | const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1"; 10 | const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1"; 11 | const OID_NIST_P256: &str = "1.2.840.10045.3.1.7"; 12 | const OID_NIST_P384: &str = "1.3.132.0.34"; 13 | 14 | impl From> for Error { 15 | fn from(_: x509_parser::nom::Err) -> Error { 16 | Error::ParsingError 17 | } 18 | } 19 | 20 | impl From for Error { 21 | fn from(_: BerError) -> Error { 22 | Error::ParsingError 23 | } 24 | } 25 | 26 | /// Helper function to convert a DER encoded public key, into an SSH formatted 27 | /// public key that can be used with the rest of the SSHCerts library. This 28 | /// function only supports NISTP256 and NISTP384 Ecdsa keys 29 | pub fn der_encoding_to_ssh_public_key(key: &[u8]) -> Result { 30 | let (_rem, parsed) = parse_der_sequence(key).map_err(|_| Error::ParsingError)?; 31 | let parsed = parsed.as_sequence().map_err(|_| Error::ParsingError)?; 32 | 33 | if parsed.len() != 2 { 34 | return Err(Error::ParsingError); 35 | } 36 | 37 | let oids = &parsed[0].as_sequence()?; 38 | if oids.len() != 2 { 39 | return Err(Error::ParsingError); 40 | } 41 | 42 | let type_oid = oids[0].as_oid()?; 43 | let key_size_oid = oids[1].as_oid()?; 44 | if type_oid.to_id_string() != OID_EC_PUBLIC_KEY { 45 | return Err(Error::ParsingError); 46 | } 47 | 48 | let data = &parsed[1].as_bitstring()?.data; 49 | let (key_type, curve) = match key_size_oid.to_id_string().as_str() { 50 | OID_NIST_P256 => { 51 | let key_type = KeyType::from_name("ecdsa-sha2-nistp256").unwrap(); 52 | let curve = Curve::from_identifier("nistp256").unwrap(); 53 | (key_type, curve) 54 | } 55 | OID_NIST_P384 => { 56 | let key_type = KeyType::from_name("ecdsa-sha2-nistp384").unwrap(); 57 | let curve = Curve::from_identifier("nistp384").unwrap(); 58 | (key_type, curve) 59 | } 60 | _ => return Err(Error::KeyTypeMismatch), 61 | }; 62 | 63 | let kind = EcdsaPublicKey { 64 | curve, 65 | key: data.to_vec(), 66 | sk_application: None, 67 | }; 68 | 69 | Ok(PublicKey { 70 | key_type, 71 | kind: PublicKeyKind::Ecdsa(kind), 72 | comment: None, 73 | }) 74 | } 75 | 76 | /// This function is used to extract an SSH public key from an x509 77 | /// certificate 78 | pub fn extract_ssh_pubkey_from_x509_certificate(cert: &[u8]) -> Result { 79 | let parsed_cert = match x509_parser::parse_x509_certificate(cert) { 80 | Ok((_, c)) => c, 81 | Err(_) => return Err(Error::ParsingError), 82 | }; 83 | let pki = &parsed_cert.tbs_certificate.subject_pki; 84 | convert_x509_pki_to_pubkey(pki) 85 | } 86 | 87 | /// This function is used to extract an SSH public key from an x509 88 | /// certificate signing request 89 | pub fn extract_ssh_pubkey_from_x509_csr(csr: &[u8]) -> Result { 90 | let parsed_csr = 91 | match x509_parser::certification_request::X509CertificationRequest::from_der(csr) { 92 | Ok((_, csr)) => csr, 93 | Err(_) => return Err(Error::ParsingError), 94 | }; 95 | let pki = &parsed_csr.certification_request_info.subject_pki; 96 | convert_x509_pki_to_pubkey(pki) 97 | } 98 | 99 | fn convert_x509_pki_to_pubkey( 100 | pki: &x509_parser::x509::SubjectPublicKeyInfo<'_>, 101 | ) -> Result { 102 | return match pki.algorithm.algorithm.to_string().as_str() { 103 | OID_RSA_ENCRYPTION => Err(Error::Unsupported), 104 | OID_EC_PUBLIC_KEY => { 105 | let key_bytes = &pki.subject_public_key.data; 106 | let algorithm_parameters = pki 107 | .algorithm 108 | .parameters 109 | .as_ref() 110 | .ok_or(Error::ParsingError)?; 111 | 112 | let curve_oid = algorithm_parameters 113 | .as_oid() 114 | .map_err(|_| Error::ParsingError)?; 115 | 116 | match curve_oid.to_string().as_str() { 117 | OID_NIST_P256 => { 118 | let key_type = KeyType::from_name("ecdsa-sha2-nistp256").unwrap(); 119 | let curve = Curve::from_identifier("nistp256").unwrap(); 120 | let kind = EcdsaPublicKey { 121 | curve, 122 | key: key_bytes.to_vec(), 123 | sk_application: None, 124 | }; 125 | 126 | Ok(PublicKey { 127 | key_type, 128 | kind: PublicKeyKind::Ecdsa(kind), 129 | comment: None, 130 | }) 131 | } 132 | OID_NIST_P384 => { 133 | let key_type = KeyType::from_name("ecdsa-sha2-nistp384").unwrap(); 134 | let curve = Curve::from_identifier("nistp384").unwrap(); 135 | let kind = EcdsaPublicKey { 136 | curve, 137 | key: key_bytes.to_vec(), 138 | sk_application: None, 139 | }; 140 | 141 | Ok(PublicKey { 142 | key_type, 143 | kind: PublicKeyKind::Ecdsa(kind), 144 | comment: None, 145 | }) 146 | } 147 | _ => Err(Error::KeyTypeMismatch), 148 | } 149 | } 150 | _ => Err(Error::ParsingError), 151 | }; 152 | } 153 | -------------------------------------------------------------------------------- /src/yubikey/mod.rs: -------------------------------------------------------------------------------- 1 | /// Functions for dealing with Yubikey signing using PIV. 2 | /// Also contains an SSH submodule containing helper functions to generate 3 | /// SSH encoded versions of it's normal functions. 4 | #[cfg(any(feature = "yubikey-support"))] 5 | pub mod piv; 6 | 7 | /// For verifying attestation chains of Yubikey PIV keys 8 | #[cfg(any(feature = "yubikey-lite", feature = "yubikey-support"))] 9 | pub mod verification; 10 | -------------------------------------------------------------------------------- /src/yubikey/piv/management.rs: -------------------------------------------------------------------------------- 1 | use crate::PublicKey; 2 | 3 | use ring::digest; 4 | 5 | use yubikey::certificate::{Certificate, PublicKeyInfo}; 6 | use yubikey::piv::{attest, sign_data as yk_sign_data, AlgorithmId, SlotId}; 7 | use yubikey::{MgmKey, YubiKey}; 8 | use yubikey::{PinPolicy, TouchPolicy}; 9 | 10 | use x509::RelativeDistinguishedName; 11 | 12 | use super::{Error, Result}; 13 | 14 | #[derive(Debug)] 15 | /// A struct that allows the generation of CSRs via the rcgen library. This is 16 | /// only used when calling the `generate_csr` function. 17 | pub struct CSRSigner { 18 | slot: SlotId, 19 | serial: u32, 20 | public_key: Vec, 21 | algorithm: AlgorithmId, 22 | } 23 | 24 | impl CSRSigner { 25 | /// Create a new certificate signer based on a Yubikey serial 26 | /// and slot 27 | pub fn new(serial: u32, slot: SlotId) -> Self { 28 | let mut yk = super::Yubikey::open(serial).unwrap(); 29 | let pki = yk.configured(&slot).unwrap(); 30 | let (public_key, algorithm) = match pki { 31 | PublicKeyInfo::Rsa { pubkey: _, .. } => panic!("RSA keys not supported"), 32 | PublicKeyInfo::EcP256(pubkey) => (pubkey.as_bytes().to_vec(), AlgorithmId::EccP256), 33 | PublicKeyInfo::EcP384(pubkey) => (pubkey.as_bytes().to_vec(), AlgorithmId::EccP384), 34 | }; 35 | 36 | Self { 37 | slot, 38 | serial, 39 | public_key, 40 | algorithm, 41 | } 42 | } 43 | } 44 | 45 | impl rcgen::RemoteKeyPair for CSRSigner { 46 | fn public_key(&self) -> &[u8] { 47 | &self.public_key 48 | } 49 | 50 | fn sign(&self, message: &[u8]) -> std::result::Result, rcgen::RcgenError> { 51 | let mut yk = if let Ok(yk) = super::Yubikey::open(self.serial) { 52 | yk 53 | } else { 54 | return Err(rcgen::RcgenError::RemoteKeyError); 55 | }; 56 | 57 | yk.sign_data(message, self.algorithm, &self.slot).map_err(|_| rcgen::RcgenError::RemoteKeyError) 58 | } 59 | 60 | fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm { 61 | match self.algorithm { 62 | AlgorithmId::EccP256 => &rcgen::PKCS_ECDSA_P256_SHA256, 63 | AlgorithmId::EccP384 => &rcgen::PKCS_ECDSA_P384_SHA384, 64 | _ => panic!("Unimplemented"), 65 | } 66 | } 67 | } 68 | 69 | impl super::Yubikey { 70 | /// Create a new YubiKey. Assumes there is only one Yubikey connected 71 | pub fn new() -> Result { 72 | Ok(Self { 73 | yk: YubiKey::open()?, 74 | }) 75 | } 76 | 77 | /// Open a Yubikey from a serial 78 | pub fn open(serial: u32) -> Result { 79 | match YubiKey::open_by_serial(serial.into()) { 80 | Ok(yk) => Ok(Self { yk }), 81 | Err(_) => Err(Error::NoSuchYubikey), 82 | } 83 | } 84 | 85 | /// Reconnet to the Yubikey (if possible, if it's disconnected) 86 | pub fn reconnect(&mut self) -> Result<()> { 87 | match self.yk.reconnect() { 88 | Ok(()) => Ok(()), 89 | Err(_) => match YubiKey::open_by_serial(self.yk.serial()) { 90 | Ok(yk) => { 91 | self.yk = yk; 92 | Ok(()) 93 | } 94 | Err(_) => Err(Error::NoSuchYubikey), 95 | }, 96 | } 97 | } 98 | 99 | /// Unlock the yubikey for signing or provisioning operations 100 | pub fn unlock(&mut self, pin: &[u8], mgm_key: &[u8]) -> Result<()> { 101 | self.yk.verify_pin(pin)?; 102 | 103 | match MgmKey::from_bytes(mgm_key) { 104 | Ok(mgm) => self.yk.authenticate(mgm)?, 105 | Err(_) => return Err(Error::InvalidManagementKey), 106 | }; 107 | Ok(()) 108 | } 109 | 110 | /// Check to see that a provided Yubikey and slot is configured for signing 111 | pub fn configured(&mut self, slot: &SlotId) -> Result { 112 | let cert = Certificate::read(&mut self.yk, *slot)?; 113 | Ok(cert.subject_pki().clone()) 114 | } 115 | 116 | /// Check to see that a provided Yubikey and slot is configured for signing 117 | pub fn fetch_subject(&mut self, slot: &SlotId) -> Result { 118 | let cert = Certificate::read(&mut self.yk, *slot)?; 119 | Ok(cert.subject().to_string()) 120 | } 121 | 122 | /// Fetch the certificate from a given Yubikey slot. 123 | pub fn fetch_certificate(&mut self, slot: &SlotId) -> Result> { 124 | let cert = Certificate::read(&mut self.yk, *slot)?; 125 | Ok(cert.as_ref().to_vec()) 126 | } 127 | 128 | /// Write the certificate from a given Yubikey slot. 129 | pub fn write_certificate(&mut self, slot: &SlotId, data: &[u8]) -> Result<()> { 130 | Ok(Certificate::from_bytes(data.to_vec())?.write( 131 | &mut self.yk, 132 | *slot, 133 | yubikey::certificate::CertInfo::Uncompressed, 134 | )?) 135 | } 136 | 137 | /// Generate attestation for a slot 138 | pub fn fetch_attestation(&mut self, slot: &SlotId) -> Result> { 139 | Ok(attest(&mut self.yk, *slot)?.to_vec()) 140 | } 141 | 142 | /// Generate CSR for slot 143 | pub fn generate_csr(&mut self, slot: &SlotId, common_name: &str) -> Result> { 144 | let mut params = rcgen::CertificateParams::new(vec![]); 145 | params.alg = match self.configured(slot)? { 146 | PublicKeyInfo::EcP256(_) => &rcgen::PKCS_ECDSA_P256_SHA256, 147 | PublicKeyInfo::EcP384(_) => &rcgen::PKCS_ECDSA_P384_SHA384, 148 | _ => return Err(Error::Unsupported), 149 | }; 150 | params 151 | .distinguished_name 152 | .push(rcgen::DnType::CommonName, common_name.to_string()); 153 | 154 | let csr_signer = CSRSigner::new(self.yk.serial().into(), *slot); 155 | params.key_pair = Some(rcgen::KeyPair::from_remote(Box::new(csr_signer)).map_err(|e| Error::InternalYubiKeyError(format!("{}", e)))?); 156 | 157 | let csr = rcgen::Certificate::from_params(params).map_err(|e| Error::InternalYubiKeyError(format!("{}", e)))?; 158 | let csr = csr.serialize_request_der().map_err(|e| Error::InternalYubiKeyError(format!("{}", e)))?; 159 | 160 | Ok(csr) 161 | } 162 | 163 | /// Provisions the YubiKey with a new certificate generated on the device. 164 | /// Only keys that are generated this way can use the attestation functionality. 165 | pub fn provision( 166 | &mut self, 167 | slot: &SlotId, 168 | common_name: &str, 169 | alg: AlgorithmId, 170 | touch_policy: TouchPolicy, 171 | pin_policy: PinPolicy, 172 | ) -> Result { 173 | let key_info = yubikey::piv::generate(&mut self.yk, *slot, alg, pin_policy, touch_policy)?; 174 | let extensions: &[x509::Extension<'_, &[u64]>] = &[]; 175 | // Generate a self-signed certificate for the new key. 176 | Certificate::generate_self_signed( 177 | &mut self.yk, 178 | *slot, 179 | [0u8; 20], 180 | None, 181 | &[RelativeDistinguishedName::common_name(common_name)], 182 | key_info, 183 | extensions, 184 | )?; 185 | 186 | self.ssh_cert_fetch_pubkey(slot) 187 | } 188 | 189 | /// Take data, an algorithm, and a slot and attempt to sign the data field 190 | /// 191 | /// If the requested algorithm doesn't match the key in the slot (or the slot 192 | /// is empty) this will error. 193 | pub fn sign_data(&mut self, data: &[u8], alg: AlgorithmId, slot: &SlotId) -> Result> { 194 | let (slot_alg, hash_alg) = match self.configured(slot) { 195 | Ok(PublicKeyInfo::EcP256(_)) => (AlgorithmId::EccP256, &digest::SHA256), 196 | Ok(PublicKeyInfo::EcP384(_)) => (AlgorithmId::EccP384, &digest::SHA384), 197 | Ok(_) => (AlgorithmId::Rsa2048, &digest::SHA256), // RSAish 198 | Err(_) => return Err(Error::Unprovisioned), 199 | }; 200 | 201 | if slot_alg != alg { 202 | return Err(Error::WrongKeyType); 203 | } 204 | let signature = yk_sign_data( 205 | &mut self.yk, 206 | digest::digest(hash_alg, data).as_ref(), 207 | alg, 208 | *slot, 209 | )?; 210 | Ok(signature.to_vec()) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/yubikey/piv/mod.rs: -------------------------------------------------------------------------------- 1 | /// Contains all the functions used for creating new keys, unlocking, and 2 | /// managing the yubikey 3 | pub mod management; 4 | /// The SSH submodule contains functions relevant to SSH uses that are backed 5 | /// by the Yubikey. This includes things like signing and SSH public key 6 | /// export. 7 | pub mod ssh; 8 | 9 | /// Errors when interacting with the Yubikey. 10 | #[derive(Debug)] 11 | pub enum Error { 12 | /// Generally this occurs when a slot is asked to return or process data 13 | /// when it has no certificate or private key. 14 | Unprovisioned, 15 | /// This occurs when the signature type requested does not match the key 16 | /// in the slot on the key 17 | WrongKeyType, 18 | /// This occurs when you try to use a feature that should technically work 19 | /// but is currently unimplemented or unsupported on the hardware connected. 20 | /// For example, RSA signing will currently throw this error. 21 | Unsupported, 22 | /// If you pass a management key into the provision function that does not 23 | /// deserialize from bytes, you will get this error. 24 | InvalidManagementKey, 25 | /// If you provide invalid bytes that cannot be converted from an x509 to 26 | /// a SSH key. 27 | ParsingError, 28 | /// The requested key could not be found connected to the system. It's 29 | /// possible it was removed while running. 30 | NoSuchYubikey, 31 | /// If the Yubikey throws an error we don't recognize, it's encapsulated 32 | /// and returned 33 | InternalYubiKeyError(String), 34 | } 35 | 36 | impl std::error::Error for Error {} 37 | 38 | type Result = std::result::Result; 39 | 40 | // Re-export because it's used as a parameter in `sign_data` 41 | pub use yubikey::piv::{AlgorithmId, RetiredSlotId, SlotId}; 42 | pub use yubikey::{PinPolicy, TouchPolicy}; 43 | 44 | /// Structure to wrap a yubikey and abstract actions 45 | pub struct Yubikey { 46 | /// We make this public to allow deeper access the Yubikey 47 | /// type if needed 48 | pub yk: yubikey::YubiKey, 49 | } 50 | 51 | impl std::fmt::Debug for Yubikey { 52 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 | writeln!(f, "YubiKey: {}", self.yk.serial()) 54 | } 55 | } 56 | 57 | impl From for Error { 58 | fn from(e: yubikey::Error) -> Self { 59 | Error::InternalYubiKeyError(e.to_string()) 60 | } 61 | } 62 | 63 | impl std::fmt::Display for Error { 64 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 65 | match *self { 66 | Error::Unprovisioned => write!(f, "Slot is unprovisioned for signing"), 67 | Error::WrongKeyType => write!( 68 | f, 69 | "Wrong key type was provided for requested signing operation" 70 | ), 71 | Error::Unsupported => { 72 | write!(f, "This key is not supported the way you tried to use it") 73 | } 74 | Error::InvalidManagementKey => { 75 | write!(f, "Could not use the management key as provided") 76 | } 77 | Error::ParsingError => write!(f, "Could not parse data"), 78 | Error::NoSuchYubikey => write!(f, "Could not find the requested Yubikey"), 79 | Error::InternalYubiKeyError(ref err) => write!(f, "Yubikey error: {}", err), 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/yubikey/piv/ssh.rs: -------------------------------------------------------------------------------- 1 | use crate::ssh::{CurveKind, PublicKey, PublicKeyKind}; 2 | use yubikey::piv::{AlgorithmId, SlotId}; 3 | 4 | use crate::x509::extract_ssh_pubkey_from_x509_certificate; 5 | 6 | use super::{Error, Result}; 7 | 8 | impl super::Yubikey { 9 | /// Pull the public key from the YubiKey and wrap it in a sshcerts 10 | /// PublicKey object. 11 | pub fn ssh_cert_fetch_pubkey(&mut self, slot: &SlotId) -> Result { 12 | match extract_ssh_pubkey_from_x509_certificate(&self.fetch_certificate(slot)?) { 13 | Ok(public_key) => Ok(public_key), 14 | Err(crate::error::Error::ParsingError) => Err(Error::ParsingError), 15 | Err(crate::error::Error::KeyTypeMismatch) => Err(Error::WrongKeyType), 16 | Err(_) => Err(Error::Unsupported), 17 | } 18 | } 19 | 20 | /// Returns the AlgorithmId of the kind of key stored in the given 21 | /// slot. This could return RSA key types as they are valid but 22 | /// currently it only differentiates between no key, and ECCP256 and ECCP384 23 | pub fn get_ssh_key_type(&mut self, slot: &SlotId) -> Result { 24 | let pubkey = self.ssh_cert_fetch_pubkey(slot)?; 25 | 26 | match pubkey.kind { 27 | PublicKeyKind::Ecdsa(x) => match x.curve.kind { 28 | CurveKind::Nistp256 => Ok(AlgorithmId::EccP256), 29 | CurveKind::Nistp384 => Ok(AlgorithmId::EccP384), 30 | CurveKind::Nistp521 => Err(Error::Unsupported), 31 | }, 32 | PublicKeyKind::Rsa(_) => Err(Error::Unsupported), 33 | PublicKeyKind::Ed25519(_) => Err(Error::Unsupported), 34 | } 35 | } 36 | 37 | /// Sign the provided buffer of data 38 | pub fn ssh_cert_signer(&mut self, buf: &[u8], slot: &SlotId) -> Result> { 39 | let alg = match self.get_ssh_key_type(slot) { 40 | Ok(x) => x, 41 | _ => return Err(Error::Unsupported), 42 | }; 43 | 44 | let buf = self.sign_data(buf, alg, slot)?; 45 | let pub_key = self.ssh_cert_fetch_pubkey(&slot)?; 46 | 47 | match crate::utils::format_signature_for_ssh(&pub_key, &buf) { 48 | Some(x) => Ok(x), 49 | None => Err(Error::ParsingError), 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/allowed-signers.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::AllowedSigners; 2 | 3 | #[test] 4 | fn parse_good_allowed_signers() { 5 | let allowed_signers = AllowedSigners::from_path("tests/allowed_signers/good_allowed_signers"); 6 | assert!(allowed_signers.is_ok()); 7 | let AllowedSigners(allowed_signers) = allowed_signers.unwrap(); 8 | assert_eq!(allowed_signers.len(), 3); 9 | 10 | assert_eq!( 11 | allowed_signers[0].key.fingerprint().to_string(), 12 | "SHA256:QAtqtvvCePelMMUNPP7madH2zNa1ATxX1nt9L/0C5+M", 13 | ); 14 | assert_eq!( 15 | allowed_signers[0].principals, 16 | vec!["mitchell@confurious.io".to_string()], 17 | ); 18 | assert!(!allowed_signers[0].cert_authority); 19 | assert!(allowed_signers[0].namespaces.is_none()); 20 | assert!(allowed_signers[0].valid_after.is_none()); 21 | assert!(allowed_signers[0].valid_before.is_none()); 22 | 23 | assert_eq!( 24 | allowed_signers[1].principals, 25 | vec!["mitchell@confurious.io".to_string(), "mitchel2@confurious.io".to_string()], 26 | ); 27 | assert!(allowed_signers[1].cert_authority); 28 | assert_eq!( 29 | allowed_signers[1].namespaces, 30 | Some(vec!["thanh".to_string(), "#mitchell".to_string()]) 31 | ); 32 | assert!(allowed_signers[1].valid_after.is_none()); 33 | assert_eq!( 34 | allowed_signers[1].valid_before, 35 | Some(1714867200i64), 36 | ); 37 | 38 | assert_eq!( 39 | allowed_signers[2].namespaces, 40 | Some(vec![ 41 | "thanh".to_string(), 42 | " ".to_string(), 43 | "mitchell mitchell".to_string(), 44 | " andrew andrew".to_string(), 45 | ]), 46 | ); 47 | assert!(allowed_signers[2].valid_after.is_none()); 48 | assert_eq!( 49 | allowed_signers[2].valid_before, 50 | Some(1714867200i64), 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /tests/allowed_signers/good_allowed_signers: -------------------------------------------------------------------------------- 1 | # Comments are ignored 2 | mitchell@confurious.io ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDO0VQD9TIdICZLWFWwtf7s8/aENve8twGTEmNV0myh5 3 | # Empty lines are also ignored 4 | 5 | mitchell@confurious.io,mitchel2@confurious.io cert-authority namespaces="thanh,#mitchell" valid-before="20240505Z" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDO0VQD9TIdICZLWFWwtf7s8/aENve8twGTEmNV0myh5 # End of line comment is ignored 6 | mitchell@confurious.io,mitchel2@confurious.io cert-authority namespaces="thanh, ,mitchell mitchell, andrew andrew" valid-before="20240505Z" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDO0VQD9TIdICZLWFWwtf7s8/aENve8twGTEmNV0myh5 7 | -------------------------------------------------------------------------------- /tests/allowed_signers/test_keys: -------------------------------------------------------------------------------- 1 | mitchell@confurious.io ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDO0VQD9TIdICZLWFWwtf7s8/aENve8twGTEmNV0myh5 ed25519_1 2 | mitchell@confurious.io ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM0JfpeVmfRBExbXgAFlrkZlzrpT5ywSIqyCRnAYrT4U ed25519_2 3 | mitchell@confurious.io sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIIVTblnROOE/e2jCl6ieSgqPjWtnxjzmpCHU+TJ3EbL8AAAAEHNzaDpTU0hDZXJ0c1Rlc3Q= sk_ed25519 4 | mitchell@confurious.io ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBYEU2maNpyuWdSfjAxDO3s5NjqLCR+FFHmADo3sdoZl13alTDHIpoJuwfkCsNhNv5gLOsCY76mJsn2oJ1evoyo= ecdsa_256_1 5 | mitchell@confurious.io ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBBQ8HnzFz1zFwunWzCMnzteMPZu60fluVIn5U8779lX7IWfsSVchHO+b6LZo+PT99zngtJ0TxJmUC7tEu7ICDAfwip+EsBMbeart8M9KdFKfvMSuMhQY69FLuDm+EEf1HQ== ecdsa_384_1 -------------------------------------------------------------------------------- /tests/cert-conformance.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::Certificate; 2 | 3 | #[test] 4 | fn check_pubkey_extracts_from_certificate_correctly_ed25519() { 5 | let cert = Certificate::from_string(include_str!("certs/ed25519_signed_by_ed25519-cert.pub")); 6 | let user_pubkey = 7 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDRlrLwx21DZPH4pLkK6cJBn2bvGS3PGqwqa61XgwVkH"; 8 | assert!(cert.is_ok()); 9 | let cert = cert.unwrap(); 10 | assert_eq!(format!("{}", cert.key), user_pubkey); 11 | } 12 | 13 | #[test] 14 | fn check_pubkey_extracts_from_certificate_correctly_ecdsa384() { 15 | let cert = Certificate::from_string(include_str!("certs/ecdsa384_signed_by_ecdsa384-cert.pub")); 16 | let user_pubkey = "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBFLokpkryGhSfa6c1XkoYYdSghpoc5OAKn+y3vTAfw6Bi+Q6Y1vJV81jCoTPWQoxgp4wZ+2vXYytUuaiwAc03KHKazsCCTUUR9FHKafx8E20Pub67yTRpBCU9JTF2lIjkw=="; 17 | assert!(cert.is_ok()); 18 | let cert = cert.unwrap(); 19 | assert_eq!(format!("{}", cert.key), user_pubkey); 20 | } 21 | 22 | #[test] 23 | fn check_pubkey_extracts_from_certificate_correctly_eed25519_sk() { 24 | let cert = 25 | Certificate::from_string(include_str!("certs/ed25519_sk_signed_by_ecdsa384-cert.pub")); 26 | let user_pubkey = "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIGYMHSzST3lQhQKYYSdosWFQZiP2YSFwCySgOyC93jWCAAAABHNzaDo="; 27 | assert!(cert.is_ok()); 28 | let cert = cert.unwrap(); 29 | assert_eq!(format!("{}", cert.key), user_pubkey); 30 | } 31 | -------------------------------------------------------------------------------- /tests/cert-creation-parse-rsa.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::{CertType, Certificate, PrivateKey, PublicKey}; 2 | 3 | // Constants available for multiple tests 4 | const RSA2048_CA_PRIVATE_KEY: &str = include_str!("keys/unencrypted/rsa-2048"); 5 | 6 | const LEGACY_RSA4096_SHA2_512_PUBLIC_KEY: &str = include_str!("keys/public/rsa-sha2-512-4096.pub"); 7 | const RSA4096_SHA2_512_PRIVATE_KEY: &str = include_str!("keys/unencrypted/rsa-sha2-512-4096"); 8 | const RSA4096_CA_PRIVATE_KEY: &str = include_str!("keys/unencrypted/rsa-sha2-256-4096"); 9 | // End constants 10 | 11 | #[test] 12 | fn create_and_reparse_sign_parse_verify_minimal_ecdsa384_rsa2048ca() { 13 | let ssh_pubkey = PublicKey::from_string("ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uRUfk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVA== obelisk@exclave.lan"); 14 | assert!(ssh_pubkey.is_ok()); 15 | 16 | let ssh_pubkey = ssh_pubkey.unwrap(); 17 | 18 | let private_key = PrivateKey::from_string(RSA2048_CA_PRIVATE_KEY).unwrap(); 19 | let ca_pubkey = private_key.pubkey.clone(); 20 | 21 | let user_cert = Certificate::builder(&ssh_pubkey, CertType::User, &ca_pubkey) 22 | .unwrap() 23 | .key_id("key_id") 24 | .valid_after(0) 25 | .valid_before(0xFFFFFFFFFFFFFFFF) 26 | .sign(&private_key); 27 | 28 | assert!(user_cert.is_ok()); 29 | let user_cert = user_cert.unwrap(); 30 | 31 | // Check CA fields 32 | assert_eq!( 33 | user_cert.signature_key.fingerprint().hash, 34 | "n3kYx3FlLBGcCJWtzkm1YF6vIvtJcp3m+H7u3SnaGxc" 35 | ); 36 | 37 | // Check that we can correctly reparse the serialized certificate 38 | let cert = format!("{}", user_cert); 39 | 40 | let cert = Certificate::from_string(&cert); 41 | assert!(cert.is_ok()); 42 | } 43 | 44 | #[test] 45 | fn create_and_reparse_sign_parse_verify_minimal_ed25519_rsa2048ca() { 46 | let ssh_pubkey = PublicKey::from_string( 47 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILHHgBVMG7TU30Z8lFfHPwBx98w3wkhoaybFc6/tjasI", 48 | ); 49 | assert!(ssh_pubkey.is_ok()); 50 | 51 | let ssh_pubkey = ssh_pubkey.unwrap(); 52 | 53 | let private_key = PrivateKey::from_string(RSA2048_CA_PRIVATE_KEY).unwrap(); 54 | let ca_pubkey = private_key.pubkey.clone(); 55 | 56 | let user_cert = Certificate::builder(&ssh_pubkey, CertType::User, &ca_pubkey) 57 | .unwrap() 58 | .key_id("key_id") 59 | .valid_after(0) 60 | .valid_before(0xFFFFFFFFFFFFFFFF) 61 | .sign(&private_key); 62 | 63 | assert!(user_cert.is_ok()); 64 | let user_cert = user_cert.unwrap(); 65 | 66 | // Check CA fields 67 | assert_eq!( 68 | user_cert.signature_key.fingerprint().hash, 69 | "n3kYx3FlLBGcCJWtzkm1YF6vIvtJcp3m+H7u3SnaGxc" 70 | ); 71 | 72 | // Check that we can correctly reparse the serialized certificate 73 | let cert = format!("{}", user_cert); 74 | 75 | let cert = Certificate::from_string(&cert); 76 | assert!(cert.is_ok()); 77 | } 78 | 79 | #[test] 80 | fn sign_and_certify_rsa_sha2_256() { 81 | let private_key = PrivateKey::from_string(RSA4096_CA_PRIVATE_KEY).unwrap(); 82 | let ca_pubkey = private_key.pubkey.clone(); 83 | 84 | let user_cert = Certificate::builder(&private_key.pubkey, CertType::User, &ca_pubkey) 85 | .unwrap() 86 | .key_id("key_id") 87 | .valid_after(0) 88 | .valid_before(0xFFFFFFFFFFFFFFFF) 89 | .sign(&private_key); 90 | 91 | let user_cert = user_cert.unwrap(); 92 | 93 | // Check CA fields 94 | assert_eq!( 95 | user_cert.signature_key.fingerprint().hash, 96 | "sqSMm4+0OSx6UlrEUW7Khu40yymOGt9nkF2U2/ixHKQ" 97 | ); 98 | 99 | // Check that we can correctly reparse the serialized certificate 100 | let cert = format!("{}", user_cert); 101 | 102 | let cert = Certificate::from_string(&cert); 103 | assert!(cert.is_ok()); 104 | } 105 | 106 | #[test] 107 | fn check_legacy_signing_sha2_512_signing() { 108 | let ssh_pubkey = PublicKey::from_string(LEGACY_RSA4096_SHA2_512_PUBLIC_KEY).unwrap(); 109 | 110 | let private_key = PrivateKey::from_string(RSA4096_SHA2_512_PRIVATE_KEY).unwrap(); 111 | let ca_pubkey = private_key.pubkey.clone(); 112 | 113 | let user_cert = Certificate::builder(&ssh_pubkey, CertType::User, &ca_pubkey) 114 | .unwrap() 115 | .key_id("key_id") 116 | .valid_after(0) 117 | .valid_before(0xFFFFFFFFFFFFFFFF) 118 | .sign(&private_key); 119 | 120 | let user_cert = user_cert.unwrap(); 121 | 122 | // Check CA fields 123 | assert_eq!( 124 | user_cert.signature_key.fingerprint().hash, 125 | "A+mZxJjDySutGP+sUtDX2KkZPxKloVLev+bDoJWhLn0" 126 | ); 127 | 128 | // Check User fields 129 | assert_eq!( 130 | user_cert.key.fingerprint().hash, 131 | "A+mZxJjDySutGP+sUtDX2KkZPxKloVLev+bDoJWhLn0" 132 | ); 133 | 134 | 135 | // Check that we can correctly reparse the serialized certificate 136 | let cert = format!("{}", user_cert); 137 | 138 | let cert = Certificate::from_string(&cert); 139 | assert!(cert.is_ok()); 140 | } -------------------------------------------------------------------------------- /tests/cert-creation-parse.rs: -------------------------------------------------------------------------------- 1 | use ring::{rand, signature}; 2 | 3 | use sshcerts::ssh::{CertType, Certificate, PrivateKey, PublicKey}; 4 | use sshcerts::utils::format_signature_for_ssh; 5 | 6 | use std::collections::HashMap; 7 | 8 | // Constants available for multiple tests 9 | const ECDSA256_CA_PRIVATE_KEY: &str = concat!( 10 | "308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02", 11 | "0101042063b3b4925287d2d20fd53c297ef80cdcd438764d40999ba60f6f1b08", 12 | "14e3b49ea14403420004dc3f4472cea77335a6ef9ac7bc73a37aac9f234a58d6", 13 | "0566a1946b135879db89a0a346fbc6f4db9ee5c30380f479280d62c9a65b6f50", 14 | "81fbc6b6f70048c6290f" 15 | ); 16 | 17 | const ECDSA256_SSH_PUBLIC_KEY: &str = concat!( 18 | "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy", 19 | "NTYAAABBBNw/RHLOp3M1pu+ax7xzo3qsnyNKWNYFZqGUaxNYeduJoKNG+8b0257l", 20 | "wwOA9HkoDWLJpltvUIH7xrb3AEjGKQ8= obelisk@exclave.lan" 21 | ); 22 | 23 | const ECDSA384_CA_PRIVATE_KEY: &str = concat!( 24 | "3081b6020100301006072a8648ce3d020106052b8104002204819e30819b020", 25 | "1010430ed4d1e49a2b25dcde5091f5104d3c1647336ac44afad699728a9f0c9", 26 | "e3b0ce39b49927f80f38398f72365014b74933c5a16403620004c895d0676a6", 27 | "a550c09e41bd0b68eea4e6697a060ac43933cb1c544d99155cd93cf2ef9f041", 28 | "429a99ee3443f6c1a574d00ba03c32cfc23386759ea60f1d43413deb4c86c2f", 29 | "326fd575b1a2f43e706df2fb6b228275aad698f79aefa622f663e4a" 30 | ); 31 | 32 | const ECDSA384_SSH_PUBLIC_KEY: &str = concat!( 33 | "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAz", 34 | "ODQAAABhBMiV0GdqalUMCeQb0LaO6k5ml6BgrEOTPLHFRNmRVc2Tzy758EFCmpnu", 35 | "NEP2waV00AugPDLPwjOGdZ6mDx1DQT3rTIbC8yb9V1saL0PnBt8vtrIoJ1qtaY95", 36 | "rvpiL2Y+Sg== obelisk@exclave.lan" 37 | ); 38 | // End constants 39 | 40 | // Test signing and parsing work together 41 | fn test_ecdsa256_signer(buf: &[u8]) -> Option> { 42 | let pkcs8_bytes = hex::decode(ECDSA256_CA_PRIVATE_KEY).unwrap(); 43 | let rng = rand::SystemRandom::new(); 44 | let key_pair = signature::EcdsaKeyPair::from_pkcs8( 45 | &signature::ECDSA_P256_SHA256_ASN1_SIGNING, 46 | pkcs8_bytes.as_ref(), 47 | &rng, 48 | ) 49 | .unwrap(); 50 | 51 | let pubkey = PublicKey::from_string(ECDSA256_SSH_PUBLIC_KEY).unwrap(); 52 | format_signature_for_ssh(&pubkey, key_pair.sign(&rng, buf).ok()?.as_ref()) 53 | } 54 | 55 | // Test signing and parsing work together 56 | fn test_ecdsa384_signer(buf: &[u8]) -> Option> { 57 | let pkcs8_bytes = hex::decode(ECDSA384_CA_PRIVATE_KEY).unwrap(); 58 | let rng = rand::SystemRandom::new(); 59 | let key_pair = signature::EcdsaKeyPair::from_pkcs8( 60 | &signature::ECDSA_P384_SHA384_ASN1_SIGNING, 61 | pkcs8_bytes.as_ref(), 62 | &rng, 63 | ) 64 | .unwrap(); 65 | 66 | let pubkey = PublicKey::from_string(ECDSA384_SSH_PUBLIC_KEY).unwrap(); 67 | format_signature_for_ssh(&pubkey, key_pair.sign(&rng, buf).ok()?.as_ref()) 68 | } 69 | 70 | #[test] 71 | fn create_and_reparse_sign_parse_verify_ed25519ca() { 72 | let privkey = concat!( 73 | "-----BEGIN OPENSSH PRIVATE KEY-----\n", 74 | "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n", 75 | "QyNTUxOQAAACDNCX6XlZn0QRMW14ABZa5GZc66U+csEiKsgkZwGK0+FAAAAJiT9ajkk/Wo\n", 76 | "5AAAAAtzc2gtZWQyNTUxOQAAACDNCX6XlZn0QRMW14ABZa5GZc66U+csEiKsgkZwGK0+FA\n", 77 | "AAAED6HgUU3Ps5TVdFCVO8uTpbfVdg3JBxnOz3DIWO1u1Xbc0JfpeVmfRBExbXgAFlrkZl\n", 78 | "zrpT5ywSIqyCRnAYrT4UAAAAE29iZWxpc2tAZXhjbGF2ZS5sYW4BAg==\n", 79 | "-----END OPENSSH PRIVATE KEY-----" 80 | ); 81 | 82 | let privkey = PrivateKey::from_string(privkey); 83 | match &privkey { 84 | Ok(_) => (), 85 | Err(e) => println!("{}", e), 86 | }; 87 | assert!(privkey.is_ok()); 88 | let privkey = privkey.unwrap(); 89 | 90 | let ssh_pubkey = PublicKey::from_string("ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uRUfk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVA== obelisk@exclave.lan"); 91 | assert!(ssh_pubkey.is_ok()); 92 | 93 | let ssh_pubkey = ssh_pubkey.unwrap(); 94 | let pubkey = privkey.pubkey.clone(); 95 | 96 | let user_cert = Certificate::builder(&ssh_pubkey, CertType::User, &pubkey) 97 | .unwrap() 98 | .serial(0xFEFEFEFEFEFEFEFE) 99 | .key_id("key_id") 100 | .key_id("overwrite_key_id") 101 | .principal("obelisk") 102 | .principal("mitchell") 103 | .valid_after(0) 104 | .valid_before(0xFFFFFFFFFFFFFFFF) 105 | .set_critical_options(HashMap::new()) 106 | .critical_option("test", "test_value") 107 | .set_extensions(Certificate::standard_extensions()) 108 | .extension("extension_test", "extension_test_value") 109 | .sign(&privkey); 110 | 111 | assert!(user_cert.is_ok()); 112 | 113 | // Check user fields 114 | let user_cert = user_cert.unwrap(); 115 | assert_eq!( 116 | user_cert.key.fingerprint().hash, 117 | "uzOtIxALSM+OuY+LmdU1xFLzY4zBvom/1Etb385O0ek" 118 | ); 119 | assert_eq!(user_cert.key_id, String::from("overwrite_key_id")); 120 | assert_eq!(user_cert.principals, vec!["obelisk", "mitchell"]); 121 | assert_eq!(user_cert.critical_options.len(), 1); 122 | assert!(user_cert.critical_options.get("test").is_some()); 123 | assert_eq!( 124 | user_cert.critical_options.get("test").unwrap(), 125 | &String::from("test_value") 126 | ); 127 | assert_eq!(user_cert.extensions.len(), 6); 128 | assert!(user_cert.extensions.get("extension_test").is_some()); 129 | assert_eq!( 130 | user_cert.extensions.get("extension_test").unwrap(), 131 | &String::from("extension_test_value") 132 | ); 133 | assert_eq!(user_cert.serial, 0xFEFEFEFEFEFEFEFE); 134 | assert_eq!(user_cert.valid_after, 0); 135 | assert_eq!(user_cert.valid_before, 0xFFFFFFFFFFFFFFFF); 136 | 137 | // Check CA fields 138 | assert_eq!( 139 | user_cert.signature_key.fingerprint().hash, 140 | "XfK1zRAFSKTh7bYdKwli8mJ0P4q/bV2pXdmjyw5p0DI" 141 | ); 142 | 143 | // Check that we can correctly reparse the serialized certificate 144 | let cert = format!("{}", user_cert); 145 | 146 | let cert = Certificate::from_string(&cert); 147 | assert!(cert.is_ok()); 148 | } 149 | 150 | #[test] 151 | fn create_and_reparse_sign_parse_verify_minimal_ecdsa256ca() { 152 | let ssh_pubkey = PublicKey::from_string("ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uRUfk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVA== obelisk@exclave.lan"); 153 | assert!(ssh_pubkey.is_ok()); 154 | 155 | let ssh_pubkey = ssh_pubkey.unwrap(); 156 | let ca_pubkey = PublicKey::from_string(ECDSA256_SSH_PUBLIC_KEY).unwrap(); 157 | 158 | let user_cert_partial = Certificate::builder(&ssh_pubkey, CertType::User, &ca_pubkey) 159 | .unwrap() 160 | .key_id("key_id") 161 | .valid_after(0) 162 | .valid_before(0xFFFFFFFFFFFFFFFF); 163 | 164 | let signature = test_ecdsa256_signer(&user_cert_partial.tbs_certificate()); 165 | assert!(signature.is_some()); 166 | 167 | let user_cert = user_cert_partial.add_signature(&signature.unwrap()); 168 | assert!(user_cert.is_ok()); 169 | 170 | let user_cert = user_cert.unwrap(); 171 | 172 | // Check CA fields 173 | assert_eq!( 174 | user_cert.signature_key.fingerprint().hash, 175 | "7mPQx8ezzmG9QpBbAVaA4kBwiWoNmIYodjlng3xzw4o" 176 | ); 177 | 178 | // Check that we can correctly reparse the serialized certificate 179 | let cert = format!("{}", user_cert); 180 | 181 | let cert = Certificate::from_string(&cert); 182 | assert!(cert.is_ok()); 183 | } 184 | 185 | #[test] 186 | fn create_and_reparse_sign_parse_verify_minimal_ecdsa384ca() { 187 | let ssh_pubkey = PublicKey::from_string( 188 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILHHgBVMG7TU30Z8lFfHPwBx98w3wkhoaybFc6/tjasI testuser", 189 | ); 190 | assert!(ssh_pubkey.is_ok()); 191 | 192 | let ssh_pubkey = ssh_pubkey.unwrap(); 193 | let ca_pubkey = PublicKey::from_string(ECDSA384_SSH_PUBLIC_KEY).unwrap(); 194 | 195 | let user_cert_partial = Certificate::builder(&ssh_pubkey, CertType::User, &ca_pubkey) 196 | .unwrap() 197 | .key_id("key_id") 198 | .valid_after(0) 199 | .valid_before(0xFFFFFFFFFFFFFFFF); 200 | 201 | let signature = test_ecdsa384_signer(&user_cert_partial.tbs_certificate()); 202 | assert!(signature.is_some()); 203 | 204 | let user_cert = user_cert_partial.add_signature(&signature.unwrap()); 205 | assert!(user_cert.is_ok()); 206 | let user_cert = user_cert.unwrap(); 207 | 208 | // Check CA fields 209 | assert_eq!( 210 | user_cert.signature_key.fingerprint().hash, 211 | "gxy7uWVVeYxXSb6Op57fSDdzSWF9T7HwqnDr7IID1m8" 212 | ); 213 | 214 | // Check that we can correctly reparse the serialized certificate 215 | let cert = format!("{}", user_cert); 216 | 217 | let cert = Certificate::from_string(&cert); 218 | assert!(cert.is_ok()); 219 | } 220 | -------------------------------------------------------------------------------- /tests/cert-offensive.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::Certificate; 2 | 3 | #[test] 4 | fn bad_signature_ecdsa_rs_length() { 5 | let cert = concat!( 6 | "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHWz37ZJkNhpEhC6pJkY", 7 | "cbKvPMgazcFt1hlgweWQVV/YAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uR", 8 | "Ufk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVP7+/v7+/v7+AAAAAQAAAAZrZXlfaWQAAAALAAAAB29iZWxpc2sAAAAAAAAAAAAAAABgKVzrAAA", 9 | "AAAAAAIIAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAABV", 10 | "wZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEPgO", 11 | "vv62WogLxC0i8XBvjd+KplMG1okf4IBC7zZvxpyCFbXQV8BgB38R1KbcoJcxhl5hXujr2HI1SDQ9tXCPLIwAAAGMAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAP8", 12 | "AAAAfeKNwK3aNCzo32Ha1GF+dUV5j72XsBKY2E6kQQvFfPwAAACEA8kuD4M0umeh0zG7MXaZNHJk2tg+7e1T64Eu0+WdbShs= key_id"); 13 | 14 | let cert = Certificate::from_string(cert); 15 | assert!(cert.is_err()); 16 | } 17 | 18 | #[test] 19 | fn bad_signature_ecdsa_length() { 20 | let cert = concat!( 21 | "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHWz37ZJkNhpEhC6pJkY", 22 | "cbKvPMgazcFt1hlgweWQVV/YAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uR", 23 | "Ufk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVP7+/v7+/v7+AAAAAQAAAAZrZXlfaWQAAAALAAAAB29iZWxpc2sAAAAAAAAAAAAAAABgKVzrAAA", 24 | "AAAAAAIIAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAABV", 25 | "wZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEPgO", 26 | "vv62WogLxC0i8XBvjd+KplMG1okf4IBC7zZvxpyCFbXQV8BgB38R1KbcoJcxhl5hXujr2HI1SDQ9tXCPLIwAAAP8AAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEg", 27 | "AAAAfeKNwK3aNCzo32Ha1GF+dUV5j72XsBKY2E6kQQvFfPwAAACEA8kuD4M0umeh0zG7MXaZNHJk2tg+7e1T64Eu0+WdbShs= key_id"); 28 | 29 | let cert = Certificate::from_string(cert); 30 | assert!(cert.is_err()); 31 | } 32 | 33 | #[test] 34 | fn bad_signature_ecdsa_r_offbyone() { 35 | let cert = concat!( 36 | "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHWz37ZJkNhpEhC6pJkY", 37 | "cbKvPMgazcFt1hlgweWQVV/YAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uR", 38 | "Ufk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVP7+/v7+/v7+AAAAAQAAAAZrZXlfaWQAAAALAAAAB29iZWxpc2sAAAAAAAAAAAAAAABgKVzrAAA", 39 | "AAAAAAIIAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAABV", 40 | "wZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEPgO", 41 | "vv62WogLxC0i8XBvjd+KplMG1okf4IBC7zZvxpyCFbXQV8BgB38R1KbcoJcxhl5hXujr2HI1SDQ9tXCPLIwAAAGMAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEg", 42 | "AAAAgeKNwK3aNCzo32Ha1GF+dUV5j72XsBKY2E6kQQvFfPwAAACEA8kuD4M0umeh0zG7MXaZNHJk2tg+7e1T64Eu0+WdbShs= key_id"); 43 | 44 | let cert = Certificate::from_string(cert); 45 | assert!(cert.is_err()); 46 | } 47 | 48 | #[test] 49 | fn bad_signature_ecdsa_s_bad_length() { 50 | let cert = concat!( 51 | "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHWz37ZJkNhpEhC6pJkY", 52 | "cbKvPMgazcFt1hlgweWQVV/YAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uR", 53 | "Ufk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVP7+/v7+/v7+AAAAAQAAAAZrZXlfaWQAAAALAAAAB29iZWxpc2sAAAAAAAAAAAAAAABgKVzrAAA", 54 | "AAAAAAIIAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAABV", 55 | "wZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEPgO", 56 | "vv62WogLxC0i8XBvjd+KplMG1okf4IBC7zZvxpyCFbXQV8BgB38R1KbcoJcxhl5hXujr2HI1SDQ9tXCPLIwAAAGMAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEg", 57 | "AAAAfeKNwK3aNCzo32Ha1GF+dUV5j72XsBKY2E6kQQvFfPwAAAP8A8kuD4M0umeh0zG7MXaZNHJk2tg+7e1T64Eu0+WdbShs= key_id"); 58 | 59 | let cert = Certificate::from_string(cert); 60 | assert!(cert.is_err()); 61 | } 62 | 63 | #[test] 64 | fn modified_principal() { 65 | let cert = concat!( 66 | "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHWz37ZJkNhpEhC6pJkY", 67 | "cbKvPMgazcFt1hlgweWQVV/YAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uR", 68 | "Ufk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVP7+/v7+/v7+AAAAAQAAAAZrZXlfaWQAAAALAAAAB2dyZWdvcnkAAAAAAAAAAAAAAABgKVzrAAA", 69 | "AAAAAAIIAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAABV", 70 | "wZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEPgO", 71 | "vv62WogLxC0i8XBvjd+KplMG1okf4IBC7zZvxpyCFbXQV8BgB38R1KbcoJcxhl5hXujr2HI1SDQ9tXCPLIwAAAGMAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEg", 72 | "AAAAfeKNwK3aNCzo32Ha1GF+dUV5j72XsBKY2E6kQQvFfPwAAACEA8kuD4M0umeh0zG7MXaZNHJk2tg+7e1T64Eu0+WdbShs="); 73 | 74 | let cert = Certificate::from_string(cert); 75 | assert!(cert.is_err()); 76 | } 77 | 78 | #[test] 79 | fn changed_pubkey_type() { 80 | let cert = concat!( 81 | "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHWz37ZJkNhpEhC6pJkY", 82 | "cbKvPMgazcFt1hlgweWQVV/YAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uR", 83 | "Ufk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVP7+/v7+/v7+AAAAAQAAAAZrZXlfaWQAAAALAAAAB29iZWxpc2sAAAAAAAAAAAAAAABgKVzrAAA", 84 | "AAAAAAIIAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAABV", 85 | "wZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDI1NgAAAEEEPgO", 86 | "vv62WogLxC0i8XBvjd+KplMG1okf4IBC7zZvxpyCFbXQV8BgB38R1KbcoJcxhl5hXujr2HI1SDQ9tXCPLIwAAAGMAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEg", 87 | "AAAAfeKNwK3aNCzo32Ha1GF+dUV5j72XsBKY2E6kQQvFfPwAAACEA8kuD4M0umeh0zG7MXaZNHJk2tg+7e1T64Eu0+WdbShs="); 88 | 89 | let cert = Certificate::from_string(cert); 90 | assert!(cert.is_err()); 91 | } 92 | 93 | #[test] 94 | fn changed_pubkey_curve() { 95 | let cert = concat!( 96 | "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHWz37ZJkNhpEhC6pJkY", 97 | "cbKvPMgazcFt1hlgweWQVV/YAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uR", 98 | "Ufk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVP7+/v7+/v7+AAAAAQAAAAZrZXlfaWQAAAALAAAAB29iZWxpc2sAAAAAAAAAAAAAAABgKVzrAAA", 99 | "AAAAAAIIAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAABV", 100 | "wZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDM4NAAAAEEEPgO", 101 | "vv62WogLxC0i8XBvjd+KplMG1okf4IBC7zZvxpyCFbXQV8BgB38R1KbcoJcxhl5hXujr2HI1SDQ9tXCPLIwAAAGMAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEg", 102 | "AAAAfeKNwK3aNCzo32Ha1GF+dUV5j72XsBKY2E6kQQvFfPwAAACEA8kuD4M0umeh0zG7MXaZNHJk2tg+7e1T64Eu0+WdbShs="); 103 | 104 | let cert = Certificate::from_string(cert); 105 | assert!(cert.is_err()); 106 | } 107 | 108 | #[test] 109 | fn actually_good() { 110 | let cert = concat!( 111 | "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHWz37ZJkNhpEhC6pJkY", 112 | "cbKvPMgazcFt1hlgweWQVV/YAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1uR", 113 | "Ufk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVP7+/v7+/v7+AAAAAQAAAAZrZXlfaWQAAAALAAAAB29iZWxpc2sAAAAAAAAAAAAAAABgKVzrAAA", 114 | "AAAAAAIIAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAABV", 115 | "wZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEPgO", 116 | "vv62WogLxC0i8XBvjd+KplMG1okf4IBC7zZvxpyCFbXQV8BgB38R1KbcoJcxhl5hXujr2HI1SDQ9tXCPLIwAAAGMAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEg", 117 | "AAAAfeKNwK3aNCzo32Ha1GF+dUV5j72XsBKY2E6kQQvFfPwAAACEA8kuD4M0umeh0zG7MXaZNHJk2tg+7e1T64Eu0+WdbShs="); 118 | 119 | let cert = Certificate::from_string(cert); 120 | assert!(cert.is_ok()); 121 | } 122 | -------------------------------------------------------------------------------- /tests/cert-options.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::Certificate; 2 | 3 | #[test] 4 | fn parse_check_critical_options() { 5 | let cert = concat!( 6 | "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIMfkr8HI/IgjjxXbqjKFUkrbmar75c179HDc++", 7 | "r2t5nsAAAAIB7fwcuszYuMUHSRn/Jgx0R5o8440VO5fuRzFwz6gBpv/v7+/v7+/v4AAAABAAAAD29iZWxpc2tAZXhjbGF2ZQAAABcAAAAHb2JlbGlzawAAAAhta", 8 | "XRjaGVsbAAAAAAAAAAA//////////8AAAAiAAAADWZvcmNlLWNvbW1hbmQAAAANAAAACS9iaW4vdHJ1ZQAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAA", 9 | "AAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXV", 10 | "zZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgRfH40I4T4iEKCKUmVECQq4wQqizmp0D0/U9lQ86GMkgAAABTAAAAC3NzaC1lZDI1NTE5AAAAQG", 11 | "177VlVbAR36GpGzTor6r0q6kkpobH8g/JRoNzAe6CRgz5b202PpA61gCw/kgR8qY7wDaRu9MYNTUnvPRGYGgc= obelisk@exclave.lan"); 12 | 13 | let cert = Certificate::from_string(cert); 14 | assert!(cert.is_ok()); 15 | let cert = cert.unwrap(); 16 | 17 | assert_eq!(cert.critical_options.len(), 1); 18 | assert_eq!(cert.critical_options["force-command"], "/bin/true"); 19 | } 20 | 21 | #[test] 22 | fn parse_check_extensions() { 23 | let cert = concat!( 24 | "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHWz37ZJkNhpEhC6pJk", 25 | "YcbKvPMgazcFt1hlgweWQVV/YAAAAIbmlzdHAzODQAAABhBCEPn99p8iLo9pyPBW0MzsWdWtvlvGKfnFKc/pOF3sV2mCNYp06mgfXm3ZPKioIjYHjj9Y1E4W8x1", 26 | "uRUfk/MM7ZGe3prAEHs4evenCMNRqHmrTDRSxle8A7s5vUrECtiVP7+/v7+/v7+AAAAAQAAAAZrZXlfaWQAAAALAAAAB29iZWxpc2sAAAAAAAAAAAAAAABgKVzr", 27 | "AAAAAAAAAIIAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAA", 28 | "AABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAE", 29 | "EEPgOvv62WogLxC0i8XBvjd+KplMG1okf4IBC7zZvxpyCFbXQV8BgB38R1KbcoJcxhl5hXujr2HI1SDQ9tXCPLIwAAAGMAAAATZWNkc2Etc2hhMi1uaXN0cDI1N", 30 | "gAAAEgAAAAfeKNwK3aNCzo32Ha1GF+dUV5j72XsBKY2E6kQQvFfPwAAACEA8kuD4M0umeh0zG7MXaZNHJk2tg+7e1T64Eu0+WdbShs= key_id"); 31 | 32 | let cert = Certificate::from_string(cert); 33 | assert!(cert.is_ok()); 34 | let cert = cert.unwrap(); 35 | 36 | assert_eq!(cert.extensions.len(), 5); 37 | assert_eq!(cert.extensions["permit-agent-forwarding"], ""); 38 | assert_eq!(cert.extensions["permit-user-rc"], ""); 39 | assert_eq!(cert.extensions["permit-port-forwarding"], ""); 40 | assert_eq!(cert.extensions["permit-X11-forwarding"], ""); 41 | assert_eq!(cert.extensions["permit-pty"], ""); 42 | } 43 | -------------------------------------------------------------------------------- /tests/cert-sk-ecdsa.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::Certificate; 2 | 3 | #[test] 4 | fn parse_ed15519_signed_by_sk_ecdsa() { 5 | let cert = Certificate::from_string(include_str!("certs/ed25519_signed_by_ecdsa_sk-cert.pub")); 6 | assert!(cert.is_ok()); 7 | let cert = cert.unwrap(); 8 | assert_eq!( 9 | cert.key.fingerprint().hash, 10 | "tUSfkfiocNgVTbFE2caqokLbZlw7G6qwMHLFZRAn2yk" 11 | ); 12 | assert_eq!( 13 | cert.signature_key.fingerprint().hash, 14 | "Ylfgx0U2M9/IVN0+b5/IxdNeVCotsdrRZ5lu5FG2ouc" 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /tests/cert-sk-ed25519.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::Certificate; 2 | 3 | #[test] 4 | fn parse_ed15519_signed_by_sk_ed25519() { 5 | let cert = Certificate::from_string(include_str!( 6 | "certs/ed25519_sk_signed_by_ed25519_sk-cert.pub" 7 | )); 8 | assert!(cert.is_ok()); 9 | let cert = cert.unwrap(); 10 | assert_eq!( 11 | cert.key.fingerprint().hash, 12 | "GlvFAEnledYF0XG1guJ7dT2d0Mk88GmPAiHk8+zCBlA" 13 | ); 14 | assert_eq!( 15 | cert.signature_key.fingerprint().hash, 16 | "GlvFAEnledYF0XG1guJ7dT2d0Mk88GmPAiHk8+zCBlA" 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /tests/certs/ecdsa384_signed_by_ecdsa384-cert.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgbcfLWkspBR1ZFjToBgo/76WBwm+ZDLTD8gafAKfcFRIAAAAIbmlzdHAzODQAAABhBFLokpkryGhSfa6c1XkoYYdSghpoc5OAKn+y3vTAfw6Bi+Q6Y1vJV81jCoTPWQoxgp4wZ+2vXYytUuaiwAc03KHKazsCCTUUR9FHKafx8E20Pub67yTRpBCU9JTF2lIjkwAAAAAAAAAAAAAAAQAAAAZrZXlfaWQAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKxqBT7nMZAzlLGD4VEZTyNE9E5WWNWcbKimFQVIq+ipbLo6FOkPc1WQtygGLxjkcCPP4o8oCJz0Rwu0oWYh6SMZhcxHrN0nfQcgKP2HtKn5DbBJ23+wOCRPF/M14bt/KAAAAIQAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAGkAAAAwDk644VySsfLHsU2Ota7LEtHDF/vmBhDzsZCU3lhwsWaDYUvoVRlYBLHOdHJchDvsAAAAMQDaDjkxYI9NakfgSMIDwgcyS420vMAjJcBKp1SaSXeLuzJagw1xVZdoHyKEZhC6Aoc= obelisk@Mitchells-MBP.localdomain -------------------------------------------------------------------------------- /tests/certs/ed25519_signed_by_ecdsa_sk-cert.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIFGoPJUvWbCLvihHmdZeG+FyY6Na8No9+j+7bpVdeBKIAAAAIBQEi9AZe6XqMXgDKbbOH+9Ut33SNMfSdXEDow2npqZUAAAAAAAAAAAAAAABAAAABmtleV9pZAAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAAAiwAAACJzay1lY2RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQSywahW//lH0adKvyZTKlyndXuw5D4kFH/FT3oajUTzmuzWnoqWqhq2EXmybr3vxfUdHxyie+FMuiX/sjZb5rjhAAAAEHNzaDpTU0hDZXJ0c1Rlc3QAAAB3AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAABIAAAAIFjfewmnFl08ga8mfLs/rBEOina846G+f+UcER4j+gxZAAAAIDSAEkp00dZiWgOfI/axLrWtdZru2kh1Ey+/g753yp+zBQAAACg= TestingKey 2 | -------------------------------------------------------------------------------- /tests/certs/ed25519_signed_by_ed25519-cert.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIHdG/L+a0dWOwvJqSHEm4g0QlnKR2W4gQx6vr2VYXmeBAAAAIDRlrLwx21DZPH4pLkK6cJBn2bvGS3PGqwqa61XgwVkHAAAAAAAAAAAAAAABAAAABmtleV9pZAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgmphXLY9IKmgIb/agroxKpzIjdrOXeorOHenjzAw1fY4AAABTAAAAC3NzaC1lZDI1NTE5AAAAQLPq9TFwDBNvYehvIgeCXgf9N16CiJRxckC+P2cyVEozAQf8rclW56tMl9R4y0f2gchkkenEO9Wd1fzqG7akyQU= obelisk@Mitchells-MBP.localdomain -------------------------------------------------------------------------------- /tests/certs/ed25519_sk_signed_by_ecdsa384-cert.pub: -------------------------------------------------------------------------------- 1 | sk-ssh-ed25519-cert-v01@openssh.com AAAAI3NrLXNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIEw0wEOAfyvyKOFVYYVrdD77CUj18t2ew9ARDB8D4fx2AAAAIGYMHSzST3lQhQKYYSdosWFQZiP2YSFwCySgOyC93jWCAAAABHNzaDoAAAAAAAAAAAAAAAEAAAAGa2V5X2lkAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQSsagU+5zGQM5Sxg+FRGU8jRPROVljVnGyophUFSKvoqWy6OhTpD3NVkLcoBi8Y5HAjz+KPKAic9EcLtKFmIekjGYXMR6zdJ30HICj9h7Sp+Q2wSdt/sDgkTxfzNeG7fygAAACEAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAABpAAAAMQCWzPisG5FkU3TSUJVvzGJNoI0s5YLLp6oSUIO5niDohwZ2YFIwg3ZrnpuGWQVBZnYAAAAwdA7I3/P8shaP6OLtnTypHg+TnRNXj9htFTXzDI4NNV+j7rkEgbei81vilEVaTgbI obelisk@Mitchells-MBP.localdomain -------------------------------------------------------------------------------- /tests/certs/ed25519_sk_signed_by_ed25519_sk-cert.pub: -------------------------------------------------------------------------------- 1 | sk-ssh-ed25519-cert-v01@openssh.com AAAAI3NrLXNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIKNBf7pMH9Pewsrh3tTMrhQ1RtRBodhPI8bO/go1Dq3/AAAAIIVTblnROOE/e2jCl6ieSgqPjWtnxjzmpCHU+TJ3EbL8AAAAEHNzaDpTU0hDZXJ0c1Rlc3QAAAAAAAAAAAAAAAEAAAAGa2V5X2lkAAAAAAAAAAAAAAAA//////////8AAAAAAAAAAAAAAAAAAABWAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIIVTblnROOE/e2jCl6ieSgqPjWtnxjzmpCHU+TJ3EbL8AAAAEHNzaDpTU0hDZXJ0c1Rlc3QAAABnAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAQMgPgGPN2GHabMnLcdlv3O789MxbjqsqI3OkRyWj/87Il5+ofIxbhKlb4lR3feuFXB63zQdG3loIzGH9sL9A/gsFAAAAKQ== TestEd25519KeyGeneration 2 | -------------------------------------------------------------------------------- /tests/keys/public/k1.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBED+0IKpvYRIItafkX/C+iXgMqT5TBa/Ey6TKBMh8mAcWcWvCzClW6qZo59qclUdu+Pm+k8hBWIXsdQfv3IlWvRxN9hDK4duJDKJPU0uKzUUxsxK09iIIBQ//18qw6Y/XA== -------------------------------------------------------------------------------- /tests/keys/public/k2.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBF22T16o0pemQYfg49+cw2vOSRHRFjaFzI7OlAHbPT0C2MEiUeJps8+mfbcU2dgjkaxPb2z2PwpEGoj3b/L4u3g4wxUlkNrHNPdcNw7h55s2DjbJwQZrrlx2Yu+3hYkT/w== Comment -------------------------------------------------------------------------------- /tests/keys/public/rsa-sha2-256-4096.pub: -------------------------------------------------------------------------------- 1 | rsa-sha2-256 AAAADHJzYS1zaGEyLTI1NgAAAAMBAAEAAAIBAO5+ipxVjbvYFwmsD2242ZOmPjgyf1slPycSFcET9KzXMkDow1zowvsVz98hCuWDg4P7QJDcfB6euVsTHylVW3JIZlkSYznO02KYC2PWkwucA3vWy9vNdcks1l7NF1E4NYuWGD1pRyqV0hZshBShmYr32XFAnCt3gPWIygpD0rXWG1A0Wvl5+eMjPdhX6FfiSaun64Bgmsc+IOvukYwg8gmoOd3/q5oRIGPRaaYaCHG6/XgCFcU8cRrk9wicBt+Zh9XamwVXTp36a/CB+54oLmj4c3OQ/88WHuzpzRi5tDES1y37/9U9SLsOGygkqoNuWTYJlDhJ13BAZkUcshOL+JdrtvVxrXugvwLscEh/OguYYCEVmpxsFI/LN22sHH6XiRD7R+PewUfPKHzCBoAfTp6VsA9iI8zj5NVBbqtl0+P+NcdSqdhKDcq7AKvdCaj3rYgSNPURohODrxr1cQoTF1xIHX323BRbFAasy5XZqQO0uNMUWPO/6/KuxJOWfe6IH/CSofa67b5u2QZ7+oUhiTVQK4q7Kumb9lE0VPVCrevmHAM3acJy56E6yI30A6CXu0Vik0AR+vFBZUNJ6wccLFLqzs8v17stY6yMQJUv0+srITjV8Kar6HT/S7N2IYNlpG+b72dgJCsSAv0bZAIz5v4l8BphB2uc58gpbNYd9QbD obelisk -------------------------------------------------------------------------------- /tests/keys/public/rsa-sha2-512-4096.pub: -------------------------------------------------------------------------------- 1 | rsa-sha2-512 AAAADHJzYS1zaGEyLTUxMgAAAAMBAAEAAAIBALnQ2Ticd+BeQW8W7fJO/pE11q3AWjimConyJ+gJUtx54VphPjnBxmxxzrrGrEGhWktmKYf4FP8iFbEzNwK7TdQr08EpMfmDyidcBiLIB2QsxkuK76XKIDRQINu0Gho9d5CC0PEE7y/iVY+ZyTW8v0ba3Opr2Evywnuw4Rl8kZ11BKgsjVDhzrTJ4yoe0HZ06FPXnTBROt0mxMT2kEITnTqQwKfshxzf8lqjT6/M/+uQhGWLh5Hk4FE2m76N5HbFsqEK0bRen2dpFAQk6R/fy838f6B2miZOMPfqg7y2phJGNdbog4jVmPXRSraGyRnp9jiC1+9KKXqI7w+/Ogf4t5rv1lbPa07/2z5xdogFSA+pn8Vg4guwy+9wpiDRD/hG7AkFcZdjhQ40F555e43Ol7jQvFmrIgjX6jyW56zB5RVGRd9dJvuTCvHotL/MOnLJ/3PHl8NrvFvzLdVojIiI2QYxWF0NrxaAWuMZNLNBsb/qGNFfym3Q1+rNdtR2V2qDR7RzagfHyvSB6iLD9XrDSiiHR0fR2XiLM9xIZNtrlfiLknUyjvQAR27uL2Ull94R1Qa1w8yQ2weJhQxNql9kwsjix4Vj7JSJbRLBipQbNyHVK9ioN2xbAN66pwWL/zpuR6pN/qbbW3aj/gayhOp1OHiVXT/rFle7Xtx/ALbqeZLz obelisk -------------------------------------------------------------------------------- /tests/keys/public/rsa-sha2-512-8192.pub: -------------------------------------------------------------------------------- 1 | rsa-sha2-512 AAAADHJzYS1zaGEyLTUxMgAAAAMBAAEAAAQBAM4V62byzw1AccFwBYCx1eOjjEll9JQjGoU9930OtaAlb6rEeGhBm0savoBbbAG8DE+/H0RtDOBMBwOgC7kA9Y6001vQayS7qVfPPNknX4fdx4ld6x4N+ttFCwnKPIUhX8osf68ZZr1RaAIsoBeVCSYhlONX+bQlKHR35qC1Tbqq+o78a8c1uB2A8i9dEc3SNgYvp6z+XCbSqsxRA+DMvlddHrkxi/3fGciRUr11qf2pctRVSYYtGaQC0caPbOx/oYpkGIT9pC2JGLQKUky7y6T+IEJOWTXp1pdJ8d77ZfaRF9rhzWc9xDn5sV0Gj7HuWl4zhOx7Yk2Tkc1dVrigSTAP9LMy7PHW3+dpdQPAlyC8DTHKRmhL7OBM1gCkeivxeLEPFO+QIfxN54v3BGuyY5ZaeBKpmK/9yF7xoZAXoXsZ52BIfp2/6KSSy/OWwSJtaL184S1K2+FqKuQFaMLCyq5rSvGkA0Lo2GtSPkDMrl3KbUXNxC2qAj2VMersfnJ2vMIPRhv3TYgWXz37khcsKiUCvRIxPJ8bjzu9ov6ASnjwBVJ+S8Rhk5Z1qFulEM/E5Tf8kpHuKpIW/FYu8a2kKejvadXt+3CQKmqFG5x9nrP6cw6YZrnxbb99yGUyOdP/SP/2eye9qljHkqsVvYTc9BlIuiRi8fwnnb2AQTp3Uh47HxagUYoefyptjs6xeUF6H/biimRdJ5a5b34UByoRI+Eykk9gP31iH7ENLOA/Ha+q9gWYv9ssd2CLdd0cFMejuJnXrod74qjCyCF4p9z50KGr3mEzrVdKp46AJ64HcvlQAAgwl0TKDaEhF3oLUuvzAWrkz0vCaEm4AuE31jnFdHkiu0/fAjct+wBGcl4YW3AGL8AhUsRzfdykTtzKrNWDwzzx0/U61X/0XOGfJmiYicm1ncGzwO/zxuyZ9PwJfMC+oBwkzwqtJmii/k8NYhagGsTCwYouAiCYL8f7z/3vTxqdU93fVVFSmaqRKSe+inu8qwkmKfucprgMbcHb4RT7xXPpnD887zCqFSuKzgZSnuX3CcjDt0SS2J1hHh3PpHOi598CPDPSlLItzNsl6KIsL4OiYExTTvuh1/uE06YgoVcF85lP0by3eyTrsClqvexTUA8AoYeXcnNvdy/srRYUD95jBHjEGd96Q4/8QwDe+sn7CqX2bAHz2++TC5DtnHKbc7rXivcdnCWRI84jNMrPZdN0QBWzh1M7en5tQLpZA1hXHnS4LCM1KBnz9KQIhGg9Wo/WEl5YNNJZ9lOQwDdT30mzaRM7B1YiR++KZSpna4rNKIRUvirSVxG+DE2kIRBHMqDO/3fMU9R/UdCacRgVfma6fdTduSfkrlSRgcQBZ+8= obelisk -------------------------------------------------------------------------------- /tests/keys/public/test.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEXO591YkzOIJ+CZVFwL47iitgPxnKwPpX6ScH6/LrVxN9uNvkE1j65bRSdlTxVB2aBFSf6X3C+hNkDidcN0Ww9xibzzGujeguT950RrHkN+NKvGJyftxFoPzAZwJ95LFQ== obelisk@MB-YG7VJMWGXY -------------------------------------------------------------------------------- /tests/keys/sk/ecdsa: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiwAAACJzay1lY2 3 | RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQSywahW//lH 4 | 0adKvyZTKlyndXuw5D4kFH/FT3oajUTzmuzWnoqWqhq2EXmybr3vxfUdHxyie+FMuiX/sj 5 | Zb5rjhAAAAEHNzaDpTU0hDZXJ0c1Rlc3QAAAD4GP0IcBj9CHAAAAAic2stZWNkc2Etc2hh 6 | Mi1uaXN0cDI1NkBvcGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEEssGoVv/5R9GnSr8mUy 7 | pcp3V7sOQ+JBR/xU96Go1E85rs1p6KlqoathF5sm6978X1HR8convhTLol/7I2W+a44QAA 8 | ABBzc2g6U1NIQ2VydHNUZXN0AQAAAEBsrUsovCYDhSmapkXZQWLLpLTMTe3AngOXZnLujm 9 | 2jQHLhkow98oDykpXY3cZbXj7ZFyel4Jb3HqPDWznTfeXNAAAAAAAAABZUZXN0RUNEU0FL 10 | ZXlHZW5lcmF0aW9uAQI= 11 | -----END OPENSSH PRIVATE KEY----- 12 | -------------------------------------------------------------------------------- /tests/keys/sk/ecdsa.pub: -------------------------------------------------------------------------------- 1 | sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBLLBqFb/+UfRp0q/JlMqXKd1e7DkPiQUf8VPehqNRPOa7NaeipaqGrYRebJuve/F9R0fHKJ74Uy6Jf+yNlvmuOEAAAAQc3NoOlNTSENlcnRzVGVzdA== TestECDSAKeyGeneration 2 | -------------------------------------------------------------------------------- /tests/keys/sk/ed25519: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAVgAAABpzay1zc2 3 | gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACCFU25Z0TjhP3towpeonkoKj41rZ8Y85qQh1Pky 4 | dxGy/AAAABBzc2g6U1NIQ2VydHNUZXN0AAABCO4LG4juCxuIAAAAGnNrLXNzaC1lZDI1NT 5 | E5QG9wZW5zc2guY29tAAAAIIVTblnROOE/e2jCl6ieSgqPjWtnxjzmpCHU+TJ3EbL8AAAA 6 | EHNzaDpTU0hDZXJ0c1Rlc3QBAAAAgM35ESFt2SVXhhrFxfY65NSfPapXqj1pO+7Ts5h3DW 7 | 642qQK7Px79pir/sMalsLzUSowKPJUubs/SRFUC+7AH0+XPXIWYthOKWHan+KWXq6Z68u7 8 | Qc2ofeTHwAsS8fDW5iknv/NAds54IFGuX+XWh0fSNY0CIAnKw7O9ynOOm4vjAAAAAAAAAB 9 | hUZXN0RWQyNTUxOUtleUdlbmVyYXRpb24BAgMEBQ== 10 | -----END OPENSSH PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /tests/keys/sk/ed25519.pub: -------------------------------------------------------------------------------- 1 | sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIIVTblnROOE/e2jCl6ieSgqPjWtnxjzmpCHU+TJ3EbL8AAAAEHNzaDpTU0hDZXJ0c1Rlc3Q= TestEd25519KeyGeneration 2 | -------------------------------------------------------------------------------- /tests/keys/unencrypted/ecdsa_256_1: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 3 | 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQWBFNpmjacrlnUn4wMQzt7OTY6iwkf 4 | hRR5gA6N7HaGZdd2pUwxyKaCbsH5ArDYTb+YCzrAmO+pibJ9qCdXr6MqAAAAsKCPNjagjz 5 | Y2AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBYEU2maNpyuWdSf 6 | jAxDO3s5NjqLCR+FFHmADo3sdoZl13alTDHIpoJuwfkCsNhNv5gLOsCY76mJsn2oJ1evoy 7 | oAAAAhAIZBrb9Pe0m+BkbHv0oVUfadm3kev4Nt407zcuNiEqHcAAAAE29iZWxpc2tAZXhj 8 | bGF2ZS5sYW4BAgME 9 | -----END OPENSSH PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /tests/keys/unencrypted/ecdsa_384_1: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS 3 | 1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQQUPB58xc9cxcLp1swjJ87XjD2butH5 4 | blSJ+VPO+/ZV+yFn7ElXIRzvm+i2aPj0/fc54LSdE8SZlAu7RLuyAgwH8IqfhLATG3mq7f 5 | DPSnRSn7zErjIUGOvRS7g5vhBH9R0AAADgGCxIIhgsSCIAAAATZWNkc2Etc2hhMi1uaXN0 6 | cDM4NAAAAAhuaXN0cDM4NAAAAGEEFDwefMXPXMXC6dbMIyfO14w9m7rR+W5UiflTzvv2Vf 7 | shZ+xJVyEc75votmj49P33OeC0nRPEmZQLu0S7sgIMB/CKn4SwExt5qu3wz0p0Up+8xK4y 8 | FBjr0Uu4Ob4QR/UdAAAAMQCp0le4OzcKkZTB2sFAlekoOP6+mK2/UUMsAhQ2nH+n44hgcX 9 | equV2T0EVE8vPmDgsAAAATb2JlbGlza0BleGNsYXZlLmxhbgECAwQ= 10 | -----END OPENSSH PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /tests/keys/unencrypted/ed25519_1: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACAztFUA/UyHSAmS1hVsLX+7PP2hDb3vLcBkxJjVdJsoeQAAAJgzkRiyM5EY 4 | sgAAAAtzc2gtZWQyNTUxOQAAACAztFUA/UyHSAmS1hVsLX+7PP2hDb3vLcBkxJjVdJsoeQ 5 | AAAEDJnaJY4O5n62ipU6NGquweXk5WDdCvMDO8Y6IxtsSxLTO0VQD9TIdICZLWFWwtf7s8 6 | /aENve8twGTEmNV0myh5AAAAE29iZWxpc2tAZXhjbGF2ZS5sYW4BAg== 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /tests/keys/unencrypted/ed25519_2: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACDNCX6XlZn0QRMW14ABZa5GZc66U+csEiKsgkZwGK0+FAAAAJiT9ajkk/Wo 4 | 5AAAAAtzc2gtZWQyNTUxOQAAACDNCX6XlZn0QRMW14ABZa5GZc66U+csEiKsgkZwGK0+FA 5 | AAAED6HgUU3Ps5TVdFCVO8uTpbfVdg3JBxnOz3DIWO1u1Xbc0JfpeVmfRBExbXgAFlrkZl 6 | zrpT5ywSIqyCRnAYrT4UAAAAE29iZWxpc2tAZXhjbGF2ZS5sYW4BAg== 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /tests/keys/unencrypted/rsa-2048: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn 3 | NhAAAAAwEAAQAAAQEAtRTHlBQoQW2O0s0B9C0yNXnNCP7oehwfoKtU/iCfdn9YkxHM/WMe 4 | yDZM0rN/xzU/v+pBF860T7v8epUkTToCqdWIf2P8GCstuDGyLELaO79DIWoLskuPm4zUEl 5 | 6IHweabJFyeXzTiCkSI5HP6rozh2PH/UkGdSBkkff8buSdX//Ej/vEcuRoyV2BGx2qnhNe 6 | 5OoGdV+itzec1tBAL9HUoIGWUr12T98UV2pjMW/nLD5yYM4V5V9fDbY/zeQeo3cZGnr22M 7 | 1wL+RSado4PRwYFzj7ch6C4BZp09yRAtypaSrPc4StXxavgKpJgy1PxIFdFKB2Pyg4Zj4N 8 | S75V1guBAwAAA9A1Wgm4NVoJuAAAAAdzc2gtcnNhAAABAQC1FMeUFChBbY7SzQH0LTI1ec 9 | 0I/uh6HB+gq1T+IJ92f1iTEcz9Yx7INkzSs3/HNT+/6kEXzrRPu/x6lSRNOgKp1Yh/Y/wY 10 | Ky24MbIsQto7v0MhaguyS4+bjNQSXogfB5pskXJ5fNOIKRIjkc/qujOHY8f9SQZ1IGSR9/ 11 | xu5J1f/8SP+8Ry5GjJXYEbHaqeE17k6gZ1X6K3N5zW0EAv0dSggZZSvXZP3xRXamMxb+cs 12 | PnJgzhXlX18Ntj/N5B6jdxkaevbYzXAv5FJp2jg9HBgXOPtyHoLgFmnT3JEC3KlpKs9zhK 13 | 1fFq+AqkmDLU/EgV0UoHY/KDhmPg1LvlXWC4EDAAAAAwEAAQAAAQBDmE4n6J5eThdSeVSR 14 | YY2siJsREJaXfogP4eUIOAVOpprZy3tJ2wZSlnJ29KpuImJ5sWg7Ct4kJXhKCgJTEaSM4T 15 | ji1N/15ahbabGo9Aui4fKdNojHNY2V8yv273LAooXyoRiqYP5VhX9P585FQNybXZP56JiQ 16 | gFcKZhfFDXHxs1bHaLeJfGzASoZrpAd1MaHlJPjfDncUevutm3nbbDZtFU4ZYbrPUYhe2B 17 | KBuUwunUfab9T5VkMgc3OtPiKGNaCZfMsZmOgyW27Jz0mPzSPsYFev6JifqIWtmaAW0mfu 18 | /Mt72CYv75AAQ41NmxDckloBuuPv8/NlCuhBJMEctI2BAAAAgGgXp9ryoHMEwKavUdDKX/ 19 | 0oUk0//95fyUWsw8lqTjAZ4R/QKuJWmelBBnt8E2aSgUsNWorIEBDmbL003TmPwTDIfjvB 20 | 2JRkbCMov+oN6ZpxPA3s0q55uPhxmuJSOqtzcppJmDpTFJUtmEZKKOnjh6VY2Ea4M7KK5m 21 | mwjL3QYDuUAAAAgQDqjC1nnuIwSxH/4JnhRXTpFaTWqbQPm83yj8FhBfyMH6M/pZqkvFvg 22 | D1IlqaN+m1214AOHOzW+d8STMBPjnRamGDFvpgN1K23ZOpTqhh0aV4XrfOIUWi8kHbkRpQ 23 | HRFdN07JYXefJX4RNgI+khQFsMqhQKfg5jAmbW3MTAiHHkBQAAAIEAxaS2GKlFzAiEBpsD 24 | KwdSCb8gCb80Ig+rzFfkxM4EpX89J3mRx9q1WkjCgJE365ShNnqEE1cq1hfM9sfRvWg84g 25 | YqVpOwWFNIwMopz1CgWr16bbdIu3RPsU4BMti43qxdRYIJ2vV9amCZR/UyHYagouqUu6wW 26 | 88Sh/47RYGFMJ2cAAAAZb2JlbGlza0BtaXRjaGVsbHMtbWJwLmxhbgEC 27 | -----END OPENSSH PRIVATE KEY----- -------------------------------------------------------------------------------- /tests/keys/unencrypted/rsa-sha2-256-4096: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | 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 49 | -----END OPENSSH PRIVATE KEY----- 50 | -------------------------------------------------------------------------------- /tests/keys/unencrypted/rsa-sha2-512-4096: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | 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 49 | -----END OPENSSH PRIVATE KEY----- 50 | -------------------------------------------------------------------------------- /tests/keys/unencrypted/rsa-sha2-512-8192: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | 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 93 | -----END OPENSSH PRIVATE KEY----- 94 | -------------------------------------------------------------------------------- /tests/keys/unencrypted/rsa_2048_1: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn 3 | NhAAAAAwEAAQAAAQEAw36k9Ml2c6B2K0wwa7xW464zlUPcAzi+jxJhE6D1azWr41zgH5mQ 4 | y3emsymA4n73lV4wVFfkbpu3M+TFxwYdOOjcXxuIOjlZlUMRfnM1j9Dpcty+FcMd0OJmw9 5 | 7IUyxd+mL9xIW+g8xo/0g7whYE42Ks5LrqTeyiq5OA1MU2wr4KAO1tEiBmA+36yMOuCsad 6 | TG+wAMoec9Fks2MpdeZUVRpxAUW+QRBDEDvHL5JlBFL5dIwC2PoWsHo43nc1o14kHf8TuB 7 | PBXyjbiHtElEx2TNA23gb1m7Hn+TyNfx8N0wrRVxDgbDGE0MEmwxohRA6jUPve3YXLSsrn 8 | qBXX26HZBQAAA8guZeGKLmXhigAAAAdzc2gtcnNhAAABAQDDfqT0yXZzoHYrTDBrvFbjrj 9 | OVQ9wDOL6PEmEToPVrNavjXOAfmZDLd6azKYDifveVXjBUV+Rum7cz5MXHBh046NxfG4g6 10 | OVmVQxF+czWP0Oly3L4Vwx3Q4mbD3shTLF36Yv3Ehb6DzGj/SDvCFgTjYqzkuupN7KKrk4 11 | DUxTbCvgoA7W0SIGYD7frIw64Kxp1Mb7AAyh5z0WSzYyl15lRVGnEBRb5BEEMQO8cvkmUE 12 | Uvl0jALY+hawejjedzWjXiQd/xO4E8FfKNuIe0SUTHZM0DbeBvWbsef5PI1/Hw3TCtFXEO 13 | BsMYTQwSbDGiFEDqNQ+97dhctKyueoFdfbodkFAAAAAwEAAQAAAQBQY1JnijRYJaKR+Vlr 14 | 492C8E4r0eS1um65nGAo1yYz+LT+4GEU/WvKDyCQSb/C41zhogKTnDTFuuhhwXAGgCAiF7 15 | cOsN6aupx4wUR/T9a1lb15SwYrIrmu/HovjvunH/ZhELWzSx/lSHfgiY5xJ+bIrfnUcHwK 16 | oQTfMt9mHtZ+yzd3X5KbV5JPbCFj9L1rCS4x9yG8jAUhLsFR+kDXkWVtvl8KMvp5z5ptCz 17 | 9QcL/EN120y+p2EqvSHnclkUIeCzjuFMQrY3MqSDgi7fbNEmB5yGU1Se3cmaNMggKCRYcz 18 | k4Cw6xprLRJmo26XioTYIOTYLTn5976lXBEV2kdPMzF9AAAAgEEG2EONFQeeYnitQOZcTR 19 | XYL9ifG3Np8IMa+X3wDRZASw0Z1lMBj3M9IxKhKynPxrkVYgTAjnPwJ92zTe7BuGTD2qKS 20 | Zi6PgS72dOta6eHIJCgvOAeZ2atuCcbUy7eddGbu2d7rwHBlGIPKNKBWCALcLsZxCYLblF 21 | xqSQtr0/O/AAAAgQD5TbiYzf26CzbVUIwyWI7nlvSa3au/QI24xCnESKx9hLJTQnQ0r6MQ 22 | mONvZbjJ9Dci1/BWRy9HDA5VILN0FA4ohyAnV7iEr4i1yVtuB9jcQsjKxbEQZ8GSPn1N2j 23 | BC5/zEnpJEi+uz5KlPOH6krYEIXsV3SckH22dq9LxxvLD+KwAAAIEAyL7rNXSg2K92Dt6g 24 | +ILgVPcjQPOa1h8i2xeXX+9HJdkU4mDIlR8SXIOVXSO+Ewzs2GyyU+lWcbRW+pZKbEVnV5 25 | Qx4SMZJPClPqI3dzi40Z5pRqHgW4XPQkOGvvGB3iTfBmZJ2vpkN+E3xt7FbrZ5RGpLSGrJ 26 | uWv2SXiQNYRrHY8AAAATb2JlbGlza0BleGNsYXZlLmxhbg== 27 | -----END OPENSSH PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/messages/Test: -------------------------------------------------------------------------------- 1 | Test 2 | -------------------------------------------------------------------------------- /tests/privkey.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::{PrivateKey, PrivateKeyKind}; 2 | 3 | use std::io::BufWriter; 4 | 5 | #[test] 6 | fn parse_ecdsa_256_private_key() { 7 | let privkey = include_str!("keys/unencrypted/ecdsa_256_1"); 8 | 9 | let privkey = PrivateKey::from_string(privkey); 10 | assert!(privkey.is_ok()); 11 | let privkey = privkey.unwrap(); 12 | assert_eq!( 13 | privkey.pubkey.fingerprint().hash, 14 | "26PWf/RCJx3H/oKI7peJVhnDH/cHTSccHFbFltW7/jk" 15 | ); 16 | 17 | let key = match &privkey.kind { 18 | PrivateKeyKind::Ecdsa(key) => key, 19 | _ => panic!("Wrong key type detected"), 20 | }; 21 | assert_eq!( 22 | hex::encode(&key.key), 23 | "008641adbf4f7b49be0646c7bf4a1551f69d9b791ebf836de34ef372e36212a1dc" 24 | ); 25 | 26 | let mut buf = BufWriter::new(Vec::new()); 27 | privkey.write(&mut buf).unwrap(); 28 | let serialized = String::from_utf8(buf.into_inner().unwrap()).unwrap(); 29 | assert_eq!(include_str!("keys/unencrypted/ecdsa_256_1"), serialized); 30 | } 31 | 32 | #[test] 33 | fn parse_ecdsa_384_private_key() { 34 | let privkey = include_str!("keys/unencrypted/ecdsa_384_1"); 35 | 36 | let privkey = PrivateKey::from_string(privkey); 37 | assert!(privkey.is_ok()); 38 | let privkey = privkey.unwrap(); 39 | assert_eq!( 40 | privkey.pubkey.fingerprint().hash, 41 | "qFsuxU5ubR/H/GEmI0lWsYuF6llMop6VDYMxov0wNAM" 42 | ); 43 | 44 | let key = match &privkey.kind { 45 | PrivateKeyKind::Ecdsa(key) => key, 46 | _ => panic!("Wrong key type detected"), 47 | }; 48 | assert_eq!(hex::encode(&key.key), "00a9d257b83b370a9194c1dac14095e92838febe98adbf51432c0214369c7fa7e388607177aab95d93d04544f2f3e60e0b"); 49 | 50 | let mut buf = BufWriter::new(Vec::new()); 51 | privkey.write(&mut buf).unwrap(); 52 | let serialized = String::from_utf8(buf.into_inner().unwrap()).unwrap(); 53 | assert_eq!(include_str!("keys/unencrypted/ecdsa_384_1"), serialized); 54 | } 55 | 56 | #[test] 57 | fn parse_ed25519_private_key() { 58 | let privkey = include_str!("keys/unencrypted/ed25519_1"); 59 | 60 | let privkey = PrivateKey::from_string(privkey); 61 | match &privkey { 62 | Ok(_) => (), 63 | Err(e) => println!("{}", e), 64 | }; 65 | assert!(privkey.is_ok()); 66 | let privkey = privkey.unwrap(); 67 | assert_eq!( 68 | privkey.pubkey.fingerprint().hash, 69 | "QAtqtvvCePelMMUNPP7madH2zNa1ATxX1nt9L/0C5+M" 70 | ); 71 | 72 | let key = match &privkey.kind { 73 | PrivateKeyKind::Ed25519(key) => key, 74 | _ => panic!("Wrong key type detected"), 75 | }; 76 | assert_eq!(hex::encode(&key.key), "c99da258e0ee67eb68a953a346aaec1e5e4e560dd0af3033bc63a231b6c4b12d33b45500fd4c87480992d6156c2d7fbb3cfda10dbdef2dc064c498d5749b2879"); 77 | 78 | let mut buf = BufWriter::new(Vec::new()); 79 | privkey.write(&mut buf).unwrap(); 80 | let serialized = String::from_utf8(buf.into_inner().unwrap()).unwrap(); 81 | assert_eq!(include_str!("keys/unencrypted/ed25519_1"), serialized); 82 | } 83 | 84 | #[test] 85 | fn parse_ed25519_private_key_2() { 86 | let privkey = include_str!("keys/unencrypted/ed25519_2"); 87 | 88 | let privkey = PrivateKey::from_string(privkey); 89 | match &privkey { 90 | Ok(_) => (), 91 | Err(e) => println!("{}", e), 92 | }; 93 | assert!(privkey.is_ok()); 94 | let privkey = privkey.unwrap(); 95 | assert_eq!( 96 | privkey.pubkey.fingerprint().hash, 97 | "XfK1zRAFSKTh7bYdKwli8mJ0P4q/bV2pXdmjyw5p0DI" 98 | ); 99 | 100 | let key = match &privkey.kind { 101 | PrivateKeyKind::Ed25519(key) => key, 102 | _ => panic!("Wrong key type detected"), 103 | }; 104 | assert_eq!(hex::encode(&key.key), "fa1e0514dcfb394d57450953bcb93a5b7d5760dc90719cecf70c858ed6ed576dcd097e979599f4411316d7800165ae4665ceba53e72c1222ac82467018ad3e14"); 105 | 106 | let mut buf = BufWriter::new(Vec::new()); 107 | privkey.write(&mut buf).unwrap(); 108 | let serialized = String::from_utf8(buf.into_inner().unwrap()).unwrap(); 109 | assert_eq!(include_str!("keys/unencrypted/ed25519_2"), serialized); 110 | } 111 | 112 | #[test] 113 | fn parse_rsa_2048_private_key() { 114 | let privkey = include_str!("keys/unencrypted/rsa_2048_1"); 115 | 116 | let privkey = PrivateKey::from_string(privkey); 117 | match &privkey { 118 | Ok(_) => (), 119 | Err(e) => println!("{}", e), 120 | }; 121 | assert!(privkey.is_ok()); 122 | let privkey = privkey.unwrap(); 123 | assert_eq!( 124 | privkey.pubkey.fingerprint().hash, 125 | "A7S6yWfLWgKphtN5UzBbKbhSE71bK/NB6x6NE0DJOpU" 126 | ); 127 | 128 | let key = match &privkey.kind { 129 | PrivateKeyKind::Rsa(key) => key, 130 | _ => panic!("Wrong key type detected"), 131 | }; 132 | 133 | assert_eq!(hex::encode(&key.n), "c37ea4f4c97673a0762b4c306bbc56e3ae339543dc0338be8f126113a0f56b35abe35ce01f9990cb77a6b32980e27ef7955e305457e46e9bb733e4c5c7061d38e8dc5f1b883a39599543117e73358fd0e972dcbe15c31dd0e266c3dec8532c5dfa62fdc485be83cc68ff483bc21604e362ace4baea4deca2ab9380d4c536c2be0a00ed6d12206603edfac8c3ae0ac69d4c6fb000ca1e73d164b3632975e654551a710145be411043103bc72f92650452f9748c02d8fa16b07a38de7735a35e241dff13b813c15f28db887b44944c764cd036de06f59bb1e7f93c8d7f1f0dd30ad15710e06c3184d0c126c31a21440ea350fbdedd85cb4acae7a815d7dba1d905"); 134 | assert_eq!(hex::encode(&key.e), "010001"); 135 | assert_eq!(hex::encode(&key.d), "506352678a345825a291f9596be3dd82f04e2bd1e4b5ba6eb99c6028d72633f8b4fee06114fd6bca0f209049bfc2e35ce1a202939c34c5bae861c1700680202217b70eb0de9aba9c78c1447f4fd6b595bd794b062b22b9aefc7a2f8efba71ff66110b5b34b1fe54877e0898e7127e6c8adf9d4707c0aa104df32df661ed67ecb37775f929b57924f6c2163f4bd6b092e31f721bc8c05212ec151fa40d791656dbe5f0a32fa79cf9a6d0b3f5070bfc4375db4cbea7612abd21e772591421e0b38ee14c42b63732a483822edf6cd126079c8653549eddc99a34c8202824587339380b0eb1a6b2d1266a36e978a84d820e4d82d39f9f7bea55c1115da474f33317d"); 136 | assert_eq!(hex::encode(&key.coefficient), "4106d8438d15079e6278ad40e65c4d15d82fd89f1b7369f0831af97df00d16404b0d19d653018f733d2312a12b29cfc6b9156204c08e73f027ddb34deec1b864c3daa292662e8f812ef674eb5ae9e1c824282f380799d9ab6e09c6d4cbb79d7466eed9deebc070651883ca34a0560802dc2ec6710982db945c6a490b6bd3f3bf"); 137 | assert_eq!(hex::encode(&key.p), "f94db898cdfdba0b36d5508c32588ee796f49addabbf408db8c429c448ac7d84b253427434afa31098e36f65b8c9f43722d7f056472f470c0e5520b374140e2887202757b884af88b5c95b6e07d8dc42c8cac5b11067c1923e7d4dda3042e7fcc49e92448bebb3e4a94f387ea4ad81085ec57749c907db676af4bc71bcb0fe2b"); 138 | assert_eq!(hex::encode(&key.q), "c8beeb3574a0d8af760edea0f882e054f72340f39ad61f22db17975fef4725d914e260c8951f125c83955d23be130cecd86cb253e95671b456fa964a6c4567579431e1231924f0a53ea2377738b8d19e6946a1e05b85cf424386bef181de24df066649dafa6437e137c6dec56eb679446a4b486ac9b96bf649789035846b1d8f"); 139 | 140 | #[cfg(feature = "rsa-signing")] 141 | assert_eq!(hex::encode(key.exp.as_ref().unwrap()), "f6937ca505f8926e4d09a6e543567be16b58fb638c5f5945d31d9201e5af55664dca33cc23e023f4628370c6b78267ddb0c4cb9d4a42e48e740e968d679dfe72ef534a16651637578c15602cefedf9ccc4346a5bbad2248eb4e7d27c9f874d54a054066f6dc4eee496e1180b8a6d61561a064cf9d9afbfbe05f791fb1c9a2289"); 142 | #[cfg(feature = "rsa-signing")] 143 | assert_eq!(hex::encode(key.exq.as_ref().unwrap()), "0d8dde81c07b2fc6411965ecc67ac7bcd4e6fb76b748a7d789a58122081cecb04899b46136b85f5c01c26f047fcbf77e726a7c6bf00057330f00626f69fa11ad37235b092ca472df25687c883f3b336417c59e1e70ef8afbf5653eb53dc88b02c802d60fc4024a4799a582db1fcb904a8f46bffffdd8d8324be9a90b0402db7f"); 144 | 145 | let mut buf = BufWriter::new(Vec::new()); 146 | privkey.write(&mut buf).unwrap(); 147 | let serialized = String::from_utf8(buf.into_inner().unwrap()).unwrap(); 148 | assert_eq!(include_str!("keys/unencrypted/rsa_2048_1"), serialized); 149 | } 150 | -------------------------------------------------------------------------------- /tests/privkey_encrypted.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::{PrivateKey, PrivateKeyKind}; 2 | 3 | #[test] 4 | fn parse_encrypted_ed25519_private_key() { 5 | let privkey = r#"-----BEGIN OPENSSH PRIVATE KEY----- 6 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAMSH2ak6 7 | +qM0Od6QYgqk3EAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJLaNw1wt2GAGxhZ 8 | b4TTQ3m5bWeghg0hVbUBie2IDxb1AAAAoJgXZeSQFgSB0JzfMPBB9l1roV4nZnAVG0aUC4 9 | oVhmOX/jGK2MRLusepo1tF98kou01dbVTKiZYdxrCffJDYj2H2LrtWqR2sf19mhUY0OrW8 10 | 0inHLPw5CRRPCJuZ8fdmsbtawWlajCmJykrtCLAhiUx4dJ2gYLyaSIFbFhg0B9XhuLHQ09 11 | gj+HqUxSiAOuRA5cDU+SykIfb7TLvteZOpl2I= 12 | -----END OPENSSH PRIVATE KEY-----"#; 13 | 14 | let privkey = PrivateKey::from_string_with_passphrase(privkey, Some(format!("test"))); 15 | match &privkey { 16 | Ok(_) => (), 17 | Err(e) => println!("{}", e), 18 | }; 19 | assert!(privkey.is_ok()); 20 | let privkey = privkey.unwrap(); 21 | assert_eq!( 22 | privkey.pubkey.fingerprint().hash, 23 | "bTkq+BEqfkYOgyPk2ziLwtkxDFcj531SfwEpl3IyutU" 24 | ); 25 | 26 | let key = match &privkey.kind { 27 | PrivateKeyKind::Ed25519(key) => key, 28 | _ => panic!("Wrong key type detected"), 29 | }; 30 | assert_eq!( 31 | key.key, 32 | hex::decode("697DEA3B53C8612F87DC06E92A466366866458403D9695040AD341D05D7430E692DA370D70B761801B18596F84D34379B96D67A0860D2155B50189ED880F16F5").unwrap(), 33 | ) 34 | } 35 | 36 | #[test] 37 | fn parse_encrypted_ed25519_private_key_32_rounds() { 38 | let privkey = r#"-----BEGIN OPENSSH PRIVATE KEY----- 39 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABArNg8tIr 40 | sFX6oNT1jNqVoIAAAAIAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIEofsz/Ssr2U3p81 41 | fVIYsF8uRX1qKxk5olZhhtWEcK+lAAAAoCJJBzCtLwWY0cx7G1zYbvbjUP4/lIryQEufgZ 42 | DrhYqWabR+nO8Os2U9EumbuqVM81Rrxcc1Qc9k/IDUelhGDubO7kRFDzn3BAirKl2sTADx 43 | JcR8y21R5hqNGhTlx0F8kqAGg2nW3PmsiGCwKl7Iz7IMf4iaUuufHG2RtTaFpN0n9gxbpQ 44 | 6xceKSL0Ba+hjMl54kebsfZJfwgdh6fZ8leec= 45 | -----END OPENSSH PRIVATE KEY-----"#; 46 | 47 | let privkey = PrivateKey::from_string_with_passphrase(privkey, Some(format!("test"))); 48 | match &privkey { 49 | Ok(_) => (), 50 | Err(e) => println!("{}", e), 51 | }; 52 | assert!(privkey.is_ok()); 53 | let privkey = privkey.unwrap(); 54 | assert_eq!( 55 | privkey.pubkey.fingerprint().hash, 56 | "nTCuabo74eqb8zYiqsg8x7sfgzsu1Egv0XGzaiip9XU" 57 | ); 58 | 59 | let key = match &privkey.kind { 60 | PrivateKeyKind::Ed25519(key) => key, 61 | _ => panic!("Wrong key type detected"), 62 | }; 63 | assert_eq!( 64 | key.key, 65 | hex::decode("C9753EE1FE006557E290E5EBBC67D75E9DDFC9F647291E3E89B23A83507CF5774A1FB33FD2B2BD94DE9F357D5218B05F2E457D6A2B1939A2566186D58470AFA5").unwrap(), 66 | ) 67 | } 68 | 69 | #[test] 70 | fn parse_encrypted_ec256_private_key() { 71 | let privkey = r#"-----BEGIN OPENSSH PRIVATE KEY----- 72 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAgyk4Wlj 73 | Nok6umgT5cd/0lAAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz 74 | dHAyNTYAAABBBPsoWTQSrnuRXj3a7232/SuLOIOva1RHTtmVcRpt3+ktr20hXisf4RG9LB 75 | TZkZ9q0JjvJybRpXo4mT8pVS0jQIIAAACwYyMrnR4LZovfZSMEcdxdPgwM/sioaaLyI7eb 76 | Kh4cPIS2TRZ6a8SlF/+ugnz/5kvQyazJhp07fMqusB/v+7x/jmcNs7z1aq6rh39sirf7ll 77 | kA+bCsY/r/A5G8bcYiIUbRpFIY+JXJkvv1aXIsRS+K5OXKb9aySrBddTY3Uddp9WkfG72W 78 | Gd5VYX4HxsQZWQixs9DSZyCexueq7Fw+57AW9z1XFySUhHdiRlgeXSxsF8Q= 79 | -----END OPENSSH PRIVATE KEY-----"#; 80 | 81 | let privkey = PrivateKey::from_string_with_passphrase(privkey, Some(format!("test"))); 82 | match &privkey { 83 | Ok(_) => (), 84 | Err(e) => println!("{}", e), 85 | }; 86 | assert!(privkey.is_ok()); 87 | let privkey = privkey.unwrap(); 88 | assert_eq!( 89 | privkey.pubkey.fingerprint().hash, 90 | "aeK6cuLIzfIddiLtlP+kaZqA5lo4ExdXM8ksWeJPPp8" 91 | ); 92 | 93 | match &privkey.kind { 94 | PrivateKeyKind::Ecdsa(key) => key, 95 | _ => panic!("Wrong key type detected"), 96 | }; 97 | } 98 | 99 | #[test] 100 | fn parse_encrypted_ec384_private_key() { 101 | let privkey = r#"-----BEGIN OPENSSH PRIVATE KEY----- 102 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDx+U4gFb 103 | XXB+awGkd75t8qAAAAEAAAAAEAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlz 104 | dHAzODQAAABhBFlMgDDsgzBqyzJoiM0nS0SZ2oxXBGkYcyaZ7Y3m5CO4BsUIWcHIEJ3A8D 105 | pftOVAmlRm2El4szGmwJrfgBLVF0JfqjQdTrQBG4ARRAWZCbSF7E0VWLYZOHKZZqAlrxKV 106 | eAAAAOCiWpA95P/h4YkK1Qm7C04rlstJFrjTqiVe0vg1XZ/j+4oFfKb5lZA8MbKv6QTP2Q 107 | fD4nMprI5/QXpi0jI/Po0FUJecY+xNTLzTohhwaLkg7aztAQAsChsW8txfetTkFsqz0RxI 108 | VPIGbXHVwMdRPbheRu9AOOotSJldENE5jpdQ9PuBj0IIYw/Q3ZVlT+fePQqxsJsfk820X1 109 | qE7mF1LNXlV52YOTwFvwfEwiUYyDnhVvwiR06q0ojtlz/K/9t7W+yXNWK55LduI7Q7WaDn 110 | N9Rg7yGs2leAkts+8G8w+tgJJQ== 111 | -----END OPENSSH PRIVATE KEY-----"#; 112 | 113 | let privkey = PrivateKey::from_string_with_passphrase(privkey, Some(format!("test"))); 114 | match &privkey { 115 | Ok(_) => (), 116 | Err(e) => println!("{}", e), 117 | }; 118 | assert!(privkey.is_ok()); 119 | let privkey = privkey.unwrap(); 120 | assert_eq!( 121 | privkey.pubkey.fingerprint().hash, 122 | "SvNs3N/ZVtfktcRjlcgpvOs4qFnQTIVGTt2L2S2nVI8" 123 | ); 124 | 125 | match &privkey.kind { 126 | PrivateKeyKind::Ecdsa(key) => key, 127 | _ => panic!("Wrong key type detected"), 128 | }; 129 | } 130 | 131 | #[test] 132 | fn parse_encrypted_rsa3072_private_key() { 133 | let privkey = r#"-----BEGIN OPENSSH PRIVATE KEY----- 134 | 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 170 | cRts9Ge+2sCv9AYgd/SipxopCqw= 171 | -----END OPENSSH PRIVATE KEY-----"#; 172 | 173 | let privkey = PrivateKey::from_string_with_passphrase(privkey, Some(format!("test"))); 174 | match &privkey { 175 | Ok(_) => (), 176 | Err(e) => println!("{}", e), 177 | }; 178 | assert!(privkey.is_ok()); 179 | let privkey = privkey.unwrap(); 180 | assert_eq!( 181 | privkey.pubkey.fingerprint().hash, 182 | "+fZGegm7Lmc5SJJQRXZjvWhT25Ybqb8H4Vvq91Z1JEY" 183 | ); 184 | 185 | match &privkey.kind { 186 | PrivateKeyKind::Rsa(key) => key, 187 | _ => panic!("Wrong key type detected"), 188 | }; 189 | } 190 | 191 | #[test] 192 | fn parse_encrypted_ed25519_private_key_bad_passphrase() { 193 | let privkey = r#"-----BEGIN OPENSSH PRIVATE KEY----- 194 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAMSH2ak6 195 | +qM0Od6QYgqk3EAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJLaNw1wt2GAGxhZ 196 | b4TTQ3m5bWeghg0hVbUBie2IDxb1AAAAoJgXZeSQFgSB0JzfMPBB9l1roV4nZnAVG0aUC4 197 | oVhmOX/jGK2MRLusepo1tF98kou01dbVTKiZYdxrCffJDYj2H2LrtWqR2sf19mhUY0OrW8 198 | 0inHLPw5CRRPCJuZ8fdmsbtawWlajCmJykrtCLAhiUx4dJ2gYLyaSIFbFhg0B9XhuLHQ09 199 | gj+HqUxSiAOuRA5cDU+SykIfb7TLvteZOpl2I= 200 | -----END OPENSSH PRIVATE KEY-----"#; 201 | 202 | let privkey = PrivateKey::from_string_with_passphrase(privkey, Some(format!("Test"))); 203 | match &privkey { 204 | Ok(_) => (), 205 | Err(e) => println!("{}", e), 206 | }; 207 | assert!(privkey.is_err()); 208 | } 209 | -------------------------------------------------------------------------------- /tests/pubkey.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::PublicKey; 2 | 3 | #[test] 4 | fn parse_ed25519_publickey() { 5 | let in_data = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH447ysO2G10q6wS6KveQWYJNr7Ux5WjtbDJr/MQ4Xpw obelisk@exclave.lan"; 6 | let ssh_pubkey = PublicKey::from_string(in_data); 7 | assert!(ssh_pubkey.is_ok()); 8 | let ssh_pubkey = ssh_pubkey.unwrap(); 9 | 10 | assert_eq!( 11 | ssh_pubkey.fingerprint().hash, 12 | "c4X5mQt9f37ZkdQUbNckivACZmY52rZw0jJUCA1DfkI" 13 | ); 14 | 15 | let out_data = format!("{}", ssh_pubkey); 16 | assert_eq!(in_data, out_data); 17 | } 18 | 19 | #[test] 20 | fn parse_ecdsa384_publickey() { 21 | let in_data = "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBCVw+mPwQPcohKD0L9hDOfbtzMvBUq7WE4RQZ2y7j4dMhdww0Ux0rv3gbhTsvL8GbrfZLOwVkra3WEY1qBDGPLGn1Ym6RLWMqo5qHBjcyDJZGzG0+bAa7np6mtU7ydpkvw== obelisk@exclave.lan"; 22 | let ssh_pubkey = PublicKey::from_string(in_data); 23 | assert!(ssh_pubkey.is_ok()); 24 | let ssh_pubkey = ssh_pubkey.unwrap(); 25 | 26 | assert_eq!( 27 | ssh_pubkey.fingerprint().hash, 28 | "huOgP+FbGDQ830OcBfh2j3WwGn+E66sxfZp3NwXA4jg" 29 | ); 30 | 31 | let out_data = format!("{}", ssh_pubkey); 32 | assert_eq!(in_data, out_data); 33 | } 34 | 35 | #[test] 36 | fn parse_ecdsa384_publickey_k1() { 37 | let in_data = include_str!("keys/public/k1.pub"); 38 | let ssh_pubkey = PublicKey::from_string(in_data); 39 | assert!(ssh_pubkey.is_ok()); 40 | let ssh_pubkey = ssh_pubkey.unwrap(); 41 | 42 | assert_eq!( 43 | ssh_pubkey.fingerprint().hash, 44 | "AJTGgJhPr6h3WxAY89vTudPXUAo19LegdF5Zrudxn3M" 45 | ); 46 | 47 | let out_data = format!("{}", ssh_pubkey); 48 | assert_eq!(in_data, out_data); 49 | } 50 | 51 | #[test] 52 | fn parse_ecdsa384_publickey_k2() { 53 | let ssh_pubkey = PublicKey::from_path("tests/keys/public/k2.pub"); 54 | assert!(ssh_pubkey.is_ok()); 55 | let ssh_pubkey = ssh_pubkey.unwrap(); 56 | 57 | assert_eq!( 58 | ssh_pubkey.fingerprint().hash, 59 | "2XIdt5LLgv/h/2GzC4i69h8h/ivQawyab3O1brw3WL0" 60 | ); 61 | 62 | let out_data = format!("{}", ssh_pubkey); 63 | assert_eq!(include_str!("keys/public/k2.pub"), out_data); 64 | } 65 | 66 | #[test] 67 | fn parse_ecdsa384_publickey_test_file() { 68 | let in_data = include_str!("keys/public/test.pub"); 69 | let ssh_pubkey = PublicKey::from_string(in_data); 70 | assert!(ssh_pubkey.is_ok()); 71 | let ssh_pubkey = ssh_pubkey.unwrap(); 72 | 73 | assert_eq!( 74 | ssh_pubkey.fingerprint().hash, 75 | "v5LrykdD4fIAjjUYKP9w4//TY9o7AAmw6MChHNbHOHg" 76 | ); 77 | 78 | let out_data = format!("{}", ssh_pubkey); 79 | assert_eq!(in_data, out_data); 80 | } 81 | 82 | #[test] 83 | fn parse_ecdsa256_publickey() { 84 | let in_data = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOhHAGJtT9s6zPW4OdQMzGbXEyj0ntkESrE1IZBgaCUSh9fWK1gRz+UJOcCB1JTC/kF2EPlwkX6XEpQToZl51oo= obelisk@exclave.lan"; 85 | let ssh_pubkey = PublicKey::from_string(in_data); 86 | assert!(ssh_pubkey.is_ok()); 87 | let ssh_pubkey = ssh_pubkey.unwrap(); 88 | 89 | assert_eq!( 90 | ssh_pubkey.fingerprint().hash, 91 | "BAJ7Md5+hfu6I6ojHoJpSNVXNRnxM8XfNnA8Pf1X/2I" 92 | ); 93 | 94 | let out_data = format!("{}", ssh_pubkey); 95 | assert_eq!(in_data, out_data); 96 | } 97 | 98 | #[test] 99 | fn parse_rsa4096_publickey() { 100 | let ssh_pubkey = PublicKey::from_string("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDUq0+iU7PeaeE6HPYX5bluxcZKWyFel1pR0P3367gWvAjCtqYu6yl+2K5S4GVnoFc1VaezI37dInsRdha6kGyOBudepm4XJOMZXKsgeBUTVIXzSHoexeb+C6Plp8naxOt+462eOpuLcxbp/62WfKYe1xQTfmTHHQG0TSwoz53G2RKxPQaimka0rhb4vGsQR2/DKkXoxQX26aRWPliKKFhh43NfercVqeLqNX5zM4M8yUx0kSKA2BgCaJFvdWj9b5j8X7sq229wm6dB7M4ueMLfaXJWakoRJP+bEnTat+3yIr6m8vO1qfpRW8ZHnRtObcQejDQDNjQxVMc9ST7FBWsSLPnwoUpRJhrzib2XB8VS6otZ/GpmE7SBdc3KrtXi/MgczT3Q9WW45z7vfrzsT219zh06NH/+rzA2bJibEPgmLQftwVCID9898LmsTfmSos5dohRDpfBpbYrPGPuiRSEbuxxNGnZ9UhbIPrHvCj+jxOHP3Z68a89Z5c1O+wGn91+bFDnm11KVDUk5CEW18k8ZDFLaeo2qQHYOcpqUHeOLUuaOuDjPbvbAp7lYiO0bPZi6h9z447PBrWFMEQObXO13+yv1f1svUwnumUtEb2FCKPZf3MhVRXH1sctkeIuSxnVBscV1Xsj0BnmbdY+KSj3Vqeg24nuyc0LML2i6wTleow== obelisk@exclave.lan"); 101 | assert!(ssh_pubkey.is_ok()); 102 | let ssh_pubkey = ssh_pubkey.unwrap(); 103 | 104 | assert_eq!( 105 | ssh_pubkey.fingerprint().hash, 106 | "+9NkmQZUWidGVFdel/s8bjQtVgthWEILEX2DtAZST5c" 107 | ); 108 | } 109 | 110 | #[test] 111 | fn parse_rsa3072_publickey() { 112 | let ssh_pubkey = PublicKey::from_string("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCsMWlhNHSxLoilw8hFlR/uo0Qrq0xNEO4jxsAyxnbxCckxmGPURR5t2KyUS0H7kGoCoG5+8O4dmNztTy3GPBXC+snFpbhtCAUvPo3uEVm/VHXEhXxOG0s6RjAZ2vBGED5Ahx63EYRfDeYr5bzuJqzfw4ELCFElrch3V+YWMgzeF0WYVYrrS9lHrzPOE0r9tJaFZ6yPxVRj/OCiHXnY+gElU1xUjmeACfir4z1HyvDi3C3vcMKl15ARDLzhAB4TnnIy2I5CgEVage5nySM7lttejvMjwBOvAio1YZO8F460IO7Uyk3Y6BEO+qIbtuN1vvXvOhkHN/JZZFCTOQDHSRwuc9OlsKxCFgf/XndxHTVSlhlEaWFGXzD8HS6xwiQ24M5c4DOnrxGLrUVxjU17736PAmW6L2Wsy3C+/6ns4K+lOO1IS5V4Fadtf5FGblSAU/8apH2EO+0KkdqI+2C4mIqyu1oHTfQBJ6rA/4JZTPAdh26YA+8L5ZANWkeshON3THU= obelisk@exclave.lan"); 113 | assert!(ssh_pubkey.is_ok()); 114 | let ssh_pubkey = ssh_pubkey.unwrap(); 115 | 116 | assert_eq!( 117 | ssh_pubkey.fingerprint().hash, 118 | "YlmcNWmmzkuy/5oIlCoqyd5JkIaa/RgzjlF7nFzsZ3o" 119 | ); 120 | } 121 | 122 | #[test] 123 | fn parse_rsa2048_publickey() { 124 | let in_data = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDfqT0yXZzoHYrTDBrvFbjrjOVQ9wDOL6PEmEToPVrNavjXOAfmZDLd6azKYDifveVXjBUV+Rum7cz5MXHBh046NxfG4g6OVmVQxF+czWP0Oly3L4Vwx3Q4mbD3shTLF36Yv3Ehb6DzGj/SDvCFgTjYqzkuupN7KKrk4DUxTbCvgoA7W0SIGYD7frIw64Kxp1Mb7AAyh5z0WSzYyl15lRVGnEBRb5BEEMQO8cvkmUEUvl0jALY+hawejjedzWjXiQd/xO4E8FfKNuIe0SUTHZM0DbeBvWbsef5PI1/Hw3TCtFXEOBsMYTQwSbDGiFEDqNQ+97dhctKyueoFdfbodkF obelisk@exclave.lan"; 125 | let ssh_pubkey = PublicKey::from_string(in_data); 126 | assert!(ssh_pubkey.is_ok()); 127 | let ssh_pubkey = ssh_pubkey.unwrap(); 128 | 129 | assert_eq!( 130 | ssh_pubkey.fingerprint().hash, 131 | "A7S6yWfLWgKphtN5UzBbKbhSE71bK/NB6x6NE0DJOpU" 132 | ); 133 | 134 | let out_data = format!("{}", ssh_pubkey); 135 | assert_eq!(in_data, out_data); 136 | } 137 | 138 | #[test] 139 | fn parse_rsa_sha2_512_8192_bit_publickey() { 140 | let in_data = include_str!("keys/public/rsa-sha2-512-8192.pub"); 141 | let ssh_pubkey = PublicKey::from_string(in_data); 142 | assert!(ssh_pubkey.is_ok()); 143 | let ssh_pubkey = ssh_pubkey.unwrap(); 144 | 145 | assert_eq!( 146 | ssh_pubkey.fingerprint().hash, 147 | "TL9DbJ2yKuM/NV1gbE8Y9XRiu56wzhdcXBJyw8mMOfs" 148 | ); 149 | 150 | // We cannot run this check because the re-encoding will change the name 151 | // back to ssh-rsa, making it not the same. While we could choose not to do 152 | // this, this is what ssh-keygen reports so to maintain compatibility with 153 | // it, we do it as well. 154 | 155 | //let out_data = format!("{}", ssh_pubkey); 156 | //assert_eq!(in_data, out_data); 157 | } 158 | 159 | #[test] 160 | fn parse_rsa_sha2_256_4096_bit_publickey() { 161 | let in_data = include_str!("keys/public/rsa-sha2-256-4096.pub"); 162 | let ssh_pubkey = PublicKey::from_string(in_data); 163 | assert!(ssh_pubkey.is_ok()); 164 | let ssh_pubkey = ssh_pubkey.unwrap(); 165 | 166 | assert_eq!( 167 | ssh_pubkey.fingerprint().hash, 168 | "sqSMm4+0OSx6UlrEUW7Khu40yymOGt9nkF2U2/ixHKQ" 169 | ); 170 | 171 | // We cannot run this check because the re-encoding will change the name 172 | // back to ssh-rsa, making it not the same. While we could choose not to do 173 | // this, this is what ssh-keygen reports so to maintain compatibility with 174 | // it, we do it as well. 175 | 176 | //let out_data = format!("{}", ssh_pubkey); 177 | //assert_eq!(in_data, out_data); 178 | } 179 | -------------------------------------------------------------------------------- /tests/reader.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::error::Error; 2 | use sshcerts::ssh::Reader; 3 | 4 | #[test] 5 | fn rfc4251_mpint_test_vector_one() { 6 | let test_vector = [0, 0, 0, 0]; 7 | let mut reader = Reader::new(&test_vector); 8 | let num = reader.read_positive_mpint(); 9 | assert_eq!(num.unwrap().len(), 0); 10 | } 11 | 12 | #[test] 13 | fn rfc4251_mpint_test_vector_two() { 14 | let test_vector = [ 15 | 0x00, 0x00, 0x00, 0x08, 0x09, 0xa3, 0x78, 0xf9, 0xb2, 0xe3, 0x32, 0xa7, 16 | ]; 17 | let mut reader = Reader::new(&test_vector); 18 | let num = reader.read_positive_mpint(); 19 | assert_eq!( 20 | num.unwrap(), 21 | vec![0x09, 0xa3, 0x78, 0xf9, 0xb2, 0xe3, 0x32, 0xa7] 22 | ); 23 | } 24 | 25 | #[test] 26 | fn rfc4251_mpint_test_vector_three() { 27 | let test_vector = [0x00, 0x00, 0x00, 0x02, 0x00, 0x80]; 28 | let mut reader = Reader::new(&test_vector); 29 | let num = reader.read_positive_mpint(); 30 | assert_eq!(num.unwrap(), vec![0x80]); 31 | } 32 | 33 | #[test] 34 | fn rfc4251_mpint_test_vector_four() { 35 | let test_vector = [0x00, 0x00, 0x00, 0x02, 0xed, 0xcc]; 36 | let mut reader = Reader::new(&test_vector); 37 | match reader.read_positive_mpint() { 38 | Err(Error::InvalidFormat) => (), 39 | Ok(n) => panic!( 40 | "This should have failed to parse as it's a negative mpint but instead got {:?}", 41 | n 42 | ), 43 | Err(other) => panic!("Got {other}, when expected InvalidFormat"), 44 | } 45 | } 46 | 47 | #[test] 48 | fn rfc4251_mpint_test_vector_five() { 49 | let test_vector = [0x00, 0x00, 0x00, 0x05, 0xff, 0x21, 0x52, 0x41, 0x11]; 50 | let mut reader = Reader::new(&test_vector); 51 | match reader.read_positive_mpint() { 52 | Err(Error::InvalidFormat) => (), 53 | Ok(n) => panic!( 54 | "This should have failed to parse as it's a negative mpint but instead got {:?}", 55 | n 56 | ), 57 | Err(other) => panic!("Got {other}, when expected InvalidFormat"), 58 | } 59 | } 60 | 61 | #[test] 62 | fn malicious_mpint_wrong_zero() { 63 | let test_vector = [0x00, 0x00, 0x00, 0x01, 0x00]; 64 | let mut reader = Reader::new(&test_vector); 65 | match reader.read_positive_mpint() { 66 | Err(Error::InvalidFormat) => (), 67 | Ok(n) => panic!( 68 | "This should have failed to parse as it's a negative mpint but instead got {:?}", 69 | n 70 | ), 71 | Err(other) => panic!("Got {other}, when expected InvalidFormat"), 72 | } 73 | } 74 | 75 | #[test] 76 | fn malicious_mpint_unneeded_zero() { 77 | let test_vector = [0x00, 0x00, 0x00, 0x02, 0x00, 0x01]; 78 | let mut reader = Reader::new(&test_vector); 79 | match reader.read_positive_mpint() { 80 | Err(Error::InvalidFormat) => (), 81 | Ok(n) => panic!( 82 | "This should have failed to parse as it's a negative mpint but instead got {:?}", 83 | n 84 | ), 85 | Err(other) => panic!("Got {other}, when expected InvalidFormat"), 86 | } 87 | } 88 | 89 | #[test] 90 | fn malicious_mpint_too_many_zeros() { 91 | let test_vector = [0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF]; 92 | let mut reader = Reader::new(&test_vector); 93 | match reader.read_positive_mpint() { 94 | Err(Error::InvalidFormat) => (), 95 | Ok(n) => panic!( 96 | "This should have failed to parse as it's a negative mpint but instead got {:?}", 97 | n 98 | ), 99 | Err(other) => panic!("Got {other}, when expected InvalidFormat"), 100 | } 101 | } 102 | 103 | #[test] 104 | fn extra_good_check() { 105 | let test_vector = [0x00, 0x00, 0x00, 0x01, 0x7F]; 106 | let mut reader = Reader::new(&test_vector); 107 | let num = reader.read_positive_mpint(); 108 | assert_eq!(num.unwrap(), vec![0x7F]); 109 | } 110 | 111 | #[test] 112 | fn extra_good_check_two() { 113 | let test_vector = [0x00, 0x00, 0x00, 0x02, 0x00, 0x80]; 114 | let mut reader = Reader::new(&test_vector); 115 | let num = reader.read_positive_mpint(); 116 | assert_eq!(num.unwrap(), vec![0x80]); 117 | } 118 | 119 | #[test] 120 | fn read_raw_bytes() { 121 | let test_vector = [ 122 | 0xff, 0x21, 0x52, 0x41, 0x11, 0xff, 0x21, 0x52, 0x41, 0x11, 0xff, 0x21, 0x52, 0x41, 0x11, 123 | 0xff, 0x21, 0x52, 0x41, 0x11, 124 | ]; 125 | let mut reader = Reader::new(&test_vector); 126 | let num = reader.read_raw_bytes(10); 127 | assert_eq!( 128 | num.unwrap(), 129 | vec![0xff, 0x21, 0x52, 0x41, 0x11, 0xff, 0x21, 0x52, 0x41, 0x11,] 130 | ); 131 | } 132 | 133 | #[test] 134 | fn read_raw_too_many() { 135 | let test_vector = [ 136 | 0xff, 0x21, 0x52, 0x41, 0x11, 0xff, 0x21, 0x52, 0x41, 0x11, 0xff, 0x21, 0x52, 0x41, 0x11, 137 | 0xff, 0x21, 0x52, 0x41, 0x11, 138 | ]; 139 | let mut reader = Reader::new(&test_vector); 140 | let num = reader.read_raw_bytes(40); 141 | assert_eq!(num.is_err(), true); 142 | } 143 | 144 | #[test] 145 | fn read_raw_wrap() { 146 | let test_vector = [ 147 | 0xff, 0x21, 0x52, 0x41, 0x11, 0xff, 0x21, 0x52, 0x41, 0x11, 0xff, 0x21, 0x52, 0x41, 0x11, 148 | 0xff, 0x21, 0x52, 0x41, 0x11, 149 | ]; 150 | let mut reader = Reader::new(&test_vector); 151 | reader.read_raw_bytes(4).unwrap(); 152 | let num = reader.read_raw_bytes(2 ^ 64 - 1); 153 | assert_eq!(num.is_err(), true); 154 | } 155 | -------------------------------------------------------------------------------- /tests/signature-bad.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::{ 2 | ssh::{SshSignature, VerifiedSshSignature}, 3 | PrivateKey, 4 | }; 5 | 6 | #[test] 7 | fn ensure_verification_fail_ecdsa_256_bitflip() { 8 | let signature = SshSignature::from_armored_string(include_str!( 9 | "signatures_bad/ecdsa_256_1_bitflip_Test.sig" 10 | )) 11 | .unwrap(); 12 | 13 | let message = include_bytes!("messages/Test").to_vec(); 14 | let public_key = PrivateKey::from_string(include_str!("keys/unencrypted/ecdsa_256_1")) 15 | .unwrap() 16 | .pubkey 17 | .clone(); 18 | 19 | let vs = VerifiedSshSignature::from_ssh_signature( 20 | message.as_slice(), 21 | signature, 22 | "file", 23 | Some(public_key), 24 | ); 25 | 26 | assert!(vs.is_err()); 27 | } 28 | 29 | #[test] 30 | fn ensure_parse_fail_ed25519_emptynamespace() { 31 | let signature = SshSignature::from_armored_string(include_str!( 32 | "signatures_bad/ed25519_1_empty-namespace_Test.sig" 33 | )); 34 | 35 | assert!(signature.is_err()); 36 | } 37 | -------------------------------------------------------------------------------- /tests/signature-creation-rsa.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::{ 2 | ssh::{SshSignature, VerifiedSshSignature}, 3 | PrivateKey, 4 | }; 5 | 6 | #[test] 7 | fn check_basic_creation_rsa_2048_1() { 8 | let private_key = PrivateKey::from_string(include_str!("keys/unencrypted/rsa_2048_1")).unwrap(); 9 | 10 | let message = include_bytes!("messages/Test").to_vec(); 11 | 12 | let _ = 13 | VerifiedSshSignature::new_with_private_key(&message, "file", private_key, None).unwrap(); 14 | } 15 | 16 | #[test] 17 | fn check_basic_creation_rsa_2048_1_full_loop() { 18 | let private_key = PrivateKey::from_string(include_str!("keys/unencrypted/rsa_2048_1")).unwrap(); 19 | let public_key = private_key.pubkey.clone(); 20 | 21 | let message = include_bytes!("messages/Test").to_vec(); 22 | 23 | let vss = 24 | VerifiedSshSignature::new_with_private_key(&message, "file", private_key, None).unwrap(); 25 | 26 | let armored_signature = format!("{}", vss); 27 | 28 | let fl_vss = VerifiedSshSignature::from_ssh_signature( 29 | &message, 30 | SshSignature::from_armored_string(&armored_signature).unwrap(), 31 | "file", 32 | Some(public_key), 33 | ); 34 | 35 | assert!(fl_vss.is_ok()); 36 | } 37 | -------------------------------------------------------------------------------- /tests/signature-creation.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::{ 2 | ssh::{SshSignature, VerifiedSshSignature}, 3 | PrivateKey, 4 | }; 5 | 6 | #[test] 7 | fn check_basic_creation_ed25519() { 8 | let private_key = PrivateKey::from_string(include_str!("keys/unencrypted/ed25519_1")).unwrap(); 9 | 10 | let message = include_bytes!("messages/Test").to_vec(); 11 | 12 | let _ = 13 | VerifiedSshSignature::new_with_private_key(&message, "file", private_key, None).unwrap(); 14 | } 15 | 16 | #[test] 17 | fn check_basic_creation_ecdsa256() { 18 | let private_key = 19 | PrivateKey::from_string(include_str!("keys/unencrypted/ecdsa_256_1")).unwrap(); 20 | 21 | let message = include_bytes!("messages/Test").to_vec(); 22 | 23 | let _ = 24 | VerifiedSshSignature::new_with_private_key(&message, "file", private_key, None).unwrap(); 25 | } 26 | 27 | #[test] 28 | fn check_basic_creation_ecdsa384() { 29 | let private_key = 30 | PrivateKey::from_string(include_str!("keys/unencrypted/ecdsa_384_1")).unwrap(); 31 | 32 | let message = include_bytes!("messages/Test").to_vec(); 33 | 34 | let _ = 35 | VerifiedSshSignature::new_with_private_key(&message, "file", private_key, None).unwrap(); 36 | } 37 | 38 | #[test] 39 | fn check_basic_creation_ed25519_full_loop() { 40 | let private_key = PrivateKey::from_string(include_str!("keys/unencrypted/ed25519_1")).unwrap(); 41 | let public_key = private_key.pubkey.clone(); 42 | 43 | let message = include_bytes!("messages/Test").to_vec(); 44 | 45 | let vss = 46 | VerifiedSshSignature::new_with_private_key(&message, "file", private_key, None).unwrap(); 47 | 48 | let armored_signature = format!("{}", vss); 49 | 50 | let fl_vss = VerifiedSshSignature::from_ssh_signature( 51 | &message, 52 | SshSignature::from_armored_string(&armored_signature).unwrap(), 53 | "file", 54 | Some(public_key), 55 | ); 56 | 57 | assert!(fl_vss.is_ok()); 58 | } 59 | 60 | #[test] 61 | fn check_basic_creation_ecdsa_256_full_loop() { 62 | let private_key = 63 | PrivateKey::from_string(include_str!("keys/unencrypted/ecdsa_256_1")).unwrap(); 64 | let public_key = private_key.pubkey.clone(); 65 | 66 | let message = include_bytes!("messages/Test").to_vec(); 67 | 68 | let vss = 69 | VerifiedSshSignature::new_with_private_key(&message, "file", private_key, None).unwrap(); 70 | 71 | let armored_signature = format!("{}", vss); 72 | 73 | let fl_vss = VerifiedSshSignature::from_ssh_signature( 74 | &message, 75 | SshSignature::from_armored_string(&armored_signature).unwrap(), 76 | "file", 77 | Some(public_key), 78 | ); 79 | 80 | assert!(fl_vss.is_ok()); 81 | } 82 | 83 | #[test] 84 | fn check_basic_creation_ecdsa_384_full_loop() { 85 | let private_key = 86 | PrivateKey::from_string(include_str!("keys/unencrypted/ecdsa_384_1")).unwrap(); 87 | let public_key = private_key.pubkey.clone(); 88 | 89 | let message = include_bytes!("messages/Test").to_vec(); 90 | 91 | let vss = 92 | VerifiedSshSignature::new_with_private_key(&message, "file", private_key, None).unwrap(); 93 | 94 | let armored_signature = format!("{}", vss); 95 | 96 | let fl_vss = VerifiedSshSignature::from_ssh_signature( 97 | &message, 98 | SshSignature::from_armored_string(&armored_signature).unwrap(), 99 | "file", 100 | Some(public_key), 101 | ); 102 | 103 | assert!(fl_vss.is_ok()); 104 | } 105 | -------------------------------------------------------------------------------- /tests/signature.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::{ 2 | ssh::{SshSignature, VerifiedSshSignature}, 3 | PrivateKey, 4 | }; 5 | 6 | #[test] 7 | fn check_basic_parsing_ed25519() { 8 | let _ = 9 | SshSignature::from_armored_string(include_str!("signatures/ed25519_1_Test.sig")).unwrap(); 10 | let _ = 11 | SshSignature::from_armored_string(include_str!("signatures/ed25519_2_Test.sig")).unwrap(); 12 | } 13 | 14 | #[test] 15 | fn check_basic_parsing_sk_ed25519() { 16 | let _ = 17 | SshSignature::from_armored_string(include_str!("signatures/sk_ed25519_Test.sig")).unwrap(); 18 | } 19 | 20 | #[test] 21 | fn check_basic_parsing_ecdsa256() { 22 | let _ = 23 | SshSignature::from_armored_string(include_str!("signatures/ecdsa_256_1_Test.sig")).unwrap(); 24 | } 25 | 26 | #[test] 27 | fn check_basic_parsing_sk_ecdsa() { 28 | let _ = 29 | SshSignature::from_armored_string(include_str!("signatures/sk_ecdsa_Test.sig")).unwrap(); 30 | } 31 | 32 | #[test] 33 | fn check_basic_parsing_ecdsa384() { 34 | let _ = 35 | SshSignature::from_armored_string(include_str!("signatures/ecdsa_384_1_Test.sig")).unwrap(); 36 | } 37 | 38 | #[test] 39 | fn check_basic_parsing_rsa_2048() { 40 | let _ = 41 | SshSignature::from_armored_string(include_str!("signatures/rsa_2048_1_Test.sig")).unwrap(); 42 | } 43 | 44 | #[test] 45 | fn check_basic_parsing_rsa_sha2_256() { 46 | let _ = 47 | SshSignature::from_armored_string(include_str!("signatures/rsa-sha2-256-4096_Test.sig")) 48 | .unwrap(); 49 | } 50 | 51 | #[test] 52 | fn check_basic_parsing_rsa_sha2_512() { 53 | let _ = 54 | SshSignature::from_armored_string(include_str!("signatures/rsa-sha2-512-4096_Test.sig")) 55 | .unwrap(); 56 | 57 | let _ = 58 | SshSignature::from_armored_string(include_str!("signatures/rsa-sha2-512-8192_Test.sig")) 59 | .unwrap(); 60 | } 61 | 62 | #[test] 63 | fn check_verification_ed25519() { 64 | let signature = 65 | SshSignature::from_armored_string(include_str!("signatures/ed25519_1_Test.sig")).unwrap(); 66 | 67 | let message = include_bytes!("messages/Test").to_vec(); 68 | let public_key = PrivateKey::from_string(include_str!("keys/unencrypted/ed25519_1")) 69 | .unwrap() 70 | .pubkey 71 | .clone(); 72 | 73 | let _verified_signature = VerifiedSshSignature::from_ssh_signature( 74 | message.as_slice(), 75 | signature, 76 | "file", 77 | Some(public_key), 78 | ) 79 | .expect("Failed to verify signature"); 80 | } 81 | 82 | #[test] 83 | fn check_verification_sk_ed25519() { 84 | let signature = 85 | SshSignature::from_armored_string(include_str!("signatures/sk_ed25519_Test.sig")).unwrap(); 86 | 87 | let message = include_bytes!("messages/Test").to_vec(); 88 | let public_key = PrivateKey::from_string(include_str!("keys/sk/ed25519")) 89 | .unwrap() 90 | .pubkey 91 | .clone(); 92 | 93 | let _verified_signature = VerifiedSshSignature::from_ssh_signature( 94 | message.as_slice(), 95 | signature, 96 | "file", 97 | Some(public_key), 98 | ) 99 | .expect("Failed to verify signature"); 100 | } 101 | 102 | #[test] 103 | fn check_verification_ecdsa_256() { 104 | let signature = 105 | SshSignature::from_armored_string(include_str!("signatures/ecdsa_256_1_Test.sig")).unwrap(); 106 | 107 | let message = include_bytes!("messages/Test").to_vec(); 108 | let public_key = PrivateKey::from_string(include_str!("keys/unencrypted/ecdsa_256_1")) 109 | .unwrap() 110 | .pubkey 111 | .clone(); 112 | 113 | let _verified_signature = VerifiedSshSignature::from_ssh_signature( 114 | message.as_slice(), 115 | signature, 116 | "file", 117 | Some(public_key), 118 | ) 119 | .expect("Failed to verify signature"); 120 | } 121 | 122 | #[test] 123 | fn check_verification_sk_ecdsa() { 124 | let signature = 125 | SshSignature::from_armored_string(include_str!("signatures/sk_ecdsa_Test.sig")).unwrap(); 126 | 127 | let message = include_bytes!("messages/Test").to_vec(); 128 | let public_key = PrivateKey::from_string(include_str!("keys/sk/ecdsa")) 129 | .unwrap() 130 | .pubkey 131 | .clone(); 132 | 133 | let _verified_signature = VerifiedSshSignature::from_ssh_signature( 134 | message.as_slice(), 135 | signature, 136 | "file", 137 | Some(public_key), 138 | ) 139 | .expect("Failed to verify signature"); 140 | } 141 | 142 | #[test] 143 | fn check_verification_ecdsa_384() { 144 | let signature = 145 | SshSignature::from_armored_string(include_str!("signatures/ecdsa_384_1_Test.sig")).unwrap(); 146 | 147 | let message = include_bytes!("messages/Test").to_vec(); 148 | let public_key = PrivateKey::from_string(include_str!("keys/unencrypted/ecdsa_384_1")) 149 | .unwrap() 150 | .pubkey 151 | .clone(); 152 | 153 | let _verified_signature = VerifiedSshSignature::from_ssh_signature( 154 | message.as_slice(), 155 | signature, 156 | "file", 157 | Some(public_key), 158 | ) 159 | .expect("Failed to verify signature"); 160 | } 161 | 162 | #[test] 163 | fn check_verification_rsa_2048() { 164 | let signature = 165 | SshSignature::from_armored_string(include_str!("signatures/rsa_2048_1_Test.sig")).unwrap(); 166 | 167 | let message = include_bytes!("messages/Test").to_vec(); 168 | let public_key = PrivateKey::from_string(include_str!("keys/unencrypted/rsa_2048_1")) 169 | .unwrap() 170 | .pubkey 171 | .clone(); 172 | 173 | let _verified_signature = VerifiedSshSignature::from_ssh_signature( 174 | message.as_slice(), 175 | signature, 176 | "file", 177 | Some(public_key), 178 | ) 179 | .expect("Failed to verify signature"); 180 | } 181 | 182 | #[test] 183 | fn check_verification_rsa_sha2_256() { 184 | let signature = 185 | SshSignature::from_armored_string(include_str!("signatures/rsa-sha2-256-4096_Test.sig")) 186 | .unwrap(); 187 | 188 | let message = include_bytes!("messages/Test").to_vec(); 189 | let public_key = PrivateKey::from_string(include_str!("keys/unencrypted/rsa-sha2-256-4096")) 190 | .unwrap() 191 | .pubkey 192 | .clone(); 193 | 194 | let _verified_signature = VerifiedSshSignature::from_ssh_signature( 195 | message.as_slice(), 196 | signature, 197 | "file", 198 | Some(public_key), 199 | ) 200 | .expect("Failed to verify signature"); 201 | } 202 | 203 | #[test] 204 | fn check_verification_rsa_sha2_512() { 205 | let signature = 206 | SshSignature::from_armored_string(include_str!("signatures/rsa-sha2-512-4096_Test.sig")) 207 | .unwrap(); 208 | 209 | let message = include_bytes!("messages/Test").to_vec(); 210 | let public_key = PrivateKey::from_string(include_str!("keys/unencrypted/rsa-sha2-512-4096")) 211 | .unwrap() 212 | .pubkey 213 | .clone(); 214 | 215 | let _verified_signature = VerifiedSshSignature::from_ssh_signature( 216 | message.as_slice(), 217 | signature, 218 | "file", 219 | Some(public_key), 220 | ) 221 | .expect("Failed to verify signature"); 222 | 223 | let signature = 224 | SshSignature::from_armored_string(include_str!("signatures/rsa-sha2-512-8192_Test.sig")) 225 | .unwrap(); 226 | 227 | let message = include_bytes!("messages/Test").to_vec(); 228 | let public_key = PrivateKey::from_string(include_str!("keys/unencrypted/rsa-sha2-512-8192")) 229 | .unwrap() 230 | .pubkey 231 | .clone(); 232 | 233 | let _verified_signature = VerifiedSshSignature::from_ssh_signature( 234 | message.as_slice(), 235 | signature, 236 | "file", 237 | Some(public_key), 238 | ) 239 | .expect("Failed to verify signature"); 240 | } 241 | -------------------------------------------------------------------------------- /tests/signatures/ecdsa_256_1_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAE 3 | EEFgRTaZo2nK5Z1J+MDEM7ezk2OosJH4UUeYAOjex2hmXXdqVMMcimgm7B+QKw2E2/mAs6 4 | wJjvqYmyfagnV6+jKgAAAARmaWxlAAAAAAAAAAZzaGE1MTIAAABkAAAAE2VjZHNhLXNoYT 5 | ItbmlzdHAyNTYAAABJAAAAIQCD1GfxmpqeXWCgqF56o6n5Kg6JoY++biscoNrAVyk1bgAA 6 | ACAcISU/nt3NxWZkIzuBB+aGzQrFZPgVcNP3qp++6B5Lhg== 7 | -----END SSH SIGNATURE----- 8 | -------------------------------------------------------------------------------- /tests/signatures/ecdsa_384_1_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAAIgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAG 3 | EEFDwefMXPXMXC6dbMIyfO14w9m7rR+W5UiflTzvv2VfshZ+xJVyEc75votmj49P33OeC0 4 | nRPEmZQLu0S7sgIMB/CKn4SwExt5qu3wz0p0Up+8xK4yFBjr0Uu4Ob4QR/UdAAAABGZpbG 5 | UAAAAAAAAABnNoYTUxMgAAAIQAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAGkAAAAwU+cr 6 | 7y8FzbuoIj9NEJV3InbhZ7H1yua8zEV31CJeEMNyDYCjepTWrsk/LiBzSMgVAAAAMQD/vq 7 | Fic9zusJEvSEQ2KVWv33+kXzZoWv2GSU96+xh4ZCBZlCKakgaLKhq2l3IxRoE= 8 | -----END SSH SIGNATURE----- 9 | -------------------------------------------------------------------------------- /tests/signatures/ed25519_1_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgM7RVAP1Mh0gJktYVbC1/uzz9oQ 3 | 297y3AZMSY1XSbKHkAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx 4 | OQAAAECah4fnzSZhG9dfbSdz8plN34bd1FjRkaB+vM6GT0I6/+ScmKizcnzCtuHw5iTUax 5 | WiWi7QV3NNBSUHY9bAThcB 6 | -----END SSH SIGNATURE----- 7 | -------------------------------------------------------------------------------- /tests/signatures/ed25519_2_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgzQl+l5WZ9EETFteAAWWuRmXOul 3 | PnLBIirIJGcBitPhQAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx 4 | OQAAAEDNZPW4KmP06SEyWLMiXLNHQrUXRHYBCFRuSmZqwmMLrVevKxVk4MHtmTNVEz1lI7 5 | iZKdKXK50gvRH/J7ytlBsK 6 | -----END SSH SIGNATURE----- 7 | -------------------------------------------------------------------------------- /tests/signatures/rsa-sha2-256-4096_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAO5+ipxVjbvYFwmsD2242Z 3 | OmPjgyf1slPycSFcET9KzXMkDow1zowvsVz98hCuWDg4P7QJDcfB6euVsTHylVW3JIZlkS 4 | YznO02KYC2PWkwucA3vWy9vNdcks1l7NF1E4NYuWGD1pRyqV0hZshBShmYr32XFAnCt3gP 5 | WIygpD0rXWG1A0Wvl5+eMjPdhX6FfiSaun64Bgmsc+IOvukYwg8gmoOd3/q5oRIGPRaaYa 6 | CHG6/XgCFcU8cRrk9wicBt+Zh9XamwVXTp36a/CB+54oLmj4c3OQ/88WHuzpzRi5tDES1y 7 | 37/9U9SLsOGygkqoNuWTYJlDhJ13BAZkUcshOL+JdrtvVxrXugvwLscEh/OguYYCEVmpxs 8 | FI/LN22sHH6XiRD7R+PewUfPKHzCBoAfTp6VsA9iI8zj5NVBbqtl0+P+NcdSqdhKDcq7AK 9 | vdCaj3rYgSNPURohODrxr1cQoTF1xIHX323BRbFAasy5XZqQO0uNMUWPO/6/KuxJOWfe6I 10 | H/CSofa67b5u2QZ7+oUhiTVQK4q7Kumb9lE0VPVCrevmHAM3acJy56E6yI30A6CXu0Vik0 11 | AR+vFBZUNJ6wccLFLqzs8v17stY6yMQJUv0+srITjV8Kar6HT/S7N2IYNlpG+b72dgJCsS 12 | Av0bZAIz5v4l8BphB2uc58gpbNYd9QbDAAAABGZpbGUAAAAAAAAABnNoYTUxMgAAAhQAAA 13 | AMcnNhLXNoYTItNTEyAAACAO4e+aJDhs212cyiATi3BHGPpEuH/P1KIrZGxUDhQFIetzvT 14 | fWlpKbxkwgd455+J68y3mTYVHLo2tM83Lara0JRx16ZxeTOmINfRfMMkBA5Q1fe8QzcMq8 15 | mkoqsWy89F39mv950ggRA9PZTS9jkmEszBXeGhWnuR3WNBv1jalqvrwZA5kz4OYDtlWqYf 16 | LiIxP+dm6AhqLB81MnseBM6geYN+VdB40fm+VUF0SX81C+7ZoJjd2Cop6kO9A/YpZsoeUk 17 | zA7YFy3W6lCcc1a+te/RCMrppiu6oRZIzrR1cdXPBKuYW20krXEVGJ1bxNyBEmS/HEP2Dt 18 | CxSZ7vVEEr4m2KF0insbO0Sa1HJhY+Ows5+JbwFBB/QVvWqtg2U5r0PKmAfsMsUd0eUDZ/ 19 | UiIiHtB/aXOpAHT+HMa5rzvCS+Zvvaojbdldc4vwP/RpLv8WBH8bRzpHhWWsbZT+AZDrBG 20 | Llv90q5zuao+svpeLv5zuwZa4ONnEus3awzKEDy7UzmXXA54Wmm7F7+AvbxKIY3oQZMacP 21 | gePy4KFklHBZfp7ket5r/bjMTvykyipsyCMm9B83XrQrbDcrKWhfC83m0yJxUi2hdb9xd8 22 | krDOZwLyeBhv2eGXc0eRIUYCK6LMKhyqLIp5GWAUS4M1wbgH+KI7a5aWAbUt66wQN8Dwla 23 | fGEd4+ 24 | -----END SSH SIGNATURE----- 25 | -------------------------------------------------------------------------------- /tests/signatures/rsa-sha2-512-4096_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBALnQ2Ticd+BeQW8W7fJO/p 3 | E11q3AWjimConyJ+gJUtx54VphPjnBxmxxzrrGrEGhWktmKYf4FP8iFbEzNwK7TdQr08Ep 4 | MfmDyidcBiLIB2QsxkuK76XKIDRQINu0Gho9d5CC0PEE7y/iVY+ZyTW8v0ba3Opr2Evywn 5 | uw4Rl8kZ11BKgsjVDhzrTJ4yoe0HZ06FPXnTBROt0mxMT2kEITnTqQwKfshxzf8lqjT6/M 6 | /+uQhGWLh5Hk4FE2m76N5HbFsqEK0bRen2dpFAQk6R/fy838f6B2miZOMPfqg7y2phJGNd 7 | bog4jVmPXRSraGyRnp9jiC1+9KKXqI7w+/Ogf4t5rv1lbPa07/2z5xdogFSA+pn8Vg4guw 8 | y+9wpiDRD/hG7AkFcZdjhQ40F555e43Ol7jQvFmrIgjX6jyW56zB5RVGRd9dJvuTCvHotL 9 | /MOnLJ/3PHl8NrvFvzLdVojIiI2QYxWF0NrxaAWuMZNLNBsb/qGNFfym3Q1+rNdtR2V2qD 10 | R7RzagfHyvSB6iLD9XrDSiiHR0fR2XiLM9xIZNtrlfiLknUyjvQAR27uL2Ull94R1Qa1w8 11 | yQ2weJhQxNql9kwsjix4Vj7JSJbRLBipQbNyHVK9ioN2xbAN66pwWL/zpuR6pN/qbbW3aj 12 | /gayhOp1OHiVXT/rFle7Xtx/ALbqeZLzAAAABGZpbGUAAAAAAAAABnNoYTUxMgAAAhQAAA 13 | AMcnNhLXNoYTItNTEyAAACADPaXLfF+MenKbp+6Sf1N2nEQBFsH/CVpOszAKbdmKoVKnWQ 14 | /Rc0fFCBevjxnuX2KMAbWycAvZL3F5H+vdtNcXv+TnwfCf/OJ9eV3I0lmJ+OQkS9nWSav4 15 | HfYOeWr3lXNCljUyjTz9K5Yieu1VpjpzXu64gV/ugk20FpIF7ER+F7Ii/mchKN3JISRZk0 16 | fqvWp9X38W8MWZ0t4UWd7iK6/uJEiGclT5UU50PCA6mxhGh1YxO+2wyGTIZVvwjdyAqys7 17 | dN3JCfGjuCAJbzao0Sk978XXv3z0SnYvOdkwijU4q2SDSkFlM90UKmgCko/m9Pv2XTcK0n 18 | SRrIljCXTu93k+fXeOmjvP7y0EqH5CUhCWgaWROtIHw9QKwpZihGu5VFHbtZB9UdstrYhX 19 | JeRBIK4QO7tA6pyNwTf2Gs4gouiflDm0FX/xUATWLsEkU4TqDVv8MxlhzlC/pcoi5nVA5Q 20 | fa/rjErxOD0JIXD/6qA5UAn3cSunFHEbngrYTogqvV7sbq1ATefzwsmSqf9pE5act/aY0m 21 | UbXnaRSpNnDOmmTljEypDAoMIestjNd/OyJeNYvEy0LeSWafOz+3DGnf1vOJoSE1mWGeqJ 22 | 44zpgV/NBwfqAHk8xqpQ0MN9wH7afIU35mSCDsRVVIs04oAaLauVTJNEp9gNHIV7nQJvQj 23 | FDuM2x 24 | -----END SSH SIGNATURE----- 25 | -------------------------------------------------------------------------------- /tests/signatures/rsa-sha2-512-8192_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | 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 43 | -----END SSH SIGNATURE----- 44 | -------------------------------------------------------------------------------- /tests/signatures/rsa_2048_1_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAMN+pPTJdnOgditMMGu8Vu 3 | OuM5VD3AM4vo8SYROg9Ws1q+Nc4B+ZkMt3prMpgOJ+95VeMFRX5G6btzPkxccGHTjo3F8b 4 | iDo5WZVDEX5zNY/Q6XLcvhXDHdDiZsPeyFMsXfpi/cSFvoPMaP9IO8IWBONirOS66k3soq 5 | uTgNTFNsK+CgDtbRIgZgPt+sjDrgrGnUxvsADKHnPRZLNjKXXmVFUacQFFvkEQQxA7xy+S 6 | ZQRS+XSMAtj6FrB6ON53NaNeJB3/E7gTwV8o24h7RJRMdkzQNt4G9Zux5/k8jX8fDdMK0V 7 | cQ4GwxhNDBJsMaIUQOo1D73t2Fy0rK56gV19uh2QUAAAAEZmlsZQAAAAAAAAAGc2hhNTEy 8 | AAABFAAAAAxyc2Etc2hhMi01MTIAAAEADGLl6LlgHSFCshSlFI6Gn2o40eWhhq/1UckMIO 9 | RJaioyKakKllniBAgwikeLY6RHjAcM81ul9Fg8Tz5AEVHSUIRKsmEgxqBQsh9usLoxlTSv 10 | fVbcg4xQA7avKEUyoshstQ7wQiYYOq7MWD9sph84K61ZAWJdI+C3iIqE+6jKNX2PYSADMN 11 | W96nJ8epblU5fRrTBzBEJh6BTw7eJtnFFAXuWSIMeyNKzUGG+hfxPtS7BHkgshlNcDaEhE 12 | p0e9El2rnfgXBxLn/V+wtuJ9tQnaYObElzHrR7iD1EP+aPRxPE1z25ahaTFPaZZq9haRsQ 13 | d9CYc3sjp+fOD9smDUZuY5Pw== 14 | -----END SSH SIGNATURE----- 15 | -------------------------------------------------------------------------------- /tests/signatures/sk_ecdsa_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAAIsAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBvcGVuc3NoLmNvbQ 3 | AAAAhuaXN0cDI1NgAAAEEEssGoVv/5R9GnSr8mUypcp3V7sOQ+JBR/xU96Go1E85rs1p6K 4 | lqoathF5sm6978X1HR8convhTLol/7I2W+a44QAAABBzc2g6U1NIQ2VydHNUZXN0AAAABG 5 | ZpbGUAAAAAAAAABnNoYTUxMgAAAHgAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBvcGVu 6 | c3NoLmNvbQAAAEkAAAAgWgaCdVMvGD1jYqXU7kPqzyhP8u67l5K+xA9IqUi/SYIAAAAhAM 7 | tD19+UN/QaVNcVSdg6aiZeMjtP6KINpG+J8RDGf6BZBQAAUeI= 8 | -----END SSH SIGNATURE----- 9 | -------------------------------------------------------------------------------- /tests/signatures/sk_ed25519_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAAFYAAAAac2stc3NoLWVkMjU1MTlAb3BlbnNzaC5jb20AAAAghVNuWd 3 | E44T97aMKXqJ5KCo+Na2fGPOakIdT5MncRsvwAAAAQc3NoOlNTSENlcnRzVGVzdAAAAARm 4 | aWxlAAAAAAAAAAZzaGE1MTIAAABnAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAA 5 | AAQJEH/Fvap9y5ojBlrhMJ0kBoOL1nPIPs79dDaEQATFCHIKFmH9bQF0jnJUvdu/Y5hy1f 6 | dUN5oCNPqBwZq1DXxQYFAABR2w== 7 | -----END SSH SIGNATURE----- 8 | -------------------------------------------------------------------------------- /tests/signatures_bad/ecdsa_256_1_bitflip_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAE 3 | EEFgRTaZo2nK5Z1J+MDEM7ezk2OosJH4UUeYAOjex2hmXXdqVMMcimgm7B+QKw2E2/mAs6 4 | wJjvqYmyfagnV6+jKgAAAARmawxlAAAAAAAAAAZzaGE1MTIAAABkAAAAE2VjZHNhLXNoYT 5 | ItbmlzdHAyNTYAAABJAAAAIQCD1GfxmpqeXWCgqF56o6n5Kg6JoY++biscoNrAVyk1bgAA 6 | ACAcISU/nt3NxWZkIzuBB+aGzQrFZPgVcNP3qp++6B5Lhg== 7 | -----END SSH SIGNATURE----- 8 | -------------------------------------------------------------------------------- /tests/signatures_bad/ed25519_1_empty-namespace_Test.sig: -------------------------------------------------------------------------------- 1 | -----BEGIN SSH SIGNATURE----- 2 | U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgM7RVAP1Mh0gJktYVbC1/uzz9oQ 3 | 297y3AZMSY1XSbKHkAAAAAAAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5AAAA 4 | QJqHh+fNJmEb119tJ3PymU3fht3UWNGRoH68zoZPQjr/5JyYqLNyfMK24fDmJNRrFaJaLt 5 | BXc00FJQdj1sBOFwE= 6 | -----END SSH SIGNATURE----- 7 | -------------------------------------------------------------------------------- /tests/sk-privkey.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::PrivateKey; 2 | use std::io::BufWriter; 3 | 4 | #[test] 5 | fn parse_sk_ed25519_private_key() { 6 | let privkey = PrivateKey::from_string(include_str!("keys/sk/ed25519")); 7 | assert!(privkey.is_ok()); 8 | let privkey = privkey.unwrap(); 9 | assert_eq!( 10 | privkey.pubkey.fingerprint().hash, 11 | "GlvFAEnledYF0XG1guJ7dT2d0Mk88GmPAiHk8+zCBlA" 12 | ); 13 | 14 | let mut buf = BufWriter::new(Vec::new()); 15 | privkey.write(&mut buf).unwrap(); 16 | let serialized = String::from_utf8(buf.into_inner().unwrap()).unwrap(); 17 | assert_eq!(include_str!("keys/sk/ed25519"), serialized); 18 | } 19 | 20 | #[test] 21 | fn parse_sk_ecdsa_256_private_key() { 22 | let privkey = PrivateKey::from_string(include_str!("keys/sk/ecdsa")); 23 | assert!(privkey.is_ok()); 24 | let privkey = privkey.unwrap(); 25 | assert_eq!( 26 | privkey.pubkey.fingerprint().hash, 27 | "Ylfgx0U2M9/IVN0+b5/IxdNeVCotsdrRZ5lu5FG2ouc" 28 | ); 29 | 30 | let mut buf = BufWriter::new(Vec::new()); 31 | privkey.write(&mut buf).unwrap(); 32 | let serialized = String::from_utf8(buf.into_inner().unwrap()).unwrap(); 33 | assert_eq!(include_str!("keys/sk/ecdsa"), serialized); 34 | } 35 | -------------------------------------------------------------------------------- /tests/sk-pubkey.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::PublicKey; 2 | 3 | #[test] 4 | fn parse_sk_ed25519() { 5 | let ssh_pubkey = PublicKey::from_string(include_str!("keys/sk/ed25519.pub")); 6 | assert!(ssh_pubkey.is_ok()); 7 | let ssh_pubkey = ssh_pubkey.unwrap(); 8 | 9 | assert_eq!( 10 | ssh_pubkey.fingerprint().hash, 11 | "GlvFAEnledYF0XG1guJ7dT2d0Mk88GmPAiHk8+zCBlA" 12 | ); 13 | } 14 | 15 | #[test] 16 | fn parse_sk_ecdsa256() { 17 | let in_data = include_str!("keys/sk/ecdsa.pub").trim(); 18 | let ssh_pubkey = PublicKey::from_string(in_data); 19 | assert!(ssh_pubkey.is_ok()); 20 | let ssh_pubkey = ssh_pubkey.unwrap(); 21 | 22 | assert_eq!( 23 | ssh_pubkey.fingerprint().hash, 24 | "Ylfgx0U2M9/IVN0+b5/IxdNeVCotsdrRZ5lu5FG2ouc" 25 | ); 26 | 27 | let out_data = format!("{}", ssh_pubkey); 28 | assert_eq!(in_data, out_data); 29 | } 30 | -------------------------------------------------------------------------------- /tests/writer.rs: -------------------------------------------------------------------------------- 1 | use sshcerts::ssh::Writer; 2 | 3 | #[test] 4 | fn bad_data_one() { 5 | let test_vector = [0, 0, 3]; 6 | let mut writer = Writer::new(); 7 | writer.write_mpint(&test_vector); 8 | let result = writer.as_bytes(); 9 | assert_eq!(result, &vec![0, 0, 0, 1, 3]); 10 | } 11 | 12 | #[test] 13 | fn difficult_data_one() { 14 | let test_vector = [255]; 15 | let mut writer = Writer::new(); 16 | writer.write_mpint(&test_vector); 17 | let result = writer.as_bytes(); 18 | assert_eq!(result, &vec![0, 0, 0, 2, 0, 255]); 19 | } 20 | 21 | #[test] 22 | fn edge_case_with_127() { 23 | let test_vector = [127]; 24 | let mut writer = Writer::new(); 25 | writer.write_mpint(&test_vector); 26 | let result = writer.as_bytes(); 27 | assert_eq!(result, &vec![0, 0, 0, 1, 127]); 28 | } 29 | 30 | #[test] 31 | fn edge_case_with_128() { 32 | let test_vector = [128]; 33 | let mut writer = Writer::new(); 34 | writer.write_mpint(&test_vector); 35 | let result = writer.as_bytes(); 36 | assert_eq!(result, &vec![0, 0, 0, 2, 0, 128]); 37 | } 38 | 39 | #[test] 40 | fn filled_u32_mpint() { 41 | let test_vector = [255, 255, 255, 255]; 42 | let mut writer = Writer::new(); 43 | writer.write_mpint(&test_vector); 44 | let result = writer.as_bytes(); 45 | assert_eq!(result, &vec![0, 0, 0, 5, 0, 255, 255, 255, 255]); 46 | } 47 | 48 | #[test] 49 | fn all_zeroes() { 50 | let test_vector = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 51 | let mut writer = Writer::new(); 52 | writer.write_mpint(&test_vector); 53 | let result = writer.as_bytes(); 54 | assert_eq!(result, &vec![0, 0, 0, 0]); 55 | } 56 | 57 | #[test] 58 | fn one_zero() { 59 | let test_vector = [0]; 60 | let mut writer = Writer::new(); 61 | writer.write_mpint(&test_vector); 62 | let result = writer.as_bytes(); 63 | assert_eq!(result, &vec![0, 0, 0, 0]); 64 | } 65 | 66 | #[test] 67 | fn empty() { 68 | let test_vector = []; 69 | let mut writer = Writer::new(); 70 | writer.write_mpint(&test_vector); 71 | let result = writer.as_bytes(); 72 | assert_eq!(result, &vec![0, 0, 0, 0]); 73 | } --------------------------------------------------------------------------------