main.rs (6191B)
1 #![crate_name = "gpgmime"] 2 3 //! gpgme creates multipart/encrypted emails from PGP messages. 4 //! 5 //! This multipart/encrypted structure can be successfully parsed 6 //! and decrypted inline by my favorite MUA, [mutt](http://mutt.org). 7 //! 8 //! Checking that it works - or getting it to work - with more PGP 9 //! capable MUAs wouldn't hurt. 10 //! 11 //! 12 //! ## Compatibility 13 //! 14 //! The tool parses both `.gpg` and `.asc` as input. 15 //! 16 //! 17 //! ## Example 18 //! 19 //! Let the follwing be the *ciphertext* stored in a file `msg.asc` 20 //! 21 //! ``` ignore, 22 //! -----BEGIN PGP MESSAGE----- 23 //! 24 //! hQGMA1IUfpa99cfFAQv/YrkKsUAo+jlSj3u+pSTcx+eNtCRgv3LHUUV1O3BpDMGp 25 //! tJYQrq+tRqhT29jnEZdJ+engC/gUHZYGOCburWFIKkStH1G4x5V4AWICtDcozPpK 26 //! ENIihA3kncbMsJrFcpgX4wmqA6c28ao9fzEGPcGWvA1jUV4g6qukQ2lOYgbmK2O6 27 //! wD5omaKLBguNVW6/PcTQ32kP4sGKwzS5B9R1X0FY7e3HJxMy0lnOpNQRY+g5AJZ0 28 //! ryncq+PPUYmjULCtr/BPm8idX2TBStx+1iqlvYQiW5x3tQIocWARWqILtNeYdHZF 29 //! a1CRSekj1bw4o5RzkJq92a9XqyiKlqna7X+W6E+59ZvoVUM3KgzCQ0MJkv1yQCo1 30 //! VX9kLoQ1ia658rsDg0YCZUyJ/kvD3z5gcLEL9/WRhlhbwfeUa6pkUUtk3TbAjqdb 31 //! GTj7wJDMUdA+Uuo5U5gglEL+Fl7wjDa4GnfTudtcfG0ImAoW433DstVp4Z7Qk2cW 32 //! uAprBVHHJSR+fNjszWhU0j8BxgsH/FeK87rbCgPvzZ1xWan4kdCfZXWrAt6ZV90U 33 //! Ic3i1AcP/R4tZ5oOxpLBXjkuXaOxI7YQ7LLI25t/Udo= 34 //! =YHyf 35 //! -----END PGP MESSAGE----- 36 //! ``` 37 //! 38 //! An example command and output is: 39 //! 40 //! ``` 41 //! From: "foo@bar.com" <foo@bar.com> 42 //! To: "merman@greyskull.com" <merman@greyskull.com> 43 //! Subject: Skeletor is looking a bit pale 44 //! Message-ID: <171d5d0aa79de7cf.2c1958e5681b1f5f.1dc0ff8081f726a3@piano> 45 //! Date: Wed, 12 Oct 2022 15:48:57 +0000 46 //! Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; 47 //! boundary="171d5d0aa79eef5a_ca50d29ee7659b74_1dc0ff8081f726a3" 48 //! Content-Disposition: inline 49 //! 50 //! 51 //! --171d5d0aa79eef5a_ca50d29ee7659b74_1dc0ff8081f726a3 52 //! Content-Type: application/pgp-encrypted 53 //! Content-Transfer-Encoding: base64 54 //! 55 //! VmVyc2lvbjogMQ== 56 //! 57 //! --171d5d0aa79eef5a_ca50d29ee7659b74_1dc0ff8081f726a3 58 //! Content-Type: application/octet-stream; charset="utf-8" 59 //! Content-Transfer-Encoding: 7bit 60 //! 61 //! -----BEGIN PGP MESSAGE----- 62 //! 63 //! wcDMA1IUfpa99cfFAQv/ReJTs3mkbFYI9ifay1jhL1XW5u9cDMFTymdxMtWUhJvl 64 //! AM5AgWPSnwfVVaS9Ger2robtLsR5UeAjNSOGXg8Vpu7SsbQGsDXdScCgvwEmSPPU 65 //! atJ+qdw/wPKR2GTD41hypJDsIeoT9l9lfSIBgzXdu+PNrryLFzqpI+q8y3KMOjTG 66 //! W52lvulIhYrFgRzhhOaX8Ss3Wnx7j1+4KXsvk0VY+rTdM0krsmEgzXHfHGECgKga 67 //! zlYQ3c4OhpnsmYi0+rpzCqbBrbtxDdMuBif7nbzYettcrkQssRuS41mFHHYuJWu4 68 //! A8fkoFmjlz5NVEXZSdsnMyPV6lSE9IcllUuFzKXPAGBy3xNay7JdcPU/1C7iylOh 69 //! M9mrfoe1gpPRefwXrjkiVuJ/uhYwgCxcIdzmgJ7XBtoBvZzNt440pP+bZqCGJgPt 70 //! HlmBvtGY5F6uopRC1e+PH7a+vIYss5rmJ4OQGdvWRgj2RDPo9cIfAsU6sFP530T6 71 //! ErzvOCXiYDv+9IPy7LzG0j8BGdQeIVYbHWRYxc3aQhlfSDvR6WYMWAnmXNZYUiEW 72 //! QS34XWww8FHxBGWnj8DbVXuvVYdjeiSteTdDmC4fsUY= 73 //! =dhci 74 //! -----END PGP MESSAGE----- 75 //! 76 //! --171d5d0aa79eef5a_ca50d29ee7659b74_1dc0ff8081f726a3-- 77 //! 78 //! ``` 79 //! 80 //! 81 //! ## Shortcomings 82 //! 83 //! - The application/pgp-encrypted field will contain `Version: 1` 84 //! no matter the version of the message. 85 86 use std::io::Write; 87 88 use sequoia_openpgp::parse::Parse; 89 use sequoia_openpgp::Message; 90 use sequoia_openpgp::serialize::SerializeInto; 91 use sequoia_openpgp::armor::Writer as ArmorWriter; 92 use sequoia_openpgp::armor::Kind; 93 use mail_builder::MessageBuilder; 94 use mail_builder::mime::MimePart; 95 use clap::App; 96 use clap::Arg; 97 98 struct Settings { 99 to: String, 100 from: String, 101 subject: String, 102 path: String, 103 } 104 105 impl Settings { 106 fn from_args() -> Settings { 107 let mut o = App::new("gpgmime"); 108 o = o.version(env!("CARGO_PKG_VERSION")); 109 o = o.author(env!("CARGO_PKG_AUTHORS")); 110 o = o.arg( 111 Arg::with_name("to") 112 .long("to") 113 .short("t") 114 .value_name("Email recipient") 115 .takes_value(true) 116 .required(true) 117 ); 118 o = o.arg( 119 Arg::with_name("from") 120 .long("from") 121 .short("f") 122 .value_name("Email sender") 123 .takes_value(true) 124 .required(true) 125 ); 126 o = o.arg( 127 Arg::with_name("subject") 128 .long("subject") 129 .short("s") 130 .value_name("Email subject") 131 .takes_value(true) 132 .required(true) 133 ); 134 o = o.arg( 135 Arg::with_name("PATH") 136 .help("Path to PGP message") 137 .required(true) 138 ); 139 let arg = o.get_matches(); 140 let settings = Settings{ 141 to: arg.value_of("to").unwrap().to_string(), 142 from: arg.value_of("from").unwrap().to_string(), 143 subject: arg.value_of("subject").unwrap().to_string(), 144 path: arg.value_of("PATH").unwrap().to_string(), 145 }; 146 settings 147 } 148 } 149 150 151 #[doc(hidden)] 152 fn main() { 153 let settings = Settings::from_args(); 154 155 let msg = Message::from_file(settings.path).unwrap(); 156 157 let mut msg_w = ArmorWriter::new(Vec::new(), Kind::Message).unwrap(); 158 let _c = msg_w.write_all(msg.to_vec().unwrap().as_ref()); 159 let msg_b = msg_w.finalize().unwrap(); 160 let msg_s = String::from_utf8_lossy(&msg_b); 161 162 let enc_version_part = MimePart::new_binary( 163 "application/pgp-encrypted", 164 "Version: 1".as_bytes(), 165 ); 166 167 let enc_contents_part = MimePart::new_text_other( 168 "application/octet-stream", 169 msg_s, 170 ); 171 172 let enc_envelope = MimePart::new_multipart( 173 "multipart/encrypted; protocol=\"application/pgp-encrypted\"", 174 vec![ 175 enc_version_part, 176 enc_contents_part, 177 ], 178 ) 179 .inline(); 180 181 let subject: &str = settings.subject.as_ref(); 182 let eml = MessageBuilder::new() 183 .from((settings.from.as_ref(), settings.from.as_ref())) 184 .to((settings.to.as_ref(), settings.to.as_ref())) 185 .subject(subject) 186 .body(enc_envelope); 187 188 println!("{}", eml.write_to_string().unwrap()); 189 }