Repository: lettre/lettre (language: rust)

Issue:
RUSTSEC-2022-0093: Double Public Key Signing Function Oracle Attack on `ed25519-dalek`

> Double Public Key Signing Function Oracle Attack on `ed25519-dalek`

| Details             |                                                |
| ------------------- | ---------------------------------------------- |
| Package             | `ed25519-dalek`                      |
| Version             | `1.0.1`                   |
| URL                 | [https://github.com/MystenLabs/ed25519-unsafe-libs](https://github.com/MystenLabs/ed25519-unsafe-libs) |
| Date                | 2022-06-11                         |
| Patched versions    | `>=2`                  |

Versions of `ed25519-dalek` prior to v2.0 model private and public keys as
separate types which can be assembled into a `Keypair`, and also provide APIs
for serializing and deserializing 64-byte private/public keypairs.

Such APIs and serializations are inherently unsafe as the public key is one of
the inputs used in the deterministic computation of the `S` part of the signature,
but not in the `R` value. An adversary could somehow use the signing function as
an oracle that allows arbitrary public keys as input can obtain two signatures
for the same message sharing the same `R` and only differ on the `S` part.

Unfortunately, when this happens, one can easily extract the private key.

Revised public APIs in v2.0 of `ed25519-dalek` do NOT allow a decoupled
private/public keypair as signing input, except as part of specially labeled
&quot;hazmat&quot; APIs which are clearly labeled as being dangerous if misused.

See [advisory page](https://rustsec.org/advisories/RUSTSEC-2022-0093.html) for additional details.



Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 0ef87019f..ec5e396aa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -69,7 +69,7 @@ tokio1_boring = { package = "tokio-boring", version = "2.1.4", optional = true }
 ## dkim
 sha2 = { version = "0.10", optional = true, features = ["oid"] }
 rsa = { version = "0.9", optional = true }
-ed25519-dalek = { version = "1.0.1", optional = true }
+ed25519-dalek = { version = "2.0.0", optional = true }
 
 # email formats
 email_address = { version = "0.2.1", default-features = false }
diff --git a/src/message/dkim.rs b/src/message/dkim.rs
index 0d8e708ff..646a08936 100644
--- a/src/message/dkim.rs
+++ b/src/message/dkim.rs
@@ -108,7 +108,7 @@ pub struct DkimSigningKey(InnerDkimSigningKey);
 #[derive(Debug)]
 enum InnerDkimSigningKey {
     Rsa(RsaPrivateKey),
-    Ed25519(ed25519_dalek::Keypair),
+    Ed25519(ed25519_dalek::SigningKey),
 }
 
 impl DkimSigningKey {
@@ -121,14 +121,18 @@ impl DkimSigningKey {
                 RsaPrivateKey::from_pkcs1_pem(private_key)
                     .map_err(|err| DkimSigningKeyError(InnerDkimSigningKeyError::Rsa(err)))?,
             ),
-            DkimSigningAlgorithm::Ed25519 => InnerDkimSigningKey::Ed25519(
-                ed25519_dalek::Keypair::from_bytes(
-                    &crate::base64::decode(private_key).map_err(|err| {
-                        DkimSigningKeyError(InnerDkimSigningKeyError::Base64(err))
-                    })?,
-                )
-                .map_err(|err| DkimSigningKeyError(InnerDkimSigningKeyError::Ed25519(err)))?,
-            ),
+            DkimSigningAlgorithm::Ed25519 => {
+                InnerDkimSigningKey::Ed25519(ed25519_dalek::SigningKey::from_bytes(
+                    &crate::base64::decode(private_key)
+                        .map_err(|err| DkimSigningKeyError(InnerDkimSigningKeyError::Base64(err)))?
+                        .try_into()
+                        .map_err(|_| {
+                            DkimSigningKeyError(InnerDkimSigningKeyError::Ed25519(
+                                ed25519_dalek::ed25519::Error::new(),
+                            ))
+                        })?,
+                ))
+            }
         }))
     }
     fn get_signing_algorithm(&self) -> DkimSigningAlgorithm {


Test patch: