Skip to content

Commit fe7afaf

Browse files
committed
Return an error if we try to sign with an encrypted key
Fixes #3
1 parent 13ec7fd commit fe7afaf

File tree

4 files changed

+98
-0
lines changed

4 files changed

+98
-0
lines changed

src/errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub enum ErrorKind {
2121
KDF,
2222
RNG,
2323
Encoding,
24+
EncryptedKey,
2425
}
2526

2627
/// Error structure for the `minisign` crate.
@@ -45,6 +46,11 @@ impl PError {
4546
err: err.into(),
4647
}
4748
}
49+
50+
/// Get the kind of error.
51+
pub fn kind(&self) -> &ErrorKind {
52+
&self.kind
53+
}
4854
}
4955

5056
impl fmt::Display for PError {
@@ -59,6 +65,7 @@ impl fmt::Display for PError {
5965
ErrorKind::KDF => write!(f, "{}", self.err),
6066
ErrorKind::RNG => write!(f, "{}", self.err),
6167
ErrorKind::Encoding => write!(f, "{}", self.err),
68+
ErrorKind::EncryptedKey => write!(f, "{}", self.err),
6269
}
6370
}
6471
}
@@ -74,6 +81,7 @@ impl StdError for PError {
7481
ErrorKind::KDF => "key derivation error",
7582
ErrorKind::RNG => "random number generator error",
7683
ErrorKind::Encoding => "encoding error",
84+
ErrorKind::EncryptedKey => "encrypted key error",
7785
}
7886
}
7987
}

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ pub fn sign<R>(
7676
where
7777
R: Read,
7878
{
79+
if sk.is_encrypted() {
80+
return Err(PError::new(
81+
ErrorKind::EncryptedKey,
82+
"Cannot sign with an encrypted secret key. The key must be decrypted first. Options include: SecretKeyBox::into_secret_key(password), SecretKey::from_box(box, password), SecretKey::from_file(path, password), or use KeyPair::generate_unencrypted_keypair() for passwordless keys.",
83+
));
84+
}
7985
let data = prehash(&mut data_reader)?;
8086
let trusted_comment = match trusted_comment {
8187
Some(trusted_comment) => trusted_comment.to_string(),

src/secret_key.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,25 @@ impl SecretKey {
145145
&self.keynum_sk.keynum[..]
146146
}
147147

148+
/// Returns `true` if this secret key is encrypted and requires a password to use.
149+
/// This checks both the encryption algorithm and whether the key material has been properly decrypted.
150+
pub fn is_encrypted(&self) -> bool {
151+
if self.kdf_alg == KDF_NONE {
152+
return false;
153+
}
154+
155+
// For encrypted keys, verify that the key material is valid by checking the checksum
156+
// If the checksum doesn't match, the key is still encrypted
157+
match self.read_checksum() {
158+
Ok(checksum_vec) => {
159+
let mut expected_chk = [0u8; CHK_BYTES];
160+
expected_chk.copy_from_slice(&checksum_vec[..]);
161+
expected_chk != self.keynum_sk.chk
162+
}
163+
Err(_) => true, // If we can't read checksum, assume encrypted
164+
}
165+
}
166+
148167
/// Deserialize a `SecretKey`.
149168
///
150169
/// For storage, a `SecretKeyBox` is usually what you need instead.

src/tests.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,71 @@ fn signature() {
9595
assert!(verify(&pk, &signature_box, Cursor::new(data), true, false, false).is_err());
9696
}
9797

98+
#[test]
99+
fn encrypted_key_signing_fails() {
100+
use std::io::Cursor;
101+
102+
use crate::{sign, ErrorKind, KeyPair};
103+
104+
let KeyPair { sk, .. } =
105+
KeyPair::generate_encrypted_keypair(Some("password".to_string())).unwrap();
106+
let data = b"test";
107+
let result = sign(None, &sk, Cursor::new(data), None, None);
108+
109+
assert!(result.is_err());
110+
if let Err(err) = result {
111+
// Check that we get the specific EncryptedKey error
112+
match err.kind() {
113+
ErrorKind::EncryptedKey => {
114+
// This is expected - we should get an EncryptedKey error
115+
}
116+
_ => panic!("Expected EncryptedKey error, got: {:?}", err.kind()),
117+
}
118+
}
119+
}
120+
121+
#[test]
122+
fn is_encrypted_detection() {
123+
use crate::KeyPair;
124+
125+
let KeyPair {
126+
sk: unencrypted_sk, ..
127+
} = KeyPair::generate_unencrypted_keypair().unwrap();
128+
assert!(!unencrypted_sk.is_encrypted());
129+
130+
let KeyPair {
131+
sk: encrypted_sk, ..
132+
} = KeyPair::generate_encrypted_keypair(Some("password".to_string())).unwrap();
133+
assert!(encrypted_sk.is_encrypted());
134+
}
135+
136+
#[test]
137+
fn decrypt_key_process() {
138+
use crate::{KeyPair, SecretKeyBox};
139+
140+
// Generate encrypted keypair
141+
let KeyPair {
142+
sk: encrypted_sk, ..
143+
} = KeyPair::generate_encrypted_keypair(Some("password".to_string())).unwrap();
144+
assert!(
145+
encrypted_sk.is_encrypted(),
146+
"Generated key should be encrypted"
147+
);
148+
149+
// Convert to box
150+
let sk_box_str = encrypted_sk.to_box(None).unwrap().to_string();
151+
let sk_box = SecretKeyBox::from_string(&sk_box_str).unwrap();
152+
153+
// Decrypt the key
154+
let decrypted_sk = sk_box
155+
.into_secret_key(Some("password".to_string()))
156+
.unwrap();
157+
assert!(
158+
!decrypted_sk.is_encrypted(),
159+
"Decrypted key should not be encrypted"
160+
);
161+
}
162+
98163
#[test]
99164
fn signature_bones() {
100165
use std::io::Cursor;

0 commit comments

Comments
 (0)