├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── bulk.yaml ├── src ├── conversion.rs ├── debug.rs ├── error.rs ├── lib.rs ├── openssh.rs └── stdimpls.rs ├── test-keys ├── buggy_key ├── ed25519 ├── ed25519.pub ├── rsa1024 ├── rsa1024.pub ├── rsa1024new ├── rsa2048 ├── rsa2048.pub ├── rsa4096 └── rsa4096.pub ├── tests ├── openssh_priv.rs └── openssh_pub.rs └── vagga.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | /.vagga 2 | Cargo.lock 3 | /target 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | language: rust 4 | 5 | cache: 6 | - cargo 7 | 8 | before_cache: 9 | - rm -r $TRAVIS_BUILD_DIR/target/debug 10 | 11 | jobs: 12 | include: 13 | - os: linux 14 | rust: stable 15 | - os: linux 16 | rust: beta 17 | - os: linux 18 | rust: nightly 19 | 20 | # deploy 21 | - stage: publish 22 | os: linux 23 | rust: stable 24 | env: 25 | # CARGO_TOKEN 26 | - secure: "u07mY+aJfEXv8Ds8BQN3/Kuo0JoKiBzkfLvCeRTaLxGvS7igDb6kyYEdAaUAcG6NuG7by3AGl8yU8vibryYI1+cuAQTFPuf12v0PG7t7gG7Fa6PGrN0LvpAfbdNsF8Irnl6a3Ze3032p5ZzQjNfWW0n0bAZ10Msi7hSpaAhF1XzPB7PcfMXnMqG5YLqa4E2hQHr8vjaqWsO4yZJEdxXqp/vk7tOJsnWLFk0QuiycVoIoMtIOs2yMrJuCoD2BO2UgvNWV9WjDsBS7AgHCHqa3vyeZIs46fqyKh9nypUNZWKvhu1xQmxuhxbC4Ic1i959lx52jkklKbhrK4ejK0fdKHqCZkdVWBcJpx4uRIqQYsnwba/DkFMTZ+209mznLuQ/eZsC7eq7kVh+HaM+T4pROuxm+uuZgwihgTQXSFCpHGp46eInnj8aKT05xGsUF6lXqkaXDDvrLBoQkrZxLoRPvxzefItyCeI1fLHkhw3lygqWE1Uq7AiU8/tW3HURVUy4ByiPSUKQhmfSMIVMa005YeUz7zAmmS9WaWixjdWr5tembLIrKa1s3L51XkrhE2WULDXrQofTP6TS7frseabOEvLitJYGaJ0BpzmhJVFM+POahImyLmayeUIolyM69UiZeiLq+SMvO1VOar/PeDqTZgQotj6gzotcWwNDzV32L/1U=" 27 | install: true 28 | script: true 29 | 30 | deploy: 31 | - provider: script 32 | script: 'cargo publish --verbose --token=$CARGO_TOKEN' 33 | on: 34 | tags: true 35 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ssh-keys" 3 | description = """ 4 | Parser of ssh public and private keys 5 | """ 6 | license = "MIT/Apache-2.0" 7 | readme = "README.md" 8 | keywords = ["ssh", "keys", "parser"] 9 | categories = ["parser-implementations"] 10 | homepage = "http://github.com/tailhook/ssh-keys" 11 | documentation = "http://docs.rs/ssh-keys" 12 | version = "0.1.4" 13 | authors = ["paul@colomiets.name"] 14 | 15 | [dependencies] 16 | quick-error = "1.1.0" 17 | base64 = "0.9.0" 18 | byteorder = "1.2.2" 19 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 The ssh-keys Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SSH Keys Crate 2 | ============== 3 | 4 | [Docs](https://docs.rs/ssh-keys/) | 5 | [Github](https://github.com/tailhook/ssh-keys/) | 6 | [Crate](https://crates.io/crates/ssh-keys) 7 | 8 | 9 | Pure SSH implementation of the parser of the SSH keys, both public and private. 10 | 11 | 12 | License 13 | ======= 14 | 15 | Licensed under either of 16 | 17 | * Apache License, Version 2.0, 18 | (./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 19 | * MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT) 20 | at your option. 21 | 22 | Contribution 23 | ------------ 24 | 25 | Unless you explicitly state otherwise, any contribution intentionally 26 | submitted for inclusion in the work by you, as defined in the Apache-2.0 27 | license, shall be dual licensed as above, without any additional terms or 28 | conditions. 29 | 30 | -------------------------------------------------------------------------------- /bulk.yaml: -------------------------------------------------------------------------------- 1 | minimum-bulk: v0.4.5 2 | 3 | versions: 4 | 5 | - file: Cargo.toml 6 | block-start: ^\[package\] 7 | block-end: ^\[.*\] 8 | regex: ^version\s*=\s*"(\S+)" 9 | -------------------------------------------------------------------------------- /src/conversion.rs: -------------------------------------------------------------------------------- 1 | use {PrivateKey, PublicKey}; 2 | 3 | 4 | impl PrivateKey { 5 | /// Return public key for this private key 6 | pub fn public_key(&self) -> PublicKey { 7 | match self { 8 | &PrivateKey::Ed25519(data) => { 9 | let mut ar = [0u8; 32]; 10 | ar.copy_from_slice(&data[32..]); 11 | PublicKey::Ed25519(ar) 12 | } 13 | &PrivateKey::Rsa { ref e, ref n, .. } => { 14 | PublicKey::Rsa { exponent: e.clone(), modulus: n.clone() } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use {PublicKey, PrivateKey}; 4 | 5 | impl fmt::Debug for PublicKey { 6 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 7 | use PublicKey::*; 8 | match *self { 9 | Rsa { .. } => { 10 | // TODO(tailhook) show length 11 | write!(f, "PublicKey::Rsa") 12 | } 13 | Ed25519(..) => { 14 | write!(f, "PublicKey::Ed25519") 15 | } 16 | } 17 | } 18 | } 19 | 20 | impl fmt::Debug for PrivateKey { 21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | use PrivateKey::*; 23 | match *self { 24 | Rsa { .. } => { 25 | // TODO(tailhook) show length 26 | write!(f, "PrivateKey::Rsa") 27 | } 28 | Ed25519(..) => { 29 | write!(f, "PrivateKey::Ed25519") 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | quick_error! { 2 | /// Key parsing error 3 | #[derive(Debug)] 4 | pub enum Error { 5 | /// This error usually means that file is damaged 6 | InvalidFormat { 7 | description("invalid key format") 8 | } 9 | /// Unsupported key type 10 | UnsupportedType(typ: String) { 11 | description("unsupported key type") 12 | display("unsupported key type {:?}", typ) 13 | } 14 | /// Private key was encrypted (we don't support encrypted keys yet) 15 | Encrypted { 16 | description("key was encrypted") 17 | } 18 | #[doc(hidden)] 19 | __Nonexhaustive 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Public and private key parser 2 | //! 3 | //! The only key format supported so far is ``openssh``. 4 | //! 5 | //! [Docs](https://docs.rs/ssh-keys/) | 6 | //! [Github](https://github.com/tailhook/ssh-keys/) | 7 | //! [Crate](https://crates.io/crates/ssh-keys) 8 | //! 9 | #![warn(missing_docs)] 10 | #![warn(missing_debug_implementations)] 11 | 12 | extern crate base64; 13 | extern crate byteorder; 14 | #[macro_use] extern crate quick_error; 15 | 16 | mod error; 17 | mod debug; 18 | mod stdimpls; 19 | mod conversion; 20 | pub mod openssh; 21 | 22 | pub use error::Error; 23 | 24 | /// Public key enum 25 | pub enum PublicKey { 26 | /// RSA key 27 | #[allow(missing_docs)] 28 | Rsa { exponent: Vec, modulus: Vec }, 29 | /// Ed25519 (eliptic curves) key 30 | Ed25519([u8; 32]), 31 | } 32 | 33 | /// Secret key enum 34 | pub enum PrivateKey { 35 | /// RSA key 36 | #[allow(missing_docs)] 37 | Rsa { n: Vec, e: Vec, d: Vec, iqmp: Vec, 38 | p: Vec, q: Vec }, 39 | /// Ed25519 (eliptic curves) key 40 | Ed25519([u8; 64]), 41 | } 42 | -------------------------------------------------------------------------------- /src/openssh.rs: -------------------------------------------------------------------------------- 1 | //! Parser of OpenSSH keys 2 | //! 3 | //! Key formats supported: 4 | //! 5 | //! * `ssh-rsa` 6 | //! * `ssh-ed25519` 7 | //! 8 | //! Both ASN1 and openssh-key-v1 format supported. 9 | //! 10 | //! Password-protected private keys are not supported yet 11 | 12 | use std::str::from_utf8; 13 | 14 | use base64; 15 | use byteorder::{BigEndian, ByteOrder}; 16 | 17 | 18 | use {PublicKey, PrivateKey, Error}; 19 | 20 | struct Cursor<'a> { 21 | data: &'a [u8], 22 | offset: usize, 23 | } 24 | 25 | struct Asn1<'a> { 26 | data: &'a [u8], 27 | offset: usize, 28 | } 29 | 30 | 31 | /// Parse a single SSH public key in openssh format 32 | pub fn parse_public_key(line: &str) -> Result { 33 | let mut iter = line.split_whitespace(); 34 | let kind = iter.next().ok_or(Error::InvalidFormat)?; 35 | let data = iter.next().ok_or(Error::InvalidFormat)?; 36 | let buf = b64decode(data.as_bytes())?; 37 | 38 | let mut cur = Cursor::new(&buf); 39 | let int_kind = cur.read_string()?; 40 | if int_kind != int_kind { 41 | return Err(Error::InvalidFormat); 42 | } 43 | 44 | match kind { 45 | "ssh-rsa" => { 46 | let e = cur.read_bytes()?; 47 | let n = cur.read_bytes()?; 48 | Ok(PublicKey::Rsa { exponent: e.to_vec(), modulus: n.to_vec() }) 49 | } 50 | "ssh-ed25519" => { 51 | let key = cur.read_bytes()?; 52 | if key.len() != 32 { 53 | return Err(Error::InvalidFormat); 54 | } 55 | let mut array_key = [0u8; 32]; 56 | array_key.copy_from_slice(key); 57 | Ok(PublicKey::Ed25519(array_key)) 58 | } 59 | _ => Err(Error::UnsupportedType(kind.to_string())) 60 | } 61 | } 62 | 63 | fn b64decode(data: &[u8]) -> Result, Error> { 64 | base64::decode_config(data, base64::Config::new( 65 | base64::CharacterSet::Standard, 66 | /*pad*/ true, 67 | /*strip_whitepace*/ true, 68 | base64::LineWrap::NoWrap, // irrelevant 69 | )) 70 | .map_err(|_| Error::InvalidFormat) 71 | } 72 | 73 | /// Parse a SSH private key in openssh format 74 | /// 75 | /// Note new format of SSH key can potentially contain more than one key 76 | pub fn parse_private_key(data: &str) -> Result, Error> { 77 | if data.starts_with("-----BEGIN RSA PRIVATE KEY-----") { 78 | let end = data.find("-----END RSA PRIVATE KEY-----") 79 | .ok_or(Error::InvalidFormat)?; 80 | let start = match (data[..end].find("\n\n"), 81 | data[..end].find("\r\n\r\n")) 82 | { 83 | (Some(x), Some(y)) if x < y => x, 84 | (Some(_), Some(y)) => y, 85 | (Some(x), None) => x, 86 | (None, Some(y)) => y, 87 | (None, None) => "-----BEGIN RSA PRIVATE KEY-----".len(), 88 | }; 89 | let data = b64decode(data[start..end].trim().as_bytes())?; 90 | let mut cur = Asn1::new(&data); 91 | let mut items = cur.sequence()?; 92 | let ver = items.read_short_int()?; 93 | if ver != 0 { 94 | return Err(Error::UnsupportedType(format!("version {}", ver))); 95 | } 96 | let n = items.read_big_int()?; 97 | let e = items.read_big_int()?; 98 | let d = items.read_big_int()?; 99 | let p = items.read_big_int()?; 100 | let q = items.read_big_int()?; 101 | let _x = items.read_big_int()?; 102 | let _y = items.read_big_int()?; 103 | let iqmp = items.read_big_int()?; 104 | return Ok(vec![PrivateKey::Rsa { 105 | n: n.to_vec(), e: e.to_vec(), d: d.to_vec(), 106 | iqmp: iqmp.to_vec(), 107 | p: p.to_vec(), q: q.to_vec(), 108 | }]); 109 | } else if data.starts_with("-----BEGIN OPENSSH PRIVATE KEY-----") { 110 | let start = "-----BEGIN OPENSSH PRIVATE KEY-----".len(); 111 | let end = data.find("-----END OPENSSH PRIVATE KEY-----") 112 | .ok_or(Error::InvalidFormat)?; 113 | if start >= end { 114 | return Err(Error::InvalidFormat); 115 | } 116 | let data = b64decode(data[start..end].trim().as_bytes())?; 117 | let end = data.iter().position(|&x| x == 0) 118 | .ok_or(Error::InvalidFormat)?; 119 | let kind = from_utf8(&data[..end]).map_err(|_| Error::InvalidFormat)?; 120 | match kind { 121 | "openssh-key-v1" => { 122 | let mut cur = Cursor::new(&data[end+1..]); 123 | let cipher_name = cur.read_string()?; 124 | let kdf_name = cur.read_string()?; 125 | let opt = cur.read_string()?; 126 | if cipher_name != "none" || kdf_name != "none" || opt != "" { 127 | return Err(Error::Encrypted); 128 | } 129 | let num_keys = cur.read_int()?; 130 | let mut result = Vec::new(); 131 | for _ in 0..num_keys { 132 | let _pub_key = cur.read_bytes()?; 133 | let priv_key = cur.read_bytes()?; 134 | let mut pcur = Cursor::new(priv_key); 135 | let c1 = pcur.read_int()?; 136 | let c2 = pcur.read_int()?; 137 | if c1 != c2 { 138 | return Err(Error::InvalidFormat); 139 | } 140 | let key_type = pcur.read_string()?; 141 | match key_type { 142 | "ssh-ed25519" => { 143 | let _pub_key = pcur.read_bytes()?; 144 | let priv_key = pcur.read_bytes()?; 145 | let _comment = pcur.read_string()?; 146 | 147 | let mut array_key = [0u8; 64]; 148 | array_key.copy_from_slice(priv_key); 149 | result.push(PrivateKey::Ed25519(array_key)); 150 | } 151 | "ssh-rsa" => { 152 | let n = pcur.read_bytes()?; 153 | let e = pcur.read_bytes()?; 154 | let d = pcur.read_bytes()?; 155 | let iqmp = pcur.read_bytes()?; 156 | let p = pcur.read_bytes()?; 157 | let q = pcur.read_bytes()?; 158 | let _comment = pcur.read_string()?; 159 | result.push(PrivateKey::Rsa { 160 | n: n.to_vec(), e: e.to_vec(), d: d.to_vec(), 161 | iqmp: iqmp.to_vec(), 162 | p: p.to_vec(), q: q.to_vec(), 163 | }) 164 | } 165 | _ => { 166 | return Err(Error::UnsupportedType( 167 | key_type.to_string())); 168 | } 169 | } 170 | } 171 | return Ok(result); 172 | } 173 | _ => return Err(Error::UnsupportedType(kind.to_string())), 174 | } 175 | } else { 176 | Err(Error::UnsupportedType("unknown".to_string())) 177 | } 178 | } 179 | 180 | impl<'a> Cursor<'a> { 181 | pub fn new(data: &[u8]) -> Cursor { 182 | Cursor { 183 | data: data, 184 | offset: 0, 185 | } 186 | } 187 | fn read_int(&mut self) -> Result { 188 | let cur = &self.data[self.offset..]; 189 | if cur.len() < 4 { 190 | return Err(Error::InvalidFormat); 191 | } 192 | self.offset += 4; 193 | return Ok(BigEndian::read_u32(&cur[..4])); 194 | } 195 | fn read_bytes(&mut self) -> Result<&'a [u8], Error> { 196 | let cur = &self.data[self.offset..]; 197 | if cur.len() < 4 { 198 | return Err(Error::InvalidFormat); 199 | } 200 | let len = BigEndian::read_u32(&cur[..4]) as usize; 201 | if cur.len() < len + 4 { 202 | return Err(Error::InvalidFormat); 203 | } 204 | self.offset += len + 4; 205 | return Ok(&cur[4..len+4]); 206 | } 207 | fn read_string(&mut self) -> Result<&'a str, Error> { 208 | from_utf8(self.read_bytes()?) 209 | .map_err(|_| Error::InvalidFormat) 210 | } 211 | } 212 | 213 | 214 | /// A limited ASN1 (DER) parser suitable to decode RSA key 215 | impl<'a> Asn1<'a> { 216 | pub fn new(data: &[u8]) -> Asn1 { 217 | Asn1 { 218 | data: data, 219 | offset: 0, 220 | } 221 | } 222 | fn read_len(&mut self) -> Result { 223 | if self.offset >= self.data.len() { 224 | return Err(Error::InvalidFormat); 225 | } 226 | let lbyte = self.data[self.offset]; 227 | self.offset += 1; 228 | if lbyte == 128 || lbyte == 255 { 229 | return Err(Error::InvalidFormat); 230 | } 231 | if lbyte & 128 == 0 { 232 | return Ok(lbyte as usize); 233 | } 234 | let nbytes = (lbyte & 127) as usize; 235 | if self.data.len() < self.offset + nbytes { 236 | return Err(Error::InvalidFormat); 237 | } 238 | let mut result: usize = 0; 239 | for i in 0..nbytes { 240 | result = result.checked_mul(256).ok_or(Error::InvalidFormat)? 241 | + self.data[self.offset+i] as usize; 242 | } 243 | self.offset += nbytes; 244 | return Ok(result); 245 | } 246 | pub fn sequence(&mut self) -> Result, Error> { 247 | if self.offset >= self.data.len() { 248 | return Err(Error::InvalidFormat); 249 | } 250 | let byte = self.data[self.offset]; 251 | // Universal, construct, sequence 252 | if byte != (0 << 6) | (1 << 5) | 16 { 253 | return Err(Error::InvalidFormat); 254 | } 255 | self.offset += 1; 256 | let bytes = self.read_len()?; 257 | if self.offset+bytes > self.data.len() { 258 | return Err(Error::InvalidFormat); 259 | } 260 | let res = Asn1::new(&self.data[self.offset..self.offset+bytes]); 261 | self.offset += bytes; 262 | return Ok(res); 263 | } 264 | pub fn read_big_int(&mut self) -> Result<&'a [u8], Error> { 265 | if self.offset >= self.data.len() { 266 | return Err(Error::InvalidFormat); 267 | } 268 | let byte = self.data[self.offset]; 269 | // Universal, primitive, integer 270 | if byte != (0 << 6) | (0 << 5) | 2 { 271 | return Err(Error::InvalidFormat); 272 | } 273 | self.offset += 1; 274 | let len = self.read_len()?; 275 | if self.data.len() < self.offset + len { 276 | return Err(Error::InvalidFormat); 277 | } 278 | let result = &self.data[self.offset..self.offset + len]; 279 | self.offset += len; 280 | return Ok(result); 281 | } 282 | pub fn read_short_int(&mut self) -> Result { 283 | let data = self.read_big_int()?; 284 | if data.len() != 1 { 285 | return Err(Error::InvalidFormat); 286 | } 287 | return Ok(data[0]); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/stdimpls.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use base64; 4 | use byteorder::{BigEndian, WriteBytesExt}; 5 | 6 | use {PublicKey, PrivateKey}; 7 | 8 | 9 | // We have to implement his manually because Clone doesnt work for [u8; 64] 10 | impl Clone for PublicKey { 11 | fn clone(&self) -> PublicKey { 12 | use PublicKey::*; 13 | match *self { 14 | Rsa { ref exponent, ref modulus } => { 15 | Rsa { exponent: exponent.clone(), modulus: modulus.clone() } 16 | } 17 | Ed25519(data) => Ed25519(data), 18 | } 19 | } 20 | } 21 | 22 | // We have to implement his manually because PartialEq doesnt work for [u8; 64] 23 | impl PartialEq for PublicKey { 24 | fn eq(&self, other: &PublicKey) -> bool { 25 | use PublicKey::*; 26 | match (self, other) { 27 | (&Rsa { exponent: ref e1, modulus: ref n1 }, 28 | &Rsa { exponent: ref e2, modulus: ref n2 }) 29 | => e1 == e2 && n1 == n2, 30 | (&Rsa {..}, _) => false, 31 | (&Ed25519(ref d1), &Ed25519(ref d2)) => d1 == d2, 32 | (&Ed25519(..), _) => false, 33 | } 34 | 35 | } 36 | } 37 | 38 | impl Eq for PublicKey { } 39 | 40 | impl fmt::Display for PublicKey { 41 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 42 | use PublicKey::*; 43 | match *self { 44 | Rsa { ref exponent, ref modulus } => { 45 | let mut buf = Vec::with_capacity(512); 46 | buf.write_u32::("ssh-rsa".len() as u32).unwrap(); 47 | buf.extend(b"ssh-rsa"); 48 | buf.write_u32::(exponent.len() as u32).unwrap(); 49 | buf.extend(exponent); 50 | buf.write_u32::(modulus.len() as u32).unwrap(); 51 | buf.extend(modulus); 52 | write!(f, "ssh-rsa {}", 53 | base64::display::Base64Display::standard(&buf)) 54 | } 55 | Ed25519(data) => { 56 | let mut buf = Vec::with_capacity(512); 57 | buf.write_u32::("ssh-ed25519".len() as u32).unwrap(); 58 | buf.extend(b"ssh-ed25519"); 59 | buf.write_u32::(data.len() as u32).unwrap(); 60 | buf.extend(&data); 61 | write!(f, "ssh-ed25519 {}", 62 | base64::display::Base64Display::standard(&buf)) 63 | } 64 | } 65 | } 66 | } 67 | 68 | // We have to implement his manually because Clone doesnt work for [u8; 64] 69 | impl Clone for PrivateKey { 70 | fn clone(&self) -> PrivateKey { 71 | use PrivateKey::*; 72 | match *self { 73 | Ed25519(data) => Ed25519(data), 74 | Rsa { ref n, ref e, ref d, ref iqmp, ref p, ref q } 75 | => Rsa { n: n.clone(), e: e.clone(), d: d.clone(), 76 | iqmp: iqmp.clone(), p: p.clone(), q: q.clone() }, 77 | } 78 | } 79 | } 80 | 81 | // We have to implement his manually because PartialEq doesnt work for [u8; 64] 82 | impl PartialEq for PrivateKey { 83 | fn eq(&self, other: &PrivateKey) -> bool { 84 | use PrivateKey::*; 85 | match (self, other) { 86 | (&Rsa { n: ref n1, e: ref e1, d: ref d1, iqmp: ref iqmp1, 87 | p: ref p1, q: ref q1 }, 88 | &Rsa { n: ref n2, e: ref e2, d: ref d2, iqmp: ref iqmp2, 89 | p: ref p2, q: ref q2 } 90 | ) => { 91 | n1 == n2 && e1 == e2 && d1 == d2 && iqmp1 == iqmp2 && 92 | p1 == p2 && q1 == q2 93 | } 94 | (&Rsa {..}, _) => false, 95 | (&Ed25519(ref d1), &Ed25519(ref d2)) => &d1[..] == &d2[..], 96 | (&Ed25519(..), _) => false, 97 | } 98 | 99 | } 100 | } 101 | 102 | impl Eq for PrivateKey { } 103 | -------------------------------------------------------------------------------- /test-keys/buggy_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIiCXAIBAAKBgQDApmvwhoDOXRlAl/lhWuFIuQ+hsjkgYBTJMh4xpkK1Uup8mhd/ 3 | ybbkT1Okx1UDcvYrmqQkYTbbrwnt5LtwTl9oYA8Q4XzWZf9XOmLpnQ9YL4srsaG+ 4 | vAgmGKgFl9Vo5En7QoTiqOD74sQxKRI+YgBAIuvZxsdVDyK6x3dhXHW7MwIDAQAB 5 | AoGAIzLE6e9JPWsZIAokUDtQif718i8j7NxhL9luVtfvvD1oBlgJDxoFxnUP7T1W 6 | NQkeHR1l/Bee9Eho4gQ2kvBe0dv4XV9HQlF5Ls/CGOqj88OHsjwg5JABhyQLCmZx 7 | KPSF4CC1fsUM9AXOZETNM9hqvLU2oQ4HaOks9j/aKRXVw6ECQQDly2Iv7k3QU+t6 8 | Z0VKxK0COuy2uT6w2TwUqIsSGciXBGenrld0y9mnCzWfC6yfgt4IKeaNcA6o+Rc+ 9 | 4ofVh6UrAkEA1p6lQFwAKrJMjusqRUY2iml6ohKwV61/+1Bep4WtQ8Lu1zjmvU2Y 10 | iDXLn4t+hrMmf8TwAMKqklXzIkrbJurOGQJALCmJJjMDK11lbPG55GOl6D7QzVEO 11 | lkg5frLhf0oLPdKv5Q7haeXfX3YNCRLiSwmIiEMdNuNpPdoZOJGqj0PxXwJASJ8m 12 | C8EVzVzHyxSIGSjJC8zlUjJAuWpcBA4N+WGiX2t2phz69tNq6axKWpI5HhtAy7s3 13 | +wnj7eBnhRMPVPvfAQJBALNApqPxT3xgyklOujFp88kr85jDM/wtGU7zRa1Z96WS 14 | DtML3UCMk7cuFELnQfpp0YL+WQ4JeuBtagjjHyh4c0A= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /test-keys/ed25519: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACC2h/5eqGvaiEUKTBE0zCp32ry2KvPvhyVXHV2PjxNlKgAAAJDhCJGi4QiR 4 | ogAAAAtzc2gtZWQyNTUxOQAAACC2h/5eqGvaiEUKTBE0zCp32ry2KvPvhyVXHV2PjxNlKg 5 | AAAEBuiKVsRW9rjAjpLI+tVm8DuQ8/RCxj0G1Ncsvl446uQbaH/l6oa9qIRQpMETTMKnfa 6 | vLYq8++HJVcdXY+PE2UqAAAAB3BjQGRpc2gBAgMEBQY= 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /test-keys/ed25519.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILaH/l6oa9qIRQpMETTMKnfavLYq8++HJVcdXY+PE2Uq pc@dish 2 | -------------------------------------------------------------------------------- /test-keys/rsa1024: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDApmvwhoDOXRlAl/lhWuFIuQ+hsjkgYBTJMh4xpkK1Uup8mhd/ 3 | ybbkT1Okx1UDcvYrmqQkYTbbrwnt5LtwTl9oYA8Q4XzWZf9XOmLpnQ9YL4srsaG+ 4 | vAgmGKgFl9Vo5En7QoTiqOD74sQxKRI+YgBAIuvZxsdVDyK6x3dhXHW7MwIDAQAB 5 | AoGAIzLE6e9JPWsZIAokUDtQif718i8j7NxhL9luVtfvvD1oBlgJDxoFxnUP7T1W 6 | NQkeHR1l/Bee9Eho4gQ2kvBe0dv4XV9HQlF5Ls/CGOqj88OHsjwg5JABhyQLCmZx 7 | KPSF4CC1fsUM9AXOZETNM9hqvLU2oQ4HaOks9j/aKRXVw6ECQQDly2Iv7k3QU+t6 8 | Z0VKxK0COuy2uT6w2TwUqIsSGciXBGenrld0y9mnCzWfC6yfgt4IKeaNcA6o+Rc+ 9 | 4ofVh6UrAkEA1p6lQFwAKrJMjusqRUY2iml6ohKwV61/+1Bep4WtQ8Lu1zjmvU2Y 10 | iDXLn4t+hrMmf8TwAMKqklXzIkrbJurOGQJALCmJJjMDK11lbPG55GOl6D7QzVEO 11 | lkg5frLhf0oLPdKv5Q7haeXfX3YNCRLiSwmIiEMdNuNpPdoZOJGqj0PxXwJASJ8m 12 | C8EVzVzHyxSIGSjJC8zlUjJAuWpcBA4N+WGiX2t2phz69tNq6axKWpI5HhtAy7s3 13 | +wnj7eBnhRMPVPvfAQJBALNApqPxT3xgyklOujFp88kr85jDM/wtGU7zRa1Z96WS 14 | DtML3UCMk7cuFELnQfpp0YL+WQ4JeuBtagjjHyh4c0A= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /test-keys/rsa1024.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDApmvwhoDOXRlAl/lhWuFIuQ+hsjkgYBTJMh4xpkK1Uup8mhd/ybbkT1Okx1UDcvYrmqQkYTbbrwnt5LtwTl9oYA8Q4XzWZf9XOmLpnQ9YL4srsaG+vAgmGKgFl9Vo5En7QoTiqOD74sQxKRI+YgBAIuvZxsdVDyK6x3dhXHW7Mw== pc@dish 2 | -------------------------------------------------------------------------------- /test-keys/rsa1024new: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn 3 | NhAAAAAwEAAQAAAIEAwKZr8IaAzl0ZQJf5YVrhSLkPobI5IGAUyTIeMaZCtVLqfJoXf8m2 4 | 5E9TpMdVA3L2K5qkJGE2268J7eS7cE5faGAPEOF81mX/Vzpi6Z0PWC+LK7GhvrwIJhioBZ 5 | fVaORJ+0KE4qjg++LEMSkSPmIAQCLr2cbHVQ8iusd3YVx1uzMAAAIAJ13MASddzAEAAAAH 6 | c3NoLXJzYQAAAIEAwKZr8IaAzl0ZQJf5YVrhSLkPobI5IGAUyTIeMaZCtVLqfJoXf8m25E 7 | 9TpMdVA3L2K5qkJGE2268J7eS7cE5faGAPEOF81mX/Vzpi6Z0PWC+LK7GhvrwIJhioBZfV 8 | aORJ+0KE4qjg++LEMSkSPmIAQCLr2cbHVQ8iusd3YVx1uzMAAAADAQABAAAAgCMyxOnvST 9 | 1rGSAKJFA7UIn+9fIvI+zcYS/ZblbX77w9aAZYCQ8aBcZ1D+09VjUJHh0dZfwXnvRIaOIE 10 | NpLwXtHb+F1fR0JReS7Pwhjqo/PDh7I8IOSQAYckCwpmcSj0heAgtX7FDPQFzmREzTPYar 11 | y1NqEOB2jpLPY/2ikV1cOhAAAAQQCzQKaj8U98YMpJTroxafPJK/OYwzP8LRlO80WtWfel 12 | kg7TC91AjJO3LhRC50H6adGC/lkOCXrgbWoI4x8oeHNAAAAAQQDly2Iv7k3QU+t6Z0VKxK 13 | 0COuy2uT6w2TwUqIsSGciXBGenrld0y9mnCzWfC6yfgt4IKeaNcA6o+Rc+4ofVh6UrAAAA 14 | QQDWnqVAXAAqskyO6ypFRjaKaXqiErBXrX/7UF6nha1Dwu7XOOa9TZiINcufi36GsyZ/xP 15 | AAwqqSVfMiStsm6s4ZAAAAB3BjQGRpc2gBAgM= 16 | -----END OPENSSH PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /test-keys/rsa2048: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAwGUyT41s+ApeYmDCEq8PxQOdDAYmQIx074tI1LhlK8CxMKV/ 3 | AKj1YvQLn5zJIMstCC7mAVxwe23nXiyVsAA3ZvG2X92E91MpWyA+vs7vXWKgExM1 4 | kINHzQQNy8vrd4eHgcMfLFW5XVpNH5QsvpViMNZCv3PDdS9AFKMHl8/WGrBMAqFD 5 | 68mGI5Fxhsm/vhQ30uPOpo6bbfr0Eu6IZewrnNhaXhkQYORvaPE+vw7w16qiTadW 6 | ylH+9EQbV7uUfkJyQmDcl7Dg4LfyP2g/DaLIzDz7B7qkPE/XyGg8SFJytWtDVjii 7 | f9DH3LfIQWx46cblTFBbhpybvcaaO1l1aVTH2wIDAQABAoIBAFZ2I+H0YKAnqLr1 8 | WMIkwiV7tjwNF2uys2pjYduW8oBzHC4MccLOK1jI6+BTCWAfbqFa0iqSbEpL3cpI 9 | pwxtTHiK77atSnALg25cU1ZUkOWayO1JmKzVEpNj2v9XKP6SfacnFP9Llv4I8tfa 10 | MzGC2Jmx8FUxpQkorteXXHYxBLsA7y7qgOlODyrtLpcppV6AtSiFPP4NPKy1bfbb 11 | I69jQnR3I1uPl05sq8ltG86qWS74q0nsRDfzT9GnHIyz8mCVBymXqGthQYIlYSGS 12 | QheBCHSvuGnXUVQ3lu62ywsbryvUzxQAm+rGM9ludaWXT2ib7hlJpjIMAw03kZ0W 13 | whP3VSECgYEA8gfoiaI/kWoeHtKVySds/azvktcMDgqfK2RFc8zLWNGippHw1vNi 14 | KIH9lS/3CXTpUmt+pa6bk1r/YwVpl9pjvN+Dwuo0c1eVK93lLHYQks3ntY5OPono 15 | K9WH41aHsxNeszW0SYavuIMLEw5zeZkeEgScT5o31hLkzwWmxFe+ue8CgYEAy3/n 16 | ob4OjwSmzUtfxE/hpBydggyLQWb0xs1i3GPxEgFUlAfLTFMD+pE0fqgboBlXxStr 17 | f4bNrrGC5s7+3RDW40riOoHoGRaeR1id2OVbEYqNe32q4iMdwukgTlu+ErLyW2k4 18 | cuGJp8c+o5Ix8Hg7UONLVBr20yWgZ47FgjZBLNUCgYAjapDWgw12Q+cAb6RHeSNz 19 | w3iTLjCvZVKBUPzwwwkGiDaa1UQ+0AKPFuZ6jDhEA/2Yd+5iZDiTyX8RiFs5Q1XQ 20 | 74wiW48tSaWkc6XzaZtnQAYmBRrh/+AfefGWjLTyuWTkKHmnQLQ59cMLGNRA+Ozz 21 | igUtiM2e38jnmlYa6RQZtQKBgCjcMr84MSYoUfGwq7b4Lx9Lm5isOh3UtVthi/Mi 22 | ohnyaHAcrIZTuN3L4BV6XamkzQnK8Re3En98JwN/Fo9+vRcARzJmORsofnlm4rLV 23 | QpbgEg50qiYj1Or48kKpix2oDp+qHep7PK+SgxJG3X4iNg7ExLBLhbnZuT5x3ViK 24 | 2kVNAoGBANMvW1q2XfIIAnt9HMHFbf6tnIAHuqJinLcJ8pRIqkjbtg5StKAqbzvy 25 | 10gyrZaXFNj/y5FUhYDRhRZN6597bp6gsMJ4KN/U7Xc+87nrpuhUK+g7QoJXOvi+ 26 | 9lsjFBjYVjE786LnQ4V9Rixp5tpropcpRZaoAgaQ7EolQCH8kSJK 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test-keys/rsa2048.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAZTJPjWz4Cl5iYMISrw/FA50MBiZAjHTvi0jUuGUrwLEwpX8AqPVi9AufnMkgyy0ILuYBXHB7bedeLJWwADdm8bZf3YT3UylbID6+zu9dYqATEzWQg0fNBA3Ly+t3h4eBwx8sVbldWk0flCy+lWIw1kK/c8N1L0AUoweXz9YasEwCoUPryYYjkXGGyb++FDfS486mjptt+vQS7ohl7Cuc2FpeGRBg5G9o8T6/DvDXqqJNp1bKUf70RBtXu5R+QnJCYNyXsODgt/I/aD8NosjMPPsHuqQ8T9fIaDxIUnK1a0NWOKJ/0Mfct8hBbHjpxuVMUFuGnJu9xpo7WXVpVMfb pc@dish 2 | -------------------------------------------------------------------------------- /test-keys/rsa4096: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /test-keys/rsa4096.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDkEfdI1ruShdl7Hg00lKzXKc1TAYANzS/z4V17Gcoy4hAsCeeL2K8a+TePg4XdF/YKbbOKSeDSVcdiOiuslQtrrlZ1rYMUN+6HzQWu46C4dMB10oqZYCWSJr4LjeLuyW+oDm4C5NQwxl6fbk9u3JWzdc0MQFgc08k5wLUGSduB/auCTMpXiGtPMnLoGvDifBx8zxYVg4ABQgI2dQF1vJ+Ghcof8s3WcwR+kGkKK2BCev3agDqjICMt57Fo5l4Suk2NSFtsDLApCqHlAiI+rEfggx4FkuwViwNiis9rA/iWDNRYzH40C4D3PixESKSgEiHBPcZpnhqdcYcnye/6zpSUPrk+PW2r7PNz5S8TN6ajPdiS4RwJ3RIyJN+AziIodMrQvV/eFz7zDurDFGlQ5F2CNGfq4LyfC8KgwYboDYrkBl+4NOW9xf2uLuU9EgwL7p9XaYESLVJ/eG5KjOYhBUgdM8mFmJXHnjcB3UVNI5LljUFBdAXmFpJabcK+vB+VgNmsAAX/+7u4Ja58ouct3PdWHmTrtQ6aS8Zvm+HbDxOszsTiLquOtplZOnsey3cJ1l1v2qJxsSKLVx7J0gBDBjqbhMCbDTuo/84mz4oJNWCx+tAQJsi37VGaHQm2/W3xudVncqhfBgbzBMLNU8PIQi54bwEb83mBkhi+jO3ZkOViNQ== pc@dish 2 | -------------------------------------------------------------------------------- /tests/openssh_priv.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read}; 2 | use std::path::Path; 3 | use std::fs::File; 4 | 5 | extern crate ssh_keys; 6 | 7 | use ssh_keys::{PrivateKey, PublicKey}; 8 | 9 | 10 | fn read_file>(path: T) -> String { 11 | let mut f = File::open(path).unwrap(); 12 | let mut buf = String::with_capacity(128); 13 | f.read_to_string(&mut buf).unwrap(); 14 | return buf; 15 | } 16 | 17 | #[test] 18 | fn invalid_key() { 19 | assert!(ssh_keys::openssh::parse_private_key( 20 | "-----BEGIN OPENSSH PRIVATE KEY------END OPENSSH PRIVATE KEY-----" 21 | ).is_err()); 22 | } 23 | 24 | #[test] 25 | fn ed25519() { 26 | let key = ssh_keys::openssh::parse_private_key( 27 | &read_file("test-keys/ed25519") 28 | ).unwrap(); 29 | assert_eq!(key, vec![PrivateKey::Ed25519( 30 | [110, 136, 165, 108, 69, 111, 107, 140, 8, 233, 44, 143, 173, 86, 111, 31 | 3, 185, 15, 63, 68, 44, 99, 208, 109, 77, 114, 203, 229, 227, 142, 32 | 174, 65, 182, 135, 254, 94, 168, 107, 218, 136, 69, 10, 76, 17, 52, 33 | 204, 42, 119, 218, 188, 182, 42, 243, 239, 135, 37, 87, 29, 93, 143, 34 | 143, 19, 101, 42] 35 | )]); 36 | } 37 | 38 | #[test] 39 | fn ed25519_private_to_public() { 40 | let key = ssh_keys::openssh::parse_private_key( 41 | &read_file("test-keys/ed25519") 42 | ).unwrap(); 43 | let public = key[0].public_key(); 44 | assert_eq!(public, PublicKey::Ed25519( 45 | [182, 135, 254, 94, 168, 107, 218, 136, 69, 10, 76, 17, 52, 204, 42, 46 | 119, 218, 188, 182, 42, 243, 239, 135, 37, 87, 29, 93, 143, 143, 19, 47 | 101, 42])); 48 | } 49 | 50 | #[test] 51 | fn rsa1024new() { 52 | let key = ssh_keys::openssh::parse_private_key( 53 | &read_file("test-keys/rsa1024new") 54 | ).unwrap(); 55 | assert_eq!(key, vec![PrivateKey::Rsa { 56 | n: vec![0, 192, 166, 107, 240, 134, 128, 206, 93, 25, 64, 151, 249, 97, 57 | 90, 225, 72, 185, 15, 161, 178, 57, 32, 96, 20, 201, 50, 30, 49, 58 | 166, 66, 181, 82, 234, 124, 154, 23, 127, 201, 182, 228, 79, 83, 59 | 164, 199, 85, 3, 114, 246, 43, 154, 164, 36, 97, 54, 219, 175, 9, 60 | 237, 228, 187, 112, 78, 95, 104, 96, 15, 16, 225, 124, 214, 101, 61 | 255, 87, 58, 98, 233, 157, 15, 88, 47, 139, 43, 177, 161, 190, 62 | 188, 8, 38, 24, 168, 5, 151, 213, 104, 228, 73, 251, 66, 132, 226, 63 | 168, 224, 251, 226, 196, 49, 41, 18, 62, 98, 0, 64, 34, 235, 217, 64 | 198, 199, 85, 15, 34, 186, 199, 119, 97, 92, 117, 187, 51], 65 | e: vec![1, 0, 1], 66 | d: vec![35, 50, 196, 233, 239, 73, 61, 107, 25, 32, 10, 36, 80, 59, 80, 67 | 137, 254, 245, 242, 47, 35, 236, 220, 97, 47, 217, 110, 86, 215, 68 | 239, 188, 61, 104, 6, 88, 9, 15, 26, 5, 198, 117, 15, 237, 61, 86, 69 | 53, 9, 30, 29, 29, 101, 252, 23, 158, 244, 72, 104, 226, 4, 54, 70 | 146, 240, 94, 209, 219, 248, 93, 95, 71, 66, 81, 121, 46, 207, 71 | 194, 24, 234, 163, 243, 195, 135, 178, 60, 32, 228, 144, 1, 135, 72 | 36, 11, 10, 102, 113, 40, 244, 133, 224, 32, 181, 126, 197, 12, 73 | 244, 5, 206, 100, 68, 205, 51, 216, 106, 188, 181, 54, 161, 14, 7, 74 | 104, 233, 44, 246, 63, 218, 41, 21, 213, 195, 161], 75 | iqmp: vec![0, 179, 64, 166, 163, 241, 79, 124, 96, 202, 73, 78, 186, 76 | 49, 105, 243, 201, 43, 243, 152, 195, 51, 252, 45, 25, 78, 243, 77 | 69, 173, 89, 247, 165, 146, 14, 211, 11, 221, 64, 140, 147, 183, 78 | 46, 20, 66, 231, 65, 250, 105, 209, 130, 254, 89, 14, 9, 122, 224, 79 | 109, 106, 8, 227, 31, 40, 120, 115, 64], 80 | p: vec![0, 229, 203, 98, 47, 238, 77, 208, 83, 235, 122, 103, 69, 74, 81 | 196, 173, 2, 58, 236, 182, 185, 62, 176, 217, 60, 20, 168, 139, 82 | 18, 25, 200, 151, 4, 103, 167, 174, 87, 116, 203, 217, 167, 11, 83 | 53, 159, 11, 172, 159, 130, 222, 8, 41, 230, 141, 112, 14, 168, 84 | 249, 23, 62, 226, 135, 213, 135, 165, 43], 85 | q: vec![0, 214, 158, 165, 64, 92, 0, 42, 178, 76, 142, 235, 42, 69, 70, 86 | 54, 138, 105, 122, 162, 18, 176, 87, 173, 127, 251, 80, 94, 167, 87 | 133, 173, 67, 194, 238, 215, 56, 230, 189, 77, 152, 136, 53, 203, 88 | 159, 139, 126, 134, 179, 38, 127, 196, 240, 0, 194, 170, 146, 85, 89 | 243, 34, 74, 219, 38, 234, 206, 25], 90 | }]); 91 | } 92 | 93 | #[test] 94 | fn rsa1024() { 95 | let key = ssh_keys::openssh::parse_private_key( 96 | &read_file("test-keys/rsa1024") 97 | ).unwrap(); 98 | assert_eq!(key, vec![PrivateKey::Rsa { 99 | n: vec![0, 192, 166, 107, 240, 134, 128, 206, 93, 25, 64, 151, 249, 97, 100 | 90, 225, 72, 185, 15, 161, 178, 57, 32, 96, 20, 201, 50, 30, 49, 101 | 166, 66, 181, 82, 234, 124, 154, 23, 127, 201, 182, 228, 79, 83, 102 | 164, 199, 85, 3, 114, 246, 43, 154, 164, 36, 97, 54, 219, 175, 9, 103 | 237, 228, 187, 112, 78, 95, 104, 96, 15, 16, 225, 124, 214, 101, 104 | 255, 87, 58, 98, 233, 157, 15, 88, 47, 139, 43, 177, 161, 190, 105 | 188, 8, 38, 24, 168, 5, 151, 213, 104, 228, 73, 251, 66, 132, 226, 106 | 168, 224, 251, 226, 196, 49, 41, 18, 62, 98, 0, 64, 34, 235, 217, 107 | 198, 199, 85, 15, 34, 186, 199, 119, 97, 92, 117, 187, 51], 108 | e: vec![1, 0, 1], 109 | d: vec![35, 50, 196, 233, 239, 73, 61, 107, 25, 32, 10, 36, 80, 59, 80, 110 | 137, 254, 245, 242, 47, 35, 236, 220, 97, 47, 217, 110, 86, 215, 111 | 239, 188, 61, 104, 6, 88, 9, 15, 26, 5, 198, 117, 15, 237, 61, 86, 112 | 53, 9, 30, 29, 29, 101, 252, 23, 158, 244, 72, 104, 226, 4, 54, 113 | 146, 240, 94, 209, 219, 248, 93, 95, 71, 66, 81, 121, 46, 207, 114 | 194, 24, 234, 163, 243, 195, 135, 178, 60, 32, 228, 144, 1, 135, 115 | 36, 11, 10, 102, 113, 40, 244, 133, 224, 32, 181, 126, 197, 12, 116 | 244, 5, 206, 100, 68, 205, 51, 216, 106, 188, 181, 54, 161, 14, 7, 117 | 104, 233, 44, 246, 63, 218, 41, 21, 213, 195, 161], 118 | iqmp: vec![0, 179, 64, 166, 163, 241, 79, 124, 96, 202, 73, 78, 186, 119 | 49, 105, 243, 201, 43, 243, 152, 195, 51, 252, 45, 25, 78, 243, 120 | 69, 173, 89, 247, 165, 146, 14, 211, 11, 221, 64, 140, 147, 183, 121 | 46, 20, 66, 231, 65, 250, 105, 209, 130, 254, 89, 14, 9, 122, 224, 122 | 109, 106, 8, 227, 31, 40, 120, 115, 64], 123 | p: vec![0, 229, 203, 98, 47, 238, 77, 208, 83, 235, 122, 103, 69, 74, 124 | 196, 173, 2, 58, 236, 182, 185, 62, 176, 217, 60, 20, 168, 139, 125 | 18, 25, 200, 151, 4, 103, 167, 174, 87, 116, 203, 217, 167, 11, 126 | 53, 159, 11, 172, 159, 130, 222, 8, 41, 230, 141, 112, 14, 168, 127 | 249, 23, 62, 226, 135, 213, 135, 165, 43], 128 | q: vec![0, 214, 158, 165, 64, 92, 0, 42, 178, 76, 142, 235, 42, 69, 70, 129 | 54, 138, 105, 122, 162, 18, 176, 87, 173, 127, 251, 80, 94, 167, 130 | 133, 173, 67, 194, 238, 215, 56, 230, 189, 77, 152, 136, 53, 203, 131 | 159, 139, 126, 134, 179, 38, 127, 196, 240, 0, 194, 170, 146, 85, 132 | 243, 34, 74, 219, 38, 234, 206, 25], 133 | }]); 134 | } 135 | 136 | #[test] 137 | fn rsa1024_pub() { 138 | let key = ssh_keys::openssh::parse_private_key( 139 | &read_file("test-keys/rsa1024") 140 | ).unwrap(); 141 | let public = key[0].public_key(); 142 | assert_eq!(public, PublicKey::Rsa { 143 | exponent: vec![1, 0, 1], 144 | modulus: vec![0, 192, 166, 107, 240, 134, 128, 206, 93, 25, 64, 151, 145 | 249, 97, 90, 225, 72, 185, 15, 161, 178, 57, 32, 96, 20, 201, 50, 146 | 30, 49, 166, 66, 181, 82 , 234, 124, 154, 23, 127, 201, 182, 228, 147 | 79, 83, 164, 199, 85, 3, 114, 246, 43, 154, 164, 36, 97, 54, 219, 148 | 175, 9, 237, 228, 187, 112, 78, 95, 104, 96, 15, 16 , 225, 124, 149 | 214, 101, 255, 87, 58, 98, 233, 157, 15, 88, 47, 139, 43, 177, 150 | 161, 190, 188, 8, 38, 24, 168, 5, 151, 213, 104, 228, 73, 251, 66, 151 | 132, 226, 168, 224, 251, 226, 196, 49, 41, 18, 62, 98, 0, 64, 34, 152 | 235, 217, 198, 199, 85, 15, 34, 186, 199, 119, 97, 92, 117, 187, 153 | 51], 154 | }); 155 | } 156 | 157 | #[test] 158 | fn bounds_check() { 159 | ssh_keys::openssh::parse_private_key( 160 | &read_file("test-keys/buggy_key") 161 | ).unwrap_err(); 162 | } 163 | -------------------------------------------------------------------------------- /tests/openssh_pub.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read}; 2 | use std::path::Path; 3 | use std::fs::File; 4 | 5 | extern crate ssh_keys; 6 | 7 | use ssh_keys::PublicKey; 8 | 9 | 10 | fn read_file>(path: T) -> String { 11 | let mut f = File::open(path).unwrap(); 12 | let mut buf = String::with_capacity(128); 13 | f.read_to_string(&mut buf).unwrap(); 14 | return buf; 15 | } 16 | 17 | #[test] 18 | fn rsa1024() { 19 | let key = ssh_keys::openssh::parse_public_key( 20 | &read_file("test-keys/rsa1024.pub") 21 | ).unwrap(); 22 | assert_eq!(key, PublicKey::Rsa { 23 | exponent: vec![1, 0, 1], 24 | modulus: vec![0, 192, 166, 107, 240, 134, 128, 206, 93, 25, 64, 151, 25 | 249, 97, 90, 225, 72, 185, 15, 161, 178, 57, 32, 96, 20, 201, 50, 26 | 30, 49, 166, 66, 181, 82 , 234, 124, 154, 23, 127, 201, 182, 228, 27 | 79, 83, 164, 199, 85, 3, 114, 246, 43, 154, 164, 36, 97, 54, 219, 28 | 175, 9, 237, 228, 187, 112, 78, 95, 104, 96, 15, 16 , 225, 124, 29 | 214, 101, 255, 87, 58, 98, 233, 157, 15, 88, 47, 139, 43, 177, 30 | 161, 190, 188, 8, 38, 24, 168, 5, 151, 213, 104, 228, 73, 251, 66, 31 | 132, 226, 168, 224, 251, 226, 196, 49, 41, 18, 62, 98, 0, 64, 34, 32 | 235, 217, 198, 199, 85, 15, 34, 186, 199, 119, 97, 92, 117, 187, 33 | 51], 34 | }); 35 | } 36 | 37 | #[test] 38 | fn rsa1024_display() { 39 | let data = read_file("test-keys/rsa1024.pub"); 40 | let key = ssh_keys::openssh::parse_public_key(&data).unwrap(); 41 | let value = key.to_string() + " "; 42 | assert!(data.starts_with(&value)); 43 | } 44 | 45 | 46 | #[test] 47 | fn rsa2048() { 48 | let key = ssh_keys::openssh::parse_public_key( 49 | &read_file("test-keys/rsa2048.pub") 50 | ).unwrap(); 51 | assert_eq!(key, PublicKey::Rsa { 52 | exponent: vec![1, 0, 1], 53 | modulus: vec![0, 192, 101, 50, 79, 141, 108, 248, 10, 94, 98, 96, 194, 54 | 18, 175, 15, 197, 3, 157, 12, 6, 38, 64, 140, 116, 239, 139, 72, 55 | 212, 184, 101, 43, 192, 177, 48 , 165, 127, 0, 168, 245, 98, 244, 56 | 11, 159, 156, 201, 32, 203, 45, 8, 46, 230, 1, 92, 112, 123, 109, 57 | 231, 94, 44, 149, 176, 0, 55, 102, 241, 182, 95, 221, 132, 247, 58 | 83, 41, 91, 32, 62, 190, 206, 239, 93, 98, 160, 19, 19, 53, 144, 59 | 131, 71, 205, 4, 13, 203, 203, 235, 119, 135, 135, 129, 195, 31, 60 | 44, 85, 185, 93, 90, 77, 31, 148, 44, 190, 149, 98, 48, 214, 66, 61 | 191, 115, 195, 117, 47, 64, 20, 163, 7, 151, 207, 214, 26, 176, 62 | 76, 2, 161, 67, 235, 201, 134, 35, 145, 113, 134, 201, 191, 190, 63 | 20, 55, 210, 227, 206, 166, 142, 155, 109, 250, 244, 18, 238, 136, 64 | 101, 236, 43, 156, 216, 90, 94, 25, 16, 96, 228, 111, 104, 241, 65 | 62, 191, 14, 240, 215, 170, 162, 77, 167, 86, 202, 81, 254, 244, 66 | 68, 27, 87, 187, 148, 126, 66, 114, 66, 96, 220, 151, 176, 224, 67 | 224, 183, 242, 63, 104, 63, 13, 162, 200, 204, 60, 251, 7, 186, 68 | 164, 60, 79, 215, 200, 104, 60, 72, 82, 114, 181, 107, 67, 86, 56, 69 | 162, 127, 208, 199, 220, 183, 200, 65, 108, 120, 233, 198, 229, 70 | 76, 80, 91, 134, 156, 155, 189, 198, 154, 59, 89, 117, 105, 84, 71 | 199, 219], 72 | }); 73 | } 74 | 75 | #[test] 76 | fn rsa4096() { 77 | let key = ssh_keys::openssh::parse_public_key( 78 | &read_file("test-keys/rsa4096.pub") 79 | ).unwrap(); 80 | assert_eq!(key, PublicKey::Rsa { 81 | exponent: vec![1, 0, 1], 82 | modulus: vec![0, 228, 17, 247, 72, 214, 187, 146, 133, 217, 123, 30, 83 | 13, 52, 148, 172, 215, 41, 205, 83, 1, 128, 13, 205, 47, 243, 225, 84 | 93, 123, 25, 202, 50, 226, 16, 44, 9, 231, 139, 216, 175, 26, 249, 85 | 55, 143, 131, 133, 221, 23, 246, 10, 109, 179, 138, 73, 224, 210, 86 | 85, 199, 98, 58, 43, 172, 149, 11, 107, 174, 86, 117, 173, 131, 87 | 20, 55, 238, 135, 205, 5, 174, 227, 160, 184, 116, 192, 117, 210, 88 | 138, 153, 96, 37, 146, 38, 190, 11, 141, 226, 238, 201, 111, 168, 89 | 14, 110, 2, 228, 212, 48, 198, 94, 159, 110, 79, 110, 220, 149, 90 | 179, 117, 205, 12, 64, 88, 28, 211, 201, 57, 192, 181, 6, 73, 219, 91 | 129, 253, 171, 130, 76, 202, 87, 136, 107, 79, 50, 114, 232, 26, 92 | 240, 226, 124, 28, 124, 207, 22, 21, 131, 128, 1, 66, 2, 54, 117, 93 | 1, 117, 188, 159, 134, 133, 202, 31, 242, 205, 214, 115, 4, 126, 94 | 144, 105, 10, 43, 96, 66, 122, 253, 218, 128, 58, 163, 32, 35, 45, 95 | 231, 177, 104, 230, 94, 18, 186, 77, 141, 72, 91, 108, 12, 176, 96 | 41, 10, 161, 229, 2, 34, 62, 172, 71, 224, 131, 30, 5, 146, 236, 97 | 21, 139, 3, 98, 138, 207, 107, 3, 248, 150, 12, 212, 88, 204, 126, 98 | 52, 11, 128, 247, 62, 44, 68, 72, 164, 160, 18, 33, 193, 61, 198, 99 | 105, 158, 26, 157, 113, 135, 39, 201, 239, 250, 206, 148, 148, 62, 100 | 185, 62, 61, 109, 171, 236, 243, 115, 229, 47, 19, 55, 166, 163, 101 | 61, 216, 146, 225, 28, 9, 221, 18, 50, 36, 223, 128, 206, 34, 40, 102 | 116, 202, 208, 189, 95, 222, 23, 62, 243, 14, 234, 195, 20, 105, 103 | 80, 228, 93, 130, 52, 103, 234, 224, 188, 159, 11, 194, 160, 193, 104 | 134, 232, 13, 138, 228, 6, 95, 184, 52, 229, 189, 197, 253, 174, 105 | 46, 229, 61, 18, 12, 11, 238, 159, 87, 105, 129, 18, 45, 82, 127, 106 | 120, 110, 74, 140, 230, 33, 5, 72, 29, 51, 201, 133, 152, 149, 107 | 199, 158, 55, 1, 221, 69, 77, 35, 146, 229, 141, 65, 65, 116, 5, 108 | 230, 22, 146, 90, 109, 194, 190, 188, 31, 149, 128, 217, 172, 0, 109 | 5, 255, 251, 187, 184, 37, 174, 124, 162, 231, 45, 220, 247, 86, 110 | 30, 100, 235, 181, 14, 154, 75, 198, 111, 155, 225, 219, 15, 19, 111 | 172, 206, 196, 226, 46, 171, 142, 182, 153, 89, 58, 123, 30, 203, 112 | 119, 9, 214, 93, 111, 218, 162, 113, 177, 34, 139, 87, 30, 201, 113 | 210, 0, 67, 6, 58, 155, 132, 192, 155, 13, 59, 168, 255, 206, 38, 114 | 207, 138, 9, 53, 96, 177, 250, 208, 16, 38, 200, 183, 237, 81, 115 | 154, 29, 9, 182, 253, 109, 241, 185, 213, 103, 114, 168, 95, 6, 6, 116 | 243, 4, 194, 205, 83, 195, 200, 66, 46, 120, 111, 1, 27, 243, 121, 117 | 129, 146, 24, 190, 140, 237, 217, 144, 229, 98, 53], 118 | }); 119 | } 120 | 121 | #[test] 122 | fn ed25519() { 123 | let key = ssh_keys::openssh::parse_public_key( 124 | &read_file("test-keys/ed25519.pub") 125 | ).unwrap(); 126 | assert_eq!(key, PublicKey::Ed25519( 127 | [182, 135, 254, 94, 168, 107, 218, 136, 69, 10, 76, 17, 52, 204, 42, 128 | 119, 218, 188, 182, 42, 243, 239, 135, 37, 87, 29, 93, 143, 143, 19, 129 | 101, 42])); 130 | } 131 | 132 | #[test] 133 | fn ed25519_display() { 134 | let key = ssh_keys::openssh::parse_public_key( 135 | &read_file("test-keys/ed25519.pub") 136 | ).unwrap(); 137 | assert_eq!(key.to_string(), 138 | "ssh-ed25519 \ 139 | AAAAC3NzaC1lZDI1NTE5AAAAILaH/l6oa9qIRQp\ 140 | METTMKnfavLYq8++HJVcdXY+PE2Uq"); 141 | } 142 | -------------------------------------------------------------------------------- /vagga.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | 3 | make: !Command 4 | description: Build the library 5 | container: ubuntu 6 | run: [cargo, build] 7 | 8 | cargo: !Command 9 | description: Run arbitrary cargo command 10 | container: ubuntu 11 | run: [cargo] 12 | 13 | test: !Command 14 | description: Run tests 15 | container: ubuntu 16 | run: [cargo, test] 17 | 18 | bench: !Command 19 | description: Run benchmarks 20 | container: nightly 21 | run: [cargo, bench] 22 | 23 | _bulk: !Command 24 | description: Run `bulk` command (for version bookkeeping) 25 | container: ubuntu 26 | run: [bulk] 27 | 28 | containers: 29 | 30 | ubuntu: 31 | setup: 32 | - !Ubuntu xenial 33 | - !Install [ca-certificates, git, build-essential, vim] 34 | 35 | - !TarInstall 36 | url: "https://static.rust-lang.org/dist/rust-1.31.0-x86_64-unknown-linux-gnu.tar.gz" 37 | script: "./install.sh --prefix=/usr \ 38 | --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" 39 | - &bulk !Tar 40 | url: "https://github.com/tailhook/bulk/releases/download/v0.4.11/bulk-v0.4.11.tar.gz" 41 | sha256: b718bb8448e726690c94d98d004bf7575f7a429106ec26ad3faf11e0fd9a7978 42 | path: / 43 | 44 | environ: 45 | HOME: /work/target 46 | RUST_BACKTRACE: 1 47 | 48 | nightly: 49 | setup: 50 | - !Ubuntu xenial 51 | - !Install [ca-certificates, git, build-essential] 52 | 53 | - !TarInstall 54 | url: "https://static.rust-lang.org/dist/rust-nightly-x86_64-unknown-linux-gnu.tar.gz" 55 | script: "./install.sh --prefix=/usr \ 56 | --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" 57 | 58 | environ: 59 | HOME: /work/target 60 | RUST_BACKTRACE: 1 61 | --------------------------------------------------------------------------------