mirror of
https://github.com/RustCrypto/signatures
synced 2026-06-21 13:45:42 +00:00
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:
@@ -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
@@ -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,6 +1,7 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"dsa",
|
||||
"ecdsa",
|
||||
"ed25519",
|
||||
"rfc6979"
|
||||
|
||||
@@ -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) | [](https://crates.io/crates/dsa) | [](https://docs.rs/dsa) | [](https://github.com/RustCrypto/signatures/actions?query=workflow%3Adsa)
|
||||
| [`ecdsa`] | [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) | [](https://crates.io/crates/ecdsa) | [](https://docs.rs/ecdsa) | [](https://github.com/RustCrypto/signatures/actions?query=workflow%3Aecdsa) |
|
||||
| [`ed25519`] | [Ed25519](https://en.wikipedia.org/wiki/EdDSA) | [](https://crates.io/crates/ed25519) | [](https://docs.rs/ed25519) | [](https://github.com/RustCrypto/signatures/actions?query=workflow%3Aed25519)
|
||||
| [`rfc6979`] | [RFC6979](https://datatracker.ietf.org/doc/html/rfc6979) | [](https://crates.io/crates/rfc6979) | [](https://docs.rs/rfc6979) | [](https://github.com/RustCrypto/signatures/actions/workflows/rfc6979.yml)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
target/
|
||||
Cargo.lock
|
||||
*.pem
|
||||
*.der
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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(¶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<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 {}
|
||||
@@ -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(¶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<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
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
Reference in New Issue
Block a user