From 23330776638cae9c08541ce72b9d4e57e3f109d9 Mon Sep 17 00:00:00 2001 From: aumetra Date: Mon, 9 May 2022 02:41:49 +0200 Subject: [PATCH 01/27] dsa: add DSA implementation --- Cargo.lock | 139 ++++++++++++++++++++- Cargo.toml | 1 + dsa/.gitignore | 4 + dsa/Cargo.toml | 26 ++++ dsa/LICENSE-APACHE | 201 ++++++++++++++++++++++++++++++ dsa/LICENSE-MIT | 25 ++++ dsa/examples/export.rs | 25 ++++ dsa/examples/generate.rs | 8 ++ dsa/examples/sign.rs | 34 +++++ dsa/src/compat.rs | 144 +++++++++++++++++++++ dsa/src/components.rs | 103 +++++++++++++++ dsa/src/consts.rs | 24 ++++ dsa/src/generate/components.rs | 74 +++++++++++ dsa/src/generate/keypair.rs | 21 ++++ dsa/src/generate/mod.rs | 32 +++++ dsa/src/generate/secret_number.rs | 40 ++++++ dsa/src/lib.rs | 87 +++++++++++++ dsa/src/privatekey.rs | 199 +++++++++++++++++++++++++++++ dsa/src/publickey.rs | 168 +++++++++++++++++++++++++ dsa/src/signature.rs | 75 +++++++++++ 20 files changed, 1428 insertions(+), 2 deletions(-) create mode 100644 dsa/.gitignore create mode 100644 dsa/Cargo.toml create mode 100644 dsa/LICENSE-APACHE create mode 100644 dsa/LICENSE-MIT create mode 100644 dsa/examples/export.rs create mode 100644 dsa/examples/generate.rs create mode 100644 dsa/examples/sign.rs create mode 100644 dsa/src/compat.rs create mode 100644 dsa/src/components.rs create mode 100644 dsa/src/consts.rs create mode 100644 dsa/src/generate/components.rs create mode 100644 dsa/src/generate/keypair.rs create mode 100644 dsa/src/generate/mod.rs create mode 100644 dsa/src/generate/secret_number.rs create mode 100644 dsa/src/lib.rs create mode 100644 dsa/src/privatekey.rs create mode 100644 dsa/src/publickey.rs create mode 100644 dsa/src/signature.rs diff --git a/Cargo.lock b/Cargo.lock index 406e2a06..d4e9b6bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base16ct" version = "0.1.1" @@ -150,6 +156,22 @@ dependencies = [ "subtle", ] +[[package]] +name = "dsa" +version = "0.0.1" +dependencies = [ + "digest 0.10.3", + "num-bigint-dig", + "num-traits", + "opaque-debug", + "paste", + "pkcs8 0.9.0-pre.3", + "rand 0.8.5", + "sha-1", + "signature", + "zeroize", +] + [[package]] name = "ecdsa" version = "0.14.0-pre.5" @@ -195,7 +217,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519 1.4.0", - "rand", + "rand 0.7.3", "serde", "sha2 0.9.9", "zeroize", @@ -292,18 +314,87 @@ dependencies = [ "digest 0.10.3", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + [[package]] name = "libc" version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad5c14e80759d0939d013e6ca49930e59fc53dd8e5009132f76240c179380c09" +[[package]] +name = "libm" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" + +[[package]] +name = "num-bigint-dig" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566d173b2f9406afbc5510a90925d5a2cd80cae4605631f1212303df265de011" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "paste" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + [[package]] name = "pem-rfc7468" version = "0.3.1" @@ -384,11 +475,22 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha", + "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -399,6 +501,16 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -475,6 +587,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sha2" version = "0.9.9" @@ -509,6 +632,18 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" version = "0.5.4" diff --git a/Cargo.toml b/Cargo.toml index e5e84f16..528cd4ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "dsa", "ecdsa", "ed25519", "rfc6979" diff --git a/dsa/.gitignore b/dsa/.gitignore new file mode 100644 index 00000000..49068fad --- /dev/null +++ b/dsa/.gitignore @@ -0,0 +1,4 @@ +target/ +Cargo.lock +*.pem +*.der diff --git a/dsa/Cargo.toml b/dsa/Cargo.toml new file mode 100644 index 00000000..595fea4e --- /dev/null +++ b/dsa/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "dsa" +version = "0.0.1" +edition = "2021" +license = "Apache-2.0 OR MIT" +readme = "README.md" +categories = ["cryptography"] +keywords = ["crypto", "nist", "signature"] + +[dependencies] +digest = "0.10.3" +num-bigint = { package = "num-bigint-dig", version = "0.8.1", features = ["prime", "rand", "zeroize"] } +num-traits = "0.2.15" +opaque-debug = "0.3.0" +paste = "1.0.7" +pkcs8 = { version = "0.9.0-pre.3", features = ["std"] } +rand = "0.8.5" +signature = "1.5.0" +zeroize = "1.5.5" + +[features] +default = [] + +[dev-dependencies] +pkcs8 = { version = "0.9.0-pre.3", features = ["pem"] } +sha-1 = "0.10.0" diff --git a/dsa/LICENSE-APACHE b/dsa/LICENSE-APACHE new file mode 100644 index 00000000..c394d8ad --- /dev/null +++ b/dsa/LICENSE-APACHE @@ -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 2018-2022 RustCrypto Developers + +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. diff --git a/dsa/LICENSE-MIT b/dsa/LICENSE-MIT new file mode 100644 index 00000000..81a3d57a --- /dev/null +++ b/dsa/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018-2022 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. diff --git a/dsa/examples/export.rs b/dsa/examples/export.rs new file mode 100644 index 00000000..41a7154d --- /dev/null +++ b/dsa/examples/export.rs @@ -0,0 +1,25 @@ +use dsa::{consts::DSA_2048_256, Components, PrivateKey}; +use pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding}; +use std::{fs::File, io::Write}; + +fn main() { + let mut rng = rand::thread_rng(); + let components = Components::generate(&mut rng, DSA_2048_256); + let private_key = PrivateKey::generate(&mut rng, components); + let public_key = private_key.public_key(); + + let mut file = File::create("public.pem").unwrap(); + file.write_all( + public_key + .to_public_key_pem(LineEnding::LF) + .unwrap() + .as_bytes(), + ) + .unwrap(); + file.flush().unwrap(); + + let mut file = File::create("private.pem").unwrap(); + file.write_all(private_key.to_pkcs8_pem(LineEnding::LF).unwrap().as_bytes()) + .unwrap(); + file.flush().unwrap(); +} diff --git a/dsa/examples/generate.rs b/dsa/examples/generate.rs new file mode 100644 index 00000000..03b47d07 --- /dev/null +++ b/dsa/examples/generate.rs @@ -0,0 +1,8 @@ +use dsa::{consts::DSA_2048_256, Components, PrivateKey}; + +fn main() { + let mut rng = rand::thread_rng(); + let components = Components::generate(&mut rng, DSA_2048_256); + let private_key = PrivateKey::generate(&mut rng, components); + let _public_key = private_key.public_key(); +} diff --git a/dsa/examples/sign.rs b/dsa/examples/sign.rs new file mode 100644 index 00000000..631430c5 --- /dev/null +++ b/dsa/examples/sign.rs @@ -0,0 +1,34 @@ +use dsa::{consts::DSA_2048_256, Components, PrivateKey}; +use pkcs8::{der::Encode, EncodePrivateKey, EncodePublicKey, LineEnding}; +use sha1::Sha1; +use std::{fs::File, io::Write}; + +fn main() { + let mut rng = rand::thread_rng(); + let components = Components::generate(&mut rng, DSA_2048_256); + let private_key = PrivateKey::generate(&mut rng, components); + let public_key = private_key.public_key(); + let signature = private_key + .sign::<_, Sha1>(&mut rng, b"hello world") + .unwrap(); + + let mut file = File::create("public.pem").unwrap(); + file.write_all( + public_key + .to_public_key_pem(LineEnding::LF) + .unwrap() + .as_bytes(), + ) + .unwrap(); + file.flush().unwrap(); + + let mut file = File::create("signature.der").unwrap(); + file.write_all(signature.to_vec().unwrap().as_ref()) + .unwrap(); + file.flush().unwrap(); + + let mut file = File::create("private.pem").unwrap(); + file.write_all(private_key.to_pkcs8_pem(LineEnding::LF).unwrap().as_bytes()) + .unwrap(); + file.flush().unwrap(); +} diff --git a/dsa/src/compat.rs b/dsa/src/compat.rs new file mode 100644 index 00000000..1ec18ab5 --- /dev/null +++ b/dsa/src/compat.rs @@ -0,0 +1,144 @@ +//! +//! Types providing compatibility with the [signature](::signature) crate +//! + +use crate::{PrivateKey, PublicKey, Signature as InnerSignature}; +use core::fmt; +use digest::Digest; +use pkcs8::der::Encode; +use std::{marker::PhantomData, ops::Deref}; + +/// Container implementing the [Signature](::signature::Signature) trait +pub struct Signature { + /// Signature in DER form. This field gets generated whenever this container is constructed + /// + /// If the signature for whatever reason gets updated, this field needs to be updated as well. + /// Otherwise calls to the `as_ref` function will not reflect the changes + raw_signature: Vec, + + /// The actual signature used for the signing operations + signature: InnerSignature, +} + +impl Signature { + /// Deconstruct the compatibility container + pub fn into_inner(self) -> InnerSignature { + self.signature + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.raw_signature + } +} + +impl fmt::Debug for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.signature, f) + } +} + +impl Deref for Signature { + type Target = InnerSignature; + + fn deref(&self) -> &Self::Target { + &self.signature + } +} + +impl From for Signature { + fn from(signature: InnerSignature) -> Self { + Self { + raw_signature: signature.to_vec().expect("Failed to serialise signature"), + signature, + } + } +} + +impl signature::Signature for Signature { + fn from_bytes(bytes: &[u8]) -> Result { + InnerSignature::from_der(bytes) + .map(Into::into) + .map_err(signature::Error::from_source) + } +} + +/// Container implementing the [Signer](::signature::Signer) trait +pub struct Signer { + /// Phantom data for binding the digest generic parameter + _digest_phantom: PhantomData, + + /// Private key for signing messages + private_key: PrivateKey, +} + +impl Signer { + /// Deconstruct the compatibility container + pub fn into_inner(self) -> PrivateKey { + self.private_key + } +} + +impl From for Signer { + fn from(private_key: PrivateKey) -> Self { + Self { + _digest_phantom: PhantomData, + private_key, + } + } +} + +impl signature::Signer for Signer +where + D: Digest, +{ + fn try_sign(&self, msg: &[u8]) -> Result { + self.private_key + .sign::<_, D>(&mut rand::thread_rng(), msg) + .map(Into::into) + .ok_or_else(signature::Error::new) + } +} + +/// Container implementing the [Verifier](::signature::Verifier) trait +pub struct Verifier { + /// Phantom data for binding the digest generic parameter + _digest_phantom: PhantomData, + + /// Public key for verifying messages + public_key: PublicKey, +} + +impl Verifier { + /// Deconstruct the compatibility container + pub fn into_inner(self) -> PublicKey { + self.public_key + } +} + +impl From for Verifier { + fn from(public_key: PublicKey) -> Self { + Self { + _digest_phantom: PhantomData, + public_key, + } + } +} + +impl signature::Verifier for Verifier +where + D: Digest, +{ + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + if !self + .public_key + .verify::(msg, signature) + .ok_or_else(signature::Error::new)? + { + return Err(signature::Error::new()); + } + + Ok(()) + } +} diff --git a/dsa/src/components.rs b/dsa/src/components.rs new file mode 100644 index 00000000..a2718e5c --- /dev/null +++ b/dsa/src/components.rs @@ -0,0 +1,103 @@ +//! +//! Module containing the definition of the common components container +//! + +use num_bigint::BigUint; +use num_traits::One; +use pkcs8::der::{self, asn1::UIntRef, DecodeValue, Encode, Header, Reader, Sequence}; +use rand::{CryptoRng, RngCore}; + +/// The common components of an DSA keypair +/// +/// (the prime p, quotient q and generator g) +#[derive(Clone, PartialEq, PartialOrd)] +#[must_use] +pub struct Components { + /// Prime p + p: BigUint, + + /// Quotient q + q: BigUint, + + /// Generator g + g: BigUint, +} + +opaque_debug::implement!(Components); + +impl Components { + /// Construct the common components container from its inner values (p, q and g) + /// + /// These values are not getting verified for validity + pub const fn from_components(p: BigUint, q: BigUint, g: BigUint) -> Self { + Self { p, q, g } + } + + /// Generate a new pair of common components + /// + /// Please only use the parameter sizes defined by NIST. + /// We allow you to plug in any numbers you want but just because you can doesn't mean you should! + pub fn generate(rng: &mut R, size_param: (u32, u32)) -> Self { + let (p, q, g) = crate::generate::common_components(rng, size_param); + Self::from_components(p, q, g) + } + + /// DSA prime p + #[must_use] + pub const fn p(&self) -> &BigUint { + &self.p + } + + /// DSA quotient q + #[must_use] + pub const fn q(&self) -> &BigUint { + &self.q + } + + /// DSA generator g + #[must_use] + pub const fn g(&self) -> &BigUint { + &self.g + } + + /// Check whether the components are valid + #[must_use] + pub fn is_valid(&self) -> bool { + if *self.p() <= BigUint::one() || *self.q() <= BigUint::one() { + return false; + } + + true + } +} + +impl<'a> DecodeValue<'a> for Components { + fn decode_value>(reader: &mut R, _header: Header) -> der::Result { + let p = reader.decode::>()?; + let q = reader.decode::>()?; + let g = reader.decode::>()?; + + let p = BigUint::from_bytes_be(p.as_bytes()); + let q = BigUint::from_bytes_be(q.as_bytes()); + let g = BigUint::from_bytes_be(g.as_bytes()); + + Ok(Self::from_components(p, q, g)) + } +} + +impl<'a> Sequence<'a> for Components { + fn fields(&self, encoder: F) -> der::Result + where + F: FnOnce(&[&dyn Encode]) -> der::Result, + { + let p_bytes = self.p.to_bytes_be(); + let q_bytes = self.q.to_bytes_be(); + let g_bytes = self.g.to_bytes_be(); + + let p = UIntRef::new(&p_bytes)?; + let q = UIntRef::new(&q_bytes)?; + let g = UIntRef::new(&g_bytes)?; + + encoder(&[&p, &q, &g]) + } +} diff --git a/dsa/src/consts.rs b/dsa/src/consts.rs new file mode 100644 index 00000000..3c3efb9c --- /dev/null +++ b/dsa/src/consts.rs @@ -0,0 +1,24 @@ +//! +//! DSA-related constants like parameter sizes +//! + +macro_rules! define_param_size { + ($l:literal, $n:literal) => { + ::paste::paste! { + #[doc = "DSA paramter size constant; L = " $l ", N = " $n] + pub const []: (u32, u32) = ($l, $n); + } + }; + (deprecated: $l:literal, $n:literal) => { + ::paste::paste! { + #[deprecated(note="This size constant has a security strength of under 112 bits per SP 800-57 Part 1 Rev. 5")] + #[doc = "DSA paramter size constant; L = " $l ", N = " $n] + pub const []: (u32, u32) = ($l, $n); + } + }; +} + +define_param_size!(deprecated: 1024, 160); +define_param_size!(2048, 224); +define_param_size!(2048, 256); +define_param_size!(3072, 256); diff --git a/dsa/src/generate/components.rs b/dsa/src/generate/components.rs new file mode 100644 index 00000000..c0f2a13a --- /dev/null +++ b/dsa/src/generate/components.rs @@ -0,0 +1,74 @@ +//! +//! Generate DSA key components +//! + +use crate::{ + generate::{calculate_bounds, generate_prime}, + two, Components, +}; +use num_bigint::{prime::probably_prime, BigUint, RandBigInt}; +use num_traits::One; +use rand::{CryptoRng, RngCore}; + +/// Numbers of miller-rabin rounds performed to determine primality +const MR_ROUNDS: usize = 64; + +/// Generate the common components p, q, and g +/// +/// # Returns +/// +/// Tuple of three `BigUint`s. Ordered like this `(p, q, g)` +pub fn common(rng: &mut R, (l, n): (u32, u32)) -> (BigUint, BigUint, BigUint) +where + R: CryptoRng + RngCore + ?Sized, +{ + // Calculate the lower and upper bounds of p and q + let (p_min, p_max) = calculate_bounds(l); + let (q_min, q_max) = calculate_bounds(n); + + let (p, q) = 'gen_pq: loop { + let q = generate_prime(n as usize, rng); + if q < q_min || q > q_max { + continue; + } + + // Attempt to find a prime p which has a subgroup of the order q + for _ in 0..4096 { + let m = 'gen_m: loop { + let m = rng.gen_biguint(l as usize); + if m > p_min && m < p_max { + break 'gen_m m; + } + }; + let mr = &m % (two() * &q); + let p = m - mr + BigUint::one(); + + if probably_prime(&p, MR_ROUNDS) { + break 'gen_pq (p, q); + } + } + }; + + // Generate g using the unverifiable method as defined by Appendix A.2.1 + let e = (&p - BigUint::one()) / &q; + let mut h = BigUint::one(); + let g = loop { + let g = h.modpow(&e, &p); + if !g.is_one() { + break g; + } + + h += BigUint::one(); + }; + + (p, q, g) +} + +/// Calculate the public component from the common components and the private component +#[inline] +pub fn public(components: &Components, x: &BigUint) -> BigUint { + let p = components.p(); + let g = components.g(); + + g.modpow(x, p) +} diff --git a/dsa/src/generate/keypair.rs b/dsa/src/generate/keypair.rs new file mode 100644 index 00000000..75875451 --- /dev/null +++ b/dsa/src/generate/keypair.rs @@ -0,0 +1,21 @@ +//! +//! Generate a DSA keypair +//! + +use crate::{generate::components, Components, PrivateKey, PublicKey}; +use num_bigint::{BigUint, RandBigInt}; +use num_traits::One; +use rand::{CryptoRng, RngCore}; + +/// Generate a new keypair +#[inline] +pub fn keypair(rng: &mut R, components: Components) -> PrivateKey +where + R: CryptoRng + RngCore + ?Sized, +{ + let x = rng.gen_biguint_range(&BigUint::one(), components.q()); + let y = components::public(&components, &x); + + let public_key = PublicKey::from_components(components, y); + PrivateKey::from_components(public_key, x) +} diff --git a/dsa/src/generate/mod.rs b/dsa/src/generate/mod.rs new file mode 100644 index 00000000..354096a0 --- /dev/null +++ b/dsa/src/generate/mod.rs @@ -0,0 +1,32 @@ +use crate::two; +use num_bigint::{BigUint, RandPrime}; +use num_traits::Pow; +use rand::{CryptoRng, RngCore}; + +mod components; +mod keypair; +mod secret_number; + +pub use self::components::{common as common_components, public as public_component}; +pub use self::keypair::keypair; +pub use self::secret_number::secret_number; + +/// Calculate the upper and lower bounds for generating values like p or q +#[inline] +fn calculate_bounds(size: u32) -> (BigUint, BigUint) { + let lower = two().pow(size - 1); + let upper = two().pow(size); + + (lower, upper) +} + +/// Generate a prime number using a cryptographically secure pseudo-random number generator +/// +/// This wrapper function mainly exists to enforce the [`CryptoRng`](rand::CryptoRng) requirement (I might otherwise forget it) +#[inline] +fn generate_prime(bit_length: usize, rng: &mut R) -> BigUint +where + R: CryptoRng + RngCore + ?Sized, +{ + rng.gen_prime(bit_length) +} diff --git a/dsa/src/generate/secret_number.rs b/dsa/src/generate/secret_number.rs new file mode 100644 index 00000000..9388f70f --- /dev/null +++ b/dsa/src/generate/secret_number.rs @@ -0,0 +1,40 @@ +//! +//! Generate a per-message secret number +//! + +use crate::Components; +use num_bigint::{BigUint, ModInverse, RandBigInt}; +use num_traits::{One, Zero}; +use rand::{CryptoRng, RngCore}; + +/// Generate a per-message secret number k according to Appendix B.2.1 +/// +/// # Returns +/// +/// Secret number k and its modular multiplicative inverse with q +#[inline] +pub fn secret_number(rng: &mut R, components: &Components) -> Option<(BigUint, BigUint)> +where + R: CryptoRng + RngCore + ?Sized, +{ + let q = components.q(); + let n = q.bits(); + + // Attempt to try a fitting secret number + // Give up after 4096 tries + for _ in 0..4096 { + let c = rng.gen_biguint(n + 64); + let k = (c % (q - BigUint::one())) + BigUint::one(); + + if let Some(inv_k) = (&k).mod_inverse(q) { + let inv_k = inv_k.to_biguint().unwrap(); + + // `k` and `k^-1` both have to be in the range `[1, q-1]` + if (inv_k > BigUint::zero() && &inv_k < q) && (k > BigUint::zero() && &k < q) { + return Some((k, inv_k)); + } + } + } + + None +} diff --git a/dsa/src/lib.rs b/dsa/src/lib.rs new file mode 100644 index 00000000..d0121ce7 --- /dev/null +++ b/dsa/src/lib.rs @@ -0,0 +1,87 @@ +//! +//! DSA implementation in pure Rust +//! +//! # Disclaimer +//! +//! 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. +//! +//! This software has **NOT** been audited and therefore most likely contains security issues! +//! +//! **USE AT YOUR OWN RISK!** +//! +//! ## Implementation progress +//! +//! - [x] Generate components +//! - [x] Generate keypair +//! - [x] Import keys +//! - [x] Export keys +//! - [x] Sign data +//! - [x] Verify signatures +//! - [ ] Test vectors +//! +//! ## Example +//! +//! Generate a DSA keypair +//! +//! ``` +//! # use dsa::{consts::DSA_2048_256, Components, PrivateKey}; +//! let mut csprng = rand::thread_rng(); +//! let components = Components::generate(&mut csprng, DSA_2048_256); +//! let private_key = PrivateKey::generate(&mut csprng, components); +//! let public_key = private_key.public_key(); +//! ``` +//! +//! Create keypair from existing components +//! +//! ``` +//! # use dsa::{Components, PrivateKey, PublicKey}; +//! # use num_bigint::BigUint; +//! # use num_traits::One; +//! # let read_common_parameters = || (BigUint::one(), BigUint::one(), BigUint::one()); +//! # let read_public_component = || BigUint::one(); +//! # let read_private_component = || BigUint::one(); +//! let (p, q, g) = read_common_parameters(); +//! let components = Components::from_components(p, q, g); +//! +//! let x = read_public_component(); +//! let public_key = PublicKey::from_components(components, x); +//! +//! let y = read_private_component(); +//! let private_key = PrivateKey::from_components(public_key, y); +//! ``` +//! + +#![forbid(missing_docs, unsafe_code)] +#![deny(rust_2018_idioms)] + +/// DSA object identifier as defined by RFC-3279, section 2.3.2 +const DSA_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10040.4.1"); + +pub use self::components::Components; +pub use self::privatekey::PrivateKey; +pub use self::publickey::PublicKey; +pub use self::signature::Signature; + +// Re-export the types needed for de-/serialising keys to DER and PEM +pub use pkcs8; + +pub mod compat; +pub mod consts; + +use num_bigint::BigUint; +use pkcs8::spki::ObjectIdentifier; + +mod components; +mod generate; +mod privatekey; +mod publickey; +mod signature; + +/// Returns a `BigUint` with the value 2 +#[inline] +fn two() -> BigUint { + BigUint::from(2_u8) +} diff --git a/dsa/src/privatekey.rs b/dsa/src/privatekey.rs new file mode 100644 index 00000000..add96f8b --- /dev/null +++ b/dsa/src/privatekey.rs @@ -0,0 +1,199 @@ +//! +//! Module containing the definition of the private key container +//! + +use crate::{Components, PublicKey, Signature, DSA_OID}; +use digest::Digest; +use num_bigint::BigUint; +use pkcs8::{ + der::{asn1::UIntRef, AnyRef, Decode, Encode}, + AlgorithmIdentifier, DecodePrivateKey, EncodePrivateKey, PrivateKeyInfo, SecretDocument, +}; +use rand::{CryptoRng, RngCore}; +use std::cmp::min; +use zeroize::Zeroizing; + +/// DSA private key +#[derive(Clone, PartialEq)] +#[must_use] +pub struct PrivateKey { + /// Public key + public_key: PublicKey, + + /// Private component x + x: Zeroizing, +} + +opaque_debug::implement!(PrivateKey); + +impl PrivateKey { + /// Construct a new private key from the public key and private component + /// + /// These values are not getting verified for validity + pub fn from_components(public_key: PublicKey, x: BigUint) -> Self { + Self { + public_key, + x: Zeroizing::new(x), + } + } + + /// Generate a new DSA keypair + /// Generate a new DSA keypair using the common components + #[inline] + pub fn generate( + rng: &mut R, + components: Components, + ) -> PrivateKey { + crate::generate::keypair(rng, components) + } + + /// DSA public key + pub const fn public_key(&self) -> &PublicKey { + &self.public_key + } + + /// DSA private component + #[must_use] + pub fn x(&self) -> &BigUint { + &self.x + } + + /// Check whether the private key is valid + #[must_use] + pub fn is_valid(&self) -> bool { + self.public_key().components().is_valid() + } + + /// Sign data with the private key + pub fn sign( + &self, + rng: &mut R, + data: &[u8], + ) -> Option { + // Refuse to sign with an invalid key + if !self.is_valid() { + return None; + } + + let components = self.public_key().components(); + let (k, inv_k) = crate::generate::secret_number(rng, components)?; + let (p, q, g) = (components.p(), components.q(), components.g()); + let hash = D::digest(data); + let x = self.x(); + + let r = g.modpow(&k, p) % q; + + let n = (q.bits() / 8) as usize; + let block_size = ::output_size(); + + let z_len = min(n, block_size); + let z = BigUint::from_bytes_be(&hash[..z_len]); + + let s = (inv_k * (z + x * &r)) % q; + + Some(Signature::new(r, s)) + } +} + +impl EncodePrivateKey for PrivateKey { + fn to_pkcs8_der(&self) -> pkcs8::Result { + let parameters = self.public_key().components().to_vec()?; + let parameters = AnyRef::from_der(¶meters)?; + let algorithm = AlgorithmIdentifier { + oid: DSA_OID, + parameters: Some(parameters), + }; + + let x_bytes = self.x.to_bytes_be(); + let x = UIntRef::new(&x_bytes)?; + let private_key = x.to_vec()?; + + let private_key_info = PrivateKeyInfo::new(algorithm, &private_key); + private_key_info.try_into() + } +} + +impl<'a> TryFrom> for PrivateKey { + type Error = pkcs8::Error; + + fn try_from(value: PrivateKeyInfo<'a>) -> Result { + value.algorithm.assert_algorithm_oid(DSA_OID)?; + + let parameters = value.algorithm.parameters_any()?; + let components = parameters.decode_into()?; + + let x = UIntRef::from_der(value.private_key)?; + let x = BigUint::from_bytes_be(x.as_bytes()); + + let y = if let Some(y_bytes) = value.public_key { + let y = UIntRef::from_der(y_bytes)?; + BigUint::from_bytes_be(y.as_bytes()) + } else { + crate::generate::public_component(&components, &x) + }; + + let public_key = PublicKey::from_components(components, y); + Ok(PrivateKey::from_components(public_key, x)) + } +} + +impl DecodePrivateKey for PrivateKey {} + +#[cfg(test)] +mod test { + // We abused the deprecated attribute for unsecure key sizes + // But we want to use those small key sizes for fast tests + #![allow(deprecated)] + + use crate::{consts::DSA_1024_160, Components, PrivateKey}; + use num_bigint::BigUint; + use num_traits::Zero; + use pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}; + use sha1::Sha1; + + fn generate_keypair() -> PrivateKey { + let mut rng = rand::thread_rng(); + let components = Components::generate(&mut rng, DSA_1024_160); + PrivateKey::generate(&mut rng, components) + } + + #[test] + fn encode_decode_private_key() { + let private_key = generate_keypair(); + let encoded_private_key = private_key.to_pkcs8_pem(LineEnding::LF).unwrap(); + let decoded_private_key = PrivateKey::from_pkcs8_pem(&encoded_private_key).unwrap(); + + assert_eq!(private_key, decoded_private_key); + } + + #[test] + fn sign_and_verify() { + const DATA: &[u8] = b"SIGN AND VERIFY THOSE BYTES"; + + let private_key = generate_keypair(); + let public_key = private_key.public_key(); + + let signature = private_key + .sign::<_, Sha1>(&mut rand::thread_rng(), DATA) + .expect("Failed to sign"); + assert!(public_key + .verify::(DATA, &signature) + .expect("Failed to verify")); + } + + #[test] + fn verify_validity() { + let private_key = generate_keypair(); + let components = private_key.public_key().components(); + + assert!( + BigUint::zero() < *private_key.x() && private_key.x() < components.q(), + "Requirement 0 Self { + Self { components, y } + } + + /// DSA common components + pub const fn components(&self) -> &Components { + &self.components + } + + /// DSA public component + #[must_use] + pub const fn y(&self) -> &BigUint { + &self.y + } + + /// Check whether the public key is valid + #[must_use] + pub fn is_valid(&self) -> bool { + let components = self.components(); + if !components.is_valid() { + return false; + } + + self.y().modpow(components.q(), components.p()) == BigUint::one() + } + + /// Verify if the signature matches the provided hash + #[must_use] + pub fn verify(&self, data: &[u8], signature: &Signature) -> Option + where + D: Digest, + { + // Refuse to verify with an invalid key + if !self.is_valid() { + return None; + } + + let components = self.components(); + let (p, q, g) = (components.p(), components.q(), components.g()); + let (r, s) = (signature.r(), signature.s()); + let y = self.y(); + let hash = D::digest(data); + + let w = s.mod_inverse(q)?.to_biguint().unwrap(); + + let n = (q.bits() / 8) as usize; + let block_size = ::output_size(); + + let z_len = min(n, block_size); + let z = BigUint::from_bytes_be(&hash[..z_len]); + + let u1 = (&z * &w) % q; + let u2 = (r * &w) % q; + let v = (g.modpow(&u1, p) * y.modpow(&u2, p) % p) % q; + + Some(v == *r) + } +} + +impl EncodePublicKey for PublicKey { + fn to_public_key_der(&self) -> spki::Result { + let parameters = self.components.to_vec()?; + let parameters = AnyRef::from_der(¶meters)?; + let algorithm = AlgorithmIdentifier { + oid: DSA_OID, + parameters: Some(parameters), + }; + + let y_bytes = self.y.to_bytes_be(); + let y = UIntRef::new(&y_bytes)?; + let public_key = y.to_vec()?; + + let public_key_info = SubjectPublicKeyInfo { + algorithm, + subject_public_key: &public_key, + }; + + public_key_info.try_into() + } +} + +impl<'a> TryFrom> for PublicKey { + type Error = spki::Error; + + fn try_from(value: SubjectPublicKeyInfo<'a>) -> Result { + value.algorithm.assert_algorithm_oid(DSA_OID)?; + + let parameters = value.algorithm.parameters_any()?; + let components = parameters.decode_into()?; + + let y = UIntRef::from_der(value.subject_public_key)?; + let y = BigUint::from_bytes_be(y.as_bytes()); + + Ok(Self::from_components(components, y)) + } +} + +impl DecodePublicKey for PublicKey {} + +#[cfg(test)] +mod test { + // We abused the deprecated attribute for unsecure key sizes + // But we want to use those small key sizes for fast tests + #![allow(deprecated)] + + use crate::{consts::DSA_1024_160, Components, PrivateKey, PublicKey}; + use num_bigint::BigUint; + use num_traits::One; + use pkcs8::{DecodePublicKey, EncodePublicKey, LineEnding}; + + fn generate_public_key() -> PublicKey { + let mut rng = rand::thread_rng(); + let components = Components::generate(&mut rng, DSA_1024_160); + let private_key = PrivateKey::generate(&mut rng, components); + + private_key.public_key().clone() + } + + #[test] + fn encode_decode_public_key() { + let public_key = generate_public_key(); + let encoded_public_key = public_key.to_public_key_pem(LineEnding::LF).unwrap(); + let decoded_public_key = PublicKey::from_public_key_pem(&encoded_public_key).unwrap(); + + assert_eq!(public_key, decoded_public_key); + } + + #[test] + fn validate_public_key() { + let public_key = generate_public_key(); + let p = public_key.components().p(); + let q = public_key.components().q(); + + // Taken from the parameter validation from bouncy castle + assert_eq!(public_key.y().modpow(q, p), BigUint::one()); + } +} diff --git a/dsa/src/signature.rs b/dsa/src/signature.rs new file mode 100644 index 00000000..c0d2c387 --- /dev/null +++ b/dsa/src/signature.rs @@ -0,0 +1,75 @@ +//! +//! Module containing the definition of the signature container +//! + +use num_bigint::BigUint; +use pkcs8::der::{self, asn1::UIntRef, Decode, Decoder, Reader, Sequence}; + +/// Container of the DSA signature +#[derive(Clone, PartialEq, PartialOrd)] +#[must_use] +pub struct Signature { + /// Signature part r + r: BigUint, + + /// Signature part s + s: BigUint, +} + +opaque_debug::implement!(Signature); + +impl Signature { + /// Create a new signature container from its components + pub fn new(r: BigUint, s: BigUint) -> Self { + Self { r, s } + } + + /// Decode a signature from its DER representation + /// + /// # Errors + /// + /// See the [`der` errors](pkcs8::der::Error) + pub fn from_der(data: &[u8]) -> der::Result { + let mut reader = Decoder::new(data)?; + reader.decode() + } + + /// Signature part r + #[must_use] + pub fn r(&self) -> &BigUint { + &self.r + } + + /// Signature part s + #[must_use] + pub fn s(&self) -> &BigUint { + &self.s + } +} + +impl<'a> Decode<'a> for Signature { + fn decode>(reader: &mut R) -> der::Result { + let r = reader.decode::>()?; + let s = reader.decode::>()?; + + let r = BigUint::from_bytes_be(r.as_bytes()); + let s = BigUint::from_bytes_be(s.as_bytes()); + + Ok(Self::new(r, s)) + } +} + +impl<'a> Sequence<'a> for Signature { + fn fields(&self, encoder: F) -> der::Result + where + F: FnOnce(&[&dyn der::Encode]) -> der::Result, + { + let r_bytes = self.r.to_bytes_be(); + let s_bytes = self.s.to_bytes_be(); + + let r = UIntRef::new(&r_bytes)?; + let s = UIntRef::new(&s_bytes)?; + + encoder(&[&r, &s]) + } +} From ef94e41d4ff6f84c0c715c156e4a427fe989271f Mon Sep 17 00:00:00 2001 From: aumetra Date: Mon, 9 May 2022 02:46:29 +0200 Subject: [PATCH 02/27] dsa: Add README --- dsa/README.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 dsa/README.md diff --git a/dsa/README.md b/dsa/README.md new file mode 100644 index 00000000..aa996888 --- /dev/null +++ b/dsa/README.md @@ -0,0 +1,52 @@ +# dsa + + +DSA implementation in pure Rust + +## Disclaimer + +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. + +This software has **NOT** been audited and therefore most likely contains security issues! + +**USE AT YOUR OWN RISK!** + +### Implementation progress + +- [x] Generate components +- [x] Generate keypair +- [x] Import keys +- [x] Export keys +- [x] Sign data +- [x] Verify signatures +- [ ] Test vectors + +### Example + +Generate a DSA keypair + +```rust +let mut csprng = rand::thread_rng(); +let components = Components::generate(&mut csprng, DSA_2048_256); +let private_key = PrivateKey::generate(&mut csprng, components); +let public_key = private_key.public_key(); +``` + +Create keypair from existing components + +```rust +let (p, q, g) = read_common_parameters(); +let components = Components::from_components(p, q, g); + +let x = read_public_component(); +let public_key = PublicKey::from_components(components, x); + +let y = read_private_component(); +let private_key = PrivateKey::from_components(public_key, y); +``` + + +License: Apache-2.0 OR MIT From 70f88aed4e1ad9ed7c40098fcd8ce594db2bb3e1 Mon Sep 17 00:00:00 2001 From: aumetra Date: Mon, 9 May 2022 02:53:11 +0200 Subject: [PATCH 03/27] dsa: Add workflow, add to README --- .github/workflows/dsa.yml | 57 +++++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 58 insertions(+) create mode 100644 .github/workflows/dsa.yml diff --git a/.github/workflows/dsa.yml b/.github/workflows/dsa.yml new file mode 100644 index 00000000..7ee2873c --- /dev/null +++ b/.github/workflows/dsa.yml @@ -0,0 +1,57 @@ +name: dsa +on: + pull_request: + paths: + - "dsa/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: dsa + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + toolchain: + - 1.56.0 # MSRV + - stable + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + target: ${{ matrix.target }} + toolchain: ${{ matrix.toolchain }} + override: true + - run: cargo build --target ${{ matrix.target }} --release --no-default-features + + test: + strategy: + matrix: + platform: + - ubuntu-latest + - macos-latest + - windows-latest + toolchain: + - 1.56.0 # MSRV + - stable + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + override: true + - run: cargo test --release --no-default-features + - run: cargo test --release + - run: cargo test --release --all-features \ No newline at end of file diff --git a/README.md b/README.md index e6216fc3..ee9e1daa 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ and can be easily used for bare-metal or lightweight WebAssembly programming. | Name | Algorithm | Crates.io | Documentation | Build | |-------------|-----------|-----------|---------------|-------| +| [`dsa`] | [DSA](https://en.wikipedia.org/wiki/Digital_Signature_Algorithm) | [![crates.io](https://img.shields.io/crates/v/dsa.svg)](https://crates.io/crates/dsa) | [![Documentation](https://docs.rs/dsa/badge.svg)](https://docs.rs/dsa) | [![dsa build](https://github.com/RustCrypto/signatures/workflows/dsa/badge.svg?branch=master&event=push)](https://github.com/RustCrypto/signatures/actions?query=workflow%3Adsa) | [`ecdsa`] | [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) | [![crates.io](https://img.shields.io/crates/v/ecdsa.svg)](https://crates.io/crates/ecdsa) | [![Documentation](https://docs.rs/ecdsa/badge.svg)](https://docs.rs/ecdsa) | [![ecdsa build](https://github.com/RustCrypto/signatures/workflows/ecdsa/badge.svg?branch=master&event=push)](https://github.com/RustCrypto/signatures/actions?query=workflow%3Aecdsa) | | [`ed25519`] | [Ed25519](https://en.wikipedia.org/wiki/EdDSA) | [![crates.io](https://img.shields.io/crates/v/ed25519.svg)](https://crates.io/crates/ed25519) | [![Documentation](https://docs.rs/ed25519/badge.svg)](https://docs.rs/ed25519) | [![ed25519 build](https://github.com/RustCrypto/signatures/workflows/ed25519/badge.svg?branch=master&event=push)](https://github.com/RustCrypto/signatures/actions?query=workflow%3Aed25519) | [`rfc6979`] | [RFC6979](https://datatracker.ietf.org/doc/html/rfc6979) | [![crates.io](https://img.shields.io/crates/v/rfc6979.svg)](https://crates.io/crates/rfc6979) | [![Documentation](https://docs.rs/rfc6979/badge.svg)](https://docs.rs/rfc6979) | [![rfc6979 build](https://github.com/RustCrypto/signatures/actions/workflows/rfc6979.yml/badge.svg)](https://github.com/RustCrypto/signatures/actions/workflows/rfc6979.yml) From 955f25f5cfea8e451486479f6bc8dd3132ae85fc Mon Sep 17 00:00:00 2001 From: aumetra Date: Mon, 9 May 2022 09:52:50 +0200 Subject: [PATCH 04/27] dsa: Update to pkcs8 0.9.0 --- Cargo.lock | 2 +- dsa/Cargo.toml | 4 ++-- dsa/src/signature.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f914076e..c392fdcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,7 +165,7 @@ dependencies = [ "num-traits", "opaque-debug", "paste", - "pkcs8 0.9.0-pre.3", + "pkcs8 0.9.0", "rand 0.8.5", "sha-1", "signature", diff --git a/dsa/Cargo.toml b/dsa/Cargo.toml index 595fea4e..bba0d2d0 100644 --- a/dsa/Cargo.toml +++ b/dsa/Cargo.toml @@ -13,7 +13,7 @@ num-bigint = { package = "num-bigint-dig", version = "0.8.1", features = ["prime num-traits = "0.2.15" opaque-debug = "0.3.0" paste = "1.0.7" -pkcs8 = { version = "0.9.0-pre.3", features = ["std"] } +pkcs8 = { version = "0.9.0", features = ["std"] } rand = "0.8.5" signature = "1.5.0" zeroize = "1.5.5" @@ -22,5 +22,5 @@ zeroize = "1.5.5" default = [] [dev-dependencies] -pkcs8 = { version = "0.9.0-pre.3", features = ["pem"] } +pkcs8 = { version = "0.9.0", features = ["pem"] } sha-1 = "0.10.0" diff --git a/dsa/src/signature.rs b/dsa/src/signature.rs index c0d2c387..c82eb441 100644 --- a/dsa/src/signature.rs +++ b/dsa/src/signature.rs @@ -3,7 +3,7 @@ //! use num_bigint::BigUint; -use pkcs8::der::{self, asn1::UIntRef, Decode, Decoder, Reader, Sequence}; +use pkcs8::der::{self, asn1::UIntRef, Decode, Reader, Sequence, SliceReader}; /// Container of the DSA signature #[derive(Clone, PartialEq, PartialOrd)] @@ -30,7 +30,7 @@ impl Signature { /// /// See the [`der` errors](pkcs8::der::Error) pub fn from_der(data: &[u8]) -> der::Result { - let mut reader = Decoder::new(data)?; + let mut reader = SliceReader::new(data)?; reader.decode() } From 9e2da390dd4ba009b14d9d526188eca6fddd669a Mon Sep 17 00:00:00 2001 From: aumetra Date: Mon, 9 May 2022 10:18:29 +0200 Subject: [PATCH 05/27] dsa: Make signature conversion fallible, add example --- dsa/src/compat.rs | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/dsa/src/compat.rs b/dsa/src/compat.rs index 1ec18ab5..5649e069 100644 --- a/dsa/src/compat.rs +++ b/dsa/src/compat.rs @@ -1,11 +1,32 @@ //! //! Types providing compatibility with the [signature](::signature) crate //! +//! Example: +//! +//! ``` +//! # use dsa::{Components, PrivateKey, compat::{Signer, Verifier}, consts::DSA_1024_160}; +//! # use signature::{Signer as _, Verifier as _}; +//! # use sha1::Sha1; +//! # fn generate() -> PrivateKey { +//! # let mut rng = rand::thread_rng(); +//! # let components = Components::generate(&mut rng, DSA_1024_160); +//! # PrivateKey::generate(&mut rng, components) +//! # } +//! let private_key = generate(); +//! let public_key = private_key.public_key().clone(); +//! let signer: Signer = private_key.into(); +//! let verifier: Verifier = public_key.into(); +//! +//! let signature = signer.sign(b"hello world"); +//! assert!(verifier.verify(b"hello world", &signature).is_ok()); +//! assert!(verifier.verify(b"not hello world", &signature).is_err()); +//! ``` +//! use crate::{PrivateKey, PublicKey, Signature as InnerSignature}; use core::fmt; use digest::Digest; -use pkcs8::der::Encode; +use pkcs8::der::{self, Encode}; use std::{marker::PhantomData, ops::Deref}; /// Container implementing the [Signature](::signature::Signature) trait @@ -47,19 +68,22 @@ impl Deref for Signature { } } -impl From for Signature { - fn from(signature: InnerSignature) -> Self { - Self { - raw_signature: signature.to_vec().expect("Failed to serialise signature"), +impl TryFrom for Signature { + type Error = der::Error; + + fn try_from(signature: InnerSignature) -> Result { + Ok(Self { + raw_signature: signature.to_vec()?, signature, - } + }) } } impl signature::Signature for Signature { fn from_bytes(bytes: &[u8]) -> Result { InnerSignature::from_der(bytes) - .map(Into::into) + .map(TryInto::try_into) + .map_err(signature::Error::from_source)? .map_err(signature::Error::from_source) } } @@ -96,8 +120,9 @@ where fn try_sign(&self, msg: &[u8]) -> Result { self.private_key .sign::<_, D>(&mut rand::thread_rng(), msg) - .map(Into::into) - .ok_or_else(signature::Error::new) + .map(TryInto::try_into) + .ok_or_else(signature::Error::new)? + .map_err(signature::Error::from_source) } } From e71fc2678f7c6f76f108c97248682e1849841787 Mon Sep 17 00:00:00 2001 From: aumetra Date: Mon, 9 May 2022 10:44:45 +0200 Subject: [PATCH 06/27] dsa: Add signature feature, disable default features --- Cargo.lock | 1 - dsa/Cargo.toml | 19 +++++++++++-------- dsa/src/lib.rs | 1 + 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c392fdcc..0ffaf4ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,7 +348,6 @@ dependencies = [ "num-iter", "num-traits", "rand 0.8.5", - "serde", "smallvec", "zeroize", ] diff --git a/dsa/Cargo.toml b/dsa/Cargo.toml index bba0d2d0..1cd19ab6 100644 --- a/dsa/Cargo.toml +++ b/dsa/Cargo.toml @@ -6,21 +6,24 @@ license = "Apache-2.0 OR MIT" readme = "README.md" categories = ["cryptography"] keywords = ["crypto", "nist", "signature"] +rust-version = "1.60" # MSRV because of the feature syntax [dependencies] digest = "0.10.3" -num-bigint = { package = "num-bigint-dig", version = "0.8.1", features = ["prime", "rand", "zeroize"] } -num-traits = "0.2.15" +num-bigint = { package = "num-bigint-dig", version = "0.8.1", default-features = false, features = ["prime", "rand", "zeroize"] } +num-traits = { version = "0.2.15", default-features = false } opaque-debug = "0.3.0" paste = "1.0.7" -pkcs8 = { version = "0.9.0", features = ["std"] } -rand = "0.8.5" -signature = "1.5.0" -zeroize = "1.5.5" +pkcs8 = { version = "0.9.0", default-features = false, features = ["std"] } +rand = { version = "0.8.5", default-features = false } +signature = { version = "1.5.0", optional = true } +zeroize = { version = "1.5.5", default-features = false } [features] -default = [] +default = ["signature"] +signature = ["dep:signature", "rand/std", "rand/std_rng"] [dev-dependencies] -pkcs8 = { version = "0.9.0", features = ["pem"] } +pkcs8 = { version = "0.9.0", default-features = false, features = ["pem"] } +rand = "0.8.5" sha-1 = "0.10.0" diff --git a/dsa/src/lib.rs b/dsa/src/lib.rs index d0121ce7..2f32f4ac 100644 --- a/dsa/src/lib.rs +++ b/dsa/src/lib.rs @@ -68,6 +68,7 @@ pub use self::signature::Signature; // Re-export the types needed for de-/serialising keys to DER and PEM pub use pkcs8; +#[cfg(feature = "signature")] pub mod compat; pub mod consts; From 3d7cd4a9a487776e42b9192fa1c423700ea4ee67 Mon Sep 17 00:00:00 2001 From: aumetra Date: Mon, 9 May 2022 10:46:47 +0200 Subject: [PATCH 07/27] dsa: Add feature documentation --- dsa/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/dsa/src/lib.rs b/dsa/src/lib.rs index 2f32f4ac..34949997 100644 --- a/dsa/src/lib.rs +++ b/dsa/src/lib.rs @@ -69,6 +69,7 @@ pub use self::signature::Signature; pub use pkcs8; #[cfg(feature = "signature")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "signature")))] pub mod compat; pub mod consts; From 84715de375e1022c02b67cad089acd8f76f150f4 Mon Sep 17 00:00:00 2001 From: aumetra Date: Mon, 9 May 2022 10:50:38 +0200 Subject: [PATCH 08/27] dsa: Adjust MSRV in workflow --- .github/workflows/dsa.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dsa.yml b/.github/workflows/dsa.yml index 7ee2873c..d87a0d3d 100644 --- a/.github/workflows/dsa.yml +++ b/.github/workflows/dsa.yml @@ -24,7 +24,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown toolchain: - - 1.56.0 # MSRV + - 1.60.0 # MSRV - stable steps: - uses: actions/checkout@v2 @@ -43,7 +43,7 @@ jobs: - macos-latest - windows-latest toolchain: - - 1.56.0 # MSRV + - 1.60.0 # MSRV - stable runs-on: ${{ matrix.platform }} steps: From 1c332243cce68435d2aa8f5b632c907bc0b81c0b Mon Sep 17 00:00:00 2001 From: aumetra Date: Mon, 9 May 2022 11:23:44 +0200 Subject: [PATCH 09/27] dsa: Make compilation work on thumbv7em-none-eabi --- .github/workflows/dsa.yml | 4 ++-- dsa/Cargo.toml | 8 ++++---- dsa/src/lib.rs | 5 +++-- dsa/src/privatekey.rs | 2 +- dsa/src/publickey.rs | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/dsa.yml b/.github/workflows/dsa.yml index d87a0d3d..f70969d7 100644 --- a/.github/workflows/dsa.yml +++ b/.github/workflows/dsa.yml @@ -24,7 +24,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown toolchain: - - 1.60.0 # MSRV + - 1.57.0 # MSRV - stable steps: - uses: actions/checkout@v2 @@ -43,7 +43,7 @@ jobs: - macos-latest - windows-latest toolchain: - - 1.60.0 # MSRV + - 1.57.0 # MSRV - stable runs-on: ${{ matrix.platform }} steps: diff --git a/dsa/Cargo.toml b/dsa/Cargo.toml index 1cd19ab6..421ab492 100644 --- a/dsa/Cargo.toml +++ b/dsa/Cargo.toml @@ -6,7 +6,7 @@ license = "Apache-2.0 OR MIT" readme = "README.md" categories = ["cryptography"] keywords = ["crypto", "nist", "signature"] -rust-version = "1.60" # MSRV because of the feature syntax +rust-version = "1.57" [dependencies] digest = "0.10.3" @@ -14,14 +14,14 @@ num-bigint = { package = "num-bigint-dig", version = "0.8.1", default-features = num-traits = { version = "0.2.15", default-features = false } opaque-debug = "0.3.0" paste = "1.0.7" -pkcs8 = { version = "0.9.0", default-features = false, features = ["std"] } +pkcs8 = { version = "0.9.0", default-features = false, features = ["alloc"] } rand = { version = "0.8.5", default-features = false } signature = { version = "1.5.0", optional = true } zeroize = { version = "1.5.5", default-features = false } [features] -default = ["signature"] -signature = ["dep:signature", "rand/std", "rand/std_rng"] +default = ["signature-compat"] +signature-compat = ["signature", "pkcs8/std", "rand/std", "rand/std_rng"] [dev-dependencies] pkcs8 = { version = "0.9.0", default-features = false, features = ["pem"] } diff --git a/dsa/src/lib.rs b/dsa/src/lib.rs index 34949997..dcddce63 100644 --- a/dsa/src/lib.rs +++ b/dsa/src/lib.rs @@ -54,6 +54,7 @@ //! ``` //! +#![cfg_attr(not(feature = "signature-compat"), no_std)] #![forbid(missing_docs, unsafe_code)] #![deny(rust_2018_idioms)] @@ -68,8 +69,8 @@ pub use self::signature::Signature; // Re-export the types needed for de-/serialising keys to DER and PEM pub use pkcs8; -#[cfg(feature = "signature")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "signature")))] +#[cfg(feature = "signature-compat")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "signature-compat")))] pub mod compat; pub mod consts; diff --git a/dsa/src/privatekey.rs b/dsa/src/privatekey.rs index add96f8b..43788ef6 100644 --- a/dsa/src/privatekey.rs +++ b/dsa/src/privatekey.rs @@ -3,6 +3,7 @@ //! use crate::{Components, PublicKey, Signature, DSA_OID}; +use core::cmp::min; use digest::Digest; use num_bigint::BigUint; use pkcs8::{ @@ -10,7 +11,6 @@ use pkcs8::{ AlgorithmIdentifier, DecodePrivateKey, EncodePrivateKey, PrivateKeyInfo, SecretDocument, }; use rand::{CryptoRng, RngCore}; -use std::cmp::min; use zeroize::Zeroizing; /// DSA private key diff --git a/dsa/src/publickey.rs b/dsa/src/publickey.rs index 2085d92c..f78ef74a 100644 --- a/dsa/src/publickey.rs +++ b/dsa/src/publickey.rs @@ -3,6 +3,7 @@ //! use crate::{Components, Signature, DSA_OID}; +use core::cmp::min; use digest::Digest; use num_bigint::{BigUint, ModInverse}; use num_traits::One; @@ -10,7 +11,6 @@ use pkcs8::{ der::{asn1::UIntRef, AnyRef, Decode, Encode}, spki, AlgorithmIdentifier, DecodePublicKey, EncodePublicKey, SubjectPublicKeyInfo, }; -use std::cmp::min; /// DSA public key #[derive(Clone, PartialEq, PartialOrd)] From 6bc6cc184c05ac95037fc6f3f1ad4925c3ea3ecf Mon Sep 17 00:00:00 2001 From: aumetra Date: Mon, 9 May 2022 11:59:46 +0200 Subject: [PATCH 10/27] dsa: Update sanity checks --- dsa/src/components.rs | 11 ++++++----- dsa/src/privatekey.rs | 21 ++++++++++++++++++--- dsa/src/publickey.rs | 4 ++-- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/dsa/src/components.rs b/dsa/src/components.rs index a2718e5c..5e70ae1e 100644 --- a/dsa/src/components.rs +++ b/dsa/src/components.rs @@ -7,6 +7,8 @@ use num_traits::One; use pkcs8::der::{self, asn1::UIntRef, DecodeValue, Encode, Header, Reader, Sequence}; use rand::{CryptoRng, RngCore}; +use crate::two; + /// The common components of an DSA keypair /// /// (the prime p, quotient q and generator g) @@ -63,11 +65,10 @@ impl Components { /// Check whether the components are valid #[must_use] pub fn is_valid(&self) -> bool { - if *self.p() <= BigUint::one() || *self.q() <= BigUint::one() { - return false; - } - - true + *self.p() >= two() + && *self.q() >= two() + && *self.g() >= BigUint::one() + && self.g() < self.p() } } diff --git a/dsa/src/privatekey.rs b/dsa/src/privatekey.rs index 43788ef6..f0ad2451 100644 --- a/dsa/src/privatekey.rs +++ b/dsa/src/privatekey.rs @@ -6,6 +6,7 @@ use crate::{Components, PublicKey, Signature, DSA_OID}; use core::cmp::min; use digest::Digest; use num_bigint::BigUint; +use num_traits::One; use pkcs8::{ der::{asn1::UIntRef, AnyRef, Decode, Encode}, AlgorithmIdentifier, DecodePrivateKey, EncodePrivateKey, PrivateKeyInfo, SecretDocument, @@ -61,7 +62,11 @@ impl PrivateKey { /// Check whether the private key is valid #[must_use] pub fn is_valid(&self) -> bool { - self.public_key().components().is_valid() + if !self.public_key().is_valid() { + return false; + } + + *self.x() >= BigUint::one() && self.x() < self.public_key().components().q() } /// Sign data with the private key @@ -120,7 +125,11 @@ impl<'a> TryFrom> for PrivateKey { value.algorithm.assert_algorithm_oid(DSA_OID)?; let parameters = value.algorithm.parameters_any()?; - let components = parameters.decode_into()?; + let components: Components = parameters.decode_into()?; + + if !components.is_valid() { + return Err(pkcs8::Error::KeyMalformed); + } let x = UIntRef::from_der(value.private_key)?; let x = BigUint::from_bytes_be(x.as_bytes()); @@ -133,7 +142,13 @@ impl<'a> TryFrom> for PrivateKey { }; let public_key = PublicKey::from_components(components, y); - Ok(PrivateKey::from_components(public_key, x)) + let private_key = PrivateKey::from_components(public_key, x); + + if !private_key.is_valid() { + return Err(pkcs8::Error::KeyMalformed); + } + + Ok(private_key) } } diff --git a/dsa/src/publickey.rs b/dsa/src/publickey.rs index f78ef74a..8a8e4ce0 100644 --- a/dsa/src/publickey.rs +++ b/dsa/src/publickey.rs @@ -2,7 +2,7 @@ //! Module containing the definition of the public key container //! -use crate::{Components, Signature, DSA_OID}; +use crate::{two, Components, Signature, DSA_OID}; use core::cmp::min; use digest::Digest; use num_bigint::{BigUint, ModInverse}; @@ -52,7 +52,7 @@ impl PublicKey { return false; } - self.y().modpow(components.q(), components.p()) == BigUint::one() + *self.y() >= two() && self.y().modpow(components.q(), components.p()) == BigUint::one() } /// Verify if the signature matches the provided hash From 7123e0f193b30b075e42e31992085f90be50dc79 Mon Sep 17 00:00:00 2001 From: aumetra Date: Tue, 10 May 2022 18:41:17 +0200 Subject: [PATCH 11/27] dsa: Implement signature traits directly on the structures --- dsa/Cargo.toml | 7 +- dsa/examples/export.rs | 14 +-- dsa/examples/sign.rs | 25 ++--- dsa/src/compat.rs | 169 ------------------------------- dsa/src/lib.rs | 12 +-- dsa/src/privatekey.rs | 48 ++++++--- dsa/src/publickey.rs | 32 ++++-- dsa/src/{signature.rs => sig.rs} | 36 ++++++- 8 files changed, 113 insertions(+), 230 deletions(-) delete mode 100644 dsa/src/compat.rs rename dsa/src/{signature.rs => sig.rs} (60%) diff --git a/dsa/Cargo.toml b/dsa/Cargo.toml index 421ab492..2929c5d7 100644 --- a/dsa/Cargo.toml +++ b/dsa/Cargo.toml @@ -14,14 +14,13 @@ num-bigint = { package = "num-bigint-dig", version = "0.8.1", default-features = num-traits = { version = "0.2.15", default-features = false } opaque-debug = "0.3.0" paste = "1.0.7" -pkcs8 = { version = "0.9.0", default-features = false, features = ["alloc"] } +pkcs8 = { version = "0.9.0", default-features = false } rand = { version = "0.8.5", default-features = false } -signature = { version = "1.5.0", optional = true } +signature = { version = ">= 1.5.0, < 1.6.0", default-features = false, features = ["digest-preview", "rand-preview"] } zeroize = { version = "1.5.5", default-features = false } [features] -default = ["signature-compat"] -signature-compat = ["signature", "pkcs8/std", "rand/std", "rand/std_rng"] +default = [] [dev-dependencies] pkcs8 = { version = "0.9.0", default-features = false, features = ["pem"] } diff --git a/dsa/examples/export.rs b/dsa/examples/export.rs index 41a7154d..7e9face7 100644 --- a/dsa/examples/export.rs +++ b/dsa/examples/export.rs @@ -8,18 +8,14 @@ fn main() { let private_key = PrivateKey::generate(&mut rng, components); let public_key = private_key.public_key(); + let private_key_bytes = private_key.to_pkcs8_pem(LineEnding::LF).unwrap(); + let public_key_bytes = public_key.to_public_key_pem(LineEnding::LF).unwrap(); + let mut file = File::create("public.pem").unwrap(); - file.write_all( - public_key - .to_public_key_pem(LineEnding::LF) - .unwrap() - .as_bytes(), - ) - .unwrap(); + file.write_all(public_key_bytes.as_bytes()).unwrap(); file.flush().unwrap(); let mut file = File::create("private.pem").unwrap(); - file.write_all(private_key.to_pkcs8_pem(LineEnding::LF).unwrap().as_bytes()) - .unwrap(); + file.write_all(private_key_bytes.as_bytes()).unwrap(); file.flush().unwrap(); } diff --git a/dsa/examples/sign.rs b/dsa/examples/sign.rs index 631430c5..27c38534 100644 --- a/dsa/examples/sign.rs +++ b/dsa/examples/sign.rs @@ -1,6 +1,8 @@ +use digest::Digest; use dsa::{consts::DSA_2048_256, Components, PrivateKey}; -use pkcs8::{der::Encode, EncodePrivateKey, EncodePublicKey, LineEnding}; +use pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding}; use sha1::Sha1; +use signature::{RandomizedDigestSigner, Signature}; use std::{fs::File, io::Write}; fn main() { @@ -8,27 +10,22 @@ fn main() { let components = Components::generate(&mut rng, DSA_2048_256); let private_key = PrivateKey::generate(&mut rng, components); let public_key = private_key.public_key(); + let signature = private_key - .sign::<_, Sha1>(&mut rng, b"hello world") - .unwrap(); + .sign_digest_with_rng(rand::thread_rng(), Sha1::new().chain_update(b"hello world")); + + let private_key_bytes = private_key.to_pkcs8_pem(LineEnding::LF).unwrap(); + let public_key_bytes = public_key.to_public_key_pem(LineEnding::LF).unwrap(); let mut file = File::create("public.pem").unwrap(); - file.write_all( - public_key - .to_public_key_pem(LineEnding::LF) - .unwrap() - .as_bytes(), - ) - .unwrap(); + file.write_all(public_key_bytes.as_bytes()).unwrap(); file.flush().unwrap(); let mut file = File::create("signature.der").unwrap(); - file.write_all(signature.to_vec().unwrap().as_ref()) - .unwrap(); + file.write_all(signature.as_bytes()).unwrap(); file.flush().unwrap(); let mut file = File::create("private.pem").unwrap(); - file.write_all(private_key.to_pkcs8_pem(LineEnding::LF).unwrap().as_bytes()) - .unwrap(); + file.write_all(private_key_bytes.as_bytes()).unwrap(); file.flush().unwrap(); } diff --git a/dsa/src/compat.rs b/dsa/src/compat.rs deleted file mode 100644 index 5649e069..00000000 --- a/dsa/src/compat.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! -//! Types providing compatibility with the [signature](::signature) crate -//! -//! Example: -//! -//! ``` -//! # use dsa::{Components, PrivateKey, compat::{Signer, Verifier}, consts::DSA_1024_160}; -//! # use signature::{Signer as _, Verifier as _}; -//! # use sha1::Sha1; -//! # fn generate() -> PrivateKey { -//! # let mut rng = rand::thread_rng(); -//! # let components = Components::generate(&mut rng, DSA_1024_160); -//! # PrivateKey::generate(&mut rng, components) -//! # } -//! let private_key = generate(); -//! let public_key = private_key.public_key().clone(); -//! let signer: Signer = private_key.into(); -//! let verifier: Verifier = public_key.into(); -//! -//! let signature = signer.sign(b"hello world"); -//! assert!(verifier.verify(b"hello world", &signature).is_ok()); -//! assert!(verifier.verify(b"not hello world", &signature).is_err()); -//! ``` -//! - -use crate::{PrivateKey, PublicKey, Signature as InnerSignature}; -use core::fmt; -use digest::Digest; -use pkcs8::der::{self, Encode}; -use std::{marker::PhantomData, ops::Deref}; - -/// Container implementing the [Signature](::signature::Signature) trait -pub struct Signature { - /// Signature in DER form. This field gets generated whenever this container is constructed - /// - /// If the signature for whatever reason gets updated, this field needs to be updated as well. - /// Otherwise calls to the `as_ref` function will not reflect the changes - raw_signature: Vec, - - /// The actual signature used for the signing operations - signature: InnerSignature, -} - -impl Signature { - /// Deconstruct the compatibility container - pub fn into_inner(self) -> InnerSignature { - self.signature - } -} - -impl AsRef<[u8]> for Signature { - fn as_ref(&self) -> &[u8] { - &self.raw_signature - } -} - -impl fmt::Debug for Signature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.signature, f) - } -} - -impl Deref for Signature { - type Target = InnerSignature; - - fn deref(&self) -> &Self::Target { - &self.signature - } -} - -impl TryFrom for Signature { - type Error = der::Error; - - fn try_from(signature: InnerSignature) -> Result { - Ok(Self { - raw_signature: signature.to_vec()?, - signature, - }) - } -} - -impl signature::Signature for Signature { - fn from_bytes(bytes: &[u8]) -> Result { - InnerSignature::from_der(bytes) - .map(TryInto::try_into) - .map_err(signature::Error::from_source)? - .map_err(signature::Error::from_source) - } -} - -/// Container implementing the [Signer](::signature::Signer) trait -pub struct Signer { - /// Phantom data for binding the digest generic parameter - _digest_phantom: PhantomData, - - /// Private key for signing messages - private_key: PrivateKey, -} - -impl Signer { - /// Deconstruct the compatibility container - pub fn into_inner(self) -> PrivateKey { - self.private_key - } -} - -impl From for Signer { - fn from(private_key: PrivateKey) -> Self { - Self { - _digest_phantom: PhantomData, - private_key, - } - } -} - -impl signature::Signer for Signer -where - D: Digest, -{ - fn try_sign(&self, msg: &[u8]) -> Result { - self.private_key - .sign::<_, D>(&mut rand::thread_rng(), msg) - .map(TryInto::try_into) - .ok_or_else(signature::Error::new)? - .map_err(signature::Error::from_source) - } -} - -/// Container implementing the [Verifier](::signature::Verifier) trait -pub struct Verifier { - /// Phantom data for binding the digest generic parameter - _digest_phantom: PhantomData, - - /// Public key for verifying messages - public_key: PublicKey, -} - -impl Verifier { - /// Deconstruct the compatibility container - pub fn into_inner(self) -> PublicKey { - self.public_key - } -} - -impl From for Verifier { - fn from(public_key: PublicKey) -> Self { - Self { - _digest_phantom: PhantomData, - public_key, - } - } -} - -impl signature::Verifier for Verifier -where - D: Digest, -{ - fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { - if !self - .public_key - .verify::(msg, signature) - .ok_or_else(signature::Error::new)? - { - return Err(signature::Error::new()); - } - - Ok(()) - } -} diff --git a/dsa/src/lib.rs b/dsa/src/lib.rs index dcddce63..120e2d9f 100644 --- a/dsa/src/lib.rs +++ b/dsa/src/lib.rs @@ -54,24 +54,24 @@ //! ``` //! -#![cfg_attr(not(feature = "signature-compat"), no_std)] +#![no_std] #![forbid(missing_docs, unsafe_code)] #![deny(rust_2018_idioms)] +extern crate alloc; + /// DSA object identifier as defined by RFC-3279, section 2.3.2 const DSA_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10040.4.1"); pub use self::components::Components; pub use self::privatekey::PrivateKey; pub use self::publickey::PublicKey; -pub use self::signature::Signature; +pub use self::sig::Signature; // Re-export the types needed for de-/serialising keys to DER and PEM pub use pkcs8; +pub use signature; -#[cfg(feature = "signature-compat")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "signature-compat")))] -pub mod compat; pub mod consts; use num_bigint::BigUint; @@ -81,7 +81,7 @@ mod components; mod generate; mod privatekey; mod publickey; -mod signature; +mod sig; /// Returns a `BigUint` with the value 2 #[inline] diff --git a/dsa/src/privatekey.rs b/dsa/src/privatekey.rs index f0ad2451..9835b275 100644 --- a/dsa/src/privatekey.rs +++ b/dsa/src/privatekey.rs @@ -2,7 +2,7 @@ //! Module containing the definition of the private key container //! -use crate::{Components, PublicKey, Signature, DSA_OID}; +use crate::{sig::Signature, Components, PublicKey, DSA_OID}; use core::cmp::min; use digest::Digest; use num_bigint::BigUint; @@ -12,6 +12,7 @@ use pkcs8::{ AlgorithmIdentifier, DecodePrivateKey, EncodePrivateKey, PrivateKeyInfo, SecretDocument, }; use rand::{CryptoRng, RngCore}; +use signature::RandomizedDigestSigner; use zeroize::Zeroizing; /// DSA private key @@ -39,7 +40,6 @@ impl PrivateKey { } /// Generate a new DSA keypair - /// Generate a new DSA keypair using the common components #[inline] pub fn generate( rng: &mut R, @@ -69,12 +69,11 @@ impl PrivateKey { *self.x() >= BigUint::one() && self.x() < self.public_key().components().q() } - /// Sign data with the private key - pub fn sign( - &self, - rng: &mut R, - data: &[u8], - ) -> Option { + /// Sign some pre-hashed data + fn sign(&self, rng: &mut R, hash: &[u8]) -> Option + where + R: CryptoRng + RngCore, + { // Refuse to sign with an invalid key if !self.is_valid() { return None; @@ -83,13 +82,12 @@ impl PrivateKey { let components = self.public_key().components(); let (k, inv_k) = crate::generate::secret_number(rng, components)?; let (p, q, g) = (components.p(), components.q(), components.g()); - let hash = D::digest(data); let x = self.x(); let r = g.modpow(&k, p) % q; let n = (q.bits() / 8) as usize; - let block_size = ::output_size(); + let block_size = hash.len(); // Hash function output size let z_len = min(n, block_size); let z = BigUint::from_bytes_be(&hash[..z_len]); @@ -100,6 +98,24 @@ impl PrivateKey { } } +impl RandomizedDigestSigner for PrivateKey +where + D: Digest, +{ + fn try_sign_digest_with_rng( + &self, + mut rng: impl CryptoRng + RngCore, + digest: D, + ) -> Result { + let hash = digest.finalize(); + + self.sign(&mut rng, &hash) + .map(TryInto::try_into) + .ok_or_else(signature::Error::new)? + .map_err(|_| signature::Error::new()) + } +} + impl EncodePrivateKey for PrivateKey { fn to_pkcs8_der(&self) -> pkcs8::Result { let parameters = self.public_key().components().to_vec()?; @@ -161,10 +177,12 @@ mod test { #![allow(deprecated)] use crate::{consts::DSA_1024_160, Components, PrivateKey}; + use digest::Digest; use num_bigint::BigUint; use num_traits::Zero; use pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}; use sha1::Sha1; + use signature::{DigestVerifier, RandomizedDigestSigner}; fn generate_keypair() -> PrivateKey { let mut rng = rand::thread_rng(); @@ -188,12 +206,12 @@ mod test { let private_key = generate_keypair(); let public_key = private_key.public_key(); - let signature = private_key - .sign::<_, Sha1>(&mut rand::thread_rng(), DATA) - .expect("Failed to sign"); + let signature = + private_key.sign_digest_with_rng(rand::thread_rng(), Sha1::new().chain_update(DATA)); + assert!(public_key - .verify::(DATA, &signature) - .expect("Failed to verify")); + .verify_digest(Sha1::new().chain_update(DATA), &signature) + .is_ok()); } #[test] diff --git a/dsa/src/publickey.rs b/dsa/src/publickey.rs index 8a8e4ce0..7e8bc87e 100644 --- a/dsa/src/publickey.rs +++ b/dsa/src/publickey.rs @@ -2,7 +2,7 @@ //! Module containing the definition of the public key container //! -use crate::{two, Components, Signature, DSA_OID}; +use crate::{sig::Signature, two, Components, DSA_OID}; use core::cmp::min; use digest::Digest; use num_bigint::{BigUint, ModInverse}; @@ -11,6 +11,7 @@ use pkcs8::{ der::{asn1::UIntRef, AnyRef, Decode, Encode}, spki, AlgorithmIdentifier, DecodePublicKey, EncodePublicKey, SubjectPublicKeyInfo, }; +use signature::DigestVerifier; /// DSA public key #[derive(Clone, PartialEq, PartialOrd)] @@ -55,12 +56,9 @@ impl PublicKey { *self.y() >= two() && self.y().modpow(components.q(), components.p()) == BigUint::one() } - /// Verify if the signature matches the provided hash + /// Verify some prehashed data #[must_use] - pub fn verify(&self, data: &[u8], signature: &Signature) -> Option - where - D: Digest, - { + fn verify(&self, hash: &[u8], signature: &Signature) -> Option { // Refuse to verify with an invalid key if !self.is_valid() { return None; @@ -70,12 +68,11 @@ impl PublicKey { let (p, q, g) = (components.p(), components.q(), components.g()); let (r, s) = (signature.r(), signature.s()); let y = self.y(); - let hash = D::digest(data); let w = s.mod_inverse(q)?.to_biguint().unwrap(); let n = (q.bits() / 8) as usize; - let block_size = ::output_size(); + let block_size = hash.len(); // Hash function output size let z_len = min(n, block_size); let z = BigUint::from_bytes_be(&hash[..z_len]); @@ -88,6 +85,25 @@ impl PublicKey { } } +impl DigestVerifier for PublicKey +where + D: Digest, +{ + fn verify_digest(&self, digest: D, signature: &Signature) -> Result<(), signature::Error> { + let hash = digest.finalize(); + + let is_valid = self + .verify(&hash, signature) + .ok_or_else(signature::Error::new)?; + + if !is_valid { + return Err(signature::Error::new()); + } + + Ok(()) + } +} + impl EncodePublicKey for PublicKey { fn to_public_key_der(&self) -> spki::Result { let parameters = self.components.to_vec()?; diff --git a/dsa/src/signature.rs b/dsa/src/sig.rs similarity index 60% rename from dsa/src/signature.rs rename to dsa/src/sig.rs index c82eb441..04ba6047 100644 --- a/dsa/src/signature.rs +++ b/dsa/src/sig.rs @@ -1,14 +1,18 @@ //! -//! Module containing the definition of the signature container +//! Module containing the definition of the Signature container //! +use alloc::vec::Vec; use num_bigint::BigUint; -use pkcs8::der::{self, asn1::UIntRef, Decode, Reader, Sequence, SliceReader}; +use pkcs8::der::{self, asn1::UIntRef, Decode, Encode, Reader, Sequence, SliceReader}; /// Container of the DSA signature #[derive(Clone, PartialEq, PartialOrd)] #[must_use] pub struct Signature { + /// Internally cached DER representation of the signature + der_repr: Vec, + /// Signature part r r: BigUint, @@ -19,12 +23,19 @@ pub struct Signature { opaque_debug::implement!(Signature); impl Signature { - /// Create a new signature container from its components + /// Create a new Signature container from its components pub fn new(r: BigUint, s: BigUint) -> Self { - Self { r, s } + let mut signature = Self { + der_repr: Vec::with_capacity(0), + r, + s, + }; + signature.der_repr = signature.to_vec().unwrap(); + + signature } - /// Decode a signature from its DER representation + /// Decode a Signature from its DER representation /// /// # Errors /// @@ -47,6 +58,12 @@ impl Signature { } } +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.der_repr + } +} + impl<'a> Decode<'a> for Signature { fn decode>(reader: &mut R) -> der::Result { let r = reader.decode::>()?; @@ -73,3 +90,12 @@ impl<'a> Sequence<'a> for Signature { encoder(&[&r, &s]) } } + +impl signature::Signature for Signature { + fn from_bytes(bytes: &[u8]) -> Result { + Signature::from_der(bytes) + .map(TryInto::try_into) + .map_err(|_| signature::Error::new())? + .map_err(|_| signature::Error::new()) + } +} From 62c402882b7a788bceb226576fea3d25a279943e Mon Sep 17 00:00:00 2001 From: aumetra Date: Tue, 10 May 2022 18:45:34 +0200 Subject: [PATCH 12/27] dsa: Add missing alloc feature --- dsa/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsa/Cargo.toml b/dsa/Cargo.toml index 2929c5d7..8473c396 100644 --- a/dsa/Cargo.toml +++ b/dsa/Cargo.toml @@ -14,7 +14,7 @@ num-bigint = { package = "num-bigint-dig", version = "0.8.1", default-features = num-traits = { version = "0.2.15", default-features = false } opaque-debug = "0.3.0" paste = "1.0.7" -pkcs8 = { version = "0.9.0", default-features = false } +pkcs8 = { version = "0.9.0", default-features = false, features = ["alloc"] } rand = { version = "0.8.5", default-features = false } signature = { version = ">= 1.5.0, < 1.6.0", default-features = false, features = ["digest-preview", "rand-preview"] } zeroize = { version = "1.5.5", default-features = false } From f4df2694949f72d92e687f4f58f6aac18636bdcf Mon Sep 17 00:00:00 2001 From: aumetra Date: Wed, 11 May 2022 13:33:57 +0200 Subject: [PATCH 13/27] dsa: Add support for RFC6979 deterministic signatures --- Cargo.lock | 12 ++- dsa/Cargo.toml | 4 +- dsa/src/generate/mod.rs | 2 +- dsa/src/generate/secret_number.rs | 65 +++++++++++- dsa/src/lib.rs | 2 +- dsa/src/privatekey.rs | 162 +++++++++++++++++++++++++++--- dsa/src/publickey.rs | 4 +- dsa/src/sig.rs | 25 +++-- 8 files changed, 242 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d237fba2..d1ac11eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,9 +149,11 @@ dependencies = [ "num-traits", "opaque-debug", "paste", - "pkcs8 0.9.0", + "pkcs8", "rand 0.8.5", - "sha-1", + "rfc6979", + "sha1", + "sha2 0.10.2", "signature", "zeroize", ] @@ -542,10 +544,10 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.10.0" +name = "sha1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" dependencies = [ "cfg-if", "cpufeatures", diff --git a/dsa/Cargo.toml b/dsa/Cargo.toml index 8473c396..b769011a 100644 --- a/dsa/Cargo.toml +++ b/dsa/Cargo.toml @@ -16,6 +16,7 @@ opaque-debug = "0.3.0" paste = "1.0.7" pkcs8 = { version = "0.9.0", default-features = false, features = ["alloc"] } rand = { version = "0.8.5", default-features = false } +rfc6979 = { version = "0.2.0", path = "../rfc6979" } signature = { version = ">= 1.5.0, < 1.6.0", default-features = false, features = ["digest-preview", "rand-preview"] } zeroize = { version = "1.5.5", default-features = false } @@ -25,4 +26,5 @@ default = [] [dev-dependencies] pkcs8 = { version = "0.9.0", default-features = false, features = ["pem"] } rand = "0.8.5" -sha-1 = "0.10.0" +sha1 = "0.10.1" +sha2 = "0.10.2" diff --git a/dsa/src/generate/mod.rs b/dsa/src/generate/mod.rs index 354096a0..84483a8c 100644 --- a/dsa/src/generate/mod.rs +++ b/dsa/src/generate/mod.rs @@ -9,7 +9,7 @@ mod secret_number; pub use self::components::{common as common_components, public as public_component}; pub use self::keypair::keypair; -pub use self::secret_number::secret_number; +pub use self::secret_number::{secret_number, secret_number_rfc6979}; /// Calculate the upper and lower bounds for generating values like p or q #[inline] diff --git a/dsa/src/generate/secret_number.rs b/dsa/src/generate/secret_number.rs index 9388f70f..820bdc68 100644 --- a/dsa/src/generate/secret_number.rs +++ b/dsa/src/generate/secret_number.rs @@ -2,10 +2,73 @@ //! Generate a per-message secret number //! -use crate::Components; +use crate::{Components, PrivateKey}; +use alloc::vec; +use core::cmp::min; +use digest::{ + block_buffer::Eager, + consts::U256, + core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore}, + typenum::{IsLess, Le, NonZero}, + FixedOutput, HashMarker, OutputSizeUser, +}; use num_bigint::{BigUint, ModInverse, RandBigInt}; use num_traits::{One, Zero}; use rand::{CryptoRng, RngCore}; +use rfc6979::HmacDrbg; +use zeroize::Zeroize; + +/// Reduce the hash into an RFC-6979 appropriate form +fn reduce_hash(q: &BigUint, hash: &[u8]) -> Vec { + // Reduce the hash modulo Q + let hash_len = min(hash.len(), q.bits() / 8); + let hash = &hash[..hash_len]; + + let hash = BigUint::from_bytes_be(hash); + (hash % q).to_bytes_be() +} + +/// Generate a per-message secret number k deterministically using the method described in RFC 6979 +/// +/// # Returns +/// +/// Secret number k and its modular multiplicative inverse with q +#[inline] +pub fn secret_number_rfc6979(private_key: &PrivateKey, hash: &[u8]) -> (BigUint, BigUint) +where + D: CoreProxy + FixedOutput, + D::Core: BlockSizeUser + + BufferKindUser + + Clone + + Default + + FixedOutputCore + + HashMarker + + OutputSizeUser, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + let q = private_key.public_key().components().q(); + let k_size = q.bits() / 8; + let hash = reduce_hash(q, hash); + + let mut x_bytes = private_key.x().to_bytes_be(); + let mut hmac = HmacDrbg::::new(&x_bytes, &hash, &[]); + x_bytes.zeroize(); + + loop { + let mut buffer = vec![0; k_size]; + hmac.fill_bytes(&mut buffer); + + let k = BigUint::from_bytes_be(&buffer); + if let Some(inv_k) = (&k).mod_inverse(q) { + let inv_k = inv_k.to_biguint().unwrap(); + + if k > BigUint::zero() && &k < q { + return (k, inv_k); + } + } + } +} /// Generate a per-message secret number k according to Appendix B.2.1 /// diff --git a/dsa/src/lib.rs b/dsa/src/lib.rs index 120e2d9f..f8e106b0 100644 --- a/dsa/src/lib.rs +++ b/dsa/src/lib.rs @@ -54,7 +54,7 @@ //! ``` //! -#![no_std] +//#![no_std] #![forbid(missing_docs, unsafe_code)] #![deny(rust_2018_idioms)] diff --git a/dsa/src/privatekey.rs b/dsa/src/privatekey.rs index 9835b275..b051ce62 100644 --- a/dsa/src/privatekey.rs +++ b/dsa/src/privatekey.rs @@ -4,7 +4,13 @@ use crate::{sig::Signature, Components, PublicKey, DSA_OID}; use core::cmp::min; -use digest::Digest; +use digest::{ + block_buffer::Eager, + consts::U256, + core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore}, + typenum::{IsLess, Le, NonZero}, + Digest, FixedOutput, HashMarker, OutputSizeUser, +}; use num_bigint::BigUint; use num_traits::One; use pkcs8::{ @@ -12,10 +18,13 @@ use pkcs8::{ AlgorithmIdentifier, DecodePrivateKey, EncodePrivateKey, PrivateKeyInfo, SecretDocument, }; use rand::{CryptoRng, RngCore}; -use signature::RandomizedDigestSigner; +use signature::{DigestSigner, RandomizedDigestSigner}; use zeroize::Zeroizing; /// DSA private key +/// +/// The [`(try_)sign_digest_with_rng`](::signature::RandomizedDigestSigner) API uses regular non-deterministic signatures, +/// while the [`(try_)sign_digest`](::signature::DigestSigner) API uses deterministic signatures as described in RFC 6979 #[derive(Clone, PartialEq)] #[must_use] pub struct PrivateKey { @@ -54,6 +63,8 @@ impl PrivateKey { } /// DSA private component + /// + /// If you decide to clone this value, please consider using [`Zeroize::zeroize`](::zeroize::Zeroize::zeroize()) to zero out the memory region the libs of this integer are located in #[must_use] pub fn x(&self) -> &BigUint { &self.x @@ -70,17 +81,13 @@ impl PrivateKey { } /// Sign some pre-hashed data - fn sign(&self, rng: &mut R, hash: &[u8]) -> Option - where - R: CryptoRng + RngCore, - { + fn sign_prehashed(&self, (k, inv_k): (BigUint, BigUint), hash: &[u8]) -> Option { // Refuse to sign with an invalid key if !self.is_valid() { return None; } let components = self.public_key().components(); - let (k, inv_k) = crate::generate::secret_number(rng, components)?; let (p, q, g) = (components.p(), components.q(), components.g()); let x = self.x(); @@ -94,7 +101,29 @@ impl PrivateKey { let s = (inv_k * (z + x * &r)) % q; - Some(Signature::new(r, s)) + Some(Signature::from_components(r, s)) + } +} + +impl DigestSigner for PrivateKey +where + D: Digest + CoreProxy + FixedOutput, + D::Core: BlockSizeUser + + BufferKindUser + + Clone + + Default + + FixedOutputCore + + HashMarker + + OutputSizeUser, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + fn try_sign_digest(&self, digest: D) -> Result { + let hash = digest.finalize_fixed(); + let ks = crate::generate::secret_number_rfc6979::(self, &hash); + + self.sign_prehashed(ks, &hash) + .ok_or_else(signature::Error::new) } } @@ -107,12 +136,12 @@ where mut rng: impl CryptoRng + RngCore, digest: D, ) -> Result { + let ks = crate::generate::secret_number(&mut rng, self.public_key().components()) + .ok_or_else(signature::Error::new)?; let hash = digest.finalize(); - self.sign(&mut rng, &hash) - .map(TryInto::try_into) - .ok_or_else(signature::Error::new)? - .map_err(|_| signature::Error::new()) + self.sign_prehashed(ks, &hash) + .ok_or_else(signature::Error::new) } } @@ -176,13 +205,14 @@ mod test { // But we want to use those small key sizes for fast tests #![allow(deprecated)] - use crate::{consts::DSA_1024_160, Components, PrivateKey}; + use crate::{consts::DSA_1024_160, Components, PrivateKey, PublicKey, Signature}; use digest::Digest; use num_bigint::BigUint; - use num_traits::Zero; + use num_traits::{Num, Zero}; use pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}; use sha1::Sha1; - use signature::{DigestVerifier, RandomizedDigestSigner}; + use sha2::Sha224; + use signature::{DigestSigner, DigestVerifier, RandomizedDigestSigner}; fn generate_keypair() -> PrivateKey { let mut rng = rand::thread_rng(); @@ -229,4 +259,106 @@ mod test { "Requirement y=(g^x)%p not met" ); } + + #[test] + fn rfc6979_signatures() { + // TODO: Clean up this messy test + + const MESSAGE: &[u8] = b"sample"; + const MESSAGE_2: &[u8] = b"test"; + + let p = BigUint::from_str_radix( + "86F5CA03DCFEB225063FF830A0C769B9DD9D6153AD91D7CE27F787C43278B447\ + E6533B86B18BED6E8A48B784A14C252C5BE0DBF60B86D6385BD2F12FB763ED88\ + 73ABFD3F5BA2E0A8C0A59082EAC056935E529DAF7C610467899C77ADEDFC846C\ + 881870B7B19B2B58F9BE0521A17002E3BDD6B86685EE90B3D9A1B02B782B1779", + 16, + ) + .unwrap(); + let q = BigUint::from_str_radix("996F967F6C8E388D9E28D01E205FBA957A5698B1", 16).unwrap(); + let g = BigUint::from_str_radix( + "07B0F92546150B62514BB771E2A0C0CE387F03BDA6C56B505209FF25FD3C133D\ + 89BBCD97E904E09114D9A7DEFDEADFC9078EA544D2E401AEECC40BB9FBBF78FD\ + 87995A10A1C27CB7789B594BA7EFB5C4326A9FE59A070E136DB77175464ADCA4\ + 17BE5DCE2F40D10A46A3A3943F26AB7FD9C0398FF8C76EE0A56826A8A88F1DBD", + 16, + ) + .unwrap(); + + let x = BigUint::from_str_radix("411602CB19A6CCC34494D79D98EF1E7ED5AF25F7", 16).unwrap(); + let y = BigUint::from_str_radix( + "5DF5E01DED31D0297E274E1691C192FE5868FEF9E19A84776454B100CF16F653\ + 92195A38B90523E2542EE61871C0440CB87C322FC4B4D2EC5E1E7EC766E1BE8D\ + 4CE935437DC11C3C8FD426338933EBFE739CB3465F4D3668C5E473508253B1E6\ + 82F65CBDC4FAE93C2EA212390E54905A86E2223170B44EAA7DA5DD9FFCFB7F3B", + 16, + ) + .unwrap(); + + let sha1_signature = Signature::from_components( + BigUint::from_str_radix("2E1A0C2562B2912CAAF89186FB0F42001585DA55", 16).unwrap(), + BigUint::from_str_radix("29EFB6B0AFF2D7A68EB70CA313022253B9A88DF5", 16).unwrap(), + ); + let sha1_signature_2 = Signature::from_components( + BigUint::from_str_radix("42AB2052FD43E123F0607F115052A67DCD9C5C77", 16).unwrap(), + BigUint::from_str_radix("183916B0230D45B9931491D4C6B0BD2FB4AAF088", 16).unwrap(), + ); + + let sha224_signature = Signature::from_components( + BigUint::from_str_radix("4BC3B686AEA70145856814A6F1BB53346F02101E", 16).unwrap(), + BigUint::from_str_radix("410697B92295D994D21EDD2F4ADA85566F6F94C1", 16).unwrap(), + ); + let sha224_signature_2 = Signature::from_components( + BigUint::from_str_radix("6868E9964E36C1689F6037F91F28D5F2C30610F2", 16).unwrap(), + BigUint::from_str_radix("49CEC3ACDC83018C5BD2674ECAAD35B8CD22940F", 16).unwrap(), + ); + + let components = Components::from_components(p, q, g); + let public_key = PublicKey::from_components(components, y); + let private_key = PrivateKey::from_components(public_key, x); + + let sha1_hash = Sha1::digest(MESSAGE); + let sha1_hash2 = Sha1::digest(MESSAGE_2); + + let sha224_hash = Sha224::digest(MESSAGE); + let sha224_hash_2 = Sha224::digest(MESSAGE_2); + + let sha1_k = + BigUint::from_str_radix("7BDB6B0FF756E1BB5D53583EF979082F9AD5BD5B", 16).unwrap(); + let sha1_k_2 = + BigUint::from_str_radix("5C842DF4F9E344EE09F056838B42C7A17F4A6433", 16).unwrap(); + + let sha224_k = + BigUint::from_str_radix("562097C06782D60C3037BA7BE104774344687649", 16).unwrap(); + let sha224_k_2 = + BigUint::from_str_radix("4598B8EFC1A53BC8AECD58D1ABBB0C0C71E67297", 16).unwrap(); + + let (gen_sha1_k, _) = + crate::generate::secret_number_rfc6979::(&private_key, &sha1_hash); + let (gen_sha1_k_2, _) = + crate::generate::secret_number_rfc6979::(&private_key, &sha1_hash2); + + let (gen_sha224_k, _) = + crate::generate::secret_number_rfc6979::(&private_key, &sha224_hash); + let (gen_sha224_k_2, _) = + crate::generate::secret_number_rfc6979::(&private_key, &sha224_hash_2); + + assert_eq!(sha1_k, gen_sha1_k, "SHA1 test #1"); + assert_eq!(sha1_k_2, gen_sha1_k_2, "SHA1 test #2"); + + assert_eq!(sha224_k, gen_sha224_k, "SHA224 test #1"); + assert_eq!(sha224_k_2, gen_sha224_k_2, "SHA224 test #2"); + + let sha1_generated = private_key.sign_digest(Sha1::new().chain_update(MESSAGE)); + let sha1_generated_2 = private_key.sign_digest(Sha1::new().chain_update(MESSAGE_2)); + + let sha224_generated = private_key.sign_digest(Sha224::new().chain_update(MESSAGE)); + let sha224_generated_2 = private_key.sign_digest(Sha224::new().chain_update(MESSAGE_2)); + + assert_eq!(sha1_signature, sha1_generated, "SHA1 test #1"); + assert_eq!(sha1_signature_2, sha1_generated_2, "SHA1 test #2"); + + assert_eq!(sha224_signature, sha224_generated, "SHA224 test #1"); + assert_eq!(sha224_signature_2, sha224_generated_2, "SHA224 test #2"); + } } diff --git a/dsa/src/publickey.rs b/dsa/src/publickey.rs index 7e8bc87e..65c3fcde 100644 --- a/dsa/src/publickey.rs +++ b/dsa/src/publickey.rs @@ -58,7 +58,7 @@ impl PublicKey { /// Verify some prehashed data #[must_use] - fn verify(&self, hash: &[u8], signature: &Signature) -> Option { + fn verify_prehashed(&self, hash: &[u8], signature: &Signature) -> Option { // Refuse to verify with an invalid key if !self.is_valid() { return None; @@ -93,7 +93,7 @@ where let hash = digest.finalize(); let is_valid = self - .verify(&hash, signature) + .verify_prehashed(&hash, signature) .ok_or_else(signature::Error::new)?; if !is_valid { diff --git a/dsa/src/sig.rs b/dsa/src/sig.rs index 04ba6047..20b0ce3e 100644 --- a/dsa/src/sig.rs +++ b/dsa/src/sig.rs @@ -7,7 +7,7 @@ use num_bigint::BigUint; use pkcs8::der::{self, asn1::UIntRef, Decode, Encode, Reader, Sequence, SliceReader}; /// Container of the DSA signature -#[derive(Clone, PartialEq, PartialOrd)] +#[derive(Clone)] #[must_use] pub struct Signature { /// Internally cached DER representation of the signature @@ -24,7 +24,7 @@ opaque_debug::implement!(Signature); impl Signature { /// Create a new Signature container from its components - pub fn new(r: BigUint, s: BigUint) -> Self { + pub fn from_components(r: BigUint, s: BigUint) -> Self { let mut signature = Self { der_repr: Vec::with_capacity(0), r, @@ -39,7 +39,7 @@ impl Signature { /// /// # Errors /// - /// See the [`der` errors](pkcs8::der::Error) + /// See the [`der` errors](::pkcs8::der::Error) pub fn from_der(data: &[u8]) -> der::Result { let mut reader = SliceReader::new(data)?; reader.decode() @@ -72,7 +72,19 @@ impl<'a> Decode<'a> for Signature { let r = BigUint::from_bytes_be(r.as_bytes()); let s = BigUint::from_bytes_be(s.as_bytes()); - Ok(Self::new(r, s)) + Ok(Self::from_components(r, s)) + } +} + +impl PartialEq for Signature { + fn eq(&self, other: &Self) -> bool { + self.r().eq(other.r()) && self.s().eq(other.s()) + } +} + +impl PartialOrd for Signature { + fn partial_cmp(&self, other: &Self) -> Option { + (self.r(), self.s()).partial_cmp(&(other.r(), other.s())) } } @@ -93,9 +105,6 @@ impl<'a> Sequence<'a> for Signature { impl signature::Signature for Signature { fn from_bytes(bytes: &[u8]) -> Result { - Signature::from_der(bytes) - .map(TryInto::try_into) - .map_err(|_| signature::Error::new())? - .map_err(|_| signature::Error::new()) + Signature::from_der(bytes).map_err(|_| signature::Error::new()) } } From 3b0fa037c6407d0ea25a09d5defd0da61868f548 Mon Sep 17 00:00:00 2001 From: aumetra Date: Wed, 11 May 2022 14:00:39 +0200 Subject: [PATCH 14/27] dsa: Re-add no_std attribute --- dsa/src/generate/secret_number.rs | 2 +- dsa/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsa/src/generate/secret_number.rs b/dsa/src/generate/secret_number.rs index 820bdc68..d6b258d4 100644 --- a/dsa/src/generate/secret_number.rs +++ b/dsa/src/generate/secret_number.rs @@ -3,7 +3,7 @@ //! use crate::{Components, PrivateKey}; -use alloc::vec; +use alloc::{vec, vec::Vec}; use core::cmp::min; use digest::{ block_buffer::Eager, diff --git a/dsa/src/lib.rs b/dsa/src/lib.rs index f8e106b0..120e2d9f 100644 --- a/dsa/src/lib.rs +++ b/dsa/src/lib.rs @@ -54,7 +54,7 @@ //! ``` //! -//#![no_std] +#![no_std] #![forbid(missing_docs, unsafe_code)] #![deny(rust_2018_idioms)] From f1ec422d14c52bb3809636b180243ef258ddeda5 Mon Sep 17 00:00:00 2001 From: aumetra Date: Thu, 12 May 2022 13:31:06 +0200 Subject: [PATCH 15/27] dsa: Update documentation --- dsa/src/consts.rs | 2 +- dsa/src/privatekey.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsa/src/consts.rs b/dsa/src/consts.rs index 3c3efb9c..3f648828 100644 --- a/dsa/src/consts.rs +++ b/dsa/src/consts.rs @@ -1,5 +1,5 @@ //! -//! DSA-related constants like parameter sizes +//! DSA-related constants (like parameter sizes) //! macro_rules! define_param_size { diff --git a/dsa/src/privatekey.rs b/dsa/src/privatekey.rs index b051ce62..8f5145fc 100644 --- a/dsa/src/privatekey.rs +++ b/dsa/src/privatekey.rs @@ -64,7 +64,7 @@ impl PrivateKey { /// DSA private component /// - /// If you decide to clone this value, please consider using [`Zeroize::zeroize`](::zeroize::Zeroize::zeroize()) to zero out the memory region the libs of this integer are located in + /// If you decide to clone this value, please consider using [`Zeroize::zeroize`](::zeroize::Zeroize::zeroize()) to zero out the memory after you're done using the clone #[must_use] pub fn x(&self) -> &BigUint { &self.x From ea4caffb1c44547b91441b0de9cbe82dbdb757cc Mon Sep 17 00:00:00 2001 From: aumetra Date: Thu, 12 May 2022 13:32:25 +0200 Subject: [PATCH 16/27] dsa: Reuse buffer --- dsa/src/generate/secret_number.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsa/src/generate/secret_number.rs b/dsa/src/generate/secret_number.rs index d6b258d4..7a904929 100644 --- a/dsa/src/generate/secret_number.rs +++ b/dsa/src/generate/secret_number.rs @@ -55,8 +55,8 @@ where let mut hmac = HmacDrbg::::new(&x_bytes, &hash, &[]); x_bytes.zeroize(); + let mut buffer = vec![0; k_size]; loop { - let mut buffer = vec![0; k_size]; hmac.fill_bytes(&mut buffer); let k = BigUint::from_bytes_be(&buffer); From 26379d8abaf793f162a2e84144ee6b57065dcba2 Mon Sep 17 00:00:00 2001 From: aumetra Date: Fri, 13 May 2022 15:43:44 +0200 Subject: [PATCH 17/27] dsa: Move tests into own directory, add all 1024-bit deterministic test vectors --- dsa/src/privatekey.rs | 164 ------------------------------- dsa/src/publickey.rs | 39 -------- dsa/tests/deterministic.rs | 192 +++++++++++++++++++++++++++++++++++++ dsa/tests/privatekey.rs | 57 +++++++++++ dsa/tests/publickey.rs | 35 +++++++ 5 files changed, 284 insertions(+), 203 deletions(-) create mode 100644 dsa/tests/deterministic.rs create mode 100644 dsa/tests/privatekey.rs create mode 100644 dsa/tests/publickey.rs diff --git a/dsa/src/privatekey.rs b/dsa/src/privatekey.rs index 8f5145fc..65cabde1 100644 --- a/dsa/src/privatekey.rs +++ b/dsa/src/privatekey.rs @@ -198,167 +198,3 @@ impl<'a> TryFrom> for PrivateKey { } impl DecodePrivateKey for PrivateKey {} - -#[cfg(test)] -mod test { - // We abused the deprecated attribute for unsecure key sizes - // But we want to use those small key sizes for fast tests - #![allow(deprecated)] - - use crate::{consts::DSA_1024_160, Components, PrivateKey, PublicKey, Signature}; - use digest::Digest; - use num_bigint::BigUint; - use num_traits::{Num, Zero}; - use pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}; - use sha1::Sha1; - use sha2::Sha224; - use signature::{DigestSigner, DigestVerifier, RandomizedDigestSigner}; - - fn generate_keypair() -> PrivateKey { - let mut rng = rand::thread_rng(); - let components = Components::generate(&mut rng, DSA_1024_160); - PrivateKey::generate(&mut rng, components) - } - - #[test] - fn encode_decode_private_key() { - let private_key = generate_keypair(); - let encoded_private_key = private_key.to_pkcs8_pem(LineEnding::LF).unwrap(); - let decoded_private_key = PrivateKey::from_pkcs8_pem(&encoded_private_key).unwrap(); - - assert_eq!(private_key, decoded_private_key); - } - - #[test] - fn sign_and_verify() { - const DATA: &[u8] = b"SIGN AND VERIFY THOSE BYTES"; - - let private_key = generate_keypair(); - let public_key = private_key.public_key(); - - let signature = - private_key.sign_digest_with_rng(rand::thread_rng(), Sha1::new().chain_update(DATA)); - - assert!(public_key - .verify_digest(Sha1::new().chain_update(DATA), &signature) - .is_ok()); - } - - #[test] - fn verify_validity() { - let private_key = generate_keypair(); - let components = private_key.public_key().components(); - - assert!( - BigUint::zero() < *private_key.x() && private_key.x() < components.q(), - "Requirement 0(&private_key, &sha1_hash); - let (gen_sha1_k_2, _) = - crate::generate::secret_number_rfc6979::(&private_key, &sha1_hash2); - - let (gen_sha224_k, _) = - crate::generate::secret_number_rfc6979::(&private_key, &sha224_hash); - let (gen_sha224_k_2, _) = - crate::generate::secret_number_rfc6979::(&private_key, &sha224_hash_2); - - assert_eq!(sha1_k, gen_sha1_k, "SHA1 test #1"); - assert_eq!(sha1_k_2, gen_sha1_k_2, "SHA1 test #2"); - - assert_eq!(sha224_k, gen_sha224_k, "SHA224 test #1"); - assert_eq!(sha224_k_2, gen_sha224_k_2, "SHA224 test #2"); - - let sha1_generated = private_key.sign_digest(Sha1::new().chain_update(MESSAGE)); - let sha1_generated_2 = private_key.sign_digest(Sha1::new().chain_update(MESSAGE_2)); - - let sha224_generated = private_key.sign_digest(Sha224::new().chain_update(MESSAGE)); - let sha224_generated_2 = private_key.sign_digest(Sha224::new().chain_update(MESSAGE_2)); - - assert_eq!(sha1_signature, sha1_generated, "SHA1 test #1"); - assert_eq!(sha1_signature_2, sha1_generated_2, "SHA1 test #2"); - - assert_eq!(sha224_signature, sha224_generated, "SHA224 test #1"); - assert_eq!(sha224_signature_2, sha224_generated_2, "SHA224 test #2"); - } -} diff --git a/dsa/src/publickey.rs b/dsa/src/publickey.rs index 65c3fcde..537bc72a 100644 --- a/dsa/src/publickey.rs +++ b/dsa/src/publickey.rs @@ -143,42 +143,3 @@ impl<'a> TryFrom> for PublicKey { } impl DecodePublicKey for PublicKey {} - -#[cfg(test)] -mod test { - // We abused the deprecated attribute for unsecure key sizes - // But we want to use those small key sizes for fast tests - #![allow(deprecated)] - - use crate::{consts::DSA_1024_160, Components, PrivateKey, PublicKey}; - use num_bigint::BigUint; - use num_traits::One; - use pkcs8::{DecodePublicKey, EncodePublicKey, LineEnding}; - - fn generate_public_key() -> PublicKey { - let mut rng = rand::thread_rng(); - let components = Components::generate(&mut rng, DSA_1024_160); - let private_key = PrivateKey::generate(&mut rng, components); - - private_key.public_key().clone() - } - - #[test] - fn encode_decode_public_key() { - let public_key = generate_public_key(); - let encoded_public_key = public_key.to_public_key_pem(LineEnding::LF).unwrap(); - let decoded_public_key = PublicKey::from_public_key_pem(&encoded_public_key).unwrap(); - - assert_eq!(public_key, decoded_public_key); - } - - #[test] - fn validate_public_key() { - let public_key = generate_public_key(); - let p = public_key.components().p(); - let q = public_key.components().q(); - - // Taken from the parameter validation from bouncy castle - assert_eq!(public_key.y().modpow(q, p), BigUint::one()); - } -} diff --git a/dsa/tests/deterministic.rs b/dsa/tests/deterministic.rs new file mode 100644 index 00000000..c2657a5a --- /dev/null +++ b/dsa/tests/deterministic.rs @@ -0,0 +1,192 @@ +use digest::{ + block_buffer::Eager, + consts::U256, + core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore}, + typenum::{IsLess, Le, NonZero}, + Digest, FixedOutput, HashMarker, OutputSizeUser, +}; +use dsa::{Components, PrivateKey, PublicKey, Signature}; +use num_bigint::BigUint; +use num_traits::Num; +use sha1::Sha1; +use sha2::{Sha224, Sha256, Sha384, Sha512}; +use signature::DigestSigner; + +fn dsa_1024_private_key() -> PrivateKey { + let p = BigUint::from_str_radix( + "86F5CA03DCFEB225063FF830A0C769B9DD9D6153AD91D7CE27F787C43278B447\ + E6533B86B18BED6E8A48B784A14C252C5BE0DBF60B86D6385BD2F12FB763ED88\ + 73ABFD3F5BA2E0A8C0A59082EAC056935E529DAF7C610467899C77ADEDFC846C\ + 881870B7B19B2B58F9BE0521A17002E3BDD6B86685EE90B3D9A1B02B782B1779", + 16, + ) + .unwrap(); + let q = BigUint::from_str_radix("996F967F6C8E388D9E28D01E205FBA957A5698B1", 16).unwrap(); + let g = BigUint::from_str_radix( + "07B0F92546150B62514BB771E2A0C0CE387F03BDA6C56B505209FF25FD3C133D\ + 89BBCD97E904E09114D9A7DEFDEADFC9078EA544D2E401AEECC40BB9FBBF78FD\ + 87995A10A1C27CB7789B594BA7EFB5C4326A9FE59A070E136DB77175464ADCA4\ + 17BE5DCE2F40D10A46A3A3943F26AB7FD9C0398FF8C76EE0A56826A8A88F1DBD", + 16, + ) + .unwrap(); + + let x = BigUint::from_str_radix("411602CB19A6CCC34494D79D98EF1E7ED5AF25F7", 16).unwrap(); + let y = BigUint::from_str_radix( + "5DF5E01DED31D0297E274E1691C192FE5868FEF9E19A84776454B100CF16F653\ + 92195A38B90523E2542EE61871C0440CB87C322FC4B4D2EC5E1E7EC766E1BE8D\ + 4CE935437DC11C3C8FD426338933EBFE739CB3465F4D3668C5E473508253B1E6\ + 82F65CBDC4FAE93C2EA212390E54905A86E2223170B44EAA7DA5DD9FFCFB7F3B", + 16, + ) + .unwrap(); + + let components = Components::from_components(p, q, g); + let public_key = PublicKey::from_components(components, y); + + PrivateKey::from_components(public_key, x) +} + +const MESSAGE: &[u8] = b"sample"; +const MESSAGE_2: &[u8] = b"test"; + +/// Generate a signature given the unhashed message and a private key +fn generate_signature(private_key: PrivateKey, data: &[u8]) -> Signature +where + D: Digest + CoreProxy + FixedOutput, + D::Core: BlockSizeUser + + BufferKindUser + + Clone + + Default + + FixedOutputCore + + HashMarker + + OutputSizeUser, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + private_key.sign_digest(::new().chain_update(data)) +} + +/// Generate a signature using the 1024-bit DSA key +fn generate_1024_signature(data: &[u8]) -> Signature +where + D: Digest + CoreProxy + FixedOutput, + D::Core: BlockSizeUser + + BufferKindUser + + Clone + + Default + + FixedOutputCore + + HashMarker + + OutputSizeUser, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + generate_signature::(dsa_1024_private_key(), data) +} + +/// Create a signature container from the two components in their textual hexadecimal form +fn from_str_signature(r: &str, s: &str) -> Signature { + Signature::from_components( + BigUint::from_str_radix(r, 16).unwrap(), + BigUint::from_str_radix(s, 16).unwrap(), + ) +} + +/// Return the RFC 6979 test cases +/// +/// # Returns +/// +/// Vector of tuples. +/// First element is the message appended to the panic upon signature mismatch, the second one is the expected signature and the third element is a function +/// that generates an RFC-6979 signature using the `dsa` crate +fn cases() -> Vec<(Signature, Box Signature>)> { + vec![ + // sha1, 1024, "sample" + ( + from_str_signature( + "2E1A0C2562B2912CAAF89186FB0F42001585DA55", + "29EFB6B0AFF2D7A68EB70CA313022253B9A88DF5", + ), + Box::new(|| generate_1024_signature::(MESSAGE)), + ), + // sha1, 1024, "test" + ( + from_str_signature( + "42AB2052FD43E123F0607F115052A67DCD9C5C77", + "183916B0230D45B9931491D4C6B0BD2FB4AAF088", + ), + Box::new(|| generate_1024_signature::(MESSAGE_2)), + ), + // sha224, 1024, "sample" + ( + from_str_signature( + "4BC3B686AEA70145856814A6F1BB53346F02101E", + "410697B92295D994D21EDD2F4ADA85566F6F94C1", + ), + Box::new(|| generate_1024_signature::(MESSAGE)), + ), + // sha224, 1024, "test" + ( + from_str_signature( + "6868E9964E36C1689F6037F91F28D5F2C30610F2", + "49CEC3ACDC83018C5BD2674ECAAD35B8CD22940F", + ), + Box::new(|| generate_1024_signature::(MESSAGE_2)), + ), + // sha256, 1024, "sample" + ( + from_str_signature( + "81F2F5850BE5BC123C43F71A3033E9384611C545", + "4CDD914B65EB6C66A8AAAD27299BEE6B035F5E89", + ), + Box::new(|| generate_1024_signature::(MESSAGE)), + ), + // sha256, 1024, "test" + ( + from_str_signature( + "22518C127299B0F6FDC9872B282B9E70D0790812", + "6837EC18F150D55DE95B5E29BE7AF5D01E4FE160", + ), + Box::new(|| generate_1024_signature::(MESSAGE_2)), + ), + // sha384, 1024, "sample" + ( + from_str_signature( + "07F2108557EE0E3921BC1774F1CA9B410B4CE65A", + "54DF70456C86FAC10FAB47C1949AB83F2C6F7595", + ), + Box::new(|| generate_1024_signature::(MESSAGE)), + ), + // sha384, 1024, "test" + ( + from_str_signature( + "854CF929B58D73C3CBFDC421E8D5430CD6DB5E66", + "91D0E0F53E22F898D158380676A871A157CDA622", + ), + Box::new(|| generate_1024_signature::(MESSAGE_2)), + ), + // sha512, 1024, "sample" + ( + from_str_signature( + "16C3491F9B8C3FBBDD5E7A7B667057F0D8EE8E1B", + "02C36A127A7B89EDBB72E4FFBC71DABC7D4FC69C", + ), + Box::new(|| generate_1024_signature::(MESSAGE)), + ), + // sha512, 1024, "test" + ( + from_str_signature( + "8EA47E475BA8AC6F2D821DA3BD212D11A3DEB9A0", + "7C670C7AD72B6C050C109E1790008097125433E8", + ), + Box::new(|| generate_1024_signature::(MESSAGE_2)), + ), + ] +} + +#[test] +fn rfc6979_signatures() { + for (idx, (expected, gen_fn)) in cases().into_iter().enumerate() { + assert_eq!(expected, gen_fn(), "{}th test case", idx); + } +} diff --git a/dsa/tests/privatekey.rs b/dsa/tests/privatekey.rs new file mode 100644 index 00000000..01c408c0 --- /dev/null +++ b/dsa/tests/privatekey.rs @@ -0,0 +1,57 @@ +// We abused the deprecated attribute for unsecure key sizes +// But we want to use those small key sizes for fast tests +#![allow(deprecated)] + +use digest::Digest; +use dsa::{consts::DSA_1024_160, Components, PrivateKey}; +use num_bigint::BigUint; +use num_traits::Zero; +use pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}; +use sha1::Sha1; +use signature::{DigestVerifier, RandomizedDigestSigner}; + +fn generate_keypair() -> PrivateKey { + let mut rng = rand::thread_rng(); + let components = Components::generate(&mut rng, DSA_1024_160); + PrivateKey::generate(&mut rng, components) +} + +#[test] +fn encode_decode_private_key() { + let private_key = generate_keypair(); + let encoded_private_key = private_key.to_pkcs8_pem(LineEnding::LF).unwrap(); + let decoded_private_key = PrivateKey::from_pkcs8_pem(&encoded_private_key).unwrap(); + + assert_eq!(private_key, decoded_private_key); +} + +#[test] +fn sign_and_verify() { + const DATA: &[u8] = b"SIGN AND VERIFY THOSE BYTES"; + + let private_key = generate_keypair(); + let public_key = private_key.public_key(); + + let signature = + private_key.sign_digest_with_rng(rand::thread_rng(), Sha1::new().chain_update(DATA)); + + assert!(public_key + .verify_digest(Sha1::new().chain_update(DATA), &signature) + .is_ok()); +} + +#[test] +fn verify_validity() { + let private_key = generate_keypair(); + let components = private_key.public_key().components(); + + assert!( + BigUint::zero() < *private_key.x() && private_key.x() < components.q(), + "Requirement 0 PublicKey { + let mut rng = rand::thread_rng(); + let components = Components::generate(&mut rng, DSA_1024_160); + let private_key = PrivateKey::generate(&mut rng, components); + + private_key.public_key().clone() +} + +#[test] +fn encode_decode_public_key() { + let public_key = generate_public_key(); + let encoded_public_key = public_key.to_public_key_pem(LineEnding::LF).unwrap(); + let decoded_public_key = PublicKey::from_public_key_pem(&encoded_public_key).unwrap(); + + assert_eq!(public_key, decoded_public_key); +} + +#[test] +fn validate_public_key() { + let public_key = generate_public_key(); + let p = public_key.components().p(); + let q = public_key.components().q(); + + // Taken from the parameter validation from bouncy castle + assert_eq!(public_key.y().modpow(q, p), BigUint::one()); +} From 630380f950a240fb80a8fa5c66dcc3de00ebcb5f Mon Sep 17 00:00:00 2001 From: aumetra Date: Fri, 13 May 2022 16:07:16 +0200 Subject: [PATCH 18/27] dsa: Add 2048-bit key tests --- dsa/tests/deterministic.rs | 156 ++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 2 deletions(-) diff --git a/dsa/tests/deterministic.rs b/dsa/tests/deterministic.rs index c2657a5a..1b21d4d4 100644 --- a/dsa/tests/deterministic.rs +++ b/dsa/tests/deterministic.rs @@ -12,6 +12,9 @@ use sha1::Sha1; use sha2::{Sha224, Sha256, Sha384, Sha512}; use signature::DigestSigner; +const MESSAGE: &[u8] = b"sample"; +const MESSAGE_2: &[u8] = b"test"; + fn dsa_1024_private_key() -> PrivateKey { let p = BigUint::from_str_radix( "86F5CA03DCFEB225063FF830A0C769B9DD9D6153AD91D7CE27F787C43278B447\ @@ -47,8 +50,60 @@ fn dsa_1024_private_key() -> PrivateKey { PrivateKey::from_components(public_key, x) } -const MESSAGE: &[u8] = b"sample"; -const MESSAGE_2: &[u8] = b"test"; +fn dsa_2048_private_key() -> PrivateKey { + let p = BigUint::from_str_radix( + "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48\ + C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F\ + FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5\ + B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2\ + 35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41\ + F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE\ + 92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15\ + 3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B", + 16, + ) + .unwrap(); + let q = BigUint::from_str_radix( + "F2C3119374CE76C9356990B465374A17F23F9ED35089BD969F61C6DDE9998C1F", + 16, + ) + .unwrap(); + let g = BigUint::from_str_radix( + "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613\ + D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4\ + 6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472\ + 085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5\ + AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA\ + 3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71\ + BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0\ + DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", + 16, + ) + .unwrap(); + + let x = BigUint::from_str_radix( + "69C7548C21D0DFEA6B9A51C9EAD4E27C33D3B3F180316E5BCAB92C933F0E4DBC", + 16, + ) + .unwrap(); + let y = BigUint::from_str_radix( + "667098C654426C78D7F8201EAC6C203EF030D43605032C2F1FA937E5237DBD94\ + 9F34A0A2564FE126DC8B715C5141802CE0979C8246463C40E6B6BDAA2513FA61\ + 1728716C2E4FD53BC95B89E69949D96512E873B9C8F8DFD499CC312882561ADE\ + CB31F658E934C0C197F2C4D96B05CBAD67381E7B768891E4DA3843D24D94CDFB\ + 5126E9B8BF21E8358EE0E0A30EF13FD6A664C0DCE3731F7FB49A4845A4FD8254\ + 687972A2D382599C9BAC4E0ED7998193078913032558134976410B89D2C171D1\ + 23AC35FD977219597AA7D15C1A9A428E59194F75C721EBCBCFAE44696A499AFA\ + 74E04299F132026601638CB87AB79190D4A0986315DA8EEC6561C938996BEADF", + 16, + ) + .unwrap(); + + let components = Components::from_components(p, q, g); + let public_key = PublicKey::from_components(components, y); + + PrivateKey::from_components(public_key, x) +} /// Generate a signature given the unhashed message and a private key fn generate_signature(private_key: PrivateKey, data: &[u8]) -> Signature @@ -84,6 +139,23 @@ where generate_signature::(dsa_1024_private_key(), data) } +/// Generate a signature using the 2048-bit DSA key +fn generate_2048_signature(data: &[u8]) -> Signature +where + D: Digest + CoreProxy + FixedOutput, + D::Core: BlockSizeUser + + BufferKindUser + + Clone + + Default + + FixedOutputCore + + HashMarker + + OutputSizeUser, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + generate_signature::(dsa_2048_private_key(), data) +} + /// Create a signature container from the two components in their textual hexadecimal form fn from_str_signature(r: &str, s: &str) -> Signature { Signature::from_components( @@ -181,6 +253,86 @@ fn cases() -> Vec<(Signature, Box Signature>)> { ), Box::new(|| generate_1024_signature::(MESSAGE_2)), ), + // sha1, 2048, "sample" + ( + from_str_signature( + "3A1B2DBD7489D6ED7E608FD036C83AF396E290DBD602408E8677DAABD6E7445A", + "D26FCBA19FA3E3058FFC02CA1596CDBB6E0D20CB37B06054F7E36DED0CDBBCCF", + ), + Box::new(|| generate_2048_signature::(MESSAGE)), + ), + // sha1, 2048, "test" + ( + from_str_signature( + "C18270A93CFC6063F57A4DFA86024F700D980E4CF4E2CB65A504397273D98EA0", + "414F22E5F31A8B6D33295C7539C1C1BA3A6160D7D68D50AC0D3A5BEAC2884FAA", + ), + Box::new(|| generate_2048_signature::(MESSAGE_2)), + ), + // sha224, 2048, "sample" + ( + from_str_signature( + "DC9F4DEADA8D8FF588E98FED0AB690FFCE858DC8C79376450EB6B76C24537E2C", + "A65A9C3BC7BABE286B195D5DA68616DA8D47FA0097F36DD19F517327DC848CEC", + ), + Box::new(|| generate_2048_signature::(MESSAGE)), + ), + // sha224, 2048, "test" + ( + from_str_signature( + "272ABA31572F6CC55E30BF616B7A265312018DD325BE031BE0CC82AA17870EA3", + "E9CC286A52CCE201586722D36D1E917EB96A4EBDB47932F9576AC645B3A60806", + ), + Box::new(|| generate_2048_signature::(MESSAGE_2)), + ), + // sha256, 2048, "sample" + ( + from_str_signature( + "EACE8BDBBE353C432A795D9EC556C6D021F7A03F42C36E9BC87E4AC7932CC809", + "7081E175455F9247B812B74583E9E94F9EA79BD640DC962533B0680793A38D53", + ), + Box::new(|| generate_2048_signature::(MESSAGE)), + ), + // sha256, 2048, "test" + ( + from_str_signature( + "8190012A1969F9957D56FCCAAD223186F423398D58EF5B3CEFD5A4146A4476F0", + "7452A53F7075D417B4B013B278D1BB8BBD21863F5E7B1CEE679CF2188E1AB19E", + ), + Box::new(|| generate_2048_signature::(MESSAGE_2)), + ), + // sha384, 2048, "sample" + ( + from_str_signature( + "B2DA945E91858834FD9BF616EBAC151EDBC4B45D27D0DD4A7F6A22739F45C00B", + "19048B63D9FD6BCA1D9BAE3664E1BCB97F7276C306130969F63F38FA8319021B", + ), + Box::new(|| generate_2048_signature::(MESSAGE)), + ), + // sha384, 2048, "test" + ( + from_str_signature( + "239E66DDBE8F8C230A3D071D601B6FFBDFB5901F94D444C6AF56F732BEB954BE", + "6BD737513D5E72FE85D1C750E0F73921FE299B945AAD1C802F15C26A43D34961", + ), + Box::new(|| generate_2048_signature::(MESSAGE_2)), + ), + // sha512, 2048, "sample" + ( + from_str_signature( + "2016ED092DC5FB669B8EFB3D1F31A91EECB199879BE0CF78F02BA062CB4C942E", + "D0C76F84B5F091E141572A639A4FB8C230807EEA7D55C8A154A224400AFF2351", + ), + Box::new(|| generate_2048_signature::(MESSAGE)), + ), + // sha512, 2048, "test" + ( + from_str_signature( + "89EC4BB1400ECCFF8E7D9AA515CD1DE7803F2DAFF09693EE7FD1353E90A68307", + "C9F0BDABCC0D880BB137A994CC7F3980CE91CC10FAF529FC46565B15CEA854E1", + ), + Box::new(|| generate_2048_signature::(MESSAGE_2)), + ), ] } From 080ab30c6a24f470d243fbd613ce17758ba17abd Mon Sep 17 00:00:00 2001 From: aumetra Date: Fri, 13 May 2022 16:36:05 +0200 Subject: [PATCH 19/27] dsa: Fix RFC 6979 hash reduction function --- dsa/src/generate/secret_number.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/dsa/src/generate/secret_number.rs b/dsa/src/generate/secret_number.rs index 7a904929..8a358556 100644 --- a/dsa/src/generate/secret_number.rs +++ b/dsa/src/generate/secret_number.rs @@ -21,11 +21,19 @@ use zeroize::Zeroize; /// Reduce the hash into an RFC-6979 appropriate form fn reduce_hash(q: &BigUint, hash: &[u8]) -> Vec { // Reduce the hash modulo Q - let hash_len = min(hash.len(), q.bits() / 8); + let q_byte_len = q.bits() / 8; + + let hash_len = min(hash.len(), q_byte_len); let hash = &hash[..hash_len]; let hash = BigUint::from_bytes_be(hash); - (hash % q).to_bytes_be() + let mut reduced = (hash % q).to_bytes_be(); + + while reduced.len() < q_byte_len { + reduced.insert(0, 0); + } + + reduced } /// Generate a per-message secret number k deterministically using the method described in RFC 6979 From 68714fe1de6323e124a7bfd48ddcbe14a86ad3a6 Mon Sep 17 00:00:00 2001 From: aumetra Date: Fri, 13 May 2022 16:53:20 +0200 Subject: [PATCH 20/27] dsa: Update README --- dsa/README.md | 86 +++++++++++++++++++++++++------------------ dsa/src/components.rs | 3 +- dsa/src/lib.rs | 39 +++++--------------- 3 files changed, 61 insertions(+), 67 deletions(-) diff --git a/dsa/README.md b/dsa/README.md index aa996888..6b73b7a0 100644 --- a/dsa/README.md +++ b/dsa/README.md @@ -1,52 +1,68 @@ -# dsa +# [RustCrypto]: DSA +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![MSRV][rustc-image] +[![Project Chat][chat-image]][chat-link] -DSA implementation in pure Rust +[Digital Signature Algorithm (DSA)][1] as specified in +[FIPS 186-4][2] (Digital Signature Standard). -## Disclaimer +[Documentation][docs-link] -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. +## About -This software has **NOT** been audited and therefore most likely contains security issues! +This crate provides an implementation of DSA in pure Rust. +It utilises the [`signature`] crate to provide an interface for creating and verifying signatures. -**USE AT YOUR OWN RISK!** +## Minimum Supported Rust Version -### Implementation progress +This crate requires **Rust 1.57** at a minimum. -- [x] Generate components -- [x] Generate keypair -- [x] Import keys -- [x] Export keys -- [x] Sign data -- [x] Verify signatures -- [ ] Test vectors +We may change the MSRV in the future, but it will be accompanied by a minor +version bump. -### Example +## License -Generate a DSA keypair +All crates licensed under either of -```rust -let mut csprng = rand::thread_rng(); -let components = Components::generate(&mut csprng, DSA_2048_256); -let private_key = PrivateKey::generate(&mut csprng, components); -let public_key = private_key.public_key(); -``` + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) -Create keypair from existing components +at your option. -```rust -let (p, q, g) = read_common_parameters(); -let components = Components::from_components(p, q, g); +### Contribution -let x = read_public_component(); -let public_key = PublicKey::from_components(components, x); +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. -let y = read_private_component(); -let private_key = PrivateKey::from_components(public_key, y); -``` +[//]: # (badges) +[crate-image]: https://buildstats.info/crate/dsa +[crate-link]: https://crates.io/crates/dsa +[docs-image]: https://docs.rs/dsa/badge.svg +[docs-link]: https://docs.rs/dsa/ +[build-image]: https://github.com/RustCrypto/signatures/actions/workflows/dsa.yml/badge.svg +[build-link]: https://github.com/RustCrypto/signatures/actions/workflows/dsa.yml +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.57+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260048-signatures -License: Apache-2.0 OR MIT +[//]: # (links) + +[RustCrypto]: https://github.com/RustCrypto + +[//]: # (footnotes) + +[1]: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm +[2]: https://csrc.nist.gov/publications/detail/fips/186/4/final + +[//]: # (docs.rs definitions) + +[`signature`]: https://docs.rs/signature +[`signature::Signer`]: https://docs.rs/signature/latest/signature/trait.Signer.html +[`signature::Verifier`]: https://docs.rs/signature/latest/signature/trait.Verifier.html \ No newline at end of file diff --git a/dsa/src/components.rs b/dsa/src/components.rs index 5e70ae1e..618c945f 100644 --- a/dsa/src/components.rs +++ b/dsa/src/components.rs @@ -2,13 +2,12 @@ //! Module containing the definition of the common components container //! +use crate::two; use num_bigint::BigUint; use num_traits::One; use pkcs8::der::{self, asn1::UIntRef, DecodeValue, Encode, Header, Reader, Sequence}; use rand::{CryptoRng, RngCore}; -use crate::two; - /// The common components of an DSA keypair /// /// (the prime p, quotient q and generator g) diff --git a/dsa/src/lib.rs b/dsa/src/lib.rs index 120e2d9f..96be218e 100644 --- a/dsa/src/lib.rs +++ b/dsa/src/lib.rs @@ -1,28 +1,12 @@ -//! -//! DSA implementation in pure Rust -//! -//! # Disclaimer -//! -//! 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. -//! -//! This software has **NOT** been audited and therefore most likely contains security issues! -//! -//! **USE AT YOUR OWN RISK!** -//! -//! ## Implementation progress -//! -//! - [x] Generate components -//! - [x] Generate keypair -//! - [x] Import keys -//! - [x] Export keys -//! - [x] Sign data -//! - [x] Verify signatures -//! - [ ] Test vectors -//! -//! ## Example +#![no_std] +#![forbid(missing_docs, unsafe_code)] +#![deny(rust_2018_idioms)] +#![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" +)] + //! //! Generate a DSA keypair //! @@ -54,10 +38,6 @@ //! ``` //! -#![no_std] -#![forbid(missing_docs, unsafe_code)] -#![deny(rust_2018_idioms)] - extern crate alloc; /// DSA object identifier as defined by RFC-3279, section 2.3.2 @@ -68,7 +48,6 @@ pub use self::privatekey::PrivateKey; pub use self::publickey::PublicKey; pub use self::sig::Signature; -// Re-export the types needed for de-/serialising keys to DER and PEM pub use pkcs8; pub use signature; From 40e7fd28ca20115429f7594aa055b0df5609a9a7 Mon Sep 17 00:00:00 2001 From: aumetra Date: Fri, 13 May 2022 21:06:45 +0200 Subject: [PATCH 21/27] dsa: Add signature tests --- Cargo.lock | 1 + dsa/Cargo.toml | 1 + dsa/src/sig.rs | 24 ++++-------- dsa/tests/signature.rs | 89 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 dsa/tests/signature.rs diff --git a/Cargo.lock b/Cargo.lock index d1ac11eb..2b848469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,6 +151,7 @@ dependencies = [ "paste", "pkcs8", "rand 0.8.5", + "rand_chacha 0.3.1", "rfc6979", "sha1", "sha2 0.10.2", diff --git a/dsa/Cargo.toml b/dsa/Cargo.toml index b769011a..afd2fe20 100644 --- a/dsa/Cargo.toml +++ b/dsa/Cargo.toml @@ -26,5 +26,6 @@ default = [] [dev-dependencies] pkcs8 = { version = "0.9.0", default-features = false, features = ["pem"] } rand = "0.8.5" +rand_chacha = "0.3.1" sha1 = "0.10.1" sha2 = "0.10.2" diff --git a/dsa/src/sig.rs b/dsa/src/sig.rs index 20b0ce3e..0c291de8 100644 --- a/dsa/src/sig.rs +++ b/dsa/src/sig.rs @@ -4,7 +4,7 @@ use alloc::vec::Vec; use num_bigint::BigUint; -use pkcs8::der::{self, asn1::UIntRef, Decode, Encode, Reader, Sequence, SliceReader}; +use pkcs8::der::{self, asn1::UIntRef, Decode, Encode, Reader, Sequence}; /// Container of the DSA signature #[derive(Clone)] @@ -35,16 +35,6 @@ impl Signature { signature } - /// Decode a Signature from its DER representation - /// - /// # Errors - /// - /// See the [`der` errors](::pkcs8::der::Error) - pub fn from_der(data: &[u8]) -> der::Result { - let mut reader = SliceReader::new(data)?; - reader.decode() - } - /// Signature part r #[must_use] pub fn r(&self) -> &BigUint { @@ -66,13 +56,15 @@ impl AsRef<[u8]> for Signature { impl<'a> Decode<'a> for Signature { fn decode>(reader: &mut R) -> der::Result { - let r = reader.decode::>()?; - let s = reader.decode::>()?; + reader.sequence(|sequence| { + let r = sequence.decode::>()?; + let s = sequence.decode::>()?; - let r = BigUint::from_bytes_be(r.as_bytes()); - let s = BigUint::from_bytes_be(s.as_bytes()); + let r = BigUint::from_bytes_be(r.as_bytes()); + let s = BigUint::from_bytes_be(s.as_bytes()); - Ok(Self::from_components(r, s)) + Ok(Self::from_components(r, s)) + }) } } diff --git a/dsa/tests/signature.rs b/dsa/tests/signature.rs new file mode 100644 index 00000000..8d4634f4 --- /dev/null +++ b/dsa/tests/signature.rs @@ -0,0 +1,89 @@ +#![allow(deprecated)] + +use digest::Digest; +use dsa::{consts::DSA_1024_160, Components, PrivateKey, Signature}; +use pkcs8::der::{Decode, Encode}; +use rand::{CryptoRng, RngCore, SeedableRng}; +use rand_chacha::ChaCha8Rng; +use sha2::Sha256; +use signature::{DigestVerifier, RandomizedDigestSigner}; + +/// Seed used for the ChaCha8 RNG +const SEED: u64 = 0x2103_1949; + +/// Message to be signed/verified +const MESSAGE: &[u8] = b"test"; + +/// Message signed by this crate using the keys generated by this CSPRNG +/// +/// This signature was generated using the keys generated by this CSPRNG (the per-message `k` component was also generated using the CSPRNG) +const MESSAGE_SIGNATURE_CRATE_ASN1: &[u8] = &[ + 0x30, 0x2C, 0x02, 0x14, 0x45, 0x1D, 0xE5, 0x76, 0x21, 0xD8, 0xFD, 0x76, 0xC1, 0x6F, 0x45, 0x4E, + 0xDE, 0x5F, 0x09, 0x79, 0x76, 0x52, 0xF3, 0xA5, 0x02, 0x14, 0x53, 0x60, 0xE6, 0xB7, 0xF0, 0xCF, + 0xAE, 0x49, 0xB1, 0x58, 0x5C, 0xCF, 0x5F, 0x3F, 0x94, 0x49, 0x21, 0xA0, 0xBF, 0xD2, +]; + +/// Message signed by OpenSSL using the keys generated by this CSPRNG +/// +/// This signature was generated using the SHA-256 digest +const MESSAGE_SIGNATURE_OPENSSL_ASN1: &[u8] = &[ + 0x30, 0x2C, 0x02, 0x14, 0x6D, 0xB3, 0x8E, 0xAF, 0x97, 0x13, 0x7E, 0x07, 0xFF, 0x24, 0xB8, 0x66, + 0x97, 0x18, 0xE1, 0x6F, 0xD7, 0x9A, 0x28, 0x2D, 0x02, 0x14, 0x47, 0x8C, 0x0B, 0x96, 0x51, 0x08, + 0x08, 0xC8, 0x34, 0x9D, 0x0D, 0x41, 0xC7, 0x73, 0x0F, 0xB5, 0x9C, 0xBB, 0x00, 0x34, +]; + +/// Get the seeded CSPRNG +fn seeded_csprng() -> impl CryptoRng + RngCore { + ChaCha8Rng::seed_from_u64(SEED) +} + +/// Generate a DSA keypair using a seeded CSPRNG +fn generate_deterministic_keypair() -> PrivateKey { + let mut rng = seeded_csprng(); + let components = Components::generate(&mut rng, DSA_1024_160); + PrivateKey::generate(&mut rng, components) +} + +#[test] +fn decode_encode_signature() { + let signature_openssl = + Signature::from_der(MESSAGE_SIGNATURE_OPENSSL_ASN1).expect("Failed to decode signature"); + let encoded_signature_openssl = signature_openssl + .to_vec() + .expect("Failed to encode signature"); + + assert_eq!(MESSAGE_SIGNATURE_OPENSSL_ASN1, encoded_signature_openssl); + + let signature_crate = + Signature::from_der(MESSAGE_SIGNATURE_CRATE_ASN1).expect("Failed to decode signature"); + let encoded_signature_crate = signature_crate + .to_vec() + .expect("Failed to encode signature"); + + assert_eq!(MESSAGE_SIGNATURE_CRATE_ASN1, encoded_signature_crate); +} + +#[test] +fn sign_message() { + let private_key = generate_deterministic_keypair(); + let generated_signature = + private_key.sign_digest_with_rng(seeded_csprng(), Sha256::new().chain_update(MESSAGE)); + + let expected_signature = + Signature::from_der(MESSAGE_SIGNATURE_CRATE_ASN1).expect("Failed to decode signature"); + + assert_eq!(generated_signature, expected_signature); +} + +#[test] +fn verify_signature() { + let private_key = generate_deterministic_keypair(); + let public_key = private_key.public_key(); + + let signature = Signature::from_der(MESSAGE_SIGNATURE_OPENSSL_ASN1) + .expect("Failed to parse ASN.1 representation of the test signature"); + + assert!(public_key + .verify_digest(Sha256::new().chain_update(MESSAGE), &signature) + .is_ok()); +} From c2187f7d1d105a25a49dc70601e25c17561a244d Mon Sep 17 00:00:00 2001 From: aumetra Date: Sat, 14 May 2022 16:02:21 +0200 Subject: [PATCH 22/27] dsa: Add OpenSSL encoding/decoding tests --- dsa/tests/components.rs | 25 +++++++++++++++++++++++++ dsa/tests/pems/params.pem | 14 ++++++++++++++ dsa/tests/pems/private.pem | 15 +++++++++++++++ dsa/tests/pems/public.pem | 20 ++++++++++++++++++++ dsa/tests/privatekey.rs | 15 +++++++++++++++ dsa/tests/publickey.rs | 15 +++++++++++++++ 6 files changed, 104 insertions(+) create mode 100644 dsa/tests/components.rs create mode 100644 dsa/tests/pems/params.pem create mode 100644 dsa/tests/pems/private.pem create mode 100644 dsa/tests/pems/public.pem diff --git a/dsa/tests/components.rs b/dsa/tests/components.rs new file mode 100644 index 00000000..e3bb04aa --- /dev/null +++ b/dsa/tests/components.rs @@ -0,0 +1,25 @@ +use dsa::Components; +use pkcs8::{ + der::{Decode, Encode}, + Document, +}; + +const OPENSSL_PEM_COMPONENTS: &str = include_str!("pems/params.pem"); + +#[test] +fn decode_encode_openssl_components() { + let (_, document) = + Document::from_pem(OPENSSL_PEM_COMPONENTS).expect("Failed to parse components PEM"); + let raw_components = document.as_bytes(); + + let components = + Components::from_der(raw_components).expect("Failed to parse DER into component structure"); + + assert!(components.is_valid()); + + let reencoded_components = components + .to_vec() + .expect("Failed to encode components to DER"); + + assert_eq!(raw_components, reencoded_components); +} diff --git a/dsa/tests/pems/params.pem b/dsa/tests/pems/params.pem new file mode 100644 index 00000000..7f24db1d --- /dev/null +++ b/dsa/tests/pems/params.pem @@ -0,0 +1,14 @@ +-----BEGIN DSA PARAMETERS----- +MIICLAKCAQEAkGWPlAeMc0XCaHO80sng7cLuefKO4FS5N3Bp95ZV7M14z0lOZz5X +69OfpacMM9uWPplYS/yY1XhpybBDbGVE+nU6PlTjrCZxIaSeyMg1paa7iyVQvSJ9 +yJfcl+6ufSMtNB7fwJklodaJsSgst2rKC0QtBNkCIxl0/q3GI3uF4GVxUQ0Y9H8K +O8Fj2Vn3ju4/pTk32dR12RK1vhvfUR9LCpz5WTdnWdKa2lcCmppUBTHG2ZEWUHju +P2XcI295mjE71TR1W+XwpRznLhHF0v+da1tzLP9q8xiGFyYqI0iOGAWPKJPjfTlq +/kqo/AzwiB3dRU2R7XbGvLKUhMPZTiNBmQIhAJzihmrV1ZA2bA9/416eDLdQGrUB +u0qd3ZEvBpwzTjxPAoIBAGOua3Cqupe0wqmjb4PhnMNvoudl3g0TCuFwxX+ClsaN +uTio1VPQ6/EmOaS+69ItkAdjcHWCMmuEoa9kpzEroBOSQbu7lek1CS0X89bt0fOR +TSI54rJR8iBYSvePC9IcnOWTyyp0vrxKb+OGLvUQ2cy0wsRGXt9/DVx1k8+OLlBk +Enf2z4IS2Oqsh34ZNqRktiIkY0NEAhHE5yVdVJbTXV7w5BYeiJqnXV6dEjYFukqh +vFKcXD1n2iC9xP6oBbgphYULIcPfCZVnJTpPiVz+YqDIh6iVC58QYZebNSccMHY2 +DxY8zZUb0kHSyaDtYm3CY2Nn8WFFQoqQqNfb+PB9dLs= +-----END DSA PARAMETERS----- diff --git a/dsa/tests/pems/private.pem b/dsa/tests/pems/private.pem new file mode 100644 index 00000000..02d6fa99 --- /dev/null +++ b/dsa/tests/pems/private.pem @@ -0,0 +1,15 @@ +-----BEGIN PRIVATE KEY----- +MIICZAIBADCCAjkGByqGSM44BAEwggIsAoIBAQCQZY+UB4xzRcJoc7zSyeDtwu55 +8o7gVLk3cGn3llXszXjPSU5nPlfr05+lpwwz25Y+mVhL/JjVeGnJsENsZUT6dTo+ +VOOsJnEhpJ7IyDWlpruLJVC9In3Il9yX7q59Iy00Ht/AmSWh1omxKCy3asoLRC0E +2QIjGXT+rcYje4XgZXFRDRj0fwo7wWPZWfeO7j+lOTfZ1HXZErW+G99RH0sKnPlZ +N2dZ0praVwKamlQFMcbZkRZQeO4/Zdwjb3maMTvVNHVb5fClHOcuEcXS/51rW3Ms +/2rzGIYXJiojSI4YBY8ok+N9OWr+Sqj8DPCIHd1FTZHtdsa8spSEw9lOI0GZAiEA +nOKGatXVkDZsD3/jXp4Mt1AatQG7Sp3dkS8GnDNOPE8CggEAY65rcKq6l7TCqaNv +g+Gcw2+i52XeDRMK4XDFf4KWxo25OKjVU9Dr8SY5pL7r0i2QB2NwdYIya4Shr2Sn +MSugE5JBu7uV6TUJLRfz1u3R85FNIjnislHyIFhK948L0hyc5ZPLKnS+vEpv44Yu +9RDZzLTCxEZe338NXHWTz44uUGQSd/bPghLY6qyHfhk2pGS2IiRjQ0QCEcTnJV1U +ltNdXvDkFh6ImqddXp0SNgW6SqG8UpxcPWfaIL3E/qgFuCmFhQshw98JlWclOk+J +XP5ioMiHqJULnxBhl5s1JxwwdjYPFjzNlRvSQdLJoO1ibcJjY2fxYUVCipCo19v4 +8H10uwQiAiBcmS3IlLPz13zgcj5s6pMApi3F2TshHKiCMp7D866qjA== +-----END PRIVATE KEY----- diff --git a/dsa/tests/pems/public.pem b/dsa/tests/pems/public.pem new file mode 100644 index 00000000..1b01f1f5 --- /dev/null +++ b/dsa/tests/pems/public.pem @@ -0,0 +1,20 @@ +-----BEGIN PUBLIC KEY----- +MIIDRjCCAjkGByqGSM44BAEwggIsAoIBAQCQZY+UB4xzRcJoc7zSyeDtwu558o7g +VLk3cGn3llXszXjPSU5nPlfr05+lpwwz25Y+mVhL/JjVeGnJsENsZUT6dTo+VOOs +JnEhpJ7IyDWlpruLJVC9In3Il9yX7q59Iy00Ht/AmSWh1omxKCy3asoLRC0E2QIj +GXT+rcYje4XgZXFRDRj0fwo7wWPZWfeO7j+lOTfZ1HXZErW+G99RH0sKnPlZN2dZ +0praVwKamlQFMcbZkRZQeO4/Zdwjb3maMTvVNHVb5fClHOcuEcXS/51rW3Ms/2rz +GIYXJiojSI4YBY8ok+N9OWr+Sqj8DPCIHd1FTZHtdsa8spSEw9lOI0GZAiEAnOKG +atXVkDZsD3/jXp4Mt1AatQG7Sp3dkS8GnDNOPE8CggEAY65rcKq6l7TCqaNvg+Gc +w2+i52XeDRMK4XDFf4KWxo25OKjVU9Dr8SY5pL7r0i2QB2NwdYIya4Shr2SnMSug +E5JBu7uV6TUJLRfz1u3R85FNIjnislHyIFhK948L0hyc5ZPLKnS+vEpv44Yu9RDZ +zLTCxEZe338NXHWTz44uUGQSd/bPghLY6qyHfhk2pGS2IiRjQ0QCEcTnJV1UltNd +XvDkFh6ImqddXp0SNgW6SqG8UpxcPWfaIL3E/qgFuCmFhQshw98JlWclOk+JXP5i +oMiHqJULnxBhl5s1JxwwdjYPFjzNlRvSQdLJoO1ibcJjY2fxYUVCipCo19v48H10 +uwOCAQUAAoIBACMQg2b+H3R2sEsTWDaE9w9qOzWiIpmX+ZbErFqkzmNO3C3IFJUU +ZoJoc/SHYV3YF/LOTJEAbvD6IJoRGgPFHzLG+0OcaBdGcSanHF8wFBu169wTeCuO +vcok3AObiwakjUd3auufHUCct6KsLNM2ssG3pwOS+bYz4YfaCO/Tu11XUDQ5Gyr/ +WUJaJh2b7o1KLHGuh/dhU6t6nEofOIgwwqVSfL6Pm9v0c1a43SaQnk3pdSNg0o5i +2IxZvMIO7kN4PyvmVQnz/E2qRY6SzSEM0QY+KtPz/T0rp9DvzDiQ5gC+UYudWPVD +o4kkbTEJ8K++5MtETUjXovJp4VoRALL82CY= +-----END PUBLIC KEY----- diff --git a/dsa/tests/privatekey.rs b/dsa/tests/privatekey.rs index 01c408c0..837c2440 100644 --- a/dsa/tests/privatekey.rs +++ b/dsa/tests/privatekey.rs @@ -10,12 +10,27 @@ use pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}; use sha1::Sha1; use signature::{DigestVerifier, RandomizedDigestSigner}; +const OPENSSL_PEM_PRIVATE_KEY: &str = include_str!("pems/private.pem"); + fn generate_keypair() -> PrivateKey { let mut rng = rand::thread_rng(); let components = Components::generate(&mut rng, DSA_1024_160); PrivateKey::generate(&mut rng, components) } +#[test] +fn decode_encode_openssl_private_key() { + let private_key = PrivateKey::from_pkcs8_pem(OPENSSL_PEM_PRIVATE_KEY) + .expect("Failed to decode PEM encoded OpenSSL key"); + assert!(private_key.is_valid()); + + let reencoded_private_key = private_key + .to_pkcs8_pem(LineEnding::LF) + .expect("Failed to encode private key into PEM representation"); + + assert_eq!(*reencoded_private_key, OPENSSL_PEM_PRIVATE_KEY); +} + #[test] fn encode_decode_private_key() { let private_key = generate_keypair(); diff --git a/dsa/tests/publickey.rs b/dsa/tests/publickey.rs index 56996d6c..5a9ad411 100644 --- a/dsa/tests/publickey.rs +++ b/dsa/tests/publickey.rs @@ -7,6 +7,8 @@ use num_bigint::BigUint; use num_traits::One; use pkcs8::{DecodePublicKey, EncodePublicKey, LineEnding}; +const OPENSSL_PEM_PUBLIC_KEY: &str = include_str!("pems/public.pem"); + fn generate_public_key() -> PublicKey { let mut rng = rand::thread_rng(); let components = Components::generate(&mut rng, DSA_1024_160); @@ -15,6 +17,19 @@ fn generate_public_key() -> PublicKey { private_key.public_key().clone() } +#[test] +fn decode_encode_openssl_public_key() { + let public_key = PublicKey::from_public_key_pem(OPENSSL_PEM_PUBLIC_KEY) + .expect("Failed to decode PEM encoded OpenSSL public key"); + assert!(public_key.is_valid()); + + let reencoded_public_key = public_key + .to_public_key_pem(LineEnding::LF) + .expect("Failed to encode public key into PEM representation"); + + assert_eq!(reencoded_public_key, OPENSSL_PEM_PUBLIC_KEY); +} + #[test] fn encode_decode_public_key() { let public_key = generate_public_key(); From 64d74cb37c85decad253d601a0f88f006393447c Mon Sep 17 00:00:00 2001 From: aumetra Date: Sat, 14 May 2022 16:09:40 +0200 Subject: [PATCH 23/27] dsa: Use the OS native line ending --- dsa/tests/privatekey.rs | 2 +- dsa/tests/publickey.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsa/tests/privatekey.rs b/dsa/tests/privatekey.rs index 837c2440..21055de5 100644 --- a/dsa/tests/privatekey.rs +++ b/dsa/tests/privatekey.rs @@ -25,7 +25,7 @@ fn decode_encode_openssl_private_key() { assert!(private_key.is_valid()); let reencoded_private_key = private_key - .to_pkcs8_pem(LineEnding::LF) + .to_pkcs8_pem(LineEnding::default()) .expect("Failed to encode private key into PEM representation"); assert_eq!(*reencoded_private_key, OPENSSL_PEM_PRIVATE_KEY); diff --git a/dsa/tests/publickey.rs b/dsa/tests/publickey.rs index 5a9ad411..42703f28 100644 --- a/dsa/tests/publickey.rs +++ b/dsa/tests/publickey.rs @@ -24,7 +24,7 @@ fn decode_encode_openssl_public_key() { assert!(public_key.is_valid()); let reencoded_public_key = public_key - .to_public_key_pem(LineEnding::LF) + .to_public_key_pem(LineEnding::default()) .expect("Failed to encode public key into PEM representation"); assert_eq!(reencoded_public_key, OPENSSL_PEM_PUBLIC_KEY); From 2f1f818cf0031371c200ef36343b523def790d46 Mon Sep 17 00:00:00 2001 From: aumetra Date: Sat, 14 May 2022 17:28:53 +0200 Subject: [PATCH 24/27] Revert "dsa: Use the OS native line ending" This reverts commit 64d74cb37c85decad253d601a0f88f006393447c. --- dsa/tests/privatekey.rs | 2 +- dsa/tests/publickey.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsa/tests/privatekey.rs b/dsa/tests/privatekey.rs index 21055de5..837c2440 100644 --- a/dsa/tests/privatekey.rs +++ b/dsa/tests/privatekey.rs @@ -25,7 +25,7 @@ fn decode_encode_openssl_private_key() { assert!(private_key.is_valid()); let reencoded_private_key = private_key - .to_pkcs8_pem(LineEnding::default()) + .to_pkcs8_pem(LineEnding::LF) .expect("Failed to encode private key into PEM representation"); assert_eq!(*reencoded_private_key, OPENSSL_PEM_PRIVATE_KEY); diff --git a/dsa/tests/publickey.rs b/dsa/tests/publickey.rs index 42703f28..5a9ad411 100644 --- a/dsa/tests/publickey.rs +++ b/dsa/tests/publickey.rs @@ -24,7 +24,7 @@ fn decode_encode_openssl_public_key() { assert!(public_key.is_valid()); let reencoded_public_key = public_key - .to_public_key_pem(LineEnding::default()) + .to_public_key_pem(LineEnding::LF) .expect("Failed to encode public key into PEM representation"); assert_eq!(reencoded_public_key, OPENSSL_PEM_PUBLIC_KEY); From 7ef831b53519cad0e13e18af6e45fad7715a72fe Mon Sep 17 00:00:00 2001 From: aumetra Date: Sat, 14 May 2022 17:31:02 +0200 Subject: [PATCH 25/27] dsa: Enforce LF on windows --- .github/workflows/dsa.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/dsa.yml b/.github/workflows/dsa.yml index f70969d7..d39647a5 100644 --- a/.github/workflows/dsa.yml +++ b/.github/workflows/dsa.yml @@ -47,6 +47,11 @@ jobs: - stable runs-on: ${{ matrix.platform }} steps: + - name: Enforce LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: From 4daff59c79cbb331c725ecd23f6793ecbb43eab4 Mon Sep 17 00:00:00 2001 From: aumetra Date: Sat, 14 May 2022 17:38:59 +0200 Subject: [PATCH 26/27] dsa: Adjust working directory --- .github/workflows/dsa.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dsa.yml b/.github/workflows/dsa.yml index d39647a5..6e8d7fc7 100644 --- a/.github/workflows/dsa.yml +++ b/.github/workflows/dsa.yml @@ -48,6 +48,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - name: Enforce LF + working-directory: . run: | git config --global core.autocrlf false git config --global core.eol lf From 7009ede87be91cc481d652966d2d13ac40fb6d18 Mon Sep 17 00:00:00 2001 From: aumetra Date: Sun, 15 May 2022 17:37:48 +0200 Subject: [PATCH 27/27] dsa: filenames to snake case, use warn instead of forbid/deny --- dsa/src/lib.rs | 12 ++++++------ dsa/src/{privatekey.rs => private_key.rs} | 0 dsa/src/{publickey.rs => public_key.rs} | 0 dsa/tests/{privatekey.rs => private_key.rs} | 0 dsa/tests/{publickey.rs => public_key.rs} | 0 5 files changed, 6 insertions(+), 6 deletions(-) rename dsa/src/{privatekey.rs => private_key.rs} (100%) rename dsa/src/{publickey.rs => public_key.rs} (100%) rename dsa/tests/{privatekey.rs => private_key.rs} (100%) rename dsa/tests/{publickey.rs => public_key.rs} (100%) diff --git a/dsa/src/lib.rs b/dsa/src/lib.rs index 96be218e..8b329ca9 100644 --- a/dsa/src/lib.rs +++ b/dsa/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -#![forbid(missing_docs, unsafe_code)] -#![deny(rust_2018_idioms)] +#![forbid(unsafe_code)] +#![warn(missing_docs, rust_2018_idioms)] #![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", @@ -44,8 +44,8 @@ extern crate alloc; const DSA_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10040.4.1"); pub use self::components::Components; -pub use self::privatekey::PrivateKey; -pub use self::publickey::PublicKey; +pub use self::private_key::PrivateKey; +pub use self::public_key::PublicKey; pub use self::sig::Signature; pub use pkcs8; @@ -58,8 +58,8 @@ use pkcs8::spki::ObjectIdentifier; mod components; mod generate; -mod privatekey; -mod publickey; +mod private_key; +mod public_key; mod sig; /// Returns a `BigUint` with the value 2 diff --git a/dsa/src/privatekey.rs b/dsa/src/private_key.rs similarity index 100% rename from dsa/src/privatekey.rs rename to dsa/src/private_key.rs diff --git a/dsa/src/publickey.rs b/dsa/src/public_key.rs similarity index 100% rename from dsa/src/publickey.rs rename to dsa/src/public_key.rs diff --git a/dsa/tests/privatekey.rs b/dsa/tests/private_key.rs similarity index 100% rename from dsa/tests/privatekey.rs rename to dsa/tests/private_key.rs diff --git a/dsa/tests/publickey.rs b/dsa/tests/public_key.rs similarity index 100% rename from dsa/tests/publickey.rs rename to dsa/tests/public_key.rs