chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

1
vendor/ssh-key/.cargo-checksum.json vendored Normal file

File diff suppressed because one or more lines are too long

6
vendor/ssh-key/.cargo_vcs_info.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "6ae2c0850c0febb485fa53678cd622aa0e5f3db5"
},
"path_in_vcs": "ssh-key"
}

246
vendor/ssh-key/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,246 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.6.7 (2024-10-15)
### Fixed
- Parsing `AuthorizedKeys` with whitespace in comments ([#289])
- `mpint` decoding in ECDSA signatures ([#290], [#291])
[#289]: https://github.com/RustCrypto/SSH/pull/289
[#290]: https://github.com/RustCrypto/SSH/pull/290
[#291]: https://github.com/RustCrypto/SSH/pull/291
## 0.6.6 (2024-04-11)
### Added
- impl `decode_as` for `KeypairData` ([#211])
### Changed
- clarify SSH vs OpenSSH formats ([#206])
### Fixed
- fix `certificate::OptionsMap` encoding ([#207])
- fixup `EcdsaPrivateKey` Debug impl ([#210])
[#206]: https://github.com/RustCrypto/SSH/pull/206
[#207]: https://github.com/RustCrypto/SSH/pull/207
[#210]: https://github.com/RustCrypto/SSH/pull/210
[#211]: https://github.com/RustCrypto/SSH/pull/211
## 0.6.5 (2024-03-12)
### Added
- `Sk*` constructors ([#201], [#204])
### Changed
- Simplify DSA signature encoding ([#193])
### Fixed
- Correct erroneous signature constants ([#202])
[#193]: https://github.com/RustCrypto/SSH/pull/193
[#201]: https://github.com/RustCrypto/SSH/pull/201
[#202]: https://github.com/RustCrypto/SSH/pull/202
[#204]: https://github.com/RustCrypto/SSH/pull/204
## 0.6.4 (2024-01-11)
### Added
- `Algorithm::Other` signature support ([#189])
### Fixed
- Add newline to `PublicKey::write_openssh_file` output ([#188])
- `DsaKeypair::try_sign` format error ([#191])
[#188]: https://github.com/RustCrypto/SSH/pull/188
[#189]: https://github.com/RustCrypto/SSH/pull/189
[#191]: https://github.com/RustCrypto/SSH/pull/191
## 0.6.3 (2023-11-20)
### Added
- `SkEcdsaSha2NistP256` signature validation ([#169])
- `p521` feature ([#180])
### Changed
- Maximum certificate timestamp time is now `i64::MAX` ([#175])
### Fixed
- Handle leading zeroes in `Mpint::from_positive_bytes` ([#171])
[#169]: https://github.com/RustCrypto/SSH/pull/169
[#171]: https://github.com/RustCrypto/SSH/pull/171
[#175]: https://github.com/RustCrypto/SSH/pull/175
[#180]: https://github.com/RustCrypto/SSH/pull/180
## 0.6.2 (2023-10-15)
### Added
- `SshSig` usage examples ([#166], [#167])
[#166]: https://github.com/RustCrypto/SSH/pull/166
[#167]: https://github.com/RustCrypto/SSH/pull/167
## 0.6.1 (2023-08-15)
### Fixed
- `minimal-versions` correctness for `sec1` dependency ([#154])
[#154]: https://github.com/RustCrypto/SSH/pull/154
## 0.6.0 (2023-08-13)
### Added
- Partial support for U2F signature verification ([#44])
- Support for `aes256-gcm@openssh.com` encryption ([#75])
- "randomart" public key fingerprint visualizations ([#77])
- `PrivateKey::encrypt_with_cipher` ([#79])
- Propagate `ssh_key::Error` through `signature::Error` ([#82])
- `crypto` feature ([#83])
- Support for AES-CBC, ChaCha20Poly1305, and TDES encryption ([#118])
- Basic support for nonstandard SSH key algorithms ([#136])
- Impl `Hash` for `PublicKey` and its parts ([#145], [#149])
### Changed
- Bump `signature` crate dependency to v2 ([#58])
- Use `ssh_key::Error` as error type for `TryFrom<&[u8]>` impl on `Signature` ([#59])
- Bump elliptic curve and password hash deps; MSRV 1.65 ([#66])
- `bcrypt-pbkdf` v0.10
- `dsa` v0.6
- `p256` v0.13
- `p384` v0.13
- `sec1` v0.7
- Use `&mut impl CryptoRngCore` for RNGs ([#67])
- Make `certificate::Builder::new` fallible ([#71])
- Rename `MPInt` => `Mpint` ([#76])
- Split `AlgorithmUnknown` and `AlgorithmUnsupported` ([#81])
- Bump `rsa` dependency to v0.9 ([#107])
- Extract symmetric encryption into `ssh-cipher` crate ([#125])
- Bump `ed25519-dalek` dependency to v2 ([#146])
- Bump `ssh-encoding` dependency to v0.2 ([#147])
### Fixed
- DSA signature encoding ([#115])
- `certificate::Builder::new_with_validity_times` ([#143])
[#44]: https://github.com/RustCrypto/SSH/pull/44
[#58]: https://github.com/RustCrypto/SSH/pull/58
[#59]: https://github.com/RustCrypto/SSH/pull/59
[#66]: https://github.com/RustCrypto/SSH/pull/66
[#67]: https://github.com/RustCrypto/SSH/pull/67
[#71]: https://github.com/RustCrypto/SSH/pull/71
[#75]: https://github.com/RustCrypto/SSH/pull/75
[#76]: https://github.com/RustCrypto/SSH/pull/76
[#77]: https://github.com/RustCrypto/SSH/pull/77
[#79]: https://github.com/RustCrypto/SSH/pull/79
[#81]: https://github.com/RustCrypto/SSH/pull/81
[#82]: https://github.com/RustCrypto/SSH/pull/82
[#83]: https://github.com/RustCrypto/SSH/pull/83
[#107]: https://github.com/RustCrypto/SSH/pull/107
[#115]: https://github.com/RustCrypto/SSH/pull/115
[#118]: https://github.com/RustCrypto/SSH/pull/118
[#125]: https://github.com/RustCrypto/SSH/pull/125
[#136]: https://github.com/RustCrypto/SSH/pull/136
[#143]: https://github.com/RustCrypto/SSH/pull/143
[#145]: https://github.com/RustCrypto/SSH/pull/145
[#146]: https://github.com/RustCrypto/SSH/pull/146
[#147]: https://github.com/RustCrypto/SSH/pull/147
[#149]: https://github.com/RustCrypto/SSH/pull/149
## 0.5.1 (2022-10-25)
### Changed
- README.md improvements ([#41])
[#41]: https://github.com/RustCrypto/SSH/pull/41
## 0.5.0 (2022-10-25)
### Added
- `p384` feature ([#21])
- `dsa` feature ([#22], [#23])
- "[sshsig]" support ([#28])
### Changed
- Bump `p256` to v0.11 ([#10])
- Bump MSRV to 1.60 ([#16])
- Bump `rsa` to v0.7 ([#20])
- Use `ssh-encoding` encoding crate ([#29], [#37])
### Removed
- `fingerprint` feature removed, now always-on ([#27])
[#10]: https://github.com/RustCrypto/SSH/pull/10
[#16]: https://github.com/RustCrypto/SSH/pull/16
[#20]: https://github.com/RustCrypto/SSH/pull/20
[#21]: https://github.com/RustCrypto/SSH/pull/21
[#22]: https://github.com/RustCrypto/SSH/pull/22
[#23]: https://github.com/RustCrypto/SSH/pull/23
[#27]: https://github.com/RustCrypto/SSH/pull/27
[#28]: https://github.com/RustCrypto/SSH/pull/28
[#29]: https://github.com/RustCrypto/SSH/pull/29
[#37]: https://github.com/RustCrypto/SSH/pull/37
[sshsig]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.sshsig?annotate=HEAD
## 0.4.3 (2022-09-25)
### Changed
- Move source code repository to <https://github.com/RustCrypto/SSH> ([#1])
[#1]: https://github.com/RustCrypto/SSH/pull/1
## 0.4.2 (2022-05-02)
### Added
- Support for parsing keys out of the ssh known_hosts file format
- Export `RsaPrivateKey`
- `From` conversions between algorithmic-specific key types and `PublicKey`/`PrivateKey`
## 0.4.1 (2022-04-26)
### Added
- Internal `UnixTime` helper type
### Changed
- Bump `pem-rfc7468` dependency to v0.6.0
- Further restrict maximum allowed timestamps
## 0.4.0 (2022-04-12)
### Added
- Private key decryption support
- Private key encryption support
- Ed25519 keygen/sign/verify support using `ed25519-dalek`
- Private key encryption
- Certificate decoder
- Certificate encoder
- Certificate validation support
- FIDO/U2F (`sk-*`) certificate and key support
- `certificate::Builder` (i.e. SSH CA support)
- ECDSA/NIST P-256 keygen/sign/verify support using `p256` crate
- RSA keygen/sign/verify support using `rsa` crate
- SHA-512 fingerprint support
- `serde` support
### Changed
- Consolidate `KdfAlg` and `KdfOpts` into `Kdf`
- Rename `CipherAlg` => `Cipher`
### Removed
- `PrivateKey::kdf_alg`
## 0.3.0 (2022-03-16)
### Added
- `FromStr` impls for key types
- `PublicKey` encoder
- `AuthorizedKeys` parser
- `PrivateKey::public_key` and `From` conversions
- `PrivateKey` encoder
- Validate private key padding bytes
- File I/O methods for `PrivateKey` and `PublicKey`
- SHA-256 fingerprint support
### Changed
- Use `pem-rfc7468` for private key PEM parser
- Make `PublicKey`/`PrivateKey` fields private
## 0.2.0 (2021-12-29)
### Added
- OpenSSH private key decoder
- `MPInt::as_positive_bytes`
### Changed
- `MPInt` validates the correct number of leading zeroes are used
## 0.1.0 (2021-12-02)
- Initial release

269
vendor/ssh-key/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,269 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.65"
name = "ssh-key"
version = "0.6.7"
authors = ["RustCrypto Developers"]
build = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = '''
Pure Rust implementation of SSH key file format decoders/encoders as described
in RFC4251/RFC4253 and OpenSSH key formats, as well as "sshsig" signatures and
certificates (including certificate validation and certificate authority support),
with further support for the `authorized_keys` and `known_hosts` file formats.
'''
readme = "README.md"
keywords = [
"crypto",
"certificate",
"openssh",
"ssh",
"sshsig",
]
categories = [
"authentication",
"cryptography",
"encoding",
"no-std",
"parser-implementations",
]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/RustCrypto/SSH/tree/master/ssh-key"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[lib]
name = "ssh_key"
path = "src/lib.rs"
[[test]]
name = "algorithm_name"
path = "tests/algorithm_name.rs"
[[test]]
name = "authorized_keys"
path = "tests/authorized_keys.rs"
[[test]]
name = "certificate"
path = "tests/certificate.rs"
[[test]]
name = "certificate_builder"
path = "tests/certificate_builder.rs"
[[test]]
name = "encrypted_private_key"
path = "tests/encrypted_private_key.rs"
[[test]]
name = "known_hosts"
path = "tests/known_hosts.rs"
[[test]]
name = "private_key"
path = "tests/private_key.rs"
[[test]]
name = "public_key"
path = "tests/public_key.rs"
[[test]]
name = "sshsig"
path = "tests/sshsig.rs"
[dependencies.bcrypt-pbkdf]
version = "0.10"
features = ["alloc"]
optional = true
default-features = false
[dependencies.bigint]
version = "0.8"
optional = true
default-features = false
package = "num-bigint-dig"
[dependencies.cipher]
version = "0.2"
package = "ssh-cipher"
[dependencies.dsa]
version = "0.6"
optional = true
default-features = false
[dependencies.ed25519-dalek]
version = "2"
optional = true
default-features = false
[dependencies.encoding]
version = "0.2"
features = [
"base64",
"pem",
"sha2",
]
package = "ssh-encoding"
[dependencies.p256]
version = "0.13"
features = ["ecdsa"]
optional = true
default-features = false
[dependencies.p384]
version = "0.13"
features = ["ecdsa"]
optional = true
default-features = false
[dependencies.p521]
version = "0.13.3"
features = [
"ecdsa",
"getrandom",
]
optional = true
default-features = false
[dependencies.rand_core]
version = "0.6.4"
optional = true
default-features = false
[dependencies.rsa]
version = "0.9"
features = ["sha2"]
optional = true
default-features = false
[dependencies.sec1]
version = "0.7.3"
features = ["point"]
optional = true
default-features = false
[dependencies.serde]
version = "1"
optional = true
[dependencies.sha1]
version = "0.10"
optional = true
default-features = false
[dependencies.sha2]
version = "0.10.8"
default-features = false
[dependencies.signature]
version = "2"
default-features = false
[dependencies.subtle]
version = "2"
default-features = false
[dependencies.zeroize]
version = "1"
default-features = false
[dev-dependencies.hex-literal]
version = "0.4.1"
[dev-dependencies.rand_chacha]
version = "0.3"
[features]
alloc = [
"encoding/alloc",
"signature/alloc",
"zeroize/alloc",
]
crypto = [
"ed25519",
"p256",
"p384",
"p521",
"rsa",
]
default = [
"ecdsa",
"rand_core",
"std",
]
dsa = [
"dep:bigint",
"dep:dsa",
"dep:sha1",
"alloc",
"signature/rand_core",
]
ecdsa = ["dep:sec1"]
ed25519 = [
"dep:ed25519-dalek",
"rand_core",
]
encryption = [
"dep:bcrypt-pbkdf",
"alloc",
"cipher/aes-cbc",
"cipher/aes-ctr",
"cipher/aes-gcm",
"cipher/chacha20poly1305",
"rand_core",
]
getrandom = ["rand_core/getrandom"]
p256 = [
"dep:p256",
"ecdsa",
]
p384 = [
"dep:p384",
"ecdsa",
]
p521 = [
"dep:p521",
"ecdsa",
]
rsa = [
"dep:bigint",
"dep:rsa",
"alloc",
"rand_core",
]
std = [
"alloc",
"encoding/std",
"p256?/std",
"p384?/std",
"p521?/std",
"rsa?/std",
"sec1?/std",
"signature/std",
]
tdes = [
"cipher/tdes",
"encryption",
]

201
vendor/ssh-key/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

25
vendor/ssh-key/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2021-2024 The RustCrypto Project Developers
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:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
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.

131
vendor/ssh-key/README.md vendored Normal file
View File

@@ -0,0 +1,131 @@
# [RustCrypto]: SSH Keys and Certificates
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
[![Build Status][build-image]][build-link]
![Apache2/MIT licensed][license-image]
![Rust Version][rustc-image]
[![Project Chat][chat-image]][chat-link]
[Documentation][docs-link]
## About
Pure Rust implementation of SSH key file format decoders/encoders as described
in [RFC4251] and [RFC4253] as well as OpenSSH's [PROTOCOL.key] format
specification.
Additionally provides support for SSH signatures as described in
[PROTOCOL.sshsig], OpenSSH certificates as specified in [PROTOCOL.certkeys]
including certificate validation and certificate authority (CA) support,
FIDO/U2F keys as specified in [PROTOCOL.u2f] (and certificates thereof), and
also the `authorized_keys` and `known_hosts` file formats.
Supports a minimal profile which works on heapless `no_std` targets. See
"Supported algorithms" table below for which key formats work on heapless
targets and which algorithms require `alloc`.
When the `ed25519`, `p256`, and/or `rsa` features of this crate are enabled,
provides key generation and certificate signing/verification support for that
respective SSH key algorithm.
## Features
- [x] Constant-time Base64 decoder/encoder using `base64ct`/`pem-rfc7468` crates
- [x] OpenSSH-compatible decoder/encoders for the following formats:
- [x] OpenSSH public keys
- [x] OpenSSH private keys (i.e. `BEGIN OPENSSH PRIVATE KEY`)
- [x] OpenSSH certificates
- [x] OpenSSH signatures (a.k.a. "sshsig")
- [x] OpenSSH certificate support
- [x] OpenSSH certificate validation
- [x] OpenSSH certificate authority (CA) support i.e. cert builder/signer
- [x] Private key encryption/decryption (`bcrypt-pbkdf` + `aes256-ctr` only)
- [x] Private key generation support: DSA, Ed25519, ECDSA (P-256/P-384/P-521),
and RSA
- [x] FIDO/U2F key support (`sk-*`) as specified in [PROTOCOL.u2f]
- [x] Fingerprint support
- [x] "randomart" fingerprint visualizations
- [x] `no_std` support including support for "heapless" (no-`alloc`) targets
- [x] Parsing `authorized_keys` files
- [x] Parsing `known_hosts` files
- [x] `serde` support
- [x] `zeroize` support for private keys
#### TODO
- [ ] FIDO/U2F signature support
- [ ] Legacy (pre-OpenSSH) SSH key format support
- [ ] PKCS#1 SSH private keys (i.e. RSA-only)
- [ ] PKCS#8 SSH private keys
- [ ] [RFC4716] SSH public keys
- [ ] SEC1 SSH public keys
### Supported Signature Algorithms
| Name | Decode | Encode | Cert | Keygen | Sign | Verify | Feature | `no_std` |
|--------------------------------------|--------|--------|------|--------|------|--------|-----------|----------|
| `ecdsasha2nistp256` | ✅ | ✅ | ✅ | ✅️ | ✅️ | ✅️ | `p256` | heapless |
| `ecdsasha2nistp384` | ✅ | ✅ | ✅ | ✅️ | ✅️ | ✅️ | `p384` | heapless |
| `ecdsasha2nistp521` | ✅ | ✅ | ✅ | ✅️️ | ✅️ | ✅️️ | `p521` | heapless |
| `sshdsa` | ✅ | ✅ | ✅ | ✅ | ✅️ | ✅️ | `dsa` | `alloc` |
| `sshed25519` | ✅ | ✅ | ✅ | ✅️ | ✅️ | ✅ | `ed25519` | heapless |
| `sshrsa` | ✅ | ✅ | ✅ | ✅️ | ✅️ | ✅ | `rsa` | `alloc` |
| `skecdsasha2nistp256@openssh.com` | ✅ | ✅ | ✅ | ⛔ | ⛔️ | ✅️ | ⛔ | `alloc` |
| `sksshed25519@openssh.com` | ✅ | ✅ | ✅ | ⛔ | ⛔️ | ✅️️ | `ed25519` | `alloc` |
By default *no SSH signature algorithms are enabled* and you will get an
`Error::AlgorithmUnsupported` error if you try to use them.
Enable the `crypto` feature or the "Feature" for specific algorithms in the
chart above (e.g. `p256`, `rsa`) in order to use cryptographic functionality.
The "Feature" column lists the name of `ssh-key` crate features which can
be enabled to provide full support for the "Keygen", "Sign", and "Verify"
functionality for a particular SSH key algorithm.
## Minimum Supported Rust Version
This crate requires **Rust 1.65** at a minimum.
We may change the MSRV in the future, but it will be accompanied by a minor
version bump.
## License
Licensed under either of:
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
* [MIT license](http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
[//]: # (badges)
[crate-image]: https://buildstats.info/crate/ssh-key
[crate-link]: https://crates.io/crates/ssh-key
[docs-image]: https://docs.rs/ssh-key/badge.svg
[docs-link]: https://docs.rs/ssh-key/
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.65+-blue.svg
[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/346919-SSH
[build-image]: https://github.com/RustCrypto/SSH/actions/workflows/ssh-key.yml/badge.svg
[build-link]: https://github.com/RustCrypto/SSH/actions/workflows/ssh-key.yml
[//]: # (links)
[RustCrypto]: https://github.com/rustcrypto
[RFC4251]: https://datatracker.ietf.org/doc/html/rfc4251
[RFC4253]: https://datatracker.ietf.org/doc/html/rfc4253
[RFC4716]: https://datatracker.ietf.org/doc/html/rfc4716
[PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
[PROTOCOL.key]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
[PROTOCOL.sshsig]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.sshsig?annotate=HEAD
[PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD

515
vendor/ssh-key/src/algorithm.rs vendored Normal file
View File

@@ -0,0 +1,515 @@
//! Algorithm support.
#[cfg(feature = "alloc")]
mod name;
use crate::{Error, Result};
use core::{fmt, str};
use encoding::{Label, LabelError};
#[cfg(feature = "alloc")]
use {
alloc::{borrow::ToOwned, string::String, vec::Vec},
sha2::{Digest, Sha256, Sha512},
};
#[cfg(feature = "alloc")]
pub use name::AlgorithmName;
/// bcrypt-pbkdf
const BCRYPT: &str = "bcrypt";
/// OpenSSH certificate for DSA public key
const CERT_DSA: &str = "ssh-dss-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-256) public key
const CERT_ECDSA_SHA2_P256: &str = "ecdsa-sha2-nistp256-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-384) public key
const CERT_ECDSA_SHA2_P384: &str = "ecdsa-sha2-nistp384-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-521) public key
const CERT_ECDSA_SHA2_P521: &str = "ecdsa-sha2-nistp521-cert-v01@openssh.com";
/// OpenSSH certificate for Ed25519 public key
const CERT_ED25519: &str = "ssh-ed25519-cert-v01@openssh.com";
/// OpenSSH certificate with RSA public key
const CERT_RSA: &str = "ssh-rsa-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-256) U2F/FIDO security key
const CERT_SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com";
/// OpenSSH certificate for Ed25519 U2F/FIDO security key
const CERT_SK_SSH_ED25519: &str = "sk-ssh-ed25519-cert-v01@openssh.com";
/// ECDSA with SHA-256 + NIST P-256
const ECDSA_SHA2_P256: &str = "ecdsa-sha2-nistp256";
/// ECDSA with SHA-256 + NIST P-256
const ECDSA_SHA2_P384: &str = "ecdsa-sha2-nistp384";
/// ECDSA with SHA-256 + NIST P-256
const ECDSA_SHA2_P521: &str = "ecdsa-sha2-nistp521";
/// None
const NONE: &str = "none";
/// RSA with SHA-256 as described in RFC8332 § 3
const RSA_SHA2_256: &str = "rsa-sha2-256";
/// RSA with SHA-512 as described in RFC8332 § 3
const RSA_SHA2_512: &str = "rsa-sha2-512";
/// SHA-256 hash function
const SHA256: &str = "sha256";
/// SHA-512 hash function
const SHA512: &str = "sha512";
/// Digital Signature Algorithm
const SSH_DSA: &str = "ssh-dss";
/// Ed25519
const SSH_ED25519: &str = "ssh-ed25519";
/// RSA
const SSH_RSA: &str = "ssh-rsa";
/// U2F/FIDO security key with ECDSA/NIST P-256
const SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256@openssh.com";
/// U2F/FIDO security key with Ed25519
const SK_SSH_ED25519: &str = "sk-ssh-ed25519@openssh.com";
/// SSH key algorithms.
///
/// This type provides a registry of supported digital signature algorithms
/// used for SSH keys.
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Algorithm {
/// Digital Signature Algorithm
Dsa,
/// Elliptic Curve Digital Signature Algorithm
Ecdsa {
/// Elliptic curve with which to instantiate ECDSA.
curve: EcdsaCurve,
},
/// Ed25519
#[default]
Ed25519,
/// RSA
Rsa {
/// Hash function to use with RSASSA-PKCS#1v15 signatures as specified
/// using [RFC8332] algorithm identifiers.
///
/// If `hash` is set to `None`, then `ssh-rsa` is used as the algorithm
/// name.
///
/// [RFC8332]: https://datatracker.ietf.org/doc/html/rfc8332
hash: Option<HashAlg>,
},
/// FIDO/U2F key with ECDSA/NIST-P256 + SHA-256
SkEcdsaSha2NistP256,
/// FIDO/U2F key with Ed25519
SkEd25519,
/// Other
#[cfg(feature = "alloc")]
Other(AlgorithmName),
}
impl Algorithm {
/// Decode algorithm from the given string identifier.
///
/// # Supported algorithms
/// - `ecdsa-sha2-nistp256`
/// - `ecdsa-sha2-nistp384`
/// - `ecdsa-sha2-nistp521`
/// - `ssh-dss`
/// - `ssh-ed25519`
/// - `ssh-rsa`
/// - `sk-ecdsa-sha2-nistp256@openssh.com` (FIDO/U2F key)
/// - `sk-ssh-ed25519@openssh.com` (FIDO/U2F key)
///
/// Any other algorithms are mapped to the [`Algorithm::Other`] variant.
pub fn new(id: &str) -> Result<Self> {
Ok(id.parse()?)
}
/// Decode algorithm from the given string identifier as used by
/// the OpenSSH certificate format.
///
/// OpenSSH certificate algorithms end in `*-cert-v01@openssh.com`.
/// See [PROTOCOL.certkeys] for more information.
///
/// # Supported algorithms
/// - `ssh-rsa-cert-v01@openssh.com`
/// - `ssh-dss-cert-v01@openssh.com`
/// - `ecdsa-sha2-nistp256-cert-v01@openssh.com`
/// - `ecdsa-sha2-nistp384-cert-v01@openssh.com`
/// - `ecdsa-sha2-nistp521-cert-v01@openssh.com`
/// - `ssh-ed25519-cert-v01@openssh.com`
/// - `sk-ecdsa-sha2-nistp256-cert-v01@openssh.com` (FIDO/U2F key)
/// - `sk-ssh-ed25519-cert-v01@openssh.com` (FIDO/U2F key)
///
/// Any other algorithms are mapped to the [`Algorithm::Other`] variant.
///
/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
pub fn new_certificate(id: &str) -> Result<Self> {
match id {
CERT_DSA => Ok(Algorithm::Dsa),
CERT_ECDSA_SHA2_P256 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256,
}),
CERT_ECDSA_SHA2_P384 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP384,
}),
CERT_ECDSA_SHA2_P521 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP521,
}),
CERT_ED25519 => Ok(Algorithm::Ed25519),
CERT_RSA => Ok(Algorithm::Rsa { hash: None }),
CERT_SK_ECDSA_SHA2_P256 => Ok(Algorithm::SkEcdsaSha2NistP256),
CERT_SK_SSH_ED25519 => Ok(Algorithm::SkEd25519),
#[cfg(feature = "alloc")]
_ => Ok(Algorithm::Other(AlgorithmName::from_certificate_type(id)?)),
#[cfg(not(feature = "alloc"))]
_ => Err(Error::AlgorithmUnknown),
}
}
/// Get the string identifier which corresponds to this algorithm.
pub fn as_str(&self) -> &str {
match self {
Algorithm::Dsa => SSH_DSA,
Algorithm::Ecdsa { curve } => match curve {
EcdsaCurve::NistP256 => ECDSA_SHA2_P256,
EcdsaCurve::NistP384 => ECDSA_SHA2_P384,
EcdsaCurve::NistP521 => ECDSA_SHA2_P521,
},
Algorithm::Ed25519 => SSH_ED25519,
Algorithm::Rsa { hash } => match hash {
None => SSH_RSA,
Some(HashAlg::Sha256) => RSA_SHA2_256,
Some(HashAlg::Sha512) => RSA_SHA2_512,
},
Algorithm::SkEcdsaSha2NistP256 => SK_ECDSA_SHA2_P256,
Algorithm::SkEd25519 => SK_SSH_ED25519,
#[cfg(feature = "alloc")]
Algorithm::Other(algorithm) => algorithm.as_str(),
}
}
/// Get the string identifier which corresponds to the OpenSSH certificate
/// format.
///
/// OpenSSH certificate algorithms end in `*-cert-v01@openssh.com`.
/// See [PROTOCOL.certkeys] for more information.
///
/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
#[cfg(feature = "alloc")]
pub fn to_certificate_type(&self) -> String {
match self {
Algorithm::Dsa => CERT_DSA,
Algorithm::Ecdsa { curve } => match curve {
EcdsaCurve::NistP256 => CERT_ECDSA_SHA2_P256,
EcdsaCurve::NistP384 => CERT_ECDSA_SHA2_P384,
EcdsaCurve::NistP521 => CERT_ECDSA_SHA2_P521,
},
Algorithm::Ed25519 => CERT_ED25519,
Algorithm::Rsa { .. } => CERT_RSA,
Algorithm::SkEcdsaSha2NistP256 => CERT_SK_ECDSA_SHA2_P256,
Algorithm::SkEd25519 => CERT_SK_SSH_ED25519,
Algorithm::Other(algorithm) => return algorithm.certificate_type(),
}
.to_owned()
}
/// Is the algorithm DSA?
pub fn is_dsa(self) -> bool {
self == Algorithm::Dsa
}
/// Is the algorithm ECDSA?
pub fn is_ecdsa(self) -> bool {
matches!(self, Algorithm::Ecdsa { .. })
}
/// Is the algorithm Ed25519?
pub fn is_ed25519(self) -> bool {
self == Algorithm::Ed25519
}
/// Is the algorithm RSA?
pub fn is_rsa(self) -> bool {
matches!(self, Algorithm::Rsa { .. })
}
/// Return an error indicating this algorithm is unsupported.
#[allow(dead_code)]
pub(crate) fn unsupported_error(self) -> Error {
Error::AlgorithmUnsupported { algorithm: self }
}
}
impl AsRef<str> for Algorithm {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Label for Algorithm {}
impl fmt::Display for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl str::FromStr for Algorithm {
type Err = LabelError;
fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
match id {
SSH_DSA => Ok(Algorithm::Dsa),
ECDSA_SHA2_P256 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256,
}),
ECDSA_SHA2_P384 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP384,
}),
ECDSA_SHA2_P521 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP521,
}),
RSA_SHA2_256 => Ok(Algorithm::Rsa {
hash: Some(HashAlg::Sha256),
}),
RSA_SHA2_512 => Ok(Algorithm::Rsa {
hash: Some(HashAlg::Sha512),
}),
SSH_ED25519 => Ok(Algorithm::Ed25519),
SSH_RSA => Ok(Algorithm::Rsa { hash: None }),
SK_ECDSA_SHA2_P256 => Ok(Algorithm::SkEcdsaSha2NistP256),
SK_SSH_ED25519 => Ok(Algorithm::SkEd25519),
#[cfg(feature = "alloc")]
_ => Ok(Algorithm::Other(AlgorithmName::from_str(id)?)),
#[cfg(not(feature = "alloc"))]
_ => Err(LabelError::new(id)),
}
}
}
/// Elliptic curves supported for use with ECDSA.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum EcdsaCurve {
/// NIST P-256 (a.k.a. prime256v1, secp256r1)
NistP256,
/// NIST P-384 (a.k.a. secp384r1)
NistP384,
/// NIST P-521 (a.k.a. secp521r1)
NistP521,
}
impl EcdsaCurve {
/// Decode elliptic curve from the given string identifier.
///
/// # Supported curves
///
/// - `nistp256`
/// - `nistp384`
/// - `nistp521`
pub fn new(id: &str) -> Result<Self> {
Ok(id.parse()?)
}
/// Get the string identifier which corresponds to this ECDSA elliptic curve.
pub fn as_str(self) -> &'static str {
match self {
EcdsaCurve::NistP256 => "nistp256",
EcdsaCurve::NistP384 => "nistp384",
EcdsaCurve::NistP521 => "nistp521",
}
}
/// Get the number of bytes needed to encode a field element for this curve.
#[cfg(feature = "alloc")]
pub(crate) const fn field_size(self) -> usize {
match self {
EcdsaCurve::NistP256 => 32,
EcdsaCurve::NistP384 => 48,
EcdsaCurve::NistP521 => 66,
}
}
}
impl AsRef<str> for EcdsaCurve {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Label for EcdsaCurve {}
impl fmt::Display for EcdsaCurve {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl str::FromStr for EcdsaCurve {
type Err = LabelError;
fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
match id {
"nistp256" => Ok(EcdsaCurve::NistP256),
"nistp384" => Ok(EcdsaCurve::NistP384),
"nistp521" => Ok(EcdsaCurve::NistP521),
_ => Err(LabelError::new(id)),
}
}
}
/// Hashing algorithms a.k.a. digest functions.
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum HashAlg {
/// SHA-256
#[default]
Sha256,
/// SHA-512
Sha512,
}
impl HashAlg {
/// Decode elliptic curve from the given string identifier.
///
/// # Supported hash algorithms
///
/// - `sha256`
/// - `sha512`
pub fn new(id: &str) -> Result<Self> {
Ok(id.parse()?)
}
/// Get the string identifier for this hash algorithm.
pub fn as_str(self) -> &'static str {
match self {
HashAlg::Sha256 => SHA256,
HashAlg::Sha512 => SHA512,
}
}
/// Get the size of a digest produced by this hash function.
pub const fn digest_size(self) -> usize {
match self {
HashAlg::Sha256 => 32,
HashAlg::Sha512 => 64,
}
}
/// Compute a digest of the given message using this hash function.
#[cfg(feature = "alloc")]
pub fn digest(self, msg: &[u8]) -> Vec<u8> {
match self {
HashAlg::Sha256 => Sha256::digest(msg).to_vec(),
HashAlg::Sha512 => Sha512::digest(msg).to_vec(),
}
}
}
impl Label for HashAlg {}
impl AsRef<str> for HashAlg {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for HashAlg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl str::FromStr for HashAlg {
type Err = LabelError;
fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
match id {
SHA256 => Ok(HashAlg::Sha256),
SHA512 => Ok(HashAlg::Sha512),
_ => Err(LabelError::new(id)),
}
}
}
/// Key Derivation Function (KDF) algorithms.
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum KdfAlg {
/// None.
None,
/// bcrypt-pbkdf.
#[default]
Bcrypt,
}
impl KdfAlg {
/// Decode KDF algorithm from the given `kdfname`.
///
/// # Supported KDF names
/// - `none`
pub fn new(kdfname: &str) -> Result<Self> {
Ok(kdfname.parse()?)
}
/// Get the string identifier which corresponds to this algorithm.
pub fn as_str(self) -> &'static str {
match self {
Self::None => NONE,
Self::Bcrypt => BCRYPT,
}
}
/// Is the KDF algorithm "none"?
pub fn is_none(self) -> bool {
self == Self::None
}
}
impl Label for KdfAlg {}
impl AsRef<str> for KdfAlg {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for KdfAlg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl str::FromStr for KdfAlg {
type Err = LabelError;
fn from_str(kdfname: &str) -> core::result::Result<Self, LabelError> {
match kdfname {
NONE => Ok(Self::None),
BCRYPT => Ok(Self::Bcrypt),
_ => Err(LabelError::new(kdfname)),
}
}
}

101
vendor/ssh-key/src/algorithm/name.rs vendored Normal file
View File

@@ -0,0 +1,101 @@
use alloc::string::String;
use core::str::{self, FromStr};
use encoding::LabelError;
/// The suffix added to the `name` in a `name@domainname` algorithm string identifier.
const CERT_STR_SUFFIX: &str = "-cert-v01";
/// According to [RFC4251 § 6], algorithm names are ASCII strings that are at most 64
/// characters long.
///
/// [RFC4251 § 6]: https://www.rfc-editor.org/rfc/rfc4251.html#section-6
const MAX_ALGORITHM_NAME_LEN: usize = 64;
/// The maximum length of the certificate string identifier is [`MAX_ALGORITHM_NAME_LEN`] +
/// `"-cert-v01".len()` (the certificate identifier is obtained by inserting `"-cert-v01"` in the
/// algorithm name).
const MAX_CERT_STR_LEN: usize = MAX_ALGORITHM_NAME_LEN + CERT_STR_SUFFIX.len();
/// A string representing an additional algorithm name in the `name@domainname` format (see
/// [RFC4251 § 6]).
///
/// Additional algorithm names must be non-empty printable ASCII strings no longer than 64
/// characters.
///
/// This also provides a `name-cert-v01@domainnname` string identifier for the corresponding
/// OpenSSH certificate format, derived from the specified `name@domainname` string.
///
/// NOTE: RFC4251 specifies additional validation criteria for algorithm names, but we do not
/// implement all of them here.
///
/// [RFC4251 § 6]: https://www.rfc-editor.org/rfc/rfc4251.html#section-6
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct AlgorithmName {
/// The string identifier which corresponds to this algorithm.
id: String,
}
impl AlgorithmName {
/// Create a new algorithm identifier.
pub fn new(id: impl Into<String>) -> Result<Self, LabelError> {
let id = id.into();
validate_algorithm_id(&id, MAX_ALGORITHM_NAME_LEN)?;
split_algorithm_id(&id)?;
Ok(Self { id })
}
/// Get the string identifier which corresponds to this algorithm name.
pub fn as_str(&self) -> &str {
&self.id
}
/// Get the string identifier which corresponds to the OpenSSH certificate format.
pub fn certificate_type(&self) -> String {
let (name, domain) = split_algorithm_id(&self.id).expect("format checked in constructor");
format!("{name}{CERT_STR_SUFFIX}@{domain}")
}
/// Create a new [`AlgorithmName`] from an OpenSSH certificate format string identifier.
pub fn from_certificate_type(id: &str) -> Result<Self, LabelError> {
validate_algorithm_id(id, MAX_CERT_STR_LEN)?;
// Derive the algorithm name from the certificate format string identifier:
let (name, domain) = split_algorithm_id(id)?;
let name = name
.strip_suffix(CERT_STR_SUFFIX)
.ok_or_else(|| LabelError::new(id))?;
let algorithm_name = format!("{name}@{domain}");
Ok(Self { id: algorithm_name })
}
}
impl FromStr for AlgorithmName {
type Err = LabelError;
fn from_str(id: &str) -> Result<Self, LabelError> {
Self::new(id)
}
}
/// Check if the length of `id` is at most `n`, and that `id` only consists of ASCII characters.
fn validate_algorithm_id(id: &str, n: usize) -> Result<(), LabelError> {
if id.len() > n || !id.is_ascii() {
return Err(LabelError::new(id));
}
Ok(())
}
/// Split a `name@domainname` algorithm string identifier into `(name, domainname)`.
fn split_algorithm_id(id: &str) -> Result<(&str, &str), LabelError> {
let (name, domain) = id.split_once('@').ok_or_else(|| LabelError::new(id))?;
// TODO: validate name and domain_name according to the criteria from RFC4251
if name.is_empty() || domain.is_empty() || domain.contains('@') {
return Err(LabelError::new(id));
}
Ok((name, domain))
}

385
vendor/ssh-key/src/authorized_keys.rs vendored Normal file
View File

@@ -0,0 +1,385 @@
//! Parser for `AuthorizedKeysFile`-formatted data.
use crate::{Error, PublicKey, Result};
use core::str;
#[cfg(feature = "alloc")]
use {
alloc::string::{String, ToString},
core::fmt,
};
#[cfg(feature = "std")]
use std::{fs, path::Path, vec::Vec};
/// Character that begins a comment
const COMMENT_DELIMITER: char = '#';
/// Parser for `AuthorizedKeysFile`-formatted data, typically found in
/// `~/.ssh/authorized_keys`.
///
/// For a full description of the format, see:
/// <https://man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT>
///
/// Each line of the file consists of a single public key. Blank lines are ignored.
///
/// Public keys consist of the following space-separated fields:
///
/// ```text
/// options, keytype, base64-encoded key, comment
/// ```
///
/// - The options field is optional.
/// - The keytype is `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`,
/// `ssh-ed25519`, `ssh-dss` or `ssh-rsa`
/// - The comment field is not used for anything (but may be convenient for the user to identify
/// the key).
pub struct AuthorizedKeys<'a> {
/// Lines of the file being iterated over
lines: str::Lines<'a>,
}
impl<'a> AuthorizedKeys<'a> {
/// Create a new parser for the given input buffer.
pub fn new(input: &'a str) -> Self {
Self {
lines: input.lines(),
}
}
/// Read an [`AuthorizedKeys`] file from the filesystem, returning an
/// [`Entry`] vector on success.
#[cfg(feature = "std")]
pub fn read_file(path: impl AsRef<Path>) -> Result<Vec<Entry>> {
// TODO(tarcieri): permissions checks
let input = fs::read_to_string(path)?;
AuthorizedKeys::new(&input).collect()
}
/// Get the next line, trimming any comments and trailing whitespace.
///
/// Ignores empty lines.
fn next_line_trimmed(&mut self) -> Option<&'a str> {
loop {
let mut line = self.lines.next()?;
// Strip comment if present
if let Some((l, _)) = line.split_once(COMMENT_DELIMITER) {
line = l;
}
// Trim trailing whitespace
line = line.trim_end();
if !line.is_empty() {
return Some(line);
}
}
}
}
impl Iterator for AuthorizedKeys<'_> {
type Item = Result<Entry>;
fn next(&mut self) -> Option<Result<Entry>> {
self.next_line_trimmed().map(|line| line.parse())
}
}
/// Individual entry in an `authorized_keys` file containing a single public key.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Entry {
/// Configuration options field, if present.
#[cfg(feature = "alloc")]
config_opts: ConfigOpts,
/// Public key
public_key: PublicKey,
}
impl Entry {
/// Get configuration options for this entry.
#[cfg(feature = "alloc")]
pub fn config_opts(&self) -> &ConfigOpts {
&self.config_opts
}
/// Get public key for this entry.
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
}
#[cfg(feature = "alloc")]
impl From<Entry> for ConfigOpts {
fn from(entry: Entry) -> ConfigOpts {
entry.config_opts
}
}
impl From<Entry> for PublicKey {
fn from(entry: Entry) -> PublicKey {
entry.public_key
}
}
impl From<PublicKey> for Entry {
fn from(public_key: PublicKey) -> Entry {
Entry {
#[cfg(feature = "alloc")]
config_opts: ConfigOpts::default(),
public_key,
}
}
}
impl str::FromStr for Entry {
type Err = Error;
fn from_str(line: &str) -> Result<Self> {
match line.matches(' ').count() {
1..=2 => Ok(Self {
#[cfg(feature = "alloc")]
config_opts: Default::default(),
public_key: line.parse()?,
}),
3.. => {
// Having >= 3 spaces is ambiguous: it's either a key preceded
// by options, or a key with spaces in its comment. We'll try
// parsing as a single key first, then fall back to parsing as
// option + key.
match line.parse() {
Ok(public_key) => Ok(Self {
#[cfg(feature = "alloc")]
config_opts: Default::default(),
public_key,
}),
Err(_) => line
.split_once(' ')
.map(|(config_opts_str, public_key_str)| {
ConfigOptsIter(config_opts_str).validate()?;
Ok(Self {
#[cfg(feature = "alloc")]
config_opts: ConfigOpts(config_opts_str.to_string()),
public_key: public_key_str.parse()?,
})
})
.ok_or(Error::FormatEncoding)?,
}
}
_ => Err(Error::FormatEncoding),
}
}
}
#[cfg(feature = "alloc")]
impl ToString for Entry {
fn to_string(&self) -> String {
let mut s = String::new();
if !self.config_opts.is_empty() {
s.push_str(self.config_opts.as_str());
s.push(' ');
}
s.push_str(&self.public_key.to_string());
s
}
}
/// Configuration options associated with a particular public key.
///
/// These options are a comma-separated list preceding each public key
/// in the `authorized_keys` file.
///
/// The [`ConfigOpts::iter`] method can be used to iterate over each
/// comma-separated value.
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ConfigOpts(String);
#[cfg(feature = "alloc")]
impl ConfigOpts {
/// Parse an options string.
pub fn new(string: impl Into<String>) -> Result<Self> {
let ret = Self(string.into());
ret.iter().validate()?;
Ok(ret)
}
/// Borrow the configuration options as a `str`.
pub fn as_str(&self) -> &str {
self.0.as_str()
}
/// Are there no configuration options?
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Iterate over the comma-delimited configuration options.
pub fn iter(&self) -> ConfigOptsIter<'_> {
ConfigOptsIter(self.as_str())
}
}
#[cfg(feature = "alloc")]
impl AsRef<str> for ConfigOpts {
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[cfg(feature = "alloc")]
impl str::FromStr for ConfigOpts {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::new(s)
}
}
#[cfg(feature = "alloc")]
impl fmt::Display for ConfigOpts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
/// Iterator over configuration options.
#[derive(Clone, Debug)]
pub struct ConfigOptsIter<'a>(&'a str);
impl<'a> ConfigOptsIter<'a> {
/// Create new configuration options iterator.
///
/// Validates that the options are well-formed.
pub fn new(s: &'a str) -> Result<Self> {
let ret = Self(s);
ret.clone().validate()?;
Ok(ret)
}
/// Validate that config options are well-formed.
fn validate(&mut self) -> Result<()> {
while self.try_next()?.is_some() {}
Ok(())
}
/// Attempt to parse the next comma-delimited option string.
fn try_next(&mut self) -> Result<Option<&'a str>> {
if self.0.is_empty() {
return Ok(None);
}
let mut quoted = false;
let mut index = 0;
while let Some(byte) = self.0.as_bytes().get(index).cloned() {
match byte {
b',' => {
// Commas inside quoted text are ignored
if !quoted {
let (next, rest) = self.0.split_at(index);
self.0 = &rest[1..]; // Strip comma
return Ok(Some(next));
}
}
// TODO(tarcieri): stricter handling of quotes
b'"' => {
// Toggle quoted mode on-off
quoted = !quoted;
}
// Valid characters
b'A'..=b'Z'
| b'a'..=b'z'
| b'0'..=b'9'
| b'!'..=b'/'
| b':'..=b'@'
| b'['..=b'_'
| b'{'
| b'}'
| b'|'
| b'~' => (),
_ => return Err(encoding::Error::CharacterEncoding.into()),
}
index = index.checked_add(1).ok_or(encoding::Error::Length)?;
}
let remaining = self.0;
self.0 = "";
Ok(Some(remaining))
}
}
impl<'a> Iterator for ConfigOptsIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
// Ensured valid by constructor
self.try_next().expect("malformed options string")
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::ConfigOptsIter;
#[test]
fn options_empty() {
assert_eq!(ConfigOptsIter("").try_next(), Ok(None));
}
#[test]
fn options_no_comma() {
let mut opts = ConfigOptsIter("foo");
assert_eq!(opts.try_next(), Ok(Some("foo")));
assert_eq!(opts.try_next(), Ok(None));
}
#[test]
fn options_no_comma_quoted() {
let mut opts = ConfigOptsIter("foo=\"bar\"");
assert_eq!(opts.try_next(), Ok(Some("foo=\"bar\"")));
assert_eq!(opts.try_next(), Ok(None));
// Comma inside quoted section
let mut opts = ConfigOptsIter("foo=\"bar,baz\"");
assert_eq!(opts.try_next(), Ok(Some("foo=\"bar,baz\"")));
assert_eq!(opts.try_next(), Ok(None));
}
#[test]
fn options_comma_delimited() {
let mut opts = ConfigOptsIter("foo,bar");
assert_eq!(opts.try_next(), Ok(Some("foo")));
assert_eq!(opts.try_next(), Ok(Some("bar")));
assert_eq!(opts.try_next(), Ok(None));
}
#[test]
fn options_comma_delimited_quoted() {
let mut opts = ConfigOptsIter("foo=\"bar\",baz");
assert_eq!(opts.try_next(), Ok(Some("foo=\"bar\"")));
assert_eq!(opts.try_next(), Ok(Some("baz")));
assert_eq!(opts.try_next(), Ok(None));
}
#[test]
fn options_invalid_character() {
let mut opts = ConfigOptsIter("");
assert_eq!(
opts.try_next(),
Err(encoding::Error::CharacterEncoding.into())
);
let mut opts = ConfigOptsIter("x,❌");
assert_eq!(opts.try_next(), Ok(Some("x")));
assert_eq!(
opts.try_next(),
Err(encoding::Error::CharacterEncoding.into())
);
}
}

550
vendor/ssh-key/src/certificate.rs vendored Normal file
View File

@@ -0,0 +1,550 @@
//! OpenSSH certificate support.
mod builder;
mod cert_type;
mod field;
mod options_map;
mod unix_time;
pub use self::{builder::Builder, cert_type::CertType, field::Field, options_map::OptionsMap};
use self::unix_time::UnixTime;
use crate::{
public::{KeyData, SshFormat},
Algorithm, Error, Fingerprint, HashAlg, Result, Signature,
};
use alloc::{
borrow::ToOwned,
string::{String, ToString},
vec::Vec,
};
use core::str::FromStr;
use encoding::{Base64Reader, CheckedSum, Decode, Encode, Reader, Writer};
use signature::Verifier;
#[cfg(feature = "serde")]
use serde::{de, ser, Deserialize, Serialize};
#[cfg(feature = "std")]
use std::{fs, path::Path, time::SystemTime};
/// OpenSSH certificate as specified in [PROTOCOL.certkeys].
///
/// OpenSSH supports X.509-like certificate authorities, but using a custom
/// encoding format.
///
/// # ⚠️ Security Warning
///
/// Certificates must be validated before they can be trusted!
///
/// The [`Certificate`] type does not automatically perform validation checks
/// and supports parsing certificates which may potentially be invalid.
/// Just because a [`Certificate`] parses successfully does not mean that it
/// can be trusted.
///
/// See "Certificate Validation" documentation below for more information on
/// how to properly validate certificates.
///
/// # Certificate Validation
///
/// For a certificate to be trusted, the following properties MUST be
/// validated:
///
/// - Certificate is signed by a trusted certificate authority (CA)
/// - Signature over the certificate verifies successfully
/// - Current time is within the certificate's validity window
/// - Certificate authorizes the expected principal
/// - All critical extensions to the certificate are recognized and validate
/// successfully.
///
/// The [`Certificate::validate`] and [`Certificate::validate_at`] methods can
/// be used to validate a certificate.
///
/// ## Example
///
/// The following example walks through how to implement the steps outlined
/// above for validating a certificate:
///
#[cfg_attr(all(feature = "p256", feature = "std"), doc = " ```")]
#[cfg_attr(not(all(feature = "p256", feature = "std")), doc = " ```ignore")]
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use ssh_key::{Certificate, Fingerprint};
/// use std::str::FromStr;
///
/// // List of trusted certificate authority (CA) fingerprints
/// let ca_fingerprints = [
/// Fingerprint::from_str("SHA256:JQ6FV0rf7qqJHZqIj4zNH8eV0oB8KLKh9Pph3FTD98g")?
/// ];
///
/// // Certificate to be validated
/// let certificate = Certificate::from_str(
/// "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIE7x9ln6uZLLkfXM8iatrnAAuytVHeCznU8VlEgx7TvLAAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAAAAAAAAAAAABAAAAFGVkMjU1MTktd2l0aC1wMjU2LWNhAAAAAAAAAABiUZm7AAAAAPTaMrsAAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR8H9hzDOU0V76NkkCY7DZIgw+SqoojY6xlb91FIfpjE+UR8YkbTp5ar44ULQatFaZqQlfz8FHYTooOL5G6gHBHAAAAZAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAASQAAACEA/0Cwxhkac5AeNYE958j8GgvmkIESDH1TE7QYIqxsFsIAAAAgTEw8WVjlz8AnvyaKGOUELMpyFFJagtD2JFAIAJvilrc= user@example.com"
/// )?;
///
/// // Perform basic certificate validation, ensuring that the certificate is
/// // signed by a trusted certificate authority (CA) and checking that the
/// // current system clock time is within the certificate's validity window
/// certificate.validate(&ca_fingerprints)?;
///
/// // Check that the certificate includes the expected principal name
/// // (i.e. username or hostname)
/// // if !certificate.principals().contains(expected_principal) { return Err(...) }
///
/// // Check that all of the critical extensions are recognized
/// // if !certificate.critical_options.iter().all(|critical| ...) { return Err(...) }
///
/// // If we've made it this far, the certificate can be trusted
/// Ok(())
/// # }
/// ```
///
/// # Certificate Builder (SSH CA support)
///
/// This crate implements all of the functionality needed for a pure Rust
/// SSH certificate authority which can build and sign OpenSSH certificates.
///
/// See the [`Builder`] type's documentation for more information.
///
/// # `serde` support
///
/// When the `serde` feature of this crate is enabled, this type receives impls
/// of [`Deserialize`][`serde::Deserialize`] and [`Serialize`][`serde::Serialize`].
///
/// The serialization uses a binary encoding with binary formats like bincode
/// and CBOR, and the OpenSSH string serialization when used with
/// human-readable formats like JSON and TOML.
///
/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Certificate {
/// CA-provided random bitstring of arbitrary length
/// (but typically 16 or 32 bytes).
nonce: Vec<u8>,
/// Public key data.
public_key: KeyData,
/// Serial number.
serial: u64,
/// Certificate type.
cert_type: CertType,
/// Key ID.
key_id: String,
/// Valid principals.
valid_principals: Vec<String>,
/// Valid after.
valid_after: UnixTime,
/// Valid before.
valid_before: UnixTime,
/// Critical options.
critical_options: OptionsMap,
/// Extensions.
extensions: OptionsMap,
/// Reserved field.
reserved: Vec<u8>,
/// Signature key of signing CA.
signature_key: KeyData,
/// Signature over the certificate.
signature: Signature,
/// Comment on the certificate.
comment: String,
}
impl Certificate {
/// Parse an OpenSSH-formatted certificate.
///
/// OpenSSH-formatted certificates look like the following
/// (i.e. similar to OpenSSH public keys with `-cert-v01@openssh.com`):
///
/// ```text
/// ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlc...8REbCaAw== user@example.com
/// ```
pub fn from_openssh(certificate_str: &str) -> Result<Self> {
let encapsulation = SshFormat::decode(certificate_str.trim_end().as_bytes())?;
let mut reader = Base64Reader::new(encapsulation.base64_data)?;
let mut cert = Certificate::decode(&mut reader)?;
// Verify that the algorithm in the Base64-encoded data matches the text
if encapsulation.algorithm_id != cert.algorithm().to_certificate_type() {
return Err(Error::AlgorithmUnknown);
}
cert.comment = encapsulation.comment.to_owned();
Ok(reader.finish(cert)?)
}
/// Parse a raw binary OpenSSH certificate.
pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
let reader = &mut bytes;
let cert = Certificate::decode(reader)?;
Ok(reader.finish(cert)?)
}
/// Encode OpenSSH certificate to a [`String`].
pub fn to_openssh(&self) -> Result<String> {
SshFormat::encode_string(
&self.algorithm().to_certificate_type(),
self,
self.comment(),
)
}
/// Serialize OpenSSH certificate as raw bytes.
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut cert_bytes = Vec::new();
self.encode(&mut cert_bytes)?;
Ok(cert_bytes)
}
/// Read OpenSSH certificate from a file.
#[cfg(feature = "std")]
pub fn read_file(path: &Path) -> Result<Self> {
let input = fs::read_to_string(path)?;
Self::from_openssh(&input)
}
/// Write OpenSSH certificate to a file.
#[cfg(feature = "std")]
pub fn write_file(&self, path: &Path) -> Result<()> {
let encoded = self.to_openssh()?;
fs::write(path, encoded.as_bytes())?;
Ok(())
}
/// Get the public key algorithm for this certificate.
pub fn algorithm(&self) -> Algorithm {
self.public_key.algorithm()
}
/// Get the comment on this certificate.
pub fn comment(&self) -> &str {
self.comment.as_str()
}
/// Nonces are a CA-provided random bitstring of arbitrary length
/// (but typically 16 or 32 bytes).
///
/// It's included to make attacks that depend on inducing collisions in the
/// signature hash infeasible.
pub fn nonce(&self) -> &[u8] {
&self.nonce
}
/// Get this certificate's public key data.
pub fn public_key(&self) -> &KeyData {
&self.public_key
}
/// Optional certificate serial number set by the CA to provide an
/// abbreviated way to refer to certificates from that CA.
///
/// If a CA does not wish to number its certificates, it must set this
/// field to zero.
pub fn serial(&self) -> u64 {
self.serial
}
/// Specifies whether this certificate is for identification of a user or
/// a host.
pub fn cert_type(&self) -> CertType {
self.cert_type
}
/// Key IDs are a free-form text field that is filled in by the CA at the
/// time of signing.
///
/// The intention is that the contents of this field are used to identify
/// the identity principal in log messages.
pub fn key_id(&self) -> &str {
&self.key_id
}
/// List of zero or more principals which this certificate is valid for.
///
/// Principals are hostnames for host certificates and usernames for user
/// certificates.
///
/// As a special case, a zero-length "valid principals" field means the
/// certificate is valid for any principal of the specified type.
pub fn valid_principals(&self) -> &[String] {
&self.valid_principals
}
/// Valid after (Unix time).
pub fn valid_after(&self) -> u64 {
self.valid_after.into()
}
/// Valid before (Unix time).
pub fn valid_before(&self) -> u64 {
self.valid_before.into()
}
/// Valid after (system time).
#[cfg(feature = "std")]
pub fn valid_after_time(&self) -> SystemTime {
self.valid_after.into()
}
/// Valid before (system time).
#[cfg(feature = "std")]
pub fn valid_before_time(&self) -> SystemTime {
self.valid_before.into()
}
/// The critical options section of the certificate specifies zero or more
/// options on the certificate's validity.
///
/// Each named option may only appear once in a certificate.
///
/// All options are "critical"; if an implementation does not recognize an
/// option, then the validating party should refuse to accept the
/// certificate.
pub fn critical_options(&self) -> &OptionsMap {
&self.critical_options
}
/// The extensions section of the certificate specifies zero or more
/// non-critical certificate extensions.
///
/// If an implementation does not recognise an extension, then it should
/// ignore it.
pub fn extensions(&self) -> &OptionsMap {
&self.extensions
}
/// Signature key of signing CA.
pub fn signature_key(&self) -> &KeyData {
&self.signature_key
}
/// Signature computed over all preceding fields from the initial string up
/// to, and including the signature key.
pub fn signature(&self) -> &Signature {
&self.signature
}
/// Perform certificate validation using the system clock to check that
/// the current time is within the certificate's validity window.
///
/// # ⚠️ Security Warning: Some Assembly Required
///
/// See [`Certificate::validate_at`] documentation for important notes on
/// how to properly validate certificates!
#[cfg(feature = "std")]
pub fn validate<'a, I>(&self, ca_fingerprints: I) -> Result<()>
where
I: IntoIterator<Item = &'a Fingerprint>,
{
self.validate_at(UnixTime::now()?.into(), ca_fingerprints)
}
/// Perform certificate validation.
///
/// Checks for the following:
///
/// - Specified Unix timestamp is within the certificate's valid range
/// - Certificate's signature validates against the public key included in
/// the certificate
/// - Fingerprint of the public key included in the certificate matches one
/// of the trusted certificate authority (CA) fingerprints provided in
/// the `ca_fingerprints` parameter.
///
/// NOTE: only SHA-256 fingerprints are supported at this time.
///
/// # ⚠️ Security Warning: Some Assembly Required
///
/// This method does not perform the full set of validation checks needed
/// to determine if a certificate is to be trusted.
///
/// If this method succeeds, the following properties still need to be
/// checked to ensure the certificate is valid:
///
/// - `valid_principals` is empty or contains the expected principal
/// - `critical_options` is empty or contains *only* options which are
/// recognized, and that the recognized options are all valid
///
/// ## Returns
/// - `Ok` if the certificate validated successfully
/// - `Error::CertificateValidation` if the certificate failed to validate
pub fn validate_at<'a, I>(&self, unix_timestamp: u64, ca_fingerprints: I) -> Result<()>
where
I: IntoIterator<Item = &'a Fingerprint>,
{
self.verify_signature()?;
// TODO(tarcieri): support non SHA-256 public key fingerprints?
let cert_fingerprint = self.signature_key.fingerprint(HashAlg::Sha256);
if !ca_fingerprints.into_iter().any(|f| f == &cert_fingerprint) {
return Err(Error::CertificateValidation);
}
let unix_timestamp = UnixTime::new(unix_timestamp)?;
// From PROTOCOL.certkeys:
//
// "valid after" and "valid before" specify a validity period for the
// certificate. Each represents a time in seconds since 1970-01-01
// A certificate is considered valid if:
//
// valid after <= current time < valid before
if self.valid_after <= unix_timestamp && unix_timestamp < self.valid_before {
Ok(())
} else {
Err(Error::CertificateValidation)
}
}
/// Verify the signature on the certificate against the public key in the
/// certificate.
///
/// # ⚠️ Security Warning
///
/// DON'T USE THIS!
///
/// This function alone does not provide any security guarantees whatsoever.
///
/// It verifies the signature in the certificate matches the CA public key
/// in the certificate, but does not ensure the CA is trusted.
///
/// It is public only for testing purposes, and deliberately hidden from
/// the documentation for that reason.
#[doc(hidden)]
pub fn verify_signature(&self) -> Result<()> {
let mut tbs_certificate = Vec::new();
self.encode_tbs(&mut tbs_certificate)?;
self.signature_key
.verify(&tbs_certificate, &self.signature)
.map_err(|_| Error::CertificateValidation)
}
/// Encode the portion of the certificate "to be signed" by the CA
/// (or to be verified against an existing CA signature)
fn encode_tbs(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.algorithm().to_certificate_type().encode(writer)?;
self.nonce.encode(writer)?;
self.public_key.encode_key_data(writer)?;
self.serial.encode(writer)?;
self.cert_type.encode(writer)?;
self.key_id.encode(writer)?;
self.valid_principals.encode(writer)?;
self.valid_after.encode(writer)?;
self.valid_before.encode(writer)?;
self.critical_options.encode(writer)?;
self.extensions.encode(writer)?;
self.reserved.encode(writer)?;
self.signature_key.encode_prefixed(writer)
}
}
impl Decode for Certificate {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let algorithm = Algorithm::new_certificate(&String::decode(reader)?)?;
Ok(Self {
nonce: Vec::decode(reader)?,
public_key: KeyData::decode_as(reader, algorithm)?,
serial: u64::decode(reader)?,
cert_type: CertType::decode(reader)?,
key_id: String::decode(reader)?,
valid_principals: Vec::decode(reader)?,
valid_after: UnixTime::decode(reader)?,
valid_before: UnixTime::decode(reader)?,
critical_options: OptionsMap::decode(reader)?,
extensions: OptionsMap::decode(reader)?,
reserved: Vec::decode(reader)?,
signature_key: reader.read_prefixed(KeyData::decode)?,
signature: reader.read_prefixed(Signature::decode)?,
comment: String::new(),
})
}
}
impl Encode for Certificate {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.algorithm().to_certificate_type().encoded_len()?,
self.nonce.encoded_len()?,
self.public_key.encoded_key_data_len()?,
self.serial.encoded_len()?,
self.cert_type.encoded_len()?,
self.key_id.encoded_len()?,
self.valid_principals.encoded_len()?,
self.valid_after.encoded_len()?,
self.valid_before.encoded_len()?,
self.critical_options.encoded_len()?,
self.extensions.encoded_len()?,
self.reserved.encoded_len()?,
self.signature_key.encoded_len_prefixed()?,
self.signature.encoded_len_prefixed()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.encode_tbs(writer)?;
self.signature.encode_prefixed(writer)
}
}
impl FromStr for Certificate {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_openssh(s)
}
}
impl ToString for Certificate {
fn to_string(&self) -> String {
self.to_openssh().expect("SSH certificate encoding error")
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Certificate {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let string = String::deserialize(deserializer)?;
Self::from_openssh(&string).map_err(de::Error::custom)
} else {
let bytes = Vec::<u8>::deserialize(deserializer)?;
Self::from_bytes(&bytes).map_err(de::Error::custom)
}
}
}
#[cfg(feature = "serde")]
impl Serialize for Certificate {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
if serializer.is_human_readable() {
self.to_openssh()
.map_err(ser::Error::custom)?
.serialize(serializer)
} else {
self.to_bytes()
.map_err(ser::Error::custom)?
.serialize(serializer)
}
}
}

View File

@@ -0,0 +1,313 @@
//! OpenSSH certificate builder.
use super::{unix_time::UnixTime, CertType, Certificate, Field, OptionsMap};
use crate::{public, Result, Signature, SigningKey};
use alloc::{string::String, vec::Vec};
#[cfg(feature = "rand_core")]
use rand_core::CryptoRngCore;
#[cfg(feature = "std")]
use std::time::SystemTime;
#[cfg(doc)]
use crate::PrivateKey;
/// OpenSSH certificate builder.
///
/// This type provides the core functionality of an OpenSSH certificate
/// authority.
///
/// It can build and sign OpenSSH certificates.
///
/// ## Principals
///
/// Certificates are valid for a specific set of principal names:
///
/// - Usernames for [`CertType::User`].
/// - Hostnames for [`CertType::Host`].
///
/// When building a certificate, you will either need to specify principals
/// by calling [`Builder::valid_principal`] one or more times, or explicitly
/// marking a certificate as valid for all principals (i.e. "golden ticket")
/// using the [`Builder::all_principals_valid`] method.
///
/// ## Example
///
#[cfg_attr(
all(feature = "ed25519", feature = "getrandom", feature = "std"),
doc = " ```"
)]
#[cfg_attr(
not(all(feature = "ed25519", feature = "getrandom", feature = "std")),
doc = " ```ignore"
)]
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use ssh_key::{Algorithm, PrivateKey, certificate, rand_core::OsRng};
/// use std::time::{SystemTime, UNIX_EPOCH};
///
/// // Generate the certificate authority's private key
/// let ca_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
///
/// // Generate a "subject" key to be signed by the certificate authority.
/// // Normally a user or host would do this locally and give the certificate
/// // authority the public key.
/// let subject_private_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
/// let subject_public_key = subject_private_key.public_key();
///
/// // Create certificate validity window
/// let valid_after = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
/// let valid_before = valid_after + (365 * 86400); // e.g. 1 year
///
/// // Initialize certificate builder
/// let mut cert_builder = certificate::Builder::new_with_random_nonce(
/// &mut OsRng,
/// subject_public_key,
/// valid_after,
/// valid_before,
/// )?;
/// cert_builder.serial(42)?; // Optional: serial number chosen by the CA
/// cert_builder.key_id("nobody-cert-02")?; // Optional: CA-specific key identifier
/// cert_builder.cert_type(certificate::CertType::User)?; // User or host certificate
/// cert_builder.valid_principal("nobody")?; // Unix username or hostname
/// cert_builder.comment("nobody@example.com")?; // Comment (typically an email address)
///
/// // Sign and return the `Certificate` for `subject_public_key`
/// let cert = cert_builder.sign(&ca_key)?;
/// # Ok(())
/// # }
/// ```
pub struct Builder {
public_key: public::KeyData,
nonce: Vec<u8>,
serial: Option<u64>,
cert_type: Option<CertType>,
key_id: Option<String>,
valid_principals: Option<Vec<String>>,
valid_after: UnixTime,
valid_before: UnixTime,
critical_options: OptionsMap,
extensions: OptionsMap,
comment: Option<String>,
}
impl Builder {
/// Recommended size for a nonce.
pub const RECOMMENDED_NONCE_SIZE: usize = 16;
/// Create a new certificate builder for the given subject's public key.
///
/// Also requires a nonce (random value typically 16 or 32 bytes long) and
/// the validity window of the certificate as Unix seconds.
pub fn new(
nonce: impl Into<Vec<u8>>,
public_key: impl Into<public::KeyData>,
valid_after: u64,
valid_before: u64,
) -> Result<Self> {
let valid_after =
UnixTime::new(valid_after).map_err(|_| Field::ValidAfter.invalid_error())?;
let valid_before =
UnixTime::new(valid_before).map_err(|_| Field::ValidBefore.invalid_error())?;
if valid_before < valid_after {
return Err(Field::ValidBefore.invalid_error());
}
Ok(Self {
nonce: nonce.into(),
public_key: public_key.into(),
serial: None,
cert_type: None,
key_id: None,
valid_principals: None,
valid_after,
valid_before,
critical_options: OptionsMap::new(),
extensions: OptionsMap::new(),
comment: None,
})
}
/// Create a new certificate builder with the validity window specified
/// using [`SystemTime`] values.
#[cfg(feature = "std")]
pub fn new_with_validity_times(
nonce: impl Into<Vec<u8>>,
public_key: impl Into<public::KeyData>,
valid_after: SystemTime,
valid_before: SystemTime,
) -> Result<Self> {
let valid_after =
UnixTime::try_from(valid_after).map_err(|_| Field::ValidAfter.invalid_error())?;
let valid_before =
UnixTime::try_from(valid_before).map_err(|_| Field::ValidBefore.invalid_error())?;
Self::new(nonce, public_key, valid_after.into(), valid_before.into())
}
/// Create a new certificate builder, generating a random nonce using the
/// provided random number generator.
#[cfg(feature = "rand_core")]
pub fn new_with_random_nonce(
rng: &mut impl CryptoRngCore,
public_key: impl Into<public::KeyData>,
valid_after: u64,
valid_before: u64,
) -> Result<Self> {
let mut nonce = vec![0u8; Self::RECOMMENDED_NONCE_SIZE];
rng.fill_bytes(&mut nonce);
Self::new(nonce, public_key, valid_after, valid_before)
}
/// Set certificate serial number.
///
/// Default: `0`.
pub fn serial(&mut self, serial: u64) -> Result<&mut Self> {
if self.serial.is_some() {
return Err(Field::Serial.invalid_error());
}
self.serial = Some(serial);
Ok(self)
}
/// Set certificate type: user or host.
///
/// Default: [`CertType::User`].
pub fn cert_type(&mut self, cert_type: CertType) -> Result<&mut Self> {
if self.cert_type.is_some() {
return Err(Field::Type.invalid_error());
}
self.cert_type = Some(cert_type);
Ok(self)
}
/// Set key ID: label to identify this particular certificate.
///
/// Default `""`
pub fn key_id(&mut self, key_id: impl Into<String>) -> Result<&mut Self> {
if self.key_id.is_some() {
return Err(Field::KeyId.invalid_error());
}
self.key_id = Some(key_id.into());
Ok(self)
}
/// Add a principal (i.e. username or hostname) to `valid_principals`.
pub fn valid_principal(&mut self, principal: impl Into<String>) -> Result<&mut Self> {
match &mut self.valid_principals {
Some(principals) => principals.push(principal.into()),
None => self.valid_principals = Some(vec![principal.into()]),
}
Ok(self)
}
/// Mark this certificate as being valid for all principals.
///
/// # ⚠️ Security Warning
///
/// Use this method with care! It generates "golden ticket" certificates
/// which can e.g. authenticate as any user on a system, or impersonate
/// any host.
pub fn all_principals_valid(&mut self) -> Result<&mut Self> {
self.valid_principals = Some(Vec::new());
Ok(self)
}
/// Add a critical option to this certificate.
///
/// Critical options must be recognized or the certificate must be rejected.
pub fn critical_option(
&mut self,
name: impl Into<String>,
data: impl Into<String>,
) -> Result<&mut Self> {
let name = name.into();
let data = data.into();
if self.critical_options.contains_key(&name) {
return Err(Field::CriticalOptions.invalid_error());
}
self.critical_options.insert(name, data);
Ok(self)
}
/// Add an extension to this certificate.
///
/// Extensions can be unrecognized without impacting the certificate.
pub fn extension(
&mut self,
name: impl Into<String>,
data: impl Into<String>,
) -> Result<&mut Self> {
let name = name.into();
let data = data.into();
if self.extensions.contains_key(&name) {
return Err(Field::Extensions.invalid_error());
}
self.extensions.insert(name, data);
Ok(self)
}
/// Add a comment to this certificate.
///
/// Default `""`
pub fn comment(&mut self, comment: impl Into<String>) -> Result<&mut Self> {
if self.comment.is_some() {
return Err(Field::Comment.invalid_error());
}
self.comment = Some(comment.into());
Ok(self)
}
/// Sign the certificate using the provided signer type.
///
/// The [`PrivateKey`] type can be used as a signer.
pub fn sign<S: SigningKey>(self, signing_key: &S) -> Result<Certificate> {
// Empty valid principals result in a "golden ticket", so this check
// ensures that was explicitly configured via `all_principals_valid`.
let valid_principals = match self.valid_principals {
Some(principals) => principals,
None => return Err(Field::ValidPrincipals.invalid_error()),
};
let mut cert = Certificate {
nonce: self.nonce,
public_key: self.public_key,
serial: self.serial.unwrap_or_default(),
cert_type: self.cert_type.unwrap_or_default(),
key_id: self.key_id.unwrap_or_default(),
valid_principals,
valid_after: self.valid_after,
valid_before: self.valid_before,
critical_options: self.critical_options,
extensions: self.extensions,
reserved: Vec::new(),
comment: self.comment.unwrap_or_default(),
signature_key: signing_key.public_key(),
signature: Signature::placeholder(),
};
let mut tbs_cert = Vec::new();
cert.encode_tbs(&mut tbs_cert)?;
cert.signature = signing_key.try_sign(&tbs_cert)?;
#[cfg(debug_assertions)]
cert.validate_at(
cert.valid_after.into(),
&[cert.signature_key.fingerprint(Default::default())],
)?;
Ok(cert)
}
}

View File

@@ -0,0 +1,70 @@
//! OpenSSH certificate types.
use crate::{Error, Result};
use encoding::{Decode, Encode, Reader, Writer};
/// Types of OpenSSH certificates: user or host.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum CertType {
/// User certificate
User = 1,
/// Host certificate
Host = 2,
}
impl CertType {
/// Is this a host certificate?
pub fn is_host(self) -> bool {
self == CertType::Host
}
/// Is this a user certificate?
pub fn is_user(self) -> bool {
self == CertType::User
}
}
impl Default for CertType {
fn default() -> Self {
Self::User
}
}
impl Decode for CertType {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
u32::decode(reader)?.try_into()
}
}
impl Encode for CertType {
fn encoded_len(&self) -> encoding::Result<usize> {
Ok(4)
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
u32::from(*self).encode(writer)?;
Ok(())
}
}
impl From<CertType> for u32 {
fn from(cert_type: CertType) -> u32 {
cert_type as u32
}
}
impl TryFrom<u32> for CertType {
type Error = Error;
fn try_from(n: u32) -> Result<CertType> {
match n {
1 => Ok(CertType::User),
2 => Ok(CertType::Host),
_ => Err(Error::FormatEncoding),
}
}
}

88
vendor/ssh-key/src/certificate/field.rs vendored Normal file
View File

@@ -0,0 +1,88 @@
//! Certificate fields.
use crate::Error;
use core::fmt;
/// Certificate fields.
///
/// This type is primarily used by the certificate builder for reporting
/// errors in certificates.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Field {
/// Subject public key
PublicKey,
/// Nonce
Nonce,
/// Serial number
Serial,
/// Certificate type: user or host
Type,
/// Key ID
KeyId,
/// Valid principals
ValidPrincipals,
/// Valid after (Unix time)
ValidAfter,
/// Valid before (Unix time)
ValidBefore,
/// Critical options
CriticalOptions,
/// Extensions
Extensions,
/// Signature key (i.e. CA key)
SignatureKey,
/// Signature
Signature,
/// Comment
Comment,
}
impl Field {
/// Get the field name as a string
pub fn as_str(self) -> &'static str {
match self {
Self::PublicKey => "public key",
Self::Nonce => "nonce",
Self::Serial => "serial",
Self::Type => "type",
Self::KeyId => "key id",
Self::ValidPrincipals => "valid principals",
Self::ValidAfter => "valid after",
Self::ValidBefore => "valid before",
Self::CriticalOptions => "critical options",
Self::Extensions => "extensions",
Self::SignatureKey => "signature key",
Self::Signature => "signature",
Self::Comment => "comment",
}
}
/// Get an [`Error`] that this field is invalid.
pub fn invalid_error(self) -> Error {
Error::CertificateFieldInvalid(self)
}
}
impl AsRef<str> for Field {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for Field {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}

View File

@@ -0,0 +1,126 @@
//! OpenSSH certificate options used by critical options and extensions.
use crate::{Error, Result};
use alloc::{collections::BTreeMap, string::String, vec::Vec};
use core::{
cmp::Ordering,
ops::{Deref, DerefMut},
};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Key/value map type used for certificate's critical options and extensions.
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
pub struct OptionsMap(pub BTreeMap<String, String>);
impl OptionsMap {
/// Create a new [`OptionsMap`].
pub fn new() -> Self {
Self::default()
}
}
impl Deref for OptionsMap {
type Target = BTreeMap<String, String>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for OptionsMap {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Decode for OptionsMap {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
reader.read_prefixed(|reader| {
let mut entries = Vec::<(String, String)>::new();
while !reader.is_finished() {
let name = String::decode(reader)?;
let data = reader.read_prefixed(|reader| {
if reader.remaining_len() > 0 {
String::decode(reader)
} else {
Ok(String::default())
}
})?;
// Options must be lexically ordered by "name" if they appear in
// the sequence. Each named option may only appear once in a
// certificate.
if let Some((prev_name, _)) = entries.last() {
if prev_name.cmp(&name) != Ordering::Less {
return Err(Error::FormatEncoding);
}
}
entries.push((name, data));
}
Ok(OptionsMap::from_iter(entries))
})
}
}
impl Encode for OptionsMap {
fn encoded_len(&self) -> encoding::Result<usize> {
self.iter()
.try_fold(4, |acc, (name, data)| {
[
acc,
name.encoded_len()?,
if data.is_empty() {
4
} else {
data.encoded_len_prefixed()?
},
]
.checked_sum()
})
.map_err(Into::into)
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.encoded_len()?
.checked_sub(4)
.ok_or(encoding::Error::Length)?
.encode(writer)?;
for (name, data) in self.iter() {
name.encode(writer)?;
if data.is_empty() {
0usize.encode(writer)?;
} else {
data.encode_prefixed(writer)?
}
}
Ok(())
}
}
impl From<BTreeMap<String, String>> for OptionsMap {
fn from(map: BTreeMap<String, String>) -> OptionsMap {
OptionsMap(map)
}
}
impl From<OptionsMap> for BTreeMap<String, String> {
fn from(map: OptionsMap) -> BTreeMap<String, String> {
map.0
}
}
impl FromIterator<(String, String)> for OptionsMap {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = (String, String)>,
{
BTreeMap::from_iter(iter).into()
}
}

View File

@@ -0,0 +1,7 @@
//! Certificate signing key trait.
use crate::{public, Signature};
use signature::Signer;
#[cfg(doc)]
use super::Builder;

View File

@@ -0,0 +1,132 @@
//! Unix timestamps.
use crate::{Error, Result};
use core::fmt;
use core::fmt::Formatter;
use encoding::{Decode, Encode, Reader, Writer};
#[cfg(feature = "std")]
use std::time::{Duration, SystemTime, UNIX_EPOCH};
/// Maximum allowed value for a Unix timestamp.
pub const MAX_SECS: u64 = i64::MAX as u64;
/// Unix timestamps as used in OpenSSH certificates.
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub(super) struct UnixTime {
/// Number of seconds since the Unix epoch
secs: u64,
/// System time corresponding to this Unix timestamp
#[cfg(feature = "std")]
time: SystemTime,
}
impl UnixTime {
/// Create a new Unix timestamp.
///
/// `secs` is the number of seconds since the Unix epoch and must be less
/// than or equal to `i64::MAX`.
#[cfg(not(feature = "std"))]
pub fn new(secs: u64) -> Result<Self> {
if secs <= MAX_SECS {
Ok(Self { secs })
} else {
Err(Error::Time)
}
}
/// Create a new Unix timestamp.
///
/// This version requires `std` and ensures there's a valid `SystemTime`
/// representation with an infallible conversion (which also improves the
/// `Debug` output)
#[cfg(feature = "std")]
pub fn new(secs: u64) -> Result<Self> {
if secs > MAX_SECS {
return Err(Error::Time);
}
match UNIX_EPOCH.checked_add(Duration::from_secs(secs)) {
Some(time) => Ok(Self { secs, time }),
None => Err(Error::Time),
}
}
/// Get the current time as a Unix timestamp.
#[cfg(feature = "std")]
pub fn now() -> Result<Self> {
SystemTime::now().try_into()
}
}
impl Decode for UnixTime {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
u64::decode(reader)?.try_into()
}
}
impl Encode for UnixTime {
fn encoded_len(&self) -> encoding::Result<usize> {
self.secs.encoded_len()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.secs.encode(writer)?;
Ok(())
}
}
impl From<UnixTime> for u64 {
fn from(unix_time: UnixTime) -> u64 {
unix_time.secs
}
}
#[cfg(feature = "std")]
impl From<UnixTime> for SystemTime {
fn from(unix_time: UnixTime) -> SystemTime {
unix_time.time
}
}
impl TryFrom<u64> for UnixTime {
type Error = Error;
fn try_from(unix_secs: u64) -> Result<UnixTime> {
Self::new(unix_secs)
}
}
#[cfg(feature = "std")]
impl TryFrom<SystemTime> for UnixTime {
type Error = Error;
fn try_from(time: SystemTime) -> Result<UnixTime> {
Self::new(time.duration_since(UNIX_EPOCH)?.as_secs())
}
}
impl fmt::Debug for UnixTime {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.secs)
}
}
#[cfg(test)]
mod tests {
use super::{UnixTime, MAX_SECS};
use crate::Error;
#[test]
fn new_with_max_secs() {
assert!(UnixTime::new(MAX_SECS).is_ok());
}
#[test]
fn new_over_max_secs_returns_error() {
assert_eq!(UnixTime::new(MAX_SECS + 1), Err(Error::Time));
}
}

237
vendor/ssh-key/src/error.rs vendored Normal file
View File

@@ -0,0 +1,237 @@
//! Error types
use crate::Algorithm;
use core::fmt;
#[cfg(feature = "alloc")]
use crate::certificate;
/// Result type with `ssh-key`'s [`Error`] as the error type.
pub type Result<T> = core::result::Result<T, Error>;
/// Error type.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// Unknown algorithm.
///
/// This is returned when an algorithm is completely unknown to this crate.
AlgorithmUnknown,
/// Unsupported algorithm.
///
/// This is typically returned when an algorithm is recognized, but the
/// relevant crate features to support it haven't been enabled.
///
/// It may also be returned in the event an algorithm is inappropriate for
/// a given usage pattern or context.
AlgorithmUnsupported {
/// Algorithm identifier.
algorithm: Algorithm,
},
/// Certificate field is invalid or already set.
#[cfg(feature = "alloc")]
CertificateFieldInvalid(certificate::Field),
/// Certificate validation failed.
CertificateValidation,
/// Cryptographic errors.
Crypto,
/// Cannot perform operation on decrypted private key.
Decrypted,
/// ECDSA key encoding errors.
#[cfg(feature = "ecdsa")]
Ecdsa(sec1::Error),
/// Encoding errors.
Encoding(encoding::Error),
/// Cannot perform operation on encrypted private key.
Encrypted,
/// Other format encoding errors.
FormatEncoding,
/// Input/output errors.
#[cfg(feature = "std")]
Io(std::io::ErrorKind),
/// Namespace invalid.
Namespace,
/// Public key is incorrect.
PublicKey,
/// Invalid timestamp (e.g. in a certificate)
Time,
/// Unexpected trailing data at end of message.
TrailingData {
/// Number of bytes of remaining data at end of message.
remaining: usize,
},
/// Unsupported version.
Version {
/// Version number.
number: u32,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::AlgorithmUnknown => write!(f, "unknown algorithm"),
Error::AlgorithmUnsupported { algorithm } => {
write!(f, "unsupported algorithm: {algorithm}")
}
#[cfg(feature = "alloc")]
Error::CertificateFieldInvalid(field) => {
write!(f, "certificate field invalid: {field}")
}
Error::CertificateValidation => write!(f, "certificate validation failed"),
Error::Crypto => write!(f, "cryptographic error"),
Error::Decrypted => write!(f, "private key is already decrypted"),
#[cfg(feature = "ecdsa")]
Error::Ecdsa(err) => write!(f, "ECDSA encoding error: {err}"),
Error::Encoding(err) => write!(f, "{err}"),
Error::Encrypted => write!(f, "private key is encrypted"),
Error::FormatEncoding => write!(f, "format encoding error"),
#[cfg(feature = "std")]
Error::Io(err) => write!(f, "I/O error: {}", std::io::Error::from(*err)),
Error::Namespace => write!(f, "namespace invalid"),
Error::PublicKey => write!(f, "public key is incorrect"),
Error::Time => write!(f, "invalid time"),
Error::TrailingData { remaining } => write!(
f,
"unexpected trailing data at end of message ({remaining} bytes)",
),
Error::Version { number: version } => write!(f, "version unsupported: {version}"),
}
}
}
impl From<cipher::Error> for Error {
fn from(_: cipher::Error) -> Error {
Error::Crypto
}
}
impl From<core::array::TryFromSliceError> for Error {
fn from(_: core::array::TryFromSliceError) -> Error {
Error::Encoding(encoding::Error::Length)
}
}
impl From<core::str::Utf8Error> for Error {
fn from(err: core::str::Utf8Error) -> Error {
Error::Encoding(err.into())
}
}
impl From<encoding::Error> for Error {
fn from(err: encoding::Error) -> Error {
Error::Encoding(err)
}
}
impl From<encoding::LabelError> for Error {
fn from(err: encoding::LabelError) -> Error {
Error::Encoding(err.into())
}
}
impl From<encoding::base64::Error> for Error {
fn from(err: encoding::base64::Error) -> Error {
Error::Encoding(err.into())
}
}
impl From<encoding::pem::Error> for Error {
fn from(err: encoding::pem::Error) -> Error {
Error::Encoding(err.into())
}
}
#[cfg(not(feature = "std"))]
impl From<signature::Error> for Error {
fn from(_: signature::Error) -> Error {
Error::Crypto
}
}
#[cfg(feature = "std")]
impl From<signature::Error> for Error {
fn from(err: signature::Error) -> Error {
use std::error::Error as _;
err.source()
.and_then(|source| source.downcast_ref().cloned())
.unwrap_or(Error::Crypto)
}
}
#[cfg(not(feature = "std"))]
impl From<Error> for signature::Error {
fn from(_: Error) -> signature::Error {
signature::Error::new()
}
}
#[cfg(feature = "std")]
impl From<Error> for signature::Error {
fn from(err: Error) -> signature::Error {
signature::Error::from_source(err)
}
}
#[cfg(feature = "alloc")]
impl From<alloc::string::FromUtf8Error> for Error {
fn from(err: alloc::string::FromUtf8Error) -> Error {
Error::Encoding(err.into())
}
}
#[cfg(feature = "ecdsa")]
impl From<sec1::Error> for Error {
fn from(err: sec1::Error) -> Error {
Error::Ecdsa(err)
}
}
#[cfg(feature = "rsa")]
impl From<rsa::errors::Error> for Error {
fn from(_: rsa::errors::Error) -> Error {
Error::Crypto
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Io(err.kind())
}
}
#[cfg(feature = "std")]
impl From<std::time::SystemTimeError> for Error {
fn from(_: std::time::SystemTimeError) -> Error {
Error::Time
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#[cfg(feature = "ecdsa")]
Self::Ecdsa(err) => Some(err),
Self::Encoding(err) => Some(err),
_ => None,
}
}
}

221
vendor/ssh-key/src/fingerprint.rs vendored Normal file
View File

@@ -0,0 +1,221 @@
//! SSH public key fingerprints.
mod randomart;
use self::randomart::Randomart;
use crate::{public, Error, HashAlg, Result};
use core::{
fmt::{self, Display},
str::{self, FromStr},
};
use encoding::{
base64::{Base64Unpadded, Encoding},
Encode,
};
use sha2::{Digest, Sha256, Sha512};
/// Fingerprint encoding error message.
const FINGERPRINT_ERR_MSG: &str = "fingerprint encoding error";
#[cfg(feature = "alloc")]
use alloc::string::{String, ToString};
#[cfg(all(feature = "alloc", feature = "serde"))]
use serde::{de, ser, Deserialize, Serialize};
/// SSH public key fingerprints.
///
/// Fingerprints have an associated key fingerprint algorithm, i.e. a hash
/// function which is used to compute the fingerprint.
///
/// # Parsing/serializing fingerprint strings
///
/// The [`FromStr`] and [`Display`] impls on [`Fingerprint`] can be used to
/// parse and serialize fingerprints from the string format.
///
/// ### Example
///
/// ```text
/// SHA256:Nh0Me49Zh9fDw/VYUfq43IJmI1T+XrjiYONPND8GzaM
/// ```
///
/// # `serde` support
///
/// When the `serde` feature of this crate is enabled, this type receives impls
/// of [`Deserialize`][`serde::Deserialize`] and [`Serialize`][`serde::Serialize`].
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Fingerprint {
/// Fingerprints computed using SHA-256.
Sha256([u8; HashAlg::Sha256.digest_size()]),
/// Fingerprints computed using SHA-512.
Sha512([u8; HashAlg::Sha512.digest_size()]),
}
impl Fingerprint {
/// Size of a SHA-512 hash encoded as Base64.
const SHA512_BASE64_SIZE: usize = 86;
/// Create a fingerprint of the given public key data using the provided
/// hash algorithm.
pub fn new(algorithm: HashAlg, public_key: &public::KeyData) -> Self {
match algorithm {
HashAlg::Sha256 => {
let mut digest = Sha256::new();
public_key.encode(&mut digest).expect(FINGERPRINT_ERR_MSG);
Self::Sha256(digest.finalize().into())
}
HashAlg::Sha512 => {
let mut digest = Sha512::new();
public_key.encode(&mut digest).expect(FINGERPRINT_ERR_MSG);
Self::Sha512(digest.finalize().into())
}
}
}
/// Get the hash algorithm used for this fingerprint.
pub fn algorithm(self) -> HashAlg {
match self {
Self::Sha256(_) => HashAlg::Sha256,
Self::Sha512(_) => HashAlg::Sha512,
}
}
/// Get the name of the hash algorithm (upper case e.g. "SHA256").
pub fn prefix(self) -> &'static str {
match self.algorithm() {
HashAlg::Sha256 => "SHA256",
HashAlg::Sha512 => "SHA512",
}
}
/// Get the bracketed hash algorithm footer for use in "randomart".
fn footer(self) -> &'static str {
match self.algorithm() {
HashAlg::Sha256 => "[SHA256]",
HashAlg::Sha512 => "[SHA512]",
}
}
/// Get the raw digest output for the fingerprint as bytes.
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Sha256(bytes) => bytes.as_slice(),
Self::Sha512(bytes) => bytes.as_slice(),
}
}
/// Get the SHA-256 fingerprint, if this is one.
pub fn sha256(self) -> Option<[u8; HashAlg::Sha256.digest_size()]> {
match self {
Self::Sha256(fingerprint) => Some(fingerprint),
_ => None,
}
}
/// Get the SHA-512 fingerprint, if this is one.
pub fn sha512(self) -> Option<[u8; HashAlg::Sha512.digest_size()]> {
match self {
Self::Sha512(fingerprint) => Some(fingerprint),
_ => None,
}
}
/// Is this fingerprint SHA-256?
pub fn is_sha256(self) -> bool {
matches!(self, Self::Sha256(_))
}
/// Is this fingerprint SHA-512?
pub fn is_sha512(self) -> bool {
matches!(self, Self::Sha512(_))
}
/// Format "randomart" for this fingerprint using the provided formatter.
pub fn fmt_randomart(self, header: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Randomart::new(header, self).fmt(f)
}
/// Render "randomart" hash visualization for this fingerprint as a string.
///
/// ```text
/// +--[ED25519 256]--+
/// |o+oO==+ o.. |
/// |.o++Eo+o.. |
/// |. +.oO.o . . |
/// | . o..B.. . . |
/// | ...+ .S. o |
/// | .o. . . . . |
/// | o.. o |
/// | B . |
/// | .o* |
/// +----[SHA256]-----+
/// ```
#[cfg(feature = "alloc")]
pub fn to_randomart(self, header: &str) -> String {
Randomart::new(header, self).to_string()
}
}
impl AsRef<[u8]> for Fingerprint {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let prefix = self.prefix();
// Buffer size is the largest digest size of of any supported hash function
let mut buf = [0u8; Self::SHA512_BASE64_SIZE];
let base64 = Base64Unpadded::encode(self.as_bytes(), &mut buf).map_err(|_| fmt::Error)?;
write!(f, "{prefix}:{base64}")
}
}
impl FromStr for Fingerprint {
type Err = Error;
fn from_str(id: &str) -> Result<Self> {
let (alg_str, base64) = id.split_once(':').ok_or(Error::AlgorithmUnknown)?;
// Fingerprints use a special upper-case hash algorithm encoding.
let algorithm = match alg_str {
"SHA256" => HashAlg::Sha256,
"SHA512" => HashAlg::Sha512,
_ => return Err(Error::AlgorithmUnknown),
};
// Buffer size is the largest digest size of of any supported hash function
let mut buf = [0u8; HashAlg::Sha512.digest_size()];
let decoded_bytes = Base64Unpadded::decode(base64, &mut buf)?;
match algorithm {
HashAlg::Sha256 => Ok(Self::Sha256(decoded_bytes.try_into()?)),
HashAlg::Sha512 => Ok(Self::Sha512(decoded_bytes.try_into()?)),
}
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
impl<'de> Deserialize<'de> for Fingerprint {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
string.parse().map_err(de::Error::custom)
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
impl Serialize for Fingerprint {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
self.to_string().serialize(serializer)
}
}

View File

@@ -0,0 +1,113 @@
//! Support for the "drunken bishop" fingerprint algorithm, a.k.a. "randomart".
//!
//! The algorithm is described in the paper:
//!
//! "The drunken bishop: An analysis of the OpenSSH fingerprint visualization algorithm"
//!
//! <http://www.dirk-loss.de/sshvis/drunken_bishop.pdf>
use super::Fingerprint;
use core::fmt;
const WIDTH: usize = 17;
const HEIGHT: usize = 9;
const VALUES: &[u8; 17] = b" .o+=*BOX@%&#/^SE";
const NVALUES: u8 = VALUES.len() as u8 - 1;
type Field = [[u8; WIDTH]; HEIGHT];
/// "randomart" renderer.
pub(super) struct Randomart<'a> {
header: &'a str,
field: Field,
footer: &'static str,
}
impl<'a> Randomart<'a> {
/// Create new "randomart" from the given fingerprint.
// TODO: Remove this when the pipeline toolchain is updated beyond 1.69
#[allow(clippy::arithmetic_side_effects)]
pub(super) fn new(header: &'a str, fingerprint: Fingerprint) -> Self {
let mut field = Field::default();
let mut x = WIDTH / 2;
let mut y = HEIGHT / 2;
for mut byte in fingerprint.as_bytes().iter().copied() {
for _ in 0..4 {
if byte & 0x1 == 0 {
x = x.saturating_sub(1);
} else {
x = x.saturating_add(1);
}
if byte & 0x2 == 0 {
y = y.saturating_sub(1);
} else {
y = y.saturating_add(1);
}
x = x.min(WIDTH.saturating_sub(1));
y = y.min(HEIGHT.saturating_sub(1));
if field[y][x] < NVALUES - 2 {
field[y][x] = field[y][x].saturating_add(1);
}
byte >>= 2;
}
}
field[HEIGHT / 2][WIDTH / 2] = NVALUES - 1;
field[y][x] = NVALUES;
Self {
header,
field,
footer: fingerprint.footer(),
}
}
}
impl fmt::Display for Randomart<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "+{:-^width$}+", self.header, width = WIDTH)?;
for row in self.field {
write!(f, "|")?;
for c in row {
write!(f, "{}", VALUES[c as usize] as char)?;
}
writeln!(f, "|")?;
}
write!(f, "+{:-^width$}+", self.footer, width = WIDTH)
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::Fingerprint;
const EXAMPLE_FINGERPRINT: &str = "SHA256:UCUiLr7Pjs9wFFJMDByLgc3NrtdU344OgUM45wZPcIQ";
const EXAMPLE_RANDOMART: &str = "\
+--[ED25519 256]--+
|o+oO==+ o.. |
|.o++Eo+o.. |
|. +.oO.o . . |
| . o..B.. . . |
| ...+ .S. o |
| .o. . . . . |
| o.. o |
| B . |
| .o* |
+----[SHA256]-----+";
#[test]
fn generation() {
let fingerprint = EXAMPLE_FINGERPRINT.parse::<Fingerprint>().unwrap();
let randomart = fingerprint.to_randomart("[ED25519 256]");
assert_eq!(EXAMPLE_RANDOMART, randomart);
}
}

177
vendor/ssh-key/src/kdf.rs vendored Normal file
View File

@@ -0,0 +1,177 @@
//! Key Derivation Functions.
//!
//! These are used for deriving an encryption key from a password.
use crate::{Error, KdfAlg, Result};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "encryption")]
use {crate::Cipher, bcrypt_pbkdf::bcrypt_pbkdf, rand_core::CryptoRngCore, zeroize::Zeroizing};
/// Default number of rounds to use for bcrypt-pbkdf.
#[cfg(feature = "encryption")]
const DEFAULT_BCRYPT_ROUNDS: u32 = 16;
/// Default salt size. Matches OpenSSH.
#[cfg(feature = "encryption")]
const DEFAULT_SALT_SIZE: usize = 16;
/// Key Derivation Functions (KDF).
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Kdf {
/// No KDF.
None,
/// bcrypt-pbkdf options.
#[cfg(feature = "alloc")]
Bcrypt {
/// Salt
salt: Vec<u8>,
/// Rounds
rounds: u32,
},
}
impl Kdf {
/// Initialize KDF configuration for the given algorithm.
#[cfg(feature = "encryption")]
pub fn new(algorithm: KdfAlg, rng: &mut impl CryptoRngCore) -> Result<Self> {
let mut salt = vec![0u8; DEFAULT_SALT_SIZE];
rng.fill_bytes(&mut salt);
match algorithm {
KdfAlg::None => {
// Disallow explicit initialization with a `none` algorithm
Err(Error::AlgorithmUnknown)
}
KdfAlg::Bcrypt => Ok(Kdf::Bcrypt {
salt,
rounds: DEFAULT_BCRYPT_ROUNDS,
}),
}
}
/// Get the KDF algorithm.
pub fn algorithm(&self) -> KdfAlg {
match self {
Self::None => KdfAlg::None,
#[cfg(feature = "alloc")]
Self::Bcrypt { .. } => KdfAlg::Bcrypt,
}
}
/// Derive an encryption key from the given password.
#[cfg(feature = "encryption")]
pub fn derive(&self, password: impl AsRef<[u8]>, output: &mut [u8]) -> Result<()> {
match self {
Kdf::None => Err(Error::Decrypted),
Kdf::Bcrypt { salt, rounds } => {
bcrypt_pbkdf(password, salt, *rounds, output).map_err(|_| Error::Crypto)?;
Ok(())
}
}
}
/// Derive key and IV for the given [`Cipher`].
///
/// Returns two byte vectors containing the key and IV respectively.
#[cfg(feature = "encryption")]
pub fn derive_key_and_iv(
&self,
cipher: Cipher,
password: impl AsRef<[u8]>,
) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> {
let (key_size, iv_size) = cipher.key_and_iv_size().ok_or(Error::Decrypted)?;
let okm_size = key_size
.checked_add(iv_size)
.ok_or(encoding::Error::Length)?;
let mut okm = Zeroizing::new(vec![0u8; okm_size]);
self.derive(password, &mut okm)?;
let iv = okm.split_off(key_size);
Ok((okm, iv))
}
/// Is the KDF configured as `none`?
pub fn is_none(&self) -> bool {
self == &Self::None
}
/// Is the KDF configured as anything other than `none`?
pub fn is_some(&self) -> bool {
!self.is_none()
}
/// Is the KDF configured as `bcrypt` (i.e. bcrypt-pbkdf)?
#[cfg(feature = "alloc")]
pub fn is_bcrypt(&self) -> bool {
matches!(self, Self::Bcrypt { .. })
}
}
impl Default for Kdf {
fn default() -> Self {
Self::None
}
}
impl Decode for Kdf {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
match KdfAlg::decode(reader)? {
KdfAlg::None => {
if usize::decode(reader)? == 0 {
Ok(Self::None)
} else {
Err(Error::AlgorithmUnknown)
}
}
KdfAlg::Bcrypt => {
#[cfg(not(feature = "alloc"))]
return Err(Error::AlgorithmUnknown);
#[cfg(feature = "alloc")]
reader.read_prefixed(|reader| {
Ok(Self::Bcrypt {
salt: Vec::decode(reader)?,
rounds: u32::decode(reader)?,
})
})
}
}
}
}
impl Encode for Kdf {
fn encoded_len(&self) -> encoding::Result<usize> {
let kdfopts_prefixed_len = match self {
Self::None => 4,
#[cfg(feature = "alloc")]
Self::Bcrypt { salt, .. } => [12, salt.len()].checked_sum()?,
};
[self.algorithm().encoded_len()?, kdfopts_prefixed_len].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.algorithm().encode(writer)?;
match self {
Self::None => 0usize.encode(writer)?,
#[cfg(feature = "alloc")]
Self::Bcrypt { salt, rounds } => {
[8, salt.len()].checked_sum()?.encode(writer)?;
salt.encode(writer)?;
rounds.encode(writer)?
}
}
Ok(())
}
}

369
vendor/ssh-key/src/known_hosts.rs vendored Normal file
View File

@@ -0,0 +1,369 @@
//! Parser for `KnownHostsFile`-formatted data.
use crate::{Error, PublicKey, Result};
use core::str;
use encoding::base64::{Base64, Encoding};
use {
alloc::string::{String, ToString},
alloc::vec::Vec,
core::fmt,
};
#[cfg(feature = "std")]
use std::{fs, path::Path};
/// Character that begins a comment
const COMMENT_DELIMITER: char = '#';
/// The magic string prefix of a hashed hostname
const MAGIC_HASH_PREFIX: &str = "|1|";
/// Parser for `KnownHostsFile`-formatted data, typically found in
/// `~/.ssh/known_hosts`.
///
/// For a full description of the format, see:
/// <https://man7.org/linux/man-pages/man8/sshd.8.html#SSH_KNOWN_HOSTS_FILE_FORMAT>
///
/// Each line of the file consists of a single public key tied to one or more hosts.
/// Blank lines are ignored.
///
/// Public keys consist of the following space-separated fields:
///
/// ```text
/// marker, hostnames, keytype, base64-encoded key, comment
/// ```
///
/// - The marker field is optional, but if present begins with an `@`. Known markers are `@cert-authority`
/// and `@revoked`.
/// - The hostnames is a comma-separated list of patterns (with `*` and '?' as glob-style wildcards)
/// against which hosts are matched. If it begins with a `!` it is a negation of the pattern. If the
/// pattern starts with `[` and ends with `]`, it contains a hostname pattern and a port number separated
/// by a `:`. If it begins with `|1|`, the hostname is hashed. In that case, there can only be one exact
/// hostname and it can't also be negated (ie. `!|1|x|y` is not legal and you can't hash `*.example.org`).
/// - The keytype is `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`,
/// `ssh-ed25519`, `ssh-dss` or `ssh-rsa`
/// - The comment field is not used for anything (but may be convenient for the user to identify
/// the key).
pub struct KnownHosts<'a> {
/// Lines of the file being iterated over
lines: str::Lines<'a>,
}
impl<'a> KnownHosts<'a> {
/// Create a new parser for the given input buffer.
pub fn new(input: &'a str) -> Self {
Self {
lines: input.lines(),
}
}
/// Read a [`KnownHosts`] file from the filesystem, returning an
/// [`Entry`] vector on success.
#[cfg(feature = "std")]
pub fn read_file(path: impl AsRef<Path>) -> Result<Vec<Entry>> {
// TODO(tarcieri): permissions checks
let input = fs::read_to_string(path)?;
KnownHosts::new(&input).collect()
}
/// Get the next line, trimming any comments and trailing whitespace.
///
/// Ignores empty lines.
fn next_line_trimmed(&mut self) -> Option<&'a str> {
loop {
let mut line = self.lines.next()?;
// Strip comment if present
if let Some((l, _)) = line.split_once(COMMENT_DELIMITER) {
line = l;
}
// Trim trailing whitespace
line = line.trim_end();
if !line.is_empty() {
return Some(line);
}
}
}
}
impl Iterator for KnownHosts<'_> {
type Item = Result<Entry>;
fn next(&mut self) -> Option<Result<Entry>> {
self.next_line_trimmed().map(|line| line.parse())
}
}
/// Individual entry in an `known_hosts` file containing a single public key.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Entry {
/// Marker field, if present.
marker: Option<Marker>,
/// Host patterns
host_patterns: HostPatterns,
/// Public key
public_key: PublicKey,
}
impl Entry {
/// Get the marker for this entry, if present.
pub fn marker(&self) -> Option<&Marker> {
self.marker.as_ref()
}
/// Get the host pattern enumerator for this entry
pub fn host_patterns(&self) -> &HostPatterns {
&self.host_patterns
}
/// Get public key for this entry.
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
}
impl From<Entry> for Option<Marker> {
fn from(entry: Entry) -> Option<Marker> {
entry.marker
}
}
impl From<Entry> for HostPatterns {
fn from(entry: Entry) -> HostPatterns {
entry.host_patterns
}
}
impl From<Entry> for PublicKey {
fn from(entry: Entry) -> PublicKey {
entry.public_key
}
}
impl str::FromStr for Entry {
type Err = Error;
fn from_str(line: &str) -> Result<Self> {
// Unlike authorized_keys, in known_hosts it's pretty common
// to not include a key comment, so the number of spaces is
// not a reliable indicator of the fields in the line. Instead,
// the optional marker field starts with an @, so look for that
// and act accordingly.
let (marker, line) = if line.starts_with('@') {
let (marker_str, line) = line.split_once(' ').ok_or(Error::FormatEncoding)?;
(Some(marker_str.parse()?), line)
} else {
(None, line)
};
let (hosts_str, public_key_str) = line.split_once(' ').ok_or(Error::FormatEncoding)?;
let host_patterns = hosts_str.parse()?;
let public_key = public_key_str.parse()?;
Ok(Self {
marker,
host_patterns,
public_key,
})
}
}
impl ToString for Entry {
fn to_string(&self) -> String {
let mut s = String::new();
if let Some(marker) = &self.marker {
s.push_str(marker.as_str());
s.push(' ');
}
s.push_str(&self.host_patterns.to_string());
s.push(' ');
s.push_str(&self.public_key.to_string());
s
}
}
/// Markers associated with this host key entry.
///
/// There can only be one of these per host key entry.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Marker {
/// This host entry's public key is for a certificate authority's private key
CertAuthority,
/// This host entry's public key has been revoked, and should not be allowed to connect
/// regardless of any other entry.
Revoked,
}
impl Marker {
/// Get the string form of the marker
pub fn as_str(&self) -> &str {
match self {
Self::CertAuthority => "@cert-authority",
Self::Revoked => "@revoked",
}
}
}
impl AsRef<str> for Marker {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl str::FromStr for Marker {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Ok(match s {
"@cert-authority" => Marker::CertAuthority,
"@revoked" => Marker::Revoked,
_ => return Err(Error::FormatEncoding),
})
}
}
impl fmt::Display for Marker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
/// The host pattern(s) for this host entry.
///
/// The host patterns can either be a comma separated list of host patterns
/// (which may include glob patterns (`*` and `?`), negations (a `!` prefix),
/// or `pattern:port` pairs inside square brackets), or a single hashed
/// hostname prefixed with `|1|`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum HostPatterns {
/// A comma separated list of hostname patterns.
Patterns(Vec<String>),
/// A single hashed hostname
HashedName {
/// The salt used for the hash
salt: Vec<u8>,
/// An SHA-1 hash of the hostname along with the salt
hash: [u8; 20],
},
}
impl str::FromStr for HostPatterns {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if let Some(s) = s.strip_prefix(MAGIC_HASH_PREFIX) {
let mut hash = [0; 20];
let (salt, hash_str) = s.split_once('|').ok_or(Error::FormatEncoding)?;
let salt = Base64::decode_vec(salt)?;
Base64::decode(hash_str, &mut hash)?;
Ok(HostPatterns::HashedName { salt, hash })
} else if !s.is_empty() {
Ok(HostPatterns::Patterns(
s.split_terminator(',').map(str::to_string).collect(),
))
} else {
Err(Error::FormatEncoding)
}
}
}
impl ToString for HostPatterns {
fn to_string(&self) -> String {
match &self {
HostPatterns::Patterns(patterns) => patterns.join(","),
HostPatterns::HashedName { salt, hash } => {
let salt = Base64::encode_string(salt);
let hash = Base64::encode_string(hash);
format!("|1|{salt}|{hash}")
}
}
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use core::str::FromStr;
use super::Entry;
use super::HostPatterns;
use super::Marker;
#[test]
fn simple_markers() {
assert_eq!(Ok(Marker::CertAuthority), "@cert-authority".parse());
assert_eq!(Ok(Marker::Revoked), "@revoked".parse());
assert!(Marker::from_str("@gibberish").is_err());
}
#[test]
fn empty_host_patterns() {
assert!(HostPatterns::from_str("").is_err());
}
// Note: The sshd man page has this completely incomprehensible 'example known_hosts entry':
// closenet,...,192.0.2.53 1024 37 159...93 closenet.example.net
// I'm not sure how this one is supposed to work or what it means.
#[test]
fn single_host_pattern() {
assert_eq!(
Ok(HostPatterns::Patterns(vec!["cvs.example.net".to_string()])),
"cvs.example.net".parse()
);
}
#[test]
fn multiple_host_patterns() {
assert_eq!(
Ok(HostPatterns::Patterns(vec![
"cvs.example.net".to_string(),
"!test.example.???".to_string(),
"[*.example.net]:999".to_string(),
])),
"cvs.example.net,!test.example.???,[*.example.net]:999".parse()
);
}
#[test]
fn single_hashed_host() {
assert_eq!(
Ok(HostPatterns::HashedName {
salt: vec![
37, 242, 147, 116, 24, 123, 172, 214, 215, 145, 80, 16, 9, 26, 120, 57, 10, 15,
126, 98
],
hash: [
81, 33, 2, 175, 116, 150, 127, 82, 84, 62, 201, 172, 228, 10, 159, 15, 148, 31,
198, 67
],
}),
"|1|JfKTdBh7rNbXkVAQCRp4OQoPfmI=|USECr3SWf1JUPsms5AqfD5QfxkM=".parse()
);
}
#[test]
fn full_line_hashed() {
let line = "@revoked |1|lcY/In3lsGnkJikLENb0DM70B/I=|Qs4e9Nr7mM6avuEv02fw2uFnwQo= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9dG4kjRhQTtWTVzd2t27+t0DEHBPW7iOD23TUiYLio comment";
let entry = Entry::from_str(line).expect("Valid entry");
assert_eq!(entry.marker(), Some(&Marker::Revoked));
assert_eq!(
entry.host_patterns(),
&HostPatterns::HashedName {
salt: vec![
149, 198, 63, 34, 125, 229, 176, 105, 228, 38, 41, 11, 16, 214, 244, 12, 206,
244, 7, 242
],
hash: [
66, 206, 30, 244, 218, 251, 152, 206, 154, 190, 225, 47, 211, 103, 240, 218,
225, 103, 193, 10
],
}
);
// key parsing is tested elsewhere
}
}

190
vendor/ssh-key/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,190 @@
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(
clippy::arithmetic_side_effects,
clippy::mod_module_files,
clippy::panic,
clippy::panic_in_result_fn,
clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
//! ## Usage
//!
//! The main types provided by this library are:
//!
//! - [`Certificate`]: OpenSSH certificates
//! - [`Fingerprint`]: public key fingerprints (i.e. hashes)
//! - [`PrivateKey`]: SSH private keys (i.e. digital signature keys)
//! - [`PublicKey`]: SSH public keys (i.e. signature verification keys)
//! - [`SshSig`]: signatures with SSH keys ala `ssh-keygen -Y sign`/`ssh-keygen -Y verify`
//!
//! ### Parsing OpenSSH Public Keys
//!
//! OpenSSH-formatted public keys have the form:
//!
//! ```text
//! <algorithm id> <base64 data> <comment>
//! ```
//!
//! #### Example
//!
#![cfg_attr(feature = "std", doc = "```")]
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ssh_key::PublicKey;
//!
//! let encoded_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com";
//! let public_key = PublicKey::from_openssh(encoded_key)?;
//!
//! // Key attributes
//! assert_eq!(public_key.algorithm(), ssh_key::Algorithm::Ed25519);
//! assert_eq!(public_key.comment(), "user@example.com");
//!
//! // Key data: in this example an Ed25519 key
//! if let Some(ed25519_public_key) = public_key.key_data().ed25519() {
//! assert_eq!(
//! ed25519_public_key.as_ref(),
//! [
//! 0xb3, 0x3e, 0xae, 0xf3, 0x7e, 0xa2, 0xdf, 0x7c, 0xaa, 0x1, 0xd, 0xef, 0xde, 0xa3,
//! 0x4e, 0x24, 0x1f, 0x65, 0xf1, 0xb5, 0x29, 0xa4, 0xf4, 0x3e, 0xd1, 0x43, 0x27, 0xf5,
//! 0xc5, 0x4a, 0xab, 0x62
//! ].as_ref()
//! );
//! }
//! # Ok(())
//! # }
//! ```
//!
//! ### Parsing OpenSSH Private Keys
//!
//! *NOTE: for more private key usage examples, see the [`private`] module.*
//!
//! OpenSSH-formatted private keys are PEM-encoded and begin with the following:
//!
//! ```text
//! -----BEGIN OPENSSH PRIVATE KEY-----
//! ```
//!
//! #### Example
//!
#![cfg_attr(feature = "std", doc = " ```")]
#![cfg_attr(not(feature = "std"), doc = " ```ignore")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ssh_key::PrivateKey;
//!
//! // WARNING: don't actually hardcode private keys in source code!!!
//! let encoded_key = r#"
//! -----BEGIN OPENSSH PRIVATE KEY-----
//! b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
//! QyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJgAIAxdACAM
//! XQAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYg
//! AAAEC2BsIi0QwW2uFscKTUUXNHLsYX4FxlaSDSblbAj7WR7bM+rvN+ot98qgEN796jTiQf
//! ZfG1KaT0PtFDJ/XFSqtiAAAAEHVzZXJAZXhhbXBsZS5jb20BAgMEBQ==
//! -----END OPENSSH PRIVATE KEY-----
//! "#;
//!
//! let private_key = PrivateKey::from_openssh(encoded_key)?;
//!
//! // Key attributes
//! assert_eq!(private_key.algorithm(), ssh_key::Algorithm::Ed25519);
//! assert_eq!(private_key.comment(), "user@example.com");
//!
//! // Key data: in this example an Ed25519 key
//! if let Some(ed25519_keypair) = private_key.key_data().ed25519() {
//! assert_eq!(
//! ed25519_keypair.public.as_ref(),
//! [
//! 0xb3, 0x3e, 0xae, 0xf3, 0x7e, 0xa2, 0xdf, 0x7c, 0xaa, 0x1, 0xd, 0xef, 0xde, 0xa3,
//! 0x4e, 0x24, 0x1f, 0x65, 0xf1, 0xb5, 0x29, 0xa4, 0xf4, 0x3e, 0xd1, 0x43, 0x27, 0xf5,
//! 0xc5, 0x4a, 0xab, 0x62
//! ].as_ref()
//! );
//!
//! assert_eq!(
//! ed25519_keypair.private.as_ref(),
//! [
//! 0xb6, 0x6, 0xc2, 0x22, 0xd1, 0xc, 0x16, 0xda, 0xe1, 0x6c, 0x70, 0xa4, 0xd4, 0x51,
//! 0x73, 0x47, 0x2e, 0xc6, 0x17, 0xe0, 0x5c, 0x65, 0x69, 0x20, 0xd2, 0x6e, 0x56, 0xc0,
//! 0x8f, 0xb5, 0x91, 0xed
//! ].as_ref()
//! )
//! }
//! # Ok(())
//! # }
//! ```
//!
//! ## `serde` support
//!
//! When the `serde` feature of this crate is enabled, the [`Certificate`],
//! [`Fingerprint`], and [`PublicKey`] types receive impls of `serde`'s
//! [`Deserialize`][`serde::Deserialize`] and [`Serialize`][`serde::Serialize`]
//! traits.
//!
//! Serializing/deserializing [`PrivateKey`] using `serde` is presently
//! unsupported.
#[cfg(feature = "alloc")]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub mod authorized_keys;
pub mod private;
pub mod public;
#[cfg(feature = "alloc")]
pub mod certificate;
#[cfg(feature = "alloc")]
pub mod known_hosts;
mod algorithm;
mod error;
mod fingerprint;
mod kdf;
#[cfg(feature = "alloc")]
mod mpint;
#[cfg(feature = "alloc")]
mod signature;
#[cfg(feature = "alloc")]
mod sshsig;
pub use crate::{
algorithm::{Algorithm, EcdsaCurve, HashAlg, KdfAlg},
authorized_keys::AuthorizedKeys,
error::{Error, Result},
fingerprint::Fingerprint,
kdf::Kdf,
private::PrivateKey,
public::PublicKey,
};
pub use cipher::Cipher;
pub use encoding::LineEnding;
pub use sha2;
#[cfg(feature = "alloc")]
pub use crate::{
algorithm::AlgorithmName,
certificate::Certificate,
known_hosts::KnownHosts,
mpint::Mpint,
signature::{Signature, SigningKey},
sshsig::SshSig,
};
#[cfg(feature = "ecdsa")]
pub use sec1;
#[cfg(feature = "rand_core")]
pub use rand_core;

297
vendor/ssh-key/src/mpint.rs vendored Normal file
View File

@@ -0,0 +1,297 @@
//! Multiple precision integer
use crate::{Error, Result};
use alloc::{boxed::Box, vec::Vec};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;
#[cfg(any(feature = "dsa", feature = "rsa"))]
use zeroize::Zeroizing;
/// Multiple precision integer, a.k.a. "mpint".
///
/// This type is used for representing the big integer components of
/// DSA and RSA keys.
///
/// Described in [RFC4251 § 5](https://datatracker.ietf.org/doc/html/rfc4251#section-5):
///
/// > Represents multiple precision integers in two's complement format,
/// > stored as a string, 8 bits per byte, MSB first. Negative numbers
/// > have the value 1 as the most significant bit of the first byte of
/// > the data partition. If the most significant bit would be set for
/// > a positive number, the number MUST be preceded by a zero byte.
/// > Unnecessary leading bytes with the value 0 or 255 MUST NOT be
/// > included. The value zero MUST be stored as a string with zero
/// > bytes of data.
/// >
/// > By convention, a number that is used in modular computations in
/// > Z_n SHOULD be represented in the range 0 <= x < n.
///
/// ## Examples
///
/// | value (hex) | representation (hex) |
/// |-----------------|----------------------|
/// | 0 | `00 00 00 00`
/// | 9a378f9b2e332a7 | `00 00 00 08 09 a3 78 f9 b2 e3 32 a7`
/// | 80 | `00 00 00 02 00 80`
/// |-1234 | `00 00 00 02 ed cc`
/// | -deadbeef | `00 00 00 05 ff 21 52 41 11`
#[derive(Clone, PartialOrd, Ord)]
pub struct Mpint {
/// Inner big endian-serialized integer value
inner: Box<[u8]>,
}
impl Mpint {
/// Create a new multiple precision integer from the given
/// big endian-encoded byte slice.
///
/// Note that this method expects a leading zero on positive integers whose
/// MSB is set, but does *NOT* expect a 4-byte length prefix.
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
bytes.try_into()
}
/// Create a new multiple precision integer from the given big endian
/// encoded byte slice representing a positive integer.
///
/// The input may begin with leading zeros, which will be stripped when
/// converted to [`Mpint`] encoding.
pub fn from_positive_bytes(mut bytes: &[u8]) -> Result<Self> {
let mut inner = Vec::with_capacity(bytes.len());
while bytes.first().copied() == Some(0) {
bytes = &bytes[1..];
}
match bytes.first().copied() {
Some(n) if n >= 0x80 => inner.push(0),
_ => (),
}
inner.extend_from_slice(bytes);
inner.into_boxed_slice().try_into()
}
/// Get the big integer data encoded as big endian bytes.
///
/// This slice will contain a leading zero if the value is positive but the
/// MSB is also set. Use [`Mpint::as_positive_bytes`] to ensure the number
/// is positive and strip the leading zero byte if it exists.
pub fn as_bytes(&self) -> &[u8] {
&self.inner
}
/// Get the bytes of a positive integer.
///
/// # Returns
/// - `Some(bytes)` if the number is positive. The leading zero byte will be stripped.
/// - `None` if the value is negative
pub fn as_positive_bytes(&self) -> Option<&[u8]> {
match self.as_bytes() {
[0x00, rest @ ..] => Some(rest),
[byte, ..] if *byte < 0x80 => Some(self.as_bytes()),
_ => None,
}
}
}
impl AsRef<[u8]> for Mpint {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl ConstantTimeEq for Mpint {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
impl Eq for Mpint {}
impl PartialEq for Mpint {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for Mpint {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
Vec::decode(reader)?.into_boxed_slice().try_into()
}
}
impl Encode for Mpint {
fn encoded_len(&self) -> encoding::Result<usize> {
[4, self.as_bytes().len()].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.as_bytes().encode(writer)?;
Ok(())
}
}
impl TryFrom<&[u8]> for Mpint {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
Vec::from(bytes).into_boxed_slice().try_into()
}
}
impl TryFrom<Box<[u8]>> for Mpint {
type Error = Error;
fn try_from(bytes: Box<[u8]>) -> Result<Self> {
match &*bytes {
// Unnecessary leading 0
[0x00] => Err(Error::FormatEncoding),
// Unnecessary leading 0
[0x00, n, ..] if *n < 0x80 => Err(Error::FormatEncoding),
_ => Ok(Self { inner: bytes }),
}
}
}
impl Zeroize for Mpint {
fn zeroize(&mut self) {
self.inner.zeroize();
}
}
impl fmt::Debug for Mpint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Mpint({self:X})")
}
}
impl fmt::Display for Mpint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:X}")
}
}
impl fmt::LowerHex for Mpint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_bytes() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for Mpint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_bytes() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
#[cfg(any(feature = "dsa", feature = "rsa"))]
impl TryFrom<bigint::BigUint> for Mpint {
type Error = Error;
fn try_from(uint: bigint::BigUint) -> Result<Mpint> {
Mpint::try_from(&uint)
}
}
#[cfg(any(feature = "dsa", feature = "rsa"))]
impl TryFrom<&bigint::BigUint> for Mpint {
type Error = Error;
fn try_from(uint: &bigint::BigUint) -> Result<Mpint> {
let bytes = Zeroizing::new(uint.to_bytes_be());
Mpint::from_positive_bytes(bytes.as_slice())
}
}
#[cfg(any(feature = "dsa", feature = "rsa"))]
impl TryFrom<Mpint> for bigint::BigUint {
type Error = Error;
fn try_from(mpint: Mpint) -> Result<bigint::BigUint> {
bigint::BigUint::try_from(&mpint)
}
}
#[cfg(any(feature = "dsa", feature = "rsa"))]
impl TryFrom<&Mpint> for bigint::BigUint {
type Error = Error;
fn try_from(mpint: &Mpint) -> Result<bigint::BigUint> {
mpint
.as_positive_bytes()
.map(bigint::BigUint::from_bytes_be)
.ok_or(Error::Crypto)
}
}
#[cfg(test)]
mod tests {
use super::Mpint;
use hex_literal::hex;
#[test]
fn decode_0() {
let n = Mpint::from_bytes(b"").unwrap();
assert_eq!(b"", n.as_bytes())
}
#[test]
fn reject_extra_leading_zeroes() {
assert!(Mpint::from_bytes(&hex!("00")).is_err());
assert!(Mpint::from_bytes(&hex!("00 00")).is_err());
assert!(Mpint::from_bytes(&hex!("00 01")).is_err());
}
#[test]
fn decode_9a378f9b2e332a7() {
assert!(Mpint::from_bytes(&hex!("09 a3 78 f9 b2 e3 32 a7")).is_ok());
}
#[test]
fn decode_80() {
let n = Mpint::from_bytes(&hex!("00 80")).unwrap();
// Leading zero stripped
assert_eq!(&hex!("80"), n.as_positive_bytes().unwrap())
}
#[test]
fn from_positive_bytes_strips_leading_zeroes() {
assert_eq!(
Mpint::from_positive_bytes(&hex!("00")).unwrap().as_ref(),
b""
);
assert_eq!(
Mpint::from_positive_bytes(&hex!("00 00")).unwrap().as_ref(),
b""
);
assert_eq!(
Mpint::from_positive_bytes(&hex!("00 01")).unwrap().as_ref(),
b"\x01"
);
}
// TODO(tarcieri): drop support for negative numbers?
#[test]
fn decode_neg_1234() {
let n = Mpint::from_bytes(&hex!("ed cc")).unwrap();
assert!(n.as_positive_bytes().is_none());
}
// TODO(tarcieri): drop support for negative numbers?
#[test]
fn decode_neg_deadbeef() {
let n = Mpint::from_bytes(&hex!("ff 21 52 41 11")).unwrap();
assert!(n.as_positive_bytes().is_none());
}
}

895
vendor/ssh-key/src/private.rs vendored Normal file
View File

@@ -0,0 +1,895 @@
//! SSH private key support.
//!
//! Support for decoding SSH private keys (i.e. digital signature keys)
//! from the OpenSSH file format:
//!
//! <https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD>
//!
//! ## Decrypting encrypted private keys
//!
//! When the `encryption` feature of this crate is enabled, it's possible to
//! decrypt keys which have been encrypted under a password:
//!
#![cfg_attr(all(feature = "encryption", feature = "std"), doc = " ```")]
#![cfg_attr(not(all(feature = "encryption", feature = "std")), doc = " ```ignore")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ssh_key::PrivateKey;
//!
//! // WARNING: don't actually hardcode private keys in source code!!!
//! let encoded_key = r#"
//! -----BEGIN OPENSSH PRIVATE KEY-----
//! b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBKH96ujW
//! umB6/WnTNPjTeaAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN
//! 796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoFzvbvyFMhAiwBOXF0mhUUacPUCMZXivG2up2c
//! hEnAw1b6BLRPyWbY5cC2n9ggD4ivJ1zSts6sBgjyiXQAReyrP35myYvT/OIB/NpwZM/xIJ
//! N7MHSUzlkX4adBrga3f7GS4uv4ChOoxC4XsE5HsxtGsq1X8jzqLlZTmOcxkcEneYQexrUc
//! bQP0o+gL5aKK8cQgiIlXeDbRjqhc4+h4EF6lY=
//! -----END OPENSSH PRIVATE KEY-----
//! "#;
//!
//! let encrypted_key = PrivateKey::from_openssh(encoded_key)?;
//! assert!(encrypted_key.is_encrypted());
//!
//! // WARNING: don't hardcode passwords, and this one's bad anyway
//! let password = "hunter42";
//!
//! let decrypted_key = encrypted_key.decrypt(password)?;
//! assert!(!decrypted_key.is_encrypted());
//! # Ok(())
//! # }
//! ```
//!
//! ## Encrypting plaintext private keys
//!
//! When the `encryption` feature of this crate is enabled, it's possible to
//! encrypt plaintext private keys under a provided password.
//!
//! The example below also requires enabling this crate's `getrandom` feature.
//!
#![cfg_attr(
all(
feature = "ed25519",
feature = "encryption",
feature = "getrandom",
feature = "std"
),
doc = " ```"
)]
#![cfg_attr(
not(all(
feature = "ed25519",
feature = "encryption",
feature = "getrandom",
feature = "std"
)),
doc = " ```ignore"
)]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ssh_key::{Algorithm, PrivateKey, rand_core::OsRng};
//!
//! // Generate a random key
//! let unencrypted_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
//!
//! // WARNING: don't hardcode passwords, and this one's bad anyway
//! let password = "hunter42";
//!
//! let encrypted_key = unencrypted_key.encrypt(&mut OsRng, password)?;
//! assert!(encrypted_key.is_encrypted());
//! # Ok(())
//! # }
//! ```
//!
//! ## Generating random keys
//!
//! This crate supports generation of random keys using algorithm-specific
//! backends gated on cargo features.
//!
//! The examples below require enabling this crate's `getrandom` feature as
//! well as the crate feature identified in backticks in the title of each
//! example.
//!
#![cfg_attr(
all(feature = "ed25519", feature = "getrandom", feature = "std"),
doc = " ```"
)]
#![cfg_attr(
not(all(feature = "ed25519", feature = "getrandom", feature = "std")),
doc = " ```ignore"
)]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ssh_key::{Algorithm, PrivateKey, rand_core::OsRng};
//!
//! let private_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
//! # Ok(())
//! # }
//! ```
#[cfg(feature = "alloc")]
mod dsa;
#[cfg(feature = "ecdsa")]
mod ecdsa;
mod ed25519;
mod keypair;
#[cfg(feature = "alloc")]
mod opaque;
#[cfg(feature = "alloc")]
mod rsa;
#[cfg(feature = "alloc")]
mod sk;
pub use self::{
ed25519::{Ed25519Keypair, Ed25519PrivateKey},
keypair::KeypairData,
};
#[cfg(feature = "alloc")]
pub use crate::{
private::{
dsa::{DsaKeypair, DsaPrivateKey},
opaque::{OpaqueKeypair, OpaqueKeypairBytes, OpaquePrivateKeyBytes},
rsa::{RsaKeypair, RsaPrivateKey},
sk::SkEd25519,
},
SshSig,
};
#[cfg(feature = "ecdsa")]
pub use self::ecdsa::{EcdsaKeypair, EcdsaPrivateKey};
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
pub use self::sk::SkEcdsaSha2NistP256;
use crate::{public, Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result};
use cipher::Tag;
use core::str;
use encoding::{
pem::{LineEnding, PemLabel},
CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer,
};
use subtle::{Choice, ConstantTimeEq};
#[cfg(feature = "alloc")]
use {
alloc::{string::String, vec::Vec},
zeroize::Zeroizing,
};
#[cfg(feature = "rand_core")]
use rand_core::CryptoRngCore;
#[cfg(feature = "std")]
use std::{fs, path::Path};
#[cfg(all(unix, feature = "std"))]
use std::{io::Write, os::unix::fs::OpenOptionsExt};
/// Error message for infallible conversions (used by `expect`)
const CONVERSION_ERROR_MSG: &str = "SSH private key conversion error";
/// Default key size to use for RSA keys in bits.
#[cfg(all(feature = "rand_core", feature = "rsa"))]
const DEFAULT_RSA_KEY_SIZE: usize = 4096;
/// Maximum supported block size.
///
/// This is the block size used by e.g. AES.
const MAX_BLOCK_SIZE: usize = 16;
/// Padding bytes to use.
const PADDING_BYTES: [u8; MAX_BLOCK_SIZE - 1] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
/// Unix file permissions for SSH private keys.
#[cfg(all(unix, feature = "std"))]
const UNIX_FILE_PERMISSIONS: u32 = 0o600;
/// SSH private key.
#[derive(Clone, Debug)]
pub struct PrivateKey {
/// Cipher algorithm.
cipher: Cipher,
/// KDF options.
kdf: Kdf,
/// "Checkint" value used to verify successful decryption.
checkint: Option<u32>,
/// Public key.
public_key: PublicKey,
/// Private keypair data.
key_data: KeypairData,
/// Authentication tag for authenticated encryption modes.
auth_tag: Option<Tag>,
}
impl PrivateKey {
/// Magic string used to identify keys in this format.
const AUTH_MAGIC: &'static [u8] = b"openssh-key-v1\0";
/// Create a new unencrypted private key with the given keypair data and comment.
///
/// On `no_std` platforms, use `PrivateKey::from(key_data)` instead.
#[cfg(feature = "alloc")]
pub fn new(key_data: KeypairData, comment: impl Into<String>) -> Result<Self> {
if key_data.is_encrypted() {
return Err(Error::Encrypted);
}
let mut private_key = Self::try_from(key_data)?;
private_key.public_key.comment = comment.into();
Ok(private_key)
}
/// Parse an OpenSSH-formatted PEM private key.
///
/// OpenSSH-formatted private keys begin with the following:
///
/// ```text
/// -----BEGIN OPENSSH PRIVATE KEY-----
/// ```
pub fn from_openssh(pem: impl AsRef<[u8]>) -> Result<Self> {
Self::decode_pem(pem)
}
/// Parse a raw binary SSH private key.
pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
let reader = &mut bytes;
let private_key = Self::decode(reader)?;
Ok(reader.finish(private_key)?)
}
/// Encode OpenSSH-formatted (PEM) private key.
pub fn encode_openssh<'o>(
&self,
line_ending: LineEnding,
out: &'o mut [u8],
) -> Result<&'o str> {
Ok(self.encode_pem(line_ending, out)?)
}
/// Encode an OpenSSH-formatted PEM private key, allocating a
/// self-zeroizing [`String`] for the result.
#[cfg(feature = "alloc")]
pub fn to_openssh(&self, line_ending: LineEnding) -> Result<Zeroizing<String>> {
Ok(self.encode_pem_string(line_ending).map(Zeroizing::new)?)
}
/// Serialize SSH private key as raw bytes.
#[cfg(feature = "alloc")]
pub fn to_bytes(&self) -> Result<Zeroizing<Vec<u8>>> {
let mut private_key_bytes = Vec::with_capacity(self.encoded_len()?);
self.encode(&mut private_key_bytes)?;
Ok(Zeroizing::new(private_key_bytes))
}
/// Sign the given message using this private key, returning an [`SshSig`].
///
/// These signatures can be produced using `ssh-keygen -Y sign`. They're
/// encoded as PEM and begin with the following:
///
/// ```text
/// -----BEGIN SSH SIGNATURE-----
/// ```
///
/// See [PROTOCOL.sshsig] for more information.
///
/// # Usage
///
/// See also: [`PublicKey::verify`].
///
#[cfg_attr(feature = "ed25519", doc = "```")]
#[cfg_attr(not(feature = "ed25519"), doc = "```ignore")]
/// # fn main() -> Result<(), ssh_key::Error> {
/// use ssh_key::{PrivateKey, HashAlg, SshSig};
///
/// // Message to be signed.
/// let message = b"testing";
///
/// // Example domain/namespace used for the message.
/// let namespace = "example";
///
/// // Private key to use when computing the signature.
/// // WARNING: don't actually hardcode private keys in source code!!!
/// let encoded_private_key = r#"
/// -----BEGIN OPENSSH PRIVATE KEY-----
/// b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
/// QyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJgAIAxdACAM
/// XQAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYg
/// AAAEC2BsIi0QwW2uFscKTUUXNHLsYX4FxlaSDSblbAj7WR7bM+rvN+ot98qgEN796jTiQf
/// ZfG1KaT0PtFDJ/XFSqtiAAAAEHVzZXJAZXhhbXBsZS5jb20BAgMEBQ==
/// -----END OPENSSH PRIVATE KEY-----
/// "#;
///
/// let private_key = encoded_private_key.parse::<PrivateKey>()?;
/// let signature = private_key.sign(namespace, HashAlg::default(), message)?;
/// // assert!(private_key.public_key().verify(namespace, message, &signature).is_ok());
/// # Ok(())
/// # }
/// ```
///
/// [PROTOCOL.sshsig]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.sshsig?annotate=HEAD
#[cfg(feature = "alloc")]
pub fn sign(&self, namespace: &str, hash_alg: HashAlg, msg: &[u8]) -> Result<SshSig> {
SshSig::sign(self, namespace, hash_alg, msg)
}
/// Read private key from an OpenSSH-formatted PEM file.
#[cfg(feature = "std")]
pub fn read_openssh_file(path: &Path) -> Result<Self> {
// TODO(tarcieri): verify file permissions match `UNIX_FILE_PERMISSIONS`
let pem = Zeroizing::new(fs::read_to_string(path)?);
Self::from_openssh(&*pem)
}
/// Write private key as an OpenSSH-formatted PEM file.
#[cfg(feature = "std")]
pub fn write_openssh_file(&self, path: &Path, line_ending: LineEnding) -> Result<()> {
let pem = self.to_openssh(line_ending)?;
#[cfg(not(unix))]
fs::write(path, pem.as_bytes())?;
#[cfg(unix)]
fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.mode(UNIX_FILE_PERMISSIONS)
.open(path)
.and_then(|mut file| file.write_all(pem.as_bytes()))?;
Ok(())
}
/// Attempt to decrypt an encrypted private key using the provided
/// password to derive an encryption key.
///
/// Returns [`Error::Decrypted`] if the private key is already decrypted.
#[cfg(feature = "encryption")]
pub fn decrypt(&self, password: impl AsRef<[u8]>) -> Result<Self> {
let (key, iv) = self.kdf.derive_key_and_iv(self.cipher, password)?;
let ciphertext = self.key_data.encrypted().ok_or(Error::Decrypted)?;
let mut buffer = Zeroizing::new(ciphertext.to_vec());
self.cipher.decrypt(&key, &iv, &mut buffer, self.auth_tag)?;
Self::decode_privatekey_comment_pair(
&mut &**buffer,
self.public_key.key_data.clone(),
self.cipher.block_size(),
)
}
/// Encrypt an unencrypted private key using the provided password to
/// derive an encryption key.
///
/// Uses the following algorithms:
/// - Cipher: [`Cipher::Aes256Ctr`]
/// - KDF: [`Kdf::Bcrypt`] (i.e. `bcrypt-pbkdf`)
///
/// Returns [`Error::Encrypted`] if the private key is already encrypted.
#[cfg(feature = "encryption")]
pub fn encrypt(
&self,
rng: &mut impl CryptoRngCore,
password: impl AsRef<[u8]>,
) -> Result<Self> {
self.encrypt_with_cipher(rng, Cipher::Aes256Ctr, password)
}
/// Encrypt an unencrypted private key using the provided password to
/// derive an encryption key for the provided [`Cipher`].
///
/// Returns [`Error::Encrypted`] if the private key is already encrypted.
#[cfg(feature = "encryption")]
pub fn encrypt_with_cipher(
&self,
rng: &mut impl CryptoRngCore,
cipher: Cipher,
password: impl AsRef<[u8]>,
) -> Result<Self> {
let checkint = rng.next_u32();
self.encrypt_with(
cipher,
Kdf::new(Default::default(), rng)?,
checkint,
password,
)
}
/// Encrypt an unencrypted private key using the provided cipher and KDF
/// configuration.
///
/// Returns [`Error::Encrypted`] if the private key is already encrypted.
#[cfg(feature = "encryption")]
pub fn encrypt_with(
&self,
cipher: Cipher,
kdf: Kdf,
checkint: u32,
password: impl AsRef<[u8]>,
) -> Result<Self> {
if self.is_encrypted() {
return Err(Error::Encrypted);
}
let (key_bytes, iv_bytes) = kdf.derive_key_and_iv(cipher, password)?;
let msg_len = self.encoded_privatekey_comment_pair_len(cipher)?;
let mut out = Vec::with_capacity(msg_len);
// Encode and encrypt private key
self.encode_privatekey_comment_pair(&mut out, cipher, checkint)?;
let auth_tag = cipher.encrypt(&key_bytes, &iv_bytes, out.as_mut_slice())?;
Ok(Self {
cipher,
kdf,
checkint: None,
public_key: self.public_key.key_data.clone().into(),
key_data: KeypairData::Encrypted(out),
auth_tag,
})
}
/// Get the digital signature [`Algorithm`] used by this key.
pub fn algorithm(&self) -> Algorithm {
self.public_key.algorithm()
}
/// Comment on the key (e.g. email address).
pub fn comment(&self) -> &str {
self.public_key.comment()
}
/// Cipher algorithm (a.k.a. `ciphername`).
pub fn cipher(&self) -> Cipher {
self.cipher
}
/// Compute key fingerprint.
///
/// Use [`Default::default()`] to use the default hash function (SHA-256).
pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
self.public_key.fingerprint(hash_alg)
}
/// Is this key encrypted?
pub fn is_encrypted(&self) -> bool {
let ret = self.key_data.is_encrypted();
debug_assert_eq!(ret, self.cipher.is_some());
ret
}
/// Key Derivation Function (KDF) used to encrypt this key.
///
/// Returns [`Kdf::None`] if this key is not encrypted.
pub fn kdf(&self) -> &Kdf {
&self.kdf
}
/// Keypair data.
pub fn key_data(&self) -> &KeypairData {
&self.key_data
}
/// Get the [`PublicKey`] which corresponds to this private key.
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
/// Generate a random key which uses the given algorithm.
///
/// # Returns
/// - `Error::AlgorithmUnknown` if the algorithm is unsupported.
#[cfg(feature = "rand_core")]
#[allow(unreachable_code, unused_variables)]
pub fn random(rng: &mut impl CryptoRngCore, algorithm: Algorithm) -> Result<Self> {
let checkint = rng.next_u32();
let key_data = match algorithm {
#[cfg(feature = "dsa")]
Algorithm::Dsa => KeypairData::from(DsaKeypair::random(rng)?),
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
Algorithm::Ecdsa { curve } => KeypairData::from(EcdsaKeypair::random(rng, curve)?),
#[cfg(feature = "ed25519")]
Algorithm::Ed25519 => KeypairData::from(Ed25519Keypair::random(rng)),
#[cfg(feature = "rsa")]
Algorithm::Rsa { .. } => {
KeypairData::from(RsaKeypair::random(rng, DEFAULT_RSA_KEY_SIZE)?)
}
_ => return Err(Error::AlgorithmUnknown),
};
let public_key = public::KeyData::try_from(&key_data)?;
Ok(Self {
cipher: Cipher::None,
kdf: Kdf::None,
checkint: Some(checkint),
public_key: public_key.into(),
key_data,
auth_tag: None,
})
}
/// Set the comment on the key.
#[cfg(feature = "alloc")]
pub fn set_comment(&mut self, comment: impl Into<String>) {
self.public_key.set_comment(comment);
}
/// Decode [`KeypairData`] along with its associated checkints and comment,
/// storing the comment in the provided public key on success.
///
/// This method also checks padding for validity and ensures that the
/// decoded private key matches the provided public key.
///
/// For private key format specification, see OpenSSH [PROTOCOL.key] § 3:
///
/// ```text
/// uint32 checkint
/// uint32 checkint
/// byte[] privatekey1
/// string comment1
/// byte[] privatekey2
/// string comment2
/// ...
/// string privatekeyN
/// string commentN
/// char 1
/// char 2
/// char 3
/// ...
/// char padlen % 255
/// ```
///
/// [PROTOCOL.key]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
fn decode_privatekey_comment_pair(
reader: &mut impl Reader,
public_key: public::KeyData,
block_size: usize,
) -> Result<Self> {
debug_assert!(block_size <= MAX_BLOCK_SIZE);
// Ensure input data is padding-aligned
if reader.remaining_len().checked_rem(block_size) != Some(0) {
return Err(encoding::Error::Length.into());
}
let checkint1 = u32::decode(reader)?;
let checkint2 = u32::decode(reader)?;
if checkint1 != checkint2 {
return Err(Error::Crypto);
}
let key_data = KeypairData::decode(reader)?;
// Ensure public key matches private key
if public_key != public::KeyData::try_from(&key_data)? {
return Err(Error::PublicKey);
}
let mut public_key = PublicKey::from(public_key);
public_key.decode_comment(reader)?;
let padding_len = reader.remaining_len();
if padding_len >= block_size {
return Err(encoding::Error::Length.into());
}
if padding_len != 0 {
let mut padding = [0u8; MAX_BLOCK_SIZE];
reader.read(&mut padding[..padding_len])?;
if PADDING_BYTES[..padding_len] != padding[..padding_len] {
return Err(Error::FormatEncoding);
}
}
if !reader.is_finished() {
return Err(Error::TrailingData {
remaining: reader.remaining_len(),
});
}
Ok(Self {
cipher: Cipher::None,
kdf: Kdf::None,
checkint: Some(checkint1),
public_key,
key_data,
auth_tag: None,
})
}
/// Encode [`KeypairData`] along with its associated checkints, comment,
/// and padding.
fn encode_privatekey_comment_pair(
&self,
writer: &mut impl Writer,
cipher: Cipher,
checkint: u32,
) -> encoding::Result<()> {
let unpadded_len = self.unpadded_privatekey_comment_pair_len()?;
let padding_len = cipher.padding_len(unpadded_len);
checkint.encode(writer)?;
checkint.encode(writer)?;
self.key_data.encode(writer)?;
self.comment().encode(writer)?;
writer.write(&PADDING_BYTES[..padding_len])?;
Ok(())
}
/// Get the length of this private key when encoded with the given comment
/// and padded using the padding size for the given cipher.
fn encoded_privatekey_comment_pair_len(&self, cipher: Cipher) -> encoding::Result<usize> {
let len = self.unpadded_privatekey_comment_pair_len()?;
[len, cipher.padding_len(len)].checked_sum()
}
/// Get the length of this private key when encoded with the given comment.
///
/// This length is just the checkints, private key data, and comment sans
/// any padding.
fn unpadded_privatekey_comment_pair_len(&self) -> encoding::Result<usize> {
// This method is intended for use with unencrypted keys only
debug_assert!(!self.is_encrypted(), "called on encrypted key");
[
8, // 2 x uint32 checkints,
self.key_data.encoded_len()?,
self.comment().encoded_len()?,
]
.checked_sum()
}
}
impl ConstantTimeEq for PrivateKey {
fn ct_eq(&self, other: &Self) -> Choice {
// Constant-time with respect to private key data
self.key_data.ct_eq(&other.key_data)
& Choice::from(
(self.cipher == other.cipher
&& self.kdf == other.kdf
&& self.public_key == other.public_key) as u8,
)
}
}
impl Eq for PrivateKey {}
impl PartialEq for PrivateKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for PrivateKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let mut auth_magic = [0u8; Self::AUTH_MAGIC.len()];
reader.read(&mut auth_magic)?;
if auth_magic != Self::AUTH_MAGIC {
return Err(Error::FormatEncoding);
}
let cipher = Cipher::decode(reader)?;
let kdf = Kdf::decode(reader)?;
let nkeys = usize::decode(reader)?;
// TODO(tarcieri): support more than one key?
if nkeys != 1 {
return Err(encoding::Error::Length.into());
}
let public_key = reader.read_prefixed(public::KeyData::decode)?;
// Handle encrypted private key
#[cfg(not(feature = "alloc"))]
if cipher.is_some() {
return Err(Error::Encrypted);
}
#[cfg(feature = "alloc")]
if cipher.is_some() {
let ciphertext = Vec::decode(reader)?;
// Ensure ciphertext is padded to the expected length
if ciphertext.len().checked_rem(cipher.block_size()) != Some(0) {
return Err(Error::Crypto);
}
let auth_tag = if cipher.has_tag() {
let mut tag = Tag::default();
reader.read(&mut tag)?;
Some(tag)
} else {
None
};
if !reader.is_finished() {
return Err(Error::TrailingData {
remaining: reader.remaining_len(),
});
}
return Ok(Self {
cipher,
kdf,
checkint: None,
public_key: public_key.into(),
key_data: KeypairData::Encrypted(ciphertext),
auth_tag,
});
}
// Processing unencrypted key. No KDF should be set.
if kdf.is_some() {
return Err(Error::Crypto);
}
reader.read_prefixed(|reader| {
Self::decode_privatekey_comment_pair(reader, public_key, cipher.block_size())
})
}
}
impl Encode for PrivateKey {
fn encoded_len(&self) -> encoding::Result<usize> {
let private_key_len = if self.is_encrypted() {
self.key_data.encoded_len_prefixed()?
} else {
[4, self.encoded_privatekey_comment_pair_len(Cipher::None)?].checked_sum()?
};
[
Self::AUTH_MAGIC.len(),
self.cipher.encoded_len()?,
self.kdf.encoded_len()?,
4, // number of keys (uint32)
self.public_key.key_data().encoded_len_prefixed()?,
private_key_len,
self.auth_tag.map(|tag| tag.len()).unwrap_or(0),
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
writer.write(Self::AUTH_MAGIC)?;
self.cipher.encode(writer)?;
self.kdf.encode(writer)?;
// TODO(tarcieri): support for encoding more than one private key
1usize.encode(writer)?;
// Encode public key
self.public_key.key_data().encode_prefixed(writer)?;
// Encode private key
if self.is_encrypted() {
self.key_data.encode_prefixed(writer)?;
if let Some(tag) = &self.auth_tag {
writer.write(tag)?;
}
} else {
self.encoded_privatekey_comment_pair_len(Cipher::None)?
.encode(writer)?;
let checkint = self.checkint.unwrap_or_else(|| self.key_data.checkint());
self.encode_privatekey_comment_pair(writer, Cipher::None, checkint)?;
}
Ok(())
}
}
impl From<PrivateKey> for PublicKey {
fn from(private_key: PrivateKey) -> PublicKey {
private_key.public_key
}
}
impl From<&PrivateKey> for PublicKey {
fn from(private_key: &PrivateKey) -> PublicKey {
private_key.public_key.clone()
}
}
impl From<PrivateKey> for public::KeyData {
fn from(private_key: PrivateKey) -> public::KeyData {
private_key.public_key.key_data
}
}
impl From<&PrivateKey> for public::KeyData {
fn from(private_key: &PrivateKey) -> public::KeyData {
private_key.public_key.key_data.clone()
}
}
#[cfg(feature = "alloc")]
impl From<DsaKeypair> for PrivateKey {
fn from(keypair: DsaKeypair) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
#[cfg(feature = "ecdsa")]
impl From<EcdsaKeypair> for PrivateKey {
fn from(keypair: EcdsaKeypair) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
impl From<Ed25519Keypair> for PrivateKey {
fn from(keypair: Ed25519Keypair) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
#[cfg(feature = "alloc")]
impl From<RsaKeypair> for PrivateKey {
fn from(keypair: RsaKeypair) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
impl From<SkEcdsaSha2NistP256> for PrivateKey {
fn from(keypair: SkEcdsaSha2NistP256) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
#[cfg(feature = "alloc")]
impl From<SkEd25519> for PrivateKey {
fn from(keypair: SkEd25519) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
impl TryFrom<KeypairData> for PrivateKey {
type Error = Error;
fn try_from(key_data: KeypairData) -> Result<PrivateKey> {
let public_key = public::KeyData::try_from(&key_data)?;
Ok(Self {
cipher: Cipher::None,
kdf: Kdf::None,
checkint: None,
public_key: public_key.into(),
key_data,
auth_tag: None,
})
}
}
impl PemLabel for PrivateKey {
const PEM_LABEL: &'static str = "OPENSSH PRIVATE KEY";
}
impl str::FromStr for PrivateKey {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_openssh(s)
}
}

245
vendor/ssh-key/src/private/dsa.rs vendored Normal file
View File

@@ -0,0 +1,245 @@
//! Digital Signature Algorithm (DSA) private keys.
use crate::{public::DsaPublicKey, Error, Mpint, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;
#[cfg(all(feature = "dsa", feature = "rand_core"))]
use rand_core::CryptoRngCore;
/// Digital Signature Algorithm (DSA) private key.
///
/// Uniformly random integer `x`, such that `0 < x < q`, i.e. `x` is in the
/// range `[1, q1]`.
///
/// Described in [FIPS 186-4 § 4.1](https://csrc.nist.gov/publications/detail/fips/186/4/final).
#[derive(Clone)]
pub struct DsaPrivateKey {
/// Integer representing a DSA private key.
inner: Mpint,
}
impl DsaPrivateKey {
/// Get the serialized private key as bytes.
pub fn as_bytes(&self) -> &[u8] {
self.inner.as_bytes()
}
/// Get the inner [`Mpint`].
pub fn as_mpint(&self) -> &Mpint {
&self.inner
}
}
impl AsRef<[u8]> for DsaPrivateKey {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl ConstantTimeEq for DsaPrivateKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.inner.ct_eq(&other.inner)
}
}
impl Eq for DsaPrivateKey {}
impl PartialEq for DsaPrivateKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for DsaPrivateKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
Ok(Self {
inner: Mpint::decode(reader)?,
})
}
}
impl Encode for DsaPrivateKey {
fn encoded_len(&self) -> encoding::Result<usize> {
self.inner.encoded_len()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.inner.encode(writer)
}
}
impl fmt::Debug for DsaPrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DsaPrivateKey").finish_non_exhaustive()
}
}
impl Drop for DsaPrivateKey {
fn drop(&mut self) {
self.inner.zeroize();
}
}
#[cfg(feature = "dsa")]
impl TryFrom<DsaPrivateKey> for dsa::BigUint {
type Error = Error;
fn try_from(key: DsaPrivateKey) -> Result<dsa::BigUint> {
dsa::BigUint::try_from(&key.inner)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&DsaPrivateKey> for dsa::BigUint {
type Error = Error;
fn try_from(key: &DsaPrivateKey) -> Result<dsa::BigUint> {
dsa::BigUint::try_from(&key.inner)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<dsa::SigningKey> for DsaPrivateKey {
type Error = Error;
fn try_from(key: dsa::SigningKey) -> Result<DsaPrivateKey> {
DsaPrivateKey::try_from(&key)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&dsa::SigningKey> for DsaPrivateKey {
type Error = Error;
fn try_from(key: &dsa::SigningKey) -> Result<DsaPrivateKey> {
Ok(DsaPrivateKey {
inner: key.x().try_into()?,
})
}
}
/// Digital Signature Algorithm (DSA) private/public keypair.
#[derive(Clone)]
pub struct DsaKeypair {
/// Public key.
pub public: DsaPublicKey,
/// Private key.
pub private: DsaPrivateKey,
}
impl DsaKeypair {
/// Key size.
#[cfg(all(feature = "dsa", feature = "rand_core"))]
#[allow(deprecated)]
pub(crate) const KEY_SIZE: dsa::KeySize = dsa::KeySize::DSA_1024_160;
/// Generate a random DSA private key.
#[cfg(all(feature = "dsa", feature = "rand_core"))]
pub fn random(rng: &mut impl CryptoRngCore) -> Result<Self> {
let components = dsa::Components::generate(rng, Self::KEY_SIZE);
dsa::SigningKey::generate(rng, components).try_into()
}
}
impl ConstantTimeEq for DsaKeypair {
fn ct_eq(&self, other: &Self) -> Choice {
Choice::from((self.public == other.public) as u8) & self.private.ct_eq(&other.private)
}
}
impl PartialEq for DsaKeypair {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Eq for DsaKeypair {}
impl Decode for DsaKeypair {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let public = DsaPublicKey::decode(reader)?;
let private = DsaPrivateKey::decode(reader)?;
Ok(DsaKeypair { public, private })
}
}
impl Encode for DsaKeypair {
fn encoded_len(&self) -> encoding::Result<usize> {
[self.public.encoded_len()?, self.private.encoded_len()?].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.encode(writer)?;
self.private.encode(writer)
}
}
impl From<DsaKeypair> for DsaPublicKey {
fn from(keypair: DsaKeypair) -> DsaPublicKey {
keypair.public
}
}
impl From<&DsaKeypair> for DsaPublicKey {
fn from(keypair: &DsaKeypair) -> DsaPublicKey {
keypair.public.clone()
}
}
impl fmt::Debug for DsaKeypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DsaKeypair")
.field("public", &self.public)
.finish_non_exhaustive()
}
}
#[cfg(feature = "dsa")]
impl TryFrom<DsaKeypair> for dsa::SigningKey {
type Error = Error;
fn try_from(key: DsaKeypair) -> Result<dsa::SigningKey> {
dsa::SigningKey::try_from(&key)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&DsaKeypair> for dsa::SigningKey {
type Error = Error;
fn try_from(key: &DsaKeypair) -> Result<dsa::SigningKey> {
Ok(dsa::SigningKey::from_components(
dsa::VerifyingKey::try_from(&key.public)?,
dsa::BigUint::try_from(&key.private)?,
)?)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<dsa::SigningKey> for DsaKeypair {
type Error = Error;
fn try_from(key: dsa::SigningKey) -> Result<DsaKeypair> {
DsaKeypair::try_from(&key)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&dsa::SigningKey> for DsaKeypair {
type Error = Error;
fn try_from(key: &dsa::SigningKey) -> Result<DsaKeypair> {
Ok(DsaKeypair {
private: key.try_into()?,
public: key.verifying_key().try_into()?,
})
}
}

348
vendor/ssh-key/src/private/ecdsa.rs vendored Normal file
View File

@@ -0,0 +1,348 @@
//! Elliptic Curve Digital Signature Algorithm (ECDSA) private keys.
use crate::{public::EcdsaPublicKey, Algorithm, EcdsaCurve, Error, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use sec1::consts::{U32, U48, U66};
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;
#[cfg(feature = "rand_core")]
use rand_core::CryptoRngCore;
/// Elliptic Curve Digital Signature Algorithm (ECDSA) private key.
#[derive(Clone)]
pub struct EcdsaPrivateKey<const SIZE: usize> {
/// Byte array containing serialized big endian private scalar.
bytes: [u8; SIZE],
}
impl<const SIZE: usize> EcdsaPrivateKey<SIZE> {
/// Borrow the inner byte array as a slice.
pub fn as_slice(&self) -> &[u8] {
self.bytes.as_ref()
}
/// Convert to the inner byte array.
pub fn into_bytes(self) -> [u8; SIZE] {
self.bytes
}
/// Does this private key need to be prefixed with a leading zero?
fn needs_leading_zero(&self) -> bool {
self.bytes[0] >= 0x80
}
}
impl<const SIZE: usize> Decode for EcdsaPrivateKey<SIZE> {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
reader.read_prefixed(|reader| {
if reader.remaining_len() == SIZE.checked_add(1).ok_or(encoding::Error::Length)? {
// Strip leading zero
// TODO(tarcieri): make sure leading zero was necessary
if u8::decode(reader)? != 0 {
return Err(Error::FormatEncoding);
}
}
let mut bytes = [0u8; SIZE];
reader.read(&mut bytes)?;
Ok(Self { bytes })
})
}
}
impl<const SIZE: usize> Encode for EcdsaPrivateKey<SIZE> {
fn encoded_len(&self) -> encoding::Result<usize> {
[4, self.needs_leading_zero().into(), SIZE].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
[self.needs_leading_zero().into(), SIZE]
.checked_sum()?
.encode(writer)?;
if self.needs_leading_zero() {
writer.write(&[0])?;
}
writer.write(&self.bytes)?;
Ok(())
}
}
impl<const SIZE: usize> AsRef<[u8; SIZE]> for EcdsaPrivateKey<SIZE> {
fn as_ref(&self) -> &[u8; SIZE] {
&self.bytes
}
}
impl<const SIZE: usize> ConstantTimeEq for EcdsaPrivateKey<SIZE> {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
impl<const SIZE: usize> PartialEq for EcdsaPrivateKey<SIZE> {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl<const SIZE: usize> Eq for EcdsaPrivateKey<SIZE> {}
impl<const SIZE: usize> fmt::Debug for EcdsaPrivateKey<SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EcdsaPrivateKey").finish_non_exhaustive()
}
}
impl<const SIZE: usize> fmt::LowerHex for EcdsaPrivateKey<SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl<const SIZE: usize> fmt::UpperHex for EcdsaPrivateKey<SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
impl<const SIZE: usize> Drop for EcdsaPrivateKey<SIZE> {
fn drop(&mut self) {
self.bytes.zeroize();
}
}
#[cfg(feature = "p256")]
impl From<p256::SecretKey> for EcdsaPrivateKey<32> {
fn from(sk: p256::SecretKey) -> EcdsaPrivateKey<32> {
EcdsaPrivateKey {
bytes: sk.to_bytes().into(),
}
}
}
#[cfg(feature = "p384")]
impl From<p384::SecretKey> for EcdsaPrivateKey<48> {
fn from(sk: p384::SecretKey) -> EcdsaPrivateKey<48> {
EcdsaPrivateKey {
bytes: sk.to_bytes().into(),
}
}
}
#[cfg(feature = "p521")]
impl From<p521::SecretKey> for EcdsaPrivateKey<66> {
fn from(sk: p521::SecretKey) -> EcdsaPrivateKey<66> {
// TODO(tarcieri): clean this up when migrating to hybrid-array
let mut bytes = [0u8; 66];
bytes.copy_from_slice(&sk.to_bytes());
EcdsaPrivateKey { bytes }
}
}
/// Elliptic Curve Digital Signature Algorithm (ECDSA) private/public keypair.
#[derive(Clone, Debug)]
pub enum EcdsaKeypair {
/// NIST P-256 ECDSA keypair.
NistP256 {
/// Public key.
public: sec1::EncodedPoint<U32>,
/// Private key.
private: EcdsaPrivateKey<32>,
},
/// NIST P-384 ECDSA keypair.
NistP384 {
/// Public key.
public: sec1::EncodedPoint<U48>,
/// Private key.
private: EcdsaPrivateKey<48>,
},
/// NIST P-521 ECDSA keypair.
NistP521 {
/// Public key.
public: sec1::EncodedPoint<U66>,
/// Private key.
private: EcdsaPrivateKey<66>,
},
}
impl EcdsaKeypair {
/// Generate a random ECDSA private key.
#[cfg(feature = "rand_core")]
#[allow(unused_variables)]
pub fn random(rng: &mut impl CryptoRngCore, curve: EcdsaCurve) -> Result<Self> {
match curve {
#[cfg(feature = "p256")]
EcdsaCurve::NistP256 => {
let private = p256::SecretKey::random(rng);
let public = private.public_key();
Ok(EcdsaKeypair::NistP256 {
private: private.into(),
public: public.into(),
})
}
#[cfg(feature = "p384")]
EcdsaCurve::NistP384 => {
let private = p384::SecretKey::random(rng);
let public = private.public_key();
Ok(EcdsaKeypair::NistP384 {
private: private.into(),
public: public.into(),
})
}
#[cfg(feature = "p521")]
EcdsaCurve::NistP521 => {
let private = p521::SecretKey::random(rng);
let public = private.public_key();
Ok(EcdsaKeypair::NistP521 {
private: private.into(),
public: public.into(),
})
}
#[cfg(not(all(feature = "p256", feature = "p384", feature = "p521")))]
_ => Err(Error::AlgorithmUnknown),
}
}
/// Get the [`Algorithm`] for this public key type.
pub fn algorithm(&self) -> Algorithm {
Algorithm::Ecdsa {
curve: self.curve(),
}
}
/// Get the [`EcdsaCurve`] for this key.
pub fn curve(&self) -> EcdsaCurve {
match self {
Self::NistP256 { .. } => EcdsaCurve::NistP256,
Self::NistP384 { .. } => EcdsaCurve::NistP384,
Self::NistP521 { .. } => EcdsaCurve::NistP521,
}
}
/// Get the bytes representing the public key.
pub fn public_key_bytes(&self) -> &[u8] {
match self {
Self::NistP256 { public, .. } => public.as_ref(),
Self::NistP384 { public, .. } => public.as_ref(),
Self::NistP521 { public, .. } => public.as_ref(),
}
}
/// Get the bytes representing the private key.
pub fn private_key_bytes(&self) -> &[u8] {
match self {
Self::NistP256 { private, .. } => private.as_ref(),
Self::NistP384 { private, .. } => private.as_ref(),
Self::NistP521 { private, .. } => private.as_ref(),
}
}
}
impl ConstantTimeEq for EcdsaKeypair {
fn ct_eq(&self, other: &Self) -> Choice {
let public_eq =
Choice::from((EcdsaPublicKey::from(self) == EcdsaPublicKey::from(other)) as u8);
let private_key_a = match self {
Self::NistP256 { private, .. } => private.as_slice(),
Self::NistP384 { private, .. } => private.as_slice(),
Self::NistP521 { private, .. } => private.as_slice(),
};
let private_key_b = match other {
Self::NistP256 { private, .. } => private.as_slice(),
Self::NistP384 { private, .. } => private.as_slice(),
Self::NistP521 { private, .. } => private.as_slice(),
};
public_eq & private_key_a.ct_eq(private_key_b)
}
}
impl Eq for EcdsaKeypair {}
impl PartialEq for EcdsaKeypair {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for EcdsaKeypair {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
match EcdsaPublicKey::decode(reader)? {
EcdsaPublicKey::NistP256(public) => {
let private = EcdsaPrivateKey::<32>::decode(reader)?;
Ok(Self::NistP256 { public, private })
}
EcdsaPublicKey::NistP384(public) => {
let private = EcdsaPrivateKey::<48>::decode(reader)?;
Ok(Self::NistP384 { public, private })
}
EcdsaPublicKey::NistP521(public) => {
let private = EcdsaPrivateKey::<66>::decode(reader)?;
Ok(Self::NistP521 { public, private })
}
}
}
}
impl Encode for EcdsaKeypair {
fn encoded_len(&self) -> encoding::Result<usize> {
let public_len = EcdsaPublicKey::from(self).encoded_len()?;
let private_len = match self {
Self::NistP256 { private, .. } => private.encoded_len()?,
Self::NistP384 { private, .. } => private.encoded_len()?,
Self::NistP521 { private, .. } => private.encoded_len()?,
};
[public_len, private_len].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
EcdsaPublicKey::from(self).encode(writer)?;
match self {
Self::NistP256 { private, .. } => private.encode(writer)?,
Self::NistP384 { private, .. } => private.encode(writer)?,
Self::NistP521 { private, .. } => private.encode(writer)?,
}
Ok(())
}
}
impl From<EcdsaKeypair> for EcdsaPublicKey {
fn from(keypair: EcdsaKeypair) -> EcdsaPublicKey {
EcdsaPublicKey::from(&keypair)
}
}
impl From<&EcdsaKeypair> for EcdsaPublicKey {
fn from(keypair: &EcdsaKeypair) -> EcdsaPublicKey {
match keypair {
EcdsaKeypair::NistP256 { public, .. } => EcdsaPublicKey::NistP256(*public),
EcdsaKeypair::NistP384 { public, .. } => EcdsaPublicKey::NistP384(*public),
EcdsaKeypair::NistP521 { public, .. } => EcdsaPublicKey::NistP521(*public),
}
}
}

322
vendor/ssh-key/src/private/ed25519.rs vendored Normal file
View File

@@ -0,0 +1,322 @@
//! Ed25519 private keys.
//!
//! Edwards Digital Signature Algorithm (EdDSA) over Curve25519.
use crate::{public::Ed25519PublicKey, Error, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "rand_core")]
use rand_core::CryptoRngCore;
/// Ed25519 private key.
// TODO(tarcieri): use `ed25519::PrivateKey`? (doesn't exist yet)
#[derive(Clone)]
pub struct Ed25519PrivateKey([u8; Self::BYTE_SIZE]);
impl Ed25519PrivateKey {
/// Size of an Ed25519 private key in bytes.
pub const BYTE_SIZE: usize = 32;
/// Generate a random Ed25519 private key.
#[cfg(feature = "rand_core")]
pub fn random(rng: &mut impl CryptoRngCore) -> Self {
let mut key_bytes = [0u8; Self::BYTE_SIZE];
rng.fill_bytes(&mut key_bytes);
Self(key_bytes)
}
/// Parse Ed25519 private key from bytes.
pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Self {
Self(*bytes)
}
/// Convert to the inner byte array.
pub fn to_bytes(&self) -> [u8; Self::BYTE_SIZE] {
self.0
}
}
impl AsRef<[u8; Self::BYTE_SIZE]> for Ed25519PrivateKey {
fn as_ref(&self) -> &[u8; Self::BYTE_SIZE] {
&self.0
}
}
impl ConstantTimeEq for Ed25519PrivateKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
impl Eq for Ed25519PrivateKey {}
impl PartialEq for Ed25519PrivateKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl TryFrom<&[u8]> for Ed25519PrivateKey {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
Ok(Ed25519PrivateKey::from_bytes(bytes.try_into()?))
}
}
impl fmt::Debug for Ed25519PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ed25519PrivateKey").finish_non_exhaustive()
}
}
impl fmt::LowerHex for Ed25519PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for Ed25519PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
impl Drop for Ed25519PrivateKey {
fn drop(&mut self) {
self.0.zeroize();
}
}
#[cfg(feature = "ed25519")]
impl From<Ed25519PrivateKey> for ed25519_dalek::SigningKey {
fn from(key: Ed25519PrivateKey) -> ed25519_dalek::SigningKey {
ed25519_dalek::SigningKey::from(&key)
}
}
#[cfg(feature = "ed25519")]
impl From<&Ed25519PrivateKey> for ed25519_dalek::SigningKey {
fn from(key: &Ed25519PrivateKey) -> ed25519_dalek::SigningKey {
ed25519_dalek::SigningKey::from_bytes(key.as_ref())
}
}
#[cfg(feature = "ed25519")]
impl From<ed25519_dalek::SigningKey> for Ed25519PrivateKey {
fn from(key: ed25519_dalek::SigningKey) -> Ed25519PrivateKey {
Ed25519PrivateKey::from(&key)
}
}
#[cfg(feature = "ed25519")]
impl From<&ed25519_dalek::SigningKey> for Ed25519PrivateKey {
fn from(key: &ed25519_dalek::SigningKey) -> Ed25519PrivateKey {
Ed25519PrivateKey(key.to_bytes())
}
}
#[cfg(feature = "ed25519")]
impl From<Ed25519PrivateKey> for Ed25519PublicKey {
fn from(private: Ed25519PrivateKey) -> Ed25519PublicKey {
Ed25519PublicKey::from(&private)
}
}
#[cfg(feature = "ed25519")]
impl From<&Ed25519PrivateKey> for Ed25519PublicKey {
fn from(private: &Ed25519PrivateKey) -> Ed25519PublicKey {
ed25519_dalek::SigningKey::from(private)
.verifying_key()
.into()
}
}
/// Ed25519 private/public keypair.
#[derive(Clone)]
pub struct Ed25519Keypair {
/// Public key.
pub public: Ed25519PublicKey,
/// Private key.
pub private: Ed25519PrivateKey,
}
impl Ed25519Keypair {
/// Size of an Ed25519 keypair in bytes.
pub const BYTE_SIZE: usize = 64;
/// Generate a random Ed25519 private keypair.
#[cfg(feature = "ed25519")]
pub fn random(rng: &mut impl CryptoRngCore) -> Self {
Ed25519PrivateKey::random(rng).into()
}
/// Expand a keypair from a 32-byte seed value.
#[cfg(feature = "ed25519")]
pub fn from_seed(seed: &[u8; Ed25519PrivateKey::BYTE_SIZE]) -> Self {
Ed25519PrivateKey::from_bytes(seed).into()
}
/// Parse Ed25519 keypair from 64-bytes which comprise the serialized
/// private and public keys.
pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Result<Self> {
let (priv_bytes, pub_bytes) = bytes.split_at(Ed25519PrivateKey::BYTE_SIZE);
let private = Ed25519PrivateKey::try_from(priv_bytes)?;
let public = Ed25519PublicKey::try_from(pub_bytes)?;
// Validate the public key if possible
#[cfg(feature = "ed25519")]
if Ed25519PublicKey::from(&private) != public {
return Err(Error::Crypto);
}
Ok(Ed25519Keypair { private, public })
}
/// Serialize an Ed25519 keypair as bytes.
pub fn to_bytes(&self) -> [u8; Self::BYTE_SIZE] {
let mut result = [0u8; Self::BYTE_SIZE];
result[..(Self::BYTE_SIZE / 2)].copy_from_slice(self.private.as_ref());
result[(Self::BYTE_SIZE / 2)..].copy_from_slice(self.public.as_ref());
result
}
}
impl ConstantTimeEq for Ed25519Keypair {
fn ct_eq(&self, other: &Self) -> Choice {
Choice::from((self.public == other.public) as u8) & self.private.ct_eq(&other.private)
}
}
impl Eq for Ed25519Keypair {}
impl PartialEq for Ed25519Keypair {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for Ed25519Keypair {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
// Decode private key
let public = Ed25519PublicKey::decode(reader)?;
// The OpenSSH serialization of Ed25519 keys is repetitive and includes
// a serialization of `private_key[32] || public_key[32]` immediately
// following the public key.
let mut bytes = Zeroizing::new([0u8; Self::BYTE_SIZE]);
reader.read_prefixed(|reader| reader.read(&mut *bytes))?;
let keypair = Self::from_bytes(&bytes)?;
// Ensure public key matches the one one the keypair
if keypair.public == public {
Ok(keypair)
} else {
Err(Error::Crypto)
}
}
}
impl Encode for Ed25519Keypair {
fn encoded_len(&self) -> encoding::Result<usize> {
[4, self.public.encoded_len()?, Self::BYTE_SIZE].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.encode(writer)?;
Zeroizing::new(self.to_bytes()).as_ref().encode(writer)?;
Ok(())
}
}
impl From<Ed25519Keypair> for Ed25519PublicKey {
fn from(keypair: Ed25519Keypair) -> Ed25519PublicKey {
keypair.public
}
}
impl From<&Ed25519Keypair> for Ed25519PublicKey {
fn from(keypair: &Ed25519Keypair) -> Ed25519PublicKey {
keypair.public
}
}
impl TryFrom<&[u8]> for Ed25519Keypair {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
Ed25519Keypair::from_bytes(bytes.try_into()?)
}
}
impl fmt::Debug for Ed25519Keypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ed25519Keypair")
.field("public", &self.public)
.finish_non_exhaustive()
}
}
#[cfg(feature = "ed25519")]
impl From<Ed25519PrivateKey> for Ed25519Keypair {
fn from(private: Ed25519PrivateKey) -> Ed25519Keypair {
let secret = ed25519_dalek::SigningKey::from(&private);
let public = secret.verifying_key().into();
Ed25519Keypair { private, public }
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<Ed25519Keypair> for ed25519_dalek::SigningKey {
type Error = Error;
fn try_from(key: Ed25519Keypair) -> Result<ed25519_dalek::SigningKey> {
ed25519_dalek::SigningKey::try_from(&key)
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<&Ed25519Keypair> for ed25519_dalek::SigningKey {
type Error = Error;
fn try_from(key: &Ed25519Keypair) -> Result<ed25519_dalek::SigningKey> {
let signing_key = ed25519_dalek::SigningKey::from(&key.private);
let verifying_key = ed25519_dalek::VerifyingKey::try_from(&key.public)?;
if signing_key.verifying_key() == verifying_key {
Ok(signing_key)
} else {
Err(Error::PublicKey)
}
}
}
#[cfg(feature = "ed25519")]
impl From<ed25519_dalek::SigningKey> for Ed25519Keypair {
fn from(key: ed25519_dalek::SigningKey) -> Ed25519Keypair {
Ed25519Keypair::from(&key)
}
}
#[cfg(feature = "ed25519")]
impl From<&ed25519_dalek::SigningKey> for Ed25519Keypair {
fn from(key: &ed25519_dalek::SigningKey) -> Ed25519Keypair {
Ed25519Keypair {
private: key.into(),
public: key.verifying_key().into(),
}
}
}

444
vendor/ssh-key/src/private/keypair.rs vendored Normal file
View File

@@ -0,0 +1,444 @@
//! Private key pairs.
use super::ed25519::Ed25519Keypair;
use crate::{public, Algorithm, Error, Result};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
#[cfg(feature = "alloc")]
use {
super::{DsaKeypair, OpaqueKeypair, RsaKeypair, SkEd25519},
alloc::vec::Vec,
};
#[cfg(feature = "ecdsa")]
use super::EcdsaKeypair;
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
use super::SkEcdsaSha2NistP256;
/// Private key data: digital signature key pairs.
///
/// SSH private keys contain pairs of public and private keys for various
/// supported digital signature algorithms.
// TODO(tarcieri): pseudo-private keys for FIDO/U2F security keys
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum KeypairData {
/// Digital Signature Algorithm (DSA) keypair.
#[cfg(feature = "alloc")]
Dsa(DsaKeypair),
/// ECDSA keypair.
#[cfg(feature = "ecdsa")]
Ecdsa(EcdsaKeypair),
/// Ed25519 keypair.
Ed25519(Ed25519Keypair),
/// Encrypted private key (ciphertext).
#[cfg(feature = "alloc")]
Encrypted(Vec<u8>),
/// RSA keypair.
#[cfg(feature = "alloc")]
Rsa(RsaKeypair),
/// Security Key (FIDO/U2F) using ECDSA/NIST P-256 as specified in [PROTOCOL.u2f].
///
/// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
SkEcdsaSha2NistP256(SkEcdsaSha2NistP256),
/// Security Key (FIDO/U2F) using Ed25519 as specified in [PROTOCOL.u2f].
///
/// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
#[cfg(feature = "alloc")]
SkEd25519(SkEd25519),
/// Opaque keypair.
#[cfg(feature = "alloc")]
Other(OpaqueKeypair),
}
impl KeypairData {
/// Get the [`Algorithm`] for this private key.
pub fn algorithm(&self) -> Result<Algorithm> {
Ok(match self {
#[cfg(feature = "alloc")]
Self::Dsa(_) => Algorithm::Dsa,
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.algorithm(),
Self::Ed25519(_) => Algorithm::Ed25519,
#[cfg(feature = "alloc")]
Self::Encrypted(_) => return Err(Error::Encrypted),
#[cfg(feature = "alloc")]
Self::Rsa(_) => Algorithm::Rsa { hash: None },
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Self::SkEcdsaSha2NistP256(_) => Algorithm::SkEcdsaSha2NistP256,
#[cfg(feature = "alloc")]
Self::SkEd25519(_) => Algorithm::SkEd25519,
#[cfg(feature = "alloc")]
Self::Other(key) => key.algorithm(),
})
}
/// Get DSA keypair if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn dsa(&self) -> Option<&DsaKeypair> {
match self {
Self::Dsa(key) => Some(key),
_ => None,
}
}
/// Get ECDSA private key if this key is the correct type.
#[cfg(feature = "ecdsa")]
pub fn ecdsa(&self) -> Option<&EcdsaKeypair> {
match self {
Self::Ecdsa(keypair) => Some(keypair),
_ => None,
}
}
/// Get Ed25519 private key if this key is the correct type.
pub fn ed25519(&self) -> Option<&Ed25519Keypair> {
match self {
Self::Ed25519(key) => Some(key),
#[allow(unreachable_patterns)]
_ => None,
}
}
/// Get the encrypted ciphertext if this key is encrypted.
#[cfg(feature = "alloc")]
pub fn encrypted(&self) -> Option<&[u8]> {
match self {
Self::Encrypted(ciphertext) => Some(ciphertext),
_ => None,
}
}
/// Get RSA keypair if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn rsa(&self) -> Option<&RsaKeypair> {
match self {
Self::Rsa(key) => Some(key),
_ => None,
}
}
/// Get FIDO/U2F ECDSA/NIST P-256 private key if this key is the correct type.
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
pub fn sk_ecdsa_p256(&self) -> Option<&SkEcdsaSha2NistP256> {
match self {
Self::SkEcdsaSha2NistP256(sk) => Some(sk),
_ => None,
}
}
/// Get FIDO/U2F Ed25519 private key if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn sk_ed25519(&self) -> Option<&SkEd25519> {
match self {
Self::SkEd25519(sk) => Some(sk),
_ => None,
}
}
/// Get the custom, opaque private key if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn other(&self) -> Option<&OpaqueKeypair> {
match self {
Self::Other(key) => Some(key),
_ => None,
}
}
/// Is this key a DSA key?
#[cfg(feature = "alloc")]
pub fn is_dsa(&self) -> bool {
matches!(self, Self::Dsa(_))
}
/// Is this key an ECDSA key?
#[cfg(feature = "ecdsa")]
pub fn is_ecdsa(&self) -> bool {
matches!(self, Self::Ecdsa(_))
}
/// Is this key an Ed25519 key?
pub fn is_ed25519(&self) -> bool {
matches!(self, Self::Ed25519(_))
}
/// Is this key encrypted?
#[cfg(not(feature = "alloc"))]
pub fn is_encrypted(&self) -> bool {
false
}
/// Is this key encrypted?
#[cfg(feature = "alloc")]
pub fn is_encrypted(&self) -> bool {
matches!(self, Self::Encrypted(_))
}
/// Is this key an RSA key?
#[cfg(feature = "alloc")]
pub fn is_rsa(&self) -> bool {
matches!(self, Self::Rsa(_))
}
/// Is this key a FIDO/U2F ECDSA/NIST P-256 key?
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
pub fn is_sk_ecdsa_p256(&self) -> bool {
matches!(self, Self::SkEcdsaSha2NistP256(_))
}
/// Is this key a FIDO/U2F Ed25519 key?
#[cfg(feature = "alloc")]
pub fn is_sk_ed25519(&self) -> bool {
matches!(self, Self::SkEd25519(_))
}
/// Is this a key with a custom algorithm?
#[cfg(feature = "alloc")]
pub fn is_other(&self) -> bool {
matches!(self, Self::Other(_))
}
/// Compute a deterministic "checkint" for this private key.
///
/// This is a sort of primitive pseudo-MAC used by the OpenSSH key format.
// TODO(tarcieri): true randomness or a better algorithm?
pub(super) fn checkint(&self) -> u32 {
let bytes = match self {
#[cfg(feature = "alloc")]
Self::Dsa(dsa) => dsa.private.as_bytes(),
#[cfg(feature = "ecdsa")]
Self::Ecdsa(ecdsa) => ecdsa.private_key_bytes(),
Self::Ed25519(ed25519) => ed25519.private.as_ref(),
#[cfg(feature = "alloc")]
Self::Encrypted(ciphertext) => ciphertext.as_ref(),
#[cfg(feature = "alloc")]
Self::Rsa(rsa) => rsa.private.d.as_bytes(),
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Self::SkEcdsaSha2NistP256(sk) => sk.key_handle(),
#[cfg(feature = "alloc")]
Self::SkEd25519(sk) => sk.key_handle(),
#[cfg(feature = "alloc")]
Self::Other(key) => key.private.as_ref(),
};
let mut n = 0u32;
for chunk in bytes.chunks_exact(4) {
n ^= u32::from_be_bytes(chunk.try_into().expect("not 4 bytes"));
}
n
}
/// Decode [`KeypairData`] for the specified algorithm.
pub fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result<Self> {
match algorithm {
#[cfg(feature = "alloc")]
Algorithm::Dsa => DsaKeypair::decode(reader).map(Self::Dsa),
#[cfg(feature = "ecdsa")]
Algorithm::Ecdsa { curve } => match EcdsaKeypair::decode(reader)? {
keypair if keypair.curve() == curve => Ok(Self::Ecdsa(keypair)),
_ => Err(Error::AlgorithmUnknown),
},
Algorithm::Ed25519 => Ed25519Keypair::decode(reader).map(Self::Ed25519),
#[cfg(feature = "alloc")]
Algorithm::Rsa { .. } => RsaKeypair::decode(reader).map(Self::Rsa),
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Algorithm::SkEcdsaSha2NistP256 => {
SkEcdsaSha2NistP256::decode(reader).map(Self::SkEcdsaSha2NistP256)
}
#[cfg(feature = "alloc")]
Algorithm::SkEd25519 => SkEd25519::decode(reader).map(Self::SkEd25519),
#[cfg(feature = "alloc")]
algorithm @ Algorithm::Other(_) => {
OpaqueKeypair::decode_as(reader, algorithm).map(Self::Other)
}
#[allow(unreachable_patterns)]
_ => Err(Error::AlgorithmUnknown),
}
}
}
impl ConstantTimeEq for KeypairData {
fn ct_eq(&self, other: &Self) -> Choice {
// Note: constant-time with respect to key *data* comparisons, not algorithms
match (self, other) {
#[cfg(feature = "alloc")]
(Self::Dsa(a), Self::Dsa(b)) => a.ct_eq(b),
#[cfg(feature = "ecdsa")]
(Self::Ecdsa(a), Self::Ecdsa(b)) => a.ct_eq(b),
(Self::Ed25519(a), Self::Ed25519(b)) => a.ct_eq(b),
#[cfg(feature = "alloc")]
(Self::Encrypted(a), Self::Encrypted(b)) => a.ct_eq(b),
#[cfg(feature = "alloc")]
(Self::Rsa(a), Self::Rsa(b)) => a.ct_eq(b),
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
(Self::SkEcdsaSha2NistP256(a), Self::SkEcdsaSha2NistP256(b)) => {
// Security Keys store the actual private key in hardware.
// The key structs contain all public data.
Choice::from((a == b) as u8)
}
#[cfg(feature = "alloc")]
(Self::SkEd25519(a), Self::SkEd25519(b)) => {
// Security Keys store the actual private key in hardware.
// The key structs contain all public data.
Choice::from((a == b) as u8)
}
#[cfg(feature = "alloc")]
(Self::Other(a), Self::Other(b)) => a.ct_eq(b),
#[allow(unreachable_patterns)]
_ => Choice::from(0),
}
}
}
impl Eq for KeypairData {}
impl PartialEq for KeypairData {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for KeypairData {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let algorithm = Algorithm::decode(reader)?;
Self::decode_as(reader, algorithm)
}
}
impl Encode for KeypairData {
fn encoded_len(&self) -> encoding::Result<usize> {
let alg_len = self
.algorithm()
.ok()
.map(|alg| alg.encoded_len())
.transpose()?
.unwrap_or(0);
let key_len = match self {
#[cfg(feature = "alloc")]
Self::Dsa(key) => key.encoded_len()?,
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.encoded_len()?,
Self::Ed25519(key) => key.encoded_len()?,
#[cfg(feature = "alloc")]
Self::Encrypted(ciphertext) => return Ok(ciphertext.len()),
#[cfg(feature = "alloc")]
Self::Rsa(key) => key.encoded_len()?,
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Self::SkEcdsaSha2NistP256(sk) => sk.encoded_len()?,
#[cfg(feature = "alloc")]
Self::SkEd25519(sk) => sk.encoded_len()?,
#[cfg(feature = "alloc")]
Self::Other(key) => key.encoded_len()?,
};
[alg_len, key_len].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
if let Ok(alg) = self.algorithm() {
alg.encode(writer)?;
}
match self {
#[cfg(feature = "alloc")]
Self::Dsa(key) => key.encode(writer)?,
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.encode(writer)?,
Self::Ed25519(key) => key.encode(writer)?,
#[cfg(feature = "alloc")]
Self::Encrypted(ciphertext) => writer.write(ciphertext)?,
#[cfg(feature = "alloc")]
Self::Rsa(key) => key.encode(writer)?,
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Self::SkEcdsaSha2NistP256(sk) => sk.encode(writer)?,
#[cfg(feature = "alloc")]
Self::SkEd25519(sk) => sk.encode(writer)?,
#[cfg(feature = "alloc")]
Self::Other(key) => key.encode(writer)?,
}
Ok(())
}
}
impl TryFrom<&KeypairData> for public::KeyData {
type Error = Error;
fn try_from(keypair_data: &KeypairData) -> Result<public::KeyData> {
Ok(match keypair_data {
#[cfg(feature = "alloc")]
KeypairData::Dsa(dsa) => public::KeyData::Dsa(dsa.into()),
#[cfg(feature = "ecdsa")]
KeypairData::Ecdsa(ecdsa) => public::KeyData::Ecdsa(ecdsa.into()),
KeypairData::Ed25519(ed25519) => public::KeyData::Ed25519(ed25519.into()),
#[cfg(feature = "alloc")]
KeypairData::Encrypted(_) => return Err(Error::Encrypted),
#[cfg(feature = "alloc")]
KeypairData::Rsa(rsa) => public::KeyData::Rsa(rsa.into()),
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
KeypairData::SkEcdsaSha2NistP256(sk) => {
public::KeyData::SkEcdsaSha2NistP256(sk.public().clone())
}
#[cfg(feature = "alloc")]
KeypairData::SkEd25519(sk) => public::KeyData::SkEd25519(sk.public().clone()),
#[cfg(feature = "alloc")]
KeypairData::Other(key) => public::KeyData::Other(key.into()),
})
}
}
#[cfg(feature = "alloc")]
impl From<DsaKeypair> for KeypairData {
fn from(keypair: DsaKeypair) -> KeypairData {
Self::Dsa(keypair)
}
}
#[cfg(feature = "ecdsa")]
impl From<EcdsaKeypair> for KeypairData {
fn from(keypair: EcdsaKeypair) -> KeypairData {
Self::Ecdsa(keypair)
}
}
impl From<Ed25519Keypair> for KeypairData {
fn from(keypair: Ed25519Keypair) -> KeypairData {
Self::Ed25519(keypair)
}
}
#[cfg(feature = "alloc")]
impl From<RsaKeypair> for KeypairData {
fn from(keypair: RsaKeypair) -> KeypairData {
Self::Rsa(keypair)
}
}
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
impl From<SkEcdsaSha2NistP256> for KeypairData {
fn from(keypair: SkEcdsaSha2NistP256) -> KeypairData {
Self::SkEcdsaSha2NistP256(keypair)
}
}
#[cfg(feature = "alloc")]
impl From<SkEd25519> for KeypairData {
fn from(keypair: SkEd25519) -> KeypairData {
Self::SkEd25519(keypair)
}
}

155
vendor/ssh-key/src/private/opaque.rs vendored Normal file
View File

@@ -0,0 +1,155 @@
//! Opaque private keys.
//!
//! [`OpaqueKeypair`] represents a keypair meant to be used with an algorithm unknown to this
//! crate, i.e. keypairs that use a custom algorithm as specified in [RFC4251 § 6].
//!
//! They are said to be opaque, because the meaning of their underlying byte representation is not
//! specified.
//!
//! [RFC4251 § 6]: https://www.rfc-editor.org/rfc/rfc4251.html#section-6
use crate::{
public::{OpaquePublicKey, OpaquePublicKeyBytes},
Algorithm, Error, Result,
};
use alloc::vec::Vec;
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
/// An opaque private key.
///
/// The encoded representation of an `OpaquePrivateKeyBytes` consists of a 4-byte length prefix,
/// followed by its byte representation.
#[derive(Clone)]
pub struct OpaquePrivateKeyBytes(Vec<u8>);
/// An opaque keypair.
///
/// The encoded representation of an `OpaqueKeypair` consists of the encoded representation of its
/// [`OpaquePublicKey`] followed by the encoded representation of its [`OpaquePrivateKeyBytes`].
#[derive(Clone)]
pub struct OpaqueKeypair {
/// The opaque private key
pub private: OpaquePrivateKeyBytes,
/// The opaque public key
pub public: OpaquePublicKey,
}
/// The underlying representation of an [`OpaqueKeypair`].
///
/// The encoded representation of an `OpaqueKeypairBytes` consists of the encoded representation of
/// its [`OpaquePublicKeyBytes`] followed by the encoded representation of its
/// [`OpaquePrivateKeyBytes`].
pub struct OpaqueKeypairBytes {
/// The opaque private key
pub private: OpaquePrivateKeyBytes,
/// The opaque public key
pub public: OpaquePublicKeyBytes,
}
impl OpaqueKeypair {
/// Create a new `OpaqueKeypair`.
pub fn new(private_key: Vec<u8>, public: OpaquePublicKey) -> Self {
Self {
private: OpaquePrivateKeyBytes(private_key),
public,
}
}
/// Get the [`Algorithm`] for this key type.
pub fn algorithm(&self) -> Algorithm {
self.public.algorithm()
}
/// Decode [`OpaqueKeypair`] for the specified algorithm.
pub(super) fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result<Self> {
let key = OpaqueKeypairBytes::decode(reader)?;
let public = OpaquePublicKey {
algorithm,
key: key.public,
};
Ok(Self {
public,
private: key.private,
})
}
}
impl Decode for OpaquePrivateKeyBytes {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let len = usize::decode(reader)?;
let mut bytes = vec![0; len];
reader.read(&mut bytes)?;
Ok(Self(bytes))
}
}
impl Decode for OpaqueKeypairBytes {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let public = OpaquePublicKeyBytes::decode(reader)?;
let private = OpaquePrivateKeyBytes::decode(reader)?;
Ok(Self { public, private })
}
}
impl Encode for OpaqueKeypair {
fn encoded_len(&self) -> encoding::Result<usize> {
[self.public.encoded_len()?, self.private.encoded_len()?].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.encode(writer)?;
self.private.encode(writer)?;
Ok(())
}
}
impl ConstantTimeEq for OpaqueKeypair {
fn ct_eq(&self, other: &Self) -> Choice {
Choice::from((self.public == other.public) as u8) & self.private.ct_eq(&other.private)
}
}
impl Encode for OpaquePrivateKeyBytes {
fn encoded_len(&self) -> encoding::Result<usize> {
self.0.encoded_len()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.0.encode(writer)
}
}
impl From<&OpaqueKeypair> for OpaquePublicKey {
fn from(keypair: &OpaqueKeypair) -> OpaquePublicKey {
keypair.public.clone()
}
}
impl fmt::Debug for OpaqueKeypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OpaqueKeypair")
.field("public", &self.public)
.finish_non_exhaustive()
}
}
impl ConstantTimeEq for OpaquePrivateKeyBytes {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
impl AsRef<[u8]> for OpaquePrivateKeyBytes {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

260
vendor/ssh-key/src/private/rsa.rs vendored Normal file
View File

@@ -0,0 +1,260 @@
//! RivestShamirAdleman (RSA) private keys.
use crate::{public::RsaPublicKey, Error, Mpint, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;
#[cfg(feature = "rsa")]
use {
rand_core::CryptoRngCore,
rsa::{
pkcs1v15,
traits::{PrivateKeyParts, PublicKeyParts},
},
sha2::{digest::const_oid::AssociatedOid, Digest},
};
/// RSA private key.
#[derive(Clone)]
pub struct RsaPrivateKey {
/// RSA private exponent.
pub d: Mpint,
/// CRT coefficient: `(inverse of q) mod p`.
pub iqmp: Mpint,
/// First prime factor of `n`.
pub p: Mpint,
/// Second prime factor of `n`.
pub q: Mpint,
}
impl ConstantTimeEq for RsaPrivateKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.d.ct_eq(&other.d)
& self.iqmp.ct_eq(&self.iqmp)
& self.p.ct_eq(&other.p)
& self.q.ct_eq(&other.q)
}
}
impl Eq for RsaPrivateKey {}
impl PartialEq for RsaPrivateKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for RsaPrivateKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let d = Mpint::decode(reader)?;
let iqmp = Mpint::decode(reader)?;
let p = Mpint::decode(reader)?;
let q = Mpint::decode(reader)?;
Ok(Self { d, iqmp, p, q })
}
}
impl Encode for RsaPrivateKey {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.d.encoded_len()?,
self.iqmp.encoded_len()?,
self.p.encoded_len()?,
self.q.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.d.encode(writer)?;
self.iqmp.encode(writer)?;
self.p.encode(writer)?;
self.q.encode(writer)?;
Ok(())
}
}
impl Drop for RsaPrivateKey {
fn drop(&mut self) {
self.d.zeroize();
self.iqmp.zeroize();
self.p.zeroize();
self.q.zeroize();
}
}
/// RSA private/public keypair.
#[derive(Clone)]
pub struct RsaKeypair {
/// Public key.
pub public: RsaPublicKey,
/// Private key.
pub private: RsaPrivateKey,
}
impl RsaKeypair {
/// Minimum allowed RSA key size.
#[cfg(feature = "rsa")]
pub(crate) const MIN_KEY_SIZE: usize = 2048;
/// Generate a random RSA keypair of the given size.
#[cfg(feature = "rsa")]
pub fn random(rng: &mut impl CryptoRngCore, bit_size: usize) -> Result<Self> {
if bit_size >= Self::MIN_KEY_SIZE {
rsa::RsaPrivateKey::new(rng, bit_size)?.try_into()
} else {
Err(Error::Crypto)
}
}
}
impl ConstantTimeEq for RsaKeypair {
fn ct_eq(&self, other: &Self) -> Choice {
Choice::from((self.public == other.public) as u8) & self.private.ct_eq(&other.private)
}
}
impl Eq for RsaKeypair {}
impl PartialEq for RsaKeypair {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for RsaKeypair {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let n = Mpint::decode(reader)?;
let e = Mpint::decode(reader)?;
let public = RsaPublicKey { n, e };
let private = RsaPrivateKey::decode(reader)?;
Ok(RsaKeypair { public, private })
}
}
impl Encode for RsaKeypair {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.public.n.encoded_len()?,
self.public.e.encoded_len()?,
self.private.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.n.encode(writer)?;
self.public.e.encode(writer)?;
self.private.encode(writer)
}
}
impl From<RsaKeypair> for RsaPublicKey {
fn from(keypair: RsaKeypair) -> RsaPublicKey {
keypair.public
}
}
impl From<&RsaKeypair> for RsaPublicKey {
fn from(keypair: &RsaKeypair) -> RsaPublicKey {
keypair.public.clone()
}
}
impl fmt::Debug for RsaKeypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RsaKeypair")
.field("public", &self.public)
.finish_non_exhaustive()
}
}
#[cfg(feature = "rsa")]
impl TryFrom<RsaKeypair> for rsa::RsaPrivateKey {
type Error = Error;
fn try_from(key: RsaKeypair) -> Result<rsa::RsaPrivateKey> {
rsa::RsaPrivateKey::try_from(&key)
}
}
#[cfg(feature = "rsa")]
impl TryFrom<&RsaKeypair> for rsa::RsaPrivateKey {
type Error = Error;
fn try_from(key: &RsaKeypair) -> Result<rsa::RsaPrivateKey> {
let ret = rsa::RsaPrivateKey::from_components(
rsa::BigUint::try_from(&key.public.n)?,
rsa::BigUint::try_from(&key.public.e)?,
rsa::BigUint::try_from(&key.private.d)?,
vec![
rsa::BigUint::try_from(&key.private.p)?,
rsa::BigUint::try_from(&key.private.p)?,
],
)?;
if ret.size().saturating_mul(8) >= RsaKeypair::MIN_KEY_SIZE {
Ok(ret)
} else {
Err(Error::Crypto)
}
}
}
#[cfg(feature = "rsa")]
impl TryFrom<rsa::RsaPrivateKey> for RsaKeypair {
type Error = Error;
fn try_from(key: rsa::RsaPrivateKey) -> Result<RsaKeypair> {
RsaKeypair::try_from(&key)
}
}
#[cfg(feature = "rsa")]
impl TryFrom<&rsa::RsaPrivateKey> for RsaKeypair {
type Error = Error;
fn try_from(key: &rsa::RsaPrivateKey) -> Result<RsaKeypair> {
// Multi-prime keys are not supported
if key.primes().len() > 2 {
return Err(Error::Crypto);
}
let public = RsaPublicKey::try_from(key.to_public_key())?;
let p = &key.primes()[0];
let q = &key.primes()[1];
let iqmp = key.crt_coefficient().ok_or(Error::Crypto)?;
let private = RsaPrivateKey {
d: key.d().try_into()?,
iqmp: iqmp.try_into()?,
p: p.try_into()?,
q: q.try_into()?,
};
Ok(RsaKeypair { public, private })
}
}
#[cfg(feature = "rsa")]
impl<D> TryFrom<&RsaKeypair> for pkcs1v15::SigningKey<D>
where
D: Digest + AssociatedOid,
{
type Error = Error;
fn try_from(keypair: &RsaKeypair) -> Result<pkcs1v15::SigningKey<D>> {
Ok(pkcs1v15::SigningKey::new(keypair.try_into()?))
}
}

188
vendor/ssh-key/src/private/sk.rs vendored Normal file
View File

@@ -0,0 +1,188 @@
//! Security Key (FIDO/U2F) private keys as described in [PROTOCOL.u2f].
//!
//! [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
use crate::{public, Error, Result};
use alloc::vec::Vec;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Security Key (FIDO/U2F) ECDSA/NIST P-256 private key as specified in
/// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD).
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct SkEcdsaSha2NistP256 {
/// Public key.
public: public::SkEcdsaSha2NistP256,
/// Flags.
flags: u8,
/// FIDO/U2F key handle.
key_handle: Vec<u8>,
/// Reserved data.
reserved: Vec<u8>,
}
#[cfg(feature = "ecdsa")]
impl SkEcdsaSha2NistP256 {
/// Construct new instance of SkEcdsaSha2NistP256.
#[cfg(feature = "alloc")]
pub fn new(
public: public::SkEcdsaSha2NistP256,
flags: u8,
key_handle: impl Into<Vec<u8>>,
) -> Result<Self> {
let key_handle = key_handle.into();
if key_handle.len() <= 255 {
Ok(SkEcdsaSha2NistP256 {
public,
flags,
key_handle,
reserved: Vec::<u8>::new(),
})
} else {
Err(encoding::Error::Length.into())
}
}
/// Get the ECDSA/NIST P-256 public key.
pub fn public(&self) -> &public::SkEcdsaSha2NistP256 {
&self.public
}
/// Get flags.
pub fn flags(&self) -> u8 {
self.flags
}
/// Get FIDO/U2F key handle.
pub fn key_handle(&self) -> &[u8] {
&self.key_handle
}
}
#[cfg(feature = "ecdsa")]
impl Decode for SkEcdsaSha2NistP256 {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
Ok(Self {
public: public::SkEcdsaSha2NistP256::decode(reader)?,
flags: u8::decode(reader)?,
key_handle: Vec::decode(reader)?,
reserved: Vec::decode(reader)?,
})
}
}
#[cfg(feature = "ecdsa")]
impl Encode for SkEcdsaSha2NistP256 {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.public.encoded_len()?,
self.flags.encoded_len()?,
self.key_handle.encoded_len()?,
self.reserved.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.encode(writer)?;
self.flags.encode(writer)?;
self.key_handle.encode(writer)?;
self.reserved.encode(writer)?;
Ok(())
}
}
/// Security Key (FIDO/U2F) Ed25519 private key as specified in
/// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD).
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct SkEd25519 {
/// Public key.
public: public::SkEd25519,
/// Flags.
flags: u8,
/// FIDO/U2F key handle.
key_handle: Vec<u8>,
/// Reserved data.
reserved: Vec<u8>,
}
impl SkEd25519 {
/// Construct new instance of SkEd25519.
#[cfg(feature = "alloc")]
pub fn new(
public: public::SkEd25519,
flags: u8,
key_handle: impl Into<Vec<u8>>,
) -> Result<Self> {
let key_handle = key_handle.into();
if key_handle.len() <= 255 {
Ok(SkEd25519 {
public,
flags,
key_handle,
reserved: Vec::<u8>::new(),
})
} else {
Err(encoding::Error::Length.into())
}
}
/// Get the Ed25519 public key.
pub fn public(&self) -> &public::SkEd25519 {
&self.public
}
/// Get flags.
pub fn flags(&self) -> u8 {
self.flags
}
/// Get FIDO/U2F key handle.
pub fn key_handle(&self) -> &[u8] {
&self.key_handle
}
}
impl Decode for SkEd25519 {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
Ok(Self {
public: public::SkEd25519::decode(reader)?,
flags: u8::decode(reader)?,
key_handle: Vec::decode(reader)?,
reserved: Vec::decode(reader)?,
})
}
}
impl Encode for SkEd25519 {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.public.encoded_len()?,
self.flags.encoded_len()?,
self.key_handle.encoded_len()?,
self.reserved.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.encode(writer)?;
self.flags.encode(writer)?;
self.key_handle.encode(writer)?;
self.reserved.encode(writer)?;
Ok(())
}
}

403
vendor/ssh-key/src/public.rs vendored Normal file
View File

@@ -0,0 +1,403 @@
//! SSH public key support.
//!
//! Support for decoding SSH public keys from the OpenSSH file format.
#[cfg(feature = "alloc")]
mod dsa;
#[cfg(feature = "ecdsa")]
mod ecdsa;
mod ed25519;
mod key_data;
#[cfg(feature = "alloc")]
mod opaque;
#[cfg(feature = "alloc")]
mod rsa;
mod sk;
mod ssh_format;
pub use self::{ed25519::Ed25519PublicKey, key_data::KeyData, sk::SkEd25519};
#[cfg(feature = "alloc")]
pub use self::{
dsa::DsaPublicKey,
opaque::{OpaquePublicKey, OpaquePublicKeyBytes},
rsa::RsaPublicKey,
};
#[cfg(feature = "ecdsa")]
pub use self::{ecdsa::EcdsaPublicKey, sk::SkEcdsaSha2NistP256};
pub(crate) use self::ssh_format::SshFormat;
use crate::{Algorithm, Error, Fingerprint, HashAlg, Result};
use core::str::FromStr;
use encoding::{Base64Reader, Decode, Reader};
#[cfg(feature = "alloc")]
use {
crate::SshSig,
alloc::{
borrow::ToOwned,
string::{String, ToString},
vec::Vec,
},
encoding::Encode,
};
#[cfg(all(feature = "alloc", feature = "serde"))]
use serde::{de, ser, Deserialize, Serialize};
#[cfg(feature = "std")]
use std::{fs, path::Path};
#[cfg(doc)]
use crate::PrivateKey;
/// SSH public key.
///
/// # OpenSSH encoding
///
/// The OpenSSH encoding of an SSH public key looks like following:
///
/// ```text
/// ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com
/// ```
///
/// It consists of the following three parts:
///
/// 1. Algorithm identifier (in this example `ssh-ed25519`)
/// 2. Key data encoded as Base64
/// 3. Comment (optional): arbitrary label describing a key. Usually an email address
///
/// The [`PublicKey::from_openssh`] and [`PublicKey::to_openssh`] methods can be
/// used to decode/encode public keys, or alternatively, the [`FromStr`] and
/// [`ToString`] impls.
///
/// # `serde` support
///
/// When the `serde` feature of this crate is enabled, this type receives impls
/// of [`Deserialize`][`serde::Deserialize`] and [`Serialize`][`serde::Serialize`].
///
/// The serialization uses a binary encoding with binary formats like bincode
/// and CBOR, and the OpenSSH string serialization when used with
/// human-readable formats like JSON and TOML.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PublicKey {
/// Key data.
pub(crate) key_data: KeyData,
/// Comment on the key (e.g. email address)
#[cfg(feature = "alloc")]
pub(crate) comment: String,
}
impl PublicKey {
/// Create a new public key with the given comment.
///
/// On `no_std` platforms, use `PublicKey::from(key_data)` instead.
#[cfg(feature = "alloc")]
pub fn new(key_data: KeyData, comment: impl Into<String>) -> Self {
Self {
key_data,
comment: comment.into(),
}
}
/// Parse an OpenSSH-formatted public key.
///
/// OpenSSH-formatted public keys look like the following:
///
/// ```text
/// ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti foo@bar.com
/// ```
pub fn from_openssh(public_key: &str) -> Result<Self> {
let encapsulation = SshFormat::decode(public_key.trim_end().as_bytes())?;
let mut reader = Base64Reader::new(encapsulation.base64_data)?;
let key_data = KeyData::decode(&mut reader)?;
// Verify that the algorithm in the Base64-encoded data matches the text
if encapsulation.algorithm_id != key_data.algorithm().as_str() {
return Err(Error::AlgorithmUnknown);
}
let public_key = Self {
key_data,
#[cfg(feature = "alloc")]
comment: encapsulation.comment.to_owned(),
};
Ok(reader.finish(public_key)?)
}
/// Parse a raw binary SSH public key.
pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
let reader = &mut bytes;
let key_data = KeyData::decode(reader)?;
Ok(reader.finish(key_data.into())?)
}
/// Encode OpenSSH-formatted public key.
pub fn encode_openssh<'o>(&self, out: &'o mut [u8]) -> Result<&'o str> {
SshFormat::encode(
self.algorithm().as_str(),
&self.key_data,
self.comment(),
out,
)
}
/// Encode an OpenSSH-formatted public key, allocating a [`String`] for
/// the result.
#[cfg(feature = "alloc")]
pub fn to_openssh(&self) -> Result<String> {
SshFormat::encode_string(self.algorithm().as_str(), &self.key_data, self.comment())
}
/// Serialize SSH public key as raw bytes.
#[cfg(feature = "alloc")]
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut public_key_bytes = Vec::new();
self.key_data.encode(&mut public_key_bytes)?;
Ok(public_key_bytes)
}
/// Verify the [`SshSig`] signature over the given message using this
/// public key.
///
/// These signatures can be produced using `ssh-keygen -Y sign`. They're
/// encoded as PEM and begin with the following:
///
/// ```text
/// -----BEGIN SSH SIGNATURE-----
/// ```
///
/// See [PROTOCOL.sshsig] for more information.
///
/// # Usage
///
/// See also: [`PrivateKey::sign`].
///
#[cfg_attr(feature = "ed25519", doc = "```")]
#[cfg_attr(not(feature = "ed25519"), doc = "```ignore")]
/// # fn main() -> Result<(), ssh_key::Error> {
/// use ssh_key::{PublicKey, SshSig};
///
/// // Message to be verified.
/// let message = b"testing";
///
/// // Example domain/namespace used for the message.
/// let namespace = "example";
///
/// // Public key which computed the signature.
/// let encoded_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com";
///
/// // Example signature to be verified.
/// let signature_str = r#"
/// -----BEGIN SSH SIGNATURE-----
/// U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgsz6u836i33yqAQ3v3qNOJB9l8b
/// UppPQ+0UMn9cVKq2IAAAAHZXhhbXBsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQy
/// NTUxOQAAAEBPEav+tMGNnox4MuzM7rlHyVBajCn8B0kAyiOWwPKprNsG3i6X+voz/WCSik
/// /FowYwqhgCABUJSvRX3AERVBUP
/// -----END SSH SIGNATURE-----
/// "#;
///
/// let public_key = encoded_public_key.parse::<PublicKey>()?;
/// let signature = signature_str.parse::<SshSig>()?;
/// public_key.verify(namespace, message, &signature)?;
/// # Ok(())
/// # }
/// ```
///
/// [PROTOCOL.sshsig]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.sshsig?annotate=HEAD
#[cfg(feature = "alloc")]
pub fn verify(&self, namespace: &str, msg: &[u8], signature: &SshSig) -> Result<()> {
if self.key_data() != signature.public_key() {
return Err(Error::PublicKey);
}
if namespace != signature.namespace() {
return Err(Error::Namespace);
}
signature.verify(msg)
}
/// Read public key from an OpenSSH-formatted file.
#[cfg(feature = "std")]
pub fn read_openssh_file(path: &Path) -> Result<Self> {
let input = fs::read_to_string(path)?;
Self::from_openssh(&input)
}
/// Write public key as an OpenSSH-formatted file.
#[cfg(feature = "std")]
pub fn write_openssh_file(&self, path: &Path) -> Result<()> {
let mut encoded = self.to_openssh()?;
encoded.push('\n'); // TODO(tarcieri): OS-specific line endings?
fs::write(path, encoded.as_bytes())?;
Ok(())
}
/// Get the digital signature [`Algorithm`] used by this key.
pub fn algorithm(&self) -> Algorithm {
self.key_data.algorithm()
}
/// Comment on the key (e.g. email address).
#[cfg(not(feature = "alloc"))]
pub fn comment(&self) -> &str {
""
}
/// Comment on the key (e.g. email address).
#[cfg(feature = "alloc")]
pub fn comment(&self) -> &str {
&self.comment
}
/// Public key data.
pub fn key_data(&self) -> &KeyData {
&self.key_data
}
/// Compute key fingerprint.
///
/// Use [`Default::default()`] to use the default hash function (SHA-256).
pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
self.key_data.fingerprint(hash_alg)
}
/// Set the comment on the key.
#[cfg(feature = "alloc")]
pub fn set_comment(&mut self, comment: impl Into<String>) {
self.comment = comment.into();
}
/// Decode comment (e.g. email address).
///
/// This is a stub implementation that ignores the comment.
#[cfg(not(feature = "alloc"))]
pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
reader.drain_prefixed()?;
Ok(())
}
/// Decode comment (e.g. email address)
#[cfg(feature = "alloc")]
pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
self.comment = String::decode(reader)?;
Ok(())
}
}
impl From<KeyData> for PublicKey {
fn from(key_data: KeyData) -> PublicKey {
PublicKey {
key_data,
#[cfg(feature = "alloc")]
comment: String::new(),
}
}
}
impl From<PublicKey> for KeyData {
fn from(public_key: PublicKey) -> KeyData {
public_key.key_data
}
}
impl From<&PublicKey> for KeyData {
fn from(public_key: &PublicKey) -> KeyData {
public_key.key_data.clone()
}
}
#[cfg(feature = "alloc")]
impl From<DsaPublicKey> for PublicKey {
fn from(public_key: DsaPublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
#[cfg(feature = "ecdsa")]
impl From<EcdsaPublicKey> for PublicKey {
fn from(public_key: EcdsaPublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
impl From<Ed25519PublicKey> for PublicKey {
fn from(public_key: Ed25519PublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
#[cfg(feature = "alloc")]
impl From<RsaPublicKey> for PublicKey {
fn from(public_key: RsaPublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
#[cfg(feature = "ecdsa")]
impl From<SkEcdsaSha2NistP256> for PublicKey {
fn from(public_key: SkEcdsaSha2NistP256) -> PublicKey {
KeyData::from(public_key).into()
}
}
impl From<SkEd25519> for PublicKey {
fn from(public_key: SkEd25519) -> PublicKey {
KeyData::from(public_key).into()
}
}
impl FromStr for PublicKey {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_openssh(s)
}
}
#[cfg(feature = "alloc")]
impl ToString for PublicKey {
fn to_string(&self) -> String {
self.to_openssh().expect("SSH public key encoding error")
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
impl<'de> Deserialize<'de> for PublicKey {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let string = String::deserialize(deserializer)?;
Self::from_openssh(&string).map_err(de::Error::custom)
} else {
let bytes = Vec::<u8>::deserialize(deserializer)?;
Self::from_bytes(&bytes).map_err(de::Error::custom)
}
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
impl Serialize for PublicKey {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
if serializer.is_human_readable() {
self.to_openssh()
.map_err(ser::Error::custom)?
.serialize(serializer)
} else {
self.to_bytes()
.map_err(ser::Error::custom)?
.serialize(serializer)
}
}
}

113
vendor/ssh-key/src/public/dsa.rs vendored Normal file
View File

@@ -0,0 +1,113 @@
//! Digital Signature Algorithm (DSA) public keys.
use crate::{Error, Mpint, Result};
use core::hash::{Hash, Hasher};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Digital Signature Algorithm (DSA) public key.
///
/// Described in [FIPS 186-4 § 4.1](https://csrc.nist.gov/publications/detail/fips/186/4/final).
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct DsaPublicKey {
/// Prime modulus.
pub p: Mpint,
/// Prime divisor of `p - 1`.
pub q: Mpint,
/// Generator of a subgroup of order `q` in the multiplicative group
/// `GF(p)`, such that `1 < g < p`.
pub g: Mpint,
/// The public key, where `y = gˣ mod p`.
pub y: Mpint,
}
impl Decode for DsaPublicKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let p = Mpint::decode(reader)?;
let q = Mpint::decode(reader)?;
let g = Mpint::decode(reader)?;
let y = Mpint::decode(reader)?;
Ok(Self { p, q, g, y })
}
}
impl Encode for DsaPublicKey {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.p.encoded_len()?,
self.q.encoded_len()?,
self.g.encoded_len()?,
self.y.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.p.encode(writer)?;
self.q.encode(writer)?;
self.g.encode(writer)?;
self.y.encode(writer)
}
}
impl Hash for DsaPublicKey {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.p.as_bytes().hash(state);
self.q.as_bytes().hash(state);
self.g.as_bytes().hash(state);
self.y.as_bytes().hash(state);
}
}
#[cfg(feature = "dsa")]
impl TryFrom<DsaPublicKey> for dsa::VerifyingKey {
type Error = Error;
fn try_from(key: DsaPublicKey) -> Result<dsa::VerifyingKey> {
dsa::VerifyingKey::try_from(&key)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&DsaPublicKey> for dsa::VerifyingKey {
type Error = Error;
fn try_from(key: &DsaPublicKey) -> Result<dsa::VerifyingKey> {
let components = dsa::Components::from_components(
dsa::BigUint::try_from(&key.p)?,
dsa::BigUint::try_from(&key.q)?,
dsa::BigUint::try_from(&key.g)?,
)?;
dsa::VerifyingKey::from_components(components, dsa::BigUint::try_from(&key.y)?)
.map_err(|_| Error::Crypto)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<dsa::VerifyingKey> for DsaPublicKey {
type Error = Error;
fn try_from(key: dsa::VerifyingKey) -> Result<DsaPublicKey> {
DsaPublicKey::try_from(&key)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&dsa::VerifyingKey> for DsaPublicKey {
type Error = Error;
fn try_from(key: &dsa::VerifyingKey) -> Result<DsaPublicKey> {
Ok(DsaPublicKey {
p: key.components().p().try_into()?,
q: key.components().q().try_into()?,
g: key.components().g().try_into()?,
y: key.y().try_into()?,
})
}
}

202
vendor/ssh-key/src/public/ecdsa.rs vendored Normal file
View File

@@ -0,0 +1,202 @@
//! Elliptic Curve Digital Signature Algorithm (ECDSA) public keys.
use crate::{Algorithm, EcdsaCurve, Error, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use sec1::consts::{U32, U48, U66};
/// ECDSA/NIST P-256 public key.
pub type EcdsaNistP256PublicKey = sec1::EncodedPoint<U32>;
/// ECDSA/NIST P-384 public key.
pub type EcdsaNistP384PublicKey = sec1::EncodedPoint<U48>;
/// ECDSA/NIST P-521 public key.
pub type EcdsaNistP521PublicKey = sec1::EncodedPoint<U66>;
/// Elliptic Curve Digital Signature Algorithm (ECDSA) public key.
///
/// Public keys are represented as [`sec1::EncodedPoint`] and require the
/// `sec1` feature of this crate is enabled (which it is by default).
///
/// Described in [FIPS 186-4](https://csrc.nist.gov/publications/detail/fips/186/4/final).
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum EcdsaPublicKey {
/// NIST P-256 ECDSA public key.
NistP256(EcdsaNistP256PublicKey),
/// NIST P-384 ECDSA public key.
NistP384(EcdsaNistP384PublicKey),
/// NIST P-521 ECDSA public key.
NistP521(EcdsaNistP521PublicKey),
}
impl EcdsaPublicKey {
/// Maximum size of a SEC1-encoded ECDSA public key (i.e. curve point).
///
/// This is the size of 2 * P-521 field elements (2 * 66 = 132) which
/// represent the affine coordinates of a curve point plus one additional
/// byte for the SEC1 "tag" identifying the curve point encoding.
const MAX_SIZE: usize = 133;
/// Parse an ECDSA public key from a SEC1-encoded point.
///
/// Determines the key type from the SEC1 tag byte and length.
pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> {
match bytes {
[tag, rest @ ..] => {
let point_size = match sec1::point::Tag::from_u8(*tag)? {
sec1::point::Tag::CompressedEvenY | sec1::point::Tag::CompressedOddY => {
rest.len()
}
sec1::point::Tag::Uncompressed => rest.len() / 2,
_ => return Err(Error::AlgorithmUnknown),
};
match point_size {
32 => Ok(Self::NistP256(EcdsaNistP256PublicKey::from_bytes(bytes)?)),
48 => Ok(Self::NistP384(EcdsaNistP384PublicKey::from_bytes(bytes)?)),
66 => Ok(Self::NistP521(EcdsaNistP521PublicKey::from_bytes(bytes)?)),
_ => Err(encoding::Error::Length.into()),
}
}
_ => Err(encoding::Error::Length.into()),
}
}
/// Borrow the SEC1-encoded key data as bytes.
pub fn as_sec1_bytes(&self) -> &[u8] {
match self {
EcdsaPublicKey::NistP256(point) => point.as_bytes(),
EcdsaPublicKey::NistP384(point) => point.as_bytes(),
EcdsaPublicKey::NistP521(point) => point.as_bytes(),
}
}
/// Get the [`Algorithm`] for this public key type.
pub fn algorithm(&self) -> Algorithm {
Algorithm::Ecdsa {
curve: self.curve(),
}
}
/// Get the [`EcdsaCurve`] for this key.
pub fn curve(&self) -> EcdsaCurve {
match self {
EcdsaPublicKey::NistP256(_) => EcdsaCurve::NistP256,
EcdsaPublicKey::NistP384(_) => EcdsaCurve::NistP384,
EcdsaPublicKey::NistP521(_) => EcdsaCurve::NistP521,
}
}
}
impl AsRef<[u8]> for EcdsaPublicKey {
fn as_ref(&self) -> &[u8] {
self.as_sec1_bytes()
}
}
impl Decode for EcdsaPublicKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let curve = EcdsaCurve::decode(reader)?;
let mut buf = [0u8; Self::MAX_SIZE];
let key = Self::from_sec1_bytes(reader.read_byten(&mut buf)?)?;
if key.curve() == curve {
Ok(key)
} else {
Err(Error::AlgorithmUnknown)
}
}
}
impl Encode for EcdsaPublicKey {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.curve().encoded_len()?,
4, // uint32 length prefix
self.as_ref().len(),
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.curve().encode(writer)?;
self.as_ref().encode(writer)?;
Ok(())
}
}
impl fmt::Display for EcdsaPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:X}")
}
}
impl fmt::LowerHex for EcdsaPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_sec1_bytes() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for EcdsaPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_sec1_bytes() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
macro_rules! impl_ecdsa_for_curve {
($krate:ident, $feature:expr, $curve:ident) => {
#[cfg(feature = $feature)]
impl TryFrom<EcdsaPublicKey> for $krate::ecdsa::VerifyingKey {
type Error = Error;
fn try_from(key: EcdsaPublicKey) -> Result<$krate::ecdsa::VerifyingKey> {
$krate::ecdsa::VerifyingKey::try_from(&key)
}
}
#[cfg(feature = $feature)]
impl TryFrom<&EcdsaPublicKey> for $krate::ecdsa::VerifyingKey {
type Error = Error;
fn try_from(public_key: &EcdsaPublicKey) -> Result<$krate::ecdsa::VerifyingKey> {
match public_key {
EcdsaPublicKey::$curve(key) => {
$krate::ecdsa::VerifyingKey::from_encoded_point(key)
.map_err(|_| Error::Crypto)
}
_ => Err(Error::AlgorithmUnknown),
}
}
}
#[cfg(feature = $feature)]
impl From<$krate::ecdsa::VerifyingKey> for EcdsaPublicKey {
fn from(key: $krate::ecdsa::VerifyingKey) -> EcdsaPublicKey {
EcdsaPublicKey::from(&key)
}
}
#[cfg(feature = $feature)]
impl From<&$krate::ecdsa::VerifyingKey> for EcdsaPublicKey {
fn from(key: &$krate::ecdsa::VerifyingKey) -> EcdsaPublicKey {
EcdsaPublicKey::$curve(key.to_encoded_point(false))
}
}
};
}
impl_ecdsa_for_curve!(p256, "p256", NistP256);
impl_ecdsa_for_curve!(p384, "p384", NistP384);
impl_ecdsa_for_curve!(p521, "p521", NistP521);

108
vendor/ssh-key/src/public/ed25519.rs vendored Normal file
View File

@@ -0,0 +1,108 @@
//! Ed25519 public keys.
//!
//! Edwards Digital Signature Algorithm (EdDSA) over Curve25519.
use crate::{Error, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Ed25519 public key.
// TODO(tarcieri): use `ed25519::PublicKey`? (doesn't exist yet)
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Ed25519PublicKey(pub [u8; Self::BYTE_SIZE]);
impl Ed25519PublicKey {
/// Size of an Ed25519 public key in bytes.
pub const BYTE_SIZE: usize = 32;
}
impl AsRef<[u8; Self::BYTE_SIZE]> for Ed25519PublicKey {
fn as_ref(&self) -> &[u8; Self::BYTE_SIZE] {
&self.0
}
}
impl Decode for Ed25519PublicKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let mut bytes = [0u8; Self::BYTE_SIZE];
reader.read_prefixed(|reader| reader.read(&mut bytes))?;
Ok(Self(bytes))
}
}
impl Encode for Ed25519PublicKey {
fn encoded_len(&self) -> encoding::Result<usize> {
[4, Self::BYTE_SIZE].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.0.encode(writer)?;
Ok(())
}
}
impl TryFrom<&[u8]> for Ed25519PublicKey {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
Ok(Self(bytes.try_into()?))
}
}
impl fmt::Display for Ed25519PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:X}")
}
}
impl fmt::LowerHex for Ed25519PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for Ed25519PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<Ed25519PublicKey> for ed25519_dalek::VerifyingKey {
type Error = Error;
fn try_from(key: Ed25519PublicKey) -> Result<ed25519_dalek::VerifyingKey> {
ed25519_dalek::VerifyingKey::try_from(&key)
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<&Ed25519PublicKey> for ed25519_dalek::VerifyingKey {
type Error = Error;
fn try_from(key: &Ed25519PublicKey) -> Result<ed25519_dalek::VerifyingKey> {
ed25519_dalek::VerifyingKey::from_bytes(key.as_ref()).map_err(|_| Error::Crypto)
}
}
#[cfg(feature = "ed25519")]
impl From<ed25519_dalek::VerifyingKey> for Ed25519PublicKey {
fn from(key: ed25519_dalek::VerifyingKey) -> Ed25519PublicKey {
Ed25519PublicKey::from(&key)
}
}
#[cfg(feature = "ed25519")]
impl From<&ed25519_dalek::VerifyingKey> for Ed25519PublicKey {
fn from(key: &ed25519_dalek::VerifyingKey) -> Ed25519PublicKey {
Ed25519PublicKey(key.to_bytes())
}
}

301
vendor/ssh-key/src/public/key_data.rs vendored Normal file
View File

@@ -0,0 +1,301 @@
//! Public key data.
use super::{Ed25519PublicKey, SkEd25519};
use crate::{Algorithm, Error, Fingerprint, HashAlg, Result};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "alloc")]
use super::{DsaPublicKey, OpaquePublicKey, RsaPublicKey};
#[cfg(feature = "ecdsa")]
use super::{EcdsaPublicKey, SkEcdsaSha2NistP256};
/// Public key data.
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum KeyData {
/// Digital Signature Algorithm (DSA) public key data.
#[cfg(feature = "alloc")]
Dsa(DsaPublicKey),
/// Elliptic Curve Digital Signature Algorithm (ECDSA) public key data.
#[cfg(feature = "ecdsa")]
Ecdsa(EcdsaPublicKey),
/// Ed25519 public key data.
Ed25519(Ed25519PublicKey),
/// RSA public key data.
#[cfg(feature = "alloc")]
Rsa(RsaPublicKey),
/// Security Key (FIDO/U2F) using ECDSA/NIST P-256 as specified in [PROTOCOL.u2f].
///
/// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
#[cfg(feature = "ecdsa")]
SkEcdsaSha2NistP256(SkEcdsaSha2NistP256),
/// Security Key (FIDO/U2F) using Ed25519 as specified in [PROTOCOL.u2f].
///
/// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
SkEd25519(SkEd25519),
/// Opaque public key data.
#[cfg(feature = "alloc")]
Other(OpaquePublicKey),
}
impl KeyData {
/// Get the [`Algorithm`] for this public key.
pub fn algorithm(&self) -> Algorithm {
match self {
#[cfg(feature = "alloc")]
Self::Dsa(_) => Algorithm::Dsa,
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.algorithm(),
Self::Ed25519(_) => Algorithm::Ed25519,
#[cfg(feature = "alloc")]
Self::Rsa(_) => Algorithm::Rsa { hash: None },
#[cfg(feature = "ecdsa")]
Self::SkEcdsaSha2NistP256(_) => Algorithm::SkEcdsaSha2NistP256,
Self::SkEd25519(_) => Algorithm::SkEd25519,
#[cfg(feature = "alloc")]
Self::Other(key) => key.algorithm(),
}
}
/// Get DSA public key if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn dsa(&self) -> Option<&DsaPublicKey> {
match self {
Self::Dsa(key) => Some(key),
_ => None,
}
}
/// Get ECDSA public key if this key is the correct type.
#[cfg(feature = "ecdsa")]
pub fn ecdsa(&self) -> Option<&EcdsaPublicKey> {
match self {
Self::Ecdsa(key) => Some(key),
_ => None,
}
}
/// Get Ed25519 public key if this key is the correct type.
pub fn ed25519(&self) -> Option<&Ed25519PublicKey> {
match self {
Self::Ed25519(key) => Some(key),
#[allow(unreachable_patterns)]
_ => None,
}
}
/// Compute key fingerprint.
///
/// Use [`Default::default()`] to use the default hash function (SHA-256).
pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
Fingerprint::new(hash_alg, self)
}
/// Get RSA public key if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn rsa(&self) -> Option<&RsaPublicKey> {
match self {
Self::Rsa(key) => Some(key),
_ => None,
}
}
/// Get FIDO/U2F ECDSA/NIST P-256 public key if this key is the correct type.
#[cfg(feature = "ecdsa")]
pub fn sk_ecdsa_p256(&self) -> Option<&SkEcdsaSha2NistP256> {
match self {
Self::SkEcdsaSha2NistP256(sk) => Some(sk),
_ => None,
}
}
/// Get FIDO/U2F Ed25519 public key if this key is the correct type.
pub fn sk_ed25519(&self) -> Option<&SkEd25519> {
match self {
Self::SkEd25519(sk) => Some(sk),
_ => None,
}
}
/// Get the custom, opaque public key if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn other(&self) -> Option<&OpaquePublicKey> {
match self {
Self::Other(key) => Some(key),
_ => None,
}
}
/// Is this key a DSA key?
#[cfg(feature = "alloc")]
pub fn is_dsa(&self) -> bool {
matches!(self, Self::Dsa(_))
}
/// Is this key an ECDSA key?
#[cfg(feature = "ecdsa")]
pub fn is_ecdsa(&self) -> bool {
matches!(self, Self::Ecdsa(_))
}
/// Is this key an Ed25519 key?
pub fn is_ed25519(&self) -> bool {
matches!(self, Self::Ed25519(_))
}
/// Is this key an RSA key?
#[cfg(feature = "alloc")]
pub fn is_rsa(&self) -> bool {
matches!(self, Self::Rsa(_))
}
/// Is this key a FIDO/U2F ECDSA/NIST P-256 key?
#[cfg(feature = "ecdsa")]
pub fn is_sk_ecdsa_p256(&self) -> bool {
matches!(self, Self::SkEcdsaSha2NistP256(_))
}
/// Is this key a FIDO/U2F Ed25519 key?
pub fn is_sk_ed25519(&self) -> bool {
matches!(self, Self::SkEd25519(_))
}
/// Is this a key with a custom algorithm?
#[cfg(feature = "alloc")]
pub fn is_other(&self) -> bool {
matches!(self, Self::Other(_))
}
/// Decode [`KeyData`] for the specified algorithm.
pub(crate) fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result<Self> {
match algorithm {
#[cfg(feature = "alloc")]
Algorithm::Dsa => DsaPublicKey::decode(reader).map(Self::Dsa),
#[cfg(feature = "ecdsa")]
Algorithm::Ecdsa { curve } => match EcdsaPublicKey::decode(reader)? {
key if key.curve() == curve => Ok(Self::Ecdsa(key)),
_ => Err(Error::AlgorithmUnknown),
},
Algorithm::Ed25519 => Ed25519PublicKey::decode(reader).map(Self::Ed25519),
#[cfg(feature = "alloc")]
Algorithm::Rsa { .. } => RsaPublicKey::decode(reader).map(Self::Rsa),
#[cfg(feature = "ecdsa")]
Algorithm::SkEcdsaSha2NistP256 => {
SkEcdsaSha2NistP256::decode(reader).map(Self::SkEcdsaSha2NistP256)
}
Algorithm::SkEd25519 => SkEd25519::decode(reader).map(Self::SkEd25519),
#[cfg(feature = "alloc")]
Algorithm::Other(_) => OpaquePublicKey::decode_as(reader, algorithm).map(Self::Other),
#[allow(unreachable_patterns)]
_ => Err(Error::AlgorithmUnknown),
}
}
/// Get the encoded length of this key data without a leading algorithm
/// identifier.
pub(crate) fn encoded_key_data_len(&self) -> encoding::Result<usize> {
match self {
#[cfg(feature = "alloc")]
Self::Dsa(key) => key.encoded_len(),
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.encoded_len(),
Self::Ed25519(key) => key.encoded_len(),
#[cfg(feature = "alloc")]
Self::Rsa(key) => key.encoded_len(),
#[cfg(feature = "ecdsa")]
Self::SkEcdsaSha2NistP256(sk) => sk.encoded_len(),
Self::SkEd25519(sk) => sk.encoded_len(),
#[cfg(feature = "alloc")]
Self::Other(other) => other.key.encoded_len(),
}
}
/// Encode the key data without a leading algorithm identifier.
pub(crate) fn encode_key_data(&self, writer: &mut impl Writer) -> encoding::Result<()> {
match self {
#[cfg(feature = "alloc")]
Self::Dsa(key) => key.encode(writer),
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.encode(writer),
Self::Ed25519(key) => key.encode(writer),
#[cfg(feature = "alloc")]
Self::Rsa(key) => key.encode(writer),
#[cfg(feature = "ecdsa")]
Self::SkEcdsaSha2NistP256(sk) => sk.encode(writer),
Self::SkEd25519(sk) => sk.encode(writer),
#[cfg(feature = "alloc")]
Self::Other(other) => other.key.encode(writer),
}
}
}
impl Decode for KeyData {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let algorithm = Algorithm::decode(reader)?;
Self::decode_as(reader, algorithm)
}
}
impl Encode for KeyData {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.algorithm().encoded_len()?,
self.encoded_key_data_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.algorithm().encode(writer)?;
self.encode_key_data(writer)
}
}
#[cfg(feature = "alloc")]
impl From<DsaPublicKey> for KeyData {
fn from(public_key: DsaPublicKey) -> KeyData {
Self::Dsa(public_key)
}
}
#[cfg(feature = "ecdsa")]
impl From<EcdsaPublicKey> for KeyData {
fn from(public_key: EcdsaPublicKey) -> KeyData {
Self::Ecdsa(public_key)
}
}
impl From<Ed25519PublicKey> for KeyData {
fn from(public_key: Ed25519PublicKey) -> KeyData {
Self::Ed25519(public_key)
}
}
#[cfg(feature = "alloc")]
impl From<RsaPublicKey> for KeyData {
fn from(public_key: RsaPublicKey) -> KeyData {
Self::Rsa(public_key)
}
}
#[cfg(feature = "ecdsa")]
impl From<SkEcdsaSha2NistP256> for KeyData {
fn from(public_key: SkEcdsaSha2NistP256) -> KeyData {
Self::SkEcdsaSha2NistP256(public_key)
}
}
impl From<SkEd25519> for KeyData {
fn from(public_key: SkEd25519) -> KeyData {
Self::SkEd25519(public_key)
}
}

98
vendor/ssh-key/src/public/opaque.rs vendored Normal file
View File

@@ -0,0 +1,98 @@
//! Opaque public keys.
//!
//! [`OpaquePublicKey`] represents a public key meant to be used with an algorithm unknown to this
//! crate, i.e. public keys that use a custom algorithm as specified in [RFC4251 § 6].
//!
//! They are said to be opaque, because the meaning of their underlying byte representation is not
//! specified.
//!
//! [RFC4251 § 6]: https://www.rfc-editor.org/rfc/rfc4251.html#section-6
use crate::{Algorithm, Error, Result};
use alloc::vec::Vec;
use encoding::{Decode, Encode, Reader, Writer};
/// An opaque public key with a custom algorithm name.
///
/// The encoded representation of an `OpaquePublicKey` is the encoded representation of its
/// [`OpaquePublicKeyBytes`].
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct OpaquePublicKey {
/// The [`Algorithm`] of this public key.
pub algorithm: Algorithm,
/// The key data
pub key: OpaquePublicKeyBytes,
}
/// The underlying representation of an [`OpaquePublicKey`].
///
/// The encoded representation of an `OpaquePublicKeyBytes` consists of a 4-byte length prefix,
/// followed by its byte representation.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct OpaquePublicKeyBytes(Vec<u8>);
impl OpaquePublicKey {
/// Create a new `OpaquePublicKey`.
pub fn new(key: Vec<u8>, algorithm: Algorithm) -> Self {
Self {
key: OpaquePublicKeyBytes(key),
algorithm,
}
}
/// Get the [`Algorithm`] for this public key type.
pub fn algorithm(&self) -> Algorithm {
self.algorithm.clone()
}
/// Decode [`OpaquePublicKey`] for the specified algorithm.
pub(super) fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result<Self> {
Ok(Self {
algorithm,
key: OpaquePublicKeyBytes::decode(reader)?,
})
}
}
impl Decode for OpaquePublicKeyBytes {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let len = usize::decode(reader)?;
let mut bytes = vec![0; len];
reader.read(&mut bytes)?;
Ok(Self(bytes))
}
}
impl Encode for OpaquePublicKeyBytes {
fn encoded_len(&self) -> encoding::Result<usize> {
self.0.encoded_len()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.0.encode(writer)
}
}
impl Encode for OpaquePublicKey {
fn encoded_len(&self) -> encoding::Result<usize> {
self.key.encoded_len()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.key.encode(writer)
}
}
impl AsRef<[u8]> for OpaquePublicKey {
fn as_ref(&self) -> &[u8] {
self.key.as_ref()
}
}
impl AsRef<[u8]> for OpaquePublicKeyBytes {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

120
vendor/ssh-key/src/public/rsa.rs vendored Normal file
View File

@@ -0,0 +1,120 @@
//! RivestShamirAdleman (RSA) public keys.
use crate::{Error, Mpint, Result};
use core::hash::{Hash, Hasher};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "rsa")]
use {
crate::private::RsaKeypair,
rsa::{pkcs1v15, traits::PublicKeyParts},
sha2::{digest::const_oid::AssociatedOid, Digest},
};
/// RSA public key.
///
/// Described in [RFC4253 § 6.6](https://datatracker.ietf.org/doc/html/rfc4253#section-6.6).
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct RsaPublicKey {
/// RSA public exponent.
pub e: Mpint,
/// RSA modulus.
pub n: Mpint,
}
impl RsaPublicKey {
/// Minimum allowed RSA key size.
#[cfg(feature = "rsa")]
pub(crate) const MIN_KEY_SIZE: usize = RsaKeypair::MIN_KEY_SIZE;
}
impl Decode for RsaPublicKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let e = Mpint::decode(reader)?;
let n = Mpint::decode(reader)?;
Ok(Self { e, n })
}
}
impl Encode for RsaPublicKey {
fn encoded_len(&self) -> encoding::Result<usize> {
[self.e.encoded_len()?, self.n.encoded_len()?].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.e.encode(writer)?;
self.n.encode(writer)
}
}
impl Hash for RsaPublicKey {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.e.as_bytes().hash(state);
self.n.as_bytes().hash(state);
}
}
#[cfg(feature = "rsa")]
impl TryFrom<RsaPublicKey> for rsa::RsaPublicKey {
type Error = Error;
fn try_from(key: RsaPublicKey) -> Result<rsa::RsaPublicKey> {
rsa::RsaPublicKey::try_from(&key)
}
}
#[cfg(feature = "rsa")]
impl TryFrom<&RsaPublicKey> for rsa::RsaPublicKey {
type Error = Error;
fn try_from(key: &RsaPublicKey) -> Result<rsa::RsaPublicKey> {
let ret = rsa::RsaPublicKey::new(
rsa::BigUint::try_from(&key.n)?,
rsa::BigUint::try_from(&key.e)?,
)
.map_err(|_| Error::Crypto)?;
if ret.size().saturating_mul(8) >= RsaPublicKey::MIN_KEY_SIZE {
Ok(ret)
} else {
Err(Error::Crypto)
}
}
}
#[cfg(feature = "rsa")]
impl TryFrom<rsa::RsaPublicKey> for RsaPublicKey {
type Error = Error;
fn try_from(key: rsa::RsaPublicKey) -> Result<RsaPublicKey> {
RsaPublicKey::try_from(&key)
}
}
#[cfg(feature = "rsa")]
impl TryFrom<&rsa::RsaPublicKey> for RsaPublicKey {
type Error = Error;
fn try_from(key: &rsa::RsaPublicKey) -> Result<RsaPublicKey> {
Ok(RsaPublicKey {
e: key.e().try_into()?,
n: key.n().try_into()?,
})
}
}
#[cfg(feature = "rsa")]
impl<D> TryFrom<&RsaPublicKey> for pkcs1v15::VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
type Error = Error;
fn try_from(key: &RsaPublicKey) -> Result<pkcs1v15::VerifyingKey<D>> {
Ok(pkcs1v15::VerifyingKey::new(key.try_into()?))
}
}

211
vendor/ssh-key/src/public/sk.rs vendored Normal file
View File

@@ -0,0 +1,211 @@
//! Security Key (FIDO/U2F) public keys as described in [PROTOCOL.u2f].
//!
//! [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
use super::Ed25519PublicKey;
use crate::{Error, Result};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "alloc")]
use alloc::{borrow::ToOwned, string::String};
#[cfg(feature = "ecdsa")]
use crate::{public::ecdsa::EcdsaNistP256PublicKey, EcdsaCurve};
/// Default FIDO/U2F Security Key application string.
const DEFAULT_APPLICATION_STRING: &str = "ssh:";
/// Security Key (FIDO/U2F) ECDSA/NIST P-256 public key as specified in
/// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD).
#[cfg(feature = "ecdsa")]
#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
pub struct SkEcdsaSha2NistP256 {
/// Elliptic curve point representing a public key.
ec_point: EcdsaNistP256PublicKey,
/// FIDO/U2F application (typically `ssh:`)
#[cfg(feature = "alloc")]
application: String,
}
#[cfg(feature = "ecdsa")]
impl SkEcdsaSha2NistP256 {
/// Construct new instance of SkEcdsaSha2NistP256.
#[cfg(feature = "alloc")]
pub fn new(ec_point: EcdsaNistP256PublicKey, application: impl Into<String>) -> Self {
SkEcdsaSha2NistP256 {
ec_point,
application: application.into(),
}
}
/// Get the elliptic curve point for this Security Key.
pub fn ec_point(&self) -> &EcdsaNistP256PublicKey {
&self.ec_point
}
/// Get the FIDO/U2F application (typically `ssh:`).
#[cfg(not(feature = "alloc"))]
pub fn application(&self) -> &str {
DEFAULT_APPLICATION_STRING
}
/// Get the FIDO/U2F application (typically `ssh:`).
#[cfg(feature = "alloc")]
pub fn application(&self) -> &str {
&self.application
}
}
#[cfg(feature = "ecdsa")]
impl Decode for SkEcdsaSha2NistP256 {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
if EcdsaCurve::decode(reader)? != EcdsaCurve::NistP256 {
return Err(Error::Crypto);
}
let mut buf = [0u8; 65];
let ec_point = EcdsaNistP256PublicKey::from_bytes(reader.read_byten(&mut buf)?)?;
// application string (e.g. `ssh:`)
#[cfg(not(feature = "alloc"))]
reader.drain_prefixed()?;
Ok(Self {
ec_point,
#[cfg(feature = "alloc")]
application: String::decode(reader)?,
})
}
}
#[cfg(feature = "ecdsa")]
impl Encode for SkEcdsaSha2NistP256 {
fn encoded_len(&self) -> encoding::Result<usize> {
[
EcdsaCurve::NistP256.encoded_len()?,
self.ec_point.as_bytes().encoded_len()?,
self.application().encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
EcdsaCurve::NistP256.encode(writer)?;
self.ec_point.as_bytes().encode(writer)?;
self.application().encode(writer)?;
Ok(())
}
}
#[cfg(feature = "ecdsa")]
impl From<EcdsaNistP256PublicKey> for SkEcdsaSha2NistP256 {
fn from(ec_point: EcdsaNistP256PublicKey) -> SkEcdsaSha2NistP256 {
SkEcdsaSha2NistP256 {
ec_point,
#[cfg(feature = "alloc")]
application: DEFAULT_APPLICATION_STRING.to_owned(),
}
}
}
#[cfg(feature = "ecdsa")]
impl From<SkEcdsaSha2NistP256> for EcdsaNistP256PublicKey {
fn from(sk: SkEcdsaSha2NistP256) -> EcdsaNistP256PublicKey {
sk.ec_point
}
}
/// Security Key (FIDO/U2F) Ed25519 public key as specified in
/// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD).
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct SkEd25519 {
/// Ed25519 public key.
public_key: Ed25519PublicKey,
/// FIDO/U2F application (typically `ssh:`)
#[cfg(feature = "alloc")]
application: String,
}
impl SkEd25519 {
/// Construct new instance of SkEd25519.
#[cfg(feature = "alloc")]
pub fn new(public_key: Ed25519PublicKey, application: impl Into<String>) -> Self {
SkEd25519 {
public_key,
application: application.into(),
}
}
/// Get the Ed25519 private key for this security key.
pub fn public_key(&self) -> &Ed25519PublicKey {
&self.public_key
}
/// Get the FIDO/U2F application (typically `ssh:`).
#[cfg(not(feature = "alloc"))]
pub fn application(&self) -> &str {
DEFAULT_APPLICATION_STRING
}
/// Get the FIDO/U2F application (typically `ssh:`).
#[cfg(feature = "alloc")]
pub fn application(&self) -> &str {
&self.application
}
}
impl Decode for SkEd25519 {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let public_key = Ed25519PublicKey::decode(reader)?;
// application string (e.g. `ssh:`)
#[cfg(not(feature = "alloc"))]
reader.drain_prefixed()?;
Ok(Self {
public_key,
#[cfg(feature = "alloc")]
application: String::decode(reader)?,
})
}
}
impl Encode for SkEd25519 {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.public_key.encoded_len()?,
self.application().encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public_key.encode(writer)?;
self.application().encode(writer)?;
Ok(())
}
}
impl From<Ed25519PublicKey> for SkEd25519 {
fn from(public_key: Ed25519PublicKey) -> SkEd25519 {
SkEd25519 {
public_key,
#[cfg(feature = "alloc")]
application: DEFAULT_APPLICATION_STRING.to_owned(),
}
}
}
impl From<SkEd25519> for Ed25519PublicKey {
fn from(sk: SkEd25519) -> Ed25519PublicKey {
sk.public_key
}
}

192
vendor/ssh-key/src/public/ssh_format.rs vendored Normal file
View File

@@ -0,0 +1,192 @@
//! Support for OpenSSH-formatted public keys, a.k.a. `SSH-format`.
//!
//! These keys have the form:
//!
//! ```text
//! <algorithm id> <base64 key data> <comment>
//! ```
//!
//! ## Example
//!
//! ```text
//! ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com
//! ```
use crate::Result;
use core::str;
use encoding::{Base64Writer, Encode};
#[cfg(feature = "alloc")]
use {alloc::string::String, encoding::CheckedSum};
/// OpenSSH public key (a.k.a. `SSH-format`) decoder/encoder.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SshFormat<'a> {
/// Algorithm identifier
pub(crate) algorithm_id: &'a str,
/// Base64-encoded key data
pub(crate) base64_data: &'a [u8],
/// Comment
#[cfg_attr(not(feature = "alloc"), allow(dead_code))]
pub(crate) comment: &'a str,
}
impl<'a> SshFormat<'a> {
/// Parse the given binary data.
pub(crate) fn decode(mut bytes: &'a [u8]) -> Result<Self> {
let algorithm_id = decode_segment_str(&mut bytes)?;
let base64_data = decode_segment(&mut bytes)?;
let comment = str::from_utf8(bytes)?.trim_end();
if algorithm_id.is_empty() || base64_data.is_empty() {
// TODO(tarcieri): better errors for these cases?
return Err(encoding::Error::Length.into());
}
Ok(Self {
algorithm_id,
base64_data,
comment,
})
}
/// Encode data with OpenSSH public key encapsulation.
pub(crate) fn encode<'o, K>(
algorithm_id: &str,
key: &K,
comment: &str,
out: &'o mut [u8],
) -> Result<&'o str>
where
K: Encode,
{
let mut offset = 0;
encode_str(out, &mut offset, algorithm_id)?;
encode_str(out, &mut offset, " ")?;
let mut writer = Base64Writer::new(&mut out[offset..])?;
key.encode(&mut writer)?;
let base64_len = writer.finish()?.len();
offset = offset
.checked_add(base64_len)
.ok_or(encoding::Error::Length)?;
if !comment.is_empty() {
encode_str(out, &mut offset, " ")?;
encode_str(out, &mut offset, comment)?;
}
Ok(str::from_utf8(&out[..offset])?)
}
/// Encode string with OpenSSH public key encapsulation.
#[cfg(feature = "alloc")]
pub(crate) fn encode_string<K>(algorithm_id: &str, key: &K, comment: &str) -> Result<String>
where
K: Encode,
{
let encoded_len = [
2, // interstitial spaces
algorithm_id.len(),
base64_len_approx(key.encoded_len()?),
comment.len(),
]
.checked_sum()?;
let mut out = vec![0u8; encoded_len];
let actual_len = Self::encode(algorithm_id, key, comment, &mut out)?.len();
out.truncate(actual_len);
Ok(String::from_utf8(out)?)
}
}
/// Get the estimated length of data when encoded as Base64.
///
/// This is an upper bound where the actual length might be slightly shorter,
/// and can be used to estimate the capacity of an output buffer. However, the
/// final result may need to be sliced and should use the actual encoded length
/// rather than this estimate.
#[cfg(feature = "alloc")]
fn base64_len_approx(input_len: usize) -> usize {
(((input_len.saturating_mul(4)) / 3).saturating_add(3)) & !3
}
/// Parse a segment of the public key.
fn decode_segment<'a>(bytes: &mut &'a [u8]) -> Result<&'a [u8]> {
let start = *bytes;
let mut len = 0usize;
loop {
match *bytes {
[b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'+' | b'-' | b'/' | b'=' | b'@' | b'.', rest @ ..] =>
{
// Valid character; continue
*bytes = rest;
len = len.checked_add(1).ok_or(encoding::Error::Length)?;
}
[b' ', rest @ ..] => {
// Encountered space; we're done
*bytes = rest;
return start
.get(..len)
.ok_or_else(|| encoding::Error::Length.into());
}
[_, ..] => {
// Invalid character
return Err(encoding::Error::CharacterEncoding.into());
}
[] => {
// End of input, could be truncated or could be no comment
return start
.get(..len)
.ok_or_else(|| encoding::Error::Length.into());
}
}
}
}
/// Parse a segment of the public key as a `&str`.
fn decode_segment_str<'a>(bytes: &mut &'a [u8]) -> Result<&'a str> {
str::from_utf8(decode_segment(bytes)?).map_err(|_| encoding::Error::CharacterEncoding.into())
}
/// Encode a segment of the public key.
fn encode_str(out: &mut [u8], offset: &mut usize, s: &str) -> Result<()> {
let bytes = s.as_bytes();
if out.len()
< offset
.checked_add(bytes.len())
.ok_or(encoding::Error::Length)?
{
return Err(encoding::Error::Length.into());
}
out[*offset..][..bytes.len()].copy_from_slice(bytes);
*offset = offset
.checked_add(bytes.len())
.ok_or(encoding::Error::Length)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::SshFormat;
const EXAMPLE_KEY: &str = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com";
#[test]
fn decode() {
let encapsulation = SshFormat::decode(EXAMPLE_KEY.as_bytes()).unwrap();
assert_eq!(encapsulation.algorithm_id, "ssh-ed25519");
assert_eq!(
encapsulation.base64_data,
b"AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti"
);
assert_eq!(encapsulation.comment, "user@example.com");
}
}

936
vendor/ssh-key/src/signature.rs vendored Normal file
View File

@@ -0,0 +1,936 @@
//! Signatures (e.g. CA signatures over SSH certificates)
use crate::{private, public, Algorithm, EcdsaCurve, Error, Mpint, PrivateKey, PublicKey, Result};
use alloc::vec::Vec;
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use signature::{SignatureEncoding, Signer, Verifier};
#[cfg(feature = "ed25519")]
use crate::{private::Ed25519Keypair, public::Ed25519PublicKey};
#[cfg(feature = "dsa")]
use {
crate::{private::DsaKeypair, public::DsaPublicKey},
bigint::BigUint,
sha1::Sha1,
signature::{DigestSigner, DigestVerifier},
};
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
use crate::{
private::{EcdsaKeypair, EcdsaPrivateKey},
public::EcdsaPublicKey,
};
#[cfg(any(feature = "dsa", feature = "p256", feature = "p384", feature = "p521"))]
use core::iter;
#[cfg(feature = "rsa")]
use {
crate::{private::RsaKeypair, public::RsaPublicKey, HashAlg},
sha2::Sha512,
};
#[cfg(any(feature = "ed25519", feature = "rsa", feature = "p256"))]
use sha2::Sha256;
#[cfg(any(feature = "dsa", feature = "ed25519", feature = "p256"))]
use sha2::Digest;
const DSA_SIGNATURE_SIZE: usize = 40;
const ED25519_SIGNATURE_SIZE: usize = 64;
const SK_SIGNATURE_TRAILER_SIZE: usize = 5; // flags(u8), counter(u32)
const SK_ED25519_SIGNATURE_SIZE: usize = ED25519_SIGNATURE_SIZE + SK_SIGNATURE_TRAILER_SIZE;
/// Trait for signing keys which produce a [`Signature`].
///
/// This trait is automatically impl'd for any types which impl the
/// [`Signer`] trait for the SSH [`Signature`] type and also support a [`From`]
/// conversion for [`public::KeyData`].
pub trait SigningKey: Signer<Signature> {
/// Get the [`public::KeyData`] for this signing key.
fn public_key(&self) -> public::KeyData;
}
impl<T> SigningKey for T
where
T: Signer<Signature>,
public::KeyData: for<'a> From<&'a T>,
{
fn public_key(&self) -> public::KeyData {
self.into()
}
}
/// Low-level digital signature (e.g. DSA, ECDSA, Ed25519).
///
/// These are low-level signatures used as part of the OpenSSH certificate
/// format to represent signatures by certificate authorities (CAs), as well
/// as the higher-level [`SshSig`][`crate::SshSig`] format, which provides
/// general-purpose signing functionality using SSH keys.
///
/// From OpenSSH's [PROTOCOL.certkeys] specification:
///
/// > Signatures are computed and encoded according to the rules defined for
/// > the CA's public key algorithm ([RFC4253 section 6.6] for ssh-rsa and
/// > ssh-dss, [RFC5656] for the ECDSA types, and [RFC8032] for Ed25519).
///
/// RSA signature support is implemented using the SHA2 family extensions as
/// described in [RFC8332].
///
/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
/// [RFC4253 section 6.6]: https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
/// [RFC5656]: https://datatracker.ietf.org/doc/html/rfc5656
/// [RFC8032]: https://datatracker.ietf.org/doc/html/rfc8032
/// [RFC8332]: https://datatracker.ietf.org/doc/html/rfc8332
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct Signature {
/// Signature algorithm.
algorithm: Algorithm,
/// Raw signature serialized as algorithm-specific byte encoding.
data: Vec<u8>,
}
impl Signature {
/// Create a new signature with the given algorithm and raw signature data.
///
/// See specifications in toplevel [`Signature`] documentation for how to
/// format the raw signature data for a given algorithm.
///
/// # Returns
/// - [`Error::Encoding`] if the signature is not the correct length.
pub fn new(algorithm: Algorithm, data: impl Into<Vec<u8>>) -> Result<Self> {
let data = data.into();
// Validate signature is well-formed per OpensSH encoding
match algorithm {
Algorithm::Dsa if data.len() == DSA_SIGNATURE_SIZE => (),
Algorithm::Ecdsa { curve } => ecdsa_sig_size(&data, curve, false)?,
Algorithm::Ed25519 if data.len() == ED25519_SIGNATURE_SIZE => (),
Algorithm::SkEd25519 if data.len() == SK_ED25519_SIGNATURE_SIZE => (),
Algorithm::SkEcdsaSha2NistP256 => ecdsa_sig_size(&data, EcdsaCurve::NistP256, true)?,
Algorithm::Rsa { hash: Some(_) } => (),
Algorithm::Other(_) if !data.is_empty() => (),
_ => return Err(encoding::Error::Length.into()),
}
Ok(Self { algorithm, data })
}
/// Get the [`Algorithm`] associated with this signature.
pub fn algorithm(&self) -> Algorithm {
self.algorithm.clone()
}
/// Get the raw signature as bytes.
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
/// Placeholder signature used by the certificate builder.
///
/// This is guaranteed generate an error if anything attempts to encode it.
pub(crate) fn placeholder() -> Self {
Self {
algorithm: Algorithm::default(),
data: Vec::new(),
}
}
/// Check if this signature is the placeholder signature.
pub(crate) fn is_placeholder(&self) -> bool {
self.algorithm == Algorithm::default() && self.data.is_empty()
}
}
/// Returns Ok() if data holds an ecdsa signature with components of appropriate size
/// according to curve.
fn ecdsa_sig_size(data: &Vec<u8>, curve: EcdsaCurve, sk_trailer: bool) -> Result<()> {
let reader = &mut data.as_slice();
for _ in 0..2 {
let component = Mpint::decode(reader)?;
if component.as_positive_bytes().ok_or(Error::Crypto)?.len() > curve.field_size() {
return Err(encoding::Error::Length.into());
}
}
if sk_trailer {
reader.drain(SK_SIGNATURE_TRAILER_SIZE)?;
}
reader
.finish(())
.map_err(|_| encoding::Error::Length.into())
}
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Decode for Signature {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let algorithm = Algorithm::decode(reader)?;
let mut data = Vec::decode(reader)?;
if algorithm == Algorithm::SkEd25519 || algorithm == Algorithm::SkEcdsaSha2NistP256 {
let flags = u8::decode(reader)?;
let counter = u32::decode(reader)?;
data.push(flags);
data.extend(counter.to_be_bytes());
}
Self::new(algorithm, data)
}
}
impl Encode for Signature {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.algorithm().encoded_len()?,
self.as_bytes().encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
if self.is_placeholder() {
return Err(encoding::Error::Length);
}
self.algorithm().encode(writer)?;
if self.algorithm == Algorithm::SkEd25519 {
let signature_length = self
.as_bytes()
.len()
.checked_sub(SK_SIGNATURE_TRAILER_SIZE)
.ok_or(encoding::Error::Length)?;
self.as_bytes()[..signature_length].encode(writer)?;
writer.write(&self.as_bytes()[signature_length..])?;
} else {
self.as_bytes().encode(writer)?;
}
Ok(())
}
}
impl SignatureEncoding for Signature {
type Repr = Vec<u8>;
}
/// Decode [`Signature`] from an [`Algorithm`]-prefixed OpenSSH-encoded bytestring.
impl TryFrom<&[u8]> for Signature {
type Error = Error;
fn try_from(mut bytes: &[u8]) -> Result<Self> {
Self::decode(&mut bytes)
}
}
impl TryFrom<Signature> for Vec<u8> {
type Error = Error;
fn try_from(signature: Signature) -> Result<Vec<u8>> {
let mut ret = Vec::<u8>::new();
signature.encode(&mut ret)?;
Ok(ret)
}
}
impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Signature {{ algorithm: {:?}, data: {:X} }}",
self.algorithm, self
)
}
}
impl fmt::LowerHex for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
impl Signer<Signature> for PrivateKey {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
self.key_data().try_sign(message)
}
}
impl Signer<Signature> for private::KeypairData {
#[allow(unused_variables)]
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
match self {
#[cfg(feature = "dsa")]
Self::Dsa(keypair) => keypair.try_sign(message),
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
Self::Ecdsa(keypair) => keypair.try_sign(message),
#[cfg(feature = "ed25519")]
Self::Ed25519(keypair) => keypair.try_sign(message),
#[cfg(feature = "rsa")]
Self::Rsa(keypair) => keypair.try_sign(message),
_ => Err(self.algorithm()?.unsupported_error().into()),
}
}
}
impl Verifier<Signature> for PublicKey {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
self.key_data().verify(message, signature)
}
}
impl Verifier<Signature> for public::KeyData {
#[allow(unused_variables)]
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
match self {
#[cfg(feature = "dsa")]
Self::Dsa(pk) => pk.verify(message, signature),
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
Self::Ecdsa(pk) => pk.verify(message, signature),
#[cfg(feature = "ed25519")]
Self::Ed25519(pk) => pk.verify(message, signature),
#[cfg(feature = "ed25519")]
Self::SkEd25519(pk) => pk.verify(message, signature),
#[cfg(feature = "p256")]
Self::SkEcdsaSha2NistP256(pk) => pk.verify(message, signature),
#[cfg(feature = "rsa")]
Self::Rsa(pk) => pk.verify(message, signature),
#[allow(unreachable_patterns)]
_ => Err(self.algorithm().unsupported_error().into()),
}
}
}
#[cfg(feature = "dsa")]
impl Signer<Signature> for DsaKeypair {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
let signature = dsa::SigningKey::try_from(self)?
.try_sign_digest(Sha1::new_with_prefix(message))
.map_err(|_| signature::Error::new())?;
// Encode the format specified in RFC4253 section 6.6: two raw 80-bit integers concatenated
let mut data = Vec::new();
for component in [signature.r(), signature.s()] {
let mut bytes = component.to_bytes_be();
let pad_len = (DSA_SIGNATURE_SIZE / 2).saturating_sub(bytes.len());
data.extend(iter::repeat(0).take(pad_len));
data.append(&mut bytes);
}
debug_assert_eq!(data.len(), DSA_SIGNATURE_SIZE);
Ok(Signature {
algorithm: Algorithm::Dsa,
data,
})
}
}
#[cfg(feature = "dsa")]
impl Verifier<Signature> for DsaPublicKey {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
match signature.algorithm {
Algorithm::Dsa => {
let data = signature.data.as_slice();
if data.len() != DSA_SIGNATURE_SIZE {
return Err(signature::Error::new());
}
let (r, s) = data.split_at(DSA_SIGNATURE_SIZE / 2);
let signature = dsa::Signature::from_components(
BigUint::from_bytes_be(r),
BigUint::from_bytes_be(s),
)?;
dsa::VerifyingKey::try_from(self)?
.verify_digest(Sha1::new_with_prefix(message), &signature)
.map_err(|_| signature::Error::new())
}
_ => Err(signature.algorithm().unsupported_error().into()),
}
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<Signature> for ed25519_dalek::Signature {
type Error = Error;
fn try_from(signature: Signature) -> Result<ed25519_dalek::Signature> {
ed25519_dalek::Signature::try_from(&signature)
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<&Signature> for ed25519_dalek::Signature {
type Error = Error;
fn try_from(signature: &Signature) -> Result<ed25519_dalek::Signature> {
match signature.algorithm {
Algorithm::Ed25519 | Algorithm::SkEd25519 => {
Ok(ed25519_dalek::Signature::try_from(signature.as_bytes())?)
}
_ => Err(Error::AlgorithmUnknown),
}
}
}
#[cfg(feature = "ed25519")]
impl Signer<Signature> for Ed25519Keypair {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
let signature = ed25519_dalek::SigningKey::try_from(self)?.sign(message);
Ok(Signature {
algorithm: Algorithm::Ed25519,
data: signature.to_vec(),
})
}
}
#[cfg(feature = "ed25519")]
impl Verifier<Signature> for Ed25519PublicKey {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
let signature = ed25519_dalek::Signature::try_from(signature)?;
ed25519_dalek::VerifyingKey::try_from(self)?.verify(message, &signature)
}
}
#[cfg(feature = "ed25519")]
impl Verifier<Signature> for public::SkEd25519 {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
let (signature, flags_and_counter) = split_sk_signature(signature)?;
let signature = ed25519_dalek::Signature::try_from(signature)?;
ed25519_dalek::VerifyingKey::try_from(self.public_key())?.verify(
&make_sk_signed_data(self.application(), flags_and_counter, message),
&signature,
)
}
}
#[cfg(feature = "p256")]
impl Verifier<Signature> for public::SkEcdsaSha2NistP256 {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
let (signature_bytes, flags_and_counter) = split_sk_signature(signature)?;
let signature = p256_signature_from_openssh_bytes(signature_bytes)?;
p256::ecdsa::VerifyingKey::from_encoded_point(self.ec_point())?.verify(
&make_sk_signed_data(self.application(), flags_and_counter, message),
&signature,
)
}
}
#[cfg(any(feature = "p256", feature = "ed25519"))]
fn make_sk_signed_data(application: &str, flags_and_counter: &[u8], message: &[u8]) -> Vec<u8> {
const SHA256_OUTPUT_LENGTH: usize = 32;
const SIGNED_SK_DATA_LENGTH: usize = 2 * SHA256_OUTPUT_LENGTH + SK_SIGNATURE_TRAILER_SIZE;
let mut signed_data = Vec::with_capacity(SIGNED_SK_DATA_LENGTH);
signed_data.extend(Sha256::digest(application));
signed_data.extend(flags_and_counter);
signed_data.extend(Sha256::digest(message));
signed_data
}
#[cfg(any(feature = "p256", feature = "ed25519"))]
fn split_sk_signature(signature: &Signature) -> Result<(&[u8], &[u8])> {
let signature_bytes = signature.as_bytes();
let signature_len = signature_bytes
.len()
.checked_sub(SK_SIGNATURE_TRAILER_SIZE)
.ok_or(Error::Encoding(encoding::Error::Length))?;
Ok((
&signature_bytes[..signature_len],
&signature_bytes[signature_len..],
))
}
macro_rules! impl_signature_for_curve {
($krate:ident, $feature:expr, $curve:ident, $size:expr) => {
#[cfg(feature = $feature)]
impl TryFrom<$krate::ecdsa::Signature> for Signature {
type Error = Error;
fn try_from(signature: $krate::ecdsa::Signature) -> Result<Signature> {
Signature::try_from(&signature)
}
}
#[cfg(feature = $feature)]
impl TryFrom<&$krate::ecdsa::Signature> for Signature {
type Error = Error;
fn try_from(signature: &$krate::ecdsa::Signature) -> Result<Signature> {
let (r, s) = signature.split_bytes();
#[allow(clippy::arithmetic_side_effects)]
let mut data = Vec::with_capacity($size * 2 + 4 * 2 + 2);
Mpint::from_positive_bytes(&r)?.encode(&mut data)?;
Mpint::from_positive_bytes(&s)?.encode(&mut data)?;
Ok(Signature {
algorithm: Algorithm::Ecdsa {
curve: EcdsaCurve::$curve,
},
data,
})
}
}
#[cfg(feature = $feature)]
impl TryFrom<Signature> for $krate::ecdsa::Signature {
type Error = Error;
fn try_from(signature: Signature) -> Result<$krate::ecdsa::Signature> {
$krate::ecdsa::Signature::try_from(&signature)
}
}
#[cfg(feature = $feature)]
impl Signer<Signature> for EcdsaPrivateKey<$size> {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
let signing_key = $krate::ecdsa::SigningKey::from_slice(self.as_ref())?;
let signature: $krate::ecdsa::Signature = signing_key.try_sign(message)?;
Ok(signature.try_into()?)
}
}
};
}
impl_signature_for_curve!(p256, "p256", NistP256, 32);
impl_signature_for_curve!(p384, "p384", NistP384, 48);
impl_signature_for_curve!(p521, "p521", NistP521, 66);
/// Build a generic sized object from a `u8` iterator, with leading zero padding
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
fn zero_pad_field_bytes<B: FromIterator<u8> + Copy>(m: Mpint) -> Option<B> {
use core::mem::size_of;
let bytes = m.as_positive_bytes()?;
size_of::<B>()
.checked_sub(bytes.len())
.map(|i| B::from_iter(iter::repeat(0u8).take(i).chain(bytes.iter().cloned())))
}
#[cfg(feature = "p256")]
impl TryFrom<&Signature> for p256::ecdsa::Signature {
type Error = Error;
fn try_from(signature: &Signature) -> Result<p256::ecdsa::Signature> {
match signature.algorithm {
Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256,
} => p256_signature_from_openssh_bytes(signature.as_bytes()),
_ => Err(signature.algorithm.clone().unsupported_error()),
}
}
}
#[cfg(feature = "p256")]
fn p256_signature_from_openssh_bytes(mut signature_bytes: &[u8]) -> Result<p256::ecdsa::Signature> {
let reader = &mut signature_bytes;
let r = Mpint::decode(reader)?;
let s = Mpint::decode(reader)?;
match (
zero_pad_field_bytes::<p256::FieldBytes>(r),
zero_pad_field_bytes::<p256::FieldBytes>(s),
) {
(Some(r), Some(s)) => Ok(p256::ecdsa::Signature::from_scalars(r, s)?),
_ => Err(Error::Crypto),
}
}
#[cfg(feature = "p384")]
impl TryFrom<&Signature> for p384::ecdsa::Signature {
type Error = Error;
fn try_from(signature: &Signature) -> Result<p384::ecdsa::Signature> {
match signature.algorithm {
Algorithm::Ecdsa {
curve: EcdsaCurve::NistP384,
} => {
let reader = &mut signature.as_bytes();
let r = Mpint::decode(reader)?;
let s = Mpint::decode(reader)?;
match (
zero_pad_field_bytes::<p384::FieldBytes>(r),
zero_pad_field_bytes::<p384::FieldBytes>(s),
) {
(Some(r), Some(s)) => Ok(p384::ecdsa::Signature::from_scalars(r, s)?),
_ => Err(Error::Crypto),
}
}
_ => Err(signature.algorithm.clone().unsupported_error()),
}
}
}
#[cfg(feature = "p521")]
impl TryFrom<&Signature> for p521::ecdsa::Signature {
type Error = Error;
fn try_from(signature: &Signature) -> Result<p521::ecdsa::Signature> {
match signature.algorithm {
Algorithm::Ecdsa {
curve: EcdsaCurve::NistP521,
} => {
let reader = &mut signature.as_bytes();
let r = Mpint::decode(reader)?;
let s = Mpint::decode(reader)?;
match (
zero_pad_field_bytes::<p521::FieldBytes>(r),
zero_pad_field_bytes::<p521::FieldBytes>(s),
) {
(Some(r), Some(s)) => Ok(p521::ecdsa::Signature::from_scalars(r, s)?),
_ => Err(Error::Crypto),
}
}
_ => Err(signature.algorithm.clone().unsupported_error()),
}
}
}
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
impl Signer<Signature> for EcdsaKeypair {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
match self {
#[cfg(feature = "p256")]
Self::NistP256 { private, .. } => private.try_sign(message),
#[cfg(feature = "p384")]
Self::NistP384 { private, .. } => private.try_sign(message),
#[cfg(feature = "p521")]
Self::NistP521 { private, .. } => private.try_sign(message),
#[cfg(not(all(feature = "p256", feature = "p384", feature = "p521")))]
_ => Err(self.algorithm().unsupported_error().into()),
}
}
}
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
impl Verifier<Signature> for EcdsaPublicKey {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
match signature.algorithm {
Algorithm::Ecdsa { curve } => match curve {
#[cfg(feature = "p256")]
EcdsaCurve::NistP256 => {
let verifying_key = p256::ecdsa::VerifyingKey::try_from(self)?;
let signature = p256::ecdsa::Signature::try_from(signature)?;
verifying_key.verify(message, &signature)
}
#[cfg(feature = "p384")]
EcdsaCurve::NistP384 => {
let verifying_key = p384::ecdsa::VerifyingKey::try_from(self)?;
let signature = p384::ecdsa::Signature::try_from(signature)?;
verifying_key.verify(message, &signature)
}
#[cfg(feature = "p521")]
EcdsaCurve::NistP521 => {
let verifying_key = p521::ecdsa::VerifyingKey::try_from(self)?;
let signature = p521::ecdsa::Signature::try_from(signature)?;
verifying_key.verify(message, &signature)
}
#[cfg(not(all(feature = "p256", feature = "p384", feature = "p521")))]
_ => Err(signature.algorithm().unsupported_error().into()),
},
_ => Err(signature.algorithm().unsupported_error().into()),
}
}
}
#[cfg(feature = "rsa")]
impl Signer<Signature> for RsaKeypair {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
let data = rsa::pkcs1v15::SigningKey::<Sha512>::try_from(self)?
.try_sign(message)
.map_err(|_| signature::Error::new())?;
Ok(Signature {
algorithm: Algorithm::Rsa {
hash: Some(HashAlg::Sha512),
},
data: data.to_vec(),
})
}
}
#[cfg(feature = "rsa")]
impl Verifier<Signature> for RsaPublicKey {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
match signature.algorithm {
Algorithm::Rsa { hash: Some(hash) } => {
let signature = rsa::pkcs1v15::Signature::try_from(signature.data.as_ref())?;
match hash {
HashAlg::Sha256 => rsa::pkcs1v15::VerifyingKey::<Sha256>::try_from(self)?
.verify(message, &signature)
.map_err(|_| signature::Error::new()),
HashAlg::Sha512 => rsa::pkcs1v15::VerifyingKey::<Sha512>::try_from(self)?
.verify(message, &signature)
.map_err(|_| signature::Error::new()),
}
}
_ => Err(signature.algorithm().unsupported_error().into()),
}
}
}
#[cfg(test)]
mod tests {
use super::Signature;
use crate::{Algorithm, EcdsaCurve, HashAlg};
use alloc::vec::Vec;
use encoding::Encode;
use hex_literal::hex;
#[cfg(feature = "ed25519")]
use {
super::Ed25519Keypair,
signature::{Signer, Verifier},
};
#[cfg(feature = "p256")]
use super::{zero_pad_field_bytes, Mpint};
const DSA_SIGNATURE: &[u8] = &hex!("000000077373682d6473730000002866725bf3c56100e975e21fff28a60f73717534d285ea3e1beefc2891f7189d00bd4d94627e84c55c");
const ECDSA_SHA2_P256_SIGNATURE: &[u8] = &hex!("0000001365636473612d736861322d6e6973747032353600000048000000201298ab320720a32139cda8a40c97a13dc54ce032ea3c6f09ea9e87501e48fa1d0000002046e4ac697a6424a9870b9ef04ca1182cd741965f989bd1f1f4a26fd83cf70348");
const ED25519_SIGNATURE: &[u8] = &hex!("0000000b7373682d65643235353139000000403d6b9906b76875aef1e7b2f1e02078a94f439aebb9a4734da1a851a81e22ce0199bbf820387a8de9c834c9c3cc778d9972dcbe70f68d53cc6bc9e26b02b46d04");
const SK_ED25519_SIGNATURE: &[u8] = &hex!("0000001a736b2d7373682d65643235353139406f70656e7373682e636f6d000000402f5670b6f93465d17423878a74084bf331767031ed240c627c8eb79ab8fa1b935a1fd993f52f5a13fec1797f8a434f943a6096246aea8dd5c8aa922cba3d95060100000009");
const RSA_SHA512_SIGNATURE: &[u8] = &hex!("0000000c7273612d736861322d3531320000018085a4ad1a91a62c00c85de7bb511f38088ff2bce763d76f4786febbe55d47624f9e2cffce58a680183b9ad162c7f0191ea26cab001ac5f5055743eced58e9981789305c208fc98d2657954e38eb28c7e7f3fbe92393a14324ed77aebb772a41aa7a107b38cb9bd1d9ad79b275135d1d7e019bb1d56d74f2450be6db0771f48f6707d3fcf9789592ca2e55595acc16b6e8d0139b56c5d1360b3a1e060f4151a3d7841df2c2a8c94d6f8a1bf633165ee0bcadac5642763df0dd79d3235ae5506595145f199d8abe8f9980411bf70a16e30f273736324d047043317044c36374d6a5ed34cac251e01c6795e4578393f9090bf4ae3e74a0009275a197315fc9c62f1c9aec1ba3b2d37c3b207e5500df19e090e7097ebc038fb9c9e35aea9161479ba6b5190f48e89e1abe51e8ec0e120ef89776e129687ca52d1892c8e88e6ef062a7d96b8a87682ca6a42ff1df0cdf5815c3645aeed7267ca7093043db0565e0f109b796bf117b9d2bb6d6debc0c67a4c9fb3aae3e29b00c7bd70f6c11cf53c295ff");
/// Example test vector for signing.
#[cfg(feature = "ed25519")]
const EXAMPLE_MSG: &[u8] = b"Hello, world!";
#[cfg(feature = "p256")]
#[test]
fn convert_ecdsa_sha2_p256() {
let p256_signature = p256::ecdsa::Signature::try_from(hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001").as_ref()).unwrap();
let _ssh_signature = Signature::try_from(p256_signature).unwrap();
}
#[cfg(feature = "p256")]
#[test]
fn zero_pad_field_bytes_p256() {
let i = Mpint::from_bytes(&hex!(
"1122334455667788112233445566778811223344556677881122334455667788"
))
.unwrap();
let fb = zero_pad_field_bytes::<p256::FieldBytes>(i);
assert!(fb.is_some());
// too long
let i = Mpint::from_bytes(&hex!(
"991122334455667788112233445566778811223344556677881122334455667788"
))
.unwrap();
let fb = zero_pad_field_bytes::<p256::FieldBytes>(i);
assert!(fb.is_none());
// short is okay
let i = Mpint::from_bytes(&hex!(
"22334455667788112233445566778811223344556677881122334455667788"
))
.unwrap();
let fb = zero_pad_field_bytes::<p256::FieldBytes>(i)
.expect("failed to build FieldBytes from short hex string");
assert_eq!(fb[0], 0x00);
assert_eq!(fb[1], 0x22);
}
#[test]
fn decode_dsa() {
let signature = Signature::try_from(DSA_SIGNATURE).unwrap();
assert_eq!(Algorithm::Dsa, signature.algorithm());
}
#[test]
fn decode_ecdsa_sha2_p256() {
let signature = Signature::try_from(ECDSA_SHA2_P256_SIGNATURE).unwrap();
assert_eq!(
Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256
},
signature.algorithm()
);
}
#[test]
fn decode_ed25519() {
let signature = Signature::try_from(ED25519_SIGNATURE).unwrap();
assert_eq!(Algorithm::Ed25519, signature.algorithm());
}
#[test]
fn decode_sk_ed25519() {
let signature = Signature::try_from(SK_ED25519_SIGNATURE).unwrap();
assert_eq!(Algorithm::SkEd25519, signature.algorithm());
}
#[test]
fn decode_rsa() {
let signature = Signature::try_from(RSA_SHA512_SIGNATURE).unwrap();
assert_eq!(
Algorithm::Rsa {
hash: Some(HashAlg::Sha512)
},
signature.algorithm()
);
}
#[test]
fn encode_dsa() {
let signature = Signature::try_from(DSA_SIGNATURE).unwrap();
let mut result = Vec::new();
signature.encode(&mut result).unwrap();
assert_eq!(DSA_SIGNATURE, &result);
}
#[test]
fn encode_ecdsa_sha2_p256() {
let signature = Signature::try_from(ECDSA_SHA2_P256_SIGNATURE).unwrap();
let mut result = Vec::new();
signature.encode(&mut result).unwrap();
assert_eq!(ECDSA_SHA2_P256_SIGNATURE, &result);
}
#[test]
fn encode_ed25519() {
let signature = Signature::try_from(ED25519_SIGNATURE).unwrap();
let mut result = Vec::new();
signature.encode(&mut result).unwrap();
assert_eq!(ED25519_SIGNATURE, &result);
}
#[test]
fn encode_sk_ed25519() {
let signature = Signature::try_from(SK_ED25519_SIGNATURE).unwrap();
let mut result = Vec::new();
signature.encode(&mut result).unwrap();
assert_eq!(SK_ED25519_SIGNATURE, &result);
}
#[cfg(feature = "dsa")]
#[test]
fn try_sign_and_verify_dsa() {
use super::{DsaKeypair, DSA_SIGNATURE_SIZE};
use encoding::Decode as _;
use signature::{Signer as _, Verifier as _};
fn check_signature_component_lens(
keypair: &DsaKeypair,
data: &[u8],
r_len: usize,
s_len: usize,
) {
use sha1::{Digest as _, Sha1};
use signature::DigestSigner as _;
let signature = dsa::SigningKey::try_from(keypair)
.expect("valid DSA signing key")
.try_sign_digest(Sha1::new_with_prefix(data))
.expect("valid DSA signature");
let r = signature.r().to_bytes_be();
assert_eq!(
r.len(),
r_len,
"dsa signature component `r` has len {} != {}",
r.len(),
r_len
);
let s = signature.s().to_bytes_be();
assert_eq!(
s.len(),
s_len,
"dsa signature component `s` has len {} != {}",
s.len(),
s_len
);
}
let keypair = hex!("0000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf277161980000001500ced95f1c7bbb39be4987837ad1f71be31bb7b0d9");
let keypair = DsaKeypair::decode(&mut &keypair[..]).expect("properly encoded DSA keypair");
let data = hex!("F0000040713d5f6fffe0000e6421ab0b3a69774d3da02fd72b107d6b32b6dad7c1660bbf507bf3eac3304cc5058f7e6f81b04239b8471459b1f3b387e2626f7eb8f6bcdd3200000006626c616465320000000e7373682d636f6e6e656374696f6e00000009686f73746261736564000000077373682d647373000001b2000000077373682d6473730000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf2771619800000015746f6d61746f7373682e6c6f63616c646f6d61696e00000009746f6d61746f737368");
check_signature_component_lens(
&keypair,
&data,
DSA_SIGNATURE_SIZE / 2,
DSA_SIGNATURE_SIZE / 2,
);
let signature = keypair.try_sign(&data[..]).expect("dsa try_sign is ok");
keypair
.public
.verify(&data[..], &signature)
.expect("dsa verify is ok");
let data = hex!("00000040713d5f6fffe0000e6421ab0b3a69774d3da02fd72b107d6b32b6dad7c1660bbf507bf3eac3304cc5058f7e6f81b04239b8471459b1f3b387e2626f7eb8f6bcdd3200000006626c616465320000000e7373682d636f6e6e656374696f6e00000009686f73746261736564000000077373682d647373000001b2000000077373682d6473730000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf2771619800000015746f6d61746f7373682e6c6f63616c646f6d61696e00000009746f6d61746f737368");
// verify that this data produces signature with `r` integer component that is less than 160 bits/20 bytes.
check_signature_component_lens(
&keypair,
&data,
DSA_SIGNATURE_SIZE / 2 - 1,
DSA_SIGNATURE_SIZE / 2,
);
let signature = keypair
.try_sign(&data[..])
.expect("dsa try_sign for r.len() == 19 is ok");
keypair
.public
.verify(&data[..], &signature)
.expect("dsa verify is ok");
}
#[cfg(feature = "ed25519")]
#[test]
fn sign_and_verify_ed25519() {
let keypair = Ed25519Keypair::from_seed(&[42; 32]);
let signature = keypair.sign(EXAMPLE_MSG);
assert!(keypair.public.verify(EXAMPLE_MSG, &signature).is_ok());
}
#[test]
fn placeholder() {
assert!(!Signature::try_from(ED25519_SIGNATURE)
.unwrap()
.is_placeholder());
let placeholder = Signature::placeholder();
assert!(placeholder.is_placeholder());
let mut writer = Vec::new();
assert_eq!(
placeholder.encode(&mut writer),
Err(encoding::Error::Length)
);
}
}

347
vendor/ssh-key/src/sshsig.rs vendored Normal file
View File

@@ -0,0 +1,347 @@
//! `sshsig` implementation.
use crate::{public, Algorithm, Error, HashAlg, Result, Signature, SigningKey};
use alloc::{string::String, string::ToString, vec::Vec};
use core::str::FromStr;
use encoding::{
pem::{LineEnding, PemLabel},
CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer,
};
use signature::Verifier;
#[cfg(doc)]
use crate::{PrivateKey, PublicKey};
type Version = u32;
/// `sshsig` provides a general-purpose signature format based on SSH keys and
/// wire formats.
///
/// These signatures can be produced using `ssh-keygen -Y sign`. They're
/// encoded as PEM and begin with the following:
///
/// ```text
/// -----BEGIN SSH SIGNATURE-----
/// ```
///
/// See [PROTOCOL.sshsig] for more information.
///
/// # Usage
///
/// See [`PrivateKey::sign`] and [`PublicKey::verify`] for usage information.
///
/// [PROTOCOL.sshsig]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.sshsig?annotate=HEAD
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SshSig {
version: Version,
public_key: public::KeyData,
namespace: String,
reserved: Vec<u8>,
hash_alg: HashAlg,
signature: Signature,
}
impl SshSig {
/// Supported version.
pub const VERSION: Version = 1;
/// The preamble is the six-byte sequence "SSHSIG".
///
/// It is included to ensure that manual signatures can never be confused
/// with any message signed during SSH user or host authentication.
const MAGIC_PREAMBLE: &'static [u8] = b"SSHSIG";
/// Create a new signature with the given public key, namespace, hash
/// algorithm, and signature.
pub fn new(
public_key: public::KeyData,
namespace: impl Into<String>,
hash_alg: HashAlg,
signature: Signature,
) -> Result<Self> {
let version = Self::VERSION;
let namespace = namespace.into();
let reserved = Vec::new();
if namespace.is_empty() {
return Err(Error::Namespace);
}
Ok(Self {
version,
public_key,
namespace,
reserved,
hash_alg,
signature,
})
}
/// Decode signature from PEM which begins with the following:
///
/// ```text
/// -----BEGIN SSH SIGNATURE-----
/// ```
pub fn from_pem(pem: impl AsRef<[u8]>) -> Result<Self> {
Self::decode_pem(pem)
}
/// Encode signature as PEM which begins with the following:
///
/// ```text
/// -----BEGIN SSH SIGNATURE-----
/// ```
pub fn to_pem(&self, line_ending: LineEnding) -> Result<String> {
Ok(self.encode_pem_string(line_ending)?)
}
/// Sign the given message with the provided signing key.
///
/// See also: [`PrivateKey::sign`].
pub fn sign<S: SigningKey>(
signing_key: &S,
namespace: &str,
hash_alg: HashAlg,
msg: &[u8],
) -> Result<Self> {
if namespace.is_empty() {
return Err(Error::Namespace);
}
if signing_key.public_key().is_sk_ed25519() {
return Err(Algorithm::SkEd25519.unsupported_error());
}
#[cfg(feature = "ecdsa")]
if signing_key.public_key().is_sk_ecdsa_p256() {
return Err(Algorithm::SkEcdsaSha2NistP256.unsupported_error());
}
let signed_data = Self::signed_data(namespace, hash_alg, msg)?;
let signature = signing_key.try_sign(&signed_data)?;
Self::new(signing_key.public_key(), namespace, hash_alg, signature)
}
/// Get the raw message over which the signature for a given message
/// needs to be computed.
///
/// This is a low-level function intended for uses cases which can't be
/// expressed using [`SshSig::sign`], such as if the [`SigningKey`] trait
/// can't be used for some reason.
///
/// Once a [`Signature`] has been computed over the returned byte vector,
/// [`SshSig::new`] can be used to construct the final signature.
pub fn signed_data(namespace: &str, hash_alg: HashAlg, msg: &[u8]) -> Result<Vec<u8>> {
if namespace.is_empty() {
return Err(Error::Namespace);
}
SignedData {
namespace,
reserved: &[],
hash_alg,
hash: hash_alg.digest(msg).as_slice(),
}
.to_bytes()
}
/// Verify the given message against this signature.
///
/// Note that this method does not verify the public key or namespace
/// are correct and thus is crate-private so as to ensure these parameters
/// are always authenticated by users of the public API.
pub(crate) fn verify(&self, msg: &[u8]) -> Result<()> {
let signed_data = SignedData {
namespace: self.namespace.as_str(),
reserved: self.reserved.as_slice(),
hash_alg: self.hash_alg,
hash: self.hash_alg.digest(msg).as_slice(),
}
.to_bytes()?;
Ok(self.public_key.verify(&signed_data, &self.signature)?)
}
/// Get the signature algorithm.
pub fn algorithm(&self) -> Algorithm {
self.signature.algorithm()
}
/// Get version number for this signature.
///
/// Verifiers MUST reject signatures with versions greater than those
/// they support.
pub fn version(&self) -> Version {
self.version
}
/// Get public key which corresponds to the signing key that produced
/// this signature.
pub fn public_key(&self) -> &public::KeyData {
&self.public_key
}
/// Get the namespace (i.e. domain identifier) for this signature.
///
/// The purpose of the namespace value is to specify a unambiguous
/// interpretation domain for the signature, e.g. file signing.
/// This prevents cross-protocol attacks caused by signatures
/// intended for one intended domain being accepted in another.
/// The namespace value MUST NOT be the empty string.
pub fn namespace(&self) -> &str {
&self.namespace
}
/// Get reserved data associated with this signature. Typically empty.
///
/// The reserved value is present to encode future information
/// (e.g. tags) into the signature. Implementations should ignore
/// the reserved field if it is not empty.
pub fn reserved(&self) -> &[u8] {
&self.reserved
}
/// Get the hash algorithm used to produce this signature.
///
/// Data to be signed is first hashed with the specified `hash_alg`.
/// This is done to limit the amount of data presented to the signature
/// operation, which may be of concern if the signing key is held in limited
/// or slow hardware or on a remote ssh-agent. The supported hash algorithms
/// are "sha256" and "sha512".
pub fn hash_alg(&self) -> HashAlg {
self.hash_alg
}
/// Get the structured signature over the given message.
pub fn signature(&self) -> &Signature {
&self.signature
}
/// Get the bytes which comprise the serialized signature.
pub fn signature_bytes(&self) -> &[u8] {
self.signature.as_bytes()
}
}
impl Decode for SshSig {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let mut magic_preamble = [0u8; Self::MAGIC_PREAMBLE.len()];
reader.read(&mut magic_preamble)?;
if magic_preamble != Self::MAGIC_PREAMBLE {
return Err(Error::FormatEncoding);
}
let version = Version::decode(reader)?;
if version > Self::VERSION {
return Err(Error::Version { number: version });
}
let public_key = reader.read_prefixed(public::KeyData::decode)?;
let namespace = String::decode(reader)?;
if namespace.is_empty() {
return Err(Error::Namespace);
}
let reserved = Vec::decode(reader)?;
let hash_alg = HashAlg::decode(reader)?;
let signature = reader.read_prefixed(Signature::decode)?;
Ok(Self {
version,
public_key,
namespace,
reserved,
hash_alg,
signature,
})
}
}
impl Encode for SshSig {
fn encoded_len(&self) -> encoding::Result<usize> {
[
Self::MAGIC_PREAMBLE.len(),
self.version.encoded_len()?,
self.public_key.encoded_len_prefixed()?,
self.namespace.encoded_len()?,
self.reserved.encoded_len()?,
self.hash_alg.encoded_len()?,
self.signature.encoded_len_prefixed()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
writer.write(Self::MAGIC_PREAMBLE)?;
self.version.encode(writer)?;
self.public_key.encode_prefixed(writer)?;
self.namespace.encode(writer)?;
self.reserved.encode(writer)?;
self.hash_alg.encode(writer)?;
self.signature.encode_prefixed(writer)?;
Ok(())
}
}
impl FromStr for SshSig {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_pem(s)
}
}
impl PemLabel for SshSig {
const PEM_LABEL: &'static str = "SSH SIGNATURE";
}
impl ToString for SshSig {
fn to_string(&self) -> String {
self.to_pem(LineEnding::default())
.expect("SSH signature encoding error")
}
}
/// Data to be signed.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct SignedData<'a> {
namespace: &'a str,
reserved: &'a [u8],
hash_alg: HashAlg,
hash: &'a [u8],
}
impl<'a> SignedData<'a> {
fn to_bytes(self) -> Result<Vec<u8>> {
let mut signed_bytes = Vec::with_capacity(self.encoded_len()?);
self.encode(&mut signed_bytes)?;
Ok(signed_bytes)
}
}
impl Encode for SignedData<'_> {
fn encoded_len(&self) -> encoding::Result<usize> {
[
SshSig::MAGIC_PREAMBLE.len(),
self.namespace.encoded_len()?,
self.reserved.encoded_len()?,
self.hash_alg.encoded_len()?,
self.hash.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
writer.write(SshSig::MAGIC_PREAMBLE)?;
self.namespace.encode(writer)?;
self.reserved.encode(writer)?;
self.hash_alg.encode(writer)?;
self.hash.encode(writer)?;
Ok(())
}
}

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

Binary file not shown.

56
vendor/ssh-key/tests/algorithm_name.rs vendored Normal file
View File

@@ -0,0 +1,56 @@
//! Tests for `AlgorithmName` parsing.
#![cfg(feature = "alloc")]
use ssh_key::AlgorithmName;
use std::str::FromStr;
#[test]
fn additional_algorithm_name() {
const NAME: &str = "name@example.com";
const CERT_STR: &str = "name-cert-v01@example.com";
let name = AlgorithmName::from_str(NAME).unwrap();
assert_eq!(name.as_str(), NAME);
assert_eq!(name.certificate_type(), CERT_STR);
let name = AlgorithmName::from_certificate_type(CERT_STR).unwrap();
assert_eq!(name.as_str(), NAME);
assert_eq!(name.certificate_type(), CERT_STR);
}
#[test]
fn invalid_algorithm_name() {
const INVALID_NAMES: &[&str] = &[
"nameß@example.com",
"name@example@com",
"name",
"@name",
"name@",
"",
"@",
"a-name-that-is-too-long-but-would-otherwise-be-valid-@example.com",
];
const INVALID_CERT_STRS: &[&str] = &[
"nameß-cert-v01@example.com",
"name-cert-v01@example@com",
"name@example.com",
];
for name in INVALID_NAMES {
assert!(
AlgorithmName::from_str(&name).is_err(),
"{:?} should be an invalid algorithm name",
name
);
}
for name in INVALID_CERT_STRS {
assert!(
AlgorithmName::from_certificate_type(&name).is_err(),
"{:?} should be an invalid certificate str",
name
);
}
}

55
vendor/ssh-key/tests/authorized_keys.rs vendored Normal file
View File

@@ -0,0 +1,55 @@
//! Tests for parsing `authorized_keys` files.
#![cfg(all(feature = "ecdsa", feature = "std"))]
use ssh_key::AuthorizedKeys;
// TODO(tarcieri): test file permissions
#[test]
fn read_example_file() {
let authorized_keys = AuthorizedKeys::read_file("./tests/examples/authorized_keys").unwrap();
assert_eq!(authorized_keys.len(), 5);
assert_eq!(authorized_keys[0].config_opts().to_string(), "");
assert_eq!(
authorized_keys[0].public_key().to_string(),
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti"
);
assert_eq!(authorized_keys[0].public_key().comment(), "");
assert_eq!(
authorized_keys[1].config_opts().to_string(),
"command=\"/usr/bin/date\""
);
assert_eq!(authorized_keys[1].public_key().to_string(), "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2SQJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcEc= user2@example.com");
assert_eq!(
authorized_keys[1].public_key().comment(),
"user2@example.com"
);
assert_eq!(
authorized_keys[2].config_opts().to_string(),
"environment=\"PATH=/bin:/usr/bin\""
);
assert_eq!(authorized_keys[2].public_key().to_string(), "ssh-dss AAAAB3NzaC1kc3MAAACBANw9iSUO2UYhFMssjUgW46URqv8bBrDgHeF8HLBOWBvKuXF2Rx2J/XyhgX48SOLMuv0hcPaejlyLarabnF9F2V4dkpPpZSJ+7luHmxEjNxwhsdtg8UteXAWkeCzrQ6MvRJZHcDBjYh56KGvslbFnJsGLXlI4PQCyl6awNImwYGilAAAAFQCJGBU3hZf+QtP9Jh/nbfNlhFu7hwAAAIBHObOQioQVRm3HsVb7mOy3FVKhcLoLO3qoG9gTkd4KeuehtFAC3+rckiX7xSCnE/5BBKdL7VP9WRXac2Nlr9Pwl3e7zPut96wrCHt/TZX6vkfXKkbpUIj5zSqfvyNrWKaYJkfzwAQwrXNS1Hol676Ud/DDEn2oatdEhkS3beWHXAAAAIBgQqaz/YYTRMshzMzYcZ4lqgvgmA55y6v0h39e8HH2A5dwNS6sPUw2jyna+le0dceNRJifFld1J+WYM0vmquSr11DDavgEidOSaXwfMvPPPJqLmbzdtT16N+Gij9U9STQTHPQcQ3xnNNHgQAStzZJbhLOVbDDDo5BO7LMUALDfSA== user3@example.com");
assert_eq!(
authorized_keys[2].public_key().comment(),
"user3@example.com"
);
assert_eq!(
authorized_keys[3].config_opts().to_string(),
"from=\"10.0.0.?,*.example.com\",no-X11-forwarding"
);
assert_eq!(authorized_keys[3].public_key().to_string(), "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC0WRHtxuxefSJhpIxGq4ibGFgwYnESPm8C3JFM88A1JJLoprenklrd7VJ+VH3Ov/bQwZwLyRU5dRmfR/SWTtIPWs7tToJVayKKDB+/qoXmM5ui/0CU2U4rCdQ6PdaCJdC7yFgpPL8WexjWN06+eSIKYz1AAXbx9rRv1iasslK/KUqtsqzVliagI6jl7FPO2GhRZMcso6LsZGgSxuYf/Lp0D/FcBU8GkeOo1Sx5xEt8H8bJcErtCe4Blb8JxcW6EXO3sReb4z+zcR07gumPgFITZ6hDA8sSNuvo/AlWg0IKTeZSwHHVknWdQqDJ0uczE837caBxyTZllDNIGkBjCIIOFzuTT76HfYc/7CTTGk07uaNkUFXKN79xDiFOX8JQ1ZZMZvGOTwWjuT9CqgdTvQRORbRWwOYv3MH8re9ykw3Ip6lrPifY7s6hOaAKry/nkGPMt40m1TdiW98MTIpooE7W+WXu96ax2l2OJvxX8QR7l+LFlKnkIEEJd/ItF1G22UmOjkVwNASTwza/hlY+8DoVvEmwum/nMgH2TwQT3bTQzF9s9DOJkH4d8p4Mw4gEDjNx0EgUFA91ysCAeUMQQyIvuR8HXXa+VcvhOOO5mmBcVhxJ3qUOJTyDBsT0932Zb4mNtkxdigoVxu+iiwk0vwtvKwGVDYdyMP5EAQeEIP1t0w== user4@example.com");
assert_eq!(
authorized_keys[3].public_key().comment(),
"user4@example.com"
);
assert_eq!(authorized_keys[4].public_key().to_string(), "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN76zuqnjypL54/w4763l7q1Sn3IBYHptJ5wcYfEWkzeNTvpexr05Z18m2yPT2SWRd1JJ8Aj5TYidG9MdSS5J78= hello world this is a long comment");
assert_eq!(
authorized_keys[4].public_key().comment(),
"hello world this is a long comment"
);
}

354
vendor/ssh-key/tests/certificate.rs vendored Normal file
View File

@@ -0,0 +1,354 @@
//! OpenSSH certificate tests.
#![cfg(feature = "alloc")]
use hex_literal::hex;
use ssh_key::{Algorithm, Certificate};
use std::str::FromStr;
#[cfg(feature = "ecdsa")]
use ssh_key::EcdsaCurve;
#[cfg(feature = "rsa")]
use ssh_key::HashAlg;
/// DSA OpenSSH Certificate
const DSA_CERT_EXAMPLE: &str = include_str!("examples/id_dsa_1024-cert.pub");
/// ECDSA/P-256 OpenSSH Certificate
#[cfg(feature = "ecdsa")]
const ECDSA_P256_CERT_EXAMPLE: &str = include_str!("examples/id_ecdsa_p256-cert.pub");
/// Ed25519 OpenSSH Certificate
const ED25519_CERT_EXAMPLE: &str = include_str!("examples/id_ed25519-cert.pub");
/// Ed25519 OpenSSH Certificate with deliberately invalid signature
#[cfg(feature = "ed25519")]
const ED25519_CERT_BADSIG_EXAMPLE: &str = include_str!("examples/id_ed25519-cert-badsig.pub");
/// Ed25519 OpenSSH Certificate with P-256 certificate authority
#[cfg(feature = "p256")]
const ED25519_CERT_WITH_P256_CA_EXAMPLE: &str =
include_str!("examples/id_ed25519-cert-with-p256-ca.pub");
/// Ed25519 OpenSSH Certificate with RSA certificate authority
#[cfg(feature = "rsa")]
const ED25519_CERT_WITH_RSA_CA_EXAMPLE: &str =
include_str!("examples/id_ed25519-cert-with-rsa-ca.pub");
/// RSA (4096-bit) OpenSSH Certificate
const RSA_4096_CERT_EXAMPLE: &str = include_str!("examples/id_rsa_4096-cert.pub");
/// Security Key (FIDO/U2F) ECDSA/NIST P-256 OpenSSH Certificate
#[cfg(feature = "ecdsa")]
const SK_ECDSA_P256_CERT_EXAMPLE: &str = include_str!("examples/id_sk_ecdsa_p256-cert.pub");
/// Security Key (FIDO/U2F) Ed25519 OpenSSH Certificate
const SK_ED25519_CERT_EXAMPLE: &str = include_str!("examples/id_sk_ed25519-cert.pub");
/// Example certificate authority fingerprint (matches `id_ed25519.pub` example)
#[cfg(feature = "ed25519")]
const CA_FINGERPRINT: &str = "SHA256:UCUiLr7Pjs9wFFJMDByLgc3NrtdU344OgUM45wZPcIQ";
/// Valid certificate timestamp.
#[cfg(feature = "ed25519")]
const VALID_TIMESTAMP: u64 = 1750000000;
/// Timestamp which is before the validity window.
#[cfg(feature = "ed25519")]
const PAST_TIMESTAMP: u64 = 1500000000;
/// Expired certificate timestamp.
#[cfg(feature = "ed25519")]
const EXPIRED_TIMESTAMP: u64 = 2500000000;
#[test]
fn decode_dsa_openssh() {
let cert = Certificate::from_str(DSA_CERT_EXAMPLE).unwrap();
assert_eq!(Algorithm::Dsa, cert.public_key().algorithm());
let dsa_key = cert.public_key().dsa().unwrap();
assert_eq!(
&hex!(
"00dc3d89250ed9462114cb2c8d4816e3a511aaff1b06b0e01de17c1cb04e581bcab97176471d89fd7ca1817
e3c48e2ccbafd2170f69e8e5c8b6ab69b9c5f45d95e1d9293e965227eee5b879b1123371c21b1db60f14b5e
5c05a4782ceb43a32f449647703063621e7a286bec95b16726c18b5e52383d00b297a6b03489b06068a5"
),
dsa_key.p.as_bytes(),
);
assert_eq!(
&hex!("00891815378597fe42d3fd261fe76df365845bbb87"),
dsa_key.q.as_bytes(),
);
assert_eq!(
&hex!(
"4739b3908a8415466dc7b156fb98ecb71552a170ba0b3b7aa81bd81391de0a7ae7a1b45002dfeadc9225fbc
520a713fe4104a74bed53fd5915da736365afd3f09777bbccfbadf7ac2b087b7f4d95fabe47d72a46e95088
f9cd2a9fbf236b58a6982647f3c00430ad7352d47a25ebbe9477f0c3127da86ad7448644b76de5875c"
),
dsa_key.g.as_bytes(),
);
assert_eq!(
&hex!(
"6042a6b3fd861344cb21ccccd8719e25aa0be0980e79cbabf4877f5ef071f6039770352eac3d4c368f29daf
a57b475c78d44989f16577527e598334be6aae4abd750c36af80489d392697c1f32f3cf3c9a8b99bcddb53d
7a37e1a28fd53d4934131cf41c437c6734d1e04004adcd925b84b3956c30c3a3904eecb31400b0df48"
),
dsa_key.y.as_bytes(),
);
assert_eq!("user@example.com", cert.comment());
}
#[cfg(feature = "ecdsa")]
#[test]
fn decode_ecdsa_p256_openssh() {
let cert = Certificate::from_str(ECDSA_P256_CERT_EXAMPLE).unwrap();
assert_eq!(
Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256
},
cert.public_key().algorithm(),
);
let ecdsa_key = cert.public_key().ecdsa().unwrap();
assert_eq!(EcdsaCurve::NistP256, ecdsa_key.curve());
assert_eq!(
&hex!(
"047c1fd8730ce53457be8d924098ec3648830f92aa8a2363ac656fdd4521fa6313e511f1891b4e9e5aaf8e1
42d06ad15a66a4257f3f051d84e8a0e2f91ba807047"
),
ecdsa_key.as_ref(),
);
assert_eq!("user@example.com", cert.comment());
}
#[test]
fn decode_ed25519_openssh() {
let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap();
assert_eq!(Algorithm::Ed25519, cert.public_key().algorithm());
assert_eq!(
&hex!("b33eaef37ea2df7caa010defdea34e241f65f1b529a4f43ed14327f5c54aab62"),
cert.public_key().ed25519().unwrap().as_ref(),
);
assert_eq!("user@example.com", cert.comment());
assert_eq!(cert.valid_principals().len(), 1);
assert_eq!(cert.valid_principals()[0], "host.example.com");
}
#[test]
fn decode_ed25519_openssh_with_crit_options() {
let src = "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIBW/4zLqXWROWmN1sPgdySnH1GUsEFBjFrRwKKw71BoBAAAAIH1MFwI1oRdEifXgBQvWQfCBBtA/Pi8YCUE/I3wXFJo2AAAAAAAAAAAAAAABAAAAA2ZvbwAAAAAAAAAAAAAAAH//////////AAAAIwAAABFoZWxsb0BleGFtcGxlLmNvbQAAAAoAAAAGZm9vYmFyAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIH1MFwI1oRdEifXgBQvWQfCBBtA/Pi8YCUE/I3wXFJo2AAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDRoPdI48KyoaLgaDZsSGs80qBeYQOXBd84CX8GYzFt/L21rxF1EeuPOkgsx7Q39WllXp+FgMMojsHftK/DJHEN";
let cert = Certificate::from_str(src).unwrap();
assert_eq!(Algorithm::Ed25519, cert.public_key().algorithm());
assert_eq!(cert.critical_options().len(), 1);
assert_eq!(
cert.critical_options().get("hello@example.com").unwrap(),
"foobar"
);
let openssh = cert.to_openssh().unwrap();
assert_eq!(openssh, src);
assert_eq!(cert, Certificate::from_str(&openssh).unwrap());
}
#[test]
fn decode_rsa_4096_openssh() {
let cert = Certificate::from_str(RSA_4096_CERT_EXAMPLE).unwrap();
assert_eq!(Algorithm::Rsa { hash: None }, cert.public_key().algorithm());
let rsa_key = cert.public_key().rsa().unwrap();
assert_eq!(&hex!("010001"), rsa_key.e.as_bytes());
assert_eq!(
&hex!(
"00b45911edc6ec5e7d2261a48c46ab889b1858306271123e6f02dc914cf3c0352492e8a6b7a7925added527
e547dcebff6d0c19c0bc9153975199f47f4964ed20f5aceed4e82556b228a0c1fbfaa85e6339ba2ff4094d9
4e2b09d43a3dd68225d0bbc858293cbf167b18d6374ebe79220a633d400176f1f6b46fd626acb252bf294aa
db2acd59626a023a8e5ec53ced8685164c72ca3a2ec646812c6e61ffcba740ff15c054f0691e3a8d52c79c4
4b7c1fc6c9704aed09ee0195bf09c5c5ba1173b7b1179be33fb3711d3b82e98f80521367a84303cb1236ebe
8fc095683420a4de652c071d592759d42a0c9d2e73313cdfb71a071c936659433481a406308820e173b934f
be877d873fec24d31a4d3bb9a3645055ca37bf710e214e5fc250d5964c66f18e4f05a3b93f42aa0753bd044
e45b456c0e62fdcc1fcadef72930dc8a7a96b3e27d8eecea139a00aaf2fe79063ccb78d26d537625bdf0c4c
8a68a04ed6f965eef7a6b1da5d8e26fc57f1047b97e2c594a9e420410977f22d1751b6d9498e8e457034049
3c336bf86563ef03a15bc49b0ba6fe73201f64f0413ddb4d0cc5f6cf43389907e1df29e0cc388040e3371d0
4814140f75cac08079431043222fb91f075d76be55cbe138e3b99a605c561c49dea50e253c8306c4f4f77d9
96f898db64c5d8a0a15c6efa28b0934bf0b6f2b01950d877230fe4401078420fd6dd3"
),
rsa_key.n.as_bytes(),
);
assert_eq!("user@example.com", cert.comment());
}
#[cfg(feature = "ecdsa")]
#[test]
fn decode_sk_ecdsa_p256_openssh() {
let cert = Certificate::from_str(SK_ECDSA_P256_CERT_EXAMPLE).unwrap();
assert_eq!(
Algorithm::SkEcdsaSha2NistP256,
cert.public_key().algorithm()
);
let ecdsa_key = cert.public_key().sk_ecdsa_p256().unwrap();
assert_eq!(
&hex!(
"04810b409d8382f697d72425285a247d6336b2eb9a085236aa9d1e268747ca0e8ee227f17375e944a775392
f1d35842d13f6237574ab03e00e9cc1799ecd8d931e"
),
ecdsa_key.ec_point().as_ref(),
);
assert_eq!("ssh:", ecdsa_key.application());
assert_eq!("user@example.com", cert.comment());
}
#[test]
fn decode_sk_ed25519_openssh() {
let cert = Certificate::from_str(SK_ED25519_CERT_EXAMPLE).unwrap();
assert_eq!(Algorithm::SkEd25519, cert.public_key().algorithm());
let ed25519_key = cert.public_key().sk_ed25519().unwrap();
assert_eq!(
&hex!("2168fe4e4b53cf3adeeeba602f5e50edb5ef441dba884f5119109db2dafdd733"),
ed25519_key.public_key().as_ref(),
);
assert_eq!("ssh:", ed25519_key.application());
assert_eq!("user@example.com", cert.comment());
}
#[test]
fn encode_dsa_openssh() {
let cert = Certificate::from_str(DSA_CERT_EXAMPLE).unwrap();
assert_eq!(DSA_CERT_EXAMPLE.trim_end(), &cert.to_openssh().unwrap());
}
#[cfg(feature = "ecdsa")]
#[test]
fn encode_ecdsa_p256_openssh() {
let cert = Certificate::from_str(ECDSA_P256_CERT_EXAMPLE).unwrap();
assert_eq!(
ECDSA_P256_CERT_EXAMPLE.trim_end(),
&cert.to_openssh().unwrap()
);
}
#[test]
fn encode_ed25519_openssh() {
let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap();
assert_eq!(ED25519_CERT_EXAMPLE.trim_end(), &cert.to_openssh().unwrap());
}
#[test]
fn encode_rsa_4096_openssh() {
let cert = Certificate::from_str(RSA_4096_CERT_EXAMPLE).unwrap();
assert_eq!(
RSA_4096_CERT_EXAMPLE.trim_end(),
&cert.to_openssh().unwrap()
);
}
#[cfg(feature = "ed25519")]
#[test]
fn verify_ed25519_certificate_signature() {
let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap();
assert_eq!(Algorithm::Ed25519, cert.signature_key().algorithm());
assert!(cert.verify_signature().is_ok());
}
#[cfg(feature = "ed25519")]
#[test]
fn reject_ed25519_certificate_with_invalid_signature() {
let cert = Certificate::from_str(ED25519_CERT_BADSIG_EXAMPLE).unwrap();
assert!(cert.verify_signature().is_err());
}
#[cfg(feature = "ed25519")]
#[test]
fn validate_certificate() {
let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap();
let ca = CA_FINGERPRINT.parse().unwrap();
assert!(cert.validate_at(VALID_TIMESTAMP, &[ca]).is_ok());
}
#[cfg(all(feature = "ed25519", feature = "std"))]
#[test]
fn validate_certificate_against_system_clock() {
let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap();
let ca = CA_FINGERPRINT.parse().unwrap();
assert!(cert.validate(&[ca]).is_ok());
}
#[cfg(feature = "ed25519")]
#[test]
fn reject_certificate_with_invalid_signature() {
let cert = Certificate::from_str(ED25519_CERT_BADSIG_EXAMPLE).unwrap();
let ca = CA_FINGERPRINT.parse().unwrap();
assert!(cert.validate_at(VALID_TIMESTAMP, &[ca]).is_err());
}
#[cfg(feature = "ed25519")]
#[test]
fn reject_certificate_with_untrusted_ca() {
let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap();
let ca = Certificate::from_str(DSA_CERT_EXAMPLE)
.unwrap()
.public_key()
.fingerprint(Default::default());
assert!(cert.validate_at(VALID_TIMESTAMP, &[ca]).is_err());
}
#[cfg(feature = "ed25519")]
#[test]
fn reject_expired_certificate() {
let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap();
let ca = CA_FINGERPRINT.parse().unwrap();
assert!(cert.validate_at(EXPIRED_TIMESTAMP, &[ca]).is_err());
}
#[cfg(feature = "ed25519")]
#[test]
fn reject_certificate_with_future_valid_after() {
let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap();
let ca = CA_FINGERPRINT.parse().unwrap();
assert!(cert.validate_at(PAST_TIMESTAMP, &[ca]).is_err())
}
#[cfg(feature = "p256")]
#[test]
fn verify_p256_certificate_signature() {
let cert = Certificate::from_str(ED25519_CERT_WITH_P256_CA_EXAMPLE).unwrap();
assert_eq!(
Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256
},
cert.signature_key().algorithm()
);
assert!(cert.verify_signature().is_ok());
}
#[cfg(feature = "rsa")]
#[test]
fn verify_rsa_certificate_signature() {
let cert = Certificate::from_str(ED25519_CERT_WITH_RSA_CA_EXAMPLE).unwrap();
assert_eq!(
Algorithm::Rsa { hash: None },
cert.signature_key().algorithm()
);
assert!(cert.verify_signature().is_ok());
assert_eq!(
Algorithm::Rsa {
hash: Some(HashAlg::Sha512)
},
cert.signature().algorithm()
);
}

View File

@@ -0,0 +1,209 @@
//! Certificate builder tests.
#![cfg(all(
feature = "alloc",
feature = "rand_core",
any(feature = "ed25519", feature = "p256")
))]
use hex_literal::hex;
use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
use ssh_key::{certificate, Algorithm, PrivateKey};
#[cfg(feature = "p256")]
use ssh_key::EcdsaCurve;
#[cfg(all(feature = "ed25519", feature = "rsa"))]
use std::str::FromStr;
#[cfg(all(feature = "ed25519", feature = "std"))]
use std::time::{Duration, SystemTime};
/// Example Unix timestamp when a certificate was issued (2020-09-13 12:26:40 UTC).
const ISSUED_AT: u64 = 1600000000;
/// Example Unix timestamp when a certificate is valid (2022-04-15 05:20:00 UTC).
const VALID_AT: u64 = 1650000000;
/// Example Unix timestamp when a certificate expires (2023-11-14 22:13:20 UTC).
const EXPIRES_AT: u64 = 1700000000;
/// Seed to use for PRNG.
const PRNG_SEED: [u8; 32] = [42; 32];
#[cfg(feature = "ed25519")]
#[test]
fn ed25519_sign_and_verify() {
const SERIAL: u64 = 42;
const KEY_ID: &str = "example";
const PRINCIPAL: &str = "nobody";
const CRITICAL_EXTENSION_1: (&str, &str) = ("critical name 1", "critical data 2");
const CRITICAL_EXTENSION_2: (&str, &str) = ("critical name 2", "critical data 2");
const EXTENSION_1: (&str, &str) = ("extension name 1", "extension data 1");
const EXTENSION_2: (&str, &str) = ("extension name 2", "extension data 2");
const COMMENT: &str = "user@example.com";
let mut rng = ChaCha8Rng::from_seed(PRNG_SEED);
let ca_key = PrivateKey::random(&mut rng, Algorithm::Ed25519).unwrap();
let subject_key = PrivateKey::random(&mut rng, Algorithm::Ed25519).unwrap();
let mut cert_builder = certificate::Builder::new_with_random_nonce(
&mut rng,
subject_key.public_key(),
ISSUED_AT,
EXPIRES_AT,
)
.unwrap();
cert_builder.serial(SERIAL).unwrap();
cert_builder.key_id(KEY_ID).unwrap();
cert_builder.valid_principal(PRINCIPAL).unwrap();
cert_builder
.critical_option(CRITICAL_EXTENSION_1.0, CRITICAL_EXTENSION_1.1)
.unwrap();
cert_builder
.critical_option(CRITICAL_EXTENSION_2.0, CRITICAL_EXTENSION_2.1)
.unwrap();
cert_builder
.extension(EXTENSION_1.0, EXTENSION_1.1)
.unwrap();
cert_builder
.extension(EXTENSION_2.0, EXTENSION_2.1)
.unwrap();
cert_builder.comment(COMMENT).unwrap();
let cert = cert_builder.sign(&ca_key).unwrap();
assert_eq!(cert.algorithm(), Algorithm::Ed25519);
assert_eq!(cert.nonce(), &hex!("321fdf7e0a2afe803308f394f54c6abe"));
assert_eq!(cert.public_key(), subject_key.public_key().key_data());
assert_eq!(cert.serial(), SERIAL);
assert_eq!(cert.cert_type(), certificate::CertType::User);
assert_eq!(cert.key_id(), KEY_ID);
assert_eq!(cert.valid_principals().len(), 1);
assert_eq!(cert.valid_principals()[0], PRINCIPAL);
assert_eq!(cert.valid_after(), ISSUED_AT);
assert_eq!(cert.valid_before(), EXPIRES_AT);
assert_eq!(cert.critical_options().len(), 2);
assert_eq!(
cert.critical_options().get(CRITICAL_EXTENSION_1.0).unwrap(),
CRITICAL_EXTENSION_1.1
);
assert_eq!(cert.extensions().get(EXTENSION_2.0).unwrap(), EXTENSION_2.1);
assert_eq!(cert.extensions().len(), 2);
assert_eq!(cert.extensions().get(EXTENSION_1.0).unwrap(), EXTENSION_1.1);
assert_eq!(cert.extensions().get(EXTENSION_2.0).unwrap(), EXTENSION_2.1);
assert_eq!(cert.signature_key(), ca_key.public_key().key_data());
assert_eq!(cert.comment(), COMMENT);
let ca_fingerprint = ca_key.fingerprint(Default::default());
assert!(cert.validate_at(VALID_AT, &[ca_fingerprint]).is_ok());
}
#[cfg(feature = "p256")]
#[test]
fn ecdsa_nistp256_sign_and_verify() {
let mut rng = ChaCha8Rng::from_seed(PRNG_SEED);
let algorithm = Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256,
};
let ca_key = PrivateKey::random(&mut rng, algorithm.clone()).unwrap();
let subject_key = PrivateKey::random(&mut rng, algorithm.clone()).unwrap();
let mut cert_builder = certificate::Builder::new_with_random_nonce(
&mut rng,
subject_key.public_key(),
ISSUED_AT,
EXPIRES_AT,
)
.unwrap();
cert_builder.all_principals_valid().unwrap();
let cert = cert_builder.sign(&ca_key).unwrap();
assert_eq!(cert.algorithm(), algorithm);
assert_eq!(cert.nonce(), &hex!("321fdf7e0a2afe803308f394f54c6abe"));
assert_eq!(cert.public_key(), subject_key.public_key().key_data());
assert_eq!(cert.signature_key(), ca_key.public_key().key_data());
let ca_fingerprint = ca_key.fingerprint(Default::default());
assert!(cert.validate_at(VALID_AT, &[ca_fingerprint]).is_ok());
}
#[cfg(all(feature = "ed25519", feature = "rsa"))]
#[test]
fn rsa_sign_and_verify() {
let ca_key = PrivateKey::from_str(
r#"-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEAyng6J3IE5++Ji7EfVNTANDnhYH46LnZW+bwW45etzKswQkc/AvSA
9ih2VAhE8FFUR0Z6pyl4hEn/878x50pGt1FHplbbe4wZ5aornT1hcGGYy313Glt+zyn96M
BTAjO0yULa1RrhBBmeY3yXIEAApUIVdvxcLOvJgltSFmFURtbY5cZkweuspwnHBE/JUPBX
9/Njb+z2R4BTnf0UrudxRKA/TJx9mL3Pb2JjkXfQ07pZqp+oEiUoGMvdfN9vYW4J5LTbXo
n20kRt5UKSxKggBBa0rzGabF+P/BTd39ZrI27WRYhDAzeYJoLq/xfO6qCgAM3TKxe0tDeT
gV4akFJ9CwAAA7hN/dPaTf3T2gAAAAdzc2gtcnNhAAABAQDKeDoncgTn74mLsR9U1MA0Oe
Fgfjoudlb5vBbjl63MqzBCRz8C9ID2KHZUCETwUVRHRnqnKXiESf/zvzHnSka3UUemVtt7
jBnlqiudPWFwYZjLfXcaW37PKf3owFMCM7TJQtrVGuEEGZ5jfJcgQAClQhV2/Fws68mCW1
IWYVRG1tjlxmTB66ynCccET8lQ8Ff382Nv7PZHgFOd/RSu53FEoD9MnH2Yvc9vYmORd9DT
ulmqn6gSJSgYy918329hbgnktNteifbSRG3lQpLEqCAEFrSvMZpsX4/8FN3f1msjbtZFiE
MDN5gmgur/F87qoKAAzdMrF7S0N5OBXhqQUn0LAAAAAwEAAQAAAQAxxSgWdjK6iOl4y0t2
YO32aJv8SksnDLQIo7HEtI5ml1Y/lJ/qrAvfdsbPlVDM+lELTEnuOYWEj2Q5mLA9uMZ1Xa
eNPiCp2CCtkg0yk9oV9AfJTcgvVHpxllLyGgTNr8QrDSIZ7IePqHSE5CWKKfF+riX0n8hQ
yo04XBZrpfU/jDQV8ENKiNQd3Aiy6ppSbnDhyTzZEYIxtvnh1FmvU0Ct1jQRd8p42gurEn
sq6nAPE9pnn0otKmjRdfGCnM9X/ZbUcaUcU/X8pPYG1pW0GZR7eTO+1f9s8TS5LIqz2Eru
L4gBQweASh9mhatsMqJX/ZRrdHvdIuH8N1VDSahf1ZTxAAAAgF1+qA6ZVBEaoCj+fAJZyU
EYf7NMI/nPqEVxiIjg4WKmRYKC9Pb9cuGehOs/XTi3KMEHzYJIKT1K+uO0OG025XVH06qk
9qyWcBwtRbCPVFJPSkKyGBPaUIxMI07x1+434vig6z7iwVROxy3vyhslgiJNpIkaWVUhQN
EGEHX0oWLfAAAAgQDLd25QLAb1kngTsuwQ+xo3S6UcQvOTiDnVRvxWPaW4yn/3qO55+esd
dzxUujFXhUO/POeUJiHv0B1QlDm/sHYL6YVI5+XRaWAst/z0T93mM4ts63Z1OoJbAtE5qH
yGlKVPQ5ZG8SUVElbX+SZE2CcnsPx53trW8qQu/R2bPdDN7QAAAIEA/r7nlgz6D93vMVkn
wq38d49h+PTfyBQ1bum8AhxCEfTaK94YrH9BeizO6Ma5MIjY6WHWbq7Co93J3fl8f4eTCo
CpHJYWfbBqrf/5PUoOIjdMdfFHK6GpUCQNxhbSpnL4l75sxrhkEXtBHVKRXCNR5T4JnOcx
R6qbyo6hPuCiV9cAAAAAAQID
-----END OPENSSH PRIVATE KEY-----"#,
)
.unwrap();
let mut rng = ChaCha8Rng::from_seed(PRNG_SEED);
let subject_key = PrivateKey::random(&mut rng, Algorithm::Ed25519).unwrap();
let mut cert_builder = certificate::Builder::new_with_random_nonce(
&mut rng,
subject_key.public_key(),
ISSUED_AT,
EXPIRES_AT,
)
.unwrap();
cert_builder.all_principals_valid().unwrap();
let cert = cert_builder.sign(&ca_key).unwrap();
assert_eq!(
cert.signature_key().algorithm(),
Algorithm::Rsa { hash: None }
);
assert_eq!(cert.nonce(), &hex!("55742ecb25ee56057b9e35eae54c40a9"));
assert_eq!(cert.public_key(), subject_key.public_key().key_data());
assert_eq!(cert.signature_key(), ca_key.public_key().key_data());
let ca_fingerprint = ca_key.fingerprint(Default::default());
assert!(cert.validate_at(VALID_AT, &[ca_fingerprint]).is_ok());
}
#[cfg(all(feature = "ed25519", feature = "std"))]
#[test]
fn new_with_validity_times() {
let mut rng = ChaCha8Rng::from_seed(PRNG_SEED);
let subject_key = PrivateKey::random(&mut rng, Algorithm::Ed25519).unwrap();
// NOTE: use a random nonce, not an all-zero one!
let nonce = [0u8; certificate::Builder::RECOMMENDED_NONCE_SIZE];
let issued_at = SystemTime::now();
let expires_at = issued_at + Duration::from_secs(3600);
assert!(certificate::Builder::new_with_validity_times(
nonce,
subject_key.public_key(),
issued_at,
expires_at
)
.is_ok());
}

View File

@@ -0,0 +1,495 @@
//! Encrypted SSH private key tests.
#![cfg(feature = "alloc")]
use hex_literal::hex;
use ssh_key::{Algorithm, Cipher, Kdf, KdfAlg, PrivateKey};
/// Unencrypted Ed25519 OpenSSH-formatted private key.
#[cfg(feature = "encryption")]
const OPENSSH_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519");
/// AES128-CBC encrypted Ed25519 OpenSSH-formatted private key.
///
/// Plaintext is `OPENSSH_ED25519_EXAMPLE`.
#[cfg(feature = "encryption")]
const OPENSSH_AES128_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-cbc.enc");
/// AES192-CBC encrypted Ed25519 OpenSSH-formatted private key.
///
/// Plaintext is `OPENSSH_ED25519_EXAMPLE`.
#[cfg(feature = "encryption")]
const OPENSSH_AES192_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes192-cbc.enc");
/// AES256-CBC encrypted Ed25519 OpenSSH-formatted private key.
///
/// Plaintext is `OPENSSH_ED25519_EXAMPLE`.
#[cfg(feature = "encryption")]
const OPENSSH_AES256_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes256-cbc.enc");
/// AES128-CTR encrypted Ed25519 OpenSSH-formatted private key.
///
/// Plaintext is `OPENSSH_ED25519_EXAMPLE`.
#[cfg(feature = "encryption")]
const OPENSSH_AES128_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-ctr.enc");
/// AES192-CTR encrypted Ed25519 OpenSSH-formatted private key.
///
/// Plaintext is `OPENSSH_ED25519_EXAMPLE`.
#[cfg(feature = "encryption")]
const OPENSSH_AES192_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes192-ctr.enc");
/// AES256-CTR encrypted Ed25519 OpenSSH-formatted private key.
///
/// Plaintext is `OPENSSH_ED25519_EXAMPLE`.
const OPENSSH_AES256_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes256-ctr.enc");
/// AES256-GCM encrypted Ed25519 OpenSSH-formatted private key.
///
/// Plaintext is `OPENSSH_ED25519_EXAMPLE`.
#[cfg(feature = "encryption")]
const OPENSSH_AES128_GCM_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-gcm.enc");
/// AES256-GCM encrypted Ed25519 OpenSSH-formatted private key.
///
/// Plaintext is `OPENSSH_ED25519_EXAMPLE`.
const OPENSSH_AES256_GCM_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes256-gcm.enc");
/// ChaCha20-Poly1305 encrypted Ed25519 OpenSSH-formatted private key.
///
/// Plaintext is `OPENSSH_ED25519_EXAMPLE`.
const OPENSSH_CHACHA20_POLY1305_ED25519_EXAMPLE: &str =
include_str!("examples/id_ed25519.chacha20-poly1305.enc");
/// TripleDES-CBC encrypted Ed25519 OpenSSH-formatted private key.
///
/// Plaintext is `OPENSSH_ED25519_EXAMPLE`.
const OPENSSH_3DES_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.3des-cbc.enc");
/// Bad password; don't actually use outside tests!
#[cfg(feature = "encryption")]
const PASSWORD: &[u8] = b"hunter42";
#[test]
fn decode_openssh_aes256_ctr() {
let key = PrivateKey::from_openssh(OPENSSH_AES256_CTR_ED25519_EXAMPLE).unwrap();
assert_eq!(Algorithm::Ed25519, key.algorithm());
assert_eq!(Cipher::Aes256Ctr, key.cipher());
assert_eq!(KdfAlg::Bcrypt, key.kdf().algorithm());
match key.kdf() {
Kdf::Bcrypt { salt, rounds } => {
assert_eq!(salt, &hex!("4a1fdeae8d6ba607afd69d334f8d379a"));
assert_eq!(*rounds, 16);
}
other => panic!("unexpected KDF algorithm: {:?}", other),
}
assert_eq!(
&hex!("b33eaef37ea2df7caa010defdea34e241f65f1b529a4f43ed14327f5c54aab62"),
key.public_key().key_data().ed25519().unwrap().as_ref(),
);
}
#[test]
fn decode_openssh_aes256_gcm() {
let key = PrivateKey::from_openssh(OPENSSH_AES256_GCM_ED25519_EXAMPLE).unwrap();
assert_eq!(Algorithm::Ed25519, key.algorithm());
assert_eq!(Cipher::Aes256Gcm, key.cipher());
assert_eq!(KdfAlg::Bcrypt, key.kdf().algorithm());
match key.kdf() {
Kdf::Bcrypt { salt, rounds } => {
assert_eq!(salt, &hex!("11bdc133ef64644115b176917e47cbaf"));
assert_eq!(*rounds, 16);
}
other => panic!("unexpected KDF algorithm: {:?}", other),
}
assert_eq!(
&hex!("b33eaef37ea2df7caa010defdea34e241f65f1b529a4f43ed14327f5c54aab62"),
key.public_key().key_data().ed25519().unwrap().as_ref(),
);
}
#[test]
fn decode_openssh_chacha20_poly1305() {
let key = PrivateKey::from_openssh(OPENSSH_CHACHA20_POLY1305_ED25519_EXAMPLE).unwrap();
assert_eq!(Algorithm::Ed25519, key.algorithm());
assert_eq!(Cipher::ChaCha20Poly1305, key.cipher());
assert_eq!(KdfAlg::Bcrypt, key.kdf().algorithm());
match key.kdf() {
Kdf::Bcrypt { salt, rounds } => {
assert_eq!(salt, &hex!("f651ca3efb15904d05c216a5041ea89a"));
assert_eq!(*rounds, 16);
}
other => panic!("unexpected KDF algorithm: {:?}", other),
}
assert_eq!(
&hex!("b33eaef37ea2df7caa010defdea34e241f65f1b529a4f43ed14327f5c54aab62"),
key.public_key().key_data().ed25519().unwrap().as_ref(),
);
}
#[test]
fn decode_openssh_3des_cbc() {
let key = PrivateKey::from_openssh(OPENSSH_3DES_CBC_ED25519_EXAMPLE).unwrap();
assert_eq!(Algorithm::Ed25519, key.algorithm());
assert_eq!(Cipher::TDesCbc, key.cipher());
assert_eq!(KdfAlg::Bcrypt, key.kdf().algorithm());
match key.kdf() {
Kdf::Bcrypt { salt, rounds } => {
assert_eq!(salt, &hex!("1afcebea3c598c277e7edc2b78db1e94"));
assert_eq!(*rounds, 16);
}
other => panic!("unexpected KDF algorithm: {:?}", other),
}
assert_eq!(
&hex!("b33eaef37ea2df7caa010defdea34e241f65f1b529a4f43ed14327f5c54aab62"),
key.public_key().key_data().ed25519().unwrap().as_ref(),
);
}
#[cfg(feature = "encryption")]
#[test]
fn decrypt_openssh_aes128_ctr() {
let key_enc = PrivateKey::from_openssh(OPENSSH_AES128_CTR_ED25519_EXAMPLE).unwrap();
assert_eq!(Cipher::Aes128Ctr, key_enc.cipher());
let key_dec = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(
PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(),
key_dec
);
}
#[cfg(feature = "encryption")]
#[test]
fn decrypt_openssh_aes192_ctr() {
let key_enc = PrivateKey::from_openssh(OPENSSH_AES192_CTR_ED25519_EXAMPLE).unwrap();
assert_eq!(Cipher::Aes192Ctr, key_enc.cipher());
let key_dec = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(
PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(),
key_dec
);
}
#[cfg(feature = "encryption")]
#[test]
fn decrypt_openssh_aes256_ctr() {
let key_enc = PrivateKey::from_openssh(OPENSSH_AES256_CTR_ED25519_EXAMPLE).unwrap();
assert_eq!(Cipher::Aes256Ctr, key_enc.cipher());
let key_dec = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(
PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(),
key_dec
);
}
#[cfg(feature = "encryption")]
#[test]
fn decrypt_openssh_aes128_cbc() {
let key_enc = PrivateKey::from_openssh(OPENSSH_AES128_CBC_ED25519_EXAMPLE).unwrap();
assert_eq!(Cipher::Aes128Cbc, key_enc.cipher());
let key_dec = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(
PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(),
key_dec
);
}
#[cfg(feature = "encryption")]
#[test]
fn decrypt_openssh_aes192_cbc() {
let key_enc = PrivateKey::from_openssh(OPENSSH_AES192_CBC_ED25519_EXAMPLE).unwrap();
assert_eq!(Cipher::Aes192Cbc, key_enc.cipher());
let key_dec = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(
PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(),
key_dec
);
}
#[cfg(feature = "encryption")]
#[test]
fn decrypt_openssh_aes256_cbc() {
let key_enc = PrivateKey::from_openssh(OPENSSH_AES256_CBC_ED25519_EXAMPLE).unwrap();
assert_eq!(Cipher::Aes256Cbc, key_enc.cipher());
let key_dec = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(
PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(),
key_dec
);
}
#[cfg(feature = "encryption")]
#[test]
fn decrypt_openssh_aes128_gcm() {
let key_enc = PrivateKey::from_openssh(OPENSSH_AES128_GCM_ED25519_EXAMPLE).unwrap();
assert_eq!(Cipher::Aes128Gcm, key_enc.cipher());
let key_dec = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(
PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(),
key_dec
);
}
#[cfg(feature = "encryption")]
#[test]
fn decrypt_openssh_aes256_gcm() {
let key_enc = PrivateKey::from_openssh(OPENSSH_AES256_GCM_ED25519_EXAMPLE).unwrap();
assert_eq!(Cipher::Aes256Gcm, key_enc.cipher());
let key_dec = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(
PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(),
key_dec
);
}
#[cfg(feature = "encryption")]
#[test]
fn decrypt_openssh_chacha20_poly1305() {
let key_enc = PrivateKey::from_openssh(OPENSSH_CHACHA20_POLY1305_ED25519_EXAMPLE).unwrap();
assert_eq!(Cipher::ChaCha20Poly1305, key_enc.cipher());
let key_dec = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(
PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(),
key_dec
);
}
#[cfg(feature = "tdes")]
#[test]
fn decrypt_openssh_3des() {
let key_enc = PrivateKey::from_openssh(OPENSSH_3DES_CBC_ED25519_EXAMPLE).unwrap();
assert_eq!(Cipher::TDesCbc, key_enc.cipher());
let key_dec = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(
PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(),
key_dec
);
}
#[test]
fn encode_openssh_aes256_ctr() {
let key = PrivateKey::from_openssh(OPENSSH_AES256_CTR_ED25519_EXAMPLE).unwrap();
assert_eq!(
OPENSSH_AES256_CTR_ED25519_EXAMPLE.trim_end(),
key.to_openssh(Default::default()).unwrap().trim_end()
);
}
#[test]
fn encode_openssh_aes256_gcm() {
let key = PrivateKey::from_openssh(OPENSSH_AES256_GCM_ED25519_EXAMPLE).unwrap();
assert_eq!(
OPENSSH_AES256_GCM_ED25519_EXAMPLE.trim_end(),
key.to_openssh(Default::default()).unwrap().trim_end()
);
}
#[cfg(all(feature = "encryption", feature = "getrandom"))]
#[test]
fn encrypt_openssh_aes128_cbc() {
use rand_core::OsRng;
let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap();
let key_enc = key_dec
.encrypt_with_cipher(&mut OsRng, Cipher::Aes128Cbc, PASSWORD)
.unwrap();
// Ensure encrypted key round trips through encoder/decoder
let key_enc_str = key_enc.to_openssh(Default::default()).unwrap();
let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap();
assert_eq!(key_enc, key_enc2);
// Ensure decrypted key matches the original
let key_dec2 = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(key_dec, key_dec2);
}
#[cfg(all(feature = "encryption", feature = "getrandom"))]
#[test]
fn encrypt_openssh_aes192_cbc() {
use rand_core::OsRng;
let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap();
let key_enc = key_dec
.encrypt_with_cipher(&mut OsRng, Cipher::Aes192Cbc, PASSWORD)
.unwrap();
// Ensure encrypted key round trips through encoder/decoder
let key_enc_str = key_enc.to_openssh(Default::default()).unwrap();
let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap();
assert_eq!(key_enc, key_enc2);
// Ensure decrypted key matches the original
let key_dec2 = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(key_dec, key_dec2);
}
#[cfg(all(feature = "encryption", feature = "getrandom"))]
#[test]
fn encrypt_openssh_aes256_cbc() {
use rand_core::OsRng;
let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap();
let key_enc = key_dec
.encrypt_with_cipher(&mut OsRng, Cipher::Aes256Cbc, PASSWORD)
.unwrap();
// Ensure encrypted key round trips through encoder/decoder
let key_enc_str = key_enc.to_openssh(Default::default()).unwrap();
let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap();
assert_eq!(key_enc, key_enc2);
// Ensure decrypted key matches the original
let key_dec2 = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(key_dec, key_dec2);
}
#[cfg(all(feature = "encryption", feature = "getrandom"))]
#[test]
fn encrypt_openssh_aes128_ctr() {
use rand_core::OsRng;
let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap();
let key_enc = key_dec
.encrypt_with_cipher(&mut OsRng, Cipher::Aes128Ctr, PASSWORD)
.unwrap();
// Ensure encrypted key round trips through encoder/decoder
let key_enc_str = key_enc.to_openssh(Default::default()).unwrap();
let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap();
assert_eq!(key_enc, key_enc2);
// Ensure decrypted key matches the original
let key_dec2 = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(key_dec, key_dec2);
}
#[cfg(all(feature = "encryption", feature = "getrandom"))]
#[test]
fn encrypt_openssh_aes192_ctr() {
use rand_core::OsRng;
let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap();
let key_enc = key_dec
.encrypt_with_cipher(&mut OsRng, Cipher::Aes192Ctr, PASSWORD)
.unwrap();
// Ensure encrypted key round trips through encoder/decoder
let key_enc_str = key_enc.to_openssh(Default::default()).unwrap();
let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap();
assert_eq!(key_enc, key_enc2);
// Ensure decrypted key matches the original
let key_dec2 = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(key_dec, key_dec2);
}
#[cfg(all(feature = "encryption", feature = "getrandom"))]
#[test]
fn encrypt_openssh_aes256_ctr() {
use rand_core::OsRng;
let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap();
let key_enc = key_dec.encrypt(&mut OsRng, PASSWORD).unwrap();
// Ensure encrypted key round trips through encoder/decoder
let key_enc_str = key_enc.to_openssh(Default::default()).unwrap();
let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap();
assert_eq!(key_enc, key_enc2);
// Ensure decrypted key matches the original
let key_dec2 = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(key_dec, key_dec2);
}
#[cfg(all(feature = "encryption", feature = "getrandom"))]
#[test]
fn encrypt_openssh_aes128_gcm() {
use rand_core::OsRng;
let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap();
let key_enc = key_dec
.encrypt_with_cipher(&mut OsRng, Cipher::Aes128Gcm, PASSWORD)
.unwrap();
// Ensure encrypted key round trips through encoder/decoder
let key_enc_str = key_enc.to_openssh(Default::default()).unwrap();
let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap();
assert_eq!(key_enc, key_enc2);
// Ensure decrypted key matches the original
let key_dec2 = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(key_dec, key_dec2);
}
#[cfg(all(feature = "encryption", feature = "getrandom"))]
#[test]
fn encrypt_openssh_aes256_gcm() {
use rand_core::OsRng;
let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap();
let key_enc = key_dec
.encrypt_with_cipher(&mut OsRng, Cipher::Aes256Gcm, PASSWORD)
.unwrap();
// Ensure encrypted key round trips through encoder/decoder
let key_enc_str = key_enc.to_openssh(Default::default()).unwrap();
let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap();
assert_eq!(key_enc, key_enc2);
// Ensure decrypted key matches the original
let key_dec2 = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(key_dec, key_dec2);
}
#[cfg(all(feature = "encryption", feature = "getrandom"))]
#[test]
fn encrypt_openssh_chacha20_poly1305() {
use rand_core::OsRng;
let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap();
let key_enc = key_dec
.encrypt_with_cipher(&mut OsRng, Cipher::ChaCha20Poly1305, PASSWORD)
.unwrap();
// Ensure encrypted key round trips through encoder/decoder
let key_enc_str = key_enc.to_openssh(Default::default()).unwrap();
let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap();
assert_eq!(key_enc, key_enc2);
// Ensure decrypted key matches the original
let key_dec2 = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(key_dec, key_dec2);
}
#[cfg(all(feature = "tdes", feature = "getrandom"))]
#[test]
fn encrypt_openssh_3des() {
use rand_core::OsRng;
let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap();
let key_enc = key_dec
.encrypt_with_cipher(&mut OsRng, Cipher::TDesCbc, PASSWORD)
.unwrap();
// Ensure encrypted key round trips through encoder/decoder
let key_enc_str = key_enc.to_openssh(Default::default()).unwrap();
let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap();
assert_eq!(key_enc, key_enc2);
// Ensure decrypted key matches the original
let key_dec2 = key_enc.decrypt(PASSWORD).unwrap();
assert_eq!(key_dec, key_dec2);
}

View File

@@ -0,0 +1,31 @@
# Example authorized keys file
#
# - Comments in these files begin with `#`
# - They can also contain blank lines
# - Lines which are not blank each contain a single public key
# - Maximum line length is 8 kilobytes
#
# Public keys consist of the following space-separated fields:
#
# options, keytype, base64-encoded key, comment
#
# - The options field is optional.
# - The keytype is `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`,
# `ssh-ed25519`, `ssh-dss` or `ssh-rsa`
# - The comment field is not used for anything (but may be convenient for the user to
# identify the key).
# Public key with no options and no comment
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti
# Public key which can only read the current date
command="/usr/bin/date" ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2SQJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcEc= user2@example.com
# Public key which ensures a certain environment is set
environment="PATH=/bin:/usr/bin" ssh-dss AAAAB3NzaC1kc3MAAACBANw9iSUO2UYhFMssjUgW46URqv8bBrDgHeF8HLBOWBvKuXF2Rx2J/XyhgX48SOLMuv0hcPaejlyLarabnF9F2V4dkpPpZSJ+7luHmxEjNxwhsdtg8UteXAWkeCzrQ6MvRJZHcDBjYh56KGvslbFnJsGLXlI4PQCyl6awNImwYGilAAAAFQCJGBU3hZf+QtP9Jh/nbfNlhFu7hwAAAIBHObOQioQVRm3HsVb7mOy3FVKhcLoLO3qoG9gTkd4KeuehtFAC3+rckiX7xSCnE/5BBKdL7VP9WRXac2Nlr9Pwl3e7zPut96wrCHt/TZX6vkfXKkbpUIj5zSqfvyNrWKaYJkfzwAQwrXNS1Hol676Ud/DDEn2oatdEhkS3beWHXAAAAIBgQqaz/YYTRMshzMzYcZ4lqgvgmA55y6v0h39e8HH2A5dwNS6sPUw2jyna+le0dceNRJifFld1J+WYM0vmquSr11DDavgEidOSaXwfMvPPPJqLmbzdtT16N+Gij9U9STQTHPQcQ3xnNNHgQAStzZJbhLOVbDDDo5BO7LMUALDfSA== user3@example.com
# Public key which can only be used from certain source addresses and disallows X11 forwarding
from="10.0.0.?,*.example.com",no-X11-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC0WRHtxuxefSJhpIxGq4ibGFgwYnESPm8C3JFM88A1JJLoprenklrd7VJ+VH3Ov/bQwZwLyRU5dRmfR/SWTtIPWs7tToJVayKKDB+/qoXmM5ui/0CU2U4rCdQ6PdaCJdC7yFgpPL8WexjWN06+eSIKYz1AAXbx9rRv1iasslK/KUqtsqzVliagI6jl7FPO2GhRZMcso6LsZGgSxuYf/Lp0D/FcBU8GkeOo1Sx5xEt8H8bJcErtCe4Blb8JxcW6EXO3sReb4z+zcR07gumPgFITZ6hDA8sSNuvo/AlWg0IKTeZSwHHVknWdQqDJ0uczE837caBxyTZllDNIGkBjCIIOFzuTT76HfYc/7CTTGk07uaNkUFXKN79xDiFOX8JQ1ZZMZvGOTwWjuT9CqgdTvQRORbRWwOYv3MH8re9ykw3Ip6lrPifY7s6hOaAKry/nkGPMt40m1TdiW98MTIpooE7W+WXu96ax2l2OJvxX8QR7l+LFlKnkIEEJd/ItF1G22UmOjkVwNASTwza/hlY+8DoVvEmwum/nMgH2TwQT3bTQzF9s9DOJkH4d8p4Mw4gEDjNx0EgUFA91ysCAeUMQQyIvuR8HXXa+VcvhOOO5mmBcVhxJ3qUOJTyDBsT0932Zb4mNtkxdigoVxu+iiwk0vwtvKwGVDYdyMP5EAQeEIP1t0w== user4@example.com
# Public key with a comment that contains multiple spaces
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN76zuqnjypL54/w4763l7q1Sn3IBYHptJ5wcYfEWkzeNTvpexr05Z18m2yPT2SWRd1JJ8Aj5TYidG9MdSS5J78= hello world this is a long comment

14
vendor/ssh-key/tests/examples/generate.sh vendored Executable file
View File

@@ -0,0 +1,14 @@
# Generator script for test cases
#
# This shouldn't ever need to be run again, but automates and documents how the
# test vectors in this directory were created.
set -eux
ssh-keygen -t dsa -f id_dsa_1024 -C user@example.com
ssh-keygen -t ecdsa -b 256 -f id_ecdsa_p256 -C user@example.com
ssh-keygen -t ecdsa -b 384 -f id_ecdsa_p384 -C user@example.com
ssh-keygen -t ecdsa -b 521 -f id_ecdsa_p521 -C user@example.com
ssh-keygen -t ed25519 -f id_ed25519 -C user@example.com
ssh-keygen -t rsa -b 3072 -f id_rsa_3072 -C user@example.com
ssh-keygen -t rsa -b 4096 -f id_rsa_4096 -C user@example.com

View File

@@ -0,0 +1,21 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH
NzAAAAgQDcPYklDtlGIRTLLI1IFuOlEar/Gwaw4B3hfBywTlgbyrlxdkcdif18oYF+PEji
zLr9IXD2no5ci2q2m5xfRdleHZKT6WUifu5bh5sRIzccIbHbYPFLXlwFpHgs60OjL0SWR3
AwY2Ieeihr7JWxZybBi15SOD0AspemsDSJsGBopQAAABUAiRgVN4WX/kLT/SYf523zZYRb
u4cAAACARzmzkIqEFUZtx7FW+5jstxVSoXC6Czt6qBvYE5HeCnrnobRQAt/q3JIl+8Ugpx
P+QQSnS+1T/VkV2nNjZa/T8Jd3u8z7rfesKwh7f02V+r5H1ypG6VCI+c0qn78ja1immCZH
88AEMK1zUtR6Jeu+lHfwwxJ9qGrXRIZEt23lh1wAAACAYEKms/2GE0TLIczM2HGeJaoL4J
gOecur9Id/XvBx9gOXcDUurD1MNo8p2vpXtHXHjUSYnxZXdSflmDNL5qrkq9dQw2r4BInT
kml8HzLzzzyai5m83bU9ejfhoo/VPUk0Exz0HEN8ZzTR4EAErc2SW4SzlWwww6OQTuyzFA
Cw30gAAAHo9I0p/vSNKf4AAAAHc3NoLWRzcwAAAIEA3D2JJQ7ZRiEUyyyNSBbjpRGq/xsG
sOAd4XwcsE5YG8q5cXZHHYn9fKGBfjxI4sy6/SFw9p6OXItqtpucX0XZXh2Sk+llIn7uW4
ebESM3HCGx22DxS15cBaR4LOtDoy9ElkdwMGNiHnooa+yVsWcmwYteUjg9ALKXprA0ibBg
aKUAAAAVAIkYFTeFl/5C0/0mH+dt82WEW7uHAAAAgEc5s5CKhBVGbcexVvuY7LcVUqFwug
s7eqgb2BOR3gp656G0UALf6tySJfvFIKcT/kEEp0vtU/1ZFdpzY2Wv0/CXd7vM+633rCsI
e39Nlfq+R9cqRulQiPnNKp+/I2tYppgmR/PABDCtc1LUeiXrvpR38MMSfahq10SGRLdt5Y
dcAAAAgGBCprP9hhNEyyHMzNhxniWqC+CYDnnLq/SHf17wcfYDl3A1Lqw9TDaPKdr6V7R1
x41EmJ8WV3Un5ZgzS+aq5KvXUMNq+ASJ05JpfB8y8888mouZvN21PXo34aKP1T1JNBMc9B
xDfGc00eBABK3NkluEs5VsMMOjkE7ssxQAsN9IAAAAFAw3esRJ53DYmjVXdDy9BQOWEUti
AAAAEHVzZXJAZXhhbXBsZS5jb20BAgM=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb6UUNOel9C6CnoLpzrqm3TmZRFmHbkxSmus9tQ35W4UAAACBANw9iSUO2UYhFMssjUgW46URqv8bBrDgHeF8HLBOWBvKuXF2Rx2J/XyhgX48SOLMuv0hcPaejlyLarabnF9F2V4dkpPpZSJ+7luHmxEjNxwhsdtg8UteXAWkeCzrQ6MvRJZHcDBjYh56KGvslbFnJsGLXlI4PQCyl6awNImwYGilAAAAFQCJGBU3hZf+QtP9Jh/nbfNlhFu7hwAAAIBHObOQioQVRm3HsVb7mOy3FVKhcLoLO3qoG9gTkd4KeuehtFAC3+rckiX7xSCnE/5BBKdL7VP9WRXac2Nlr9Pwl3e7zPut96wrCHt/TZX6vkfXKkbpUIj5zSqfvyNrWKaYJkfzwAQwrXNS1Hol676Ud/DDEn2oatdEhkS3beWHXAAAAIBgQqaz/YYTRMshzMzYcZ4lqgvgmA55y6v0h39e8HH2A5dwNS6sPUw2jyna+le0dceNRJifFld1J+WYM0vmquSr11DDavgEidOSaXwfMvPPPJqLmbzdtT16N+Gij9U9STQTHPQcQ3xnNNHgQAStzZJbhLOVbDDDo5BO7LMUALDfSAAAAAAAAAAAAAAAAQAAAANkc2EAAAAAAAAAAGJHj+AAAAAA9NAo4AAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEA9a5kGt2h1rvHnsvHgIHipT0Oa67mkc02hqFGoHiLOAZm7+CA4eo3pyDTJw8x3jZly3L5w9o1TzGvJ4msCtG0E user@example.com

View File

@@ -0,0 +1 @@
ssh-dss AAAAB3NzaC1kc3MAAACBANw9iSUO2UYhFMssjUgW46URqv8bBrDgHeF8HLBOWBvKuXF2Rx2J/XyhgX48SOLMuv0hcPaejlyLarabnF9F2V4dkpPpZSJ+7luHmxEjNxwhsdtg8UteXAWkeCzrQ6MvRJZHcDBjYh56KGvslbFnJsGLXlI4PQCyl6awNImwYGilAAAAFQCJGBU3hZf+QtP9Jh/nbfNlhFu7hwAAAIBHObOQioQVRm3HsVb7mOy3FVKhcLoLO3qoG9gTkd4KeuehtFAC3+rckiX7xSCnE/5BBKdL7VP9WRXac2Nlr9Pwl3e7zPut96wrCHt/TZX6vkfXKkbpUIj5zSqfvyNrWKaYJkfzwAQwrXNS1Hol676Ud/DDEn2oatdEhkS3beWHXAAAAIBgQqaz/YYTRMshzMzYcZ4lqgvgmA55y6v0h39e8HH2A5dwNS6sPUw2jyna+le0dceNRJifFld1J+WYM0vmquSr11DDavgEidOSaXwfMvPPPJqLmbzdtT16N+Gij9U9STQTHPQcQ3xnNNHgQAStzZJbhLOVbDDDo5BO7LMUALDfSA== user@example.com

View File

@@ -0,0 +1,9 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR8H9hzDOU0V76NkkCY7DZIgw+Sqooj
Y6xlb91FIfpjE+UR8YkbTp5ar44ULQatFaZqQlfz8FHYTooOL5G6gHBHAAAAsB8RBhUfEQ
YVAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2S
QJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcE
cAAAAhAMp4pkd0v643EjIkk38DmJYBiXB6ygqGRc60NZxCO6B5AAAAEHVzZXJAZXhhbXBs
ZS5jb20BAgMEBQYH
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgZBJzVkJPUyTSRHpKR/MzGrek4PEGgKoSP0LhLueu15kAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2SQJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcEcAAAAAAAAAAAAAAAIAAAAOZWNkc2EtbmlzdHAyNTYAAAAUAAAAEGhvc3QuZXhhbXBsZS5jb20AAAAAYkowRgAAAAD00slGAAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAFMAAAALc3NoLWVkMjU1MTkAAABAMKGSXD1fMVh1N5fqBX20M7U9UPX06h0B+1jGV0soYNiORAUGGAQKWiOslk/xz7hhcnqwaDRG7+5eJTcPbnHqAA== user@example.com

View File

@@ -0,0 +1 @@
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2SQJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcEc= user@example.com

View File

@@ -0,0 +1,10 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQQuboLcVAfxBKERF8fAWxmTw86z2yX6
5ouhaVAqT/k5XZrTa1Q+gBT/FdcI4h8J9YWqbfrVdbeblDQYuGGY2bzZsH//k5mxXUPTTv
rrLla3szz/iAskKz4LWK+Wx1hB7EEAAADY/9Gv3P/Rr9wAAAATZWNkc2Etc2hhMi1uaXN0
cDM4NAAAAAhuaXN0cDM4NAAAAGEELm6C3FQH8QShERfHwFsZk8POs9sl+uaLoWlQKk/5OV
2a02tUPoAU/xXXCOIfCfWFqm361XW3m5Q0GLhhmNm82bB//5OZsV1D00766y5Wt7M8/4gL
JCs+C1ivlsdYQexBAAAAMAN32ekyiyklGWl3Mgor/gE4AYl/oCh4SK+Be9x/QA6IAf0PnA
V9EGkUs4nLFW9gCwAAABB1c2VyQGV4YW1wbGUuY29t
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBC5ugtxUB/EEoREXx8BbGZPDzrPbJfrmi6FpUCpP+TldmtNrVD6AFP8V1wjiHwn1hapt+tV1t5uUNBi4YZjZvNmwf/+TmbFdQ9NO+usuVrezPP+ICyQrPgtYr5bHWEHsQQ== user@example.com

View File

@@ -0,0 +1,12 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBYTaTTxkrI9lh+/RMgYQWYALOosfR
iyCtAY0EbvBo0+glD9Tp8XymaTqFVMMmmm2fV2Ki+aLLh5fUsgHeQh09zFgBA8uUeoWLt3
g9+GP4KVHZb5Gnkl1+K6rSbkfj8vpbB8gnKEikQjt1DXrSuLaS1m3eyuxThQhrH9G2gsop
HIjWN2IAAAEQn8R7O5/EezsAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
AAAIUEAWE2k08ZKyPZYfv0TIGEFmACzqLH0YsgrQGNBG7waNPoJQ/U6fF8pmk6hVTDJppt
n1diovmiy4eX1LIB3kIdPcxYAQPLlHqFi7d4Pfhj+ClR2W+Rp5Jdfiuq0m5H4/L6WwfIJy
hIpEI7dQ160ri2ktZt3srsU4UIax/RtoLKKRyI1jdiAAAAQgHskF8qt6kWnxYfCeVn/Ksi
W75idnJ6XyckU1wrZj162OMlJ9f1mYqZIkDLuQzsPtZ/6QK87ViL65csdxbgknzaggAAAB
B1c2VyQGV4YW1wbGUuY29tAQI=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFhNpNPGSsj2WH79EyBhBZgAs6ix9GLIK0BjQRu8GjT6CUP1OnxfKZpOoVUwyaabZ9XYqL5osuHl9SyAd5CHT3MWAEDy5R6hYu3eD34Y/gpUdlvkaeSXX4rqtJuR+Py+lsHyCcoSKRCO3UNetK4tpLWbd7K7FOFCGsf0baCyikciNY3Yg== user@example.com

View File

@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJgAIAxdACAM
XQAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYg
AAAEC2BsIi0QwW2uFscKTUUXNHLsYX4FxlaSDSblbAj7WR7bM+rvN+ot98qgEN796jTiQf
ZfG1KaT0PtFDJ/XFSqtiAAAAEHVzZXJAZXhhbXBsZS5jb20BAgMEBQ==
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIAYkJPGaYen7NK8MwZwWmNAyRaFNsc86AU9NObU2cM2uAAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAAAAAAAAAAAACAAAAB2VkMjU1MTkAAAAUAAAAEGhvc3QuZXhhbXBsZS5vcmcAAAAAYkx3NwAAAAB8DuY3AAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAFMAAAALc3NoLWVkMjU1MTkAAABApVXBNiYPlPoa1BYH5G4NP9XtjTMZlm7HO5GdbLSvvAw5Vdob7Ka+23hB7isJKHYtzFGGSKXAqxp/Zi8REbCaAw== user@example.com

View File

@@ -0,0 +1 @@
ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIE7x9ln6uZLLkfXM8iatrnAAuytVHeCznU8VlEgx7TvLAAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAAAAAAAAAAAABAAAAFGVkMjU1MTktd2l0aC1wMjU2LWNhAAAAAAAAAABiUZm7AAAAAPTaMrsAAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR8H9hzDOU0V76NkkCY7DZIgw+SqoojY6xlb91FIfpjE+UR8YkbTp5ar44ULQatFaZqQlfz8FHYTooOL5G6gHBHAAAAZAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAASQAAACEA/0Cwxhkac5AeNYE958j8GgvmkIESDH1TE7QYIqxsFsIAAAAgTEw8WVjlz8AnvyaKGOUELMpyFFJagtD2JFAIAJvilrc= user@example.com

View File

@@ -0,0 +1 @@
ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIDG45tSWpvNVTj7LUy9Vqz+LBxVsVsj5km8OIl+yUctjAAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAAAAAAAAAAAABAAAAE2VkMjU1MTktd2l0aC1yc2EtY2EAAAAAAAAAAGJRmeoAAAAA9Noy6gAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAIXAAAAB3NzaC1yc2EAAAADAQABAAACAQC0WRHtxuxefSJhpIxGq4ibGFgwYnESPm8C3JFM88A1JJLoprenklrd7VJ+VH3Ov/bQwZwLyRU5dRmfR/SWTtIPWs7tToJVayKKDB+/qoXmM5ui/0CU2U4rCdQ6PdaCJdC7yFgpPL8WexjWN06+eSIKYz1AAXbx9rRv1iasslK/KUqtsqzVliagI6jl7FPO2GhRZMcso6LsZGgSxuYf/Lp0D/FcBU8GkeOo1Sx5xEt8H8bJcErtCe4Blb8JxcW6EXO3sReb4z+zcR07gumPgFITZ6hDA8sSNuvo/AlWg0IKTeZSwHHVknWdQqDJ0uczE837caBxyTZllDNIGkBjCIIOFzuTT76HfYc/7CTTGk07uaNkUFXKN79xDiFOX8JQ1ZZMZvGOTwWjuT9CqgdTvQRORbRWwOYv3MH8re9ykw3Ip6lrPifY7s6hOaAKry/nkGPMt40m1TdiW98MTIpooE7W+WXu96ax2l2OJvxX8QR7l+LFlKnkIEEJd/ItF1G22UmOjkVwNASTwza/hlY+8DoVvEmwum/nMgH2TwQT3bTQzF9s9DOJkH4d8p4Mw4gEDjNx0EgUFA91ysCAeUMQQyIvuR8HXXa+VcvhOOO5mmBcVhxJ3qUOJTyDBsT0932Zb4mNtkxdigoVxu+iiwk0vwtvKwGVDYdyMP5EAQeEIP1t0wAAAhQAAAAMcnNhLXNoYTItNTEyAAACAAIskD6OCWhvsEzV6U+gq1c1XH3In7YWlNQjPc4LaeRiDeVzUq2Wx/Cimf/7Azq95fbbO0h7x/M63uzu7Ui5hFkIB0ok6MocdU46qayF9K7i5ealdMU39bG5YwWGQUMBZUWe3y6aRCk0KWIBiFICOMFWkTqiGhUISwecRXPbC2TBXgFnmfsGPUJLn+829iuKF5kN+llN0FInYKdVYRPuMKy1SQ0NFgr6OSbgelK58cYYC3I+/0TuG1gwSEOGIHs2akvMwQJ2Z8AmZHj857m44NA2pdHpq411GqD/IGMDsSSt3MrG4D/gNaU7R7n3/7y5Lx92PCnUh95Q978/YIlsHnzW2t8LpP8PvT7NhRp0RPnvyCdeWrUrmNOC04DIwnxVlV+BOa2KpcEBoH40IOMPHKyA6TQPV9B29vuUMBXvDZtn2ECQ0/GQAQGfukgaQqPP8pkezOlfMPup8itA3y2aS63WodfTMy0jvNFHU3DPocl4rtiayXAXx/nuAGKb52mBQF6+nx7sVF1SKuii3lclJqFK66vSYlKtVvRqxaLl37LlBNqkG/jHp8yf4HGjvcRzAMPo5qw+92cU1Xc4DHEIwoUiecSQYVhG0DaCVujYLnZ79/2cS+YU0XjwUWbQlJvYrqh+7FngV2WraWkF+Sd1bxRdyuB5Hrw4tmdoKw6KWTAT user@example.com

View File

@@ -0,0 +1 @@
ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIAYkJPGaYen7NK8MwZwWmNAyRaFNsc86AU9NObU2cM2uAAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAAAAAAAAAAAACAAAAB2VkMjU1MTkAAAAUAAAAEGhvc3QuZXhhbXBsZS5jb20AAAAAYkx3NwAAAAB8DuY3AAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAFMAAAALc3NoLWVkMjU1MTkAAABApVXBNiYPlPoa1BYH5G4NP9XtjTMZlm7HO5GdbLSvvAw5Vdob7Ka+23hB7isJKHYtzFGGSKXAqxp/Zi8REbCaAw== user@example.com

View File

@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACDNkZXMtY2JjAAAABmJjcnlwdAAAABgAAAAQGvzr6jxZjC
d+ftwreNselAAAABAAAAABAAAAMwAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/e
o04kH2XxtSmk9D7RQyf1xUqrYgAAAJg/4GdFw1boDlKQK5tYsHQUUIyHIYR91BH6LfVhG1
gj+b6Cj3dm1ESAUdg1qhKac4oZS6AYx6L3m7M4rHXYi2SncRqPoa+DODCxcvUqUNQOrT54
6PjbIx6P7Ewam4PtXENekmx0gOsBshNkQyzP8XA86DtorA+kW0VU1YUTmJrqwAzZrrg2Nf
Rueif44KC5+spnhieuaDf6Lg==
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDfKCxn9E
flgqvP7Uxtpmp5AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN
796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoEVNJyfF6Jecws0dSosXLdcRh4doAv07rra0K7
4BxIFyzyoMqFpEdb0jNgyNlMiiIzglU3W9Nd5JQgA06YMtv5bdfJKu9Z9/nxmfTOmPSMlN
hjsXJdmKJjJmJ9ZeMeh2wzOFRewbQ3mtyroK1dXPbiIH601aW00/LYc/HqxnoA0HRkXqwj
nKUyE9vGurvcGySgo1+n7/1u+b9NbqEmt9f+g=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jdHIAAAAGYmNyeXB0AAAAGAAAABDQaLR2sa
YpWEjw/HUGyEGmAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN
796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoN+GP4jh5KCPGL/0OcFfYZBkS1vtY0QsSd++FF
qMaww+pOEru2QYaEu6VYDeHNDsYRbvSgLRTP5pdyIzY2WSxmKMkRAYTj57LQmokRjLwmHV
mKQKOQ901sAocIq/40ZCwxVXBNu49seU8u6kEgifckpjjraZ/p908QlQFtArVZrwSYvCQT
q2bFsEkyp+3e5QvkidZWbL3/f41uxirt3L28Y=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAAFmFlczEyOC1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA
AAGAAAABCiZCxATdLothFeOkNhDdB0AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA
ILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoAni5wCWsx/J4HXd/6UeSH
+me0c0b5CtvJiHk37UXf+tH2f+byjhNqWSyJLgrBlR7waruc5XtsM6HPRo+HeA6VjcYqnP
r8SIipPyLAPtmNttsnc0KZRnzAri4xVIoLLRCJOmL6lzdDBCEKzrXmHZBd/MfLpZRCCoT6
rKuxoSbFrteXAQ6bLHSLid+I+Zm2RgrT+Q9oxLHgrKifLwS1pdE9ER2FYC79hshy83fAlG
i05w
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczE5Mi1jYmMAAAAGYmNyeXB0AAAAGAAAABCAILpRu7
oyyiJZTIy4qfJTAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN
796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoC6RbA0+5zoA0g4f/g04BxpoymbgU2wX83rrdW
E3C+IGJTdYrf/wTJ+ubtQ4JpfESA7Gr2i3mIv9NTtd54y5XzvLpe9S/NfBoFfGz2INvbD8
BnFovA0tPHUrmy6BiUatdpoFEeBwwScB3nw+65DhngCydM2WIsUe1juIq1qpsyZRy0UI0Q
hVgHTGTC4QadWtWtanI93tSyObzke4LClIoiw=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczE5Mi1jdHIAAAAGYmNyeXB0AAAAGAAAABDI/chiIV
ViHJOdrglsLV1pAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN
796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoNAcEUO0a1eQXgZ6S/m4XoPuo3o+F9S7P795C+
qggI+pDNisImcblRTLhpEBwT9JErq5+zj8qZ2rhCSN+UuvLa28AF2tSZmqAcRXcUNrvgCw
VcqKDR3SgHtifeCB5i32+PT9kLQc7jKQIgZNFv4zF0bmYtEZ9fboDNQZgwvu36Gegv+WNa
g2RecYJ3E7Nmte1H0qjk2aPYKtEfoo/CGl7Dw=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABC596ibeu
zBIwgxU2VhAfNPAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN
796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoBfqsNSTaDbJdvexzf9ykDWUG6XCqprKijfHka
YCIWuNs/8rjXwRWolpUIGTdThAlZVyrG7t7wjyr3m2jZQ8Xr4KesY7ODJ25Cz4dVBoqj9c
DdPMlIuimygqHZQgpNG7YVie669nyNuHJHFLhfAcNQCz03hhAB2NpEidzIqWZOgqch0qst
tbX5XgVeGiK6YZqYK53vAA44pFkYMGdP/iEBY=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBKH96ujW
umB6/WnTNPjTeaAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN
796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoFzvbvyFMhAiwBOXF0mhUUacPUCMZXivG2up2c
hEnAw1b6BLRPyWbY5cC2n9ggD4ivJ1zSts6sBgjyiXQAReyrP35myYvT/OIB/NpwZM/xIJ
N7MHSUzlkX4adBrga3f7GS4uv4ChOoxC4XsE5HsxtGsq1X8jzqLlZTmOcxkcEneYQexrUc
bQP0o+gL5aKK8cQgiIlXeDbRjqhc4+h4EF6lY=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAAFmFlczI1Ni1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA
AAGAAAABARvcEz72RkQRWxdpF+R8uvAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA
ILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoIJQm81qpEdHOG7cGK5d27
FAelmbS6xxp7YaqYnD+9agVk6KsbAM8SMDF6AEiVaxoVPX/+HRV1HwA5BRpWijXmC6meyV
604UAY1ubJKemubnSrNSa4slV/r6wLut1vqFD8ro6nobT+wCgUrwDsL7ZI/9i6nQYXFdDS
vKbSu+2Nwh3B78JQoZXyetXQy3fOZKqrvy/6BFRDsOTKckfRCiAaTcNzfq+DH3OG5x+brH
Yl4J
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAAHWNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc2guY29tAAAABm
JjcnlwdAAAABgAAAAQ9lHKPvsVkE0FwhalBB6omgAAABAAAAABAAAAMwAAAAtzc2gtZWQy
NTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJiRvYDd00XU/W
BkZ93ZW52HNwvM2m3z/MHuqD8q/tk16rKKtBNOc95wo4gyRzkdGYhKnF1RFCJYcdvlw6zo
kctfmmhQ6W54G6u9Eh9bIJtHt3l4FQgzriuIsBTUKZIlvvk6Fo5ItNPHM00r2ehuX81lcZ
QHMaims6Blw8Esl6G3NYCAa2NKyqlmM5LIfkga/Ymydvrbc7EQmN2hbii0c0aMUdYQclyk
F4o=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com

View File

@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAOAAAABBuYW1lQG
V4YW1wbGUuY29tAAAAIIiPJO4Xrf7QCR5n5IX7mETP7WByysHQY5DkAF9QFbRPAAAAgH7Q
MVF+0DFRAAAAEG5hbWVAZXhhbXBsZS5jb20AAAAgiI8k7het/tAJHmfkhfuYRM/tYHLKwd
BjkOQAX1AVtE8AAAAgmGyVO0te+zKF/yB8HKXuOaWQR7xIj7w7HvA278dXXHUAAAATY29t
bWVudEBleGFtcGxlLmNvbQECAwQF
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
name@example.com AAAAEG5hbWVAZXhhbXBsZS5jb20AAAAgiI8k7het/tAJHmfkhfuYRM/tYHLKwdBjkOQAX1AVtE8= comment@example.com

View File

@@ -0,0 +1,38 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEApo5HjJvJNyZDa39enm+aRuG3O+wejLd1TeLGpbbEVfLwEqclmvz5
QYHWnpXTmjSeTStIKlNysolDcx23XHPOe9nuyFAQyUv65WlgEYki+GqLNlWzV9JOemec2K
fZv26uZvf5pW/j0JDQYyIYpoKWDYqtk8AYmHgOrS2+/XD7RwNHF+QS5P2uaFKS7IkeJCP3
/kPfL1QymrCl11YeWC5C6G667gwenq9gPXznCFBdDuCQkS4fw3NetYBN30K2EzEHp26aWc
38a2X0PGMCz7yo56pvl0V/qW07Wibo9BIE0s1CvhGcaEsPAjcImaca48HnEzFUPMP7K0Jo
eAARrk6pNMD/B3CO4YPn6Qb+5Ino4eV/znocbfj7rvObvRlV29WtGr/74Sb1AgXLiErwgP
89cFSdMXS4W9f2Ykw3U88jW2UNDkIo8yvntUpZDYaft3hlWbt6TWb506acCF798IOpFdR6
HZFhoIdWsmOwbnOdmfKJA2KryWreQszo+TmkDa/5AAAFiD9lruM/Za7jAAAAB3NzaC1yc2
EAAAGBAKaOR4ybyTcmQ2t/Xp5vmkbhtzvsHoy3dU3ixqW2xFXy8BKnJZr8+UGB1p6V05o0
nk0rSCpTcrKJQ3Mdt1xzznvZ7shQEMlL+uVpYBGJIvhqizZVs1fSTnpnnNin2b9urmb3+a
Vv49CQ0GMiGKaClg2KrZPAGJh4Dq0tvv1w+0cDRxfkEuT9rmhSkuyJHiQj9/5D3y9UMpqw
pddWHlguQuhuuu4MHp6vYD185whQXQ7gkJEuH8NzXrWATd9CthMxB6dumlnN/Gtl9DxjAs
+8qOeqb5dFf6ltO1om6PQSBNLNQr4RnGhLDwI3CJmnGuPB5xMxVDzD+ytCaHgAEa5OqTTA
/wdwjuGD5+kG/uSJ6OHlf856HG34+67zm70ZVdvVrRq/++Em9QIFy4hK8ID/PXBUnTF0uF
vX9mJMN1PPI1tlDQ5CKPMr57VKWQ2Gn7d4ZVm7ek1m+dOmnAhe/fCDqRXUeh2RYaCHVrJj
sG5znZnyiQNiq8lq3kLM6Pk5pA2v+QAAAAMBAAEAAAGAa2MLEMaVCsDZ8WJzEDYmw5LewH
zyCYpz0J7ps4jOuBfl4DDy1yZKU4kyZpd1klRgyKKiad/Z8PD9kyhSxAJK3KHcCj1NRWx+
vRGfBk9kQ8T2Mzc4ZeRMAzHw9+PpSjtDqVIzHQ6yVRQ5t+ERAbLqqpqCZeQSN6QY2mHHZc
NF0Dh1yxqbcBd8Lvkmj+msjGLAj6kVKn/gDMrecqOs9vAE5bYXQkqAJ5ItvBdfIoYmKeRy
cZjKlAs7wkySaOOrX15ZZbg4fhRwZ5s+poCWX4FZPLFBMQ1MQVaeJbN2otxO2S+RSbdelw
6CJHMJRswg81H4EVsbv8uzj2vQbGIEcrdtZB01gCre8VIgq5sqV+NZGP4n4TgRnMpWqYzP
PA/Gg6GfJyGodm7N2cV2d2YmVvPT4FMl8/s3MmYj277GOz2YSDCy3Se+u2vS7VNF3/8Y3x
gGrevO2phFgElokwaBrD5SMTjFIWyxNZl+PhQ6eBasw9h0HqzsfhX1PaDwgQaRcI2dAAAA
wFRAWqZjrp4IADWnEAL0w1HX0ALDUgByXm3A/22QGjBLEDouoBZQeZbTGTWLW+pP60CY9T
BSjxK5jFDH3fyF/Er5JXuvmqcjXN9GdzSbd+UqQKXi9EEi0YzkCUGRTpkWnEi3CImNKYaW
VmB7fi62NUHgu9Vo5Pd0vsMTfQKlkcjHey4Yjdb3Lu9c/xknzeVzpMoNQ8K2xqlXIURRIu
HPaqXwW2XLnIYST595+inwXj8G87g+3KmUH1cWUOD7RoquTAAAAMEA0R564khkDTsgKTaR
iGVEzf4HeamqtWyPlia/HmZIv9mIvbCsfRGnPjQFYzbUrTkA/3GE7kBLhLrrEaKjAvmC2U
7vt1cDDsbXfZEV6u+Aq1dJoPW1kLKZ/96U+ZMN7bqyrzMwlbCKUEubMPERLc5R837QDQQz
Q9Qg0uL7iL1/iBt8iZDki5P9HShPzIwcB/vvwE0CklsvFZqan1Zwc+HJT9xuRy9IljvhbF
xUU4Vq0r95FuQsNudaUBiRDY2tA41zAAAAwQDL5Q5+zfXiyG52ypS+iwwFsJBB0rzd7rRn
LnEg6syDgOXWt3yFWDxQj47o1VfKvLbfroxyOF8PaTRevBWl3+yUnAdw0C15Rd01klYtpz
iGYuBTxUVNJpDeKmPMVV4aAQ4toK4wfRwR+FKpx1aOAvk9SbKo+Se3mUOykgytMhqiCEEJ
0TbQhcHQXDn0w2z4n9w8ZqdV5j9EbhYwKxNZlADwqDMhoua5FT3wLwPeMY6gkDkoKFPyAR
4JBdEVdmfK8eMAAAAQdXNlckBleGFtcGxlLmNvbQECAw==
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCmjkeMm8k3JkNrf16eb5pG4bc77B6Mt3VN4saltsRV8vASpyWa/PlBgdaeldOaNJ5NK0gqU3KyiUNzHbdcc8572e7IUBDJS/rlaWARiSL4aos2VbNX0k56Z5zYp9m/bq5m9/mlb+PQkNBjIhimgpYNiq2TwBiYeA6tLb79cPtHA0cX5BLk/a5oUpLsiR4kI/f+Q98vVDKasKXXVh5YLkLobrruDB6er2A9fOcIUF0O4JCRLh/Dc161gE3fQrYTMQenbppZzfxrZfQ8YwLPvKjnqm+XRX+pbTtaJuj0EgTSzUK+EZxoSw8CNwiZpxrjwecTMVQ8w/srQmh4ABGuTqk0wP8HcI7hg+fpBv7kiejh5X/Oehxt+Puu85u9GVXb1a0av/vhJvUCBcuISvCA/z1wVJ0xdLhb1/ZiTDdTzyNbZQ0OQijzK+e1SlkNhp+3eGVZu3pNZvnTppwIXv3wg6kV1HodkWGgh1ayY7Buc52Z8okDYqvJat5CzOj5OaQNr/k= user@example.com

View File

@@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAgEAtFkR7cbsXn0iYaSMRquImxhYMGJxEj5vAtyRTPPANSSS6Ka3p5Ja
3e1SflR9zr/20MGcC8kVOXUZn0f0lk7SD1rO7U6CVWsiigwfv6qF5jObov9AlNlOKwnUOj
3WgiXQu8hYKTy/FnsY1jdOvnkiCmM9QAF28fa0b9YmrLJSvylKrbKs1ZYmoCOo5exTztho
UWTHLKOi7GRoEsbmH/y6dA/xXAVPBpHjqNUsecRLfB/GyXBK7QnuAZW/CcXFuhFzt7EXm+
M/s3EdO4Lpj4BSE2eoQwPLEjbr6PwJVoNCCk3mUsBx1ZJ1nUKgydLnMxPN+3Ggcck2ZZQz
SBpAYwiCDhc7k0++h32HP+wk0xpNO7mjZFBVyje/cQ4hTl/CUNWWTGbxjk8Fo7k/QqoHU7
0ETkW0VsDmL9zB/K3vcpMNyKepaz4n2O7OoTmgCq8v55BjzLeNJtU3YlvfDEyKaKBO1vll
7vemsdpdjib8V/EEe5fixZSp5CBBCXfyLRdRttlJjo5FcDQEk8M2v4ZWPvA6FbxJsLpv5z
IB9k8EE9200MxfbPQziZB+HfKeDMOIBA4zcdBIFBQPdcrAgHlDEEMiL7kfB112vlXL4Tjj
uZpgXFYcSd6lDiU8gwbE9Pd9mW+JjbZMXYoKFcbvoosJNL8LbysBlQ2HcjD+RAEHhCD9bd
MAAAdI79BESO/QREgAAAAHc3NoLXJzYQAAAgEAtFkR7cbsXn0iYaSMRquImxhYMGJxEj5v
AtyRTPPANSSS6Ka3p5Ja3e1SflR9zr/20MGcC8kVOXUZn0f0lk7SD1rO7U6CVWsiigwfv6
qF5jObov9AlNlOKwnUOj3WgiXQu8hYKTy/FnsY1jdOvnkiCmM9QAF28fa0b9YmrLJSvylK
rbKs1ZYmoCOo5exTzthoUWTHLKOi7GRoEsbmH/y6dA/xXAVPBpHjqNUsecRLfB/GyXBK7Q
nuAZW/CcXFuhFzt7EXm+M/s3EdO4Lpj4BSE2eoQwPLEjbr6PwJVoNCCk3mUsBx1ZJ1nUKg
ydLnMxPN+3Ggcck2ZZQzSBpAYwiCDhc7k0++h32HP+wk0xpNO7mjZFBVyje/cQ4hTl/CUN
WWTGbxjk8Fo7k/QqoHU70ETkW0VsDmL9zB/K3vcpMNyKepaz4n2O7OoTmgCq8v55BjzLeN
JtU3YlvfDEyKaKBO1vll7vemsdpdjib8V/EEe5fixZSp5CBBCXfyLRdRttlJjo5FcDQEk8
M2v4ZWPvA6FbxJsLpv5zIB9k8EE9200MxfbPQziZB+HfKeDMOIBA4zcdBIFBQPdcrAgHlD
EEMiL7kfB112vlXL4TjjuZpgXFYcSd6lDiU8gwbE9Pd9mW+JjbZMXYoKFcbvoosJNL8Lby
sBlQ2HcjD+RAEHhCD9bdMAAAADAQABAAACAHDWOa13hHQp/tTwywN8V2ASfzrmnLA5d+Nm
dVKcP2oAlBoUFVw26btovPBllMFCwf4i5KtLCIiGh51su88/SZZpzoYTVOB0w4tzwnl9C4
HYUExPP+zheVLcN3ipMAkF9+9FjkNeyoAaTJPazt3FlFLDfJMLV4xUOtiuOExc1gDcqOi8
nf6Uj14qcYZJsrX8GGi0kRmQ2GLm/2agI2NoEJCFWRGmEKefp7z+g3E8K65hg1KNe5OLXu
qG8pv+rZOZT7lih871A+oVn6CYa+Fo+/FALbqgKPIggsGmz4DdZvhjfPPRjGd/1y6pfUhJ
OHZwsbPch/IpXmt3qg42voo3zIZPJ4bfqjxFIoNuRDPY3ZxGTBVaeKwZ5JsB9WlZADuqzV
GDwK7vH/DamYj+qh23qt/V4kPqUJXTRI9UEeGY/yniuv2hwmAH7/42loYXFhViWvVjTcmM
6B7QJOpVnYv+/G5xcqnRQJJz+U0CNbLna7QaUy22zp8KKNTrDbcKvO2VRi7sXiItOz5hwz
CpyXkTp95ihSTj2qJM6T0qz5RA4DxXBj48dQkzKt3zfVyEA3Wgud4daIIs3PiLRM1q6ov8
ZGzlL50F5+hnoy1GLjWhY8FbffAOnAqHA0W4bniClxtNedQlB63nwm5tsp9S++WEMPkVxV
QUX6qVCua25Ph78kphAAABAFgPNYCirVT35DkKmaBbN3cwrr5jHNQfZFJCTgN2PS1DrzJ/
kZqpa/dIowQf1vdrRxtLgCnrrAHfGGkrFhLF0EZkAIOrEjVGSVu/q3fV+aS46+/86ZdBfq
ViWwcL4c+MWyU+3YJr5gQu4fcaE7cujfb9y3+KlF71kppMeQgDvDH+/yT48UiSbqOqAsaQ
iJuu6x5ycpVkLxOVUGf+5ACyMIdiUv2dzx9WpDB9PXF88CNYM/3JOUeitO1F1D31EIfZHV
nrC/Cf5vRQNrI8lErdzil2uAVCXGhBEpvlsXxNzEHWLaoFPQah+/08IFQ6YwZq1pkzrmRT
jDBa5kXYFVem88kAAAEBAOK3qpViHsBlrNG57dRgkMcV4dIS8RrCT2HDAWpLQRolxjUAdl
TcGaFFUx+dSfeWlloo9nV1pam/hStTR0xDRc9gTUC2FOMdUPDKVkFPFStJ1rkth2fUcKWh
CvtuVGGJ1umXOa7/eggdlv1cFkbFq7uEgd+TbGWq1RpVNZbhb0mwn4gXXSyTj5LsrMxhMT
Uj/WeFMwB/Bcq1Hf0Wz680OQM706TYRcCONAl7b37NBhMILn0YMPk24px4ZcK4rNMIcN0g
Z5eI4LKqoihdNepzR8QIPi7pyS3LEe6hFCRcXyLXr+udUcvAyhcRYmH6yKjzwwVNofU60p
f4zhhGY+xOYX0AAAEBAMukNjMmN77P297CSdvBIeUwnJlk8gU8IhtYtgGEav18yLeI1r+b
cTRbG93ns2cgTgEO5gwhJjUkds6YiZtyA1618q6N2XVN1QA1TEGMu/dd/UvyApqaPI4Jfv
2zNOgiinOLHD+sQ7SCI2SlS0w0gEI2m1nPCGsl2yMib3Ht6uWOd/xvEEk2QcQlTCiZm+Vi
jNdOJZ1f5dOcmKnAuFQ71YyJuzTqGeGK9xTxRG4pPQmIHtf6X0mzdLyrl9r6Bn6OtjvJ3f
Jmi/PrsrtYXXsS/1keb/NIiRlrnlKTgJ8WjWgbt7CWgP7wk8iijvDSVWj85Kteh5/uIadS
WsCMr576LY8AAAAQdXNlckBleGFtcGxlLmNvbQECAw==
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgN1C5LY0DdJp9w6/4w9zr8W1MrjF3Wl5kt8s3N5G8ZkIAAAADAQABAAACAQC0WRHtxuxefSJhpIxGq4ibGFgwYnESPm8C3JFM88A1JJLoprenklrd7VJ+VH3Ov/bQwZwLyRU5dRmfR/SWTtIPWs7tToJVayKKDB+/qoXmM5ui/0CU2U4rCdQ6PdaCJdC7yFgpPL8WexjWN06+eSIKYz1AAXbx9rRv1iasslK/KUqtsqzVliagI6jl7FPO2GhRZMcso6LsZGgSxuYf/Lp0D/FcBU8GkeOo1Sx5xEt8H8bJcErtCe4Blb8JxcW6EXO3sReb4z+zcR07gumPgFITZ6hDA8sSNuvo/AlWg0IKTeZSwHHVknWdQqDJ0uczE837caBxyTZllDNIGkBjCIIOFzuTT76HfYc/7CTTGk07uaNkUFXKN79xDiFOX8JQ1ZZMZvGOTwWjuT9CqgdTvQRORbRWwOYv3MH8re9ykw3Ip6lrPifY7s6hOaAKry/nkGPMt40m1TdiW98MTIpooE7W+WXu96ax2l2OJvxX8QR7l+LFlKnkIEEJd/ItF1G22UmOjkVwNASTwza/hlY+8DoVvEmwum/nMgH2TwQT3bTQzF9s9DOJkH4d8p4Mw4gEDjNx0EgUFA91ysCAeUMQQyIvuR8HXXa+VcvhOOO5mmBcVhxJ3qUOJTyDBsT0932Zb4mNtkxdigoVxu+iiwk0vwtvKwGVDYdyMP5EAQeEIP1t0wAAAAAAAAAAAAAAAQAAAANyc2EAAAAAAAAAAGJHkAUAAAAA9NApBQAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEB5WnlZ0Gu6EX+BUDsyV0Ty/7iLsZ39LEe1v1nWKuqteIbgEt2MFwoenLpCyn83uKXqtc8HkeV0UJ0kpTZM1NQG user@example.com

View File

@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC0WRHtxuxefSJhpIxGq4ibGFgwYnESPm8C3JFM88A1JJLoprenklrd7VJ+VH3Ov/bQwZwLyRU5dRmfR/SWTtIPWs7tToJVayKKDB+/qoXmM5ui/0CU2U4rCdQ6PdaCJdC7yFgpPL8WexjWN06+eSIKYz1AAXbx9rRv1iasslK/KUqtsqzVliagI6jl7FPO2GhRZMcso6LsZGgSxuYf/Lp0D/FcBU8GkeOo1Sx5xEt8H8bJcErtCe4Blb8JxcW6EXO3sReb4z+zcR07gumPgFITZ6hDA8sSNuvo/AlWg0IKTeZSwHHVknWdQqDJ0uczE837caBxyTZllDNIGkBjCIIOFzuTT76HfYc/7CTTGk07uaNkUFXKN79xDiFOX8JQ1ZZMZvGOTwWjuT9CqgdTvQRORbRWwOYv3MH8re9ykw3Ip6lrPifY7s6hOaAKry/nkGPMt40m1TdiW98MTIpooE7W+WXu96ax2l2OJvxX8QR7l+LFlKnkIEEJd/ItF1G22UmOjkVwNASTwza/hlY+8DoVvEmwum/nMgH2TwQT3bTQzF9s9DOJkH4d8p4Mw4gEDjNx0EgUFA91ysCAeUMQQyIvuR8HXXa+VcvhOOO5mmBcVhxJ3qUOJTyDBsT0932Zb4mNtkxdigoVxu+iiwk0vwtvKwGVDYdyMP5EAQeEIP1t0w== user@example.com

View File

@@ -0,0 +1 @@
sk-ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAK3NrLWVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAguiTG3xznoyOCvoGoekia/u/JJZ51Hmy5fcHDBXA/atwAAAAIbmlzdHAyNTYAAABBBIELQJ2DgvaX1yQlKFokfWM2suuaCFI2qp0eJodHyg6O4ifxc3XpRKd1OS8dNYQtE/YjdXSrA+AOnMF5ns2Nkx4AAAAEc3NoOgAAAAAAAAAAAAAAAQAAABFzay1lY2RzYS1uaXN0cDI1NgAAAAAAAAAAYk3NggAAAAD01maCAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgsz6u836i33yqAQ3v3qNOJB9l8bUppPQ+0UMn9cVKq2IAAABTAAAAC3NzaC1lZDI1NTE5AAAAQPv0I2T2ypcmZjff59v0lTUPllGxc+Cn5mACERgsHM9sSxsBq7WLiuzV+31JYh9B3CiavhI4Tl6CYGijdX1M4wo= user@example.com

View File

@@ -0,0 +1 @@
sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBIELQJ2DgvaX1yQlKFokfWM2suuaCFI2qp0eJodHyg6O4ifxc3XpRKd1OS8dNYQtE/YjdXSrA+AOnMF5ns2Nkx4AAAAEc3NoOg== user@example.com

View File

@@ -0,0 +1 @@
sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBNdo6XfhTK080uz5UbGyOcNMo+R3nPXMBxurwH2M1bDtQYbDT6qBE7EdQGkcy/EJDXbzT0KlU9rROjcX+JsgtGAAAAAEc3NoOg== user@example.com

View File

@@ -0,0 +1 @@
sk-ssh-ed25519-cert-v01@openssh.com AAAAI3NrLXNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIG/VTdX1zj24l7+wPGYDN/QPXBDyBjGwUj7wTk1vgC9iAAAAICFo/k5LU8863u66YC9eUO2170QduohPURkQnbLa/dczAAAABHNzaDoAAAAAAAAAAAAAAAEAAAAKc2stZWQyNTUxOQAAAAAAAAAAYk3NxAAAAAD01mbEAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgsz6u836i33yqAQ3v3qNOJB9l8bUppPQ+0UMn9cVKq2IAAABTAAAAC3NzaC1lZDI1NTE5AAAAQFnv46uyvpzZFXBXGRkGEgp/HsMM4iYexEfU+rHJFi25s4RfVktxwJptE6QaUzm5TcZW9pyP8+DHkJp20QItuwg= user@example.com

View File

@@ -0,0 +1 @@
sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAICFo/k5LU8863u66YC9eUO2170QduohPURkQnbLa/dczAAAABHNzaDo= user@example.com

View File

@@ -0,0 +1 @@
sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAINSoElFleH+nN83FoLqqepJjN+y7Gs5lrn7qXjBqQZyuAAAABHNzaDo= user@example.com

View File

@@ -0,0 +1,34 @@
# Example authorized keys file
#
# - Comments in these files begin with `#`
# - They can also contain blank lines
# - Lines which are not blank each contain a single host-pattern's public key
# - Maximum line length is 8 kilobytes
#
# Known hosts consist of the following space-separated fields:
#
# marker, hostnames, keytype, base64-encoded key, comment
#
# - The marker field is optional, but if present begins with an `@`. Known markers are `@cert-authority`
# and `@revoked`.
# - The hostnames is a comma-separated list of patterns (with `*` and '?' as glob-style wildcards)
# against which hosts are matched. If it begins with a `!` it is a negation of the pattern. If the
# pattern starts with `[` and ends with `]`, it contains a hostname pattern and a port number separated
# by a `:`. If it begins with `|1|`, the hostname is hashed. In that case, there can only be one exact
# hostname and it can't also be negated (ie. `!|1|x|y` is not legal and you can't hash `*.example.org`).
# - The keytype is `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`,
# `ssh-ed25519`, `ssh-dss` or `ssh-rsa`
# - The comment field is not used for anything (but may be convenient for the user to identify
# the key).
# Single simple patterned hostname with no comment in the key
test.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti
# Multiple hostnames with various patterns and a comment
cvs.example.net,!test.example.???,[*.example.net]:999 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2SQJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcEc= example.com
# A revoked, hashed hostname
@revoked |1|JfKTdBh7rNbXkVAQCRp4OQoPfmI=|USECr3SWf1JUPsms5AqfD5QfxkM= ssh-dss AAAAB3NzaC1kc3MAAACBANw9iSUO2UYhFMssjUgW46URqv8bBrDgHeF8HLBOWBvKuXF2Rx2J/XyhgX48SOLMuv0hcPaejlyLarabnF9F2V4dkpPpZSJ+7luHmxEjNxwhsdtg8UteXAWkeCzrQ6MvRJZHcDBjYh56KGvslbFnJsGLXlI4PQCyl6awNImwYGilAAAAFQCJGBU3hZf+QtP9Jh/nbfNlhFu7hwAAAIBHObOQioQVRm3HsVb7mOy3FVKhcLoLO3qoG9gTkd4KeuehtFAC3+rckiX7xSCnE/5BBKdL7VP9WRXac2Nlr9Pwl3e7zPut96wrCHt/TZX6vkfXKkbpUIj5zSqfvyNrWKaYJkfzwAQwrXNS1Hol676Ud/DDEn2oatdEhkS3beWHXAAAAIBgQqaz/YYTRMshzMzYcZ4lqgvgmA55y6v0h39e8HH2A5dwNS6sPUw2jyna+le0dceNRJifFld1J+WYM0vmquSr11DDavgEidOSaXwfMvPPPJqLmbzdtT16N+Gij9U9STQTHPQcQ3xnNNHgQAStzZJbhLOVbDDDo5BO7LMUALDfSA==
# A certificate authority with a comment
@cert-authority *.example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC0WRHtxuxefSJhpIxGq4ibGFgwYnESPm8C3JFM88A1JJLoprenklrd7VJ+VH3Ov/bQwZwLyRU5dRmfR/SWTtIPWs7tToJVayKKDB+/qoXmM5ui/0CU2U4rCdQ6PdaCJdC7yFgpPL8WexjWN06+eSIKYz1AAXbx9rRv1iasslK/KUqtsqzVliagI6jl7FPO2GhRZMcso6LsZGgSxuYf/Lp0D/FcBU8GkeOo1Sx5xEt8H8bJcErtCe4Blb8JxcW6EXO3sReb4z+zcR07gumPgFITZ6hDA8sSNuvo/AlWg0IKTeZSwHHVknWdQqDJ0uczE837caBxyTZllDNIGkBjCIIOFzuTT76HfYc/7CTTGk07uaNkUFXKN79xDiFOX8JQ1ZZMZvGOTwWjuT9CqgdTvQRORbRWwOYv3MH8re9ykw3Ip6lrPifY7s6hOaAKry/nkGPMt40m1TdiW98MTIpooE7W+WXu96ax2l2OJvxX8QR7l+LFlKnkIEEJd/ItF1G22UmOjkVwNASTwza/hlY+8DoVvEmwum/nMgH2TwQT3bTQzF9s9DOJkH4d8p4Mw4gEDjNx0EgUFA91ysCAeUMQQyIvuR8HXXa+VcvhOOO5mmBcVhxJ3qUOJTyDBsT0932Zb4mNtkxdigoVxu+iiwk0vwtvKwGVDYdyMP5EAQeEIP1t0w== authority@example.com

View File

@@ -0,0 +1,13 @@
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAAbEAAAAHc3NoLWRzcwAAAIEA3D2JJQ7ZRiEUyyyNSBbjpRGq/xsGsO
Ad4XwcsE5YG8q5cXZHHYn9fKGBfjxI4sy6/SFw9p6OXItqtpucX0XZXh2Sk+llIn7uW4eb
ESM3HCGx22DxS15cBaR4LOtDoy9ElkdwMGNiHnooa+yVsWcmwYteUjg9ALKXprA0ibBgaK
UAAAAVAIkYFTeFl/5C0/0mH+dt82WEW7uHAAAAgEc5s5CKhBVGbcexVvuY7LcVUqFwugs7
eqgb2BOR3gp656G0UALf6tySJfvFIKcT/kEEp0vtU/1ZFdpzY2Wv0/CXd7vM+633rCsIe3
9Nlfq+R9cqRulQiPnNKp+/I2tYppgmR/PABDCtc1LUeiXrvpR38MMSfahq10SGRLdt5Ydc
AAAAgGBCprP9hhNEyyHMzNhxniWqC+CYDnnLq/SHf17wcfYDl3A1Lqw9TDaPKdr6V7R1x4
1EmJ8WV3Un5ZgzS+aq5KvXUMNq+ASJ05JpfB8y8888mouZvN21PXo34aKP1T1JNBMc9BxD
fGc00eBABK3NkluEs5VsMMOjkE7ssxQAsN9IAAAAB2V4YW1wbGUAAAAAAAAABnNoYTUxMg
AAADcAAAAHc3NoLWRzcwAAACgpmje+ZfkBQr6VEub3uY2yZNOwXSdFsEtmylVrgY2cEr39
gGnpa50C
-----END SSH SIGNATURE-----

View File

@@ -0,0 +1,7 @@
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAE
EEfB/YcwzlNFe+jZJAmOw2SIMPkqqKI2OsZW/dRSH6YxPlEfGJG06eWq+OFC0GrRWmakJX
8/BR2E6KDi+RuoBwRwAAAAdleGFtcGxlAAAAAAAAAAZzaGE1MTIAAABjAAAAE2VjZHNhLX
NoYTItbmlzdHAyNTYAAABIAAAAID884rM9MmiHWlfwoX+JY6rrivMILmCEganDbXs6ShAB
AAAAIEYXgcOegKAJkfP1P7kw2AnlEIY0hRwtUm7moeouFySh
-----END SSH SIGNATURE-----

View File

@@ -0,0 +1,8 @@
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAAIgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAG
EELm6C3FQH8QShERfHwFsZk8POs9sl+uaLoWlQKk/5OV2a02tUPoAU/xXXCOIfCfWFqm36
1XW3m5Q0GLhhmNm82bB//5OZsV1D00766y5Wt7M8/4gLJCs+C1ivlsdYQexBAAAAB2V4YW
1wbGUAAAAAAAAABnNoYTUxMgAAAIQAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAGkAAAAw
WRSBydn20NFbMvdQGNRgO+e/AFdDm9eaT/AqQk3rFKKyuyC71gaiGXke3BWByW57AAAAMQ
DshCxIODastNeRULgAtklWgLC/6i1JQeelDPMFVD/T4tvQKBy2tnpUf1+9FKpoB34=
-----END SSH SIGNATURE-----

View File

@@ -0,0 +1,6 @@
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgsz6u836i33yqAQ3v3qNOJB9l8b
UppPQ+0UMn9cVKq2IAAAAHZXhhbXBsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQy
NTUxOQAAAEBPEav+tMGNnox4MuzM7rlHyVBajCn8B0kAyiOWwPKprNsG3i6X+voz/WCSik
/FowYwqhgCABUJSvRX3AERVBUP
-----END SSH SIGNATURE-----

View File

@@ -0,0 +1,19 @@
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAKaOR4ybyTcmQ2t/Xp5vmk
bhtzvsHoy3dU3ixqW2xFXy8BKnJZr8+UGB1p6V05o0nk0rSCpTcrKJQ3Mdt1xzznvZ7shQ
EMlL+uVpYBGJIvhqizZVs1fSTnpnnNin2b9urmb3+aVv49CQ0GMiGKaClg2KrZPAGJh4Dq
0tvv1w+0cDRxfkEuT9rmhSkuyJHiQj9/5D3y9UMpqwpddWHlguQuhuuu4MHp6vYD185whQ
XQ7gkJEuH8NzXrWATd9CthMxB6dumlnN/Gtl9DxjAs+8qOeqb5dFf6ltO1om6PQSBNLNQr
4RnGhLDwI3CJmnGuPB5xMxVDzD+ytCaHgAEa5OqTTA/wdwjuGD5+kG/uSJ6OHlf856HG34
+67zm70ZVdvVrRq/++Em9QIFy4hK8ID/PXBUnTF0uFvX9mJMN1PPI1tlDQ5CKPMr57VKWQ
2Gn7d4ZVm7ek1m+dOmnAhe/fCDqRXUeh2RYaCHVrJjsG5znZnyiQNiq8lq3kLM6Pk5pA2v
+QAAAAdleGFtcGxlAAAAAAAAAAZzaGE1MTIAAAGUAAAADHJzYS1zaGEyLTUxMgAAAYBgAr
3zDww5X0fRn4fSzHi0okXxU8N4R0qJp2GJ/z9MlwIbxJ+NuWV9MbCRc27lBxklvos69kJ0
zD0yXfpBHB+ABPO+7GPOm/8cj1ldy7AvjfGYb+UOwppAwGueaRGoeYXdiM5d9obpDLpX0u
UOoPkvLoXT/RHC8H+imHvDDfWTAvTe2a4Dukf78VA0ZGOI0BDO7ZIkcNL9t380cggEEzgj
Dj5215DG1Bk9cSw2P2WozuOeaTBuJttY+8/yhO7B9LVVJIPkxDewmbhQs5ycWWuUSUzGZC
YyHPTVbEu0zJSStQBUELR7qneFfHPpFO43L05yKmHOf+wtpo5Cp2JXV3+lpOtjdsP1Odu6
/Gu7MX2qXgNEeTa9tFlybWxzTGKGMb0jrAHO4ivVlfYl+gjpd5URxf8pllnOaHQutPBTZX
UR1izJqucaJyQdyuI4Q2B7jl20zQlpwEqM6GiCbct7BJ3i38DgBXdcrz735+Tmuf+6LPMV
S9OHR3RCWZBFnFyt7dA=
-----END SSH SIGNATURE-----

View File

@@ -0,0 +1,7 @@
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAAEoAAAAac2stc3NoLWVkMjU1MTlAb3BlbnNzaC5jb20AAAAg1KgSUW
V4f6c3zcWguqp6kmM37LsazmWufupeMGpBnK4AAAAEc3NoOgAAAAdleGFtcGxlAAAAAAAA
AAZzaGE1MTIAAABnAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAQC9WcLb5NG
XRdCOHinQIS/MxdnAx7SQMYnyOt5q4+huTWh/Zk/UvWhP+wXl/ikNPlDpgliRq6o3VyKqS
LLo9lQYBAAAACQ==
-----END SSH SIGNATURE-----

Some files were not shown because too many files have changed in this diff Show More