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

View File

@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"b105317d0b4324835e43ccecd4b9fdba97bc253ce64b062844d1a92e7ca2ab1b","CHANGELOG.md":"162a9fad9a9a220c2e62f93e6e1e2458f138baf7761f101137979cd31818251e","Cargo.toml":"0680aa660e52066a5c4409d0d23212a15f555031e8ccaa9ea8aac5958a3ebe89","Cargo.toml.orig":"c6cdae28333e7222973dc29735ad460d45c46521be337cd4141c75f8d215885d","LICENSE-APACHE":"a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5","LICENSE-MIT":"233b95ccbf90dc67e32f3e8995c489f6312d9191ebd141a931c3b684f1e3be6d","README.md":"59481c2d31aabedd213f3572def3969a0de18a1f05a41fc547d074047470a6c9","src/encoding.rs":"c77d441ed057982a3847146d22f0154bd541fe66a157f0f33691fc36dc50ce93","src/errors.rs":"14137cdcd7e0137f806e6664f17f6d17e5b0e55b946898a1b2c02bfa086dd39b","src/ident.rs":"14fe7330bdad50d932efef361c5d0631ae94f173aa1c5741533f3107253e7365","src/lib.rs":"b63c09576f0c8c8094b6cbe8bb4c13702adf7dc01d4185a3b11cb37e6316dca8","src/output.rs":"dedbd1eea5a8c6a1ffb6b6d68507ef89fa5f059b55c2b89da3385cbbf0f704d5","src/params.rs":"c76838597ded5162c1860fac341850ddfe6df5156d45a1514d8bebeee34d15be","src/salt.rs":"c475e04c49716ec7278abf7147980c73ed53081f32f4b118474d10c1e6a89cb8","src/traits.rs":"7ee9e9d21e416590ea529121b0de6960e9f1f2dd1b0a309a566efebf631f035f","src/value.rs":"6541cad2260442117297ab463b88773b4caee60ebd42ffb4ceff7f75d6606b62","tests/encoding.rs":"616fa947a4ae0af2c26d0c98c63dc12a5f677c863120c44f02e120e9416b804d","tests/hashing.rs":"e2bfd8effde3586ebcc0b566f2b8840d94adc644837ad5eb6abf98cb526f59f8","tests/password_hash.rs":"97e58fa5c58297b5ed6cda9edaa282c87d482d092c76c92fe10b6905b08da8e5","tests/test_vectors.rs":"cd10b9b085947b06bf5c7894fe7ea0290b963bdde7daea59052b3efaefa0edd7"},"package":"346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"}

View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "d80228437165f30bb20a2136d39e4c2de9cae5a5"
},
"path_in_vcs": "password-hash"
}

167
vendor/password-hash/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,167 @@
# 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.5.0 (2023-03-04)
### Added
- `Error::OutputSize` ([#1026])
- `std::error::Error::source` for `Error` ([#1264])
- `getrandom` feature ([#1267])
### Changed
- Use `Salt` type with `PasswordHasher` ([#1187])
- Rename `Salt::new` => `Salt::from_b64` ([#1266])
- Rename `Salt::b64_decode` => `Salt::decode_b64` ([#1266])
- Rename `SaltString::new` => `SaltString::from_b64` ([#1266])
- Rename `SaltString::b64_decode` => `SaltString::decode_b64` ([#1266])
- Rename `SaltString::b64_encode` => `SaltString::encode_b64` ([#1266])
### Fixed
- Allow `Salt` to be exactly the same amount as `MAX_LENGTH` value ([#1246])
[#1026]: https://github.com/RustCrypto/traits/pull/1026
[#1187]: https://github.com/RustCrypto/traits/pull/1187
[#1246]: https://github.com/RustCrypto/traits/pull/1246
[#1264]: https://github.com/RustCrypto/traits/pull/1264
[#1266]: https://github.com/RustCrypto/traits/pull/1266
[#1267]: https://github.com/RustCrypto/traits/pull/1267
## 0.4.2 (2022-06-27)
### Fixed
- docs.rs metadata ([#1031])
[#1031]: https://github.com/RustCrypto/traits/pull/1031
## 0.4.1 (2022-04-22)
### Added
- `authentication` category to Cargo.toml ([#976])
[#976]: https://github.com/RustCrypto/traits/pull/976
## 0.4.0 (2022-03-09)
### Changed
- Leverage `const_panic`; MSRV 1.57 ([#896])
- Rust 2021 edition upgrade ([#897])
- Make `Ident::new` fallible; add `Ident::new_unwrap` ([#896], [#960])
### Fixed
- Better `Debug`/`Display` impls for `SaltString` ([#804])
### Removed
- `base64ct` version restrictions ([#914])
[#804]: https://github.com/RustCrypto/traits/pull/804
[#896]: https://github.com/RustCrypto/traits/pull/896
[#897]: https://github.com/RustCrypto/traits/pull/897
[#897]: https://github.com/RustCrypto/traits/pull/897
[#914]: https://github.com/RustCrypto/traits/pull/914
[#960]: https://github.com/RustCrypto/traits/pull/960
## 0.3.2 (2021-09-15)
### Fixed
- Remove unused lifetimes ([#760])
[#760]: https://github.com/RustCrypto/traits/pull/760
## 0.3.1 (2021-09-14) [YANKED]
### Added
- `PasswordHashString` ([#758])
### Fixed
- Handling of empty salts in `fmt::Display` impl for PasswordHash ([#748])
- MSRV regression from `base64ct` ([#757])
[#748]: https://github.com/RustCrypto/traits/pull/748
[#757]: https://github.com/RustCrypto/traits/pull/757
[#758]: https://github.com/RustCrypto/traits/pull/758
## 0.3.0 (2021-08-27) [YANKED]
### Added
- More details to `ParamValueInvalid` ([#713])
- `SaltInvalid` error ([#713])
- `version` param to `PasswordHasher` ([#719])
- `ParamsString::add_b64_bytes` method ([#722])
### Changed
- Rename `PasswordHash::hash_password_simple` => `PasswordHash::hash_password` ([#720])
- Rename `PasswordHash::hash_password` => `PasswordHash::hash_password_customized` ([#720])
- Rename `Error::B64` => `Error::B64Encoding` ([#721])
[#713]: https://github.com/RustCrypto/traits/pull/713
[#719]: https://github.com/RustCrypto/traits/pull/719
[#720]: https://github.com/RustCrypto/traits/pull/720
[#721]: https://github.com/RustCrypto/traits/pull/721
[#722]: https://github.com/RustCrypto/traits/pull/722
## 0.2.3 (2021-08-23)
### Changed
- Make max lengths of `Value` and `Salt` both 64 ([#707])
[#707]: https://github.com/RustCrypto/traits/pull/707
## 0.2.2 (2021-07-20)
### Changed
- Pin `subtle` dependency to v2.4 ([#689])
### Added
- Re-export `rand_core` ([#683])
[#683]: https://github.com/RustCrypto/traits/pull/683
[#689]: https://github.com/RustCrypto/traits/pull/689
## 0.2.1 (2021-05-05)
### Changed
- Use `subtle` crate for comparing hash `Output` ([#631])
[#631]: https://github.com/RustCrypto/traits/pull/631
## 0.2.0 (2021-04-29)
### Changed
- Allow specifying output length and version with params ([#615])
- Allow passing `&str`, `&Salt`, or `&SaltString` as salt ([#615])
- Simplify error handling ([#615])
[#615]: https://github.com/RustCrypto/traits/pull/615
## 0.1.4 (2021-04-19)
### Added
- Length constants ([#600])
### Changed
- Deprecate functions for obtaining length constants ([#600])
[#600]: https://github.com/RustCrypto/traits/pull/600
## 0.1.3 (2021-04-17)
### Changed
- Update docs for PHC string <version> field ([#593])
### Fixed
- Broken `b64` links in rustdoc ([#594])
[#593]: https://github.com/RustCrypto/traits/pull/593
[#594]: https://github.com/RustCrypto/traits/pull/594
## 0.1.2 (2021-03-17)
### Changed
- Bump `base64ct` dependency to v1.0 ([#579])
[#579]: https://github.com/RustCrypto/traits/pull/579
## 0.1.1 (2021-02-01)
### Added
- `Encoding` enum with bcrypt and `crypt(3)` Base64 support ([#515])
- Support for using `PasswordHash` with an alternate `Encoding` ([#518])
### Changed
- Bump `base64ct` dependency to v0.2 ([#519])
[#515]: https://github.com/RustCrypto/traits/pull/515
[#518]: https://github.com/RustCrypto/traits/pull/518
[#519]: https://github.com/RustCrypto/traits/pull/519
## 0.1.0 (2021-01-28)
- Initial release

67
vendor/password-hash/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,67 @@
# 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.60"
name = "password-hash"
version = "0.5.0"
authors = ["RustCrypto Developers"]
description = """
Traits which describe the functionality of password hashing algorithms,
as well as a `no_std`-friendly implementation of the PHC string format
(a well-defined subset of the Modular Crypt Format a.k.a. MCF)
"""
documentation = "https://docs.rs/password-hash"
readme = "README.md"
keywords = [
"crypt",
"mcf",
"password",
"pbkdf",
"phc",
]
categories = [
"authentication",
"cryptography",
"no-std",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/RustCrypto/traits/tree/master/password-hash"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[dependencies.base64ct]
version = "1"
[dependencies.rand_core]
version = "0.6.4"
optional = true
default-features = false
[dependencies.subtle]
version = "2"
default-features = false
[features]
alloc = ["base64ct/alloc"]
default = ["rand_core"]
getrandom = ["rand_core/getrandom"]
std = [
"alloc",
"base64ct/std",
"rand_core/std",
]

201
vendor/password-hash/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/password-hash/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2020-2023 RustCrypto 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.

78
vendor/password-hash/README.md vendored Normal file
View File

@@ -0,0 +1,78 @@
# RustCrypto: Password Hashing Traits
[![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]
Traits which describe the functionality of [password hashing algorithms].
[Documentation][docs-link]
## About
Provides a `no_std`-friendly implementation of the
[Password Hashing Competition (PHC) string format specification][PHC]
(a well-defined subset of the [Modular Crypt Format a.k.a. MCF][MCF]) which
works in conjunction with the traits this crate defines.
## Supported Crates
See [RustCrypto/password-hashes] for algorithm implementations which use
this crate for interoperability:
- [`argon2`] - Argon2 memory hard key derivation function
- [`pbkdf2`] - Password-Based Key Derivation Function v2
- [`scrypt`] - scrypt key derivation function
## Minimum Supported Rust Version
Rust **1.60** or higher.
Minimum supported Rust version may be changed in the future, but it will be
accompanied by a minor version bump.
## SemVer Policy
- All on-by-default features of this library are covered by SemVer
- MSRV is considered exempt from SemVer as noted above
## License
Licensed under either of:
- [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
- [MIT license](https://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/password-hash
[crate-link]: https://crates.io/crates/password-hash
[docs-image]: https://docs.rs/password-hash/badge.svg
[docs-link]: https://docs.rs/password-hash/
[build-image]: https://github.com/RustCrypto/traits/workflows/password-hash/badge.svg?branch=master&event=push
[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow:password-hash
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes
[//]: # (general links)
[password hashing algorithms]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Password_verification
[PHC]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
[MCF]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html
[RustCrypto/password-hashes]: https://github.com/RustCrypto/password-hashes
[`argon2`]: https://docs.rs/argon2
[`pbkdf2`]: https://docs.rs/pbkdf2
[`scrypt`]: https://docs.rs/scrypt

73
vendor/password-hash/src/encoding.rs vendored Normal file
View File

@@ -0,0 +1,73 @@
//! Base64 encoding variants.
use base64ct::{
Base64Bcrypt, Base64Crypt, Base64Unpadded as B64, Encoding as _, Error as B64Error,
};
/// Base64 encoding variants.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Encoding {
/// "B64" encoding: standard Base64 without padding.
///
/// ```text
/// [A-Z] [a-z] [0-9] + /
/// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f
/// ```
/// <https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#b64>
B64,
/// bcrypt encoding.
///
/// ```text
/// ./ [A-Z] [a-z] [0-9]
/// 0x2e-0x2f, 0x41-0x5a, 0x61-0x7a, 0x30-0x39
/// ```
Bcrypt,
/// `crypt(3)` encoding.
///
/// ```text
/// [.-9] [A-Z] [a-z]
/// 0x2e-0x39, 0x41-0x5a, 0x61-0x7a
/// ```
Crypt,
}
impl Default for Encoding {
fn default() -> Self {
Self::B64
}
}
impl Encoding {
/// Decode a Base64 string into the provided destination buffer.
pub fn decode(self, src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], B64Error> {
match self {
Self::B64 => B64::decode(src, dst),
Self::Bcrypt => Base64Bcrypt::decode(src, dst),
Self::Crypt => Base64Crypt::decode(src, dst),
}
}
/// Encode the input byte slice as Base64.
///
/// Writes the result into the provided destination slice, returning an
/// ASCII-encoded Base64 string value.
pub fn encode<'a>(self, src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, B64Error> {
match self {
Self::B64 => B64::encode(src, dst),
Self::Bcrypt => Base64Bcrypt::encode(src, dst),
Self::Crypt => Base64Crypt::encode(src, dst),
}
.map_err(Into::into)
}
/// Get the length of Base64 produced by encoding the given bytes.
pub fn encoded_len(self, bytes: &[u8]) -> usize {
match self {
Self::B64 => B64::encoded_len(bytes),
Self::Bcrypt => Base64Bcrypt::encoded_len(bytes),
Self::Crypt => Base64Crypt::encoded_len(bytes),
}
}
}

171
vendor/password-hash/src/errors.rs vendored Normal file
View File

@@ -0,0 +1,171 @@
//! Error types.
pub use base64ct::Error as B64Error;
use core::cmp::Ordering;
use core::fmt;
/// Result type.
pub type Result<T> = core::result::Result<T, Error>;
/// Password hashing errors.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// Unsupported algorithm.
Algorithm,
/// "B64" encoding error.
B64Encoding(B64Error),
/// Cryptographic error.
Crypto,
/// Output size unexpected.
OutputSize {
/// Indicates why the output size is unexpected.
///
/// - [`Ordering::Less`]: Size is too small.
/// - [`Ordering::Equal`]: Size is not exactly as `expected`.
/// - [`Ordering::Greater`]: Size is too long.
provided: Ordering,
/// Expected output size in relation to `provided`.
///
/// - [`Ordering::Less`]: Minimum size.
/// - [`Ordering::Equal`]: Expecrted size.
/// - [`Ordering::Greater`]: Maximum size.
expected: usize,
},
/// Duplicate parameter name encountered.
ParamNameDuplicated,
/// Invalid parameter name.
ParamNameInvalid,
/// Invalid parameter value.
ParamValueInvalid(InvalidValue),
/// Maximum number of parameters exceeded.
ParamsMaxExceeded,
/// Invalid password.
Password,
/// Password hash string invalid.
PhcStringField,
/// Password hash string contains trailing data.
PhcStringTrailingData,
/// Salt invalid.
SaltInvalid(InvalidValue),
/// Invalid algorithm version.
Version,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> {
match self {
Self::Algorithm => write!(f, "unsupported algorithm"),
Self::B64Encoding(err) => write!(f, "{}", err),
Self::Crypto => write!(f, "cryptographic error"),
Self::OutputSize { provided, expected } => match provided {
Ordering::Less => write!(
f,
"output size too short, expected at least {} bytes",
expected
),
Ordering::Equal => write!(f, "output size unexpected, expected {} bytes", expected),
Ordering::Greater => write!(
f,
"output size too long, expected at most {} bytes",
expected
),
},
Self::ParamNameDuplicated => f.write_str("duplicate parameter"),
Self::ParamNameInvalid => f.write_str("invalid parameter name"),
Self::ParamValueInvalid(val_err) => write!(f, "invalid parameter value: {}", val_err),
Self::ParamsMaxExceeded => f.write_str("maximum number of parameters reached"),
Self::Password => write!(f, "invalid password"),
Self::PhcStringField => write!(f, "password hash string missing field"),
Self::PhcStringTrailingData => {
write!(f, "password hash string contains trailing characters")
}
Self::SaltInvalid(val_err) => write!(f, "salt invalid: {}", val_err),
Self::Version => write!(f, "invalid algorithm version"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::B64Encoding(err) => Some(err),
Self::ParamValueInvalid(err) => Some(err),
Self::SaltInvalid(err) => Some(err),
_ => None,
}
}
}
impl From<B64Error> for Error {
fn from(err: B64Error) -> Error {
Error::B64Encoding(err)
}
}
impl From<base64ct::InvalidLengthError> for Error {
fn from(_: base64ct::InvalidLengthError) -> Error {
Error::B64Encoding(B64Error::InvalidLength)
}
}
/// Parse errors relating to invalid parameter values or salts.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum InvalidValue {
/// Character is not in the allowed set.
InvalidChar(char),
/// Format is invalid.
InvalidFormat,
/// Value is malformed.
Malformed,
/// Value exceeds the maximum allowed length.
TooLong,
/// Value does not satisfy the minimum length.
TooShort,
}
impl InvalidValue {
/// Create an [`Error::ParamValueInvalid`] which warps this error.
pub fn param_error(self) -> Error {
Error::ParamValueInvalid(self)
}
/// Create an [`Error::SaltInvalid`] which wraps this error.
pub fn salt_error(self) -> Error {
Error::SaltInvalid(self)
}
}
impl fmt::Display for InvalidValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> {
match self {
Self::InvalidChar(c) => write!(f, "contains invalid character: '{}'", c),
Self::InvalidFormat => f.write_str("value format is invalid"),
Self::Malformed => f.write_str("value malformed"),
Self::TooLong => f.write_str("value to long"),
Self::TooShort => f.write_str("value to short"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidValue {}

171
vendor/password-hash/src/ident.rs vendored Normal file
View File

@@ -0,0 +1,171 @@
//! Algorithm or parameter identifier.
//!
//! Implements the following parts of the [PHC string format specification][1]:
//!
//! > The function symbolic name is a sequence of characters in: `[a-z0-9-]`
//! > (lowercase letters, digits, and the minus sign). No other character is
//! > allowed. Each function defines its own identifier (or identifiers in case
//! > of a function family); identifiers should be explicit (human readable,
//! > not a single digit), with a length of about 5 to 10 characters. An
//! > identifier name MUST NOT exceed 32 characters in length.
//! >
//! > Each parameter name shall be a sequence of characters in: `[a-z0-9-]`
//! > (lowercase letters, digits, and the minus sign). No other character is
//! > allowed. Parameter names SHOULD be readable for a human user. A
//! > parameter name MUST NOT exceed 32 characters in length.
//!
//! [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
use crate::{Error, Result};
use core::{fmt, ops::Deref, str};
/// Algorithm or parameter identifier.
///
/// This type encompasses both the "function symbolic name" and "parameter name"
/// use cases as described in the [PHC string format specification][1].
///
/// # Constraints
/// - ASCII-encoded string consisting of the characters `[a-z0-9-]`
/// (lowercase letters, digits, and the minus sign)
/// - Minimum length: 1 ASCII character (i.e. 1-byte)
/// - Maximum length: 32 ASCII characters (i.e. 32-bytes)
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Ident<'a>(&'a str);
impl<'a> Ident<'a> {
/// Maximum length of an [`Ident`] - 32 ASCII characters (i.e. 32-bytes).
///
/// This value corresponds to the maximum size of a function symbolic names
/// and parameter names according to the PHC string format.
/// Maximum length of an [`Ident`] - 32 ASCII characters (i.e. 32-bytes).
///
/// This value corresponds to the maximum size of a function symbolic names
/// and parameter names according to the PHC string format.
const MAX_LENGTH: usize = 32;
/// Parse an [`Ident`] from a string.
///
/// String must conform to the constraints given in the type-level
/// documentation.
pub const fn new(s: &'a str) -> Result<Self> {
let input = s.as_bytes();
match input.len() {
1..=Self::MAX_LENGTH => {
let mut i = 0;
while i < input.len() {
if !matches!(input[i], b'a'..=b'z' | b'0'..=b'9' | b'-') {
return Err(Error::ParamNameInvalid);
}
i += 1;
}
Ok(Self(s))
}
_ => Err(Error::ParamNameInvalid),
}
}
/// Parse an [`Ident`] from a string, panicking on parse errors.
///
/// This function exists as a workaround for `unwrap` not yet being
/// stable in `const fn` contexts, and is intended to allow the result to
/// be bound to a constant value.
pub const fn new_unwrap(s: &'a str) -> Self {
assert!(!s.is_empty(), "PHC ident string can't be empty");
assert!(s.len() <= Self::MAX_LENGTH, "PHC ident string too long");
match Self::new(s) {
Ok(ident) => ident,
Err(_) => panic!("invalid PHC string format identifier"),
}
}
/// Borrow this ident as a `str`
pub fn as_str(&self) -> &'a str {
self.0
}
}
impl<'a> AsRef<str> for Ident<'a> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> Deref for Ident<'a> {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
// Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on
// the `str` the value is being parsed from.
impl<'a> TryFrom<&'a str> for Ident<'a> {
type Error = Error;
fn try_from(s: &'a str) -> Result<Self> {
Self::new(s)
}
}
impl<'a> fmt::Display for Ident<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self)
}
}
impl<'a> fmt::Debug for Ident<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Ident").field(&self.as_ref()).finish()
}
}
#[cfg(test)]
mod tests {
use super::{Error, Ident};
// Invalid ident examples
const INVALID_EMPTY: &str = "";
const INVALID_CHAR: &str = "argon2;d";
const INVALID_TOO_LONG: &str = "012345678911234567892123456789312";
const INVALID_CHAR_AND_TOO_LONG: &str = "0!2345678911234567892123456789312";
#[test]
fn parse_valid() {
let valid_examples = ["6", "x", "argon2d", "01234567891123456789212345678931"];
for &example in &valid_examples {
assert_eq!(example, &*Ident::new(example).unwrap());
}
}
#[test]
fn reject_empty() {
assert_eq!(Ident::new(INVALID_EMPTY), Err(Error::ParamNameInvalid));
}
#[test]
fn reject_invalid() {
assert_eq!(Ident::new(INVALID_CHAR), Err(Error::ParamNameInvalid));
}
#[test]
fn reject_too_long() {
assert_eq!(Ident::new(INVALID_TOO_LONG), Err(Error::ParamNameInvalid));
}
#[test]
fn reject_invalid_char_and_too_long() {
assert_eq!(
Ident::new(INVALID_CHAR_AND_TOO_LONG),
Err(Error::ParamNameInvalid)
);
}
}

389
vendor/password-hash/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,389 @@
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, unused_lifetimes)]
//!
//! # Usage
//!
//! This crate represents password hashes using the [`PasswordHash`] type, which
//! represents a parsed "PHC string" with the following format:
//!
//! ```text
//! $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
//! ```
//!
//! For more information, please see the documentation for [`PasswordHash`].
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "rand_core")]
pub use rand_core;
pub mod errors;
mod encoding;
mod ident;
mod output;
mod params;
mod salt;
mod traits;
mod value;
pub use crate::{
encoding::Encoding,
errors::{Error, Result},
ident::Ident,
output::Output,
params::ParamsString,
salt::{Salt, SaltString},
traits::{McfHasher, PasswordHasher, PasswordVerifier},
value::{Decimal, Value},
};
use core::fmt::{self, Debug};
#[cfg(feature = "alloc")]
use alloc::{
str::FromStr,
string::{String, ToString},
};
/// Separator character used in password hashes (e.g. `$6$...`).
const PASSWORD_HASH_SEPARATOR: char = '$';
/// Password hash.
///
/// This type corresponds to the parsed representation of a PHC string as
/// described in the [PHC string format specification][1].
///
/// PHC strings have the following format:
///
/// ```text
/// $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
/// ```
///
/// where:
///
/// - `<id>` is the symbolic name for the function
/// - `<version>` is the algorithm version
/// - `<param>` is a parameter name
/// - `<value>` is a parameter value
/// - `<salt>` is an encoding of the salt
/// - `<hash>` is an encoding of the hash output
///
/// The string is then the concatenation, in that order, of:
///
/// - a `$` sign;
/// - the function symbolic name;
/// - optionally, a `$` sign followed by the algorithm version with a `v=version` format;
/// - optionally, a `$` sign followed by one or several parameters, each with a `name=value` format;
/// the parameters are separated by commas;
/// - optionally, a `$` sign followed by the (encoded) salt value;
/// - optionally, a `$` sign followed by the (encoded) hash output (the hash output may be present
/// only if the salt is present).
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PasswordHash<'a> {
/// Password hashing algorithm identifier.
///
/// This corresponds to the `<id>` field in a PHC string, a.k.a. the
/// symbolic name for the function.
pub algorithm: Ident<'a>,
/// Optional version field.
///
/// This corresponds to the `<version>` field in a PHC string.
pub version: Option<Decimal>,
/// Algorithm-specific parameters.
///
/// This corresponds to the set of `$<param>=<value>(,<param>=<value>)*`
/// name/value pairs in a PHC string.
pub params: ParamsString,
/// [`Salt`] string for personalizing a password hash output.
///
/// This corresponds to the `<salt>` value in a PHC string.
pub salt: Option<Salt<'a>>,
/// Password hashing function [`Output`], a.k.a. hash/digest.
///
/// This corresponds to the `<hash>` output in a PHC string.
pub hash: Option<Output>,
}
impl<'a> PasswordHash<'a> {
/// Parse a password hash from a string in the PHC string format.
pub fn new(s: &'a str) -> Result<Self> {
Self::parse(s, Encoding::default())
}
/// Parse a password hash from the given [`Encoding`].
pub fn parse(s: &'a str, encoding: Encoding) -> Result<Self> {
if s.is_empty() {
return Err(Error::PhcStringField);
}
let mut fields = s.split(PASSWORD_HASH_SEPARATOR);
let beginning = fields.next().expect("no first field");
if beginning.chars().next().is_some() {
return Err(Error::PhcStringField);
}
let algorithm = fields
.next()
.ok_or(Error::PhcStringField)
.and_then(Ident::try_from)?;
let mut version = None;
let mut params = ParamsString::new();
let mut salt = None;
let mut hash = None;
let mut next_field = fields.next();
if let Some(field) = next_field {
// v=<version>
if field.starts_with("v=") && !field.contains(params::PARAMS_DELIMITER) {
version = Some(Value::new(&field[2..]).and_then(|value| value.decimal())?);
next_field = None;
}
}
if next_field.is_none() {
next_field = fields.next();
}
if let Some(field) = next_field {
// <param>=<value>
if field.contains(params::PAIR_DELIMITER) {
params = field.parse()?;
next_field = None;
}
}
if next_field.is_none() {
next_field = fields.next();
}
if let Some(s) = next_field {
salt = Some(s.try_into()?);
}
if let Some(field) = fields.next() {
hash = Some(Output::decode(field, encoding)?);
}
if fields.next().is_some() {
return Err(Error::PhcStringTrailingData);
}
Ok(Self {
algorithm,
version,
params,
salt,
hash,
})
}
/// Generate a password hash using the supplied algorithm.
pub fn generate(
phf: impl PasswordHasher,
password: impl AsRef<[u8]>,
salt: impl Into<Salt<'a>>,
) -> Result<Self> {
phf.hash_password(password.as_ref(), salt)
}
/// Verify this password hash using the specified set of supported
/// [`PasswordHasher`] trait objects.
pub fn verify_password(
&self,
phfs: &[&dyn PasswordVerifier],
password: impl AsRef<[u8]>,
) -> Result<()> {
for &phf in phfs {
if phf.verify_password(password.as_ref(), self).is_ok() {
return Ok(());
}
}
Err(Error::Password)
}
/// Get the [`Encoding`] that this [`PasswordHash`] is serialized with.
pub fn encoding(&self) -> Encoding {
self.hash.map(|h| h.encoding()).unwrap_or_default()
}
/// Serialize this [`PasswordHash`] as a [`PasswordHashString`].
#[cfg(feature = "alloc")]
pub fn serialize(&self) -> PasswordHashString {
self.into()
}
}
// Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on
// the `str` the value is being parsed from.
impl<'a> TryFrom<&'a str> for PasswordHash<'a> {
type Error = Error;
fn try_from(s: &'a str) -> Result<Self> {
Self::new(s)
}
}
impl<'a> fmt::Display for PasswordHash<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.algorithm)?;
if let Some(version) = self.version {
write!(f, "{}v={}", PASSWORD_HASH_SEPARATOR, version)?;
}
if !self.params.is_empty() {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.params)?;
}
if let Some(salt) = &self.salt {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, salt)?;
if let Some(hash) = &self.hash {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, hash)?;
}
}
Ok(())
}
}
/// Serialized [`PasswordHash`].
///
/// This type contains a serialized password hash string which is ensured to
/// parse successfully.
// TODO(tarcieri): cached parsed representations? or at least structural data
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PasswordHashString {
/// String value
string: String,
/// String encoding
encoding: Encoding,
}
#[cfg(feature = "alloc")]
#[allow(clippy::len_without_is_empty)]
impl PasswordHashString {
/// Parse a password hash from a string in the PHC string format.
pub fn new(s: &str) -> Result<Self> {
Self::parse(s, Encoding::default())
}
/// Parse a password hash from the given [`Encoding`].
pub fn parse(s: &str, encoding: Encoding) -> Result<Self> {
Ok(PasswordHash::parse(s, encoding)?.into())
}
/// Parse this owned string as a [`PasswordHash`].
pub fn password_hash(&self) -> PasswordHash<'_> {
PasswordHash::parse(&self.string, self.encoding).expect("malformed password hash")
}
/// Get the [`Encoding`] that this [`PasswordHashString`] is serialized with.
pub fn encoding(&self) -> Encoding {
self.encoding
}
/// Borrow this value as a `str`.
pub fn as_str(&self) -> &str {
self.string.as_str()
}
/// Borrow this value as bytes.
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
/// Get the length of this value in ASCII characters.
pub fn len(&self) -> usize {
self.as_str().len()
}
/// Password hashing algorithm identifier.
pub fn algorithm(&self) -> Ident<'_> {
self.password_hash().algorithm
}
/// Optional version field.
pub fn version(&self) -> Option<Decimal> {
self.password_hash().version
}
/// Algorithm-specific parameters.
pub fn params(&self) -> ParamsString {
self.password_hash().params
}
/// [`Salt`] string for personalizing a password hash output.
pub fn salt(&self) -> Option<Salt<'_>> {
self.password_hash().salt
}
/// Password hashing function [`Output`], a.k.a. hash/digest.
pub fn hash(&self) -> Option<Output> {
self.password_hash().hash
}
}
#[cfg(feature = "alloc")]
impl AsRef<str> for PasswordHashString {
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[cfg(feature = "alloc")]
impl From<PasswordHash<'_>> for PasswordHashString {
fn from(hash: PasswordHash<'_>) -> PasswordHashString {
PasswordHashString::from(&hash)
}
}
#[cfg(feature = "alloc")]
impl From<&PasswordHash<'_>> for PasswordHashString {
fn from(hash: &PasswordHash<'_>) -> PasswordHashString {
PasswordHashString {
string: hash.to_string(),
encoding: hash.encoding(),
}
}
}
#[cfg(feature = "alloc")]
impl FromStr for PasswordHashString {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::new(s)
}
}
#[cfg(feature = "alloc")]
impl fmt::Display for PasswordHashString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}

334
vendor/password-hash/src/output.rs vendored Normal file
View File

@@ -0,0 +1,334 @@
//! Outputs from password hashing functions.
use crate::{Encoding, Error, Result};
use core::{
cmp::{Ordering, PartialEq},
fmt,
str::FromStr,
};
use subtle::{Choice, ConstantTimeEq};
/// Output from password hashing functions, i.e. the "hash" or "digest"
/// as raw bytes.
///
/// The [`Output`] type implements the RECOMMENDED best practices described in
/// the [PHC string format specification][1], namely:
///
/// > The hash output, for a verification, must be long enough to make preimage
/// > attacks at least as hard as password guessing. To promote wide acceptance,
/// > a default output size of 256 bits (32 bytes, encoded as 43 characters) is
/// > recommended. Function implementations SHOULD NOT allow outputs of less
/// > than 80 bits to be used for password verification.
///
/// # Recommended length
/// Per the description above, the recommended default length for an [`Output`]
/// of a password hashing function is **32-bytes** (256-bits).
///
/// # Constraints
/// The above guidelines are interpreted into the following constraints:
///
/// - Minimum length: **10**-bytes (80-bits)
/// - Maximum length: **64**-bytes (512-bits)
///
/// The specific recommendation of a 64-byte maximum length is taken as a best
/// practice from the hash output guidelines for [Argon2 Encoding][2] given in
/// the same document:
///
/// > The hash output...length shall be between 12 and 64 bytes (16 and 86
/// > characters, respectively). The default output length is 32 bytes
/// > (43 characters).
///
/// Based on this guidance, this type enforces an upper bound of 64-bytes
/// as a reasonable maximum, and recommends using 32-bytes.
///
/// # Constant-time comparisons
/// The [`Output`] type impls the [`ConstantTimeEq`] trait from the [`subtle`]
/// crate and uses it to perform constant-time comparisons.
///
/// Additionally the [`PartialEq`] and [`Eq`] trait impls for [`Output`] use
/// [`ConstantTimeEq`] when performing comparisons.
///
/// ## Attacks on non-constant-time password hash comparisons
/// Comparing password hashes in constant-time is known to mitigate at least
/// one [poorly understood attack][3] involving an adversary with the following
/// knowledge/capabilities:
///
/// - full knowledge of what password hashing algorithm is being used
/// including any relevant configurable parameters
/// - knowledge of the salt for a particular victim
/// - ability to accurately measure a timing side-channel on comparisons
/// of the password hash over the network
///
/// An attacker with the above is able to perform an offline computation of
/// the hash for any chosen password in such a way that it will match the
/// hash computed by the server.
///
/// As noted above, they also measure timing variability in the server's
/// comparison of the hash it computes for a given password and a target hash
/// the attacker is trying to learn.
///
/// When the attacker observes a hash comparison that takes longer than their
/// previous attempts, they learn that they guessed another byte in the
/// password hash correctly. They can leverage repeated measurements and
/// observations with different candidate passwords to learn the password
/// hash a byte-at-a-time in a manner similar to other such timing side-channel
/// attacks.
///
/// The attack may seem somewhat counterintuitive since learning prefixes of a
/// password hash does not reveal any additional information about the password
/// itself. However, the above can be combined with an offline dictionary
/// attack where the attacker is able to determine candidate passwords to send
/// to the server by performing a brute force search offline and selecting
/// candidate passwords whose hashes match the portion of the prefix they have
/// learned so far.
///
/// As the attacker learns a longer and longer prefix of the password hash,
/// they are able to more effectively eliminate candidate passwords offline as
/// part of a dictionary attack, until they eventually guess the correct
/// password or exhaust their set of candidate passwords.
///
/// ## Mitigations
/// While we have taken care to ensure password hashes are compared in constant
/// time, we would also suggest preventing such attacks by using randomly
/// generated salts and keeping those salts secret.
///
/// The [`SaltString::generate`][`crate::SaltString::generate`] function can be
/// used to generate random high-entropy salt values.
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties
/// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
/// [3]: https://web.archive.org/web/20130208100210/http://security-assessment.com/files/documents/presentations/TimingAttackPresentation2012.pdf
#[derive(Copy, Clone, Eq)]
pub struct Output {
/// Byte array containing a password hashing function output.
bytes: [u8; Self::MAX_LENGTH],
/// Length of the password hashing function output in bytes.
length: u8,
/// Encoding which output should be serialized with.
encoding: Encoding,
}
#[allow(clippy::len_without_is_empty)]
impl Output {
/// Minimum length of a [`Output`] string: 10-bytes.
pub const MIN_LENGTH: usize = 10;
/// Maximum length of [`Output`] string: 64-bytes.
///
/// See type-level documentation about [`Output`] for more information.
pub const MAX_LENGTH: usize = 64;
/// Maximum length of [`Output`] when encoded as B64 string: 86-bytes
/// (i.e. 86 ASCII characters)
pub const B64_MAX_LENGTH: usize = ((Self::MAX_LENGTH * 4) / 3) + 1;
/// Create a [`Output`] from the given byte slice, validating it according
/// to [`Output::MIN_LENGTH`] and [`Output::MAX_LENGTH`] restrictions.
pub fn new(input: &[u8]) -> Result<Self> {
Self::init_with(input.len(), |bytes| {
bytes.copy_from_slice(input);
Ok(())
})
}
/// Create a [`Output`] from the given byte slice and [`Encoding`],
/// validating it according to [`Output::MIN_LENGTH`] and
/// [`Output::MAX_LENGTH`] restrictions.
pub fn new_with_encoding(input: &[u8], encoding: Encoding) -> Result<Self> {
let mut result = Self::new(input)?;
result.encoding = encoding;
Ok(result)
}
/// Initialize an [`Output`] using the provided method, which is given
/// a mutable byte slice into which it should write the output.
///
/// The `output_size` (in bytes) must be known in advance, as well as at
/// least [`Output::MIN_LENGTH`] bytes and at most [`Output::MAX_LENGTH`]
/// bytes.
pub fn init_with<F>(output_size: usize, f: F) -> Result<Self>
where
F: FnOnce(&mut [u8]) -> Result<()>,
{
if output_size < Self::MIN_LENGTH {
return Err(Error::OutputSize {
provided: Ordering::Less,
expected: Self::MIN_LENGTH,
});
}
if output_size > Self::MAX_LENGTH {
return Err(Error::OutputSize {
provided: Ordering::Greater,
expected: Self::MAX_LENGTH,
});
}
let mut bytes = [0u8; Self::MAX_LENGTH];
f(&mut bytes[..output_size])?;
Ok(Self {
bytes,
length: output_size as u8,
encoding: Encoding::default(),
})
}
/// Borrow the output value as a byte slice.
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len()]
}
/// Get the [`Encoding`] that this [`Output`] is serialized with.
pub fn encoding(&self) -> Encoding {
self.encoding
}
/// Get the length of the output value as a byte slice.
pub fn len(&self) -> usize {
usize::from(self.length)
}
/// Parse B64-encoded [`Output`], i.e. using the PHC string
/// specification's restricted interpretation of Base64.
pub fn b64_decode(input: &str) -> Result<Self> {
Self::decode(input, Encoding::B64)
}
/// Write B64-encoded [`Output`] to the provided buffer, returning
/// a sub-slice containing the encoded data.
///
/// Returns an error if the buffer is too short to contain the output.
pub fn b64_encode<'a>(&self, out: &'a mut [u8]) -> Result<&'a str> {
self.encode(out, Encoding::B64)
}
/// Decode the given input string using the specified [`Encoding`].
pub fn decode(input: &str, encoding: Encoding) -> Result<Self> {
let mut bytes = [0u8; Self::MAX_LENGTH];
encoding
.decode(input, &mut bytes)
.map_err(Into::into)
.and_then(|decoded| Self::new_with_encoding(decoded, encoding))
}
/// Encode this [`Output`] using the specified [`Encoding`].
pub fn encode<'a>(&self, out: &'a mut [u8], encoding: Encoding) -> Result<&'a str> {
Ok(encoding.encode(self.as_ref(), out)?)
}
/// Get the length of this [`Output`] when encoded as B64.
pub fn b64_len(&self) -> usize {
Encoding::B64.encoded_len(self.as_ref())
}
}
impl AsRef<[u8]> for Output {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl ConstantTimeEq for Output {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
impl FromStr for Output {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::b64_decode(s)
}
}
impl PartialEq for Output {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl TryFrom<&[u8]> for Output {
type Error = Error;
fn try_from(input: &[u8]) -> Result<Output> {
Self::new(input)
}
}
impl fmt::Display for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buffer = [0u8; Self::B64_MAX_LENGTH];
self.encode(&mut buffer, self.encoding)
.map_err(|_| fmt::Error)
.and_then(|encoded| f.write_str(encoded))
}
}
impl fmt::Debug for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Output(\"{}\")", self)
}
}
#[cfg(test)]
mod tests {
use super::{Error, Ordering, Output};
#[test]
fn new_with_valid_min_length_input() {
let bytes = [10u8; 10];
let output = Output::new(&bytes).unwrap();
assert_eq!(output.as_ref(), &bytes);
}
#[test]
fn new_with_valid_max_length_input() {
let bytes = [64u8; 64];
let output = Output::new(&bytes).unwrap();
assert_eq!(output.as_ref(), &bytes);
}
#[test]
fn reject_new_too_short() {
let bytes = [9u8; 9];
let err = Output::new(&bytes).err().unwrap();
assert_eq!(
err,
Error::OutputSize {
provided: Ordering::Less,
expected: Output::MIN_LENGTH
}
);
}
#[test]
fn reject_new_too_long() {
let bytes = [65u8; 65];
let err = Output::new(&bytes).err().unwrap();
assert_eq!(
err,
Error::OutputSize {
provided: Ordering::Greater,
expected: Output::MAX_LENGTH
}
);
}
#[test]
fn partialeq_true() {
let a = Output::new(&[1u8; 32]).unwrap();
let b = Output::new(&[1u8; 32]).unwrap();
assert_eq!(a, b);
}
#[test]
fn partialeq_false() {
let a = Output::new(&[1u8; 32]).unwrap();
let b = Output::new(&[2u8; 32]).unwrap();
assert_ne!(a, b);
}
}

455
vendor/password-hash/src/params.rs vendored Normal file
View File

@@ -0,0 +1,455 @@
//! Algorithm parameters.
use crate::errors::InvalidValue;
use crate::{
value::{Decimal, Value},
Encoding, Error, Ident, Result,
};
use core::{
fmt::{self, Debug, Write},
iter::FromIterator,
str::{self, FromStr},
};
/// Individual parameter name/value pair.
pub type Pair<'a> = (Ident<'a>, Value<'a>);
/// Delimiter character between name/value pairs.
pub(crate) const PAIR_DELIMITER: char = '=';
/// Delimiter character between parameters.
pub(crate) const PARAMS_DELIMITER: char = ',';
/// Maximum number of supported parameters.
const MAX_LENGTH: usize = 127;
/// Error message used with `expect` for when internal invariants are violated
/// (i.e. the contents of a [`ParamsString`] should always be valid)
const INVARIANT_VIOLATED_MSG: &str = "PHC params invariant violated";
/// Algorithm parameter string.
///
/// The [PHC string format specification][1] defines a set of optional
/// algorithm-specific name/value pairs which can be encoded into a
/// PHC-formatted parameter string as follows:
///
/// ```text
/// $<param>=<value>(,<param>=<value>)*
/// ```
///
/// This type represents that set of parameters.
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification
#[derive(Clone, Default, Eq, PartialEq)]
pub struct ParamsString(Buffer);
impl ParamsString {
/// Create new empty [`ParamsString`].
pub fn new() -> Self {
Self::default()
}
/// Add the given byte value to the [`ParamsString`], encoding it as "B64".
pub fn add_b64_bytes<'a>(&mut self, name: impl TryInto<Ident<'a>>, bytes: &[u8]) -> Result<()> {
if !self.is_empty() {
self.0
.write_char(PARAMS_DELIMITER)
.map_err(|_| Error::ParamsMaxExceeded)?
}
let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
// Add param name
let offset = self.0.length;
if write!(self.0, "{}=", name).is_err() {
self.0.length = offset;
return Err(Error::ParamsMaxExceeded);
}
// Encode B64 value
let offset = self.0.length as usize;
let written = Encoding::B64
.encode(bytes, &mut self.0.bytes[offset..])?
.len();
self.0.length += written as u8;
Ok(())
}
/// Add a key/value pair with a decimal value to the [`ParamsString`].
pub fn add_decimal<'a>(&mut self, name: impl TryInto<Ident<'a>>, value: Decimal) -> Result<()> {
let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
self.add(name, value)
}
/// Add a key/value pair with a string value to the [`ParamsString`].
pub fn add_str<'a>(
&mut self,
name: impl TryInto<Ident<'a>>,
value: impl TryInto<Value<'a>>,
) -> Result<()> {
let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
let value = value
.try_into()
.map_err(|_| Error::ParamValueInvalid(InvalidValue::InvalidFormat))?;
self.add(name, value)
}
/// Borrow the contents of this [`ParamsString`] as a byte slice.
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
/// Borrow the contents of this [`ParamsString`] as a `str`.
pub fn as_str(&self) -> &str {
self.0.as_ref()
}
/// Get the count of the number ASCII characters in this [`ParamsString`].
pub fn len(&self) -> usize {
self.as_str().len()
}
/// Is this set of parameters empty?
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Iterate over the parameters.
pub fn iter(&self) -> Iter<'_> {
Iter::new(self.as_str())
}
/// Get a parameter [`Value`] by name.
pub fn get<'a>(&self, name: impl TryInto<Ident<'a>>) -> Option<Value<'_>> {
let name = name.try_into().ok()?;
for (n, v) in self.iter() {
if name == n {
return Some(v);
}
}
None
}
/// Get a parameter as a `str`.
pub fn get_str<'a>(&self, name: impl TryInto<Ident<'a>>) -> Option<&str> {
self.get(name).map(|value| value.as_str())
}
/// Get a parameter as a [`Decimal`].
///
/// See [`Value::decimal`] for format information.
pub fn get_decimal<'a>(&self, name: impl TryInto<Ident<'a>>) -> Option<Decimal> {
self.get(name).and_then(|value| value.decimal().ok())
}
/// Add a value to this [`ParamsString`] using the provided callback.
fn add(&mut self, name: Ident<'_>, value: impl fmt::Display) -> Result<()> {
if self.get(name).is_some() {
return Err(Error::ParamNameDuplicated);
}
let orig_len = self.0.length;
if !self.is_empty() {
self.0
.write_char(PARAMS_DELIMITER)
.map_err(|_| Error::ParamsMaxExceeded)?
}
if write!(self.0, "{}={}", name, value).is_err() {
self.0.length = orig_len;
return Err(Error::ParamsMaxExceeded);
}
Ok(())
}
}
impl FromStr for ParamsString {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if s.as_bytes().len() > MAX_LENGTH {
return Err(Error::ParamsMaxExceeded);
}
if s.is_empty() {
return Ok(ParamsString::new());
}
// Validate the string is well-formed
for mut param in s.split(PARAMS_DELIMITER).map(|p| p.split(PAIR_DELIMITER)) {
// Validate name
param
.next()
.ok_or(Error::ParamNameInvalid)
.and_then(Ident::try_from)?;
// Validate value
param
.next()
.ok_or(Error::ParamValueInvalid(InvalidValue::Malformed))
.and_then(Value::try_from)?;
if param.next().is_some() {
return Err(Error::ParamValueInvalid(InvalidValue::Malformed));
}
}
let mut bytes = [0u8; MAX_LENGTH];
bytes[..s.as_bytes().len()].copy_from_slice(s.as_bytes());
Ok(Self(Buffer {
bytes,
length: s.as_bytes().len() as u8,
}))
}
}
impl<'a> FromIterator<Pair<'a>> for ParamsString {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = Pair<'a>>,
{
let mut params = ParamsString::new();
for pair in iter {
params.add_str(pair.0, pair.1).expect("PHC params error");
}
params
}
}
impl fmt::Display for ParamsString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for ParamsString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.iter()).finish()
}
}
/// Iterator over algorithm parameters stored in a [`ParamsString`] struct.
pub struct Iter<'a> {
inner: Option<str::Split<'a, char>>,
}
impl<'a> Iter<'a> {
/// Create a new [`Iter`].
fn new(s: &'a str) -> Self {
if s.is_empty() {
Self { inner: None }
} else {
Self {
inner: Some(s.split(PARAMS_DELIMITER)),
}
}
}
}
impl<'a> Iterator for Iter<'a> {
type Item = Pair<'a>;
fn next(&mut self) -> Option<Pair<'a>> {
let mut param = self.inner.as_mut()?.next()?.split(PAIR_DELIMITER);
let name = param
.next()
.and_then(|id| Ident::try_from(id).ok())
.expect(INVARIANT_VIOLATED_MSG);
let value = param
.next()
.and_then(|value| Value::try_from(value).ok())
.expect(INVARIANT_VIOLATED_MSG);
debug_assert_eq!(param.next(), None);
Some((name, value))
}
}
/// Parameter buffer.
#[derive(Clone, Debug, Eq)]
struct Buffer {
/// Byte array containing an ASCII-encoded string.
bytes: [u8; MAX_LENGTH],
/// Length of the string in ASCII characters (i.e. bytes).
length: u8,
}
impl AsRef<str> for Buffer {
fn as_ref(&self) -> &str {
str::from_utf8(&self.bytes[..(self.length as usize)]).expect(INVARIANT_VIOLATED_MSG)
}
}
impl Default for Buffer {
fn default() -> Buffer {
Buffer {
bytes: [0u8; MAX_LENGTH],
length: 0,
}
}
}
impl PartialEq for Buffer {
fn eq(&self, other: &Self) -> bool {
// Ensure comparisons always honor the initialized portion of the buffer
self.as_ref().eq(other.as_ref())
}
}
impl Write for Buffer {
fn write_str(&mut self, input: &str) -> fmt::Result {
let bytes = input.as_bytes();
let length = self.length as usize;
if length + bytes.len() > MAX_LENGTH {
return Err(fmt::Error);
}
self.bytes[length..(length + bytes.len())].copy_from_slice(bytes);
self.length += bytes.len() as u8;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{Error, FromIterator, Ident, ParamsString, Value};
#[cfg(feature = "alloc")]
use alloc::string::ToString;
use core::str::FromStr;
#[test]
fn add() {
let mut params = ParamsString::new();
params.add_str("a", "1").unwrap();
params.add_decimal("b", 2).unwrap();
params.add_str("c", "3").unwrap();
assert_eq!(params.iter().count(), 3);
assert_eq!(params.get_decimal("a").unwrap(), 1);
assert_eq!(params.get_decimal("b").unwrap(), 2);
assert_eq!(params.get_decimal("c").unwrap(), 3);
}
#[test]
#[cfg(feature = "alloc")]
fn add_b64_bytes() {
let mut params = ParamsString::new();
params.add_b64_bytes("a", &[1]).unwrap();
params.add_b64_bytes("b", &[2, 3]).unwrap();
params.add_b64_bytes("c", &[4, 5, 6]).unwrap();
assert_eq!(params.to_string(), "a=AQ,b=AgM,c=BAUG");
}
#[test]
fn duplicate_names() {
let name = Ident::new("a").unwrap();
let mut params = ParamsString::new();
params.add_decimal(name, 1).unwrap();
let err = params.add_decimal(name, 2u32.into()).err().unwrap();
assert_eq!(err, Error::ParamNameDuplicated);
}
#[test]
fn from_iter() {
let params = ParamsString::from_iter(
[
(Ident::new("a").unwrap(), Value::try_from("1").unwrap()),
(Ident::new("b").unwrap(), Value::try_from("2").unwrap()),
(Ident::new("c").unwrap(), Value::try_from("3").unwrap()),
]
.iter()
.cloned(),
);
assert_eq!(params.iter().count(), 3);
assert_eq!(params.get_decimal("a").unwrap(), 1);
assert_eq!(params.get_decimal("b").unwrap(), 2);
assert_eq!(params.get_decimal("c").unwrap(), 3);
}
#[test]
fn iter() {
let mut params = ParamsString::new();
params.add_str("a", "1").unwrap();
params.add_str("b", "2").unwrap();
params.add_str("c", "3").unwrap();
let mut i = params.iter();
for (name, value) in &[("a", "1"), ("b", "2"), ("c", "3")] {
let name = Ident::new(name).unwrap();
let value = Value::try_from(*value).unwrap();
assert_eq!(i.next(), Some((name, value)));
}
assert_eq!(i.next(), None);
}
//
// `FromStr` tests
//
#[test]
fn parse_empty() {
let params = ParamsString::from_str("").unwrap();
assert!(params.is_empty());
}
#[test]
fn parse_one() {
let params = ParamsString::from_str("a=1").unwrap();
assert_eq!(params.iter().count(), 1);
assert_eq!(params.get("a").unwrap().decimal().unwrap(), 1);
}
#[test]
fn parse_many() {
let params = ParamsString::from_str("a=1,b=2,c=3").unwrap();
assert_eq!(params.iter().count(), 3);
assert_eq!(params.get_decimal("a").unwrap(), 1);
assert_eq!(params.get_decimal("b").unwrap(), 2);
assert_eq!(params.get_decimal("c").unwrap(), 3);
}
//
// `Display` tests
//
#[test]
#[cfg(feature = "alloc")]
fn display_empty() {
let params = ParamsString::new();
assert_eq!(params.to_string(), "");
}
#[test]
#[cfg(feature = "alloc")]
fn display_one() {
let params = ParamsString::from_str("a=1").unwrap();
assert_eq!(params.to_string(), "a=1");
}
#[test]
#[cfg(feature = "alloc")]
fn display_many() {
let params = ParamsString::from_str("a=1,b=2,c=3").unwrap();
assert_eq!(params.to_string(), "a=1,b=2,c=3");
}
}

354
vendor/password-hash/src/salt.rs vendored Normal file
View File

@@ -0,0 +1,354 @@
//! Salt string support.
use crate::{Encoding, Error, Result, Value};
use core::{fmt, str};
use crate::errors::InvalidValue;
#[cfg(feature = "rand_core")]
use rand_core::CryptoRngCore;
/// Error message used with `expect` for when internal invariants are violated
/// (i.e. the contents of a [`Salt`] should always be valid)
const INVARIANT_VIOLATED_MSG: &str = "salt string invariant violated";
/// Salt string.
///
/// In password hashing, a "salt" is an additional value used to
/// personalize/tweak the output of a password hashing function for a given
/// input password.
///
/// Salts help defend against attacks based on precomputed tables of hashed
/// passwords, i.e. "[rainbow tables][1]".
///
/// The [`Salt`] type implements the RECOMMENDED best practices for salts
/// described in the [PHC string format specification][2], namely:
///
/// > - Maximum lengths for salt, output and parameter values are meant to help
/// > consumer implementations, in particular written in C and using
/// > stack-allocated buffers. These buffers must account for the worst case,
/// > i.e. the maximum defined length. Therefore, keep these lengths low.
/// > - The role of salts is to achieve uniqueness. A random salt is fine for
/// > that as long as its length is sufficient; a 16-byte salt would work well
/// > (by definition, UUID are very good salts, and they encode over exactly
/// > 16 bytes). 16 bytes encode as 22 characters in B64. Functions should
/// > disallow salt values that are too small for security (4 bytes should be
/// > viewed as an absolute minimum).
///
/// # Recommended length
/// The recommended default length for a salt string is **16-bytes** (128-bits).
///
/// See [`Salt::RECOMMENDED_LENGTH`] for more information.
///
/// # Constraints
/// Salt strings are constrained to the following set of characters per the
/// PHC spec:
///
/// > The salt consists in a sequence of characters in: `[a-zA-Z0-9/+.-]`
/// > (lowercase letters, uppercase letters, digits, /, +, . and -).
///
/// Additionally the following length restrictions are enforced based on the
/// guidelines from the spec:
///
/// - Minimum length: **4**-bytes
/// - Maximum length: **64**-bytes
///
/// A maximum length is enforced based on the above recommendation for
/// supporting stack-allocated buffers (which this library uses), and the
/// specific determination of 64-bytes is taken as a best practice from the
/// [Argon2 Encoding][3] specification in the same document:
///
/// > The length in bytes of the salt is between 8 and 64 bytes<sup>†</sup>, thus
/// > yielding a length in characters between 11 and 64 characters (and that
/// > length is never equal to 1 modulo 4). The default byte length of the salt
/// > is 16 bytes (22 characters in B64 encoding). An encoded UUID, or a
/// > sequence of 16 bytes produced with a cryptographically strong PRNG, are
/// > appropriate salt values.
/// >
/// > <sup>†</sup>The Argon2 specification states that the salt can be much longer, up
/// > to 2^32-1 bytes, but this makes little sense for password hashing.
/// > Specifying a relatively small maximum length allows for parsing with a
/// > stack allocated buffer.)
///
/// Based on this guidance, this type enforces an upper bound of 64-bytes
/// as a reasonable maximum, and recommends using 16-bytes.
///
/// [1]: https://en.wikipedia.org/wiki/Rainbow_table
/// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties
/// [3]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Salt<'a>(Value<'a>);
#[allow(clippy::len_without_is_empty)]
impl<'a> Salt<'a> {
/// Minimum length of a [`Salt`] string: 4-bytes.
pub const MIN_LENGTH: usize = 4;
/// Maximum length of a [`Salt`] string: 64-bytes.
///
/// See type-level documentation about [`Salt`] for more information.
pub const MAX_LENGTH: usize = 64;
/// Recommended length of a salt: 16-bytes.
///
/// This recommendation comes from the [PHC string format specification]:
///
/// > The role of salts is to achieve uniqueness. A *random* salt is fine
/// > for that as long as its length is sufficient; a 16-byte salt would
/// > work well (by definition, UUID are very good salts, and they encode
/// > over exactly 16 bytes). 16 bytes encode as 22 characters in B64.
///
/// [PHC string format specification]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties
pub const RECOMMENDED_LENGTH: usize = 16;
/// Create a [`Salt`] from the given B64-encoded input string, validating
/// [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions.
pub fn from_b64(input: &'a str) -> Result<Self> {
let length = input.as_bytes().len();
if length < Self::MIN_LENGTH {
return Err(Error::SaltInvalid(InvalidValue::TooShort));
}
if length > Self::MAX_LENGTH {
return Err(Error::SaltInvalid(InvalidValue::TooLong));
}
// TODO(tarcieri): full B64 decoding check?
for char in input.chars() {
// From the PHC string format spec:
//
// > The salt consists in a sequence of characters in: `[a-zA-Z0-9/+.-]`
// > (lowercase letters, uppercase letters, digits, /, +, . and -).
if !matches!(char, 'a'..='z' | 'A'..='Z' | '0'..='9' | '/' | '+' | '.' | '-') {
return Err(Error::SaltInvalid(InvalidValue::InvalidChar(char)));
}
}
input.try_into().map(Self).map_err(|e| match e {
Error::ParamValueInvalid(value_err) => Error::SaltInvalid(value_err),
err => err,
})
}
/// Attempt to decode a B64-encoded [`Salt`] into bytes, writing the
/// decoded output into the provided buffer, and returning a slice of the
/// portion of the buffer containing the decoded result on success.
pub fn decode_b64<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> {
self.0.b64_decode(buf)
}
/// Borrow this value as a `str`.
pub fn as_str(&self) -> &'a str {
self.0.as_str()
}
/// Get the length of this value in ASCII characters.
pub fn len(&self) -> usize {
self.as_str().len()
}
/// Create a [`Salt`] from the given B64-encoded input string, validating
/// [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions.
#[deprecated(since = "0.5.0", note = "use `from_b64` instead")]
pub fn new(input: &'a str) -> Result<Self> {
Self::from_b64(input)
}
/// Attempt to decode a B64-encoded [`Salt`] into bytes, writing the
/// decoded output into the provided buffer, and returning a slice of the
/// portion of the buffer containing the decoded result on success.
#[deprecated(since = "0.5.0", note = "use `decode_b64` instead")]
pub fn b64_decode<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> {
self.decode_b64(buf)
}
}
impl<'a> AsRef<str> for Salt<'a> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> TryFrom<&'a str> for Salt<'a> {
type Error = Error;
fn try_from(input: &'a str) -> Result<Self> {
Self::from_b64(input)
}
}
impl<'a> fmt::Display for Salt<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<'a> fmt::Debug for Salt<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Salt({:?})", self.as_str())
}
}
/// Owned stack-allocated equivalent of [`Salt`].
#[derive(Clone, Eq)]
pub struct SaltString {
/// ASCII-encoded characters which comprise the salt.
chars: [u8; Salt::MAX_LENGTH],
/// Length of the string in ASCII characters (i.e. bytes).
length: u8,
}
#[allow(clippy::len_without_is_empty)]
impl SaltString {
/// Generate a random B64-encoded [`SaltString`].
#[cfg(feature = "rand_core")]
pub fn generate(mut rng: impl CryptoRngCore) -> Self {
let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH];
rng.fill_bytes(&mut bytes);
Self::encode_b64(&bytes).expect(INVARIANT_VIOLATED_MSG)
}
/// Create a new [`SaltString`] from the given B64-encoded input string,
/// validating [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions.
pub fn from_b64(s: &str) -> Result<Self> {
// Assert `s` parses successfully as a `Salt`
Salt::from_b64(s)?;
let len = s.as_bytes().len();
let mut bytes = [0u8; Salt::MAX_LENGTH];
bytes[..len].copy_from_slice(s.as_bytes());
Ok(SaltString {
chars: bytes,
length: len as u8, // `Salt::from_b64` check prevents overflow
})
}
/// Decode this [`SaltString`] from B64 into the provided output buffer.
pub fn decode_b64<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8]> {
self.as_salt().decode_b64(buf)
}
/// Encode the given byte slice as B64 into a new [`SaltString`].
///
/// Returns `Error` if the slice is too long.
pub fn encode_b64(input: &[u8]) -> Result<Self> {
let mut bytes = [0u8; Salt::MAX_LENGTH];
let length = Encoding::B64.encode(input, &mut bytes)?.len() as u8;
Ok(Self {
chars: bytes,
length,
})
}
/// Borrow the contents of a [`SaltString`] as a [`Salt`].
pub fn as_salt(&self) -> Salt<'_> {
Salt::from_b64(self.as_str()).expect(INVARIANT_VIOLATED_MSG)
}
/// Borrow the contents of a [`SaltString`] as a `str`.
pub fn as_str(&self) -> &str {
str::from_utf8(&self.chars[..(self.length as usize)]).expect(INVARIANT_VIOLATED_MSG)
}
/// Get the length of this value in ASCII characters.
pub fn len(&self) -> usize {
self.as_str().len()
}
/// Create a new [`SaltString`] from the given B64-encoded input string,
/// validating [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions.
#[deprecated(since = "0.5.0", note = "use `from_b64` instead")]
pub fn new(s: &str) -> Result<Self> {
Self::from_b64(s)
}
/// Decode this [`SaltString`] from B64 into the provided output buffer.
#[deprecated(since = "0.5.0", note = "use `decode_b64` instead")]
pub fn b64_decode<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8]> {
self.decode_b64(buf)
}
/// Encode the given byte slice as B64 into a new [`SaltString`].
///
/// Returns `Error` if the slice is too long.
#[deprecated(since = "0.5.0", note = "use `encode_b64` instead")]
pub fn b64_encode(input: &[u8]) -> Result<Self> {
Self::encode_b64(input)
}
}
impl AsRef<str> for SaltString {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl PartialEq for SaltString {
fn eq(&self, other: &Self) -> bool {
// Ensure comparisons always honor the initialized portion of the buffer
self.as_ref().eq(other.as_ref())
}
}
impl<'a> From<&'a SaltString> for Salt<'a> {
fn from(salt_string: &'a SaltString) -> Salt<'a> {
salt_string.as_salt()
}
}
impl fmt::Display for SaltString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for SaltString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SaltString({:?})", self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::{Error, Salt};
use crate::errors::InvalidValue;
#[test]
fn new_with_valid_min_length_input() {
let s = "abcd";
let salt = Salt::from_b64(s).unwrap();
assert_eq!(salt.as_ref(), s);
}
#[test]
fn new_with_valid_max_length_input() {
let s = "012345678911234567892123456789312345678941234567";
let salt = Salt::from_b64(s).unwrap();
assert_eq!(salt.as_ref(), s);
}
#[test]
fn reject_new_too_short() {
for &too_short in &["", "a", "ab", "abc"] {
let err = Salt::from_b64(too_short).err().unwrap();
assert_eq!(err, Error::SaltInvalid(InvalidValue::TooShort));
}
}
#[test]
fn reject_new_too_long() {
let s = "01234567891123456789212345678931234567894123456785234567896234567";
let err = Salt::from_b64(s).err().unwrap();
assert_eq!(err, Error::SaltInvalid(InvalidValue::TooLong));
}
#[test]
fn reject_new_invalid_char() {
let s = "01234_abcd";
let err = Salt::from_b64(s).err().unwrap();
assert_eq!(err, Error::SaltInvalid(InvalidValue::InvalidChar('_')));
}
}

101
vendor/password-hash/src/traits.rs vendored Normal file
View File

@@ -0,0 +1,101 @@
//! Trait definitions.
use crate::{Decimal, Error, Ident, ParamsString, PasswordHash, Result, Salt};
use core::fmt::Debug;
/// Trait for password hashing functions.
pub trait PasswordHasher {
/// Algorithm-specific parameters.
type Params: Clone
+ Debug
+ Default
+ for<'a> TryFrom<&'a PasswordHash<'a>, Error = Error>
+ TryInto<ParamsString, Error = Error>;
/// Compute a [`PasswordHash`] from the provided password using an
/// explicit set of customized algorithm parameters as opposed to the
/// defaults.
///
/// When in doubt, use [`PasswordHasher::hash_password`] instead.
fn hash_password_customized<'a>(
&self,
password: &[u8],
algorithm: Option<Ident<'a>>,
version: Option<Decimal>,
params: Self::Params,
salt: impl Into<Salt<'a>>,
) -> Result<PasswordHash<'a>>;
/// Simple API for computing a [`PasswordHash`] from a password and
/// salt value.
///
/// Uses the default recommended parameters for a given algorithm.
fn hash_password<'a>(
&self,
password: &[u8],
salt: impl Into<Salt<'a>>,
) -> Result<PasswordHash<'a>> {
self.hash_password_customized(password, None, None, Self::Params::default(), salt)
}
}
/// Trait for password verification.
///
/// Automatically impl'd for any type that impls [`PasswordHasher`].
///
/// This trait is object safe and can be used to implement abstractions over
/// multiple password hashing algorithms. One such abstraction is provided by
/// the [`PasswordHash::verify_password`] method.
pub trait PasswordVerifier {
/// Compute this password hashing function against the provided password
/// using the parameters from the provided password hash and see if the
/// computed output matches.
fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>;
}
impl<T: PasswordHasher> PasswordVerifier for T {
fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()> {
if let (Some(salt), Some(expected_output)) = (&hash.salt, &hash.hash) {
let computed_hash = self.hash_password_customized(
password,
Some(hash.algorithm),
hash.version,
T::Params::try_from(hash)?,
*salt,
)?;
if let Some(computed_output) = &computed_hash.hash {
// See notes on `Output` about the use of a constant-time comparison
if expected_output == computed_output {
return Ok(());
}
}
}
Err(Error::Password)
}
}
/// Trait for password hashing algorithms which support the legacy
/// [Modular Crypt Format (MCF)][MCF].
///
/// [MCF]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html
pub trait McfHasher {
/// Upgrade an MCF hash to a PHC hash. MCF follow this rough format:
///
/// ```text
/// $<id>$<content>
/// ```
///
/// MCF hashes are otherwise largely unstructured and parsed according to
/// algorithm-specific rules so hashers must parse a raw string themselves.
fn upgrade_mcf_hash<'a>(&self, hash: &'a str) -> Result<PasswordHash<'a>>;
/// Verify a password hash in MCF format against the provided password.
fn verify_mcf_hash(&self, password: &[u8], mcf_hash: &str) -> Result<()>
where
Self: PasswordVerifier,
{
self.verify_password(password, &self.upgrade_mcf_hash(mcf_hash)?)
}
}

304
vendor/password-hash/src/value.rs vendored Normal file
View File

@@ -0,0 +1,304 @@
//! Algorithm parameter value as defined by the [PHC string format].
//!
//! Implements the following parts of the specification:
//!
//! > The value for each parameter consists in characters in: `[a-zA-Z0-9/+.-]`
//! > (lowercase letters, uppercase letters, digits, /, +, . and -). No other
//! > character is allowed. Interpretation of the value depends on the
//! > parameter and the function. The function specification MUST unambiguously
//! > define the set of valid parameter values. The function specification MUST
//! > define a maximum length (in characters) for each parameter. For numerical
//! > parameters, functions SHOULD use plain decimal encoding (other encodings
//! > are possible as long as they are clearly defined).
//!
//! [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
use crate::errors::InvalidValue;
use crate::{Encoding, Error, Result};
use core::{fmt, str};
/// Type used to represent decimal (i.e. integer) values.
pub type Decimal = u32;
/// Algorithm parameter value string.
///
/// Parameter values are defined in the [PHC string format specification][1].
///
/// # Constraints
/// - ASCII-encoded string consisting of the characters `[a-zA-Z0-9/+.-]`
/// (lowercase letters, digits, and the minus sign)
/// - Minimum length: 0 (i.e. empty values are allowed)
/// - Maximum length: 64 ASCII characters (i.e. 64-bytes)
///
/// # Additional Notes
/// The PHC spec allows for algorithm-defined maximum lengths for parameter
/// values, however this library defines a [`Value::MAX_LENGTH`] of 64 ASCII
/// characters.
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
/// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Value<'a>(&'a str);
impl<'a> Value<'a> {
/// Maximum length of an [`Value`] - 64 ASCII characters (i.e. 64-bytes).
///
/// This value is selected to match the maximum length of a [`Salt`][`crate::Salt`]
/// as this library internally uses this type to represent salts.
pub const MAX_LENGTH: usize = 64;
/// Parse a [`Value`] from the provided `str`, validating it according to
/// the PHC string format's rules.
pub fn new(input: &'a str) -> Result<Self> {
if input.as_bytes().len() > Self::MAX_LENGTH {
return Err(Error::ParamValueInvalid(InvalidValue::TooLong));
}
// Check that the characters are permitted in a PHC parameter value.
assert_valid_value(input)?;
Ok(Self(input))
}
/// Attempt to decode a B64-encoded [`Value`], writing the decoded
/// result into the provided buffer, and returning a slice of the buffer
/// containing the decoded result on success.
///
/// Examples of "B64"-encoded parameters in practice are the `keyid` and
/// `data` parameters used by the [Argon2 Encoding][1] as described in the
/// PHC string format specification.
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
pub fn b64_decode<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> {
Ok(Encoding::B64.decode(self.as_str(), buf)?)
}
/// Borrow this value as a `str`.
pub fn as_str(&self) -> &'a str {
self.0
}
/// Borrow this value as bytes.
pub fn as_bytes(&self) -> &'a [u8] {
self.as_str().as_bytes()
}
/// Get the length of this value in ASCII characters.
pub fn len(&self) -> usize {
self.as_str().len()
}
/// Is this value empty?
pub fn is_empty(&self) -> bool {
self.as_str().is_empty()
}
/// Attempt to parse this [`Value`] as a PHC-encoded decimal (i.e. integer).
///
/// Decimal values are integers which follow the rules given in the
/// ["Decimal Encoding" section of the PHC string format specification][1].
///
/// The decimal encoding rules are as follows:
/// > For an integer value x, its decimal encoding consist in the following:
/// >
/// > - If x < 0, then its decimal encoding is the minus sign - followed by the decimal
/// > encoding of -x.
/// > - If x = 0, then its decimal encoding is the single character 0.
/// > - If x > 0, then its decimal encoding is the smallest sequence of ASCII digits that
/// > matches its value (i.e. there is no leading zero).
/// >
/// > Thus, a value is a valid decimal for an integer x if and only if all of the following hold true:
/// >
/// > - The first character is either a - sign, or an ASCII digit.
/// > - All characters other than the first are ASCII digits.
/// > - If the first character is - sign, then there is at least another character, and the
/// > second character is not a 0.
/// > - If the string consists in more than one character, then the first one cannot be a 0.
///
/// Note: this implementation does not support negative decimals despite
/// them being allowed per the spec above. If you need to parse a negative
/// number, please parse it from the string representation directly e.g.
/// `value.as_str().parse::<i32>()`
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#decimal-encoding
pub fn decimal(&self) -> Result<Decimal> {
let value = self.as_str();
// Empty strings aren't decimals
if value.is_empty() {
return Err(Error::ParamValueInvalid(InvalidValue::Malformed));
}
// Ensure all characters are digits
for c in value.chars() {
if !c.is_ascii_digit() {
return Err(Error::ParamValueInvalid(InvalidValue::InvalidChar(c)));
}
}
// Disallow leading zeroes
if value.starts_with('0') && value.len() > 1 {
return Err(Error::ParamValueInvalid(InvalidValue::InvalidFormat));
}
value.parse().map_err(|_| {
// In theory a value overflow should be the only potential error here.
// When `ParseIntError::kind` is stable it might be good to double check:
// <https://github.com/rust-lang/rust/issues/22639>
Error::ParamValueInvalid(InvalidValue::InvalidFormat)
})
}
/// Does this value parse successfully as a decimal?
pub fn is_decimal(&self) -> bool {
self.decimal().is_ok()
}
}
impl<'a> AsRef<str> for Value<'a> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> TryFrom<&'a str> for Value<'a> {
type Error = Error;
fn try_from(input: &'a str) -> Result<Self> {
Self::new(input)
}
}
impl<'a> TryFrom<Value<'a>> for Decimal {
type Error = Error;
fn try_from(value: Value<'a>) -> Result<Decimal> {
Decimal::try_from(&value)
}
}
impl<'a> TryFrom<&Value<'a>> for Decimal {
type Error = Error;
fn try_from(value: &Value<'a>) -> Result<Decimal> {
value.decimal()
}
}
impl<'a> fmt::Display for Value<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
/// Are all of the given bytes allowed in a [`Value`]?
fn assert_valid_value(input: &str) -> Result<()> {
for c in input.chars() {
if !is_char_valid(c) {
return Err(Error::ParamValueInvalid(InvalidValue::InvalidChar(c)));
}
}
Ok(())
}
/// Ensure the given ASCII character (i.e. byte) is allowed in a [`Value`].
fn is_char_valid(c: char) -> bool {
matches!(c, 'A' ..= 'Z' | 'a'..='z' | '0'..='9' | '/' | '+' | '.' | '-')
}
#[cfg(test)]
mod tests {
use super::{Error, InvalidValue, Value};
// Invalid value examples
const INVALID_CHAR: &str = "x;y";
const INVALID_TOO_LONG: &str =
"01234567891123456789212345678931234567894123456785234567896234567";
const INVALID_CHAR_AND_TOO_LONG: &str =
"0!234567891123456789212345678931234567894123456785234567896234567";
//
// Decimal parsing tests
//
#[test]
fn decimal_value() {
let valid_decimals = &[("0", 0u32), ("1", 1u32), ("4294967295", u32::MAX)];
for &(s, i) in valid_decimals {
let value = Value::new(s).unwrap();
assert!(value.is_decimal());
assert_eq!(value.decimal().unwrap(), i)
}
}
#[test]
fn reject_decimal_with_leading_zero() {
let value = Value::new("01").unwrap();
let err = u32::try_from(value).err().unwrap();
assert!(matches!(
err,
Error::ParamValueInvalid(InvalidValue::InvalidFormat)
));
}
#[test]
fn reject_overlong_decimal() {
let value = Value::new("4294967296").unwrap();
let err = u32::try_from(value).err().unwrap();
assert_eq!(err, Error::ParamValueInvalid(InvalidValue::InvalidFormat));
}
#[test]
fn reject_negative() {
let value = Value::new("-1").unwrap();
let err = u32::try_from(value).err().unwrap();
assert!(matches!(
err,
Error::ParamValueInvalid(InvalidValue::InvalidChar(_))
));
}
//
// String parsing tests
//
#[test]
fn string_value() {
let valid_examples = [
"",
"X",
"x",
"xXx",
"a+b.c-d",
"1/2",
"01234567891123456789212345678931",
];
for &example in &valid_examples {
let value = Value::new(example).unwrap();
assert_eq!(value.as_str(), example);
}
}
#[test]
fn reject_invalid_char() {
let err = Value::new(INVALID_CHAR).err().unwrap();
assert!(matches!(
err,
Error::ParamValueInvalid(InvalidValue::InvalidChar(_))
));
}
#[test]
fn reject_too_long() {
let err = Value::new(INVALID_TOO_LONG).err().unwrap();
assert_eq!(err, Error::ParamValueInvalid(InvalidValue::TooLong));
}
#[test]
fn reject_invalid_char_and_too_long() {
let err = Value::new(INVALID_CHAR_AND_TOO_LONG).err().unwrap();
assert_eq!(err, Error::ParamValueInvalid(InvalidValue::TooLong));
}
}

Binary file not shown.

View File

@@ -0,0 +1 @@
{"name":"password-hash","vers":"0.5.0","deps":[{"name":"base64ct","req":"^1","features":[],"optional":false,"default_features":true,"target":null,"kind":"normal","registry":"https://github.com/rust-lang/crates.io-index","package":null,"public":null,"artifact":null,"bindep_target":null,"lib":false},{"name":"rand_core","req":"^0.6.4","features":[],"optional":true,"default_features":false,"target":null,"kind":"normal","registry":"https://github.com/rust-lang/crates.io-index","package":null,"public":null,"artifact":null,"bindep_target":null,"lib":false},{"name":"subtle","req":"^2","features":[],"optional":false,"default_features":false,"target":null,"kind":"normal","registry":"https://github.com/rust-lang/crates.io-index","package":null,"public":null,"artifact":null,"bindep_target":null,"lib":false}],"features":{"alloc":["base64ct/alloc"],"default":["rand_core"],"getrandom":["rand_core/getrandom"],"std":["alloc","base64ct/std","rand_core/std"]},"features2":null,"cksum":"7023d254632b3a88e6a042f09622ea2af1dc01de897edb7498657bbe6ba2e4b3","yanked":null,"links":null,"rust_version":null,"v":2}

38
vendor/password-hash/tests/encoding.rs vendored Normal file
View File

@@ -0,0 +1,38 @@
//! Base64 encoding tests.
//!
//! # B64 Notes
//!
//! "B64" is a ubset of the standard Base64 encoding (RFC 4648, section 4) which
//! omits padding (`=`) as well as extra whitespace, as described in the PHC
//! string format specification:
//!
//! <https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#b64>
use password_hash::{Output, Salt};
// Example salt encoded as a B64 string.
const EXAMPLE_SALT_B64: &str = "REVBREJFRUZERUFEQkVFRg";
const EXAMPLE_SALT_RAW: &[u8] = b"DEADBEEFDEADBEEF";
// Example PHF output encoded as a B64 string.
const EXAMPLE_OUTPUT_B64: &str =
"REVBREJFRUZERUFEQkVFRkRFQURCRUVGREVBREJFRUZERUFEQkVFRkRFQURCRUVGREVBREJFRUZERUFEQkVFRg";
const EXAMPLE_OUTPUT_RAW: &[u8] =
b"DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF";
#[test]
fn salt_roundtrip() {
let mut buffer = [0u8; 64];
let salt = Salt::from_b64(EXAMPLE_SALT_B64).unwrap();
assert_eq!(salt.as_ref(), EXAMPLE_SALT_B64);
let salt_decoded = salt.decode_b64(&mut buffer).unwrap();
assert_eq!(salt_decoded, EXAMPLE_SALT_RAW);
}
#[test]
fn output_roundtrip() {
let out = EXAMPLE_OUTPUT_B64.parse::<Output>().unwrap();
assert_eq!(out.as_ref(), EXAMPLE_OUTPUT_RAW);
assert_eq!(out.to_string(), EXAMPLE_OUTPUT_B64);
}

88
vendor/password-hash/tests/hashing.rs vendored Normal file
View File

@@ -0,0 +1,88 @@
//! Password hashing tests
pub use password_hash::{
Decimal, Error, Ident, Output, ParamsString, PasswordHash, PasswordHasher, Result, Salt,
};
const ALG: Ident = Ident::new_unwrap("example");
/// Stub password hashing function for testing.
pub struct StubPasswordHasher;
impl PasswordHasher for StubPasswordHasher {
type Params = StubParams;
fn hash_password_customized<'a>(
&self,
password: &[u8],
algorithm: Option<Ident<'a>>,
version: Option<Decimal>,
params: StubParams,
salt: impl Into<Salt<'a>>,
) -> Result<PasswordHash<'a>> {
let salt = salt.into();
let mut output = Vec::new();
if let Some(alg) = algorithm {
if alg != ALG {
return Err(Error::Algorithm);
}
}
for slice in &[b"pw", password, b",salt:", salt.as_str().as_bytes()] {
output.extend_from_slice(slice);
}
let hash = Output::new(&output)?;
Ok(PasswordHash {
algorithm: ALG,
version,
params: params.try_into()?,
salt: Some(salt),
hash: Some(hash),
})
}
}
/// Stub parameters
#[derive(Clone, Debug, Default)]
pub struct StubParams;
impl<'a> TryFrom<&PasswordHash<'a>> for StubParams {
type Error = Error;
fn try_from(_: &PasswordHash<'a>) -> Result<Self> {
Ok(Self)
}
}
impl<'a> TryFrom<StubParams> for ParamsString {
type Error = Error;
fn try_from(_: StubParams) -> Result<Self> {
Ok(Self::default())
}
}
#[test]
fn verify_password_hash() {
let valid_password = "test password";
let salt = Salt::from_b64("test-salt").unwrap();
let hash = PasswordHash::generate(StubPasswordHasher, valid_password, salt).unwrap();
// Sanity tests for StubFunction impl above
assert_eq!(hash.algorithm, ALG);
assert_eq!(hash.salt.unwrap(), salt);
// Tests for generic password verification logic
assert_eq!(
hash.verify_password(&[&StubPasswordHasher], valid_password),
Ok(())
);
assert_eq!(
hash.verify_password(&[&StubPasswordHasher], "wrong password"),
Err(Error::Password)
);
}

View File

@@ -0,0 +1,146 @@
//! Tests for `PasswordHash` encoding/decoding.
//!
//! Each test implements a different permutation of the possible combinations
//! of the string encoding, and ensures password hashes round trip under each
//! of the conditions.
use password_hash::{Ident, ParamsString, PasswordHash, Salt};
const EXAMPLE_ALGORITHM: Ident = Ident::new_unwrap("argon2d");
const EXAMPLE_SALT: &str = "saltsaltsaltsaltsalt";
const EXAMPLE_HASH: &[u8] = &[
0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85,
0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab,
];
/// Example parameters
fn example_params() -> ParamsString {
let mut params = ParamsString::new();
params.add_decimal("a", 1).unwrap();
params.add_decimal("b", 2).unwrap();
params.add_decimal("c", 3).unwrap();
params
}
#[test]
fn algorithm_alone() {
let ph = PasswordHash::new("$argon2d").unwrap();
assert_eq!(ph.algorithm, EXAMPLE_ALGORITHM);
let s = ph.to_string();
assert_eq!(s, "$argon2d");
let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
assert_eq!(ph, ph2);
}
#[test]
fn params() {
let ph = PasswordHash {
algorithm: EXAMPLE_ALGORITHM,
version: None,
params: example_params(),
salt: None,
hash: None,
};
let s = ph.to_string();
assert_eq!(s, "$argon2d$a=1,b=2,c=3");
let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
assert_eq!(ph, ph2);
}
#[test]
fn salt() {
let ph = PasswordHash {
algorithm: EXAMPLE_ALGORITHM,
version: None,
params: ParamsString::new(),
salt: Some(Salt::from_b64(EXAMPLE_SALT).unwrap()),
hash: None,
};
let s = ph.to_string();
assert_eq!(s, "$argon2d$saltsaltsaltsaltsalt");
let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
assert_eq!(ph, ph2);
}
#[test]
fn one_param_and_salt() {
let mut params = ParamsString::new();
params.add_decimal("a", 1).unwrap();
let ph = PasswordHash {
algorithm: EXAMPLE_ALGORITHM,
version: None,
params,
salt: Some(Salt::from_b64(EXAMPLE_SALT).unwrap()),
hash: None,
};
let s = ph.to_string();
assert_eq!(s, "$argon2d$a=1$saltsaltsaltsaltsalt");
let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
assert_eq!(ph, ph2);
}
#[test]
fn params_and_salt() {
let ph = PasswordHash {
algorithm: EXAMPLE_ALGORITHM,
version: None,
params: example_params(),
salt: Some(Salt::from_b64(EXAMPLE_SALT).unwrap()),
hash: None,
};
let s = ph.to_string();
assert_eq!(s, "$argon2d$a=1,b=2,c=3$saltsaltsaltsaltsalt");
let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
assert_eq!(ph, ph2);
}
#[test]
fn salt_and_hash() {
let ph = PasswordHash {
algorithm: EXAMPLE_ALGORITHM,
version: None,
params: ParamsString::default(),
salt: Some(Salt::from_b64(EXAMPLE_SALT).unwrap()),
hash: Some(EXAMPLE_HASH.try_into().unwrap()),
};
let s = ph.to_string();
assert_eq!(
s,
"$argon2d$saltsaltsaltsaltsalt$hashhashhashhashhashhashhashhashhashhashhas"
);
let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
assert_eq!(ph, ph2);
}
#[test]
fn all_fields() {
let ph = PasswordHash {
algorithm: EXAMPLE_ALGORITHM,
version: None,
params: example_params(),
salt: Some(Salt::from_b64(EXAMPLE_SALT).unwrap()),
hash: Some(EXAMPLE_HASH.try_into().unwrap()),
};
let s = ph.to_string();
assert_eq!(
s,
"$argon2d$a=1,b=2,c=3$saltsaltsaltsaltsalt$hashhashhashhashhashhashhashhashhashhashhas"
);
let ph2 = PasswordHash::try_from(s.as_str()).unwrap();
assert_eq!(ph, ph2);
}

View File

@@ -0,0 +1,51 @@
//! Test vectors for commonly used password hashing algorithms.
use password_hash::{Ident, PasswordHash};
const ARGON2D_HASH: &str =
"$argon2d$v=19$m=512,t=3,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ";
const BCRYPT_HASH: &str = "$2b$MTIzNA$i5btSOiulHhaPHPbgNUGdObga/GCAVG/y5HHY1ra7L0C9dpCaw8u";
const SCRYPT_HASH: &str =
"$scrypt$epIxT/h6HbbwHaehFnh/bw$7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0";
#[test]
fn argon2id() {
let ph = PasswordHash::new(ARGON2D_HASH).unwrap();
assert_eq!(ph.algorithm, Ident::new("argon2d").unwrap());
assert_eq!(ph.version, Some(19));
assert_eq!(ph.params.iter().count(), 3);
assert_eq!(ph.params.get_decimal("m").unwrap(), 512);
assert_eq!(ph.params.get_decimal("t").unwrap(), 3);
assert_eq!(ph.params.get_decimal("p").unwrap(), 2);
assert_eq!(ph.salt.unwrap().as_ref(), "5VtWOO3cGWYQHEMaYGbsfQ");
assert_eq!(ph.hash.unwrap().to_string(), "AcmqasQgW/wI6wAHAMk4aQ");
assert_eq!(ph.to_string(), ARGON2D_HASH);
}
#[test]
fn bcrypt() {
let ph = PasswordHash::new(BCRYPT_HASH).unwrap();
assert_eq!(ph.algorithm, Ident::new("2b").unwrap());
assert_eq!(ph.version, None);
assert_eq!(ph.params.len(), 0);
assert_eq!(ph.salt.unwrap().to_string(), "MTIzNA");
assert_eq!(
ph.hash.unwrap().to_string(),
"i5btSOiulHhaPHPbgNUGdObga/GCAVG/y5HHY1ra7L0C9dpCaw8u"
);
assert_eq!(ph.to_string(), BCRYPT_HASH);
}
#[test]
fn scrypt() {
let ph = PasswordHash::new(SCRYPT_HASH).unwrap();
assert_eq!(ph.algorithm, Ident::new("scrypt").unwrap());
assert_eq!(ph.version, None);
assert_eq!(ph.params.len(), 0);
assert_eq!(ph.salt.unwrap().to_string(), "epIxT/h6HbbwHaehFnh/bw");
assert_eq!(
ph.hash.unwrap().to_string(),
"7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0"
);
assert_eq!(ph.to_string(), SCRYPT_HASH);
}