mirror of
https://github.com/narrrl/proton-sdk-rs.git
synced 2026-11-03 16:41:26 +01:00
bumped deps + fixed deprecations + session for live tests
This commit is contained in:
1 parent
b9742e122a
commit
70e573ebe1
10 files changed
+761
-312
No files matched your search
@@ -26,3 +26,6 @@ todo.md
|
||||
sdk/
|
||||
|
||||
.env
|
||||
|
||||
# Cached live-test session (resume instead of re-running SRP+2FA)
|
||||
.proton_session.json
|
||||
Generated
+584
-247
File diff suppressed because it is too large.
Load diff
+9
-8
@@ -15,7 +15,7 @@ categories = ["api-bindings", "cryptography", "filesystem"]
|
||||
|
||||
[workspace.dependencies]
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "fs", "io-util", "time"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream", "multipart"] }
|
||||
reqwest = { version = "0.13", default-features = false, features = ["json", "rustls", "webpki-roots", "stream", "multipart"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_repr = "0.1"
|
||||
@@ -25,18 +25,19 @@ futures = "0.3"
|
||||
bytes = "1"
|
||||
base64 = "0.22"
|
||||
hex = "0.4"
|
||||
hmac = "0.12"
|
||||
sha1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
hmac = "0.13"
|
||||
sha1 = "0.11"
|
||||
sha2 = "0.11"
|
||||
aes-gcm = "0.10"
|
||||
hkdf = "0.12"
|
||||
bcrypt = "0.17"
|
||||
pgp = "0.16"
|
||||
hkdf = "0.13"
|
||||
bcrypt = "0.19"
|
||||
pgp = "0.20"
|
||||
# Match the chrono line rPGP pulls in; used to stamp detached-signature subpackets.
|
||||
chrono = "0.4"
|
||||
tracing = "0.1"
|
||||
num-bigint = "0.4"
|
||||
num-traits = "0.2"
|
||||
getrandom = "0.2"
|
||||
# rPGP re-exports rand_core 0.6 traits; match its 0.8 line for keygen/encryption RNG.
|
||||
# rPGP (pgp 0.20) still depends on rand 0.8 and passes its CryptoRng/Rng traits
|
||||
# across its public API, so the RNG we hand to keygen/encryption must be rand 0.8.
|
||||
rand = "0.8"
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use hmac::{Hmac, Mac};
|
||||
use hmac::{Hmac, KeyInit, Mac};
|
||||
use proton_drive_sdk::ProtonDriveClient;
|
||||
use proton_sdk::config::ProtonClientConfiguration;
|
||||
use proton_sdk::session::ProtonApiSession;
|
||||
|
||||
@@ -22,7 +22,7 @@ use proton_sdk::ids::{AddressId, DriveEventId, LinkId, NodeUid, ShareId, VolumeI
|
||||
use proton_sdk::session::ProtonApiSession;
|
||||
use proton_sdk::telemetry::{NoopTelemetry, Telemetry, TelemetryExt};
|
||||
|
||||
use hmac::{Hmac, Mac};
|
||||
use hmac::{Hmac, KeyInit, Mac};
|
||||
use sha1::Sha1;
|
||||
|
||||
use crate::cache::DriveEntityCache;
|
||||
|
||||
@@ -13,10 +13,11 @@
|
||||
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use hmac::{Hmac, Mac};
|
||||
use hmac::{Hmac, KeyInit, Mac};
|
||||
use proton_drive_sdk::ProtonDriveClient;
|
||||
use proton_sdk::config::ProtonClientConfiguration;
|
||||
use proton_sdk::session::ProtonApiSession;
|
||||
use proton_sdk::session::{PasswordMode, ProtonApiSession, ResumeParameters};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha1::Sha1;
|
||||
|
||||
// Must follow Proton's required shape (sdk/README.md "Operational requirements").
|
||||
@@ -51,6 +52,12 @@ pub async fn live_client() -> Option<&'static LiveClient> {
|
||||
}
|
||||
|
||||
/// Perform the one-time SRP + 2FA login and build the client.
|
||||
///
|
||||
/// To avoid burning a TOTP window (and tripping Proton anti-abuse) on every run,
|
||||
/// a successful login is persisted to a gitignored cache file. On the next run we
|
||||
/// [`ProtonApiSession::resume`] from that file instead of doing SRP+2FA again; the
|
||||
/// stored refresh token auto-refreshes via the http client's 401 path, so no TOTP
|
||||
/// is needed. A missing/dead cache falls back to a fresh login.
|
||||
async fn build_live_client() -> Option<LiveClient> {
|
||||
let (username, password) = match read_dotenv() {
|
||||
Ok(creds) => creds,
|
||||
@@ -59,6 +66,33 @@ async fn build_live_client() -> Option<LiveClient> {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let config = ProtonClientConfiguration::new(APP_VERSION).with_user_agent(USER_AGENT);
|
||||
|
||||
// Fast path: resume a cached session (no TOTP). Validate with one cheap
|
||||
// authenticated call so a dead/revoked session falls through to a fresh login.
|
||||
if let Some(stored) = load_cached_session() {
|
||||
let session = ProtonApiSession::resume(config.clone(), stored.into_params())
|
||||
.expect("resume cached session");
|
||||
match session
|
||||
.http()
|
||||
.get::<serde_json::Value>("core/v4/users")
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
eprintln!("[auth] resumed cached session (no TOTP)");
|
||||
// Refresh may have rotated the tokens; re-persist the current set.
|
||||
save_session(&session).await;
|
||||
let client = ProtonDriveClient::new(&session, password.into_bytes());
|
||||
return Some(LiveClient {
|
||||
client,
|
||||
_session: session,
|
||||
});
|
||||
}
|
||||
Err(e) => eprintln!("[auth] cached session invalid ({e}); logging in fresh"),
|
||||
}
|
||||
}
|
||||
|
||||
let totp_secret = match read_totp_secret() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
@@ -67,8 +101,6 @@ async fn build_live_client() -> Option<LiveClient> {
|
||||
}
|
||||
};
|
||||
|
||||
let config = ProtonClientConfiguration::new(APP_VERSION).with_user_agent(USER_AGENT);
|
||||
|
||||
let mut session = ProtonApiSession::begin(config, &username, password.as_bytes())
|
||||
.await
|
||||
.expect("SRP login failed");
|
||||
@@ -81,6 +113,7 @@ async fn build_live_client() -> Option<LiveClient> {
|
||||
.expect("2FA submission failed");
|
||||
}
|
||||
|
||||
save_session(&session).await;
|
||||
let client = ProtonDriveClient::new(&session, password.into_bytes());
|
||||
Some(LiveClient {
|
||||
client,
|
||||
@@ -88,6 +121,74 @@ async fn build_live_client() -> Option<LiveClient> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Persisted session credentials. Mirrors [`ResumeParameters`], serialized to a
|
||||
/// gitignored cache file so reruns can skip SRP+2FA.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct StoredSession {
|
||||
session_id: String,
|
||||
username: String,
|
||||
user_id: String,
|
||||
access_token: String,
|
||||
refresh_token: String,
|
||||
scopes: Vec<String>,
|
||||
/// `1` = single, `2` = dual (matches Proton's wire value).
|
||||
password_mode: u8,
|
||||
}
|
||||
|
||||
impl StoredSession {
|
||||
fn into_params(self) -> ResumeParameters {
|
||||
ResumeParameters {
|
||||
session_id: self.session_id.into(),
|
||||
username: self.username,
|
||||
user_id: self.user_id.into(),
|
||||
access_token: self.access_token,
|
||||
refresh_token: self.refresh_token,
|
||||
scopes: self.scopes,
|
||||
is_waiting_for_second_factor_code: false,
|
||||
password_mode: match self.password_mode {
|
||||
1 => PasswordMode::Single,
|
||||
_ => PasswordMode::Dual,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Repo-root path of the gitignored session cache.
|
||||
fn session_cache_path() -> String {
|
||||
concat!(env!("CARGO_MANIFEST_DIR"), "/../../.proton_session.json").to_owned()
|
||||
}
|
||||
|
||||
/// Load the cached session, or `None` if absent/unparseable.
|
||||
fn load_cached_session() -> Option<StoredSession> {
|
||||
let text = std::fs::read_to_string(session_cache_path()).ok()?;
|
||||
serde_json::from_str(&text).ok()
|
||||
}
|
||||
|
||||
/// Persist the session's current tokens to the cache file (best-effort).
|
||||
async fn save_session(session: &ProtonApiSession) {
|
||||
let tokens = session.current_tokens().await;
|
||||
let stored = StoredSession {
|
||||
session_id: session.session_id().as_str().to_owned(),
|
||||
username: session.username().to_owned(),
|
||||
user_id: session.user_id().as_str().to_owned(),
|
||||
access_token: tokens.access_token,
|
||||
refresh_token: tokens.refresh_token,
|
||||
scopes: session.scopes().to_vec(),
|
||||
password_mode: match session.password_mode() {
|
||||
PasswordMode::Single => 1,
|
||||
PasswordMode::Dual => 2,
|
||||
},
|
||||
};
|
||||
match serde_json::to_string_pretty(&stored) {
|
||||
Ok(json) => {
|
||||
if let Err(e) = std::fs::write(session_cache_path(), json) {
|
||||
eprintln!("[auth] could not write session cache: {e}");
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("[auth] could not serialize session: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal `.env` reader: `key=value` lines, trims whitespace, ignores `#`.
|
||||
fn read_dotenv() -> Result<(String, String), Box<dyn std::error::Error>> {
|
||||
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../.env");
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use pgp::armor::Dearmor;
|
||||
use pgp::composed::{MessageBuilder, PlainSessionKey};
|
||||
use pgp::composed::{MessageBuilder, PlainSessionKey, RawSessionKey};
|
||||
use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
|
||||
use pgp::crypto::hash::HashAlgorithm;
|
||||
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
||||
@@ -17,7 +17,7 @@ use pgp::packet::{
|
||||
LiteralData, Packet, PacketParser, PacketTrait, PublicKeyEncryptedSessionKey,
|
||||
SymEncryptedProtectedData,
|
||||
};
|
||||
use pgp::types::{EskType, PkeskVersion, PublicKeyTrait};
|
||||
use pgp::types::{DecryptionKey, EncryptionKey, EskType, PkeskVersion, Seipdv1ReadMode};
|
||||
|
||||
use super::encrypt::{recipient_encryption_key, RecipientOp};
|
||||
use super::errors::CryptoError;
|
||||
@@ -49,7 +49,7 @@ impl ContentKey {
|
||||
pub fn generate() -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
let sym_alg = SymmetricKeyAlgorithm::AES256;
|
||||
let key = sym_alg.new_session_key(&mut rng).to_vec();
|
||||
let key = sym_alg.new_session_key(&mut rng);
|
||||
ContentKey {
|
||||
session_key: PlainSessionKey::V3_4 { sym_alg, key },
|
||||
}
|
||||
@@ -62,9 +62,7 @@ impl ContentKey {
|
||||
/// `PgpSessionKey.GenerateForAead()`.
|
||||
pub fn generate_aead() -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
let key = SymmetricKeyAlgorithm::AES256
|
||||
.new_session_key(&mut rng)
|
||||
.to_vec();
|
||||
let key = SymmetricKeyAlgorithm::AES256.new_session_key(&mut rng);
|
||||
ContentKey {
|
||||
session_key: PlainSessionKey::V6 { key },
|
||||
}
|
||||
@@ -191,8 +189,8 @@ impl ContentKey {
|
||||
/// official clients, which hardcode it for v6 session keys.
|
||||
fn session_parts(&self) -> Result<(SymmetricKeyAlgorithm, &[u8]), CryptoError> {
|
||||
match &self.session_key {
|
||||
PlainSessionKey::V3_4 { sym_alg, key } => Ok((*sym_alg, key)),
|
||||
PlainSessionKey::V6 { key } => Ok((SymmetricKeyAlgorithm::AES256, key)),
|
||||
PlainSessionKey::V3_4 { sym_alg, key } => Ok((*sym_alg, key.as_ref())),
|
||||
PlainSessionKey::V6 { key } => Ok((SymmetricKeyAlgorithm::AES256, key.as_ref())),
|
||||
_ => Err(CryptoError::Encrypt(
|
||||
"content key is not a V3/V4 or V6 session key".into(),
|
||||
)),
|
||||
@@ -284,7 +282,7 @@ impl ContentKey {
|
||||
};
|
||||
|
||||
seipd
|
||||
.decrypt(key, Some(sym_alg))
|
||||
.decrypt(key, Some(sym_alg), Seipdv1ReadMode::default())
|
||||
.map_err(|e| CryptoError::Decrypt(format!("block: {e}")))
|
||||
}
|
||||
}
|
||||
@@ -322,13 +320,16 @@ impl PrivateKey {
|
||||
let pw = self.password();
|
||||
let key = self.key();
|
||||
|
||||
// pgp 0.20: `DecryptionKey::decrypt` is implemented on the packet secret
|
||||
// (sub)keys (`SecretKey` / `SecretSubkey`), not on `SignedSecretKey`, so
|
||||
// reach through `primary_key` and each subkey's `.key`.
|
||||
let mut last_err: CryptoError;
|
||||
match key.decrypt_session_key(&pw, values, esk_type) {
|
||||
match key.primary_key.decrypt(&pw, values, esk_type) {
|
||||
Ok(Ok(session_key)) => return Ok(ContentKey { session_key }),
|
||||
Ok(Err(e)) | Err(e) => last_err = CryptoError::Decrypt(format!("content key: {e}")),
|
||||
}
|
||||
for subkey in &key.secret_subkeys {
|
||||
match subkey.decrypt_session_key(&pw, values, esk_type) {
|
||||
match subkey.key.decrypt(&pw, values, esk_type) {
|
||||
Ok(Ok(session_key)) => return Ok(ContentKey { session_key }),
|
||||
Ok(Err(e)) | Err(e) => {
|
||||
last_err = CryptoError::Decrypt(format!("content key (subkey): {e}"))
|
||||
@@ -428,9 +429,15 @@ struct ContentKeyPacketOp<'a> {
|
||||
impl RecipientOp for ContentKeyPacketOp<'_> {
|
||||
type Out = PublicKeyEncryptedSessionKey;
|
||||
|
||||
fn run(self, pubkey: &impl PublicKeyTrait) -> pgp::errors::Result<Self::Out> {
|
||||
fn run(self, pubkey: &impl EncryptionKey) -> pgp::errors::Result<Self::Out> {
|
||||
let mut rng = rand::thread_rng();
|
||||
PublicKeyEncryptedSessionKey::from_session_key_v3(&mut rng, self.key, self.sym_alg, pubkey)
|
||||
let session_key = RawSessionKey::from(self.key);
|
||||
PublicKeyEncryptedSessionKey::from_session_key_v3(
|
||||
&mut rng,
|
||||
&session_key,
|
||||
self.sym_alg,
|
||||
pubkey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,16 +450,19 @@ struct ContentKeyPacketV6Op<'a> {
|
||||
impl RecipientOp for ContentKeyPacketV6Op<'_> {
|
||||
type Out = PublicKeyEncryptedSessionKey;
|
||||
|
||||
fn run(self, pubkey: &impl PublicKeyTrait) -> pgp::errors::Result<Self::Out> {
|
||||
fn run(self, pubkey: &impl EncryptionKey) -> pgp::errors::Result<Self::Out> {
|
||||
let mut rng = rand::thread_rng();
|
||||
PublicKeyEncryptedSessionKey::from_session_key_v6(&mut rng, self.key, pubkey)
|
||||
let session_key = RawSessionKey::from(self.key);
|
||||
PublicKeyEncryptedSessionKey::from_session_key_v6(&mut rng, &session_key, pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pgp::composed::{KeyType, MessageBuilder, SecretKeyParamsBuilder, SignedSecretKey};
|
||||
use pgp::composed::{
|
||||
EncryptionCaps, KeyType, MessageBuilder, SecretKeyParamsBuilder, SignedSecretKey,
|
||||
};
|
||||
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
||||
|
||||
/// Generate a passphrase-locked RSA key able to both sign and encrypt — a
|
||||
@@ -463,16 +473,12 @@ mod tests {
|
||||
.key_type(KeyType::Rsa(2048))
|
||||
.can_sign(true)
|
||||
.can_certify(true)
|
||||
.can_encrypt(true)
|
||||
.can_encrypt(EncryptionCaps::All)
|
||||
.primary_user_id("node <node@proton.test>".into())
|
||||
.passphrase(Some(passphrase.to_owned()))
|
||||
.build()
|
||||
.expect("key params");
|
||||
params
|
||||
.generate(&mut rng)
|
||||
.expect("generate")
|
||||
.sign(&mut rng, &passphrase.into())
|
||||
.expect("sign")
|
||||
params.generate(&mut rng).expect("generate")
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -576,7 +582,7 @@ mod tests {
|
||||
other => panic!("expected SEIPD, got {:?}", other.tag()),
|
||||
};
|
||||
let inner = seipd
|
||||
.decrypt(key, Some(sym_alg))
|
||||
.decrypt(key, Some(sym_alg), Seipdv1ReadMode::default())
|
||||
.expect("decrypt thumbnail");
|
||||
|
||||
let mut inner_parser = PacketParser::new(Cursor::new(&inner));
|
||||
|
||||
@@ -9,15 +9,14 @@
|
||||
use base64::engine::general_purpose::STANDARD as BASE64;
|
||||
use base64::Engine;
|
||||
use bytes::Bytes;
|
||||
use chrono::SubsecRound;
|
||||
use pgp::composed::{
|
||||
ArmorOptions, KeyType, MessageBuilder, SecretKeyParamsBuilder, SignedSecretKey,
|
||||
StandaloneSignature, SubkeyParamsBuilder,
|
||||
ArmorOptions, DetachedSignature, EncryptionCaps, KeyType, MessageBuilder,
|
||||
SecretKeyParamsBuilder, SignedSecretKey, SubkeyParamsBuilder,
|
||||
};
|
||||
use pgp::crypto::hash::HashAlgorithm;
|
||||
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
||||
use pgp::packet::{SignatureConfig, SignatureType, Subpacket, SubpacketData};
|
||||
use pgp::types::{CompressionAlgorithm, KeyDetails, KeyVersion, Password, PublicKeyTrait};
|
||||
use pgp::types::{CompressionAlgorithm, EncryptionKey, KeyDetails, KeyVersion};
|
||||
use rand::RngCore;
|
||||
|
||||
use super::errors::CryptoError;
|
||||
@@ -34,7 +33,7 @@ pub(crate) trait RecipientOp {
|
||||
/// The value produced by the operation.
|
||||
type Out;
|
||||
/// Run the operation against the selected public key.
|
||||
fn run(self, pubkey: &impl PublicKeyTrait) -> pgp::errors::Result<Self::Out>;
|
||||
fn run(self, pubkey: &impl EncryptionKey) -> pgp::errors::Result<Self::Out>;
|
||||
}
|
||||
|
||||
/// Run `op` against the encryption-capable public key of `key`.
|
||||
@@ -47,14 +46,14 @@ pub(crate) fn recipient_encryption_key<Op: RecipientOp>(
|
||||
op: Op,
|
||||
) -> Result<Op::Out, CryptoError> {
|
||||
let primary = key.primary_key.public_key();
|
||||
if primary.is_encryption_key() {
|
||||
if primary.algorithm().can_encrypt() {
|
||||
return op
|
||||
.run(primary)
|
||||
.map_err(|e| CryptoError::Encrypt(format!("encrypt to primary key: {e}")));
|
||||
}
|
||||
for sub in &key.secret_subkeys {
|
||||
let pubsub = sub.public_key();
|
||||
if pubsub.is_encryption_key() {
|
||||
if pubsub.algorithm().can_encrypt() {
|
||||
return op
|
||||
.run(&pubsub)
|
||||
.map_err(|e| CryptoError::Encrypt(format!("encrypt to subkey: {e}")));
|
||||
@@ -108,7 +107,7 @@ fn generate_node_key_versioned(version: KeyVersion) -> Result<GeneratedNodeKey,
|
||||
let subkey = SubkeyParamsBuilder::default()
|
||||
.version(version)
|
||||
.key_type(KeyType::X25519)
|
||||
.can_encrypt(true)
|
||||
.can_encrypt(EncryptionCaps::All)
|
||||
.passphrase(Some(pw_string.clone()))
|
||||
.build()
|
||||
.map_err(|e| CryptoError::Encrypt(format!("node subkey params: {e}")))?;
|
||||
@@ -124,12 +123,11 @@ fn generate_node_key_versioned(version: KeyVersion) -> Result<GeneratedNodeKey,
|
||||
.build()
|
||||
.map_err(|e| CryptoError::Encrypt(format!("node key params: {e}")))?;
|
||||
|
||||
let secret = params
|
||||
// In pgp 0.20 `generate` already returns a `SignedSecretKey`, locked with the
|
||||
// passphrase set on the params (no separate sign/lock step as in 0.16).
|
||||
let signed = params
|
||||
.generate(&mut rng)
|
||||
.map_err(|e| CryptoError::Encrypt(format!("generate node key: {e}")))?;
|
||||
let signed = secret
|
||||
.sign(&mut rng, &Password::from(passphrase.as_slice()))
|
||||
.map_err(|e| CryptoError::Encrypt(format!("sign node key: {e}")))?;
|
||||
let locked_armored = signed
|
||||
.to_armored_string(None.into())
|
||||
.map_err(|e| CryptoError::Encrypt(format!("armor node key: {e}")))?;
|
||||
@@ -275,20 +273,23 @@ pub(crate) fn sign_detached(signer: &PrivateKey, data: &[u8]) -> Result<String,
|
||||
Subpacket::regular(SubpacketData::IssuerFingerprint(key.fingerprint()))
|
||||
.map_err(|e| CryptoError::Encrypt(format!("issuer fingerprint subpacket: {e}")))?,
|
||||
Subpacket::regular(SubpacketData::SignatureCreationTime(
|
||||
chrono::Utc::now().trunc_subsecs(0),
|
||||
pgp::types::Timestamp::now(),
|
||||
))
|
||||
.map_err(|e| CryptoError::Encrypt(format!("creation-time subpacket: {e}")))?,
|
||||
];
|
||||
if key.version() <= KeyVersion::V4 {
|
||||
config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(key.key_id()))
|
||||
.map_err(|e| CryptoError::Encrypt(format!("issuer subpacket: {e}")))?];
|
||||
config.unhashed_subpackets =
|
||||
vec![
|
||||
Subpacket::regular(SubpacketData::IssuerKeyId(key.legacy_key_id()))
|
||||
.map_err(|e| CryptoError::Encrypt(format!("issuer subpacket: {e}")))?,
|
||||
];
|
||||
}
|
||||
|
||||
let signature = config
|
||||
.sign(key, &pw, data)
|
||||
.map_err(|e| CryptoError::Encrypt(format!("detached sign: {e}")))?;
|
||||
|
||||
StandaloneSignature::new(signature)
|
||||
DetachedSignature::new(signature)
|
||||
.to_armored_string(ArmorOptions::default())
|
||||
.map_err(|e| CryptoError::Encrypt(format!("armor signature: {e}")))
|
||||
}
|
||||
@@ -304,7 +305,7 @@ struct EncryptSignOp<'a> {
|
||||
impl RecipientOp for EncryptSignOp<'_> {
|
||||
type Out = String;
|
||||
|
||||
fn run(self, pubkey: &impl PublicKeyTrait) -> pgp::errors::Result<String> {
|
||||
fn run(self, pubkey: &impl EncryptionKey) -> pgp::errors::Result<String> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut builder = MessageBuilder::from_bytes(Bytes::new(), self.data)
|
||||
.seipd_v1(&mut rng, SymmetricKeyAlgorithm::AES256);
|
||||
@@ -370,13 +371,13 @@ mod tests {
|
||||
// rejects with "Invalid manifest signature". rPGP self-verify above is not
|
||||
// enough to catch that — assert the subpackets explicitly.
|
||||
use pgp::composed::Deserializable as _;
|
||||
let (parsed, _) = StandaloneSignature::from_string(&sig).expect("parse detached sig");
|
||||
let (parsed, _) = DetachedSignature::from_string(&sig).expect("parse detached sig");
|
||||
assert!(
|
||||
parsed.signature.created().is_some(),
|
||||
"detached signature missing creation time"
|
||||
);
|
||||
assert!(
|
||||
!parsed.signature.issuer().is_empty(),
|
||||
!parsed.signature.issuer_key_id().is_empty(),
|
||||
"detached signature missing issuer key id"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! distinct pre-unlock step, so a [`PrivateKey`] simply pairs a parsed
|
||||
//! [`SignedSecretKey`] with the passphrase that unlocks it.
|
||||
|
||||
use pgp::composed::{Deserializable, SignedSecretKey, StandaloneSignature};
|
||||
use pgp::composed::{Deserializable, SignedSecretKey, DetachedSignature};
|
||||
use pgp::types::Password;
|
||||
|
||||
use super::errors::CryptoError;
|
||||
@@ -64,7 +64,7 @@ impl PrivateKey {
|
||||
|
||||
/// The public half of this key, for use as an anonymous-fallback verifier.
|
||||
pub(crate) fn signed_public_key(&self) -> pgp::composed::SignedPublicKey {
|
||||
self.key.signed_public_key()
|
||||
self.key.to_public_key()
|
||||
}
|
||||
|
||||
/// Verify an armored detached signature over `data` against this key's
|
||||
@@ -74,9 +74,9 @@ impl PrivateKey {
|
||||
armored_sig: &str,
|
||||
data: &[u8],
|
||||
) -> Result<(), CryptoError> {
|
||||
let (sig, _headers) = StandaloneSignature::from_string(armored_sig)
|
||||
let (sig, _headers) = DetachedSignature::from_string(armored_sig)
|
||||
.map_err(|e| CryptoError::Parse(format!("detached signature: {e}")))?;
|
||||
sig.verify(&self.key.signed_public_key(), data)
|
||||
sig.verify(&self.key.to_public_key(), data)
|
||||
.map_err(|e| CryptoError::Verification(e.to_string()))
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
//! throwing.
|
||||
|
||||
use pgp::composed::{
|
||||
Deserializable, Message, SignedPublicKey, SignedSecretKey, StandaloneSignature,
|
||||
Deserializable, Message, SignedPublicKey, SignedSecretKey, DetachedSignature,
|
||||
VerificationResult,
|
||||
};
|
||||
use pgp::types::{Password, PublicKeyTrait};
|
||||
use pgp::types::{Password, VerifyingKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::errors::CryptoError;
|
||||
@@ -111,8 +111,8 @@ impl VerificationKeyRing {
|
||||
self.keys.is_empty()
|
||||
}
|
||||
|
||||
fn dyn_refs(&self) -> Vec<&dyn PublicKeyTrait> {
|
||||
self.keys.iter().map(|k| k as &dyn PublicKeyTrait).collect()
|
||||
fn dyn_refs(&self) -> Vec<&dyn VerifyingKey> {
|
||||
self.keys.iter().map(|k| k as &dyn VerifyingKey).collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ pub fn verify_detached(
|
||||
data: &[u8],
|
||||
ring: &VerificationKeyRing,
|
||||
) -> VerificationStatus {
|
||||
let signature = match StandaloneSignature::from_string(armored_sig) {
|
||||
let signature = match DetachedSignature::from_string(armored_sig) {
|
||||
Ok((sig, _headers)) => sig,
|
||||
Err(_) => return VerificationStatus::Failed,
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user