dsa: Add initial DSA implementation (#471)

Adds an initial implementation of DSA (see #8)  

The following things work when tested against OpenSSL:

- The generated keys are valid and can be imported and exported from/to their DER/PEM representation
- Signatures generated by this library can be successfully verified
- Signatures can be imported and exported from/into their DER representation
- Signatures generated by OpenSSL can be successfully imported and verified
This commit is contained in:
aumetra
2022-05-16 19:53:39 +02:00
committed by GitHub
parent 02dad1fd9b
commit 373e4fe08b
30 changed files with 2103 additions and 2 deletions
+63
View File
@@ -0,0 +1,63 @@
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.57.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.57.0 # MSRV
- stable
runs-on: ${{ matrix.platform }}
steps:
- name: Enforce LF
working-directory: .
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- 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
Generated
+139 -2
View File
@@ -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"
@@ -134,6 +140,25 @@ dependencies = [
"subtle",
]
[[package]]
name = "dsa"
version = "0.0.1"
dependencies = [
"digest 0.10.3",
"num-bigint-dig",
"num-traits",
"opaque-debug",
"paste",
"pkcs8",
"rand 0.8.5",
"rand_chacha 0.3.1",
"rfc6979",
"sha1",
"sha2 0.10.2",
"signature",
"zeroize",
]
[[package]]
name = "ecdsa"
version = "0.14.1"
@@ -179,7 +204,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek",
"ed25519 1.4.0",
"rand",
"rand 0.7.3",
"serde",
"sha2 0.9.9",
"zeroize",
@@ -276,18 +301,86 @@ 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",
"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.6.0"
@@ -339,11 +432,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"
@@ -354,6 +458,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"
@@ -430,6 +544,17 @@ dependencies = [
"serde",
]
[[package]]
name = "sha1"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.3",
]
[[package]]
name = "sha2"
version = "0.9.9"
@@ -464,6 +589,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.6.0"
+1
View File
@@ -1,6 +1,7 @@
[workspace]
resolver = "2"
members = [
"dsa",
"ecdsa",
"ed25519",
"rfc6979"
+1
View File
@@ -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)
+4
View File
@@ -0,0 +1,4 @@
target/
Cargo.lock
*.pem
*.der
+31
View File
@@ -0,0 +1,31 @@
[package]
name = "dsa"
version = "0.0.1"
edition = "2021"
license = "Apache-2.0 OR MIT"
readme = "README.md"
categories = ["cryptography"]
keywords = ["crypto", "nist", "signature"]
rust-version = "1.57"
[dependencies]
digest = "0.10.3"
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", 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 }
[features]
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"
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 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.
+25
View File
@@ -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.
+68
View File
@@ -0,0 +1,68 @@
# [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]
[Digital Signature Algorithm (DSA)][1] as specified in
[FIPS 186-4][2] (Digital Signature Standard).
[Documentation][docs-link]
## About
This crate provides an implementation of DSA in pure Rust.
It utilises the [`signature`] crate to provide an interface for creating and verifying signatures.
## Minimum Supported Rust Version
This crate requires **Rust 1.57** at a minimum.
We may change the MSRV in the future, but it will be accompanied by a minor
version bump.
## License
All crates licensed under either of
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
* [MIT license](http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
[//]: # (badges)
[crate-image]: https://buildstats.info/crate/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
[//]: # (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
+21
View File
@@ -0,0 +1,21 @@
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 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_bytes.as_bytes()).unwrap();
file.flush().unwrap();
let mut file = File::create("private.pem").unwrap();
file.write_all(private_key_bytes.as_bytes()).unwrap();
file.flush().unwrap();
}
+8
View File
@@ -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();
}
+31
View File
@@ -0,0 +1,31 @@
use digest::Digest;
use dsa::{consts::DSA_2048_256, Components, PrivateKey};
use pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding};
use sha1::Sha1;
use signature::{RandomizedDigestSigner, Signature};
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_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_bytes.as_bytes()).unwrap();
file.flush().unwrap();
let mut file = File::create("signature.der").unwrap();
file.write_all(signature.as_bytes()).unwrap();
file.flush().unwrap();
let mut file = File::create("private.pem").unwrap();
file.write_all(private_key_bytes.as_bytes()).unwrap();
file.flush().unwrap();
}
+103
View File
@@ -0,0 +1,103 @@
//!
//! 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};
/// 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<R: CryptoRng + RngCore + ?Sized>(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 {
*self.p() >= two()
&& *self.q() >= two()
&& *self.g() >= BigUint::one()
&& self.g() < self.p()
}
}
impl<'a> DecodeValue<'a> for Components {
fn decode_value<R: Reader<'a>>(reader: &mut R, _header: Header) -> der::Result<Self> {
let p = reader.decode::<UIntRef<'_>>()?;
let q = reader.decode::<UIntRef<'_>>()?;
let g = reader.decode::<UIntRef<'_>>()?;
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<F, T>(&self, encoder: F) -> der::Result<T>
where
F: FnOnce(&[&dyn Encode]) -> der::Result<T>,
{
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])
}
}
+24
View File
@@ -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 [<DSA_ $l _ $n>]: (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 [<DSA_ $l _ $n>]: (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);
+74
View File
@@ -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<R>(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)
}
+21
View File
@@ -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<R>(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)
}
+32
View File
@@ -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, secret_number_rfc6979};
/// 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<R>(bit_length: usize, rng: &mut R) -> BigUint
where
R: CryptoRng + RngCore + ?Sized,
{
rng.gen_prime(bit_length)
}
+111
View File
@@ -0,0 +1,111 @@
//!
//! Generate a per-message secret number
//!
use crate::{Components, PrivateKey};
use alloc::{vec, vec::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<u8> {
// Reduce the hash modulo Q
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);
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
///
/// # Returns
///
/// Secret number k and its modular multiplicative inverse with q
#[inline]
pub fn secret_number_rfc6979<D>(private_key: &PrivateKey, hash: &[u8]) -> (BigUint, BigUint)
where
D: CoreProxy + FixedOutput,
D::Core: BlockSizeUser
+ BufferKindUser<BufferKind = Eager>
+ Clone
+ Default
+ FixedOutputCore
+ HashMarker
+ OutputSizeUser<OutputSize = D::OutputSize>,
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<D::Core as BlockSizeUser>::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::<D>::new(&x_bytes, &hash, &[]);
x_bytes.zeroize();
let mut buffer = vec![0; k_size];
loop {
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
///
/// # Returns
///
/// Secret number k and its modular multiplicative inverse with q
#[inline]
pub fn secret_number<R>(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
}
+69
View File
@@ -0,0 +1,69 @@
#![no_std]
#![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",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
)]
//!
//! 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);
//! ```
//!
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::private_key::PrivateKey;
pub use self::public_key::PublicKey;
pub use self::sig::Signature;
pub use pkcs8;
pub use signature;
pub mod consts;
use num_bigint::BigUint;
use pkcs8::spki::ObjectIdentifier;
mod components;
mod generate;
mod private_key;
mod public_key;
mod sig;
/// Returns a `BigUint` with the value 2
#[inline]
fn two() -> BigUint {
BigUint::from(2_u8)
}
+200
View File
@@ -0,0 +1,200 @@
//!
//! Module containing the definition of the private key container
//!
use crate::{sig::Signature, Components, PublicKey, DSA_OID};
use core::cmp::min;
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::{
der::{asn1::UIntRef, AnyRef, Decode, Encode},
AlgorithmIdentifier, DecodePrivateKey, EncodePrivateKey, PrivateKeyInfo, SecretDocument,
};
use rand::{CryptoRng, RngCore};
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 {
/// Public key
public_key: PublicKey,
/// Private component x
x: Zeroizing<BigUint>,
}
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
#[inline]
pub fn generate<R: CryptoRng + RngCore + ?Sized>(
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
///
/// 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
}
/// Check whether the private key is valid
#[must_use]
pub fn is_valid(&self) -> bool {
if !self.public_key().is_valid() {
return false;
}
*self.x() >= BigUint::one() && self.x() < self.public_key().components().q()
}
/// Sign some pre-hashed data
fn sign_prehashed(&self, (k, inv_k): (BigUint, BigUint), hash: &[u8]) -> Option<Signature> {
// Refuse to sign with an invalid key
if !self.is_valid() {
return None;
}
let components = self.public_key().components();
let (p, q, g) = (components.p(), components.q(), components.g());
let x = self.x();
let r = g.modpow(&k, p) % q;
let n = (q.bits() / 8) as usize;
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]);
let s = (inv_k * (z + x * &r)) % q;
Some(Signature::from_components(r, s))
}
}
impl<D> DigestSigner<D, Signature> for PrivateKey
where
D: Digest + CoreProxy + FixedOutput,
D::Core: BlockSizeUser
+ BufferKindUser<BufferKind = Eager>
+ Clone
+ Default
+ FixedOutputCore
+ HashMarker
+ OutputSizeUser<OutputSize = D::OutputSize>,
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
fn try_sign_digest(&self, digest: D) -> Result<Signature, signature::Error> {
let hash = digest.finalize_fixed();
let ks = crate::generate::secret_number_rfc6979::<D>(self, &hash);
self.sign_prehashed(ks, &hash)
.ok_or_else(signature::Error::new)
}
}
impl<D> RandomizedDigestSigner<D, Signature> for PrivateKey
where
D: Digest,
{
fn try_sign_digest_with_rng(
&self,
mut rng: impl CryptoRng + RngCore,
digest: D,
) -> Result<Signature, signature::Error> {
let ks = crate::generate::secret_number(&mut rng, self.public_key().components())
.ok_or_else(signature::Error::new)?;
let hash = digest.finalize();
self.sign_prehashed(ks, &hash)
.ok_or_else(signature::Error::new)
}
}
impl EncodePrivateKey for PrivateKey {
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
let parameters = self.public_key().components().to_vec()?;
let parameters = AnyRef::from_der(&parameters)?;
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<PrivateKeyInfo<'a>> for PrivateKey {
type Error = pkcs8::Error;
fn try_from(value: PrivateKeyInfo<'a>) -> Result<Self, Self::Error> {
value.algorithm.assert_algorithm_oid(DSA_OID)?;
let parameters = value.algorithm.parameters_any()?;
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());
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);
let private_key = PrivateKey::from_components(public_key, x);
if !private_key.is_valid() {
return Err(pkcs8::Error::KeyMalformed);
}
Ok(private_key)
}
}
impl DecodePrivateKey for PrivateKey {}
+145
View File
@@ -0,0 +1,145 @@
//!
//! Module containing the definition of the public key container
//!
use crate::{sig::Signature, two, Components, DSA_OID};
use core::cmp::min;
use digest::Digest;
use num_bigint::{BigUint, ModInverse};
use num_traits::One;
use pkcs8::{
der::{asn1::UIntRef, AnyRef, Decode, Encode},
spki, AlgorithmIdentifier, DecodePublicKey, EncodePublicKey, SubjectPublicKeyInfo,
};
use signature::DigestVerifier;
/// DSA public key
#[derive(Clone, PartialEq, PartialOrd)]
#[must_use]
pub struct PublicKey {
/// common components
components: Components,
/// Public component y
y: BigUint,
}
opaque_debug::implement!(PublicKey);
impl PublicKey {
/// Construct a new public key from the common components and the public component
///
/// These values are not getting verified for validity
pub const fn from_components(components: Components, y: BigUint) -> 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() >= two() && self.y().modpow(components.q(), components.p()) == BigUint::one()
}
/// Verify some prehashed data
#[must_use]
fn verify_prehashed(&self, hash: &[u8], signature: &Signature) -> Option<bool> {
// 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 w = s.mod_inverse(q)?.to_biguint().unwrap();
let n = (q.bits() / 8) as usize;
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]);
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<D> DigestVerifier<D, Signature> 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_prehashed(&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<spki::Document> {
let parameters = self.components.to_vec()?;
let parameters = AnyRef::from_der(&parameters)?;
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<SubjectPublicKeyInfo<'a>> for PublicKey {
type Error = spki::Error;
fn try_from(value: SubjectPublicKeyInfo<'a>) -> Result<Self, Self::Error> {
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 {}
+102
View File
@@ -0,0 +1,102 @@
//!
//! Module containing the definition of the Signature container
//!
use alloc::vec::Vec;
use num_bigint::BigUint;
use pkcs8::der::{self, asn1::UIntRef, Decode, Encode, Reader, Sequence};
/// Container of the DSA signature
#[derive(Clone)]
#[must_use]
pub struct Signature {
/// Internally cached DER representation of the signature
der_repr: Vec<u8>,
/// 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 from_components(r: BigUint, s: BigUint) -> Self {
let mut signature = Self {
der_repr: Vec::with_capacity(0),
r,
s,
};
signature.der_repr = signature.to_vec().unwrap();
signature
}
/// 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 AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
&self.der_repr
}
}
impl<'a> Decode<'a> for Signature {
fn decode<R: Reader<'a>>(reader: &mut R) -> der::Result<Self> {
reader.sequence(|sequence| {
let r = sequence.decode::<UIntRef<'_>>()?;
let s = sequence.decode::<UIntRef<'_>>()?;
let r = BigUint::from_bytes_be(r.as_bytes());
let s = BigUint::from_bytes_be(s.as_bytes());
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<core::cmp::Ordering> {
(self.r(), self.s()).partial_cmp(&(other.r(), other.s()))
}
}
impl<'a> Sequence<'a> for Signature {
fn fields<F, T>(&self, encoder: F) -> der::Result<T>
where
F: FnOnce(&[&dyn der::Encode]) -> der::Result<T>,
{
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])
}
}
impl signature::Signature for Signature {
fn from_bytes(bytes: &[u8]) -> Result<Self, signature::Error> {
Signature::from_der(bytes).map_err(|_| signature::Error::new())
}
}
+25
View File
@@ -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);
}
+344
View File
@@ -0,0 +1,344 @@
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;
const MESSAGE: &[u8] = b"sample";
const MESSAGE_2: &[u8] = b"test";
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)
}
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<D>(private_key: PrivateKey, data: &[u8]) -> Signature
where
D: Digest + CoreProxy + FixedOutput,
D::Core: BlockSizeUser
+ BufferKindUser<BufferKind = Eager>
+ Clone
+ Default
+ FixedOutputCore
+ HashMarker
+ OutputSizeUser<OutputSize = D::OutputSize>,
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
private_key.sign_digest(<D as Digest>::new().chain_update(data))
}
/// Generate a signature using the 1024-bit DSA key
fn generate_1024_signature<D>(data: &[u8]) -> Signature
where
D: Digest + CoreProxy + FixedOutput,
D::Core: BlockSizeUser
+ BufferKindUser<BufferKind = Eager>
+ Clone
+ Default
+ FixedOutputCore
+ HashMarker
+ OutputSizeUser<OutputSize = D::OutputSize>,
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
generate_signature::<D>(dsa_1024_private_key(), data)
}
/// Generate a signature using the 2048-bit DSA key
fn generate_2048_signature<D>(data: &[u8]) -> Signature
where
D: Digest + CoreProxy + FixedOutput,
D::Core: BlockSizeUser
+ BufferKindUser<BufferKind = Eager>
+ Clone
+ Default
+ FixedOutputCore
+ HashMarker
+ OutputSizeUser<OutputSize = D::OutputSize>,
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
generate_signature::<D>(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(
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<dyn Fn() -> Signature>)> {
vec![
// sha1, 1024, "sample"
(
from_str_signature(
"2E1A0C2562B2912CAAF89186FB0F42001585DA55",
"29EFB6B0AFF2D7A68EB70CA313022253B9A88DF5",
),
Box::new(|| generate_1024_signature::<Sha1>(MESSAGE)),
),
// sha1, 1024, "test"
(
from_str_signature(
"42AB2052FD43E123F0607F115052A67DCD9C5C77",
"183916B0230D45B9931491D4C6B0BD2FB4AAF088",
),
Box::new(|| generate_1024_signature::<Sha1>(MESSAGE_2)),
),
// sha224, 1024, "sample"
(
from_str_signature(
"4BC3B686AEA70145856814A6F1BB53346F02101E",
"410697B92295D994D21EDD2F4ADA85566F6F94C1",
),
Box::new(|| generate_1024_signature::<Sha224>(MESSAGE)),
),
// sha224, 1024, "test"
(
from_str_signature(
"6868E9964E36C1689F6037F91F28D5F2C30610F2",
"49CEC3ACDC83018C5BD2674ECAAD35B8CD22940F",
),
Box::new(|| generate_1024_signature::<Sha224>(MESSAGE_2)),
),
// sha256, 1024, "sample"
(
from_str_signature(
"81F2F5850BE5BC123C43F71A3033E9384611C545",
"4CDD914B65EB6C66A8AAAD27299BEE6B035F5E89",
),
Box::new(|| generate_1024_signature::<Sha256>(MESSAGE)),
),
// sha256, 1024, "test"
(
from_str_signature(
"22518C127299B0F6FDC9872B282B9E70D0790812",
"6837EC18F150D55DE95B5E29BE7AF5D01E4FE160",
),
Box::new(|| generate_1024_signature::<Sha256>(MESSAGE_2)),
),
// sha384, 1024, "sample"
(
from_str_signature(
"07F2108557EE0E3921BC1774F1CA9B410B4CE65A",
"54DF70456C86FAC10FAB47C1949AB83F2C6F7595",
),
Box::new(|| generate_1024_signature::<Sha384>(MESSAGE)),
),
// sha384, 1024, "test"
(
from_str_signature(
"854CF929B58D73C3CBFDC421E8D5430CD6DB5E66",
"91D0E0F53E22F898D158380676A871A157CDA622",
),
Box::new(|| generate_1024_signature::<Sha384>(MESSAGE_2)),
),
// sha512, 1024, "sample"
(
from_str_signature(
"16C3491F9B8C3FBBDD5E7A7B667057F0D8EE8E1B",
"02C36A127A7B89EDBB72E4FFBC71DABC7D4FC69C",
),
Box::new(|| generate_1024_signature::<Sha512>(MESSAGE)),
),
// sha512, 1024, "test"
(
from_str_signature(
"8EA47E475BA8AC6F2D821DA3BD212D11A3DEB9A0",
"7C670C7AD72B6C050C109E1790008097125433E8",
),
Box::new(|| generate_1024_signature::<Sha512>(MESSAGE_2)),
),
// sha1, 2048, "sample"
(
from_str_signature(
"3A1B2DBD7489D6ED7E608FD036C83AF396E290DBD602408E8677DAABD6E7445A",
"D26FCBA19FA3E3058FFC02CA1596CDBB6E0D20CB37B06054F7E36DED0CDBBCCF",
),
Box::new(|| generate_2048_signature::<Sha1>(MESSAGE)),
),
// sha1, 2048, "test"
(
from_str_signature(
"C18270A93CFC6063F57A4DFA86024F700D980E4CF4E2CB65A504397273D98EA0",
"414F22E5F31A8B6D33295C7539C1C1BA3A6160D7D68D50AC0D3A5BEAC2884FAA",
),
Box::new(|| generate_2048_signature::<Sha1>(MESSAGE_2)),
),
// sha224, 2048, "sample"
(
from_str_signature(
"DC9F4DEADA8D8FF588E98FED0AB690FFCE858DC8C79376450EB6B76C24537E2C",
"A65A9C3BC7BABE286B195D5DA68616DA8D47FA0097F36DD19F517327DC848CEC",
),
Box::new(|| generate_2048_signature::<Sha224>(MESSAGE)),
),
// sha224, 2048, "test"
(
from_str_signature(
"272ABA31572F6CC55E30BF616B7A265312018DD325BE031BE0CC82AA17870EA3",
"E9CC286A52CCE201586722D36D1E917EB96A4EBDB47932F9576AC645B3A60806",
),
Box::new(|| generate_2048_signature::<Sha224>(MESSAGE_2)),
),
// sha256, 2048, "sample"
(
from_str_signature(
"EACE8BDBBE353C432A795D9EC556C6D021F7A03F42C36E9BC87E4AC7932CC809",
"7081E175455F9247B812B74583E9E94F9EA79BD640DC962533B0680793A38D53",
),
Box::new(|| generate_2048_signature::<Sha256>(MESSAGE)),
),
// sha256, 2048, "test"
(
from_str_signature(
"8190012A1969F9957D56FCCAAD223186F423398D58EF5B3CEFD5A4146A4476F0",
"7452A53F7075D417B4B013B278D1BB8BBD21863F5E7B1CEE679CF2188E1AB19E",
),
Box::new(|| generate_2048_signature::<Sha256>(MESSAGE_2)),
),
// sha384, 2048, "sample"
(
from_str_signature(
"B2DA945E91858834FD9BF616EBAC151EDBC4B45D27D0DD4A7F6A22739F45C00B",
"19048B63D9FD6BCA1D9BAE3664E1BCB97F7276C306130969F63F38FA8319021B",
),
Box::new(|| generate_2048_signature::<Sha384>(MESSAGE)),
),
// sha384, 2048, "test"
(
from_str_signature(
"239E66DDBE8F8C230A3D071D601B6FFBDFB5901F94D444C6AF56F732BEB954BE",
"6BD737513D5E72FE85D1C750E0F73921FE299B945AAD1C802F15C26A43D34961",
),
Box::new(|| generate_2048_signature::<Sha384>(MESSAGE_2)),
),
// sha512, 2048, "sample"
(
from_str_signature(
"2016ED092DC5FB669B8EFB3D1F31A91EECB199879BE0CF78F02BA062CB4C942E",
"D0C76F84B5F091E141572A639A4FB8C230807EEA7D55C8A154A224400AFF2351",
),
Box::new(|| generate_2048_signature::<Sha512>(MESSAGE)),
),
// sha512, 2048, "test"
(
from_str_signature(
"89EC4BB1400ECCFF8E7D9AA515CD1DE7803F2DAFF09693EE7FD1353E90A68307",
"C9F0BDABCC0D880BB137A994CC7F3980CE91CC10FAF529FC46565B15CEA854E1",
),
Box::new(|| generate_2048_signature::<Sha512>(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);
}
}
+14
View File
@@ -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-----
+15
View File
@@ -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-----
+20
View File
@@ -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-----
+72
View File
@@ -0,0 +1,72 @@
// 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};
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();
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<x<q not met"
);
assert_eq!(
*private_key.public_key().y(),
components.g().modpow(private_key.x(), components.p()),
"Requirement y=(g^x)%p not met"
);
}
+50
View File
@@ -0,0 +1,50 @@
// We abused the deprecated attribute for unsecure key sizes
// But we want to use those small key sizes for fast tests
#![allow(deprecated)]
use dsa::{consts::DSA_1024_160, Components, PrivateKey, PublicKey};
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);
let private_key = PrivateKey::generate(&mut rng, components);
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();
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());
}
+89
View File
@@ -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());
}