diff --git a/Gemfile b/Gemfile index b079a4019..db01175f8 100644 --- a/Gemfile +++ b/Gemfile @@ -38,6 +38,9 @@ gem 'aasm' # core - authorization gem 'pundit' +# core - image processing +gem 'rszr', '0.5.2' + # performance - Memcached gem 'dalli' @@ -130,8 +133,8 @@ gem 'autodiscover', git: 'https://github.com/zammad-deps/autodiscover' gem 'rubyntlm', git: 'https://github.com/wimm/rubyntlm' gem 'viewpoint' -# image processing -gem 'rszr', '0.5.2' +# integrations - S/MIME +gem 'openssl' # Gems used only for develop/test and not required # in production environments by default. diff --git a/Gemfile.lock b/Gemfile.lock index a1c9ebe5e..a603ebf3c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -355,6 +355,7 @@ GEM omniauth-weibo-oauth2 (0.5.2) omniauth (~> 1.5) omniauth-oauth2 (>= 1.4.0) + openssl (2.1.2) parallel (1.19.1) parser (2.7.1.2) ast (~> 2.4.0) @@ -629,6 +630,7 @@ DEPENDENCIES omniauth-saml omniauth-twitter omniauth-weibo-oauth2 + openssl pg (= 0.21.0) pre-commit pry-rails diff --git a/LICENSE-ICONS-3RD-PARTY.json b/LICENSE-ICONS-3RD-PARTY.json index 80b42fd76..48d19536c 100644 --- a/LICENSE-ICONS-3RD-PARTY.json +++ b/LICENSE-ICONS-3RD-PARTY.json @@ -1,170 +1,135 @@ { - "spinner-small.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "sms.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "unordered-list.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "underline.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "strikethrough.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "split.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "reply.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "weibo-button.svg": { - "author": "Weibo", + "archived-modifier.svg": { + "author": "", "url": "", "license": "" }, - "telegram.svg": { - "author": "Telegram", - "url": "", - "license": "" - }, - "twitter-button.svg": { - "author": "Twitter", - "url": "twitter.com", - "license": "" - }, - "zoom-out.svg": { + "arrow-down.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "zoom-in.svg": { + "arrow-left.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "web.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "user.svg": { - "author": "R\u00e9my M\u00e9dard", - "url": "https:\/\/thenounproject.com\/search\/?q=user&i=10314", - "license": "CC 3.0 Attribution" - }, - "unmute.svg": { + "arrow-right.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "twitter.svg": { - "author": "Twitter", - "url": "twitter.com", - "license": "" - }, - "trash.svg": { - "author": "Filip Malinowski", - "url": "https:\/\/thenounproject.com\/term\/trash\/16505\/", - "license": "CC 3.0 Attribution" - }, - "total-tickets.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "tools.svg": { - "author": "Michael Kussmaul", - "url": "https:\/\/thenounproject.com\/term\/tools\/41655\/", - "license": "CC 3.0 Attribution" - }, - "templates.svg": { + "arrow-up.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "team.svg": { - "author": "R\u00e9my M\u00e9dard", - "url": "https:\/\/thenounproject.com\/catalarem\/uploads\/?i=2554", - "license": "CC 3.0 Attribution" - }, - "task-state.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "switchView.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "stopwatch.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "status.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "status-modified-outer-circle.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "file-text.svg": { + "bold.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "radio.svg": { + "chain.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "chat.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "checkbox-checked.svg": { "author": "Zammad", "url": "", "license": "MIT" }, - "radio-checked.svg": { + "checkbox-indeterminate.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "checkbox.svg": { "author": "Zammad", "url": "", "license": "MIT" }, + "checkmark.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "clipboard.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, "clock.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "important.svg": { + "cloud.svg": { + "author": "Kirill Ulitin", + "url": "https:\/\/thenounproject.com\/search\/?q=cloud&i=84976", + "license": "CC 3.0 Attribution" + }, + "cog.svg": { + "author": "Melvin Salas", + "url": "https:\/\/thenounproject.com\/term\/gear\/17369\/", + "license": "CC 3.0 Attribution" + }, + "crown.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, + "danger.svg": { + "author": "", + "url": "", + "license": "" + }, + "dashboard.svg": { + "author": "Anton Gajdosik", + "url": "https:\/\/thenounproject.com\/term\/gauge\/186120", + "license": "CC 3.0 Attribution" + }, "diagonal-cross.svg": { "author": "Zammad", "url": "", "license": "MIT" }, - "lock-open.svg": { + "document.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "download.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "draft-modifier.svg": { + "author": "", + "url": "", + "license": "" + }, + "draggable.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "dropdown-list.svg": { "author": "Zammad", "url": "", "license": "MIT" }, - "rearange.svg": { + "email-button.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "email.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" @@ -174,57 +139,107 @@ "url": "", "license": "MIT" }, - "mood-sad.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "radio.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "radio-checked.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "knowledge-base.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, "eye.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "document.svg": { + "eyedropper.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "low-priority.svg": { + "facebook-button.svg": { + "author": "Facebook", + "url": "", + "license": "" + }, + "facebook.svg": { + "author": "Facebook", + "url": "", + "license": "" + }, + "file-archive.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "long-arrow-down.svg": { + "file-code.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "italic.svg": { + "file-email.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "inactive-user.svg": { + "file-excel.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "inactive-organization.svg": { + "file-pdf.svg": { + "author": "Adobe", + "url": "", + "license": "" + }, + "file-powerpoint.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "file-text.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "file-unknown.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "file-word.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "form.svg": { + "author": "Pickin Studio", + "url": "https:\/\/thenounproject.com\/search\/?q=website&i=16523", + "license": "CC 3.0 Attribution" + }, + "forward.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "full-logo.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "github-button.svg": { + "author": "Github", + "url": "", + "license": "" + }, + "gitlab-button.svg": { + "author": "Gitlab", + "url": "", + "license": "" + }, + "google-button.svg": { + "author": "Google", + "url": "", + "license": "" + }, + "group.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "help.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" @@ -239,302 +254,17 @@ "url": "", "license": "MIT" }, - "reply-all.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "paperclip.svg": { - "author": "Cheesefork", - "url": "https:\/\/thenounproject.com\/search\/?q=attachment&i=197956", - "license": "CC 3.0 Attribution" - }, - "overflow-button.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "lock.svg": { + "in-process.svg": { "author": "Zammad", "url": "", "license": "MIT" }, - "lock-open.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "forward.svg": { + "inactive-organization.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "file-word.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "file-unknown.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "file-powerpoint.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "file-pdf.svg": { - "author": "Adobe", - "url": "", - "license": "" - }, - "file-excel.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "file-email.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "file-code.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "file-archive.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "office365-button.svg": { - "author": "Office 365", - "url": "", - "license": "" - }, - "logo.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "printer.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "note.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "phone.svg": { - "author": "Michael Zenaty", - "url": "https:\/\/thenounproject.com\/search\/?q=phone&i=21797", - "license": "CC 3.0 Attribution" - }, - "email.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "oauth2-button.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "linkedin-button.svg": { - "author": "Linkedin", - "url": "", - "license": "" - }, - "google-button.svg": { - "author": "Google", - "url": "", - "license": "" - }, - "gitlab-button.svg": { - "author": "Gitlab", - "url": "", - "license": "" - }, - "github-button.svg": { - "author": "Github", - "url": "", - "license": "" - }, - "facebook-button.svg": { - "author": "Facebook", - "url": "", - "license": "" - }, - "email-button.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "line-right-arrow.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "small-dot.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "signout.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "searchdetail.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "report.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "reopening.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "reload.svg": { - "author": "Anand A Nair", - "url": "https:\/\/thenounproject.com\/anandgrafiti\/uploads\/?i=2149", - "license": "CC 3.0 Attribution" - }, - "received-calls.svg": { - "author": "Michael Zenaty", - "url": "https:\/\/thenounproject.com\/search\/?q=phone&i=21797", - "license": "CC 3.0 Attribution" - }, - "plus.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "plus-small.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "person.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "pen.svg": { - "author": "Dmitry Baranovskiy", - "url": "https:\/\/thenounproject.com\/search\/?q=edit&i=5039", - "license": "CC 3.0 Attribution" - }, - "package.svg": { - "author": "Michael Wallner", - "url": "https:\/\/thenounproject.com\/search\/?q=package&i=25152", - "license": "CC 3.0 Attribution" - }, - "overviews.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "outbound-calls.svg": { - "author": "Michael Zenaty", - "url": "https:\/\/thenounproject.com\/search\/?q=phone&i=21797", - "license": "CC 3.0 Attribution" - }, - "organization.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "one-ticket.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "mute.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "mood-supergood.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "mood-superbad.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "mood-ok.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "mood-good.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "mood-bad.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "minus.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "minus-small.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "message.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "marker.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "magnifier.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "long-arrow-right.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "logotype.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "loading.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "list.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "line-left-arrow.svg": { + "inactive-user.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" @@ -544,147 +274,417 @@ "url": "https:\/\/thenounproject.com\/search\/?q=info&i=176431", "license": "CC 3.0 Attribution" }, - "in-process.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "help.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "group.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "full-logo.svg": { - "author": "Zammad", - "url": "", - "license": "MIT" - }, - "form.svg": { - "author": "Pickin Studio", - "url": "https:\/\/thenounproject.com\/search\/?q=website&i=16523", - "license": "CC 3.0 Attribution" - }, - "facebook.svg": { - "author": "Facebook", + "internal-modifier.svg": { + "author": "", "url": "", "license": "" }, - "eyedropper.svg": { + "italic.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "dropdown-list.svg": { + "knowledge-base-answer.svg": { + "author": "", + "url": "", + "license": "" + }, + "knowledge-base.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "line-left-arrow.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "line-right-arrow.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "linkedin-button.svg": { + "author": "Linkedin", + "url": "", + "license": "" + }, + "list.svg": { "author": "Zammad", "url": "", "license": "MIT" }, - "draggable.svg": { + "loading.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "lock-open.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "lock.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "logo.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "logotype.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "long-arrow-down.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "download.svg": { + "long-arrow-right.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "low-priority.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "dashboard.svg": { - "author": "Anton Gajdosik", - "url": "https:\/\/thenounproject.com\/term\/gauge\/186120", + "magnifier.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "marker.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "message.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "minus-small.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "minus.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "mood-bad.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "mood-good.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "mood-ok.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "mood-sad.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "mood-superbad.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "mood-supergood.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "mute.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "not-signed.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "note.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "oauth2-button.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "office365-button.svg": { + "author": "Office 365", + "url": "", + "license": "" + }, + "one-ticket.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "organization.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "outbound-calls.svg": { + "author": "Michael Zenaty", + "url": "https:\/\/thenounproject.com\/search\/?q=phone&i=21797", "license": "CC 3.0 Attribution" }, - "crown.svg": { + "overflow-button.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "cog.svg": { - "author": "Melvin Salas", - "url": "https:\/\/thenounproject.com\/term\/gear\/17369\/", + "overviews.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "package.svg": { + "author": "Michael Wallner", + "url": "https:\/\/thenounproject.com\/search\/?q=package&i=25152", "license": "CC 3.0 Attribution" }, - "cloud.svg": { - "author": "Kirill Ulitin", - "url": "https:\/\/thenounproject.com\/search\/?q=cloud&i=84976", + "paperclip.svg": { + "author": "Cheesefork", + "url": "https:\/\/thenounproject.com\/search\/?q=attachment&i=197956", "license": "CC 3.0 Attribution" }, - "clipboard.svg": { + "pen.svg": { + "author": "Dmitry Baranovskiy", + "url": "https:\/\/thenounproject.com\/search\/?q=edit&i=5039", + "license": "CC 3.0 Attribution" + }, + "person.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "bold.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" + "phone.svg": { + "author": "Michael Zenaty", + "url": "https:\/\/thenounproject.com\/search\/?q=phone&i=21797", + "license": "CC 3.0 Attribution" }, - "checkbox.svg": { + "plus-small.svg": { "author": "Zammad", "url": "", "license": "MIT" }, - "checkbox-indeterminate.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "checkmark.svg": { + "plus.svg": { "author": "Zammad", "url": "", "license": "MIT" }, - "chain.svg": { + "printer.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "bold.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "checkbox.svg": { + "radio-checked.svg": { "author": "Zammad", "url": "", "license": "MIT" }, - "checkbox-indeterminate.svg": { - "author": "Felix Niklas", - "url": "", - "license": "MIT" - }, - "checkbox-checked.svg": { + "radio.svg": { "author": "Zammad", "url": "", "license": "MIT" }, - "arrow-down.svg": { + "rearange.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "arrow-up.svg": { + "received-calls.svg": { + "author": "Michael Zenaty", + "url": "https:\/\/thenounproject.com\/search\/?q=phone&i=21797", + "license": "CC 3.0 Attribution" + }, + "reload.svg": { + "author": "Anand A Nair", + "url": "https:\/\/thenounproject.com\/anandgrafiti\/uploads\/?i=2149", + "license": "CC 3.0 Attribution" + }, + "reopening.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "reply-all.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "arrow-right.svg": { + "reply.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "arrow-left.svg": { + "report.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "searchdetail.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" }, - "chat.svg": { + "signed.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "signout.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "small-dot.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "sms.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "spinner-small.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "split.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "status-modified-outer-circle.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "status.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "stopwatch.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "strikethrough.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "switchView.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "task-state.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "team.svg": { + "author": "R\u00e9my M\u00e9dard", + "url": "https:\/\/thenounproject.com\/catalarem\/uploads\/?i=2554", + "license": "CC 3.0 Attribution" + }, + "telegram.svg": { + "author": "Telegram", + "url": "", + "license": "" + }, + "templates.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "tools.svg": { + "author": "Michael Kussmaul", + "url": "https:\/\/thenounproject.com\/term\/tools\/41655\/", + "license": "CC 3.0 Attribution" + }, + "total-tickets.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "trash.svg": { + "author": "Filip Malinowski", + "url": "https:\/\/thenounproject.com\/term\/trash\/16505\/", + "license": "CC 3.0 Attribution" + }, + "twitter-button.svg": { + "author": "Twitter", + "url": "twitter.com", + "license": "" + }, + "twitter.svg": { + "author": "Twitter", + "url": "twitter.com", + "license": "" + }, + "underline.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "unmute.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "unordered-list.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "user.svg": { + "author": "R\u00e9my M\u00e9dard", + "url": "https:\/\/thenounproject.com\/search\/?q=user&i=10314", + "license": "CC 3.0 Attribution" + }, + "web.svg": { + "author": "Zammad", + "url": "", + "license": "MIT" + }, + "weibo-button.svg": { + "author": "Weibo", + "url": "", + "license": "" + }, + "zoom-in.svg": { + "author": "Felix Niklas", + "url": "", + "license": "MIT" + }, + "zoom-out.svg": { "author": "Felix Niklas", "url": "", "license": "MIT" diff --git a/app/assets/javascripts/app/controllers/_integration/smime.coffee b/app/assets/javascripts/app/controllers/_integration/smime.coffee new file mode 100644 index 000000000..542f8bbc1 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_integration/smime.coffee @@ -0,0 +1,258 @@ +class Index extends App.ControllerIntegrationBase + featureIntegration: 'smime_integration' + featureName: 'S/MIME' + featureConfig: 'smime_config' + description: [ + ['S/MIME (Secure/Multipurpose Internet Mail Extensions) is a widely accepted method (or more precisely, a protocol) for sending digitally signed and encrypted messages.'] + ] + events: + 'change .js-switch input': 'switch' + + render: => + super + new Form( + el: @$('.js-form') + ) + + new App.HttpLog( + el: @$('.js-log') + facility: 'S/MIME' + ) + +class Form extends App.Controller + events: + 'click .js-addCertificate': 'addCertificate' + 'click .js-addPrivateKey': 'addPrivateKey' + 'click .js-updateGroup': 'updateGroup' + + constructor: -> + super + @render() + + currentConfig: -> + App.Setting.get('smime_config') + + setConfig: (value) -> + App.Setting.set('smime_config', value, {notify: true}) + + render: => + @config = @currentConfig() + + @html App.view('integration/smime')( + config: @config + ) + @certList() + @groupList() + + certList: => + new List(el: @$('.js-certList')) + + groupList: => + new Group( + el: @$('.js-groupList') + config: @config + ) + + addCertificate: => + new Certificate( + callback: @list + ) + + addPrivateKey: => + new PrivateKey( + callback: @list + ) + + updateGroup: (e) => + params = App.ControllerForm.params(e) + @setConfig(params) + +class Certificate extends App.ControllerModal + buttonClose: true + buttonCancel: true + buttonSubmit: 'Add' + autoFocusOnFirstInput: false + head: 'Add Certificate' + large: true + + content: -> + + # show start dialog + content = $(App.view('integration/smime_certificate_add')( + head: 'Add Certificate' + )) + content + + onSubmit: (e) => + params = new FormData($(e.currentTarget).closest('form').get(0)) + params.set('try', true) + if _.isEmpty(params.get('data')) + params.delete('data') + @formDisable(e) + + @ajax( + id: 'smime-certificate-add' + type: 'POST' + url: "#{@apiPath}/integration/smime/certificate" + processData: false + contentType: false + cache: false + data: params + success: (data, status, xhr) => + console.log('success') + @close() + @callback() + error: (data) => + console.log('error') + @close() + details = data.responseJSON || {} + @notify + type: 'error' + msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to import!') + timeout: 6000 + ) + +class PrivateKey extends App.ControllerModal + buttonClose: true + buttonCancel: true + buttonSubmit: 'Add' + autoFocusOnFirstInput: false + head: 'Add Private Key' + large: true + + content: -> + + # show start dialog + content = $(App.view('integration/smime_private_key_add')( + head: 'Add Private Key' + )) + content + + onSubmit: (e) => + params = new FormData($(e.currentTarget).closest('form').get(0)) + params.set('try', true) + if _.isEmpty(params.get('data')) + params.delete('data') + @formDisable(e) + + @ajax( + id: 'smime-private_key-add' + type: 'POST' + url: "#{@apiPath}/integration/smime/private_key" + processData: false + contentType: false + cache: false + data: params + success: (data, status, xhr) => + @close() + @callback() + error: (data) => + @close() + details = data.responseJSON || {} + @notify + type: 'error' + msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to import!') + timeout: 6000 + ) + + +class List extends App.Controller + events: + 'click .js-remove': 'remove' + + constructor: -> + super + @load() + + load: => + @ajax( + id: 'smime-list' + type: 'GET' + url: "#{@apiPath}/integration/smime/certificate" + success: (data, status, xhr) => + @render(data) + + error: (data, status) => + + # do not close window if request is aborted + return if status is 'abort' + + details = data.responseJSON || {} + @notify( + type: 'error' + msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to load list of certificates!') + ) + + # do something + ) + + render: (data) => + @html App.view('integration/smime_list')( + certs: data + ) + + remove: (e) => + e.preventDefault() + id = $(e.currentTarget).parents('tr').data('id') + return if !id + + @ajax( + id: 'smime-list' + type: 'DELETE' + url: "#{@apiPath}/integration/smime/certificate" + data: JSON.stringify(id: id) + success: (data, status, xhr) => + @load() + + error: (data, status) => + + # do not close window if request is aborted + return if status is 'abort' + + details = data.responseJSON || {} + @notify( + type: 'error' + msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to save!') + ) + ) + +class Group extends App.Controller + constructor: -> + super + @render() + + render: (data) => + groups = App.Group.search(sortBy: 'name', filter: active: true) + @html App.view('integration/smime_group')( + groups: groups + ) + for group in groups + for type, selector of { default_sign: 'js-signDefault', default_encryption: 'js-encryptionDefault' } + selected = true + if @config?.group_id && @config.group_id[type] + selected = @config.group_id[type][group.id.toString()] + selection = App.UiElement.boolean.render( + name: "group_id::#{type}::#{group.id}" + multiple: false + null: false + nulloption: false + value: selected + class: 'form-control--small' + ) + @$("[data-id=#{group.id}] .#{selector}").html(selection) + +class State + @current: -> + App.Setting.get('smime_integration') + +App.Config.set( + 'Integrationsmime' + { + name: 'S/MIME' + target: '#system/integration/smime' + description: 'S/MIME enables you to send digitally signed and encrypted messages.' + controller: Index + state: State + } + 'NavBarIntegrations' +) diff --git a/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee b/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee index c7d2002e6..b1ee3689c 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee @@ -428,6 +428,35 @@ class App.UiElement.ticket_perform_action elementRow.find('.js-setNotification').html(notificationElement).removeClass('hide') + if App.Config.get('smime_integration') == true + selection = App.UiElement.select.render( + name: "#{name}::sign" + multiple: false + options: { + 'no': 'Do not sign email' + 'discard': 'Sign email (if not possible, discard notification)' + 'always': 'Sign email (if not possible, send notification anyway)' + } + value: meta.sign + translate: true + ) + + elementRow.find('.js-sign').html(selection) + + selection = App.UiElement.select.render( + name: "#{name}::encryption" + multiple: false + options: { + 'no': 'Do not encrypt email' + 'discard': 'Encrypt email (if not possible, discard notification)' + 'always': 'Encrypt email (if not possible, send notification anyway)' + } + value: meta.encryption + translate: true + ) + + elementRow.find('.js-encryption').html(selection) + @buildArticleArea: (articleType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute) -> return if elementRow.find(".js-setArticle .js-body-#{articleType}").get(0) diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee index cd828e6c3..44774e636 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee @@ -1,11 +1,14 @@ class App.TicketCreate extends App.Controller + @include App.SecurityOptions + elements: '.tabsSidebar': 'sidebar' events: - 'click .type-tabs .tab': 'changeFormType' - 'submit form': 'submit' - 'click .js-cancel': 'cancel' + 'click .type-tabs .tab': 'changeFormType' + 'submit form': 'submit' + 'click .js-cancel': 'cancel' + 'click .js-active-toggle': 'toggleButton' types: { 'phone-in': { @@ -121,12 +124,25 @@ class App.TicketCreate extends App.Controller # force changing signature @$('[name="group_id"]').trigger('change') + # add observer to change options + @$('[name="cc"], [name="group_id"], [name="customer_id"]').bind('change', => + @updateSecurityOptions() + ) + @updateSecurityOptions() + # show cc if type is 'email-out' @$('[name="cc"]').closest('.form-group').removeClass('hide') + + if @securityEnabled() + @securityOptionsShow() + else @$('[name="cc"]').closest('.form-group').addClass('hide') + if @securityEnabled() + @securityOptionsHide() + # show notice @$('.js-note').addClass('hide') @$(".js-note[data-type='#{type}']").removeClass('hide') @@ -165,6 +181,13 @@ class App.TicketCreate extends App.Controller return false if !diff || _.isEmpty(diff) return true + updateSecurityOptions: => + params = @params() + if params.customer_id_completion + params.to = params.customer_id_completion + + @updateSecurityOptionsRemote(@taskKey, params, params, @paramsSecurity()) + autosaveStop: => @clearDelay('ticket-create-form-update') @el.off('change.local blur.local keyup.local paste.local input.local') @@ -385,6 +408,9 @@ class App.TicketCreate extends App.Controller @tokanice() + toggleButton: (event) -> + @$(event.currentTarget).toggleClass('btn--active') + tokanice: -> App.Utils.tokanice('.content.active input[name=cc]', 'email') @@ -447,7 +473,7 @@ class App.TicketCreate extends App.Controller # create article if sender.name is 'Customer' - params['article'] = { + params.article = { to: (group && group.name) || '' from: params.customer_id_completion cc: params.cc @@ -459,7 +485,7 @@ class App.TicketCreate extends App.Controller content_type: 'text/html' } else - params['article'] = { + params.article = { from: (group && group.name) || '' to: params.customer_id_completion cc: params.cc @@ -471,6 +497,11 @@ class App.TicketCreate extends App.Controller content_type: 'text/html' } + # add security params + if @securityOptionsShown() + params.article.preferences ||= {} + params.article.preferences.security = @paramsSecurity() + ticket.load(params) ticketErrorsTop = ticket.validate( diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.coffee index cb147cc45..a6e685d1d 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.coffee @@ -709,12 +709,11 @@ class App.TicketZoom extends App.Controller markFormDiff: (diff = {}) => ticketForm = @$('.edit') ticketSidebar = @$('.tabsSidebar-tab[data-tab="ticket"]') - articleForm = @$('.article-add') resetButton = @$('.js-reset') params = {} - params.ticket = @forRemoveMeta(@formParam(ticketForm)) - params.article = @forRemoveMeta(@formParam(articleForm)) + params.ticket = @forRemoveMeta(@ticketParams()) + params.article = @forRemoveMeta(@articleNew.params()) # clear all changes if _.isEmpty(diff.ticket) && _.isEmpty(diff.article) @@ -743,6 +742,9 @@ class App.TicketZoom extends App.Controller resetButton.removeClass('hide') + ticketParams: => + @formParam(@$('.edit')) + submitDisable: (e) => if e @formDisable(e) @@ -767,7 +769,7 @@ class App.TicketZoom extends App.Controller @submitEnable(e) return - ticketParams = @formParam(@$('.edit')) + ticketParams = @ticketParams() articleParams = @articleNew.params() # validate ticket diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee index 6144e0254..e9b19457d 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_action/email_reply.coffee @@ -253,7 +253,7 @@ class EmailReply extends App.Controller icon: 'email' attributes: attributes internal: false, - features: ['attachment'] + features: ['attachment', 'security'] } articleTypes diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee index c63f65c72..5c07d6f52 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee @@ -1,4 +1,6 @@ class App.TicketZoomArticleNew extends App.Controller + @include App.SecurityOptions + elements: '.js-textarea': 'textarea' '.attachmentPlaceholder': 'attachmentPlaceholder' @@ -24,6 +26,7 @@ class App.TicketZoomArticleNew extends App.Controller 'click .list-entry-type div': 'changeType' 'focus .js-textarea': 'openTextarea' 'input .js-textarea': 'updateLetterCount' + 'click .js-active-toggle': 'toggleButton' constructor: -> super @@ -106,6 +109,12 @@ class App.TicketZoomArticleNew extends App.Controller @render() ) + # update security options + @bind('ui::ticket::updateSecurityOptions', (data) => + return if data.taskKey isnt @taskKey + @updateSecurityOptions() + ) + tokanice: (type = 'email') -> App.Utils.tokanice('.content.active .js-to, .js-cc, js-bcc', type) @@ -294,6 +303,11 @@ class App.TicketZoomArticleNew extends App.Controller params.body = "#{params.body}\n#{@signature.text()}" break + # add security params + if @securityOptionsShown() + params.preferences ||= {} + params.preferences.security = @paramsSecurity() + params validate: => @@ -417,6 +431,15 @@ class App.TicketZoomArticleNew extends App.Controller @warningTextLength = articleType.warningTextLength @delay(@updateLetterCount, 600) @$('.js-textSizeLimit').removeClass('hide') + if name is 'security' + if @securityEnabled() + @securityOptionsShow() + + # add observer to change options + @$('.js-to, .js-cc').bind('change', => + @updateSecurityOptions() + ) + @updateSecurityOptions() # convert remote src images to data uri @$('[data-name=body] img').each( (i,image) -> @@ -434,6 +457,9 @@ class App.TicketZoomArticleNew extends App.Controller @scrollToBottom() if wasScrolledToBottom + updateSecurityOptions: => + @updateSecurityOptionsRemote(@ticket.id, @ui.ticketParams(), @params(), @paramsSecurity()) + setArticleTypePost: (type, signaturePosition = 'bottom') => for localConfig in @actions() if localConfig && localConfig.setArticleTypePost @@ -625,3 +651,6 @@ class App.TicketZoomArticleNew extends App.Controller if localConfig actions.push localConfig actions + + toggleButton: (event) -> + @$(event.currentTarget).toggleClass('btn--active') diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee index d83e03dff..09befde85 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee @@ -81,11 +81,13 @@ class ArticleViewItem extends App.ObserverController '.textBubble-overflowContainer': 'textBubbleOverflowContainer' events: - 'click .textBubble': 'toggleMetaWithDelay' - 'click .textBubble a': 'stopPropagation' - 'click .js-toggleFold': 'toggleFold' - 'click .richtext-content img': 'imageView' - 'click .attachments img': 'imageView' + 'click .article-meta-permanent': 'toggleMetaWithDelay' + 'click .textBubble': 'toggleMetaWithDelay' + 'click .textBubble a': 'stopPropagation' + 'click .js-toggleFold': 'toggleFold' + 'click .richtext-content img': 'imageView' + 'click .attachments img': 'imageView' + 'click .js-securityRetryProcess': 'retrySecurityProcess' constructor: -> super @@ -296,6 +298,46 @@ class ArticleViewItem extends App.ObserverController else bubbleOverflowContainer.addClass('hide') + retrySecurityProcess: (e) -> + e.preventDefault() + e.stopPropagation() + + article_id = $(e.target).closest('.ticket-article-item').data('id') + + @ajax( + id: 'retrySecurityProcess' + type: 'POST' + url: "#{@apiPath}/ticket_articles/#{article_id}/retry_security_process" + processData: true + success: (data, status, xhr) => + if data.sign.success + @notify + type: 'success' + msg: App.i18n.translateContent('Verify sign success!') + else if data.sign.comment + comment = App.i18n.translateContent('Verify sign failed!') + ' ' + App.i18n.translateContent(data.sign.comment || '') + @notify + type: 'error' + msg: comment + timeout: 2000 + + if data.encryption.success + @notify + type: 'success' + msg: App.i18n.translateContent('Decryption success!') + else if data.encryption.comment + comment = App.i18n.translateContent('Decryption failed!') + ' ' + App.i18n.translateContent(data.encryption.comment || '') + @notify + type: 'error' + msg: comment + timeout: 2000 + + error: (xhr) => + @notify + type: 'error' + msg: App.i18n.translateContent('Retry security process failed!') + ) + stopPropagation: (e) -> e.stopPropagation() diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/form_handler_security_options.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/form_handler_security_options.coffee new file mode 100644 index 000000000..2ff5ca738 --- /dev/null +++ b/app/assets/javascripts/app/controllers/ticket_zoom/form_handler_security_options.coffee @@ -0,0 +1,10 @@ +class TicketZoomFormHandleSecurityOptions + + # central method, is getting called on every ticket form change + # but only trigger event for group_id changes + @run: (params, attribute, attributes, classname, form, ui) -> + + return if attribute.name isnt 'group_id' + App.Event.trigger('ui::ticket::updateSecurityOptions', { taskKey: ui.taskKey }) + +App.Config.set('140-ticketFormSecurityOptions', TicketZoomFormHandleSecurityOptions, 'TicketZoomFormHandler') diff --git a/app/assets/javascripts/app/lib/mixins/security_options.coffee b/app/assets/javascripts/app/lib/mixins/security_options.coffee new file mode 100644 index 000000000..47348cd9a --- /dev/null +++ b/app/assets/javascripts/app/lib/mixins/security_options.coffee @@ -0,0 +1,89 @@ +# Methods for displaying security ui elements and to get security params + +App.SecurityOptions = + + securityOptionsShow: -> + @$('.js-securityOptions').removeClass('hide') + + securityOptionsHide: -> + @$('.js-securityOptions').addClass('hide') + + securityOptionsShown: -> + !@$('.js-securityOptions').hasClass('hide') + + securityEnabled: -> + App.Config.get('smime_integration') + + paramsSecurity: => + if @$('.js-securityOptions').hasClass('hide') + return {} + + security = {} + security.encryption ||= {} + security.sign ||= {} + security.type = 'S/MIME' + if @$('.js-securityEncrypt').hasClass('btn--active') + security.encryption.success = true + if @$('.js-securitySign').hasClass('btn--active') + security.sign.success = true + security + + updateSecurityOptionsRemote: (key, ticket, article, securityOptions) -> + callback = => + @ajax( + id: "smime-check-#{key}" + type: 'POST' + url: "#{@apiPath}/integration/smime" + data: JSON.stringify(ticket: ticket, article: article) + processData: true + success: (data, status, xhr) => + + # get default selected security options + selected = + encryption: true + sign: true + smimeConfig = App.Config.get('smime_config') + for type, selector of { default_sign: 'sign', default_encryption: 'encryption' } + if smimeConfig?.group_id?[type] && ticket.group_id + if smimeConfig.group_id[type][ticket.group_id.toString()] == false + selected[selector] = false + + @$('.js-securityEncrypt').attr('title', data.encryption.comment) + + # if encryption is possible + if data.encryption.success is true + @$('.js-securityEncrypt').attr('disabled', false) + + # overrule current selection with Group configuration + if selected.encryption + @$('.js-securityEncrypt').addClass('btn--active') + else + @$('.js-securityEncrypt').removeClass('btn--active') + + # if encryption is not possible + else + @$('.js-securityEncrypt').attr('disabled', true) + @$('.js-securityEncrypt').removeClass('btn--active') + + @$('.js-securitySign').attr('title', data.sign.comment) + + # if sign is possible + if data.sign.success is true + @$('.js-securitySign').attr('disabled', false) + + # overrule current selection with Group configuration + if selected.sign + @$('.js-securitySign').addClass('btn--active') + else + @$('.js-securitySign').removeClass('btn--active') + + # if sign is possible + else + @$('.js-securitySign').attr('disabled', true) + @$('.js-securitySign').removeClass('btn--active') + + error: (data) -> + details = data.responseJSON || {} + console.log(details) + ) + @delay(callback, 200, 'security-check') diff --git a/app/assets/javascripts/app/views/agent_ticket_create.jst.eco b/app/assets/javascripts/app/views/agent_ticket_create.jst.eco index f37872bcc..fef02088e 100644 --- a/app/assets/javascripts/app/views/agent_ticket_create.jst.eco +++ b/app/assets/javascripts/app/views/agent_ticket_create.jst.eco @@ -27,10 +27,17 @@
-
+
+
+ +
+
+
<%- @Icon('lock-open', 'btn-inactive-icon') %><%- @Icon('lock', 'btn-active-icon') %><%- @T('Encrypt') %>
+
<%- @Icon('not-signed', 'btn-inactive-icon') %><%- @Icon('signed', 'btn-active-icon') %><%- @T('Sign') %>
+
+
-
diff --git a/app/assets/javascripts/app/views/generic/select.jst.eco b/app/assets/javascripts/app/views/generic/select.jst.eco index 12d043ed8..d60ebcc05 100644 --- a/app/assets/javascripts/app/views/generic/select.jst.eco +++ b/app/assets/javascripts/app/views/generic/select.jst.eco @@ -1,5 +1,5 @@
- id="<%= @attribute.id %>"<% end %> class="form-control<%= " #{ @attribute.class }" if @attribute.class %>" name="<%= @attribute.name %>" <%= @attribute.multiple %> <%= @attribute.required %> <%= @attribute.autofocus %> <% if @attribute.disabled: %> disabled<% end %>> <% if @attribute.options: %> <% for row in @attribute.options: %> diff --git a/app/assets/javascripts/app/views/generic/ticket_perform_action/notification.jst.eco b/app/assets/javascripts/app/views/generic/ticket_perform_action/notification.jst.eco index 4348ce1af..5550fc97d 100644 --- a/app/assets/javascripts/app/views/generic/ticket_perform_action/notification.jst.eco +++ b/app/assets/javascripts/app/views/generic/ticket_perform_action/notification.jst.eco @@ -24,5 +24,13 @@
-
<%- @meta.body %>
+
+
+
<%- @meta.body %>
+
+
+
+
+
+
diff --git a/app/assets/javascripts/app/views/integration/ldap.jst.eco b/app/assets/javascripts/app/views/integration/ldap.jst.eco index b580e21fa..3b2a81f37 100644 --- a/app/assets/javascripts/app/views/integration/ldap.jst.eco +++ b/app/assets/javascripts/app/views/integration/ldap.jst.eco @@ -48,7 +48,7 @@ <%- @T('No Entries') %> <% else: %> - +
<%- @T('LDAP') %> @@ -69,7 +69,7 @@
<%- @T('No Entries') %>
<% else: %> - +
<%- @T('LDAP') %> diff --git a/app/assets/javascripts/app/views/integration/smime.jst.eco b/app/assets/javascripts/app/views/integration/smime.jst.eco new file mode 100644 index 000000000..7faf8ac95 --- /dev/null +++ b/app/assets/javascripts/app/views/integration/smime.jst.eco @@ -0,0 +1,14 @@ + +

<%- @T('Certificates & Private Keys') %>

+
+ +
<%- @T('Add Certificate') %>
+
<%- @T('Add Private Key') %>
+ +
+ +

<%- @T('Default Behavior') %>

+

Choose the default behavior of the S/MIME integration on per group basis. If signing or encrypting is not possible, the setting has no effect. Agents call always manually alter the behavior for each article.

+
+
<%- @T('Update') %>
+ diff --git a/app/assets/javascripts/app/views/integration/smime_certificate_add.jst.eco b/app/assets/javascripts/app/views/integration/smime_certificate_add.jst.eco new file mode 100644 index 000000000..731f9ff45 --- /dev/null +++ b/app/assets/javascripts/app/views/integration/smime_certificate_add.jst.eco @@ -0,0 +1,26 @@ +
+

+ +
+
+ +
+
+ +
+
+ +
+ <%- @T('or') %> +
+ +
+
+ +
+
+ +
+
+ +
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/integration/smime_group.jst.eco b/app/assets/javascripts/app/views/integration/smime_group.jst.eco new file mode 100644 index 000000000..fc57f49d6 --- /dev/null +++ b/app/assets/javascripts/app/views/integration/smime_group.jst.eco @@ -0,0 +1,24 @@ + + + + + + <% if _.isEmpty(@groups): %> + + + + <% else: %> + <% for group in @groups: %> + + +
<%- @T('Group') %> + <%- @T('Sign') %> + <%- @T('Encryption') %> +
+ <%- @T('No Entries') %> +
<%= group.name %> + + + <% end %> + <% end %> +
diff --git a/app/assets/javascripts/app/views/integration/smime_list.jst.eco b/app/assets/javascripts/app/views/integration/smime_list.jst.eco new file mode 100644 index 000000000..e716e1e88 --- /dev/null +++ b/app/assets/javascripts/app/views/integration/smime_list.jst.eco @@ -0,0 +1,34 @@ + + + + + + <% if _.isEmpty(@certs): %> + + + + <% else: %> + <% for cert in @certs: %> + + +
<%- @T('Subject') %> + <%- @T('Hash') %> + <%- @T('Fingerprint') %> + <%- @T('Created') %> + <%- @T('Expires') %> + <%- @T('Actions') %> +
+ <%- @T('No Entries') %> +
<%= cert.subject %> + <% if cert.private_key: %>
<%- @T('Including private key.') %><% end %> +
<%= cert.doc_hash %> + <%= cert.fingerprint.substr(1, 10) %>... + <%- @datetime(cert.not_before_at) %> + <%- @datetime(cert.not_after_at) %> + +
+ <%- @Icon('trash') %> +
+ <% end %> + <% end %> +
diff --git a/app/assets/javascripts/app/views/integration/smime_private_key_add.jst.eco b/app/assets/javascripts/app/views/integration/smime_private_key_add.jst.eco new file mode 100644 index 000000000..c17484d18 --- /dev/null +++ b/app/assets/javascripts/app/views/integration/smime_private_key_add.jst.eco @@ -0,0 +1,37 @@ +
+

+ +
+
+
+ +
+
+ +
+
+ +
+ <%- @T('or') %> +
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
diff --git a/app/assets/javascripts/app/views/layout_ref/buttons.jst.eco b/app/assets/javascripts/app/views/layout_ref/buttons.jst.eco index 5561ebab8..399fb89ef 100644 --- a/app/assets/javascripts/app/views/layout_ref/buttons.jst.eco +++ b/app/assets/javascripts/app/views/layout_ref/buttons.jst.eco @@ -4,9 +4,10 @@

Normal Buttons

Default

Default Button
+
Active Button
Slim Button
-
Disabled Button (via .is-disabled)
-
Disabled Button (via [disabled])
+
.is-disabled Button
+
[disabled] Button

Primary

Primary Button
@@ -24,6 +25,7 @@
Split First Button
Split Button
+
Active Split Button
Split Last Button
diff --git a/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco index 5f8eb25c3..d5ddcd324 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco @@ -56,6 +56,15 @@
+
+
+ +
+
+
<%- @Icon('lock-open', 'btn-inactive-icon') %><%- @Icon('lock', 'btn-active-icon') %><%- @T('Encrypt') %>
+
<%- @Icon('not-signed', 'btn-inactive-icon') %><%- @Icon('signed', 'btn-active-icon') %><%- @T('Sign') %>
+
+
diff --git a/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco index 275139cf6..7c82c506b 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco @@ -31,9 +31,51 @@
<% end %> + <% if @article.preferences.security?.encryption?.success || @article.preferences.security?.sign?.success: %> + + <% end %> +
<% if @article.sender.name isnt 'Agent': %> <% position = 'left' %> diff --git a/app/assets/stylesheets/svg-dimensions.css b/app/assets/stylesheets/svg-dimensions.css index c97090dab..11bd7d184 100644 --- a/app/assets/stylesheets/svg-dimensions.css +++ b/app/assets/stylesheets/svg-dimensions.css @@ -81,6 +81,7 @@ .icon-mood-superbad { width: 60px; height: 59px; } .icon-mood-supergood { width: 60px; height: 59px; } .icon-mute { width: 16px; height: 16px; } +.icon-not-signed { width: 14px; height: 14px; } .icon-note { width: 16px; height: 16px; } .icon-oauth2-button { width: 29px; height: 24px; } .icon-office365-button { width: 29px; height: 24px; } @@ -107,6 +108,7 @@ .icon-reply { width: 16px; height: 17px; } .icon-report { width: 20px; height: 20px; } .icon-searchdetail { width: 18px; height: 14px; } +.icon-signed { width: 14px; height: 14px; } .icon-signout { width: 15px; height: 19px; } .icon-small-dot { width: 16px; height: 16px; } .icon-sms { width: 17px; height: 17px; } diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss index 0bccdea8d..67796f597 100644 --- a/app/assets/stylesheets/zammad.scss +++ b/app/assets/stylesheets/zammad.scss @@ -155,7 +155,6 @@ a { &.is-disabled, &[disabled] { - pointer-events: none; cursor: not-allowed !important; opacity: .33; } @@ -443,7 +442,6 @@ pre code.hljs { &.is-disabled, &[disabled], &:disabled { - pointer-events: none; cursor: not-allowed; opacity: .33; } @@ -492,20 +490,32 @@ pre code.hljs { } &.btn--small { - height: 27px; + height: 26px; font-size: 11px; padding-left: 8px; padding-right: 8px; } } + &-active-icon { + display: none; + } + &--active { - background: hsla(0,0%,0%,.5); + background: hsl(204,7%,28%); color: white; &:active { background: hsla(0,0%,0%,.55); } + + .btn-inactive-icon { + display: none; + } + + .btn-active-icon { + display: inline; + } } // used in .recipientList-controls @@ -531,9 +541,11 @@ pre code.hljs { &--primary { color: white; background: hsl(203,65%,55%); + &:active { background: hsl(203,65%,45%); } + .icon { fill: white; opacity: 1; @@ -1543,7 +1555,7 @@ label, .checkbox.form-group label, .label { text-transform: uppercase; - color: hsl(198,19%,72%); + color: hsl(198,15%,69%); display: block; font-size: 13px; font-weight: normal; @@ -1658,6 +1670,18 @@ https://stackoverflow.com/questions/17408815/fieldset-resizes-wrong-appears-to-h fieldset { display: table-cell; } } +.form-field-group { + padding: 20px; + background: hsl(0,0%,97%); + --background: hsl(0,0%,97%); + border-radius: 4px; + margin-bottom: 16px; + + > .form-group:last-child { + margin-bottom: 0; + } +} + fieldset > .form-group { padding: 0 4px; } @@ -5660,12 +5684,63 @@ footer { vertical-align: top; margin: 2px 3px 0 0; @include rtl(margin, 2px 0 0 3px); + + &.icon-lock { + margin: 0; + } } .article-meta .text-muted { color: #96969b; } + .article-meta-permanent { + margin: 0 55px; + + + .article-content .textBubble { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-top-width: 0; + } + + .alert { + margin-bottom: 0; + padding-left: 20px; + padding-right: 20px; + border-radius: 0; + box-shadow: 0 0 0 1px inset hsla(0,0%,0%,.04); + + &:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px; + } + + &--blank { + background: hsl(212,14%,99%); + color: hsl(198,16%,70%); + + .customer.ticket-article-item & { + background: hsl(201,43%,96%); + border-color: hsl(203,38%,92%); + } + } + + .icon { + fill: currentColor; + margin: 2px 6px 0 0; + vertical-align: top; + width: 14px; + height: 14px; + } + + .icon-lock { + margin-top: 1px; + width: 16px; + height: 16px; + } + } + } + .internal-border { padding: 5px; border-radius: 8px; @@ -5775,7 +5850,7 @@ footer { .bubble-arrow { position: absolute; - width: 7px; + width: 6px; height: 9px; left: -6px; top: 15px; @@ -5786,7 +5861,7 @@ footer { content: ""; position: absolute; top: -1px; - left: 2px; + left: 1px; width: 11px; height: 11px; background: white; @@ -6724,6 +6799,15 @@ footer { &--square { border-radius: 0; } + + .btn { + margin-top: -4px; + margin-bottom: -5px; + + &:last-child { + margin-right: -5px; + } + } } .tags, @@ -7412,7 +7496,6 @@ footer { .dropdown-icon { width: 16px; height: 16px; - opacity: 0.39; } .dropdown-menu.dropdown-menu--light { @@ -8020,9 +8103,37 @@ footer { } } +.or-divider { + position: relative; + margin: 16px 0; + text-align: center; + + &:before { + content: ""; + position: absolute; + left: 0; + width: 100%; + top: 50%; + margin-top: -1px; + height: 1px; + background: hsla(0,0%,0%,.1); + } + + span { + position: relative; + background: white; + background: var(--background, white); + padding: 0 10px; + } +} + .settings-entry { margin-bottom: 42px; max-width: 700px; + + &--stretched { + max-width: none; + } } .setting-controls { diff --git a/app/controllers/integration/smime_controller.rb b/app/controllers/integration/smime_controller.rb new file mode 100644 index 000000000..c95f27888 --- /dev/null +++ b/app/controllers/integration/smime_controller.rb @@ -0,0 +1,154 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +class Integration::SMIMEController < ApplicationController + prepend_before_action { authentication_check && authorize! } + + def certificate_list + render json: SMIMECertificate.all + end + + def certificate_delete + SMIMECertificate.find(params[:id]).destroy! + render json: { + result: 'ok', + } + end + + def certificate_add + string = params[:data] + if string.blank? && params[:file].present? + string = params[:file].read.force_encoding('utf-8') + end + + item = SMIMECertificate.create!(public_key: string) + + render json: { + result: 'ok', + response: item, + } + rescue => e + unprocessable_entity(e) + end + + def private_key_delete + SMIMECertificate.find(params[:id]).update!( + private_key: nil, + private_key_secret: nil, + ) + + render json: { + result: 'ok', + } + end + + def private_key_add + string = params[:data] + if string.blank? && params[:file].present? + string = params[:file].read.force_encoding('utf-8') + end + + raise "Parameter 'data' or 'file' required." if string.blank? + + private_key = OpenSSL::PKey.read(string, params[:secret]) + modulus = private_key.public_key.n.to_s(16) + + certificate = SMIMECertificate.find_by(modulus: modulus) + + raise Exceptions::UnprocessableEntity, 'Unable for find certificate for this private key.' if !certificate + + certificate.update!(private_key: string, private_key_secret: params[:secret]) + + render json: { + result: 'ok', + } + rescue => e + unprocessable_entity(e) + end + + def search + result = { + type: 'S/MIME', + } + + result[:encryption] = article_encryption(params[:article]) + result[:sign] = article_sign(params[:ticket]) + + render json: result + end + + def article_encryption(article) + result = { + success: false, + comment: 'no recipient found', + } + + return result if article.blank? + return result if article[:to].blank? && article[:cc].blank? + + recipient = [ article[:to], article[:cc] ].compact.join(',').to_s + recipients = [] + begin + list = Mail::AddressList.new(recipient) + list.addresses.each do |address| + recipients.push address.address + end + rescue # rubocop:disable Lint/SuppressedException + end + + return result if recipients.blank? + + begin + certs = SMIMECertificate.for_recipipent_email_addresses!(recipients) + + if certs + if certs.any?(&:expired?) + result[:success] = false + result[:comment] = "certificates found for #{recipients.join(',')} but expired" + else + result[:success] = true + result[:comment] = "certificates found for #{recipients.join(',')}" + end + end + rescue => e + result[:comment] = e.message + end + + result + end + + def article_sign(ticket) + result = { + success: false, + comment: 'certificate not found', + } + + return result if ticket.blank? || !ticket[:group_id] + + group = Group.find_by(id: ticket[:group_id]) + return result if !group + + email_address = group.email_address + begin + list = Mail::AddressList.new(email_address.email) + from = list.addresses.first.to_s + cert = SMIMECertificate.for_sender_email_address(from) + if cert + if cert.expired? + result[:success] = false + result[:comment] = "certificate for #{email_address.email} found but expired" + else + result[:success] = true + result[:comment] = "certificate for #{email_address.email} found" + end + else + result[:success] = false + result[:comment] = "no certificate for #{email_address.email} found" + end + rescue => e + result[:comment] = e.message + end + + result + end + +end diff --git a/app/controllers/ticket_articles_controller.rb b/app/controllers/ticket_articles_controller.rb index 797953be1..642d11771 100644 --- a/app/controllers/ticket_articles_controller.rb +++ b/app/controllers/ticket_articles_controller.rb @@ -263,6 +263,15 @@ class TicketArticlesController < ApplicationController render json: result, status: :ok end + def retry_security_process + article = Ticket::Article.find(params[:id]) + authorize!(article, :update?) + + result = SecureMailing.retry(article) + + render json: result + end + private def sanitized_disposition diff --git a/app/jobs/ticket_article_communicate_email_job.rb b/app/jobs/ticket_article_communicate_email_job.rb index 6d22bd310..64b1ad486 100644 --- a/app/jobs/ticket_article_communicate_email_job.rb +++ b/app/jobs/ticket_article_communicate_email_job.rb @@ -62,7 +62,8 @@ class TicketArticleCommunicateEmailJob < ApplicationJob subject: subject, content_type: record.content_type, body: record.body, - attachments: record.attachments + attachments: record.attachments, + security: record.preferences[:security], }, notification ) diff --git a/app/models/channel/email_build.rb b/app/models/channel/email_build.rb index b1dc1cf13..f0d9a85e2 100644 --- a/app/models/channel/email_build.rb +++ b/app/models/channel/email_build.rb @@ -3,6 +3,8 @@ module Channel::EmailBuild =begin +generate email + mail = Channel::EmailBuild.build( from: 'sender@example.com', to: 'recipient@example.com', @@ -10,35 +12,37 @@ module Channel::EmailBuild content_type: 'text/plain', ) +generate email with S/MIME + + mail = Channel::EmailBuild.build( + from: 'sender@example.com', + to: 'recipient@example.com', + body: 'somebody with some text', + content_type: 'text/plain', + security: { + type: 'S/MIME', + encryption: { + success: true, + }, + sign: { + success: true, + }, + } + ) + =end def self.build(attr, notification = false) mail = Mail.new - # set organization - organization = Setting.get('organization') - if organization - mail['Organization'] = organization.to_s - end - - # notification - if notification - attr['X-Loop'] = 'yes' - attr['Precedence'] = 'bulk' - attr['Auto-Submitted'] = 'auto-generated' - attr['X-Auto-Response-Suppress'] = 'All' - end - - attr['X-Powered-By'] = 'Zammad - Helpdesk/Support (https://zammad.org/)' - attr['X-Mailer'] = 'Zammad Mail Service' - # set headers attr.each do |key, value| next if key.to_s == 'attachments' next if key.to_s == 'body' next if key.to_s == 'content_type' + next if key.to_s == 'security' - mail[key.to_s] = if value && value.class != Array + mail[key.to_s] = if value.present? && value.class != Array value.to_s else value @@ -70,6 +74,7 @@ module Channel::EmailBuild if !html_alternative && attr[:attachments].blank? mail.content_type 'text/plain; charset=UTF-8' mail.body attr[:body] + SecureMailing.outgoing(mail, attr[:security]) return mail end @@ -119,6 +124,25 @@ module Channel::EmailBuild } end end + + SecureMailing.outgoing(mail, attr[:security]) + + # set organization + organization = Setting.get('organization') + if organization.present? + mail['Organization'] = organization.to_s + end + + if notification + mail['X-Loop'] = 'yes' + mail['Precedence'] = 'bulk' + mail['Auto-Submitted'] = 'auto-generated' + mail['X-Auto-Response-Suppress'] = 'All' + end + + mail['X-Powered-By'] = 'Zammad - Helpdesk/Support (https://zammad.org/)' + mail['X-Mailer'] = 'Zammad Mail Service' + mail end diff --git a/app/models/channel/email_parser.rb b/app/models/channel/email_parser.rb index 598a7cbdb..f8291ea03 100644 --- a/app/models/channel/email_parser.rb +++ b/app/models/channel/email_parser.rb @@ -87,6 +87,7 @@ class Channel::EmailParser headers, body, self.class.sender_attributes(headers), + raw: msg, ] message_attributes.reduce({}.with_indifferent_access, &:merge) end diff --git a/app/models/channel/filter/secure_mailing.rb b/app/models/channel/filter/secure_mailing.rb new file mode 100644 index 000000000..84be2af01 --- /dev/null +++ b/app/models/channel/filter/secure_mailing.rb @@ -0,0 +1,8 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +module Channel::Filter::SecureMailing + + def self.run(_channel, mail) + ::SecureMailing.incoming(mail) + end +end diff --git a/app/models/smime_certificate.rb b/app/models/smime_certificate.rb new file mode 100644 index 000000000..99b1acee6 --- /dev/null +++ b/app/models/smime_certificate.rb @@ -0,0 +1,99 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +class SMIMECertificate < ApplicationModel + validates :fingerprint, uniqueness: true + + def self.parse(raw) + OpenSSL::X509::Certificate.new(raw.gsub!(/(?:TRUSTED\s)?(CERTIFICATE---)/, '\1')) + end + + # Search for the certificate of the given sender email address + # + # @example + # certificate = SMIMECertificates.for_sender_email_address('some1@example.com') + # # => # [#] The found certificate records + def self.for_recipipent_email_addresses!(addresses) + certificates = [] + remaining_addresses = addresses.map(&:downcase) + find_each do |certificate| + + # intersection of both lists + cerfiticate_for = certificate.email_addresses & remaining_addresses + next if cerfiticate_for.blank? + + certificates.push(certificate) + + # subtract found recipient(s) + remaining_addresses -= cerfiticate_for + + # end loop if no addresses are remaining + break if remaining_addresses.blank? + end + + return certificates if remaining_addresses.blank? + + raise ActiveRecord::RecordNotFound, "Can't find S/MIME encryption certificates for: #{remaining_addresses.join(', ')}" + end + + def public_key=(string) + cert = self.class.parse(string) + + self.subject = cert.subject + self.doc_hash = cert.subject.hash.to_s(16) + self.fingerprint = OpenSSL::Digest.new('SHA1', cert.to_der).to_s + self.modulus = cert.public_key.n.to_s(16) + self.not_before_at = cert.not_before + self.not_after_at = cert.not_after + self.raw = cert.to_s + end + + def parsed + @parsed ||= self.class.parse(raw) + end + + def email_addresses + @email_addresses ||= begin + subject_alt_name = parsed.extensions.detect { |extension| extension.oid == 'subjectAltName' } + if subject_alt_name.blank? + warning = <<~TEXT.squish + SMIMECertificate with ID #{id} has no subjectAltName + extension and therefore no email addresses assigned. + This makes it useless in terms of S/MIME. Please check. + TEXT + Rails.logger.warn warning + return [] + end + + # ["IP Address:192.168.7.23", "IP Address:192.168.7.42", "email:jd@example.com", "email:John.Doe@example.com", "dirName:dir_sect"] + entries = subject_alt_name.value.split(/,\s?/) + # ["email:jd@example.com", "email:John.Doe@example.com"] + email_address_entries = entries.select { |entry| entry.start_with?('email') } + # ["jd@example.com", "John.Doe@example.com"] + email_address_entries.map! { |entry| entry.split(':')[1] } + # ["jd@example.com", "john.doe@example.com"] + email_address_entries.map!(&:downcase) + end + end + + def expired? + !Time.zone.now.between?(not_before_at, not_after_at) + end +end diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 9484b0816..5af2e467e 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -1498,6 +1498,57 @@ result return end + security = nil + if Setting.get('smime_integration') + sign = value['sign'].present? && value['sign'] != 'no' + encryption = value['encryption'].present? && value['encryption'] != 'no' + security = { + type: 'S/MIME', + sign: { + success: false, + }, + encryption: { + success: false, + }, + } + + if sign + sign_found = false + begin + list = Mail::AddressList.new(email_address.email) + from = list.addresses.first.to_s + cert = SMIMECertificate.for_sender_email_address(from) + if cert && !cert.expired? + sign_found = true + security[:sign][:success] = true + security[:sign][:comment] = "certificate for #{email_address.email} found" + end + rescue # rubocop:disable Lint/SuppressedException + end + + if value['sign'] == 'discard' && !sign_found + logger.info "Unable to send trigger based notification to #{recipient_string} because of missing group #{group.name} email #{email_address.email} certificate for signing (discarding notification)." + return + end + end + + if encryption + certs_found = false + begin + SMIMECertificate.for_recipipent_email_addresses!(recipients_checked) + certs_found = true + security[:encryption][:success] = true + security[:encryption][:comment] = "certificates found for #{recipient_string}" + rescue # rubocop:disable Lint/SuppressedException + end + + if value['encryption'] == 'discard' && !certs_found + logger.info "Unable to send trigger based notification to #{recipient_string} because public certificate is not available for encryption (discarding notification)." + return + end + end + end + objects = build_notification_template_objects(article) # get subject @@ -1516,6 +1567,12 @@ result (body, attachments_inline) = HtmlSanitizer.replace_inline_images(body, id) + preferences = {} + preferences[:perform_origin] = perform_origin + if security.present? + preferences[:security] = security + end + message = Ticket::Article.create( ticket_id: id, to: recipient_string, @@ -1525,9 +1582,7 @@ result internal: value['internal'] || false, # default to public if value was not set sender: Ticket::Article::Sender.find_by(name: 'System'), type: Ticket::Article::Type.find_by(name: 'email'), - preferences: { - perform_origin: perform_origin, - }, + preferences: preferences, updated_by_id: 1, created_by_id: 1, ) diff --git a/app/models/ticket/article.rb b/app/models/ticket/article.rb index 888fe3242..1e057a4ef 100644 --- a/app/models/ticket/article.rb +++ b/app/models/ticket/article.rb @@ -337,4 +337,5 @@ returns o_id: id, ) end + end diff --git a/app/policies/controllers/integration/smime_controller_policy.rb b/app/policies/controllers/integration/smime_controller_policy.rb new file mode 100644 index 000000000..b630ad0f6 --- /dev/null +++ b/app/policies/controllers/integration/smime_controller_policy.rb @@ -0,0 +1,4 @@ +class Controllers::Integration::SMIMEControllerPolicy < Controllers::ApplicationControllerPolicy + permit! :search, to: 'ticket.agent' + default_permit!('admin.integration.smime') +end diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 58eb6be5b..87061c42a 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -20,4 +20,5 @@ ActiveSupport::Inflector.inflections(:en) do |inflect| # Rails thinks the singularized version of knowledge_bases is knowledge_basis?! # see: KnowledgeBase.table_name.singularize inflect.singular(/(knowledge_base)s$/i, '\1') + inflect.acronym 'SMIME' end diff --git a/config/routes/integration_smime.rb b/config/routes/integration_smime.rb new file mode 100644 index 000000000..07b8a38f3 --- /dev/null +++ b/config/routes/integration_smime.rb @@ -0,0 +1,10 @@ +Zammad::Application.routes.draw do + api_path = Rails.configuration.api_path + + match api_path + '/integration/smime', to: 'integration/smime#search', via: :post + match api_path + '/integration/smime/certificate', to: 'integration/smime#certificate_add', via: :post + match api_path + '/integration/smime/certificate', to: 'integration/smime#certificate_delete', via: :delete + match api_path + '/integration/smime/certificate', to: 'integration/smime#certificate_list', via: :get + match api_path + '/integration/smime/private_key', to: 'integration/smime#private_key_add', via: :post + match api_path + '/integration/smime/private_key', to: 'integration/smime#private_key_delete', via: :delete +end diff --git a/config/routes/ticket.rb b/config/routes/ticket.rb index 35c93d308..60ac8832f 100644 --- a/config/routes/ticket.rb +++ b/config/routes/ticket.rb @@ -45,5 +45,5 @@ Zammad::Application.routes.draw do match api_path + '/ticket_attachment/:ticket_id/:article_id/:id', to: 'ticket_articles#attachment', via: :get match api_path + '/ticket_attachment_upload_clone_by_article/:article_id', to: 'ticket_articles#ticket_attachment_upload_clone_by_article', via: :post match api_path + '/ticket_article_plain/:id', to: 'ticket_articles#article_plain', via: :get - + match api_path + '/ticket_articles/:id/retry_security_process', to: 'ticket_articles#retry_security_process', via: :post end diff --git a/contrib/edit-icon-license-list.php b/contrib/edit-icon-license-list.php index f276e40e1..fa438ab61 100644 --- a/contrib/edit-icon-license-list.php +++ b/contrib/edit-icon-license-list.php @@ -131,7 +131,7 @@ $imageTypes = '{*.svg}'; # Set to true if you prefer sorting images by name # If set to false, images will be sorted by date -$sortByImageName = false; +$sortByImageName = true; # Set to false if you want the oldest images to appear first # This is only used if images are sorted by date (see above) diff --git a/contrib/icon-sprite.sketch b/contrib/icon-sprite.sketch index 49bc3bcb1..6f00dab67 100644 Binary files a/contrib/icon-sprite.sketch and b/contrib/icon-sprite.sketch differ diff --git a/db/migrate/20120101000001_create_base.rb b/db/migrate/20120101000001_create_base.rb index ed28a8cd5..4c436334b 100644 --- a/db/migrate/20120101000001_create_base.rb +++ b/db/migrate/20120101000001_create_base.rb @@ -724,5 +724,21 @@ class CreateBase < ActiveRecord::Migration[4.2] end add_index :active_job_locks, :lock_key, unique: true add_index :active_job_locks, :active_job_id, unique: true + + create_table :smime_certificates do |t| + t.string :subject, limit: 500, null: false + t.string :doc_hash, limit: 250, null: false + t.string :fingerprint, limit: 250, null: false + t.string :modulus, limit: 1024, null: false + t.datetime :not_before_at, null: true + t.datetime :not_after_at, null: true + t.binary :raw, limit: 10.megabytes, null: false + t.binary :private_key, limit: 10.megabytes, null: true + t.string :private_key_secret, limit: 500, null: true + t.timestamps limit: 3, null: false + end + add_index :smime_certificates, [:fingerprint], unique: true + add_index :smime_certificates, [:modulus] + add_index :smime_certificates, [:subject] end end diff --git a/db/migrate/20200121000001_smime_support.rb b/db/migrate/20200121000001_smime_support.rb new file mode 100644 index 000000000..1c68fee4c --- /dev/null +++ b/db/migrate/20200121000001_smime_support.rb @@ -0,0 +1,74 @@ +class SMIMESupport < ActiveRecord::Migration[5.2] + def up + + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + Setting.create_if_not_exists( + title: 'S/MIME integration', + name: 'smime_integration', + area: 'Integration::Switch', + description: 'Defines if S/MIME encryption is enabled or not.', + options: { + form: [ + { + display: '', + null: true, + name: 'smime_integration', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + state: false, + preferences: { + prio: 1, + authentication: true, + permission: ['admin.integration'], + }, + frontend: true + ) + Setting.create_if_not_exists( + title: 'S/MIME config', + name: 'smime_config', + area: 'Integration::SMIME', + description: 'Defines the S/MIME config.', + options: {}, + state: {}, + preferences: { + prio: 2, + permission: ['admin.integration'], + }, + frontend: true, + ) + Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '0016_postmaster_filter_smime', + area: 'Postmaster::PreFilter', + description: 'Defines postmaster filter to handle secure mailing.', + options: {}, + state: 'Channel::Filter::SecureMailing', + frontend: false + ) + + create_table :smime_certificates do |t| + t.string :subject, limit: 500, null: false + t.string :doc_hash, limit: 250, null: false + t.string :fingerprint, limit: 250, null: false + t.string :modulus, limit: 1024, null: false + t.datetime :not_before_at, null: true + t.datetime :not_after_at, null: true + t.binary :raw, limit: 10.megabytes, null: false + t.binary :private_key, limit: 10.megabytes, null: true + t.string :private_key_secret, limit: 500, null: true + t.timestamps limit: 3, null: false + end + add_index :smime_certificates, [:fingerprint], unique: true + add_index :smime_certificates, [:modulus] + add_index :smime_certificates, [:subject] + end + +end diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb index 4273751c4..a09976d39 100644 --- a/db/seeds/settings.rb +++ b/db/seeds/settings.rb @@ -3349,6 +3349,15 @@ Setting.create_if_not_exists( state: 'Channel::Filter::IdentifySender', frontend: false ) +Setting.create_if_not_exists( + title: 'Defines postmaster filter.', + name: '0016_postmaster_filter_smime', + area: 'Postmaster::PreFilter', + description: 'Defines postmaster filter to handle secure mailing.', + options: {}, + state: 'Channel::Filter::SecureMailing', + frontend: false +) Setting.create_if_not_exists( title: 'Defines postmaster filter.', name: '0020_postmaster_filter_auto_response_check', @@ -4549,3 +4558,45 @@ Setting.create_if_not_exists( }, frontend: true ) + +Setting.create_if_not_exists( + title: 'S/MIME integration', + name: 'smime_integration', + area: 'Integration::Switch', + description: 'Defines if S/MIME encryption is enabled or not.', + options: { + form: [ + { + display: '', + null: true, + name: 'smime_integration', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + state: false, + preferences: { + prio: 1, + authentication: true, + permission: ['admin.integration'], + }, + frontend: true +) + +Setting.create_if_not_exists( + title: 'S/MIME config', + name: 'smime_config', + area: 'Integration::SMIME', + description: 'Defines the S/MIME config.', + options: {}, + state: {}, + preferences: { + prio: 2, + permission: ['admin.integration'], + }, + frontend: true, +) diff --git a/lib/secure_mailing.rb b/lib/secure_mailing.rb new file mode 100644 index 000000000..77a6a00c2 --- /dev/null +++ b/lib/secure_mailing.rb @@ -0,0 +1,25 @@ +class SecureMailing + include ::Mixin::HasBackends + + def self.incoming(mail) + active_backends.each do |backend| + "#{backend}::Incoming".constantize.process(mail) + end + end + + def self.retry(article) + active_backends.each do |backend| + "#{backend}::Retry".constantize.process(article) + end + end + + def self.outgoing(mail, security) + active_backends.each do |backend| + "#{backend}::Outgoing".constantize.process(mail, security) + end + end + + def self.active_backends + backends.select(&:active?) + end +end diff --git a/lib/secure_mailing/backend.rb b/lib/secure_mailing/backend.rb new file mode 100644 index 000000000..b840d1dea --- /dev/null +++ b/lib/secure_mailing/backend.rb @@ -0,0 +1,9 @@ +class SecureMailing::Backend + include Mixin::IsBackend + + def self.inherited(subclass) + subclass.is_backend_of(::SecureMailing) + end +end + +Mixin::RequiredSubPaths.eager_load_recursive(__dir__) diff --git a/lib/secure_mailing/backend/handler.rb b/lib/secure_mailing/backend/handler.rb new file mode 100644 index 000000000..b6263bddb --- /dev/null +++ b/lib/secure_mailing/backend/handler.rb @@ -0,0 +1,6 @@ +class SecureMailing::Backend::Handler + + def self.process(*args) + new(*args).process + end +end diff --git a/lib/secure_mailing/smime.rb b/lib/secure_mailing/smime.rb new file mode 100644 index 000000000..d022d8c9d --- /dev/null +++ b/lib/secure_mailing/smime.rb @@ -0,0 +1,6 @@ +class SecureMailing::SMIME < SecureMailing::Backend + + def self.active? + Setting.get('smime_integration') + end +end diff --git a/lib/secure_mailing/smime/incoming.rb b/lib/secure_mailing/smime/incoming.rb new file mode 100644 index 000000000..cddc24429 --- /dev/null +++ b/lib/secure_mailing/smime/incoming.rb @@ -0,0 +1,180 @@ +class SecureMailing::SMIME::Incoming < SecureMailing::Backend::Handler + + EXPRESSION_MIME = %r{application/(x-pkcs7|pkcs7)-mime}i.freeze + EXPRESSION_SIGNATURE = %r{application/(x-pkcs7|pkcs7)-signature}i.freeze + + OPENSSL_PKCS7_VERIFY_FLAGS = OpenSSL::PKCS7::NOVERIFY | OpenSSL::PKCS7::NOINTERN + + def initialize(mail) + @mail = mail + @content_type = @mail[:mail_instance].content_type + end + + def process + return if !process? + + initialize_article_preferences + decrypt + verify_signature + log + end + + def initialize_article_preferences + article_preferences[:security] = { + type: 'S/MIME', + sign: { + success: false, + comment: nil, + }, + encryption: { + success: false, + comment: nil, + } + } + end + + def article_preferences + @article_preferences ||= begin + key = 'x-zammad-article-preferences'.to_sym + @mail[ key ] ||= {} + @mail[ key ] + end + end + + def process? + signed? || smime? + end + + def signed?(content_type = @content_type) + EXPRESSION_SIGNATURE.match?(content_type) + end + + def smime?(content_type = @content_type) + EXPRESSION_MIME.match?(content_type) + end + + def decrypt + return if !smime? + + success = false + comment = 'Unable to find private key to decrypt' + ::SMIMECertificate.where.not(private_key: [nil, '']).find_each do |cert| + key = OpenSSL::PKey::RSA.new(cert.private_key, cert.private_key_secret) + + begin + decrypted_data = p7enc.decrypt(key, cert.parsed) + rescue + next + end + + @mail[:mail_instance].header['Content-Type'] = nil + @mail[:mail_instance].header['Content-Disposition'] = nil + @mail[:mail_instance].header['Content-Transfer-Encoding'] = nil + @mail[:mail_instance].header['Content-Description'] = nil + + new_raw_mail = "#{@mail[:mail_instance].header}#{decrypted_data}" + + mail_new = Channel::EmailParser.new.parse(new_raw_mail) + mail_new.each do |local_key, local_value| + @mail[local_key] = local_value + end + + success = true + comment = cert.subject + if cert.expired? + comment += " (Certificate #{cert.fingerprint} with start date #{cert.not_before_at} and end date #{cert.not_after_at} expired!)" + end + + # overwrite content_type for signature checking + @content_type = @mail[:mail_instance].content_type + break + end + + article_preferences[:security][:encryption] = { + success: success, + comment: comment, + } + end + + def verify_signature + return if !signed? + + success = false + comment = 'Unable to find certificate for verification' + ::SMIMECertificate.find_each do |cert| + verify_certs = [] + verify_ca = OpenSSL::X509::Store.new + + if cert.parsed.issuer.to_s == cert.parsed.subject.to_s + verify_ca.add_cert(cert.parsed) + + # CA + verify_certs = p7enc.certificates.select do |message_cert| + message_cert.issuer.to_s == cert.parsed.subject.to_s && verify_ca.verify(message_cert) + end + else + + # normal + verify_certs.push(cert.parsed) + end + + success = p7enc.verify(verify_certs, verify_ca, nil, OPENSSL_PKCS7_VERIFY_FLAGS) + next if !success + + comment = cert.subject + if cert.expired? + comment += " (Certificate #{cert.fingerprint} with start date #{cert.not_before_at} and end date #{cert.not_after_at} expired!)" + end + break + rescue => e + success = false + comment = e.message + end + + if success + @mail[:attachments].delete_if do |attachment| + signed?(attachment.dig(:preferences, 'Content-Type')) + end + end + + article_preferences[:security][:sign] = { + success: success, + comment: comment, + } + end + + def p7enc + OpenSSL::PKCS7.read_smime(@mail[:raw]) + end + + def log + %i[sign encryption].each do |action| + result = article_preferences[:security][action] + next if result.blank? + + if result[:success] + status = 'success' + elsif result[:comment].blank? + # means not performed + next + else + status = 'failed' + end + + HttpLog.create( + direction: 'in', + facility: 'S/MIME', + url: "#{@mail[:from]} -> #{@mail[:to]}", + status: status, + ip: nil, + request: { + message_id: @mail[:message_id], + }, + response: article_preferences[:security], + method: action, + created_by_id: 1, + updated_by_id: 1, + ) + end + end +end diff --git a/lib/secure_mailing/smime/outgoing.rb b/lib/secure_mailing/smime/outgoing.rb new file mode 100644 index 000000000..fa4bb650a --- /dev/null +++ b/lib/secure_mailing/smime/outgoing.rb @@ -0,0 +1,84 @@ +class SecureMailing::SMIME::Outgoing < SecureMailing::Backend::Handler + + def initialize(mail, security) + @mail = mail + @security = security + end + + def process + return if !process? + + if @security[:sign][:success] && @security[:encryption][:success] + processed = encrypt(signed) + log('sign', 'success') + log('encryption', 'success') + elsif @security[:sign][:success] + processed = Mail.new(signed) + log('sign', 'success') + elsif @security[:encryption][:success] + processed = encrypt(@mail.encoded) + log('encryption', 'success') + end + + overwrite_mail(processed) + end + + def process? + return false if @security.blank? + return false if @security[:type] != 'S/MIME' + + @security[:sign][:success] || @security[:encryption][:success] + end + + def overwrite_mail(processed) + @mail.body = nil + @mail.body = processed.body.encoded + + @mail.content_disposition = processed.content_disposition + @mail.content_transfer_encoding = processed.content_transfer_encoding + @mail.content_type = processed.content_type + end + + def signed + from = @mail.from.first + cert_model = SMIMECertificate.for_sender_email_address(from) + raise "Unable to find ssl private key for '#{from}'" if !cert_model + raise "Expired certificate for #{from} (fingerprint #{cert_model.fingerprint}) with #{cert_model.not_before_at} to #{cert_model.not_after_at}" if !@security[:sign][:allow_expired] && cert_model.expired? + + private_key = OpenSSL::PKey::RSA.new(cert_model.private_key, cert_model.private_key_secret) + OpenSSL::PKCS7.write_smime(OpenSSL::PKCS7.sign(cert_model.parsed, private_key, @mail.encoded, [], OpenSSL::PKCS7::DETACHED)) + rescue => e + log('sign', 'failed', e.message) + raise + end + + def encrypt(data) + certificates = SMIMECertificate.for_recipipent_email_addresses!(@mail.to) + expired_cert = certificates.detect(&:expired?) + raise "Expired certificates for cert with #{expired_cert.not_before_at} to #{expired_cert.not_after_at}" if !@security[:encryption][:allow_expired] && expired_cert.present? + + Mail.new(OpenSSL::PKCS7.write_smime(OpenSSL::PKCS7.encrypt(certificates.map(&:parsed), data, cipher))) + rescue => e + log('encryption', 'failed', e.message) + raise + end + + def cipher + @cipher ||= OpenSSL::Cipher.new('AES-128-CBC') + end + + def log(action, status, error = nil) + HttpLog.create( + direction: 'out', + facility: 'S/MIME', + url: "#{@mail[:from]} -> #{@mail[:to]}", + status: status, + ip: nil, + request: @security, + response: { error: error }, + method: action, + created_by_id: 1, + updated_by_id: 1, + ) + end +end diff --git a/lib/secure_mailing/smime/retry.rb b/lib/secure_mailing/smime/retry.rb new file mode 100644 index 000000000..eaff22bac --- /dev/null +++ b/lib/secure_mailing/smime/retry.rb @@ -0,0 +1,91 @@ +class SecureMailing::SMIME::Retry < SecureMailing::Backend::Handler + + def initialize(article) + @article = article + end + + def process + return existing_result if already_processed? + + save_result if retry_succeeded? + retry_result + end + + def signature_checked? + @signature_checked ||= existing_result&.dig('sign', 'success') || false + end + + def decrypted? + @decrypted ||= existing_result&.dig('encryption', 'success') || false + end + + def already_processed? + signature_checked? && decrypted? + end + + def existing_result + @article.preferences['security'] + end + + def mail + @mail ||= begin + raw_mail = @article.as_raw.store_file.content + Channel::EmailParser.new.parse(raw_mail).tap do |parsed| + SecureMailing.incoming(parsed) + end + end + end + + def retry_result + @retry_result ||= mail['x-zammad-article-preferences']['security'] + end + + def signature_found? + return false if signature_checked? + + retry_result['sign']['success'] + end + + def decryption_succeeded? + return false if decrypted? + + retry_result['encryption']['success'] + end + + def retry_succeeded? + return true if signature_found? + + decryption_succeeded? + end + + def save_result + save_decrypted if decryption_succeeded? + @article.preferences['security'] = retry_result + @article.save! + end + + def save_decrypted + @article.content_type = mail['content_type'] + @article.body = mail['body'] + + Store.remove( + object: 'Ticket::Article', + o_id: @article.id, + ) + + mail[:attachments]&.each do |attachment| + filename = attachment[:filename].force_encoding('utf-8') + if !filename.force_encoding('UTF-8').valid_encoding? + filename = filename.utf8_encode(fallback: :read_as_sanitized_binary) + end + Store.add( + object: 'Ticket::Article', + o_id: @article.id, + data: attachment[:data], + filename: filename, + preferences: attachment[:preferences], + created_by_id: @article.created_by_id, + ) + end + end +end diff --git a/public/assets/images/icons.svg b/public/assets/images/icons.svg index 763cce338..f117ee617 100644 --- a/public/assets/images/icons.svg +++ b/public/assets/images/icons.svg @@ -555,6 +555,11 @@ + + + not-signed + + note @@ -716,6 +721,11 @@ <path d="M13.21 8.62a1.602 1.602 0 0 1-1.59-1.61 1.604 1.604 0 0 1 1.6-1.59h.01c.427.003.827.172 1.127.476.3.304.463.707.46 1.134-.003.425-.17.826-.471 1.125-.301.3-.702.465-1.126.465h-.01M10 6.953a3.172 3.172 0 0 0 4.768 2.76l1.926 1.93.05.047a.37.37 0 0 0 .526 0l.621-.62a.375.375 0 0 0 0-.527l-.048-.05-1.93-1.93A3.17 3.17 0 0 0 13.191 3.8 3.177 3.177 0 0 0 10 6.953z" opacity=".5"/> <path d="M0 1c0-.552.449-1 1.007-1h12.986C14.549 0 15 .444 15 1c0 .552-.449 1-1.007 1H1.007A1.001 1.001 0 0 1 0 1zm0 4c0-.552.446-1 .998-1h7.004C8.553 4 9 4.444 9 5c0 .552-.446 1-.998 1H.998A.996.996 0 0 1 0 5zm0 4c0-.552.446-1 .998-1h7.004C8.553 8 9 8.444 9 9c0 .552-.446 1-.998 1H.998A.996.996 0 0 1 0 9zm0 4c0-.552.449-1 1.007-1h12.986c.556 0 1.007.444 1.007 1 0 .552-.449 1-1.007 1H1.007A1.001 1.001 0 0 1 0 13z"/> </g> +</symbol><symbol id="icon-signed" viewBox="0 0 14 14"> + <title> + signed + + signout diff --git a/public/assets/images/icons/not-signed.svg b/public/assets/images/icons/not-signed.svg new file mode 100644 index 000000000..3f2c7af3d --- /dev/null +++ b/public/assets/images/icons/not-signed.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Generator: Sketch 61.1 (89650) - https://sketch.com --> + <title>not-signed + Created with Sketch. + + + + \ No newline at end of file diff --git a/public/assets/images/icons/signed.svg b/public/assets/images/icons/signed.svg new file mode 100644 index 000000000..247b4ed87 --- /dev/null +++ b/public/assets/images/icons/signed.svg @@ -0,0 +1,9 @@ + + + + signed + Created with Sketch. + + + + \ No newline at end of file diff --git a/spec/factories/smime_certificate.rb b/spec/factories/smime_certificate.rb new file mode 100644 index 000000000..2cb6b27ea --- /dev/null +++ b/spec/factories/smime_certificate.rb @@ -0,0 +1,17 @@ +FactoryBot.define do + factory :smime_certificate do + created_at { Time.zone.now } + updated_at { Time.zone.now } + + transient do + fixture { nil } + end + + public_key { File.read( Rails.root.join("spec/fixtures/smime/#{fixture}.crt") ) if fixture } + + trait :with_private do + private_key { File.read( Rails.root.join("spec/fixtures/smime/#{fixture}.key") ) } + private_key_secret { File.read( Rails.root.join("spec/fixtures/smime/#{fixture}.secret") ).strip! } + end + end +end diff --git a/spec/factories/template.rb b/spec/factories/template.rb index f0072428a..eeeb2029c 100644 --- a/spec/factories/template.rb +++ b/spec/factories/template.rb @@ -17,12 +17,13 @@ FactoryBot.define do trait :dummy_data do options do { - 'formSenderType' => sender_type, - 'title' => title, - 'body' => body, - 'customer_id' => customer.id, - 'group_id' => group.id, - 'owner_id' => owner.id, + 'formSenderType' => sender_type, + 'title' => title, + 'body' => body, + 'customer_id' => customer.id, + 'customer_id_completion' => "#{customer.firstname} #{customer.lastname} <#{customer.email}>", + 'group_id' => group.id, + 'owner_id' => owner.id, } end end diff --git a/spec/fixtures/smime/CaseInsenstive@eXample.COM.crt b/spec/fixtures/smime/CaseInsenstive@eXample.COM.crt new file mode 100644 index 000000000..58fae7ec7 --- /dev/null +++ b/spec/fixtures/smime/CaseInsenstive@eXample.COM.crt @@ -0,0 +1,37 @@ +-----BEGIN TRUSTED CERTIFICATE----- +MIIGZDCCBEygAwIBAgIUAfZ9Sb8pCb7RN34x7oUVKZ/ftY8wDQYJKoZIhvcNAQEL +BQAwgZcxHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoMEEV4 +YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMCAXDTIwMDUyODExMzg0NFoYDzIyMjAwNDEwMTEzODQ0WjCB +ozEpMCcGCSqGSIb3DQEJARYaQ2FzZUluc2Vuc3RpdmVAZVhhbXBsZS5DT00xCzAJ +BgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEZMBcG +A1UECgwQRXhhbXBsZSBTZWN1cml0eTEWMBQGA1UECwwNSVQgRGVwYXJ0bWVudDEU +MBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQDaNxe6VMcsIVG+Y/aNlNsUWzEZvSycDGKH+1AZ2NBjeEyNRuoiBdbosyxk +mPm6fZ4Prcr2ufIPj5K0cS8xPSBGErqv/6QIUELhEupdxh5cIdhBVDIW/EPrbUFw +CyEAT5/W4p1sqOA8nK1DEac5JprXRiWXzyoWX3HhWbhC20Al1cCqTEoc0pqbSocJ +pN7pmi5VpudtdNFtdlyhkeqT/Yep8roERfEdi5NxBtrn8xWpPXG+db30NjXgZZ9n +wbOHpNKD/Cm/63vIRFtIN1AmrHGE51/2NsrxZGrZ0VoPmx8b82jWb6k22b4EUGdH +Vi7UdLFz2MjBOiYfzLeBPD8PScQQ7PqLYRCb7cTfZExazQC8uBeCHoB5HV9or3aB +CYjpI8PkPEi1CBsCc/ZWh9cUeA7ttZQBwSE+uAyqabOWRMaJO14TRWjlfu2jdRuE +bLtuPGkKzv+iHIRl41E2NgcaP42NRl5limanzvATrD2upd22OmdjzjvE2k2ffksp +tDnlisWrmg3TrAfsHL8AJGwdEzyHH/TNoDd82roOi13UNQBIS5gHFEj8SqaeSTlC +PhCWbpQSTwyi04Lamyw3x7yLsPI5QSLnngWdYgTnC0dGvV+Cn5O6svpPoavLzimF +DfMVb45uPyw/u4VSftdeN4U0jkRxPd+nRr9rcOZKllGWDUEkIQIDAQABo4GXMIGU +MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdDgQWBBT8m5pVFWnVMeZ/Hsy5 +6eaBAB/qTDAfBgNVHSMEGDAWgBQMLUKtYgxz7600qkVe6yLEMX3YbDAlBgNVHREE +HjAcgRpDYXNlSW5zZW5zdGl2ZUBlWGFtcGxlLkNPTTATBgNVHSUEDDAKBggrBgEF +BQcDBDANBgkqhkiG9w0BAQsFAAOCAgEAkL+wwgHfsFIDfXRPc3TIIUExu1PayiS8 +0ohDFPYlMpq9dUlqqANqzb2WC52E4dxi3LCJfjYuqHFmxNta/3iZwrL25JzR1g5g +ByL7lGMwbMIR/LV/noOqXbTSpwzUcpPD+ElZjexseyrj/xwU5BIMfT8cfQ2h3G+1 +nMhn7iXxV4c3LpHAbV1eKZAtbVw+Mq4QpM1F6Jd2u9aYChUfbD5HDvihhEEHA0GR +ZSSns2pQpLplneh6eemPPJSvrhp86TEbVBtaEc+oT/bJoSnByHy8RYI3ST+u/oxF ++slxBrWj1Tbwu8AlGKSxYcXRxYdhjP3R477Uu5NyP9dzrcWctLy77GSv3fLy0xpI +SrMu3J3hIdVrZL+/x/kCWQ/u5oy6dI/O4ewXWyWg/r5Ccj/xZhIdHRH7bSKXgqOn +dL2U8vQGZdIuGkHTznCLagX8Tw74LcP9oHUpQM0rj7v4IY/qaWRt9CBUTAt/CN1l +nACYUIwiWirBV9i8kwtikyqc+baZMdX/QyXqjdZBHRWm2ewQM5meA9Sqer+iIiH7 +iDVX4DROMYvOx2bfvRNR/X7q1cZHpcoa4cfMGbkYp1vlVsS3BBApbjGUsqoBYxlK +mERsZxk4XPJSeeS1i/Szp94FzKp8qg3YuAx71eJnTZxuzRvnztSufRQyOP6sSbtJ +3fE8GIlZAgYwIjAKBggrBgEFBQcDBKAUBggrBgEFBQcDAgYIKwYBBQUHAwE= +-----END TRUSTED CERTIFICATE----- diff --git a/spec/fixtures/smime/CaseInsenstive@eXample.COM.csr b/spec/fixtures/smime/CaseInsenstive@eXample.COM.csr new file mode 100644 index 000000000..40011bda2 --- /dev/null +++ b/spec/fixtures/smime/CaseInsenstive@eXample.COM.csr @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIE6TCCAtECAQAwgaMxKTAnBgkqhkiG9w0BCQEWGkNhc2VJbnNlbnN0aXZlQGVY +YW1wbGUuQ09NMQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQH +DAZCZXJsaW4xGTAXBgNVBAoMEEV4YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlU +IERlcGFydG1lbnQxFDASBgNVBAMMC2V4YW1wbGUuY29tMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2jcXulTHLCFRvmP2jZTbFFsxGb0snAxih/tQGdjQ +Y3hMjUbqIgXW6LMsZJj5un2eD63K9rnyD4+StHEvMT0gRhK6r/+kCFBC4RLqXcYe +XCHYQVQyFvxD621BcAshAE+f1uKdbKjgPJytQxGnOSaa10Yll88qFl9x4Vm4QttA +JdXAqkxKHNKam0qHCaTe6ZouVabnbXTRbXZcoZHqk/2HqfK6BEXxHYuTcQba5/MV +qT1xvnW99DY14GWfZ8Gzh6TSg/wpv+t7yERbSDdQJqxxhOdf9jbK8WRq2dFaD5sf +G/No1m+pNtm+BFBnR1Yu1HSxc9jIwTomH8y3gTw/D0nEEOz6i2EQm+3E32RMWs0A +vLgXgh6AeR1faK92gQmI6SPD5DxItQgbAnP2VofXFHgO7bWUAcEhPrgMqmmzlkTG +iTteE0Vo5X7to3UbhGy7bjxpCs7/ohyEZeNRNjYHGj+NjUZeZYpmp87wE6w9rqXd +tjpnY847xNpNn35LKbQ55YrFq5oN06wH7By/ACRsHRM8hx/0zaA3fNq6Dotd1DUA +SEuYBxRI/Eqmnkk5Qj4Qlm6UEk8MotOC2pssN8e8i7DyOUEi554FnWIE5wtHRr1f +gp+TurL6T6Gry84phQ3zFW+Obj8sP7uFUn7XXjeFNI5EcT3fp0a/a3DmSpZRlg1B +JCECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4ICAQBkUCBJ2lYcHPpJim1F09xSbP9B +nHhBXZmE8TdOIQF2Y5aJLtt9rGj+XYzypsz35+FGGSchiKFgdKN+PEGgzkKCeEIG +CIHZW2K9fVtMqsAixKbdn0fHWdItdnEkvse3fIt4O2vr6WYP6xJ4MdEFGyevOw3d +4573Gq+khzgdBY7AZDIPsJu28SkESoPw1yzo3R5XV59kGvM67gNijQWu68F6casR +kQWo8vs5TkmzERBredoJoOb6OCpbdj42jbMf9R5uu3fFJtPQqPxE2aLv5dBI6U+e +aPRnYHFOo4AMhIELaiIsXaHVEfiICFGrZtv8goHdkxeB7flwC5m1Ml7jIj/v5/Ib +1s5/x95urE+OIhUmXyr8bifRijxfqQxL0Ol/nbxZx4AjQb07MaJ7PlqatwX2SjQ/ +5Sh/Sex44qFLD+lA3+c6hH3yW4q+rcbciiYa9Imih9sflQcT53jWcwomMsRcA8cP +wMmV2K0JBBYcOOHkyT1+L1v1BGWn0sNlHU+ES58OPU1d/IMbbVK7AZghN/CLPrIG +MlfuOmehiV5vnOvUX5pWsTQAF+7tP2Gw/CUN2TRbOb63OZSlWy1xSlzE9jbs7hb2 +WrbTH8e5eI3l0cCT1Ecbz+9/Bm8gtozbbEE/yMyok+Y5NJYjwVboCwQ1QYiIr/eU +eSfenFSsad1y7r5stw== +-----END CERTIFICATE REQUEST----- diff --git a/spec/fixtures/smime/CaseInsenstive@eXample.COM.key b/spec/fixtures/smime/CaseInsenstive@eXample.COM.key new file mode 100644 index 000000000..dab0bc3e2 --- /dev/null +++ b/spec/fixtures/smime/CaseInsenstive@eXample.COM.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,897152EBDB085F8EFC4E413FBF5A35FD + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/CaseInsenstive@eXample.COM.secret b/spec/fixtures/smime/CaseInsenstive@eXample.COM.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/CaseInsenstive@eXample.COM.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/ca.crt b/spec/fixtures/smime/ca.crt new file mode 100644 index 000000000..a21cece14 --- /dev/null +++ b/spec/fixtures/smime/ca.crt @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGEzCCA/ugAwIBAgIUdRCemO0RlIZ2tZTwO/1W2eeR/eEwDQYJKoZIhvcNAQEL +BQAwgZcxHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoMEEV4 +YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMCAXDTIwMDUyMDEzNDcxMFoYDzIyMjAwNDAyMTM0NzEwWjCB +lzEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBsZS5jb20xCzAJBgNVBAYTAkRFMQ8w +DQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEZMBcGA1UECgwQRXhhbXBs +ZSBTZWN1cml0eTEWMBQGA1UECwwNSVQgRGVwYXJ0bWVudDEUMBIGA1UEAwwLZXhh +bXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1bJsQfM2d +3xnNCvQRNaj4u4s5/TnXMdQzwb1fkRTOejEzV0qEXWWTewCMqgFenf/PwHsm6dEC +ESfO8aMz3yu8b8kpIBt3H5cdzSAaQ+Vyyv4bDe+o9EGcYzhRgonef7rYEaU+9s0S +qNDUws1XEAqtu77VHG82p99KSe7nL9t+I01R+f7PNkj553O5QoCHWg1I6zfj7s95 +lk9x8wHcBdRfyvYpCAkyV357dDvky2v/zHHYOelgAF0MG77kbsxHLlS9IM+GVFsc +pun28LoS4kokO9d7sDhkwSWFYGCQ/jURDtgFl/kf3JYqoCXdGvaTC1cesX9jm7Pu +HqFB7fhXbsKi2U8RmbTQ3iSsrkn/XWEbcOwGWOBmiDdmAAdCd5Fx2ALizviCfeyE +R0FzWstdDolugCGHt+6Ymm+hn84ZJMfe8HpSb/rqupSUgJUYLJCBFAyRGs5KugQA +/h/Zcd8JTh0cg1BCBca7Gqruqp1RyY09v/VYUlvPqyurfsVGSDvB79UPC0jRSt9I +T6ztiQUEeVcwZz0h2a77NW7JhKd5u96+rbxM2JnBKiNLthEaL92FIjSaZ0bpqJ6E +j/2FO3dhxSgX+lNMEnUmXgJ4Sf/pamoHIjr/KBbB4BF19JiGNsMP2Xa6FjwCmiM/ +62s7K6N32wa7kniRVKbvkAT0N7117TqsEwIDAQABo1MwUTAdBgNVHQ4EFgQUDC1C +rWIMc++tNKpFXusixDF92GwwHwYDVR0jBBgwFoAUDC1CrWIMc++tNKpFXusixDF9 +2GwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAa6/qeDl7R1iv +rRR66oAjjkt9m191fO41c/f+kA8qOeCNKuZRGWHYPRJYg2P72uQFANHawl8jcFlW +3gFNJ/+i7CxEhxeIFxIYX+wFEgqCBZCd1Ao+4i68C4EAVNVmtPxC96aGstzgamg/ +qIv70DpLDpTC6ZcD/qHbJQb2Qspsuz1jvkELQ3on77utHEaJcBn6C1qKotRIplWH +OKTczLtCgx/BI5QGV+XjzaJXm34l5LS3fA2Y+f2faSWz6lBjSZnTTxijBQfrMDqr +2h6CkEWniPeEY85Knbh7tdc7l0Q+mtUEXrw+8hrDRu8bVAfeGsx4464gd4ulZrmH +bBVqTxsarhih/UsB37Pk+lAueB5T/aqbr4QJj1mu+hukQcqySUCbl5b8S9sgfbB0 +RP+N9OgqRnkLlnds5eG3+XJl4G6h0iWEKP/XapQcf6VBZTvs17AxVnPDvDUKirTG +ecvAj8m07orp33Ng/5obFYxgLaJEBh2bA1NIVvPQzaYxDssp21JOawyUiWEAc9u0 +itycRBjSYxp8Q84HSj2Mw0+RQ4LKpclUlBeuN9tKo155lJanGVOm3Dy7QhpI0nxT +LMOTqLLv5EJo+5ok1IUTLBkvtPvFsYVZuauJyDdTUc7fpdILXXO9jGIKcBzf7ima +FglHwSJZSR2WTTsmd6f/9grOUVNt51I= +-----END CERTIFICATE----- diff --git a/spec/fixtures/smime/ca.key b/spec/fixtures/smime/ca.key new file mode 100644 index 000000000..ecf4b81bf --- /dev/null +++ b/spec/fixtures/smime/ca.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,C883629DF6D2EB2F339DA0E9DDE3A287 + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/ca.secret b/spec/fixtures/smime/ca.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/ca.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/expiredca.crt b/spec/fixtures/smime/expiredca.crt new file mode 100644 index 000000000..0a290508a --- /dev/null +++ b/spec/fixtures/smime/expiredca.crt @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/spec/fixtures/smime/expiredca.key b/spec/fixtures/smime/expiredca.key new file mode 100644 index 000000000..a379d1b8d --- /dev/null +++ b/spec/fixtures/smime/expiredca.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,AFFF2A7AF6117991109A4B9D75D38008 + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/expiredca.secret b/spec/fixtures/smime/expiredca.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/expiredca.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/expiredsmime1@example.com.crt b/spec/fixtures/smime/expiredsmime1@example.com.crt new file mode 100644 index 000000000..fa1d90f69 --- /dev/null +++ b/spec/fixtures/smime/expiredsmime1@example.com.crt @@ -0,0 +1,37 @@ +-----BEGIN TRUSTED CERTIFICATE----- +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 +-----END TRUSTED CERTIFICATE----- diff --git a/spec/fixtures/smime/expiredsmime1@example.com.csr b/spec/fixtures/smime/expiredsmime1@example.com.csr new file mode 100644 index 000000000..5f2058ca6 --- /dev/null +++ b/spec/fixtures/smime/expiredsmime1@example.com.csr @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIE6DCCAtACAQAwgaIxKDAmBgkqhkiG9w0BCQEWGWV4cGlyZWRzbWltZTFAZXhh +bXBsZS5jb20xCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcM +BkJlcmxpbjEZMBcGA1UECgwQRXhhbXBsZSBTZWN1cml0eTEWMBQGA1UECwwNSVQg +RGVwYXJ0bWVudDEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQC7ZZBDydfZf0mpnCpCLSqJWa7mCf3nqXrc4XjuWEnM +/uGFrto/adNLK/580ef7lN30G7xWU0UxwaBwQCC2X0RLHmwUyBiOXTNFQ01xQcIx +cVDh15Orogn9bOM4hnFpLXkLLsvsJQ9gaGvGFoIJcfUKhHd9fxLPFkM0RQmwerUy +4mWf6yh1xdPThRDiuC+Ce/RQZTlPaJCgI/h+GUgLIbWJTs4HBhL6J8kMfwQlJJsO +uRtlo7XxK+Rpi7zWzUYFxI8w4kElEMrAd2P/RAQZjg+4rbFg4ThKRDfWAkCMxVpI +LEG/WL94+CCf0OX5aeXGr1Ij0rk4F3XgueulNgiSwhFDy6bqN6FHuLUkDTgMb4SX +Spk0m+BlM/+xV1fXX8d1iZFD9qA82gyVwDFZ3to0ni4dK0SWH8/n31aeOiehTwwK +mIabf6ymcgVd2DQ9dMxibWOmLHBCP1gogpKLXoAGLvsaCSdJ74Wy+5rhMla9/R6b +JKeGN7STilF963m+L+5FtS/+q2bgoVJ2RmtsEOU24s5gBOUM/0MseYaW9J+gvpda +S1gcq2vXh2FOGGk9rLE4JE/tUm3HaVjjRitMmxI9E1NKiRWnohqimmzYK+aJ+LHq +g5uwaZ8UfYi8SGuqa/+EMy9AZvd0uZ5SKiZ3kZMb1OFpka5PpdyypNU2h2zIkCKT +zwIDAQABoAAwDQYJKoZIhvcNAQELBQADggIBADS28gWmrzgcXx+ZTFc3EpffNmcx +lulJ31PXsNot2NRMga4mRA5L8GK1uKrLAIq4koaBUGT0vvV46R9zpC+bildfxNGz +5+v7mvIYkpdfFJW0vAHnO4FuUMkNHDvLSVtly+zQD6k1fwrHxCqsr0bKaZE28b5Z +Bf/9mLprifmtSOy0brSGQ/C7C3z4NCvtLHd6Tb53lnBK6WTeoRYKXvFynIN5Guf1 +ONCcUK2TIRxjSYLiz7A26SpHGHfgGnjZutJUqKtr2sWhpq/XVZe8tfAWBrMTn4oT +SJAYRQ7iHdr15egpB0Y/SNn5nkcYSsSg+deEzV2pTbxgEOlY/+ti/V1xyBumIFo8 +6Taj2mRXZHkzUNqEWB+nG6zvJwqyefTxaDA6Ijw29tFSURxH0j5priAgKJaBzQ5z +LTLfdi/HU92k7ACJxK5tJdDRj1uVUxPg2wS+mZVn3s4qU6wly3Nk8JsnG+WKUQFQ +BU/a00qy/4KjthFm6Tb++I0X7xZO42aMuaVepskQyum7AhTqehyayGo/MhGf1QIs +ouTC8BF7MMBvF2EHEf3TOazbYDoeUS3VHm1wEgjlt500/DiOlFxMWO7JCebRTxHA +NuHxvIwaPOQPP3gLi0IxV1HzG5rPxa66l+pp3M+lU8V+nQuNBvCa10FVgQB+BBrt +5En06ChboN3gVolO +-----END CERTIFICATE REQUEST----- diff --git a/spec/fixtures/smime/expiredsmime1@example.com.key b/spec/fixtures/smime/expiredsmime1@example.com.key new file mode 100644 index 000000000..ada4921c2 --- /dev/null +++ b/spec/fixtures/smime/expiredsmime1@example.com.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,8CE77F705A91FC4378059C551C7C5A05 + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/expiredsmime1@example.com.secret b/spec/fixtures/smime/expiredsmime1@example.com.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/expiredsmime1@example.com.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/expiredsmime2@example.com.crt b/spec/fixtures/smime/expiredsmime2@example.com.crt new file mode 100644 index 000000000..fd851f1f6 --- /dev/null +++ b/spec/fixtures/smime/expiredsmime2@example.com.crt @@ -0,0 +1,37 @@ +-----BEGIN TRUSTED CERTIFICATE----- +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 +-----END TRUSTED CERTIFICATE----- diff --git a/spec/fixtures/smime/expiredsmime2@example.com.csr b/spec/fixtures/smime/expiredsmime2@example.com.csr new file mode 100644 index 000000000..622f6f785 --- /dev/null +++ b/spec/fixtures/smime/expiredsmime2@example.com.csr @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIE6DCCAtACAQAwgaIxKDAmBgkqhkiG9w0BCQEWGWV4cGlyZWRzbWltZTJAZXhh +bXBsZS5jb20xCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcM +BkJlcmxpbjEZMBcGA1UECgwQRXhhbXBsZSBTZWN1cml0eTEWMBQGA1UECwwNSVQg +RGVwYXJ0bWVudDEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCfsqLrCIBVQPywMQQglALMjW0/ZVuuLoXmS5t+7ZJW +lQIviL+oL+O3WfVZ+l4pPTvLHu2FmG4x7AKuB8g403L/ietVNJgevWpn7ICSSbg1 +5MIwuSMFGKuC3hL9UpYmwqECm7PYl2WKo8+0QqFR8NCs3x+EZ4nbNyUEWjxYRG7P +IyGh/bO7aeKrFEHHe8Jql/ynPedaQ/fKxn6q8opr6x7YTWtPq33truqmBb6V6l4a +6SBJE0Yaw5EJ4FzBa12zVO2BKoDCVuA0IVq8vYDrL34oSCM4zOGyLwLN/TlMtssv +hjqwsV65nyuHCImN6NH/mqOxScVIFcjDbS6VMmx8Q7Sm4LSKsH4nnK34G0plaHZP +O+GpEgqf5TJnUH3VfJKw84aBrXaYrgdfeVjvx12a3dcMMRwv/T9zUgS5PImsOIcK +y6brSBayy5jIZF+cMmspWQV3qgBKHphgi8QFCFjSJvWqx9rbYNGqLG43jVAXAVr1 +BGFyY6raX7o2rxSXOdFj/nsrYhlAZn0E7ljiVjHhZzXH4LvlwV28ByvKPwazUTvn +b0GDd95vT2QfqcKxKJUYJzzE49+dmMjNg1U3Rb/3xJyv9RzdhHUw7d9aWOqIOvfF +Y2utBOMSCUkPB9BQbPrB8I9Aq1C6WYu8pDX2e1yU3Jb54wf4hOknVYswId/Gm3zN +ywIDAQABoAAwDQYJKoZIhvcNAQELBQADggIBAHvxYspSNaV/k9ram2S/N+OepSQq +BZh5sQKI/JXqihU6/CEXB48Ogwmg0b4HbpVmZWqgs4XbGediTtj3yq5HVmqQ3518 +ZIUH/ZwKn4HSCQyTJ4PmMCVq15E+nqhXGMCuk/4xVTuv4eCqHKHqLvliWLU6Zqy2 +5TJKLMOLlfulbN5KkV/2S185pHXDt/IHey6/GISeuIR0t9ev8+3apv7TWLT/aLbn +lJQuHFLBOWmFjk0QF69jcXA8O6wjmJguJA2NjamrjQ0Uy+PXN1IDchEwGmLDKbEv +OcuhkBoV5RV+RPw1VBTavjkmHWT6Mb4NW4Iq+iT/SV/oYNqCmJcFKj4s1qxOvB6A +DxWsbX241nf3SUJDcoKl9eLXPqYZDqoH9PNufuJBaHXwUbicKBSdSbDhNPuk3JOM +jOnLeQYE8yrS27xm83XlB0wP11zSefQ+gfKFjOQUuS1RXpQngSDk6RzusglZUhjn +UJdJhTwucOaamGdgpu+LwvZxiAmj9wM2+r6XJ01+VmoKmc+u0/5Kp29AjkjcaFQn +c5PdrgIRtypE4SNZMniY584QNE0boJ503ygiiXd4WTR58Lbg1ezd9EpQeDsfDP3u +suzS3ChO1JCidk7YhV6BKoSAR7fmRBUefu7jWtY/fOUZnl0F9+WA2lwShtFqYE0j +hLPOcHKMoBEygeCn +-----END CERTIFICATE REQUEST----- diff --git a/spec/fixtures/smime/expiredsmime2@example.com.key b/spec/fixtures/smime/expiredsmime2@example.com.key new file mode 100644 index 000000000..d7999ec23 --- /dev/null +++ b/spec/fixtures/smime/expiredsmime2@example.com.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,2D01520A669DCCAAC31310846FD3F95A + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/expiredsmime2@example.com.secret b/spec/fixtures/smime/expiredsmime2@example.com.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/expiredsmime2@example.com.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/generate/Dockerfile b/spec/fixtures/smime/generate/Dockerfile new file mode 100644 index 000000000..9c54d003a --- /dev/null +++ b/spec/fixtures/smime/generate/Dockerfile @@ -0,0 +1,21 @@ +FROM alpine + +# faketime dependencies to generate outdated certificates +# see: https://github.com/trajano/alpine-libfaketime#with-simple-command-line-apps +COPY --from=trajano/alpine-libfaketime /faketime.so /lib/faketime.so +ENV LD_PRELOAD=/lib/faketime.so +ENV CERT_DIR "/etc/ssl/certs" + +# install openssl +RUN apk add --update openssl && \ + rm -rf /var/cache/apk/* + +# move base files to the container +COPY config/* / +COPY docker-entrypoint.sh / + +# enable volume to generate certificates into the hosts FS +VOLUME ["$CERT_DIR"] + +# start +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/spec/fixtures/smime/generate/README.md b/spec/fixtures/smime/generate/README.md new file mode 100644 index 000000000..c5abad19e --- /dev/null +++ b/spec/fixtures/smime/generate/README.md @@ -0,0 +1,6 @@ +# Zammad S/MIME test certificate generation + +This folder contains a `docker` image and the required files to generate various S/MIME certificates for testing purposes. These contain CA certificates as regular certificates (1). There is one special certificate that is for multiple emails at once (2). Additionally some already expired CA and certificates are generated (3). +For the CA there are `.key` and `.crt` files. For the certificates the `.key`, `.crt` and `.csr` are generated. Additionally a `.secret` file is added that contains the corresponding pass phrase. + +These are easily generated by executing the `run.sh` file. There is nothing more to it except of having `docker` installed and running. diff --git a/spec/fixtures/smime/generate/config/config.cnf b/spec/fixtures/smime/generate/config/config.cnf new file mode 100644 index 000000000..2b9a33183 --- /dev/null +++ b/spec/fixtures/smime/generate/config/config.cnf @@ -0,0 +1,8 @@ +[smime] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectKeyIdentifier = hash +#authorityKeyIdentifier = keyid:always,issuer +authorityKeyIdentifier = keyid,issuer +subjectAltName = email:copy +extendedKeyUsage = emailProtection diff --git a/spec/fixtures/smime/generate/config/double.cnf b/spec/fixtures/smime/generate/config/double.cnf new file mode 100644 index 000000000..356c69407 --- /dev/null +++ b/spec/fixtures/smime/generate/config/double.cnf @@ -0,0 +1,15 @@ +[ smime ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectKeyIdentifier = hash +#authorityKeyIdentifier = keyid:always,issuer +authorityKeyIdentifier = keyid,issuer +extendedKeyUsage = emailProtection +subjectAltName=@alt_names + +[alt_names] +IP.1 = 192.168.0.23 +IP.2 = 192.168.0.42 +email.1 = smimedouble@example.com +email.2 = smimedouble@example.de +otherName = 1.2.3.4;UTF8:some other identifier diff --git a/spec/fixtures/smime/generate/config/pass.secret b/spec/fixtures/smime/generate/config/pass.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/generate/config/pass.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/generate/docker-entrypoint.sh b/spec/fixtures/smime/generate/docker-entrypoint.sh new file mode 100755 index 000000000..8e2b23015 --- /dev/null +++ b/spec/fixtures/smime/generate/docker-entrypoint.sh @@ -0,0 +1,104 @@ +#!/bin/sh + +echo "Zammad S/MIME test certificate generation" + +if [[ ! -e "$CERT_DIR/ca.key" ]] || [[ ! -e "$CERT_DIR/ca.crt" ]] +then + echo "Generating ca.key" + openssl genrsa -aes256 -passout file:pass.secret -out $CERT_DIR/ca.key 4096 + + echo "Generating ca.crt" + openssl req -new -x509 -days 73000 -key $CERT_DIR/ca.key -passin file:pass.secret -out $CERT_DIR/ca.crt -subj "/emailAddress=ca@example.com/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com" + + echo "Generating ca.secret" + cp pass.secret $CERT_DIR/ca.secret +fi + +for EMAIL_ADDRESS in smime1@example.com smime2@example.com smime3@example.com smimedouble@example.com CaseInsenstive@eXample.COM +do + if [[ ! -e "$CERT_DIR/$EMAIL_ADDRESS.crt" ]] + then + echo "Generating $EMAIL_ADDRESS.key" + openssl genrsa -aes256 -passout file:pass.secret -out $CERT_DIR/$EMAIL_ADDRESS.key 4096 + + echo "Generating $EMAIL_ADDRESS.csr (certificate signing request)" + openssl req -new -key $CERT_DIR/$EMAIL_ADDRESS.key -passin file:pass.secret -out $CERT_DIR/$EMAIL_ADDRESS.csr -subj "/emailAddress=$EMAIL_ADDRESS/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com" + + echo "Generating $EMAIL_ADDRESS.crt (certificate)" + + if [ "$EMAIL_ADDRESS" != "smimedouble@example.com" ] + then + extfile="config.cnf" + else + # special config that contains two email addresses in one certificate + extfile="double.cnf" + fi + + openssl x509 -req \ + -days 73000 \ + -in $CERT_DIR/$EMAIL_ADDRESS.csr \ + -CA $CERT_DIR/ca.crt \ + -CAkey $CERT_DIR/ca.key \ + -out $CERT_DIR/$EMAIL_ADDRESS.crt \ + -addtrust emailProtection \ + -addreject clientAuth \ + -addreject serverAuth \ + -trustout \ + -CAcreateserial -CAserial /tmp/ca.seq \ + -extensions smime \ + -extfile "$extfile" \ + -passin file:pass.secret + + echo "Generating $EMAIL_ADDRESS.secret" + cp pass.secret $CERT_DIR/$EMAIL_ADDRESS.secret + fi +done + +echo "Generating expired" +FAKETIME=-10y date + +if [[ ! -e "$CERT_DIR/expiredca.key" ]] || [[ ! -e "$CERT_DIR/expiredca.crt" ]] +then + echo "Generating expiredca.key" + FAKETIME=-10y openssl genrsa -aes256 -passout file:pass.secret -out $CERT_DIR/expiredca.key 4096 + + echo "Generating expiredca.crt" + FAKETIME=-10y openssl req -new -x509 -days 1 -key $CERT_DIR/expiredca.key -passin file:pass.secret -out $CERT_DIR/expiredca.crt -subj "/emailAddress=expiredca@example.com/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com" + + echo "Generating expiredca.secret" + cp pass.secret $CERT_DIR/expiredca.secret +fi + +for EMAIL_ADDRESS in expiredsmime1@example.com expiredsmime2@example.com +do + if [[ ! -e "$CERT_DIR/$EMAIL_ADDRESS.crt" ]] + then + echo "Generating $EMAIL_ADDRESS.key" + FAKETIME=-10y openssl genrsa -aes256 -passout file:pass.secret -out $CERT_DIR/$EMAIL_ADDRESS.key 4096 + + echo "Generating $EMAIL_ADDRESS.csr (certificate signing request)" + FAKETIME=-10y openssl req -new -key $CERT_DIR/$EMAIL_ADDRESS.key -passin file:pass.secret -out $CERT_DIR/$EMAIL_ADDRESS.csr -subj "/emailAddress=$EMAIL_ADDRESS/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com" + + echo "Generating $EMAIL_ADDRESS.crt (certificate)" + FAKETIME=-10y openssl x509 -req \ + -days 1 \ + -in $CERT_DIR/$EMAIL_ADDRESS.csr \ + -CA $CERT_DIR/expiredca.crt \ + -CAkey $CERT_DIR/expiredca.key \ + -out $CERT_DIR/$EMAIL_ADDRESS.crt \ + -addtrust emailProtection \ + -addreject clientAuth \ + -addreject serverAuth \ + -trustout \ + -CAcreateserial -CAserial /tmp/expiredca.seq \ + -extensions smime \ + -extfile config.cnf \ + -passin file:pass.secret + + echo "Generating $EMAIL_ADDRESS.secret" + cp pass.secret $CERT_DIR/$EMAIL_ADDRESS.secret + fi +done + +# run command passed to docker run +exec "$@" diff --git a/spec/fixtures/smime/generate/run.sh b/spec/fixtures/smime/generate/run.sh new file mode 100755 index 000000000..0d5daaafd --- /dev/null +++ b/spec/fixtures/smime/generate/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +docker build --no-cache -t zammad/smime-test-certificates:latest . + +docker run --rm -v `pwd`/../:/etc/ssl/certs zammad/smime-test-certificates:latest + diff --git a/spec/fixtures/smime/oldca.crt b/spec/fixtures/smime/oldca.crt new file mode 100644 index 000000000..4c6778f69 --- /dev/null +++ b/spec/fixtures/smime/oldca.crt @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGFzCCA/+gAwIBAgIUMmCv8hoMpL1DxnfbdnB7B8toiJYwDQYJKoZIhvcNAQEL +BQAwgZoxIDAeBgkqhkiG9w0BCQEWEW9sZGNhQGV4YW1wbGUuY29tMQswCQYDVQQG +EwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoM +EEV4YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNV +BAMMC2V4YW1wbGUuY29tMB4XDTEwMDUyMzA3MDUwOFoXDTEwMDUyNDA3MDUwOFow +gZoxIDAeBgkqhkiG9w0BCQEWEW9sZGNhQGV4YW1wbGUuY29tMQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoMEEV4 +YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtnoR +t1oSnGD2p6EF/RwRH2qrj0crDtVMubda6sls6tO14a/4qQ/za6WcSKRaPdVtsZ1j +r5yK8QaiaSZ4/jmfmuSpUvu0F7rQyhZ+bNI+ibF6YTHe2DGkdjH4rouoC7WEtfLz +saSYvojXOCelelbiMf94w6uDvMCRu5A47AHgFRAYWh3PD/kPuxefd2OYpZBjUVF0 +fOs6tkPWpPzt03Xu8RiJP1/UkLk/ZISLvG5VriMOG+KeOMFAz7gDNaGxoKJ7wlUb +A8Ht0FbQ5ZaYaDlmG0ENhnMoM74h6jxbPlRHvuDzAccZfb9m0e+HX9xP5Q8Cp+ib +EMpSe0SBE3N56a7aCPHTiXoC2wYL32rMUhjC2dgLT/UwBDiMiIksDm7W4IQv10l0 +GYGvinDBFEE+fVmQF/g0XBJGmTFi4uN+ZcXaUpLQiBi075E89Rwm9+Xrqr9L/fcm +y2GEnwN0KI2wyFnwMDDMRKfZXSMtU9sCEiqZTIXiAfy9t0wzjbG2P+cXh+p07TNv +POTCyaB98tfocIWIgB6GDFXfgZQJLtZN2Qwm1+MfG63OCFY9QCXoiTaja08+ukB8 +kZ8TNCzDs2E82D9fl5aldtaSwCuMJMc0cdIzi25SIJzc4UGtUrEFI8Olrk/QUQ+W +/h0OYptIwJGvdwEquVAfQrPDoIPEN0iJeHfNqWUCAwEAAaNTMFEwHQYDVR0OBBYE +FDw9N9kuUQx109fegyN/ZBWPLmYcMB8GA1UdIwQYMBaAFDw9N9kuUQx109fegyN/ +ZBWPLmYcMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAC30oxIo +LaQcXt/AS9FyOZ3dD/5ZzynqTyoSEcP1QzEIXdwIxY3nUaqFCiosBzsd7ZmrSA3S +oaWyFYGutZppVtw39OsbJEKKOv5SX03HEJKoOHkbanPLVNBcdVytRGUI+b61VPEt +SVtbLWD3tjF1Pl0HOtYyzy8gZZWUYRD4LBHtUeDIkY6f1XPTZJuFxpvhb+9l+nGq +OYFhclxVtSNokP+/42vHiNgCuN+9XDhkI2nSC1hKHETEASEmXWCAJ1GI1RqDQ8yr +wghe8LDxRI9R2jTwsd6KEIAufxbEksKw1pvvRS0c06i5cAMf9w/dPerOd1sEdE07 +iESaWTc73kjmgglakFRumuuOqsUVmoW9S/IMVt5gyX0wQVjNmV6Ic3LKF5tmEi3E +7h0aFRtzQE+88QcldDLnU4hQA1IwGVpsKnohPq7YWi2HTSp6cJTsdB5c8Rl7zRCw +cy3xWOyLDlUxmYJehfwlHQy+Nf2MZ/KPO8a6Sv9IPF9H6Stra3LD41Ac7+pHz2B/ +SrGKieCqoY1Moby/nipRbdwF3ExBjOyDUem+VyYZy7ldsCBW0OE8cwb0D87PN/lu +PULhsWi4ZhPV72kQ5IOvQG/qj78tbVvsoxabY+cDvSrb1NA3aaoQC8T+1Q91YpQa +8c1mJgWpM0EAmsUKjGvE3swfKDtSOPndr6vo +-----END CERTIFICATE----- diff --git a/spec/fixtures/smime/oldca.key b/spec/fixtures/smime/oldca.key new file mode 100644 index 000000000..33b7882af --- /dev/null +++ b/spec/fixtures/smime/oldca.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,1B90035BC983D129D9910AA0D912DDE2 + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/oldca.secret b/spec/fixtures/smime/oldca.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/oldca.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/oldsmime1@example.com.crt b/spec/fixtures/smime/oldsmime1@example.com.crt new file mode 100644 index 000000000..5934ad9bd --- /dev/null +++ b/spec/fixtures/smime/oldsmime1@example.com.crt @@ -0,0 +1,37 @@ +-----BEGIN TRUSTED CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIUIUrCfVRV1i03o1zHmhtNv8XMpBQwDQYJKoZIhvcNAQEL +BQAwgZoxIDAeBgkqhkiG9w0BCQEWEW9sZGNhQGV4YW1wbGUuY29tMQswCQYDVQQG +EwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoM +EEV4YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNV +BAMMC2V4YW1wbGUuY29tMB4XDTEwMDUyMzA3MDUwOVoXDTEwMDUyNDA3MDUwOVow +gZ4xJDAiBgkqhkiG9w0BCQEWFW9sZHNtaW1lMUBleGFtcGxlLmNvbTELMAkGA1UE +BhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMRkwFwYDVQQK +DBBFeGFtcGxlIFNlY3VyaXR5MRYwFAYDVQQLDA1JVCBEZXBhcnRtZW50MRQwEgYD +VQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AOlZ96ydfW6IGP3roFTyqDTs37MAdHH2VyEYU0qxM98HhDcpXn3iNVeyyOTbeCwy +7yw67EdMwukXZ7VQnxeaz37e7n6tYG/S2lIKYZJkNr2kZ66QACfhSN54wH8sz7H4 +giwBV1NShdCtdcjUqV1r8srCOIsUGsW4b8W6HqZ6XDHzmkV6N3pJueUrQdWfY1El +7BBgJY6nsTSQzoycEA5PiWxo9b3Br7z34dj7sHvnQmT8qdlkPq9xfT1tiOpWF5Pk +3Xj6lwqSueYVWpCgohcrxuSi8DpZLwAXXjx410e1YuRMDcC6EbIQioePbFDfgcHv +tgluMZkY5JR0q25na16ONuFYx8FOL+7JzIyxvnUZYUQEvuFXaZk5YhxeRjITvNHr +iV4A7iINBkfwLXeo8GDywSK89VxC6g7p435CBBJfg2kE5jHIy2qVdw83zWfWRuhJ +Xg0NEUV7K8ktDL07w5EJOhhaEqUkvSeNSxUbR/eXyRyrs7kl1VLCI+Ug1x/YBRFn +br8NSKohqvv4s4aZ66bF7nkNlLVEZYmvZorL2VGjCGztbw7nEoft8FPDMfMseKiC +i5lhc4B54d93H06HuDG7TenkXfyCB70kmBSZpImpCJVdP8E3scYbys4zSomu1BQy +/CHn/GBVnS2spE27EWEm6vfF3sghJbFjXmW4O761XM0DAgMBAAGjgZIwgY8wCQYD +VR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0OBBYEFGlwE9fYOr/AGCyldJcQvvTz +Td82MB8GA1UdIwQYMBaAFDw9N9kuUQx109fegyN/ZBWPLmYcMCAGA1UdEQQZMBeB +FW9sZHNtaW1lMUBleGFtcGxlLmNvbTATBgNVHSUEDDAKBggrBgEFBQcDBDANBgkq +hkiG9w0BAQsFAAOCAgEAEXAym+igYPgt1pY3T1fgt0TEkzeBE8BY5s/A4Kpse0aU +RtaQ9jowxSF+hg/MtsNaxIZyXwS5bewrdGla/zlpcYd5uAxw7QgH5HpSrbo0yiVa +t9jET9VZO8ErbCHlf46wBduzNh7XUzZSm2sPBnlGL4CdpWbCQPFdqENFeEKiYpvC +g1Qgw/WTBaOmvNOIWVoSWqQDdLGdBCZys5bkR2YtbZzOgVF/eumyH95yk3BchM0A +scIyxWzHvf8Vss3Idp2djMmSI6tm4wQMvRZ3ScdLBtlStz+J0D3UNhV2lTjdewwn +oJ0RRSj58AVZlD4BFQ0GCg+gwsEhH71CWOuyFWtbzxKhdVojNKHkc6FioKnNffym +nge5o8wYuEETBsu2fHgMTSYxn1jOIVQZuHHXtN/BjXAZUZdKiz7jxZuNiZw43dMg +g5diNC0+VzsMEwINFd5/vQ9R1CX91RFXnZZ5MpM6IGYEHJ7QPRmLmxFtGcFi/f/U +lFCoK0nsOCIcWzFX4PJjAKTvWMekrrp09ImL0IRx+2L7RTA9R6x4DG17ZawTYA1L +uUvvVrbSjfmEIttlktEfL7g9xoDBWxsH7GpfBk4SKNg5jgGpeH5GxzQNPBeqwfeB +XH/n30Ym0jW+y0hDKvnPdjcvz0cJYHe4W3/srtqUu1aJmPa8he7jdejJSpkeUSAw +IjAKBggrBgEFBQcDBKAUBggrBgEFBQcDAgYIKwYBBQUHAwE= +-----END TRUSTED CERTIFICATE----- diff --git a/spec/fixtures/smime/oldsmime1@example.com.csr b/spec/fixtures/smime/oldsmime1@example.com.csr new file mode 100644 index 000000000..eed51f03d --- /dev/null +++ b/spec/fixtures/smime/oldsmime1@example.com.csr @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIE5DCCAswCAQAwgZ4xJDAiBgkqhkiG9w0BCQEWFW9sZHNtaW1lMUBleGFtcGxl +LmNvbTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVy +bGluMRkwFwYDVQQKDBBFeGFtcGxlIFNlY3VyaXR5MRYwFAYDVQQLDA1JVCBEZXBh +cnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAOlZ96ydfW6IGP3roFTyqDTs37MAdHH2VyEYU0qxM98HhDcp +Xn3iNVeyyOTbeCwy7yw67EdMwukXZ7VQnxeaz37e7n6tYG/S2lIKYZJkNr2kZ66Q +ACfhSN54wH8sz7H4giwBV1NShdCtdcjUqV1r8srCOIsUGsW4b8W6HqZ6XDHzmkV6 +N3pJueUrQdWfY1El7BBgJY6nsTSQzoycEA5PiWxo9b3Br7z34dj7sHvnQmT8qdlk +Pq9xfT1tiOpWF5Pk3Xj6lwqSueYVWpCgohcrxuSi8DpZLwAXXjx410e1YuRMDcC6 +EbIQioePbFDfgcHvtgluMZkY5JR0q25na16ONuFYx8FOL+7JzIyxvnUZYUQEvuFX +aZk5YhxeRjITvNHriV4A7iINBkfwLXeo8GDywSK89VxC6g7p435CBBJfg2kE5jHI +y2qVdw83zWfWRuhJXg0NEUV7K8ktDL07w5EJOhhaEqUkvSeNSxUbR/eXyRyrs7kl +1VLCI+Ug1x/YBRFnbr8NSKohqvv4s4aZ66bF7nkNlLVEZYmvZorL2VGjCGztbw7n +Eoft8FPDMfMseKiCi5lhc4B54d93H06HuDG7TenkXfyCB70kmBSZpImpCJVdP8E3 +scYbys4zSomu1BQy/CHn/GBVnS2spE27EWEm6vfF3sghJbFjXmW4O761XM0DAgMB +AAGgADANBgkqhkiG9w0BAQsFAAOCAgEAGoMv3vAwy8cBLr9rE0r02QKLyYHXtbWX +1JbK/UF/yIfXesde3c/mQdqd3zhxb3YDBK6d483C273a1jdC+8k5NfVvDpcbyLaW +/9sDd9VNXk9CrA4VUVvTBUfYXLsShTRRcyWY9TnoTt0eGnT3PTRzbnW6jEgxStd9 +WFhh+FEIea5J/FmvKoEUOkN5j6VQveBJoDwFRZTFLsV/vK4DpcDJJ9KRjm67EssP +n473kwPdzKjjODEitpJqjNG4casn9qgKCKu+a10W9smChsMxGE7Ic1yjb3zDZNfE +sBP8rTcLVe8Y4bEljZOe0acWIFWEltk1wrIJ8IBMEREqPUE3bi/MgD0EL9y44REv ++60YMmCO3BKWEKUM2UYJtNMosNKvULEea7Q3hVVgMV2InisIs3MnDFlwuSTtYF5u +ixR08/8ksvpvrL2ybWTOsJDtiZoaBVoVYr2Z+jXuUyXAXl1rfcmMGNzZ3PYqhTPV +KWBpPiQogyCllL3J2Yf7BD2FRSyjnbsm6Do5rdJLLCFrcltrJ9nOCvLWmawIusl7 +jymxpxAf/gOsHO3OfOJARhrL2jaSalaY8BcjLuUJCQ7u1zCDLgHnW+/CPMjDphcF +L+2plCWZr5gtkypczMJFw5ZOlK+SwI0TydCoWkQQxLjaWnqoxigsx3jF9W2uCQDX +iJZ6c2DEnLU= +-----END CERTIFICATE REQUEST----- diff --git a/spec/fixtures/smime/oldsmime1@example.com.key b/spec/fixtures/smime/oldsmime1@example.com.key new file mode 100644 index 000000000..ba3952b75 --- /dev/null +++ b/spec/fixtures/smime/oldsmime1@example.com.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,075058C49FC97E93DC2A6598E450A08C + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/oldsmime1@example.com.secret b/spec/fixtures/smime/oldsmime1@example.com.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/oldsmime1@example.com.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/oldsmime2@example.com.crt b/spec/fixtures/smime/oldsmime2@example.com.crt new file mode 100644 index 000000000..8ef99aabd --- /dev/null +++ b/spec/fixtures/smime/oldsmime2@example.com.crt @@ -0,0 +1,37 @@ +-----BEGIN TRUSTED CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIUIUrCfVRV1i03o1zHmhtNv8XMpBUwDQYJKoZIhvcNAQEL +BQAwgZoxIDAeBgkqhkiG9w0BCQEWEW9sZGNhQGV4YW1wbGUuY29tMQswCQYDVQQG +EwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoM +EEV4YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNV +BAMMC2V4YW1wbGUuY29tMB4XDTEwMDUyMzA3MDUxMFoXDTEwMDUyNDA3MDUxMFow +gZ4xJDAiBgkqhkiG9w0BCQEWFW9sZHNtaW1lMkBleGFtcGxlLmNvbTELMAkGA1UE +BhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMRkwFwYDVQQK +DBBFeGFtcGxlIFNlY3VyaXR5MRYwFAYDVQQLDA1JVCBEZXBhcnRtZW50MRQwEgYD +VQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AMWrCkplNiVerePhBjafapLrl2WwpzP8O1Epf9mAUrh0iMKX2G2Y1ve+JGLHALwU +aLnOOeOutPJKDGykWQ+/zV/8U80JmOLt8LyrTLzPun1I7FqGAkx/hO6skCbDo5er +szqhIx2bhLdS43EeANRx7RdzNWEYw9Eqgmcc3MFxOGIPe54EbnDcm0qzdvIxtNbZ +ll9rBuLlYAzGSs+5+WdbjnxZGhhaAICgmslJWg1Bh8LbuQX1YYEsThq/KBnidBOl +HSPGMDAj4g075T5A5KwhF6JG2sInSVXWjXktAzxUeZGY4f3dsVc3lAchw6W25IPP +ysF8IF3OfO+N+XKZLYZ9x1P+bB1dsdfSUZFUOWRUcpngWPKofkSYjUL0JQQgKCx3 +HmwKQBYhgIeJ35eIPPoWwGdvQTqmveyiYd4TqYYP6f+ay7gIPQEtfsF18y6kdfN5 +F3gkv2GKBOvfNP86fnrEftMcQLBxAro+5ceF4ENIBm9xWSgjEISMoD9TlMtn8bIr +Kn8p3Zot3KUMG8AT8g1w8sl+43m4hfbgIZFecMtTHGTfTBKO2KBBnb9oNXh8GAyp +QYRN8oUuC72DQShxfJ5nFYYPghDX7Mlmiwt0X32gi2YwpPf7OI+DQtuUhAGOoGr7 +oThUY631kASIXSVtDz+pbP2CYwbSlRqEOE+tkps/3nhfAgMBAAGjgZIwgY8wCQYD +VR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0OBBYEFElniL85pHV/jj46LaOKnG4n +s1gwMB8GA1UdIwQYMBaAFDw9N9kuUQx109fegyN/ZBWPLmYcMCAGA1UdEQQZMBeB +FW9sZHNtaW1lMkBleGFtcGxlLmNvbTATBgNVHSUEDDAKBggrBgEFBQcDBDANBgkq +hkiG9w0BAQsFAAOCAgEAbsc1EVFSRrFivUskd1RyHPeYGaPQilru1UKOINKlblaT +5niNK053XLgKgNYNyhmrJiOmIKPnV0iuTzLOjYsQbY0hArtoHYoMjWYwrPEL6cPq +KAcbvnE3Me8KXEDT1K0ysgwbW4jUyN2qFWbM6cEVu+mv7YOAsTt3J56gbRY71cQh +5wYP6qM3AfQ8aY3e18dEW4h7vLZL/VIvs7jW5NjBzJapfzuj4gvXNKobmAOeWXF3 +xAVT93dlgEo5EFexizgyUUG2Oeu/TlN2ssTHfNhgSLfmN20Q1PZf1CqCSS+FbFTp +IgKI07cHQZTM5Uk5M+MZKhmJDIWVKIDMOED2XGeITuKXDJjvU6nFnzEdEcwhiT/w +gxn+qcOo5v7HmmOxVXhtBCsCtZPsx/2DEjRUw5jEDvSsV2+HbCws8+Ry5Q4VT7l6 +NX8vhsmyXVLBDBGAhBY4HcV+L5FzluacixGdE+eP3ndN3pOOOQ1cMVTFCotYn/IJ +bZH8mCWfLkuPqkemnIWve4Xv1zVPteMroz56g+M2PnoxPqZbBrYyUEWo3RsYmLFi +Vne8VKWl2OwA/dVoxN4Cpr0JAqXPfYfeWA/f/ZJFTEfTDUN9+MYZyI11obcs2qEp +zjcHzyJKbpQUaeaTKqb6JGSXanHCaW8ud7rTTpH4X65xSoFqb4vd077WpkatBu4w +IjAKBggrBgEFBQcDBKAUBggrBgEFBQcDAgYIKwYBBQUHAwE= +-----END TRUSTED CERTIFICATE----- diff --git a/spec/fixtures/smime/oldsmime2@example.com.csr b/spec/fixtures/smime/oldsmime2@example.com.csr new file mode 100644 index 000000000..191ed2c03 --- /dev/null +++ b/spec/fixtures/smime/oldsmime2@example.com.csr @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIE5DCCAswCAQAwgZ4xJDAiBgkqhkiG9w0BCQEWFW9sZHNtaW1lMkBleGFtcGxl +LmNvbTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVy +bGluMRkwFwYDVQQKDBBFeGFtcGxlIFNlY3VyaXR5MRYwFAYDVQQLDA1JVCBEZXBh +cnRtZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAMWrCkplNiVerePhBjafapLrl2WwpzP8O1Epf9mAUrh0iMKX +2G2Y1ve+JGLHALwUaLnOOeOutPJKDGykWQ+/zV/8U80JmOLt8LyrTLzPun1I7FqG +Akx/hO6skCbDo5erszqhIx2bhLdS43EeANRx7RdzNWEYw9Eqgmcc3MFxOGIPe54E +bnDcm0qzdvIxtNbZll9rBuLlYAzGSs+5+WdbjnxZGhhaAICgmslJWg1Bh8LbuQX1 +YYEsThq/KBnidBOlHSPGMDAj4g075T5A5KwhF6JG2sInSVXWjXktAzxUeZGY4f3d +sVc3lAchw6W25IPPysF8IF3OfO+N+XKZLYZ9x1P+bB1dsdfSUZFUOWRUcpngWPKo +fkSYjUL0JQQgKCx3HmwKQBYhgIeJ35eIPPoWwGdvQTqmveyiYd4TqYYP6f+ay7gI +PQEtfsF18y6kdfN5F3gkv2GKBOvfNP86fnrEftMcQLBxAro+5ceF4ENIBm9xWSgj +EISMoD9TlMtn8bIrKn8p3Zot3KUMG8AT8g1w8sl+43m4hfbgIZFecMtTHGTfTBKO +2KBBnb9oNXh8GAypQYRN8oUuC72DQShxfJ5nFYYPghDX7Mlmiwt0X32gi2YwpPf7 +OI+DQtuUhAGOoGr7oThUY631kASIXSVtDz+pbP2CYwbSlRqEOE+tkps/3nhfAgMB +AAGgADANBgkqhkiG9w0BAQsFAAOCAgEAOBxGS8NkStWWbcDfiuapX1XB49TCpfNZ +23ElMQ7T0vmfg/rQox1UiHPxLFlaJtdz9BoUT2PAv8aN6GCfzafGvxRwf34+EMxH +4YmekUba7Dp9xIdpApEsxA7OtXwMTgL2I47lUR4d1UI4UL1JWhAASAP2ZRiH69AH +Bkch4VFGWQE9f3F3k3oODe+yyQYeGhOvfoUrFLu05JQexYUjGjP3EVN2EVuOJLgW +w15m2wEjJk1CBptAl4AWWCKAvgwvwzHvzkaooUDZyZ2Q/rKeaM4zBu3/8/SXc7lQ +xFLfzvBuZIQX72o6d3NMAVDoQHHpi1PFgLMbbTkaLvzRzF4oLEMqQSBSa4id1uFf +7YYRop5Fu2UdyUX+dyDPZfVCJwIskdOE+AXiIlxDB/VpA1AGVDRwYPJIMjSsCr9m +xybrCZzYzVyWo5um5pVfRaJckcM6XxM60bigLPqietv9FUGfGfTlDHyRbpqJKYZ9 +azgR6QUg5PZQOF6V+iblZEsKxHpqqQT8UPBjyOH5qwq+mCbW4/kneSORPZOposnP +MHZH9lRZ/1LXM5YFrDaFMicKI/h9rRTtFMlH/dAzrvVmz+o5HXew30UipYbZOXZv +1qqvrrW/a8SwMsalEQUHDAnZe8BVI0sLUfOonaXRfzh1CwO7DIWw0XoHaYBkwwxw +bUDcHE3gKFM= +-----END CERTIFICATE REQUEST----- diff --git a/spec/fixtures/smime/oldsmime2@example.com.key b/spec/fixtures/smime/oldsmime2@example.com.key new file mode 100644 index 000000000..6486109f9 --- /dev/null +++ b/spec/fixtures/smime/oldsmime2@example.com.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,422646293BBDFBBD651E2D08160A9FE1 + +rQdENT1nj78GQlJXoriLe+0XYF+GpxwNgA71zGG/LisQsWn9SJmNS5l8eMHPOujO +8yCkyf6N8Khvxd8ry1JvUhqfo0G0fo+LES6IwekaUZz2xGNvUkGOUbq5UTaQ0aa0 +WkLhEXC2kPAyGplU7yOdpF2q4I5RExKzS3eiGTQRfvpzXfUGfB2yFzWs34EYuSxt +VaOiro9UV/HbFV6rhQydpIBLKBRjB7nx3+zGO0C2z4iKpLBqiXpfP/jO7zMqNCfv +ZJ4lLpz+JAi4FxW5fptz7ReqlbZ0NbTt7y00oKzgmsPuvVUy9flg35/QZaYe/PtT +EY/RI6VOkfZqaxM0Kn9DyOf6VKIEtypmga+moiPXppBex8Mm4vMVETy4ZJob7FaM +rDEkKOtCGJpFEnffVWLv31oRw7kfPZJw+Fa1ybHoBYo30uE9RT2EpAQ/ojI/vDTZ +lYcm3xpgNk3YnXvA0Ddw5CVrH4oFHLt583MbxkefsUzCjy59MuP9zn6m95ruxCZY +a0z/BVCA8cUcAq3qAxqtpAKslheFYOessufqNUV/cs3ylpKyCdYd5fPihJGYP3bv +0Dk+KMBRyCvDSzL6CI+f1MhizHbbgzLACNcBEVB17jmX0W4MF2qoqooNfIpz0/Bp +2MLzLgf5n17HgoOHC+vHH5/ZSQhUdUQx2IEJOlNMLRFkqKV791fkVzs4K53wJyWb +JET9rCDUUXlbodbYcg/bPoGqCLazNpXWV0FWCFdCu5IiBCbzH+iFYGptcHuicvdn +p3YJwgMc9RmjjkmHyOlBq3PXyy88k+sCWIQkRqQziT2ZpyCDrwWD4fz5wjKORhoR +74GJYSSQEZoxLv6AU2Ag2DrP3Mc+f2HGrnRGoe1jS96mqJsr8F4dcXZR+nUlconG +vcMCBR6HNtUsQ12VccOlB5LP2V8Fu93/DK+IXV4JnmeGfGrX7yQTN4LcSCR6cyhn +YL0A7HGkCmc5TUak30Yp3eL8oK67BuXcUBxScoVzbNmo5T4ogodeWiuzmJxRq/4Q +F9XDepGAy2nY8wEoEUhIVUjYswNVTcA7k+LMhrh34whJSyaJvg7T8hauRKqP7Fsq +tsn+P6jizRPZJXuea/hNqYtthSpWWhzMlCru3xwVDvEwpe8jcWTvv+wPRHs92mQc +FkTpnbLquoD0zsM8W9JLY+8mwmmt+xU4rox34PkojkSD16tSDx+LpZT+sU2IXTa6 +QbsXNBpEQJEr5reRVEg7IWlRRoFwt/wA1Qaj1PEm6Ys2RSZDnIW99t5atKPZIqsL +TQIMUAhRdWUaaDPYsLvsqyXq9pLotNXx9qzz5bXdPTi833U5Bk8qAOGhaKd+1KQP +MS7vQsHfDJLZRPeZgNiiXbuqNS+A8UsnzcbArA3VmeB/e8z7/meyyBuhy3GDsQcO +4xuBAJebpYEOnWhFA0jCwPt0DG37pInpnUlWQF+tk4StiwCZYZRKuiLeVoK6gd/U +7jhbvTMc2F9ItDvR0SPq8uA2qB2+SNqP0Cw1Fwh7iJuO/cFODzlOJ4ojwcSumxRz +tP1nLMTrrpuC2+T4yKSxVYmEyIwawAq6ttjqS5L/tv4ou4422OzBOcBTPD3U6hdy +pHm0zUjrqi/Z9mIc1vaVwFnBaHnS25ZFlVivdGx1KsCqRrEpm4TF5U4lj93qstvN +oFvvbB0xMMMzx5BQL1uwr5gFX+mneCYfTufjBo+tzUAHcYCp0gwfJOdLeCCo0xUW +gVJZTTidyC4PKexy/8AkFEG7XhMIt7NOhUxQ8fn99ndLo9EG9Hk0qXYN7ytuAAqj +01Dx26NzSLGtTAG5FjVgQFp/lzIQRjFJk/fsqmsCmdTtCAPUMNZ25hkQLcEHnlwl +FYW3pxIbGD+jv36bp8/ZmZ7DOTjL62GRbsYNF4unJyn9iAARdKLC0kfLpRG6Iyp6 +WG9xrtme2F6NmQe1AlTmoMEhGZbUDhnur7s0WkyIhXPoH3N1bv2gBluHJTsIK7po +fO53uqRT8sD78f4JjYW2j0gGjexrQQh/0GNpfDd/MI6wS5+ZkVAHZx62Hyc5ET0i +eWJsMs5FOVEf1xuNvH8Zx4AT6/FkbYMVUeZ+FGPELwt6kyeHlTQigrjbwdxw0JH5 +9Tg/ss3sBG+ik462/LQ8bpiqW+38MMnX6duSFnSWvILr93kdec1hNuYvYWLgQs3R +D7nG2f5u9elwAP559KgjCEMbvtIkHIrSFbaZRAzrp9F8jGJCpW8cIvtEW4KEmc8H +WCrZyi3EyD1NS/61ohUc+1jvMBmQcvahW2LMShgPFmJbZEdRDIt/RE6iLnCU05uB +0+KUaLiJ2ui30Ok/y8cdLs2hq7036cfzZSOFZcbs1JNE8o9Vd2d/rfqw1PVpEVYm +Qtfcjwqc30Zz9rV7ZXyZG5yRvTVHQmUsHjSh53Ty+cTD47lTtuiJnQqG+Ez1WD20 +uAsYiCYo3lJlWQg6WgQZmsGOBexOFl170Dh0yC+AgzLY7SSMUyXFN4eSJoqknmz4 +MrlU+hlmr038OmEXwa3tGzVdGU/D1leYXGw8Oqi9Mx6gD0b1KGgp5zhkR8hvZsp6 +M1h0wjeR3KlJdlOfnw220tCCZlHi/XseqDA50EUCnqb01j+6w5szeHNQcP0TD4GB +dTVCCoD1rvXumhVH4oIITMqZHFdv4Hdw+2li+ag8zPK/ZFgashJMgX3aCX8qlWmG +VP1Lh1ANZVQQ61QjEaiqzvjmRxWj/NDd/oIkBo0FHrcKUqNAU3yapjx5ePSs4D8f +5g+7cO2EmS550SlMm/7XUE5Bm7rEu2v+mCfv8BEl/qB42+abhPtxDhlZJFcET1zE +fuBiiMdWc6lBuI2gWzMDIWcr/tp1qa+XBaWdXq0PUkYz1Fk8n/bQsOO7V9p1JBxF +zGPCmc+REab+MauCSuchSHTtwYo+auBQRVGThg0ftBpiT5+0U2IvwHMVqUbgtknQ +XHeAzLysvzshWbYTP6tB9uLYZbWFXpqvGhbTlv3duae9CEBam8RKlcXWM7gzIqEx +DcpSOCehkpFa9GbxEvoDl1Q4h35SXATs0McDE+Tr3QnEPBu5kKpYh/DPNFWGJBUI +qyfHw8PirVqAgmP79moy5vY32fKnRYt2cC4aNfuUJCr6yGEgrMsaNVpiHWckRMPY +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/oldsmime2@example.com.secret b/spec/fixtures/smime/oldsmime2@example.com.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/oldsmime2@example.com.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/smime1@example.com.crt b/spec/fixtures/smime/smime1@example.com.crt new file mode 100644 index 000000000..4781ff97e --- /dev/null +++ b/spec/fixtures/smime/smime1@example.com.crt @@ -0,0 +1,37 @@ +-----BEGIN TRUSTED CERTIFICATE----- +MIIGVDCCBDygAwIBAgIUAh6m+t/Iz8s0i11uzqr7kDKyFewwDQYJKoZIhvcNAQEL +BQAwgZcxHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoMEEV4 +YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMCAXDTIwMDUyMDEzNDcxMVoYDzIyMjAwNDAyMTM0NzExWjCB +mzEhMB8GCSqGSIb3DQEJARYSc21pbWUxQGV4YW1wbGUuY29tMQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoMEEV4 +YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtUtb +zV02dBuQuR5Ef4zJAOTPMDw1DNl/GkY1MstUpoV2jjJeB1cDj8oq1RHDwBBVNsaL +4BT9pnCmy8YG0JITR85IBmWB454PSVX9lQHpBXwAihj3B/DjGV0phdo+FuYo3c7B +EygQk+RLKvD0jW/EhZ3dYw/lIU2iV+v3kBMi2R50RL5aEyRgp3/QlsViUWfJgnZn +MEHRtFLP6s/NbM6FkPZoMR36jJVL5xVn5I0+RzzsLTbaWWV6hxC2mDh16aBjcwkW +vJXpuFx/HpXMvLOjTgWssToifbqz2SE7Q4ssHnof7Mx6XD+MjCj+sbKoMbfWLSM0 +LUNBqeXhSxGDkT78C7rHzrfnRcqsp2jFpdeMBRRgEYe/2lIfrY0IKQWmfSOr6LOd +9Cs5VxG06nAZ8fF2FM6dw7DqbGXJ2kbD06yc9T9wmdU/TLNuVI4mgTLEojCHUTXG +pnfZtj8JH22lcsRglT3TmQX78UAdXWiyrPt/NoukLbeTTpKqQ2FsfS77N/B4V34/ +uu76y+opnRJiYUD+6R4q/wx5dISRyZjmfbNPLuZjXXEyckVIs2n3YfGYP1F+uDIr +UU6wRLlzBaO6rytzY2IAybp+sMIprs+j63LFMBBnnjBz3WBoE4DVnE6tKEGcDcyT +G27+9/lOsBVKIwpbuTi8PmEgaX6P123/069JMEsCAwEAAaOBjzCBjDAJBgNVHRME +AjAAMAsGA1UdDwQEAwIF4DAdBgNVHQ4EFgQUwdO+DOI19I8VZKUWNb+0CiehEYQw +HwYDVR0jBBgwFoAUDC1CrWIMc++tNKpFXusixDF92GwwHQYDVR0RBBYwFIESc21p +bWUxQGV4YW1wbGUuY29tMBMGA1UdJQQMMAoGCCsGAQUFBwMEMA0GCSqGSIb3DQEB +CwUAA4ICAQCFMaaAoAKMkNd8PhdR0/x+OHo/GXKPzy1mDs82SUx50z3CP4ZeaNpY +y13As8kkuy9apHJuOi3gCzSzJ7tWoSHObl++1knXt+0BVTDo4fAGD3+YZxohcGvZ +ua0uH0Wo38xW6fyNfzuNjCvbDaxW7eGNyUG8Zk6YUNHW9O8M3JzdwvPdBhfrnadN +tzxrClIziA2Z3JZODiSofnhkF0jzFotqSA+pA5lX9SnfOEW2CN20cV4YIKaErau5 +r1qvMe6W+PecyFGA2Ad9C9JvhKx9f14HqKMwM2MviqkAsjac7OzFrZc+Gs3lADiU +p7X9owpj+oVCGxQeBr2wwhfrzzY9emIF8oSVrFXlk9sQubGpH5R1OYBm2B16Qd0K +O46pMRq2flUnyBNqRwrCq6Mk4qsxOs6sn6dUVBiMyL0wqKlDIeYYCg9VTAblzDcj +2ipwzFmDz+wHZHHnLyuGqEId8XmpVtWiUOFZ7BfGpEPFrna8HcV6KOd+yydYmQr0 +6J8f/GnxF2qA6aZFxTFmyPt6rQ+I+deitY4RUGXm+LkXNRzR4wq5Bu+onijk8QWi +ZPhQ7ogwHgkoifMi5kzgUMz/2jF0eS9rBRgX2P9CttaKqRfD4H3hqYYxnjcGWxnZ +iRXhcgQlIAswjM8wJpeRpldOTnoB/xSJ67G6WdL4rpdP1Bh/7uZe1TAiMAoGCCsG +AQUFBwMEoBQGCCsGAQUFBwMCBggrBgEFBQcDAQ== +-----END TRUSTED CERTIFICATE----- diff --git a/spec/fixtures/smime/smime1@example.com.csr b/spec/fixtures/smime/smime1@example.com.csr new file mode 100644 index 000000000..13003027a --- /dev/null +++ b/spec/fixtures/smime/smime1@example.com.csr @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIE4TCCAskCAQAwgZsxITAfBgkqhkiG9w0BCQEWEnNtaW1lMUBleGFtcGxlLmNv +bTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGlu +MRkwFwYDVQQKDBBFeGFtcGxlIFNlY3VyaXR5MRYwFAYDVQQLDA1JVCBEZXBhcnRt +ZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALVLW81dNnQbkLkeRH+MyQDkzzA8NQzZfxpGNTLLVKaFdo4yXgdX +A4/KKtURw8AQVTbGi+AU/aZwpsvGBtCSE0fOSAZlgeOeD0lV/ZUB6QV8AIoY9wfw +4xldKYXaPhbmKN3OwRMoEJPkSyrw9I1vxIWd3WMP5SFNolfr95ATItkedES+WhMk +YKd/0JbFYlFnyYJ2ZzBB0bRSz+rPzWzOhZD2aDEd+oyVS+cVZ+SNPkc87C022lll +eocQtpg4demgY3MJFryV6bhcfx6VzLyzo04FrLE6In26s9khO0OLLB56H+zMelw/ +jIwo/rGyqDG31i0jNC1DQanl4UsRg5E+/Au6x86350XKrKdoxaXXjAUUYBGHv9pS +H62NCCkFpn0jq+iznfQrOVcRtOpwGfHxdhTOncOw6mxlydpGw9OsnPU/cJnVP0yz +blSOJoEyxKIwh1E1xqZ32bY/CR9tpXLEYJU905kF+/FAHV1osqz7fzaLpC23k06S +qkNhbH0u+zfweFd+P7ru+svqKZ0SYmFA/ukeKv8MeXSEkcmY5n2zTy7mY11xMnJF +SLNp92HxmD9RfrgyK1FOsES5cwWjuq8rc2NiAMm6frDCKa7Po+tyxTAQZ54wc91g +aBOA1ZxOrShBnA3Mkxtu/vf5TrAVSiMKW7k4vD5hIGl+j9dt/9OvSTBLAgMBAAGg +ADANBgkqhkiG9w0BAQsFAAOCAgEAo/nttSsH76fEfcpRq1WkYqvvi8ZEOPa4wehN +CqRQP2oiVuZyrDVFiDboyqAoCd0+AfIe5UQFaQ/PW1CFeVBRFStPJWxKanX/wlHA +vOyLxsH4jgDoIzH/HOMWlpUb7jaarGhkLxYJwgDoM0YznDGGZPb8GiewvL3psyPf +SKQAUWc9tVVdg0D9WhqSoseP860azx6dvKgisP2HDbZHYJkdIfCjYl6N913vhLKa +tg+FcT8DFqepH1LBUpi6Tj2SI2OYCgioY9mOG7jZn8EEYl1RH2GKj6uGaBULINBr +fWtWFsLkadnMFuR+6oIOHnisHdGNWZqJvy1YsEb/mu4yR27cgGmud8WSpeBRWPVa ++OUR1DnJgTLTcJLYnmIRKRloND58mqiswWb/IHxQgGu2v7rAYy4IxGapf+bsybVO +fgjk30yNKM2yH3ctos0yijYmt5Yj7k4sEFQl635Nwsz20qreQJ13pCQBmMg3xFNU +xpioO8PJFLrXVTQzv8BU5a8F26izDzM2RDVG7yhgrGoQYm66L6MEf5db/SNtx19I +Nmtepqdlj8vQ9OIK+MDzTT0UVEiUQJWm38lKjXFan+o6h3NkYjB+XHzLpU8q6azn +VTDmduZqCjuDU8woKWRAw95PrB3lW3TnMqnux5Ls1811tUyN1UM+JFG0EqVkEPG3 +nnV42u0= +-----END CERTIFICATE REQUEST----- diff --git a/spec/fixtures/smime/smime1@example.com.key b/spec/fixtures/smime/smime1@example.com.key new file mode 100644 index 000000000..6f569ab8a --- /dev/null +++ b/spec/fixtures/smime/smime1@example.com.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,C02E7FD1CD5C829F69D753D19B84384A + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/smime1@example.com.secret b/spec/fixtures/smime/smime1@example.com.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/smime1@example.com.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/smime2@example.com.crt b/spec/fixtures/smime/smime2@example.com.crt new file mode 100644 index 000000000..0fe5a6b63 --- /dev/null +++ b/spec/fixtures/smime/smime2@example.com.crt @@ -0,0 +1,37 @@ +-----BEGIN TRUSTED CERTIFICATE----- +MIIGVDCCBDygAwIBAgIUAh6m+t/Iz8s0i11uzqr7kDKyFe0wDQYJKoZIhvcNAQEL +BQAwgZcxHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoMEEV4 +YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMCAXDTIwMDUyMDEzNDcxMloYDzIyMjAwNDAyMTM0NzEyWjCB +mzEhMB8GCSqGSIb3DQEJARYSc21pbWUyQGV4YW1wbGUuY29tMQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoMEEV4 +YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAshnv +h0UmOnz+vUBfB6baNJRjihj9amSqMRQpQLDOUVOMwU5kaHIKiFZ2nU00eLzB7eRF +/UEO7rpFILoq49TyZ+LogpH2Z11ZY4hw6ZVcX0qJ3Aq6X4Huqt0plq4BPdBO8Mdn +dgcgGU94+wKRFWFrL7GiiZMFbc0zZfaIMJY35mhWJfc0Sj8Y6tXpfSQHP9WANPII +8l1YYybv/8nCJCZtxnERTlHoFXay5IX9nr0WSGL+uv038dqClModT3ORp23jy07w +nXmWRURXqjBOb0ZbH0YXrj1axi0vc42E1cBUFLzhxDuD97h7T8a/9QbY8wJ5fKcy +pMU8DZZeMteMbQZBqVY53c42iqj8dkVkvoqVmtTvGbUy6gxsQHkhoZHLFgrWJdLr +YPIBev1DrsZwECgPfkaVn8tFnJpehxptkqONXaz1cp2sbI2oADAlnwK4yeWEVbTs +tKIULzSJidYhBoK7bsede3SsMnGWjwzPEfIQxQj0EpFQEMA+0Egy6l4OQ/96AAMp +tr5oap4yt+N1cpRHoIj8bLg7zzoAIgNceXM4+zPDeU7oa+6DwVM0zB4PnvXvh9gn +W5LKrZZekDgCSEpmehBYReJLbsUoGDCWJ6U5skRBDThsV/On4p05psKfKtNR4fzS +hco1kL06GlXrafWp7wPoh/aLHS9xxzf3YZ3KdvMCAwEAAaOBjzCBjDAJBgNVHRME +AjAAMAsGA1UdDwQEAwIF4DAdBgNVHQ4EFgQUcy/mPtRlG+eTit/lAYQkcymZNCUw +HwYDVR0jBBgwFoAUDC1CrWIMc++tNKpFXusixDF92GwwHQYDVR0RBBYwFIESc21p +bWUyQGV4YW1wbGUuY29tMBMGA1UdJQQMMAoGCCsGAQUFBwMEMA0GCSqGSIb3DQEB +CwUAA4ICAQAMdXmNlVP8XRs6kYF2iHZnvf0Po5RWI8/qM+3s5Ljd4TyvIM8T/s/v +b7ayKJlOzZ4c5o+DE65kTDRT6LRKhWHgjQ8jzAXEb71KK1fdK+ZQ+p+vgyIOmWq6 +J0gqcsX9V77cx/Gf2NSNeVPAONggqToapCXyI16uHnaiPMx/Qf4ivXmvaTNw3bVs +U5hqYrLcTxQYR03DgahcGj/ukWdtkpidLz0ex3xpgeiA0Ca0ucExrsDCWv/I/JS4 +KHRTxkI9TMSR28KpC3uI7m+OLMohTTNGD8dcD8kIiMBL6Q8DvXYs2aYN4ZBEAxnQ +qcgWYfBkmnONm2Ze3hXBwaJDFd87mXqMpAY+Sck6aRENxG+vj255VRI7rTgw19hM +G6EEbu9pwOfpmVmrkTmNByy0gXPXLcq0zqPkSsmdPEUdj7tbVAhpnhxW7hPfr7HJ +O7uc29UTn7Oby9pZkbAvLtJuPidQY69OpNsY878h8UBbCqo4unuZqLO+7PXrfGn2 +T4suSBLUznInTEgsHJNxoKXxTcpXaMwts7DsCMl9KsdF9ea0sVZhGUZ8Yqfxu/eX +4zV3UiapXRl6mS9FCSe7CLArxok853z8RlbDjS5M0wmKvRnoYmQi0Wz6E/hN/uMW +Qs6R7xOlxvkQrLkbPPIKnuP72xOWSGPY8AU2Hu9AfYNkhZ30Lyo6mTAiMAoGCCsG +AQUFBwMEoBQGCCsGAQUFBwMCBggrBgEFBQcDAQ== +-----END TRUSTED CERTIFICATE----- diff --git a/spec/fixtures/smime/smime2@example.com.csr b/spec/fixtures/smime/smime2@example.com.csr new file mode 100644 index 000000000..863dacb4f --- /dev/null +++ b/spec/fixtures/smime/smime2@example.com.csr @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIE4TCCAskCAQAwgZsxITAfBgkqhkiG9w0BCQEWEnNtaW1lMkBleGFtcGxlLmNv +bTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGlu +MRkwFwYDVQQKDBBFeGFtcGxlIFNlY3VyaXR5MRYwFAYDVQQLDA1JVCBEZXBhcnRt +ZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALIZ74dFJjp8/r1AXwem2jSUY4oY/WpkqjEUKUCwzlFTjMFOZGhy +CohWdp1NNHi8we3kRf1BDu66RSC6KuPU8mfi6IKR9mddWWOIcOmVXF9KidwKul+B +7qrdKZauAT3QTvDHZ3YHIBlPePsCkRVhay+xoomTBW3NM2X2iDCWN+ZoViX3NEo/ +GOrV6X0kBz/VgDTyCPJdWGMm7//JwiQmbcZxEU5R6BV2suSF/Z69Fkhi/rr9N/Ha +gpTKHU9zkadt48tO8J15lkVEV6owTm9GWx9GF649WsYtL3ONhNXAVBS84cQ7g/e4 +e0/Gv/UG2PMCeXynMqTFPA2WXjLXjG0GQalWOd3ONoqo/HZFZL6KlZrU7xm1MuoM +bEB5IaGRyxYK1iXS62DyAXr9Q67GcBAoD35GlZ/LRZyaXocabZKjjV2s9XKdrGyN +qAAwJZ8CuMnlhFW07LSiFC80iYnWIQaCu27HnXt0rDJxlo8MzxHyEMUI9BKRUBDA +PtBIMupeDkP/egADKba+aGqeMrfjdXKUR6CI/Gy4O886ACIDXHlzOPszw3lO6Gvu +g8FTNMweD57174fYJ1uSyq2WXpA4AkhKZnoQWEXiS27FKBgwlielObJEQQ04bFfz +p+KdOabCnyrTUeH80oXKNZC9OhpV62n1qe8D6If2ix0vccc392GdynbzAgMBAAGg +ADANBgkqhkiG9w0BAQsFAAOCAgEAo8vNeH4f/+3vSI8cs/ZKSkq8kkrmYdqFKuJh +cQK7XoE3bgRB0ahKFoR9vRTE+l9EZZEhQPzevlV44RXmZ4z4KVmm7uLHndaEzW3h +Pe/gQxXO+N9DJKYYtzFij9D/fevWz55aUtgrQg5CrF91B7kZVILWxNNX4EObwvqF +CmaIbOU1poGw2lI/weiGe4bcAbww9c4rNsXaxgqxSpWrtHGvAnF2JDayWVREJgi3 +xXtCrh7icg8uAzvTLHzUUho/yABv+HhgAZvBwelmD89QW8AUBwtYxyEEP/LQOgcq +VOGJb89rIHJzOHUZTF+dtEfEcjarxghlw5TcoMwrCA9sc49QrS8ksJYkpMKyG5EC +Sjyjk8S7tdm7TvHJ13EP14770ZTa1Rrpo1ENfOkmqbxbjzuQ2RRxRRunjGVwbIsX ++3FBNMRb6bo/kv2D5NYBahQA/KVHTSfZpX34P3b1XawmP95/jqW2fMyXZ5LGEJ57 +hw97dSNA5RBb4Dgd6ompmstaT3U7iLqFgnJq5Br60YLJASm0f0ou8ock0hoJva3y +8XA0687jDGmtp7wWt3GQCc06SZMtBFMrJIezih32C1V/8O8b4oRoChV+2DovZz48 +QpMOzXHi0hq1D70vw7kVOkeekMTOj3Pwl4e2nXgjwI3gj29bel2vNN7QkGDQw4RD +Yhv55HQ= +-----END CERTIFICATE REQUEST----- diff --git a/spec/fixtures/smime/smime2@example.com.key b/spec/fixtures/smime/smime2@example.com.key new file mode 100644 index 000000000..e20b5e144 --- /dev/null +++ b/spec/fixtures/smime/smime2@example.com.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,E635B30D63879480FEACF294E0B57BB7 + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/smime2@example.com.secret b/spec/fixtures/smime/smime2@example.com.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/smime2@example.com.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/smime3@example.com.crt b/spec/fixtures/smime/smime3@example.com.crt new file mode 100644 index 000000000..76fa905a0 --- /dev/null +++ b/spec/fixtures/smime/smime3@example.com.crt @@ -0,0 +1,37 @@ +-----BEGIN TRUSTED CERTIFICATE----- +MIIGVDCCBDygAwIBAgIUAh6m+t/Iz8s0i11uzqr7kDKyFe4wDQYJKoZIhvcNAQEL +BQAwgZcxHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoMEEV4 +YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMCAXDTIwMDUyMDEzNDcxNFoYDzIyMjAwNDAyMTM0NzE0WjCB +mzEhMB8GCSqGSIb3DQEJARYSc21pbWUzQGV4YW1wbGUuY29tMQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoMEEV4 +YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA25Cw +6f4svFHkmWFBQzbg+3hzeCb9zgLOr4aIlq9UrNDZzO4ofhPlqInmzIIxgNgbcClM +x7ho9ArqLg0D/Nwt0DKCdKQ9Ft/BZk4Wo56DJu43KTbxyhaTK2cV1UYkvBuAwZHC +UrNgr8RfvSZsSWKOgqRCTozL/efBcv6zV9KLrN7vjboE0EhTA8MSTeruNdpjTtRV +v0BuF+oLTV1qrsNL63nmTg0MEZcqT0LAyAojfXd3vqoADDCOvY46SgMv5bMCTkR2 +9Vu5UZqAWfixyjj6nul29yyEhEbM2Vq8B5fVeHDgiCLcw2BIlYRbiQehdPjrMICz +HqERM2qeQjs29axE4kedjM6xn6lFTZoA75jUmEXZEK5srDhAU+bnus3mD6HbG+j2 +9ZIAvMnnz1vk8PN4rva8ImRUqCiocV3jblMvqHqOzWxdP1w1vN+jQaNxxkO3Oi5W +jZXPRNSfTx1sIrCaQkclmltGpIAN+qHhzcWQbnOszO0pobIAc6FpBHtjIS+8egZU +8yY98LBdgFPbnZmblEiHqlBfG+XfvxFveiIe38X5qLhyyAx2KyJaDGoUkHIXrStn +3KbdAmlRalZVAxwyXD+Jlsr3qNkiZJYOaAQK1fVxjO+ejNf3m8FxtdDa/ItBR7E1 +PjH7KehKr9QyFIhndKTyhDXA62lWqh/qzu5GPXUCAwEAAaOBjzCBjDAJBgNVHRME +AjAAMAsGA1UdDwQEAwIF4DAdBgNVHQ4EFgQUFFnnknu6xBErhaGX9FrgU+DaRx8w +HwYDVR0jBBgwFoAUDC1CrWIMc++tNKpFXusixDF92GwwHQYDVR0RBBYwFIESc21p +bWUzQGV4YW1wbGUuY29tMBMGA1UdJQQMMAoGCCsGAQUFBwMEMA0GCSqGSIb3DQEB +CwUAA4ICAQBTLrsJ5O0wZmwq3oiB57G+yjfOzvZqrl+pFKwc+VXbCYECc59o1yfh +8CFCnxsbKuG60ZwZlhuhyl2AY0GBlNNAaLbvsmInoUE7NZkiaA3wsme9DBZtoLiU ++G191veUMcvW91k4PgALckOIU8g430Qb4xjMfObz9wBqPNqxRr/JLeBYj04jhjVR +Zr0fC1q6UcMam8ZTA2SmGEgE50k0J9iBPBaRE/z6gPn04Z2PH3xD+7zA9/3QJym2 +FHEnKLRQoIO0Y3xp/h3z/TgeEUdR/Ix9IkftqcQVd60Bm/x+AxBnMNrFme0bRTh7 +ktdi9MVZT/eGYQb9Abw8T4Y+5e8J84zhoV3Ykc2WmIAPsoYWABbcP7MjDQ02S9q5 +rk7aL6ZQShp0ZYlcyrBLkWMgjW9U8w2SHvgwZv2fi9yq5Q2Or1POIJzpHFr1Vbdx +p4bwLiQMSM5sAZoRBMpvSYQRXRoW0z3qL+SvOGO0dIJPmTgpSYBEDoUiue85wywg +qt5t5Kajf+Xb+z290b/s2+r6a/7AuZYNuK2pg4hsCYQETOWIU4HobhpFO/oQF6/i +1ubKJmEc3kkCbDDnqdBrembSuDWGahueveYLHu0fkEFYTnFZCk/t/KWJ15OEHgsI +VYldqaedsdvA7Hk52EIcW4rLnLKMKvNREHzDbaL3SIn+H60kC6ZrtDAiMAoGCCsG +AQUFBwMEoBQGCCsGAQUFBwMCBggrBgEFBQcDAQ== +-----END TRUSTED CERTIFICATE----- diff --git a/spec/fixtures/smime/smime3@example.com.csr b/spec/fixtures/smime/smime3@example.com.csr new file mode 100644 index 000000000..ac58feb1a --- /dev/null +++ b/spec/fixtures/smime/smime3@example.com.csr @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIE4TCCAskCAQAwgZsxITAfBgkqhkiG9w0BCQEWEnNtaW1lM0BleGFtcGxlLmNv +bTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGlu +MRkwFwYDVQQKDBBFeGFtcGxlIFNlY3VyaXR5MRYwFAYDVQQLDA1JVCBEZXBhcnRt +ZW50MRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBANuQsOn+LLxR5JlhQUM24Pt4c3gm/c4Czq+GiJavVKzQ2czuKH4T +5aiJ5syCMYDYG3ApTMe4aPQK6i4NA/zcLdAygnSkPRbfwWZOFqOegybuNyk28coW +kytnFdVGJLwbgMGRwlKzYK/EX70mbElijoKkQk6My/3nwXL+s1fSi6ze7426BNBI +UwPDEk3q7jXaY07UVb9AbhfqC01daq7DS+t55k4NDBGXKk9CwMgKI313d76qAAww +jr2OOkoDL+WzAk5EdvVbuVGagFn4sco4+p7pdvcshIRGzNlavAeX1Xhw4Igi3MNg +SJWEW4kHoXT46zCAsx6hETNqnkI7NvWsROJHnYzOsZ+pRU2aAO+Y1JhF2RCubKw4 +QFPm57rN5g+h2xvo9vWSALzJ589b5PDzeK72vCJkVKgoqHFd425TL6h6js1sXT9c +Nbzfo0GjccZDtzouVo2Vz0TUn08dbCKwmkJHJZpbRqSADfqh4c3FkG5zrMztKaGy +AHOhaQR7YyEvvHoGVPMmPfCwXYBT252Zm5RIh6pQXxvl378Rb3oiHt/F+ai4csgM +disiWgxqFJByF60rZ9ym3QJpUWpWVQMcMlw/iZbK96jZImSWDmgECtX1cYzvnozX +95vBcbXQ2vyLQUexNT4x+ynoSq/UMhSIZ3Sk8oQ1wOtpVqof6s7uRj11AgMBAAGg +ADANBgkqhkiG9w0BAQsFAAOCAgEAKbc3Mq4Pb5EXxpvJ1J4NF2t+ZrcH2JhT10// +d0Ll6oxBzCfs0fDO5T1wUC8sz/wKJ/kV+Vd9hB4e6YVX+OLxHyoXwsyUlHf5w7y2 +B+Kmr+I1gH7Sbo8Hvu9P6+ZEI+kEQcOZlI/mymjAIXy5oBqSk2kP9zsIKkfHf3P/ +LrGBShbveJ7wFD6QPXjl0/xwDvaGx32db7iFe8cmV/RK00jyJ91WYwn7jrKL/hJk +SessFGbVGj6hGLrfv6SwuinQDWAhBqT52RUOZcGj+rC7M+Y3wGl5pwWb0qMQzOi3 +r2OE5etGt3v5ZWbs3qRZXZCQ83uXTfBKGENnAcknK7BfPSCfZVcx9Im7rDti10kN +gfh4MuuKeBTVr2asmhi+6qfW7gNF05uCZ/a7i6vSjpDNr6PhUbQ9JoeD9d7GnOLM +QqssR0S0VCU60hGw7bH4PWIxRuZYo+j5WBC/LA3scFEGlk/cCv52lIv0H4GIajus +oR1UzywntK/1GNOwO7LHePweranTP5KMdNWnYYcw43ND9hvhTZwKapKhM2TidXvT +F+mCwU5o+9CDk8nRcER1DFjyrFlzQg1i2QqyKCrKUiZ3GKj++RdzhiEypyMwS3co +fdTlN3L040+wY9v7FVItNwkhke3i9JIjxrgLHMHmq24yEtYHYXaCnQBIxNBL0OG6 +TeYaIuo= +-----END CERTIFICATE REQUEST----- diff --git a/spec/fixtures/smime/smime3@example.com.key b/spec/fixtures/smime/smime3@example.com.key new file mode 100644 index 000000000..a2c968863 --- /dev/null +++ b/spec/fixtures/smime/smime3@example.com.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,B60A858865943D0BF01E8FB0E2DF8999 + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/smime3@example.com.secret b/spec/fixtures/smime/smime3@example.com.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/smime3@example.com.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/fixtures/smime/smimedouble@example.com.crt b/spec/fixtures/smime/smimedouble@example.com.crt new file mode 100644 index 000000000..098923d66 --- /dev/null +++ b/spec/fixtures/smime/smimedouble@example.com.crt @@ -0,0 +1,39 @@ +-----BEGIN TRUSTED CERTIFICATE----- +MIIGojCCBIqgAwIBAgIUAh6m+t/Iz8s0i11uzqr7kDKyFe8wDQYJKoZIhvcNAQEL +BQAwgZcxHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMQswCQYDVQQGEwJE +RTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xGTAXBgNVBAoMEEV4 +YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMCAXDTIwMDUyMDEzNDcxNVoYDzIyMjAwNDAyMTM0NzE1WjCB +oDEmMCQGCSqGSIb3DQEJARYXc21pbWVkb3VibGVAZXhhbXBsZS5jb20xCzAJBgNV +BAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEZMBcGA1UE +CgwQRXhhbXBsZSBTZWN1cml0eTEWMBQGA1UECwwNSVQgRGVwYXJ0bWVudDEUMBIG +A1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDis/CvhZWB56xP8x8fd1B6ab3FXeSIcxT/OY4hgQbx1aJ75f6E0jbsJMGdXCiO +HWE/gRUkkJfnWbO1o/2aZoIb6jUmQnDtPruMAcAZTfcXJwwISJGdrePczLOf1hOi +93N7Jobwq25KJK5zLhggfniWrc7zUIQfOWCAvAqSKmd8rRSYVSZ2oBzwySIquVee +EmYOOfu6nJpr3fFF96u2LFTOcPTqopjdJo7M5pRhwd/42jGpRQMBwcrJyimNhNLH +CfcVblV6NFYcpueNrQfYbnfSU3NW45jzeekrOIBu2hXJ5I+k7OB3RflV82yGswa6 +VMfeyWg6LJ2B+89AFuFBS3KhRYXIyzYTz4YekXMfwn+kZbbidexhW6vTJwk6ecPj +TOGRsW+cuQrrowldBSwT46t7FjDZaDwDNsSyX7Wf0NXSVB/9VH8+Oigy/pW92EQy +OlPsSK20MFm9k0h0oc7GcsSwsKbc8HRHEjnIT7r0GSToCZ8xLevoRM5u56T+ok5E +fATCtz5hbSPW5ZUJy97KPHxzoqKgnD1IxyS000A1pmlNRHJ4exzh1YkGEBunPUHp +7Yda3WDB6neZy00exN6LFQJhx0+CV+KMCJxhZsheLH54/55TxIla1djgoToIVAVv +hQP+ST31704TGyAws68Hz/ZZHLZUL+56w/sULOJgZg3BdwIDAQABo4HYMIHVMAkG +A1UdEwQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdDgQWBBTT9j2ZNIZiBXpGLd2iEmON +biSFmzAfBgNVHSMEGDAWgBQMLUKtYgxz7600qkVe6yLEMX3YbDATBgNVHSUEDDAK +BggrBgEFBQcDBDBmBgNVHREEXzBdhwTAqAAXhwTAqAAqgRdzbWltZWRvdWJsZUBl +eGFtcGxlLmNvbYEWc21pbWVkb3VibGVAZXhhbXBsZS5kZaAeBgMqAwSgFwwVc29t +ZSBvdGhlciBpZGVudGlmaWVyMA0GCSqGSIb3DQEBCwUAA4ICAQBRd1Itr7HtXe2X +K5KfnnWnFIzcqBmwEX6Za2XuYfGtMcM0vyc45Dvns/15TTu5QswZS0DavdYg3j65 +WS1Gcf7AOkUgnBJRosS2GJwmiV4lz/UG5mCeXwyogPYU2ZYpMPst2KfpbFj7ZNZx +wxqW7iKIiEdw7RsiXkSQZ4Oc0z9peex7t+pHeAimlHCTaHfB/L09kwpO+aHmaC5S +V7wY230HQQjMpyE3U9XyCXvaHPMQkT53uMPBHw7ZE7R9CxXMnUvsjfUG9EVUj2Z6 +vg5dRCimI04o/j2mp6KHt2FCSaebgzG5obBoOJOC0FusLQ6bLs0zRmn4rwpeBTs7 +sEt/nCGmmiUKyMMVrUZtobF0vo9rV3f5CxeBZMooamM6DV/QbIGvzDOvp4eImHXw +/ppHoKAg8CZxIuqLF333Z11iCfq2/lRYD4/jKcj3izJ2sobuYifm5nPCH5k7gtyR +sXqmcdcU/O3VVSQ1nENMal6BPo7sFo3t4iT/rS9j2OfS2QqcaQeOxVe9TlJ9lbsh +gOZQOPWPiCikR/lWPwW7MUN0IUpkqlfyPlMWiQ5QpTbfOTLtfY5rTrMBdBOZ5k7c +U5u8dZBf3JeUjroDVuIHzzrebVby7Fywo8pMvZBqhJ2BWiNaIc0NuTb5KnYZzKAN +VJ6ELG5wAciX5b/KlHI1PWLgmMjdUjAiMAoGCCsGAQUFBwMEoBQGCCsGAQUFBwMC +BggrBgEFBQcDAQ== +-----END TRUSTED CERTIFICATE----- diff --git a/spec/fixtures/smime/smimedouble@example.com.csr b/spec/fixtures/smime/smimedouble@example.com.csr new file mode 100644 index 000000000..93c3c93a7 --- /dev/null +++ b/spec/fixtures/smime/smimedouble@example.com.csr @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIE5jCCAs4CAQAwgaAxJjAkBgkqhkiG9w0BCQEWF3NtaW1lZG91YmxlQGV4YW1w +bGUuY29tMQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZC +ZXJsaW4xGTAXBgNVBAoMEEV4YW1wbGUgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERl +cGFydG1lbnQxFDASBgNVBAMMC2V4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4rPwr4WVgeesT/MfH3dQemm9xV3kiHMU/zmOIYEG8dWi +e+X+hNI27CTBnVwojh1hP4EVJJCX51mztaP9mmaCG+o1JkJw7T67jAHAGU33FycM +CEiRna3j3Myzn9YTovdzeyaG8KtuSiSucy4YIH54lq3O81CEHzlggLwKkipnfK0U +mFUmdqAc8MkiKrlXnhJmDjn7upyaa93xRfertixUznD06qKY3SaOzOaUYcHf+Nox +qUUDAcHKycopjYTSxwn3FW5VejRWHKbnja0H2G530lNzVuOY83npKziAbtoVyeSP +pOzgd0X5VfNshrMGulTH3sloOiydgfvPQBbhQUtyoUWFyMs2E8+GHpFzH8J/pGW2 +4nXsYVur0ycJOnnD40zhkbFvnLkK66MJXQUsE+OrexYw2Wg8AzbEsl+1n9DV0lQf +/VR/PjooMv6VvdhEMjpT7EittDBZvZNIdKHOxnLEsLCm3PB0RxI5yE+69Bkk6Amf +MS3r6ETObuek/qJORHwEwrc+YW0j1uWVCcveyjx8c6KioJw9SMcktNNANaZpTURy +eHsc4dWJBhAbpz1B6e2HWt1gwep3mctNHsTeixUCYcdPglfijAicYWbIXix+eP+e +U8SJWtXY4KE6CFQFb4UD/kk99e9OExsgMLOvB8/2WRy2VC/uesP7FCziYGYNwXcC +AwEAAaAAMA0GCSqGSIb3DQEBCwUAA4ICAQCGOMY/DJR/qgNq4E+KPJnCEBWMoJ3i +uIwYiKPn7Wcl2WRNdJim78bCSJkquUkJHmDcLOnHU4GeBgaNZs1do18S9SUsUKtn +zrPkoAVFGE9MN6q4e7/J7DOh0qluV3qd0jHmCluDNGF+bAorYx7nx+dg7AZtZ7jE +qVCnuMpf9pXl4cOpwb+KbvPcwcUTZ/r53yh/2o9gqqojzLvC9+f1wS12qpV1aem3 +kQlR9YZX+QUJtP8X9/IfmgGH1YO9XSDwHL4TnkW9oPZR70cv70rtd8StSSfLS/Er +sIpXMiU5sbqYu/Dh1gxxCJ9Rgaesc8jGtfiux8j+3Pg4YHRyh7dV0nQPzRxZg5zA +KqLLE7erq0AwO/bq3EFlOkknd4Ewm8i50EjL8hu9zKbdsvfBc8m2ox/V9gUtLPFd +k7xa5uyZkTemi20Oe3rWiuBnRHcCGkqmwLJQ3Dn1+gFuVoeAKi5igFlIq+HipFS2 +ljLpL9gBaVrvoLH3PZ8Kl+cToXvTKZ5xQVBsXn3qg/owNqtFJnY9zel0AuTzP9Wo +xZIMBn0E+doXtRtIPxRFGgzV9fBtfaJlV85kdXPMuO9rJV6WizpNdbTI7UNIkHix +d/VZpZ94hv4yaQZkYrrxsiuD7MW/CNlsBNde2JwIV0anNEi7EPXlAvS0GrF8HiT1 +J9wg4NQrmLj5kQ== +-----END CERTIFICATE REQUEST----- diff --git a/spec/fixtures/smime/smimedouble@example.com.key b/spec/fixtures/smime/smimedouble@example.com.key new file mode 100644 index 000000000..f27b0fa18 --- /dev/null +++ b/spec/fixtures/smime/smimedouble@example.com.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,AEB75ED78474CDCBB6810500E499387A + +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/smime/smimedouble@example.com.secret b/spec/fixtures/smime/smimedouble@example.com.secret new file mode 100644 index 000000000..81c545efe --- /dev/null +++ b/spec/fixtures/smime/smimedouble@example.com.secret @@ -0,0 +1 @@ +1234 diff --git a/spec/lib/secure_mailing/smime_spec.rb b/spec/lib/secure_mailing/smime_spec.rb new file mode 100644 index 000000000..1f4933279 --- /dev/null +++ b/spec/lib/secure_mailing/smime_spec.rb @@ -0,0 +1,534 @@ +require 'rails_helper' + +RSpec.describe SecureMailing::SMIME do + before do + Setting.set('smime_integration', true) + end + + let(:raw_body) { 'Some text' } + + let(:system_email_address) { 'smime1@example.com' } + let(:customer_email_address) { 'smime2@example.com' } + + let(:sender_certificate_subject) { "/emailAddress=#{sender_email_address}/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com" } + let(:recipient_certificate_subject) { "/emailAddress=#{recipient_email_address}/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com" } + + let(:expired_email_address) { 'expiredsmime1@example.com' } + + let(:ca_certificate_subject) { '/emailAddress=ca@example.com/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com' } + + def build_mail + Channel::EmailBuild.build( + from: sender_email_address, + to: recipient_email_address, + body: raw_body, + content_type: 'text/plain', + security: security_preferences + ) + end + + describe '.outgoing' do + + shared_examples 'HttpLog writer' do |status| + + it "logs #{status}" do + expect do + build_mail + rescue + # allow failures + end.to change(HttpLog, :count).by(1) + expect(HttpLog.last.attributes).to include('direction' => 'out', 'status' => status) + end + end + + let(:sender_email_address) { system_email_address } + let(:recipient_email_address) { customer_email_address } + + context 'without security' do + let(:security_preferences) do + nil + end + + it 'builds mail' do + expect(build_mail.body).not_to match(SecureMailing::SMIME::Incoming::EXPRESSION_SIGNATURE) + expect(build_mail.body).not_to match(SecureMailing::SMIME::Incoming::EXPRESSION_MIME) + expect(build_mail.body).to eq(raw_body) + end + end + + context 'signing' do + + let(:security_preferences) do + { + type: 'S/MIME', + sign: { + success: true, + }, + encryption: { + success: false, + }, + } + end + + context 'private key present' do + + before do + create(:smime_certificate, :with_private, fixture: system_email_address) + end + + it 'builds mail' do + expect(build_mail.body).to match(SecureMailing::SMIME::Incoming::EXPRESSION_SIGNATURE) + end + + it_behaves_like 'HttpLog writer', 'success' + + context 'expired certificate' do + + let(:system_email_address) { expired_email_address } + + it 'raises exception' do + expect { build_mail }.to raise_error RuntimeError + end + + it_behaves_like 'HttpLog writer', 'failed' + end + end + + context 'no private key present' do + before do + create(:smime_certificate, fixture: system_email_address) + end + + it 'raises exception' do + expect { build_mail }.to raise_error RuntimeError + end + + it_behaves_like 'HttpLog writer', 'failed' + end + end + + context 'encryption' do + + let(:security_preferences) do + { + type: 'S/MIME', + sign: { + success: false, + }, + encryption: { + success: true, + }, + } + end + + context 'public key present' do + before do + create(:smime_certificate, fixture: recipient_email_address) + end + + it 'builds mail' do + mail = build_mail + + expect(mail['Content-Type'].value).to match(SecureMailing::SMIME::Incoming::EXPRESSION_MIME) + expect(mail.body).not_to include(raw_body) + end + + it_behaves_like 'HttpLog writer', 'success' + + context 'expired certificate' do + + let(:recipient_email_address) { expired_email_address } + + it 'raises exception' do + expect { build_mail }.to raise_error RuntimeError + end + + it_behaves_like 'HttpLog writer', 'failed' + end + end + + context 'no public key present' do + + it 'raises exception' do + expect { build_mail }.to raise_error ActiveRecord::RecordNotFound + end + + it_behaves_like 'HttpLog writer', 'failed' + end + end + end + + describe '.incoming' do + + shared_examples 'HttpLog writer' do |status| + + it "logs #{status}" do + expect do + mail + rescue + # allow failures + end.to change(HttpLog, :count).by(2) + expect(HttpLog.last.attributes).to include('direction' => 'in', 'status' => status) + end + end + + let(:sender_email_address) { customer_email_address } + let(:recipient_email_address) { system_email_address } + + context 'signature verification' do + + let(:allow_expired) { false } + + let(:security_preferences) do + { + type: 'S/MIME', + sign: { + success: true, + allow_expired: allow_expired, + }, + encryption: { + success: false, + }, + } + end + + context 'sender certificate present' do + + before do + create(:smime_certificate, :with_private, fixture: sender_email_address) + end + + let(:mail) do + smime_mail = build_mail + + mail = Channel::EmailParser.new.parse(smime_mail.to_s) + SecureMailing.incoming(mail) + + mail + end + + it 'verifies' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be true + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq(sender_certificate_subject) + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil + end + + it_behaves_like 'HttpLog writer', 'success' + + context 'expired' do + + # required to build mail with expired certificate + let(:allow_expired) { true } + let(:sender_email_address) { expired_email_address } + + it 'verifies with comment' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be true + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to include(expired_email_address).and include('expired') + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil + end + + it_behaves_like 'HttpLog writer', 'success' + end + end + + context 'no sender certificate' do + + let!(:sender_certificate) { create(:smime_certificate, :with_private, fixture: sender_email_address) } + + let(:mail) do + smime_mail = build_mail + mail = Channel::EmailParser.new.parse(smime_mail.to_s) + + sender_certificate.destroy! + + SecureMailing.incoming(mail) + + mail + end + + it 'fails' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq('Unable to find certificate for verification') + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil + end + + context 'public key present in signature' do + + let(:not_related_fixture) { 'smime3@example.com' } + let!(:not_related_certificate) { create(:smime_certificate, fixture: not_related_fixture) } + + context 'not related certificate present' do + + it 'fails' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq('Unable to find certificate for verification') + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil + end + + it_behaves_like 'HttpLog writer', 'failed' + + context 'CA' do + + let(:not_related_fixture) { 'expiredca' } + + it 'fails' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq('Unable to find certificate for verification') + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil + end + + it_behaves_like 'HttpLog writer', 'failed' + end + end + + context 'usage not prevented' do + let(:not_related_certificate_subject) { "/emailAddress=#{not_related_fixture}/C=DE/ST=Berlin/L=Berlin/O=Example Security/OU=IT Department/CN=example.com" } + + before do + # remove OpenSSL::PKCS7::NOINTERN + stub_const('SecureMailing::SMIME::Incoming::OPENSSL_PKCS7_VERIFY_FLAGS', OpenSSL::PKCS7::NOVERIFY) + end + + it 'wrongly performs a verification' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be true + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq(not_related_certificate_subject) + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil + end + end + end + + context 'root CA present' do + + before do + create(:smime_certificate, fixture: ca_fixture) + end + + let(:ca_fixture) { 'ca' } + + it 'verifies' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be true + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq(ca_certificate_subject) + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil + end + + it_behaves_like 'HttpLog writer', 'success' + + context 'expired' do + let(:ca_fixture) { 'expiredca' } + + it 'fails' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq('Unable to find certificate for verification') + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil + end + + it_behaves_like 'HttpLog writer', 'failed' + + context 'allowed' do + + let(:allow_expired) { true } + + # ATTENTION: expired CA is a special case where `allow_expired` does not count + it 'fails' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq('Unable to find certificate for verification') + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be nil + end + + it_behaves_like 'HttpLog writer', 'failed' + end + end + end + end + end + + context 'decryption' do + + let(:allow_expired) { false } + let(:security_preferences) do + { + type: 'S/MIME', + sign: { + success: false, + }, + encryption: { + success: true, + allow_expired: allow_expired, + }, + } + end + + let!(:sender_certificate) { create(:smime_certificate, :with_private, fixture: sender_email_address) } + let!(:recipient_certificate) { create(:smime_certificate, :with_private, fixture: recipient_email_address) } + + context 'private key present' do + + let(:mail) do + smime_mail = build_mail + + mail = Channel::EmailParser.new.parse(smime_mail.to_s) + SecureMailing.incoming(mail) + + mail + end + + it 'decrypts' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to be nil + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be true + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to eq(recipient_certificate_subject) + end + + it_behaves_like 'HttpLog writer', 'success' + + context 'expired allowed' do + let(:allow_expired) { true } + let(:system_email_address) { expired_email_address } + + it 'decrypts with comment' do + expect(mail[:body]).to include(raw_body) + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to be nil + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be true + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to include(expired_email_address).and include('expired') + end + + it_behaves_like 'HttpLog writer', 'success' + end + end + + context 'no private key present' do + + let(:mail) do + smime_mail = build_mail + + mail = Channel::EmailParser.new.parse(smime_mail.to_s) + + sender_certificate.destroy! + recipient_certificate.destroy! + + SecureMailing.incoming(mail) + + mail + end + + it 'fails' do + expect(mail[:body]).to include('no visible content') + expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to be nil + expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false + expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to eq('Unable to find private key to decrypt') + end + + it_behaves_like 'HttpLog writer', 'failed' + end + end + end + + describe '.retry' do + let(:sender_email_address) { customer_email_address } + let(:recipient_email_address) { system_email_address } + + let(:security_preferences) do + { + type: 'S/MIME', + sign: { + success: true, + }, + encryption: { + success: true, + }, + } + end + + let(:mail) do + sender_certificate = create(:smime_certificate, :with_private, fixture: sender_email_address) + recipient_certificate = create(:smime_certificate, :with_private, fixture: system_email_address) + + smime_mail = Channel::EmailBuild.build( + from: sender_email_address, + to: recipient_email_address, + body: raw_body, + content_type: 'text/plain', + security: security_preferences, + attachments: [ + { + content_type: 'text/plain', + content: 'blub', + filename: 'test-file1.txt', + }, + ], + ) + mail = Channel::EmailParser.new.parse(smime_mail.to_s) + + sender_certificate.destroy + recipient_certificate.destroy + + mail + end + + let!(:article) do + _ticket, article, _user, _mail = Channel::EmailParser.new.process({}, mail['raw'] ) + article + end + + context 'private key added' do + before do + create(:smime_certificate, :with_private, fixture: recipient_email_address) + create(:smime_certificate, fixture: sender_email_address) + end + + it 'succeeds' do + SecureMailing.retry(article) + + expect(article.preferences[:security][:sign][:success]).to be true + expect(article.preferences[:security][:sign][:comment]).to eq(sender_certificate_subject) + expect(article.preferences[:security][:encryption][:success]).to be true + expect(article.preferences[:security][:encryption][:comment]).to eq(recipient_certificate_subject) + expect(article.body).to include(raw_body) + expect(article.attachments.count).to eq(1) + expect(article.attachments.first.filename).to eq('test-file1.txt') + end + + context 'S/MIME activated' do + + before do + Setting.set('smime_integration', false) + end + + it 'succeeds' do + Setting.set('smime_integration', true) + + SecureMailing.retry(article) + + expect(article.preferences[:security][:sign][:success]).to be true + expect(article.preferences[:security][:sign][:comment]).to eq(sender_certificate_subject) + expect(article.preferences[:security][:encryption][:success]).to be true + expect(article.preferences[:security][:encryption][:comment]).to eq(recipient_certificate_subject) + expect(article.body).to include(raw_body) + expect(article.attachments.count).to eq(1) + expect(article.attachments.first.filename).to eq('test-file1.txt') + end + end + end + end +end diff --git a/spec/models/smime_certificate_spec.rb b/spec/models/smime_certificate_spec.rb new file mode 100644 index 000000000..6ac33e1bc --- /dev/null +++ b/spec/models/smime_certificate_spec.rb @@ -0,0 +1,195 @@ +require 'rails_helper' + +RSpec.describe SMIMECertificate, type: :model do + + describe '.for_sender_email_address' do + + let(:lookup_address) { 'smime1@example.com' } + + context 'no certificate present' do + it 'returns nil' do + expect(described_class.for_sender_email_address(lookup_address)).to be nil + end + end + + context 'certificate present' do + + context 'with private key' do + + let!(:certificate) { create(:smime_certificate, :with_private, fixture: lookup_address) } + + it 'returns certificate' do + expect(described_class.for_sender_email_address(lookup_address)).to eq(certificate) + end + end + + context 'without private key' do + + before do + create(:smime_certificate, fixture: lookup_address) + end + + it 'returns nil' do + expect(described_class.for_sender_email_address(lookup_address)).to be nil + end + end + + context 'different letter case' do + + let(:fixture) { 'CaseInsenstive@eXample.COM' } + let(:lookup_address) { 'CaseInsenStive@Example.coM' } + + context 'with private key' do + + let!(:certificate) { create(:smime_certificate, :with_private, fixture: fixture) } + + it 'returns certificate' do + expect(described_class.for_sender_email_address(lookup_address)).to eq(certificate) + end + end + end + end + end + + describe 'for_recipipent_email_addresses!' do + + context 'no certificate present' do + + let(:lookup_addresses) { ['smime1@example.com', 'smime2@example.com'] } + + it 'raises ActiveRecord::RecordNotFound' do + expect { described_class.for_recipipent_email_addresses!(lookup_addresses) }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'not all certificates present' do + + let(:existing_address) { 'smime1@example.com' } + let(:not_existing_address) { 'smime2@example.com' } + let(:lookup_addresses) { [existing_address, not_existing_address] } + + before do + create(:smime_certificate, fixture: existing_address) + end + + it 'raises ActiveRecord::RecordNotFound' do + expect { described_class.for_recipipent_email_addresses!(lookup_addresses) }.to raise_error(ActiveRecord::RecordNotFound) + end + + context 'exception message' do + + let(:message) do + described_class.for_recipipent_email_addresses!(lookup_addresses) + rescue => e + e.message + end + + it 'does not contain found address' do + expect(message).not_to include(existing_address) + end + + it 'contains address not found' do + expect(message).to include(not_existing_address) + end + end + end + + context 'all certificates present' do + + let(:lookup_addresses) { ['smime1@example.com', 'smime2@example.com'] } + + let!(:certificates) do + lookup_addresses.map do |existing_address| + create(:smime_certificate, fixture: existing_address) + end + end + + it 'returns certificates' do + expect(described_class.for_recipipent_email_addresses!(lookup_addresses)).to eq(certificates) + end + end + + context 'different letter case' do + + let(:fixture) { 'CaseInsenstive@eXample.COM' } + let(:lookup_addresses) { ['CaseInsenStive@Example.coM'] } + + let!(:certificates) do + [ create(:smime_certificate, fixture: fixture) ] + end + + it 'returns certificates' do + expect(described_class.for_recipipent_email_addresses!(lookup_addresses)).to eq(certificates) + end + end + + end + + describe '#email_addresses' do + + context 'certificate with single email address' do + let(:email_address) { 'smime1@example.com' } + let(:certificate) { create(:smime_certificate, fixture: email_address) } + + it 'returns the mail address' do + expect(certificate.email_addresses).to eq([email_address]) + end + end + + context 'certificate with multiple email addresses' do + let(:email_addresses) { ['smimedouble@example.com', 'smimedouble@example.de'] } + let(:certificate) { create(:smime_certificate, fixture: 'smimedouble@example.com') } + + it 'returns all mail addresses' do + expect(certificate.email_addresses).to eq(email_addresses) + end + end + + end + + describe '#expired?' do + + let(:certificate) { create(:smime_certificate, fixture: fixture) } + + context 'expired' do + let(:fixture) { 'expiredsmime1@example.com' } + + it 'returns true' do + expect(certificate.expired?).to be true + end + end + + context 'valid' do + let(:fixture) { 'smime1@example.com' } + + it 'returns false' do + expect(certificate.expired?).to be false + end + end + end + + context 'certificate parsing' do + + context 'expiration dates' do + + shared_examples 'correctly parsed' do |fixture| + let(:certificate) { create(:smime_certificate, fixture: fixture) } + + it "handles '#{fixture}' fixture" do + expect(certificate.not_before_at).to a_kind_of(ActiveSupport::TimeWithZone) + expect(certificate.not_after_at).to a_kind_of(ActiveSupport::TimeWithZone) + end + end + + it_behaves_like 'correctly parsed', 'smime1@example.com' + it_behaves_like 'correctly parsed', 'smime2@example.com' + it_behaves_like 'correctly parsed', 'smime3@example.com' + it_behaves_like 'correctly parsed', 'CaseInsenstive@eXample.COM' + it_behaves_like 'correctly parsed', 'ca' + end + end + + it 'ensures uniqueness of records' do + expect { create_list(:smime_certificate, 2, fixture: 'smime1@example.com') }.to raise_error(ActiveRecord::RecordInvalid, /Validation failed/) + end +end diff --git a/spec/models/trigger_spec.rb b/spec/models/trigger_spec.rb index f31b5e210..757a65b59 100644 --- a/spec/models/trigger_spec.rb +++ b/spec/models/trigger_spec.rb @@ -182,6 +182,209 @@ RSpec.describe Trigger, type: :model do end end end + + context 'active S/MIME integration' do + before do + Setting.set('smime_integration', true) + + create(:smime_certificate, :with_private, fixture: system_email_address) + create(:smime_certificate, fixture: customer_email_address) + end + + let(:system_email_address) { 'smime1@example.com' } + let(:customer_email_address) { 'smime2@example.com' } + + let(:email_address) { create(:email_address, email: system_email_address) } + + let(:group) { create(:group, email_address: email_address) } + let(:customer) { create(:customer_user, email: customer_email_address) } + + let(:security_preferences) { Ticket::Article.last.preferences[:security] } + + let(:perform) do + { + 'notification.email' => { + 'recipient' => 'ticket_customer', + 'subject' => 'Subject dummy.', + 'body' => 'Body dummy.', + }.merge(security_configuration) + } + end + + let!(:ticket) { create(:ticket, group: group, customer: customer) } + + context 'sending articles' do + + before do + Observer::Transaction.commit + end + + context 'expired certificate' do + + let(:system_email_address) { 'expiredsmime1@example.com' } + + let(:security_configuration) do + { + 'sign' => 'always', + 'encryption' => 'always', + } + end + + it 'creates unsigned article' do + expect(security_preferences[:sign][:success]).to be false + expect(security_preferences[:encryption][:success]).to be true + end + end + + context 'sign and encryption not set' do + + let(:security_configuration) { {} } + + it 'does not sign or encrypt' do + expect(security_preferences[:sign][:success]).to be false + expect(security_preferences[:encryption][:success]).to be false + end + end + + context 'sign and encryption disabled' do + let(:security_configuration) do + { + 'sign' => 'no', + 'encryption' => 'no', + } + end + + it 'does not sign or encrypt' do + expect(security_preferences[:sign][:success]).to be false + expect(security_preferences[:encryption][:success]).to be false + end + end + + context 'sign is enabled' do + let(:security_configuration) do + { + 'sign' => 'always', + 'encryption' => 'no', + } + end + + it 'signs' do + expect(security_preferences[:sign][:success]).to be true + expect(security_preferences[:encryption][:success]).to be false + end + end + + context 'encryption enabled' do + + let(:security_configuration) do + { + 'sign' => 'no', + 'encryption' => 'always', + } + end + + it 'encrypts' do + expect(security_preferences[:sign][:success]).to be false + expect(security_preferences[:encryption][:success]).to be true + end + end + + context 'sign and encryption enabled' do + + let(:security_configuration) do + { + 'sign' => 'always', + 'encryption' => 'always', + } + end + + it 'signs and encrypts' do + expect(security_preferences[:sign][:success]).to be true + expect(security_preferences[:encryption][:success]).to be true + end + end + end + + context 'discard' do + + context 'sign' do + + let(:security_configuration) do + { + 'sign' => 'discard', + } + end + + context 'group without certificate' do + let(:group) { create(:group) } + + it 'does not fire' do + expect { Observer::Transaction.commit } + .to change(Ticket::Article, :count).by(0) + end + end + end + + context 'encryption' do + + let(:security_configuration) do + { + 'encryption' => 'discard', + } + end + + context 'customer without certificate' do + let(:customer) { create(:customer) } + + it 'does not fire' do + expect { Observer::Transaction.commit } + .to change(Ticket::Article, :count).by(0) + end + end + end + + context 'mixed' do + + context 'sign' do + + let(:security_configuration) do + { + 'encryption' => 'always', + 'sign' => 'discard', + } + end + + context 'group without certificate' do + let(:group) { create(:group) } + + it 'does not fire' do + expect { Observer::Transaction.commit } + .to change(Ticket::Article, :count).by(0) + end + end + end + + context 'encryption' do + + let(:security_configuration) do + { + 'encryption' => 'discard', + 'sign' => 'always', + } + end + + context 'customer without certificate' do + let(:customer) { create(:customer) } + + it 'does not fire' do + expect { Observer::Transaction.commit } + .to change(Ticket::Article, :count).by(0) + end + end + end + end + end + end end context 'for condition "ticket updated"' do diff --git a/spec/requests/integration/smime_spec.rb b/spec/requests/integration/smime_spec.rb new file mode 100644 index 000000000..b79761a55 --- /dev/null +++ b/spec/requests/integration/smime_spec.rb @@ -0,0 +1,196 @@ +require 'rails_helper' + +RSpec.describe 'Integration SMIME', type: :request do + + let(:admin_user) { create(:admin_user) } + let(:email_address) { 'smime1@example.com' } + + before do + authenticated_as(admin_user) + end + + describe '/integration/smime/certificate' do + + let(:endpoint) { '/api/v1/integration/smime/certificate' } + + let(:certificate_path) do + Rails.root.join("spec/fixtures/smime/#{email_address}.crt") + end + let(:certificate_string) do + File.read(certificate_path) + end + + context 'POST requests' do + + let(:parsed_certificate) { SMIMECertificate.parse(certificate_string) } + + it 'adds certificate by string' do + expect do + post endpoint, params: { data: certificate_string }, as: :json + end.to change(SMIMECertificate, :count).by(1) + + expect(response).to have_http_status(:ok) + expect(DateTime.parse(json_response['response']['not_after_at'])).to eq(parsed_certificate.not_after) + end + + it 'adds certificate by file' do + expect do + post endpoint, params: { file: Rack::Test::UploadedFile.new(certificate_path, 'text/plain', true) } + end.to change(SMIMECertificate, :count).by(1) + + expect(response).to have_http_status(:ok) + expect(DateTime.parse(json_response['response']['not_after_at'])).to eq(parsed_certificate.not_after) + end + end + + context 'GET requests' do + + let!(:certificate) { create(:smime_certificate, fixture: email_address) } + + it 'lists certificates' do + get endpoint, as: :json + expect(response).to have_http_status(:ok) + + expect(json_response.any? { |e| e['id'] == certificate.id }).to be true + end + end + + context 'DELETE requests' do + + let!(:certificate) { create(:smime_certificate, fixture: email_address) } + + it 'deletes certificate' do + expect do + delete endpoint, params: { id: certificate.id }, as: :json + end.to change(SMIMECertificate, :count).by(-1) + + expect(response).to have_http_status(:ok) + end + end + end + + describe '/integration/smime/private_key' do + + let(:endpoint) { '/api/v1/integration/smime/private_key' } + + context 'POST requests' do + + let(:private_path) do + Rails.root.join("spec/fixtures/smime/#{email_address}.key") + end + + let(:private_string) { File.read(private_path) } + + let(:secret) do + File.read(Rails.root.join("spec/fixtures/smime/#{email_address}.secret")).strip + end + + let!(:certificate) { create(:smime_certificate, fixture: email_address) } + + it 'adds by string' do + expect do + post endpoint, params: { data: private_string, secret: secret }, as: :json + end.to change { + certificate.reload + certificate.private_key + } + + expect(response).to have_http_status(:ok) + expect(json_response['result']).to eq('ok') + end + + it 'adds by file' do + expect do + post endpoint, params: { file: Rack::Test::UploadedFile.new(private_path, 'text/plain', true), secret: secret } + end.to change { + certificate.reload + certificate.private_key + } + + expect(response).to have_http_status(:ok) + expect(json_response['result']).to eq('ok') + end + end + + context 'DELETE requests' do + + let!(:certificate) { create(:smime_certificate, :with_private, fixture: email_address) } + + it 'deletes private key' do + expect do + delete endpoint, params: { id: certificate.id }, as: :json + end.to change { + certificate.reload + certificate.private_key + }.to(nil) + + expect(response).to have_http_status(:ok) + end + end + end + + describe '/integration/smime' do + + let(:endpoint) { '/api/v1/integration/smime' } + + context 'POST requests' do + + let(:system_email_address) { create(:email_address, email: email_address) } + let(:group) { create(:group, email_address: system_email_address) } + + let(:search_query) do + { + article: { + to: email_address, + }, + ticket: { + group_id: group.id, + }, + } + end + + context 'certificate not present' do + it 'does not find non existing certificates' do + post endpoint, params: search_query, as: :json + + expect(response).to have_http_status(:ok) + expect(json_response['encryption']['success']).to eq(false) + expect(json_response['encryption']['comment']).to include(email_address) + expect(json_response['sign']['success']).to eq(false) + expect(json_response['sign']['comment']).to include(email_address) + end + end + + context 'certificate present' do + + before do + create(:smime_certificate, :with_private, fixture: email_address) + end + + it 'finds existing certificate' do + post endpoint, params: search_query, as: :json + + expect(response).to have_http_status(:ok) + expect(json_response['encryption']['success']).to eq(true) + expect(json_response['encryption']['comment']).to include(email_address) + expect(json_response['sign']['success']).to eq(true) + expect(json_response['sign']['comment']).to include(email_address) + end + + context 'but expired' do + let(:email_address) { 'expiredsmime1@example.com' } + + it 'finds existing certificate with comment' do + post endpoint, params: search_query, as: :json + + expect(response).to have_http_status(:ok) + expect(json_response['encryption']['success']).to eq(false) + expect(json_response['encryption']['comment']).to include(email_address).and include('expired') + expect(json_response['sign']['success']).to eq(false) + expect(json_response['sign']['comment']).to include(email_address).and include('expired') + end + end + end + end + end +end diff --git a/spec/support/capybara/browser_test_helper.rb b/spec/support/capybara/browser_test_helper.rb index 94b71be5c..2592ab4c7 100644 --- a/spec/support/capybara/browser_test_helper.rb +++ b/spec/support/capybara/browser_test_helper.rb @@ -27,6 +27,20 @@ module BrowserTestHelper Waiter.new(wait_handle) end + # This checks the number of queued AJAX requests in the frontend JS app + # and assures that the number is constantly zero for 0.5 seconds. + # It comes in handy when waiting for AJAX requests to be completed + # before performing further actions. + # + # @example + # await_empty_ajax_queue + # + def await_empty_ajax_queue + wait(5, interval: 0.5).until_constant do + page.evaluate_script('App.Ajax.queue().length').zero? + end + end + # Moves the mouse from its current position by the given offset. # If the coordinates provided are outside the viewport (the mouse will end up outside the browser window) # then the viewport is scrolled to match. @@ -72,7 +86,6 @@ module BrowserTestHelper yield rescue Capybara::ElementNotFound - end rescue Selenium::WebDriver::Error::TimeOutError => e # cleanup backtrace diff --git a/spec/support/capybara/common_actions.rb b/spec/support/capybara/common_actions.rb index e4ae54aac..065d24983 100644 --- a/spec/support/capybara/common_actions.rb +++ b/spec/support/capybara/common_actions.rb @@ -179,6 +179,15 @@ module CommonActions click '.js-openDropdownMacro' end + def open_article_meta + wrapper = all(%(div.ticket-article-item)).last + + wrapper.find('.article-content .textBubble').click + wait(3).until do + wrapper.find('.article-content-meta .article-meta.top').in_fixed_postion + end + end + def use_template(template) wait(4).until do field = find('#form-template select[name="id"]') diff --git a/spec/system/system/integration/smime_spec.rb b/spec/system/system/integration/smime_spec.rb new file mode 100644 index 000000000..e8509c56c --- /dev/null +++ b/spec/system/system/integration/smime_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +RSpec.describe 'Manage > Integration > S/MIME', type: :system do + + let(:fixture) { 'smime1@example.com' } + + let!(:certificate) do + File.read(Rails.root.join("spec/fixtures/smime/#{fixture}.crt")) + end + let!(:private_key) do + File.read(Rails.root.join("spec/fixtures/smime/#{fixture}.key")) + end + let!(:private_key_secret) do + File.read(Rails.root.join("spec/fixtures/smime/#{fixture}.secret")).strip + end + + it 'enabling and adding of public and private key' do + visit '#system/integration/smime' + + # enable S/MIME + click 'label[for=setting-switch]' + + # add cert + click '.js-addCertificate' + fill_in 'Paste Certificate', with: certificate + click '.js-submit' + + # add private key + click '.js-addPrivateKey' + fill_in 'Paste Private Key', with: private_key + fill_in 'Enter Private Key secret', with: private_key_secret + click '.js-submit' + + # wait for ajax + expect(page).to have_css('td', text: 'Including private key') + + # check result + expect( Setting.get('smime_integration') ).to be true + expect( SMIMECertificate.last.fingerprint ).to be_present + expect( SMIMECertificate.last.raw ).to be_present + expect( SMIMECertificate.last.private_key ).to be_present + end +end diff --git a/spec/system/ticket/create_spec.rb b/spec/system/ticket/create_spec.rb index 2acf01134..6a1b4bfda 100644 --- a/spec/system/ticket/create_spec.rb +++ b/spec/system/ticket/create_spec.rb @@ -21,4 +21,300 @@ RSpec.describe 'Ticket Create', type: :system do context 'when using text modules' do include_examples 'text modules', path: 'ticket/create' end + + context 'S/MIME' do + + prepend_before do + Setting.set('smime_integration', true) + end + + context 'no certificate present' do + let!(:template) { create(:template, :dummy_data) } + + it 'has no security selections' do + visit 'ticket/create' + + within(:active_content) do + use_template(template) + + expect(page).not_to have_css('div.js-securityEncrypt.btn--active', wait: 5) + expect(page).not_to have_css('div.js-securitySign.btn--active', wait: 5) + click '.js-submit' + + expect(page).to have_css('.ticket-article-item', count: 1) + + open_article_meta + + expect(page).not_to have_css('span', text: 'Signed') + expect(page).not_to have_css('span', text: 'Encrypted') + + security_result = Ticket::Article.last.preferences['security'] + expect(security_result['encryption']['success']).to be nil + expect(security_result['sign']['success']).to be nil + end + end + end + + context 'private key configured', authenticated: -> { agent } do + let!(:template) { create(:template, :dummy_data, group: group, owner: agent, customer: customer) } + + let(:system_email_address) { 'smime1@example.com' } + let(:email_address) { create(:email_address, email: system_email_address) } + let(:group) { create(:group, email_address: email_address) } + let(:agent_groups) { [group] } + let(:agent) { create(:agent_user, groups: agent_groups) } + + before do + create(:smime_certificate, :with_private, fixture: system_email_address) + end + + context 'recipient certificate present' do + + let(:recipient_email_address) { 'smime2@example.com' } + let(:customer) { create(:customer_user, email: recipient_email_address) } + + before do + create(:smime_certificate, fixture: recipient_email_address) + end + + it 'plain' do + visit 'ticket/create' + + within(:active_content) do + use_template(template) + + # wait till S/MIME check AJAX call is ready + expect(page).to have_css('div.js-securityEncrypt.btn--active', wait: 5) + expect(page).to have_css('div.js-securitySign.btn--active', wait: 5) + + # deactivate encryption and signing + click '.js-securityEncrypt' + click '.js-securitySign' + + click '.js-submit' + + expect(page).to have_css('.ticket-article-item', count: 1) + + open_article_meta + + expect(page).not_to have_css('span', text: 'Signed') + expect(page).not_to have_css('span', text: 'Encrypted') + + security_result = Ticket::Article.last.preferences['security'] + expect(security_result['encryption']['success']).to be nil + expect(security_result['sign']['success']).to be nil + end + end + + it 'signed' do + visit 'ticket/create' + + within(:active_content) do + use_template(template) + + # wait till S/MIME check AJAX call is ready + expect(page).to have_css('div.js-securityEncrypt.btn--active', wait: 5) + expect(page).to have_css('div.js-securitySign.btn--active', wait: 5) + + # deactivate encryption + click '.js-securityEncrypt' + + click '.js-submit' + + expect(page).to have_css('.ticket-article-item', count: 1) + + open_article_meta + + expect(page).to have_css('span', text: 'Signed') + expect(page).not_to have_css('span', text: 'Encrypted') + + security_result = Ticket::Article.last.preferences['security'] + expect(security_result['encryption']['success']).to be nil + expect(security_result['sign']['success']).to be true + end + end + + it 'encrypted' do + visit 'ticket/create' + + within(:active_content) do + use_template(template) + + # wait till S/MIME check AJAX call is ready + expect(page).to have_css('div.js-securityEncrypt.btn--active', wait: 5) + expect(page).to have_css('div.js-securitySign.btn--active', wait: 5) + + # deactivate signing + click '.js-securitySign' + + click '.js-submit' + + expect(page).to have_css('.ticket-article-item', count: 1) + + open_article_meta + + expect(page).not_to have_css('span', text: 'Signed') + expect(page).to have_css('span', text: 'Encrypted') + + security_result = Ticket::Article.last.preferences['security'] + expect(security_result['encryption']['success']).to be true + expect(security_result['sign']['success']).to be nil + end + end + + it 'signed and encrypted' do + visit 'ticket/create' + + within(:active_content) do + use_template(template) + + # wait till S/MIME check AJAX call is ready + expect(page).to have_css('div.js-securityEncrypt.btn--active', wait: 5) + expect(page).to have_css('div.js-securitySign.btn--active', wait: 5) + + click '.js-submit' + + expect(page).to have_css('.ticket-article-item', count: 1) + + open_article_meta + + expect(page).to have_css('span', text: 'Signed') + expect(page).to have_css('span', text: 'Encrypted') + + security_result = Ticket::Article.last.preferences['security'] + expect(security_result['encryption']['success']).to be true + expect(security_result['sign']['success']).to be true + end + end + + context 'Group default behavior' do + + let(:smime_config) { {} } + + before do + Setting.set('smime_config', smime_config) + end + + shared_examples 'security defaults example' do |sign:, encrypt:| + + it "security defaults sign: #{sign}, encrypt: #{encrypt}" do + within(:active_content) do + encrypt_button = find('.js-securityEncrypt', wait: 5) + sign_button = find('.js-securitySign', wait: 5) + + await_empty_ajax_queue + + active_button_class = '.btn--active' + expect(encrypt_button.matches_css?(active_button_class, wait: 2)).to be(encrypt) + expect(sign_button.matches_css?(active_button_class, wait: 2)).to be(sign) + end + end + end + + shared_examples 'security defaults' do |sign:, encrypt:| + + before do + visit 'ticket/create' + + within(:active_content) do + use_template(template) + end + end + + include_examples 'security defaults example', sign: sign, encrypt: encrypt + end + + shared_examples 'security defaults group change' do |sign:, encrypt:| + + before do + visit 'ticket/create' + + within(:active_content) do + use_template(template) + + await_empty_ajax_queue + + select new_group.name, from: 'group_id' + end + end + + include_examples 'security defaults example', sign: sign, encrypt: encrypt + end + + context 'not configured' do + it_behaves_like 'security defaults', sign: true, encrypt: true + end + + context 'configuration present' do + + let(:smime_config) do + { + 'group_id' => group_defaults + } + end + + let(:group_defaults) do + { + 'default_encryption' => { + group.id.to_s => default_encryption, + }, + 'default_sign' => { + group.id.to_s => default_sign, + } + } + end + + let(:default_sign) { true } + let(:default_encryption) { true } + + shared_examples 'sign and encrypt variations' do |check_examples_name| + + it_behaves_like check_examples_name, sign: true, encrypt: true + + context 'no value' do + let(:group_defaults) { {} } + + it_behaves_like check_examples_name, sign: true, encrypt: true + end + + context 'signing disabled' do + let(:default_sign) { false } + + it_behaves_like check_examples_name, sign: false, encrypt: true + end + + context 'encryption disabled' do + let(:default_encryption) { false } + + it_behaves_like check_examples_name, sign: true, encrypt: false + end + end + + context 'same Group' do + it_behaves_like 'sign and encrypt variations', 'security defaults' + end + + context 'Group change' do + let(:new_group) { create(:group, email_address: email_address) } + + let(:agent_groups) { [group, new_group] } + + let(:group_defaults) do + { + 'default_encryption' => { + new_group.id.to_s => default_encryption, + }, + 'default_sign' => { + new_group.id.to_s => default_sign, + } + } + end + + it_behaves_like 'sign and encrypt variations', 'security defaults group change' + end + end + end + end + end + end end diff --git a/spec/system/ticket/zoom_spec.rb b/spec/system/ticket/zoom_spec.rb index bf3116fb8..19fb9a390 100644 --- a/spec/system/ticket/zoom_spec.rb +++ b/spec/system/ticket/zoom_spec.rb @@ -394,4 +394,342 @@ RSpec.describe 'Ticket zoom', type: :system do end end end + + context 'S/MIME active', authenticated: -> { agent } do + + let(:system_email_address) { 'smime1@example.com' } + let(:email_address) { create(:email_address, email: system_email_address) } + let(:group) { create(:group, email_address: email_address) } + let(:agent_groups) { [group] } + let(:agent) { create(:agent_user, groups: agent_groups) } + + let(:sender_email_address) { 'smime2@example.com' } + let(:customer) { create(:customer_user, email: sender_email_address) } + + let!(:ticket) { create(:ticket, group: group, owner: agent, customer: customer) } + + before do + Setting.set('smime_integration', true) + end + + context 'received mail' do + + context 'article meta information' do + + context 'success' do + it 'shows encryption/sign information' do + create(:ticket_article, preferences: { + security: { + type: 'S/MIME', + encryption: { + success: true, + comment: 'COMMENT_ENCRYPT_SUCCESS', + }, + sign: { + success: true, + comment: 'COMMENT_SIGN_SUCCESS', + }, + } + }, ticket: ticket) + + visit "#ticket/zoom/#{ticket.id}" + + expect(page).to have_css('svg.icon-lock') + expect(page).to have_css('svg.icon-signed') + + open_article_meta + + expect(page).to have_css('span', text: 'Encrypted') + expect(page).to have_css('span', text: 'Signed') + expect(page).to have_css('span[title=COMMENT_ENCRYPT_SUCCESS]') + expect(page).to have_css('span[title=COMMENT_SIGN_SUCCESS]') + end + end + + context 'error' do + + it 'shows create information about encryption/sign failed' do + create(:ticket_article, preferences: { + security: { + type: 'S/MIME', + encryption: { + success: false, + comment: 'Encryption failed because XXX', + }, + sign: { + success: false, + comment: 'Sign failed because XXX', + }, + } + }, ticket: ticket) + visit "#ticket/zoom/#{ticket.id}" + + expect(page).to have_css('svg.icon-not-signed') + + open_article_meta + + expect(page).to have_css('div.alert.alert--warning', text: 'Encryption failed because XXX') + expect(page).to have_css('div.alert.alert--warning', text: 'Sign failed because XXX') + end + end + end + + context 'certificate not present at time of arrival' do + + it 'retry' do + smime1 = create(:smime_certificate, :with_private, fixture: system_email_address) + smime2 = create(:smime_certificate, :with_private, fixture: sender_email_address) + + mail = Channel::EmailBuild.build( + from: sender_email_address, + to: system_email_address, + body: 'somebody with some text', + content_type: 'text/plain', + security: { + type: 'S/MIME', + sign: { + success: true, + }, + encryption: { + success: true, + }, + }, + ) + + smime1.destroy + smime2.destroy + + parsed_mail = Channel::EmailParser.new.parse(mail.to_s) + ticket, article, _user, _mail = Channel::EmailParser.new.process({ group_id: group.id }, parsed_mail['raw']) + expect(Ticket::Article.find(article.id).body).to eq('no visible content') + + create(:smime_certificate, fixture: sender_email_address) + create(:smime_certificate, :with_private, fixture: system_email_address) + + visit "#ticket/zoom/#{ticket.id}" + expect(page).not_to have_css('.article-content', text: 'somebody with some text') + click '.js-securityRetryProcess' + expect(page).to have_css('.article-content', text: 'somebody with some text') + end + end + end + + context 'replying' do + + before do + create(:ticket_article, ticket: ticket, from: customer.email) + + create(:smime_certificate, :with_private, fixture: system_email_address) + create(:smime_certificate, fixture: sender_email_address) + end + + it 'plain' do + visit "#ticket/zoom/#{ticket.id}" + + all('a[data-type=emailReply]').last.click + find('.articleNewEdit-body').send_keys('Test') + + expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5) + expect(page).to have_css('.js-securitySign.btn--active', wait: 5) + + click '.js-securityEncrypt' + click '.js-securitySign' + + click '.js-submit' + expect(page).to have_css('.ticket-article-item', count: 2) + + expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be nil + expect(Ticket::Article.last.preferences['security']['sign']['success']).to be nil + end + + it 'signed' do + visit "#ticket/zoom/#{ticket.id}" + + all('a[data-type=emailReply]').last.click + find('.articleNewEdit-body').send_keys('Test') + + expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5) + expect(page).to have_css('.js-securitySign.btn--active', wait: 5) + + click '.js-securityEncrypt' + + click '.js-submit' + expect(page).to have_css('.ticket-article-item', count: 2) + + expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be nil + expect(Ticket::Article.last.preferences['security']['sign']['success']).to be true + end + + it 'encrypted' do + visit "#ticket/zoom/#{ticket.id}" + + all('a[data-type=emailReply]').last.click + find('.articleNewEdit-body').send_keys('Test') + + expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5) + expect(page).to have_css('.js-securitySign.btn--active', wait: 5) + + click '.js-securitySign' + + click '.js-submit' + expect(page).to have_css('.ticket-article-item', count: 2) + + expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be true + expect(Ticket::Article.last.preferences['security']['sign']['success']).to be nil + end + + it 'signed and encrypted' do + visit "#ticket/zoom/#{ticket.id}" + + all('a[data-type=emailReply]').last.click + find('.articleNewEdit-body').send_keys('Test') + + expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5) + expect(page).to have_css('.js-securitySign.btn--active', wait: 5) + + click '.js-submit' + expect(page).to have_css('.ticket-article-item', count: 2) + + expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be true + expect(Ticket::Article.last.preferences['security']['sign']['success']).to be true + end + end + + context 'Group default behavior' do + + let(:smime_config) { {} } + + before do + Setting.set('smime_config', smime_config) + + create(:ticket_article, ticket: ticket, from: customer.email) + + create(:smime_certificate, :with_private, fixture: system_email_address) + create(:smime_certificate, fixture: sender_email_address) + end + + shared_examples 'security defaults example' do |sign:, encrypt:| + + it "security defaults sign: #{sign}, encrypt: #{encrypt}" do + within(:active_content) do + encrypt_button = find('.js-securityEncrypt', wait: 5) + sign_button = find('.js-securitySign', wait: 5) + + await_empty_ajax_queue + + active_button_class = '.btn--active' + expect(encrypt_button.matches_css?(active_button_class, wait: 2)).to be(encrypt) + expect(sign_button.matches_css?(active_button_class, wait: 2)).to be(sign) + end + end + end + + shared_examples 'security defaults' do |sign:, encrypt:| + + before do + visit "#ticket/zoom/#{ticket.id}" + + within(:active_content) do + all('a[data-type=emailReply]').last.click + find('.articleNewEdit-body').send_keys('Test') + + await_empty_ajax_queue + end + end + + include_examples 'security defaults example', sign: sign, encrypt: encrypt + end + + shared_examples 'security defaults group change' do |sign:, encrypt:| + + before do + visit "#ticket/zoom/#{ticket.id}" + + within(:active_content) do + all('a[data-type=emailReply]').last.click + find('.articleNewEdit-body').send_keys('Test') + + await_empty_ajax_queue + + select new_group.name, from: 'group_id' + end + end + + include_examples 'security defaults example', sign: sign, encrypt: encrypt + end + + context 'not configured' do + it_behaves_like 'security defaults', sign: true, encrypt: true + end + + context 'configuration present' do + + let(:smime_config) do + { + 'group_id' => group_defaults + } + end + + let(:group_defaults) do + { + 'default_encryption' => { + group.id.to_s => default_encryption, + }, + 'default_sign' => { + group.id.to_s => default_sign, + } + } + end + + let(:default_sign) { true } + let(:default_encryption) { true } + + shared_examples 'sign and encrypt variations' do |check_examples_name| + + it_behaves_like check_examples_name, sign: true, encrypt: true + + context 'no value' do + let(:group_defaults) { {} } + + it_behaves_like check_examples_name, sign: true, encrypt: true + end + + context 'signing disabled' do + let(:default_sign) { false } + + it_behaves_like check_examples_name, sign: false, encrypt: true + end + + context 'encryption disabled' do + let(:default_encryption) { false } + + it_behaves_like check_examples_name, sign: true, encrypt: false + end + end + + context 'same Group' do + it_behaves_like 'sign and encrypt variations', 'security defaults' + end + + context 'Group change' do + let(:new_group) { create(:group, email_address: email_address) } + + let(:agent_groups) { [group, new_group] } + + let(:group_defaults) do + { + 'default_encryption' => { + new_group.id.to_s => default_encryption, + }, + 'default_sign' => { + new_group.id.to_s => default_sign, + } + } + end + + it_behaves_like 'sign and encrypt variations', 'security defaults group change' + end + end + end + end end