smime_spec.rb 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. require 'rails_helper'
  2. RSpec.describe SecureMailing::SMIME do
  3. before do
  4. Setting.set('smime_integration', true)
  5. end
  6. let(:raw_body) { 'Some text' }
  7. let(:system_email_address) { 'smime1@example.com' }
  8. let(:customer_email_address) { 'smime2@example.com' }
  9. let(:sender_certificate_subject) { "/emailAddress=#{sender_email_address}/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com" }
  10. let(:recipient_certificate_subject) { "/emailAddress=#{recipient_email_address}/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com" }
  11. let(:expired_email_address) { 'expiredsmime1@example.com' }
  12. let(:ca_certificate_subject) { '/emailAddress=ca@example.com/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com' }
  13. let(:content_type) { 'text/plain' }
  14. def build_mail
  15. Channel::EmailBuild.build(
  16. from: sender_email_address,
  17. to: recipient_email_address,
  18. body: raw_body,
  19. content_type: content_type,
  20. security: security_preferences
  21. )
  22. end
  23. describe '.outgoing' do
  24. shared_examples 'HttpLog writer' do |status|
  25. it "logs #{status}" do
  26. expect do
  27. build_mail
  28. rescue
  29. # allow failures
  30. end.to change(HttpLog, :count).by(1)
  31. expect(HttpLog.last.attributes).to include('direction' => 'out', 'status' => status)
  32. end
  33. end
  34. let(:sender_email_address) { system_email_address }
  35. let(:recipient_email_address) { customer_email_address }
  36. context 'without security' do
  37. let(:security_preferences) do
  38. nil
  39. end
  40. it 'builds mail' do
  41. expect(build_mail.body).not_to match(SecureMailing::SMIME::Incoming::EXPRESSION_SIGNATURE)
  42. expect(build_mail.body).not_to match(SecureMailing::SMIME::Incoming::EXPRESSION_MIME)
  43. expect(build_mail.body).to eq(raw_body)
  44. end
  45. end
  46. context 'signing' do
  47. let(:security_preferences) do
  48. {
  49. type: 'S/MIME',
  50. sign: {
  51. success: true,
  52. },
  53. encryption: {
  54. success: false,
  55. },
  56. }
  57. end
  58. context 'private key present' do
  59. before do
  60. create(:smime_certificate, :with_private, fixture: system_email_address)
  61. end
  62. it 'builds mail' do
  63. expect(build_mail.body).to match(SecureMailing::SMIME::Incoming::EXPRESSION_SIGNATURE)
  64. end
  65. it_behaves_like 'HttpLog writer', 'success'
  66. context 'expired certificate' do
  67. let(:system_email_address) { expired_email_address }
  68. it 'raises exception' do
  69. expect { build_mail }.to raise_error RuntimeError
  70. end
  71. it_behaves_like 'HttpLog writer', 'failed'
  72. end
  73. context 'when message is 7bit or 8bit encoded' do
  74. let(:mail) do
  75. smime_mail = build_mail
  76. mail = Channel::EmailParser.new.parse(smime_mail.to_s)
  77. SecureMailing.incoming(mail)
  78. mail
  79. end
  80. context 'when Content-Type is text/plain' do
  81. let(:raw_body) { "\r\n\r\n@john.doe, now known as John Dóe has accepted your invitation to join the Administrator / htmltest project.\r\n\r\nhttp://169.254.169.254:3000/root/htmltest\r\n\r\n-- \r\nYou're receiving this email because of your account on 169.254.169.254.\r\n\r\n\r\n\r\n" }
  82. it 'verifies' do
  83. expect(mail['x-zammad-article-preferences']['security']['sign']['success']).to be true
  84. end
  85. end
  86. context 'when Content-Type is text/html' do
  87. let(:content_type) { 'text/html' }
  88. let(:raw_body) { "<div><ul><li><p>an \nexample „Text“ with ümläütß. </p></li></ul></div>" }
  89. it 'verifies' do
  90. expect(mail['x-zammad-article-preferences']['security']['sign']['success']).to be true
  91. end
  92. end
  93. end
  94. end
  95. context 'no private key present' do
  96. before do
  97. create(:smime_certificate, fixture: system_email_address)
  98. end
  99. it 'raises exception' do
  100. expect { build_mail }.to raise_error RuntimeError
  101. end
  102. it_behaves_like 'HttpLog writer', 'failed'
  103. end
  104. end
  105. context 'encryption' do
  106. let(:security_preferences) do
  107. {
  108. type: 'S/MIME',
  109. sign: {
  110. success: false,
  111. },
  112. encryption: {
  113. success: true,
  114. },
  115. }
  116. end
  117. context 'public key present' do
  118. before do
  119. create(:smime_certificate, fixture: recipient_email_address)
  120. end
  121. it 'builds mail' do
  122. mail = build_mail
  123. expect(mail['Content-Type'].value).to match(SecureMailing::SMIME::Incoming::EXPRESSION_MIME)
  124. expect(mail.body).not_to include(raw_body)
  125. end
  126. it_behaves_like 'HttpLog writer', 'success'
  127. context 'expired certificate' do
  128. let(:recipient_email_address) { expired_email_address }
  129. it 'raises exception' do
  130. expect { build_mail }.to raise_error RuntimeError
  131. end
  132. it_behaves_like 'HttpLog writer', 'failed'
  133. end
  134. end
  135. context 'no public key present' do
  136. it 'raises exception' do
  137. expect { build_mail }.to raise_error ActiveRecord::RecordNotFound
  138. end
  139. it_behaves_like 'HttpLog writer', 'failed'
  140. end
  141. end
  142. end
  143. describe '.incoming' do
  144. shared_examples 'HttpLog writer' do |status|
  145. it "logs #{status}" do
  146. expect do
  147. mail
  148. rescue
  149. # allow failures
  150. end.to change(HttpLog, :count).by(2)
  151. expect(HttpLog.last.attributes).to include('direction' => 'in', 'status' => status)
  152. end
  153. end
  154. let(:sender_email_address) { customer_email_address }
  155. let(:recipient_email_address) { system_email_address }
  156. context 'signature verification' do
  157. let(:allow_expired) { false }
  158. let(:security_preferences) do
  159. {
  160. type: 'S/MIME',
  161. sign: {
  162. success: true,
  163. allow_expired: allow_expired,
  164. },
  165. encryption: {
  166. success: false,
  167. },
  168. }
  169. end
  170. context 'sender certificate present' do
  171. before do
  172. create(:smime_certificate, :with_private, fixture: sender_email_address)
  173. end
  174. let(:mail) do
  175. smime_mail = build_mail
  176. mail = Channel::EmailParser.new.parse(smime_mail.to_s)
  177. SecureMailing.incoming(mail)
  178. mail
  179. end
  180. it 'verifies' do
  181. expect(mail[:body]).to include(raw_body)
  182. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be true
  183. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq(sender_certificate_subject)
  184. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
  185. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil
  186. end
  187. it_behaves_like 'HttpLog writer', 'success'
  188. context 'expired' do
  189. # required to build mail with expired certificate
  190. let(:allow_expired) { true }
  191. let(:sender_email_address) { expired_email_address }
  192. it 'verifies with comment' do
  193. expect(mail[:body]).to include(raw_body)
  194. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be true
  195. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to include(expired_email_address).and include('expired')
  196. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
  197. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil
  198. end
  199. it_behaves_like 'HttpLog writer', 'success'
  200. end
  201. end
  202. context 'no sender certificate' do
  203. let!(:sender_certificate) { create(:smime_certificate, :with_private, fixture: sender_email_address) }
  204. let(:mail) do
  205. smime_mail = build_mail
  206. mail = Channel::EmailParser.new.parse(smime_mail.to_s)
  207. sender_certificate.destroy!
  208. SecureMailing.incoming(mail)
  209. mail
  210. end
  211. it 'fails' do
  212. expect(mail[:body]).to include(raw_body)
  213. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false
  214. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq('Unable to find certificate for verification')
  215. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
  216. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil
  217. end
  218. context 'public key present in signature' do
  219. let(:not_related_fixture) { 'smime3@example.com' }
  220. let!(:not_related_certificate) { create(:smime_certificate, fixture: not_related_fixture) }
  221. context 'not related certificate present' do
  222. it 'fails' do
  223. expect(mail[:body]).to include(raw_body)
  224. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false
  225. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq('Unable to find certificate for verification')
  226. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
  227. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil
  228. end
  229. it_behaves_like 'HttpLog writer', 'failed'
  230. context 'CA' do
  231. let(:not_related_fixture) { 'expiredca' }
  232. it 'fails' do
  233. expect(mail[:body]).to include(raw_body)
  234. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false
  235. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq('Unable to find certificate for verification')
  236. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
  237. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil
  238. end
  239. it_behaves_like 'HttpLog writer', 'failed'
  240. end
  241. end
  242. context 'usage not prevented' do
  243. let(:not_related_certificate_subject) { "/emailAddress=#{not_related_fixture}/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com" }
  244. before do
  245. # remove OpenSSL::PKCS7::NOINTERN
  246. stub_const('SecureMailing::SMIME::Incoming::OPENSSL_PKCS7_VERIFY_FLAGS', OpenSSL::PKCS7::NOVERIFY)
  247. end
  248. it 'wrongly performs a verification' do
  249. expect(mail[:body]).to include(raw_body)
  250. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be true
  251. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq(not_related_certificate_subject)
  252. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
  253. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil
  254. end
  255. end
  256. end
  257. context 'root CA present' do
  258. before do
  259. create(:smime_certificate, fixture: ca_fixture)
  260. end
  261. let(:ca_fixture) { 'ca' }
  262. it 'verifies' do
  263. expect(mail[:body]).to include(raw_body)
  264. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be true
  265. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq(ca_certificate_subject)
  266. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
  267. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil
  268. end
  269. it_behaves_like 'HttpLog writer', 'success'
  270. context 'expired' do
  271. let(:ca_fixture) { 'expiredca' }
  272. it 'fails' do
  273. expect(mail[:body]).to include(raw_body)
  274. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false
  275. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq('Unable to find certificate for verification')
  276. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
  277. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil
  278. end
  279. it_behaves_like 'HttpLog writer', 'failed'
  280. context 'allowed' do
  281. let(:allow_expired) { true }
  282. # ATTENTION: expired CA is a special case where `allow_expired` does not count
  283. it 'fails' do
  284. expect(mail[:body]).to include(raw_body)
  285. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false
  286. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq('Unable to find certificate for verification')
  287. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
  288. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil
  289. end
  290. it_behaves_like 'HttpLog writer', 'failed'
  291. end
  292. end
  293. end
  294. end
  295. end
  296. context 'decryption' do
  297. let(:allow_expired) { false }
  298. let(:security_preferences) do
  299. {
  300. type: 'S/MIME',
  301. sign: {
  302. success: false,
  303. },
  304. encryption: {
  305. success: true,
  306. allow_expired: allow_expired,
  307. },
  308. }
  309. end
  310. let!(:sender_certificate) { create(:smime_certificate, :with_private, fixture: sender_email_address) }
  311. let!(:recipient_certificate) { create(:smime_certificate, :with_private, fixture: recipient_email_address) }
  312. context 'private key present' do
  313. let(:mail) do
  314. smime_mail = build_mail
  315. mail = Channel::EmailParser.new.parse(smime_mail.to_s)
  316. SecureMailing.incoming(mail)
  317. mail
  318. end
  319. it 'decrypts' do
  320. expect(mail[:body]).to include(raw_body)
  321. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false
  322. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to be nil
  323. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be true
  324. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to eq(recipient_certificate_subject)
  325. end
  326. it_behaves_like 'HttpLog writer', 'success'
  327. context 'expired allowed' do
  328. let(:allow_expired) { true }
  329. let(:system_email_address) { expired_email_address }
  330. it 'decrypts with comment' do
  331. expect(mail[:body]).to include(raw_body)
  332. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false
  333. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to be nil
  334. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be true
  335. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to include(expired_email_address).and include('expired')
  336. end
  337. it_behaves_like 'HttpLog writer', 'success'
  338. end
  339. end
  340. context 'no private key present' do
  341. let(:mail) do
  342. smime_mail = build_mail
  343. mail = Channel::EmailParser.new.parse(smime_mail.to_s)
  344. sender_certificate.destroy!
  345. recipient_certificate.destroy!
  346. SecureMailing.incoming(mail)
  347. mail
  348. end
  349. it 'fails' do
  350. expect(mail[:body]).to include('no visible content')
  351. expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false
  352. expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to be nil
  353. expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
  354. expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to eq('Unable to find private key to decrypt')
  355. end
  356. it_behaves_like 'HttpLog writer', 'failed'
  357. end
  358. end
  359. end
  360. describe '.retry' do
  361. let(:sender_email_address) { customer_email_address }
  362. let(:recipient_email_address) { system_email_address }
  363. let(:security_preferences) do
  364. {
  365. type: 'S/MIME',
  366. sign: {
  367. success: true,
  368. },
  369. encryption: {
  370. success: true,
  371. },
  372. }
  373. end
  374. let(:mail) do
  375. sender_certificate = create(:smime_certificate, :with_private, fixture: sender_email_address)
  376. recipient_certificate = create(:smime_certificate, :with_private, fixture: system_email_address)
  377. smime_mail = Channel::EmailBuild.build(
  378. from: sender_email_address,
  379. to: recipient_email_address,
  380. body: raw_body,
  381. content_type: 'text/plain',
  382. security: security_preferences,
  383. attachments: [
  384. {
  385. content_type: 'text/plain',
  386. content: 'blub',
  387. filename: 'test-file1.txt',
  388. },
  389. ],
  390. )
  391. mail = Channel::EmailParser.new.parse(smime_mail.to_s)
  392. sender_certificate.destroy
  393. recipient_certificate.destroy
  394. mail
  395. end
  396. let!(:article) do
  397. _ticket, article, _user, _mail = Channel::EmailParser.new.process({}, mail['raw'] )
  398. article
  399. end
  400. context 'private key added' do
  401. before do
  402. create(:smime_certificate, :with_private, fixture: recipient_email_address)
  403. create(:smime_certificate, fixture: sender_email_address)
  404. end
  405. it 'succeeds' do
  406. SecureMailing.retry(article)
  407. expect(article.preferences[:security][:sign][:success]).to be true
  408. expect(article.preferences[:security][:sign][:comment]).to eq(sender_certificate_subject)
  409. expect(article.preferences[:security][:encryption][:success]).to be true
  410. expect(article.preferences[:security][:encryption][:comment]).to eq(recipient_certificate_subject)
  411. expect(article.body).to include(raw_body)
  412. expect(article.attachments.count).to eq(1)
  413. expect(article.attachments.first.filename).to eq('test-file1.txt')
  414. end
  415. context 'S/MIME activated' do
  416. before do
  417. Setting.set('smime_integration', false)
  418. end
  419. it 'succeeds' do
  420. Setting.set('smime_integration', true)
  421. SecureMailing.retry(article)
  422. expect(article.preferences[:security][:sign][:success]).to be true
  423. expect(article.preferences[:security][:sign][:comment]).to eq(sender_certificate_subject)
  424. expect(article.preferences[:security][:encryption][:success]).to be true
  425. expect(article.preferences[:security][:encryption][:comment]).to eq(recipient_certificate_subject)
  426. expect(article.body).to include(raw_body)
  427. expect(article.attachments.count).to eq(1)
  428. expect(article.attachments.first.filename).to eq('test-file1.txt')
  429. end
  430. end
  431. end
  432. end
  433. end