From 02d1aa12f911c06df66e48d4128114014d7f3168 Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Tue, 9 Jan 2018 15:22:04 -0500 Subject: [PATCH] View smime certificate of signed messages --- UI/MailPartViewers/UIxMailPartSignedViewer.h | 8 +- UI/MailPartViewers/UIxMailPartSignedViewer.m | 139 ++++++++++++------ UI/MailerUI/English.lproj/Localizable.strings | 13 ++ UI/Templates/MailerUI/UIxMailViewTemplate.wox | 42 +++++- UI/WebServerResources/img/certificate-off.svg | 1 + UI/WebServerResources/img/certificate.svg | 1 + .../js/Common/sgBlockToggle.directive.js | 75 ++++++++++ .../components/block-toggle/block-toggle.scss | 8 + .../components/pseudo-input/pseudo-input.scss | 18 ++- UI/WebServerResources/scss/styles.scss | 1 + 10 files changed, 242 insertions(+), 64 deletions(-) create mode 100644 UI/WebServerResources/img/certificate-off.svg create mode 100644 UI/WebServerResources/img/certificate.svg create mode 100644 UI/WebServerResources/js/Common/sgBlockToggle.directive.js create mode 100644 UI/WebServerResources/scss/components/block-toggle/block-toggle.scss diff --git a/UI/MailPartViewers/UIxMailPartSignedViewer.h b/UI/MailPartViewers/UIxMailPartSignedViewer.h index 68b143072..ec7e93e6e 100644 --- a/UI/MailPartViewers/UIxMailPartSignedViewer.h +++ b/UI/MailPartViewers/UIxMailPartSignedViewer.h @@ -1,6 +1,6 @@ /* UIxMailPartSignedViewer.h - this file is part of SOGo * - * Copyright (C) 2009-2017 Inverse inc. + * Copyright (C) 2009-2018 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,11 +30,15 @@ { BOOL processed; BOOL validSignature; - NSMutableString *validationMessage; + NSMutableArray *certificates; + NSString *validationMessage; } - (BOOL) validSignature; - (NSString *) validationMessage; +- (NSArray *) smimeCertificates; +- (NSDictionary *) certificateForSubject: (NSString *) subject + andIssuer: (NSString *) issuer; @end diff --git a/UI/MailPartViewers/UIxMailPartSignedViewer.m b/UI/MailPartViewers/UIxMailPartSignedViewer.m index 70152acb8..fb9cf2233 100644 --- a/UI/MailPartViewers/UIxMailPartSignedViewer.m +++ b/UI/MailPartViewers/UIxMailPartSignedViewer.m @@ -1,6 +1,6 @@ /* UIxMailPartSignedViewer.m - this file is part of SOGo * - * Copyright (C) 2009-2017 Inverse inc. + * Copyright (C) 2009-2018 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -53,19 +53,19 @@ success = NO; - store = X509_STORE_new (); - OpenSSL_add_all_algorithms (); + store = X509_STORE_new(); + OpenSSL_add_all_algorithms(); if (store) { - lookup = X509_STORE_add_lookup (store, X509_LOOKUP_file()); + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); if (lookup) { - X509_LOOKUP_load_file (lookup, NULL, X509_FILETYPE_DEFAULT); - lookup = X509_STORE_add_lookup (store, X509_LOOKUP_hash_dir()); + X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT); + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); if (lookup) { - X509_LOOKUP_add_dir (lookup, NULL, X509_FILETYPE_DEFAULT); + X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT); ERR_clear_error(); success = YES; } @@ -86,18 +86,15 @@ - (void) _processMessage { - NSString *issuer, *subject; NSData *signedData; STACK_OF(X509) *certs; X509_STORE *x509Store; BIO *msgBio, *inData; - char sslError[1024]; + const char* sslError; PKCS7 *p7; int err, i; - memset(sslError, 0, 1024); - ERR_clear_error(); if ([[self decodedFlatContent] isKindOfClass: [NGMimeMultipartBody class]]) @@ -110,45 +107,50 @@ inData = NULL; p7 = SMIME_read_PKCS7(msgBio, &inData); - subject = nil; - issuer = nil; certs = NULL; + certificates = [NSMutableArray array]; + validationMessage = nil; if (p7) { - i = OBJ_obj2nid(p7->type); - - if (i == NID_pkcs7_signed) + if (OBJ_obj2nid(p7->type) == NID_pkcs7_signed) { + NSString *subject, *issuer; X509 *x; - certs=p7->d.sign->cert; - - if (sk_X509_num(certs) > 0) - { + certs = p7->d.sign->cert; + + for (i = 0; i < sk_X509_num(certs); i++) + { BIO *buf; - char p[256]; - - memset(p, 0, 256); - x = sk_X509_value(certs,0); + char p[1024]; + + x = sk_X509_value(certs, i); + + memset(p, 0, 1024); buf = BIO_new(BIO_s_mem()); - X509_NAME_print_ex(buf, X509_get_subject_name(x), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB); - BIO_gets(buf, p, 256); + X509_NAME_print_ex(buf, X509_get_subject_name(x), 0, + ASN1_STRFLGS_ESC_CTRL | XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_LN); + BIO_read(buf, p, 1024); subject = [NSString stringWithUTF8String: p]; - - memset(p, 0, 256); - X509_NAME_print_ex(buf, X509_get_issuer_name(x), 0, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB); - BIO_gets(buf, p, 256); - issuer = [NSString stringWithUTF8String: p]; - BIO_free(buf); + + memset(p, 0, 1024); + buf = BIO_new(BIO_s_mem()); + X509_NAME_print_ex(buf, X509_get_issuer_name(x), 0, + ASN1_STRFLGS_ESC_CTRL | XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_LN); + BIO_read(buf, p, 1024); + issuer = [NSString stringWithUTF8String: p]; + BIO_free(buf); + + [certificates addObject: [self certificateForSubject: subject + andIssuer: issuer]]; } } err = ERR_get_error(); if (err) { - ERR_error_string_n (err, sslError, 1023); validSignature = NO; } else @@ -158,30 +160,29 @@ NULL, PKCS7_DETACHED) == 1); err = ERR_get_error(); - if (err) - ERR_error_string_n(err, sslError, 1023); if (x509Store) X509_STORE_free (x509Store); } + + if (err) + { + ERR_load_crypto_strings(); + sslError = ERR_reason_error_string(err); + validationMessage = [[self labelForKey: [NSString stringWithUTF8String: sslError]] retain]; + } } + BIO_free (msgBio); if (inData) BIO_free (inData); - validationMessage = [NSMutableString string]; + if (validSignature) + validationMessage = [NSString stringWithString: [self labelForKey: @"Message is signed"]]; + else if (!validationMessage) + validationMessage = [NSString stringWithString: [self labelForKey: @"Digital signature is not valid"]]; - if (!validSignature) - [validationMessage appendString: [self labelForKey: @"Digital signature is not valid"]]; - else - [validationMessage appendString: [self labelForKey: @"Message is signed"]]; - - if (issuer && subject) - [validationMessage appendFormat: @"\n%@: %@\n%@: %@", - [self labelForKey: @"Subject"], subject, - [self labelForKey: @"Issuer"], issuer]; - processed = YES; } @@ -193,6 +194,41 @@ return validSignature; } +- (NSArray *) componentsForDN: (NSString *) dn +{ + NSArray *pair; + NSEnumerator *componentsEnum; + NSMutableArray *components; + NSString *pairString; + + components = [NSMutableArray array]; + componentsEnum = [[dn componentsSeparatedByString: @"\n"] objectEnumerator]; + while (( pairString = [componentsEnum nextObject] )) + { + pair = [pairString componentsSeparatedByString: @"="]; + if ([pair count] == 2) + [components addObject: [NSArray arrayWithObjects: + [self labelForKey: [pair objectAtIndex: 0]], + [pair objectAtIndex: 1], nil]]; + } + + return components; +} + +- (NSDictionary *) certificateForSubject: (NSString *) subject + andIssuer: (NSString *) issuer +{ + return [NSDictionary dictionaryWithObjectsAndKeys: + [self componentsForDN: subject], @"subject", + [self componentsForDN: issuer], @"issuer", + nil]; +} + +- (NSArray *) smimeCertificates +{ + return certificates; +} + - (NSString *) validationMessage { if (!processed) @@ -206,6 +242,11 @@ return NO; } +- (NSArray *) smimeCertificates +{ + return nil; +} + - (BOOL) validSignature { return NO; @@ -251,12 +292,16 @@ [renderedParts addObject: [viewer renderedPart]]; } + if (!processed) + [self _processMessage]; + return [NSDictionary dictionaryWithObjectsAndKeys: [self className], @"type", [NSNumber numberWithBool: [self supportsSMIME]], @"supports-smime", [NSNumber numberWithBool: [self validSignature]], @"valid", - [self validationMessage], @"error", renderedParts, @"content", + [self smimeCertificates], @"certificates", + [self validationMessage], @"error", nil]; } diff --git a/UI/MailerUI/English.lproj/Localizable.strings b/UI/MailerUI/English.lproj/Localizable.strings index 7313d22fc..bd2456689 100644 --- a/UI/MailerUI/English.lproj/Localizable.strings +++ b/UI/MailerUI/English.lproj/Localizable.strings @@ -246,6 +246,19 @@ /* Number of selected messages in list */ "selected" = "selected"; +/* SMIME Certificate field */ +"countryName" = "Country"; +"organizationName" = "Organization"; +"organizationalUnitName" = "Organizational Unit"; +"commonName" = "Common Name"; +"emailAddress" = "Email Address"; + +/* OpenSSL certificate error - unknown issuer */ +"certificate verify error" = "This certificate was signed by an unknown issuer"; + +/* OpenSSL certificate error - email mismatch */ +"digest failure" = "This certificate doesn't match the sender email address"; + "This Folder" = "This Folder"; /* Label popup menu */ diff --git a/UI/Templates/MailerUI/UIxMailViewTemplate.wox b/UI/Templates/MailerUI/UIxMailViewTemplate.wox index 14cc8ba2a..16705a1b2 100644 --- a/UI/Templates/MailerUI/UIxMailViewTemplate.wox +++ b/UI/Templates/MailerUI/UIxMailViewTemplate.wox @@ -206,16 +206,42 @@ - -
-
- error - check + + + +
+ + +
+

+ expand_more +
+
+
+
+
+
Subject Name
+
+
+
+
+
+
+
Issuer
+
+
+
+
+
+
-

- +
\ No newline at end of file diff --git a/UI/WebServerResources/img/certificate.svg b/UI/WebServerResources/img/certificate.svg new file mode 100644 index 000000000..11c4ecfed --- /dev/null +++ b/UI/WebServerResources/img/certificate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/UI/WebServerResources/js/Common/sgBlockToggle.directive.js b/UI/WebServerResources/js/Common/sgBlockToggle.directive.js new file mode 100644 index 000000000..784ceb299 --- /dev/null +++ b/UI/WebServerResources/js/Common/sgBlockToggle.directive.js @@ -0,0 +1,75 @@ +/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +(function() { + 'use strict'; + + /* + * sgBlockToggle - expandable block, collapsed by default + * @memberof SOGo.Common + * @ngInject + * @example: + + + + warning +

{{ message }}

+ expand_more +
+
+ +
+ */ + sgBlockToggle.$inject = ['$mdUtil', '$animateCss', '$$rAF']; + function sgBlockToggle($mdUtil, $animateCss, $$rAF) { + return { + link: link + }; + + function link($scope, $element) { + var button = $element[0].querySelector('.sg-button-toggle'), + icon = button.querySelector('.sg-icon-toggle'), + icon_rotate_class = 'md-rotate-180-ccw', + block = $element[0].querySelector('.sg-block-toggle'), + isOpen = false; + + button.classList.add('md-clickable'); + angular.element(button).on('click', toggle); + + renderContent(); + + function renderContent() { + block.setAttribute('aria-hidden', !isOpen); + block.setAttribute('aria-expanded', isOpen); + if (!isOpen) + block.style.visibility = 'hidden'; + } + + function toggle() { + isOpen = !isOpen; + if (isOpen) + icon.classList.add(icon_rotate_class); + else + icon.classList.remove(icon_rotate_class); + + if (isOpen) + block.style.visibility = 'visible'; + + $$rAF(function() { + var targetHeight = isOpen ? block.scrollHeight : 0; + + $animateCss(angular.element(block), { + easing: 'cubic-bezier(0.35, 0, 0.25, 1)', + to: { height: targetHeight + 'px' }, + duration: 0.75 // seconds + }).start().then(function() { + renderContent(); + }); + }); + } + } + } + + angular + .module('SOGo.Common') + .directive('sgBlockToggle', sgBlockToggle); +})(); diff --git a/UI/WebServerResources/scss/components/block-toggle/block-toggle.scss b/UI/WebServerResources/scss/components/block-toggle/block-toggle.scss new file mode 100644 index 000000000..0460d9996 --- /dev/null +++ b/UI/WebServerResources/scss/components/block-toggle/block-toggle.scss @@ -0,0 +1,8 @@ +/// block-toggle.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*- + +.sg-block-toggle { + overflow: hidden; + position: relative; + z-index: 1; + height: 0; +}; diff --git a/UI/WebServerResources/scss/components/pseudo-input/pseudo-input.scss b/UI/WebServerResources/scss/components/pseudo-input/pseudo-input.scss index 1c1ad62e1..b5834a055 100644 --- a/UI/WebServerResources/scss/components/pseudo-input/pseudo-input.scss +++ b/UI/WebServerResources/scss/components/pseudo-input/pseudo-input.scss @@ -35,13 +35,16 @@ .pseudo-input-label, .button-label { display: inline-block; - margin-top: $mg; color: $colorGray; font-weight: $sg-font-regular; line-height: $sg-line-height-1; transform: scale($input-label-float-scale); } +.pseudo-input-label { + margin-top: $mg; +} + .pseudo-input-label { @include rtl(transform-origin, left top, right top); } @@ -64,15 +67,16 @@ border-width: 0; } -md-select.pseudo-input-field { +md-select.pseudo-input-field, +md-checkbox.pseudo-input-field { margin-bottom: $mg; padding: 0; } // The specs dimensions are too large to fit with angular-material // Here's a modifier -.pseudo-input-container--compact { - .pseudo-input-label { - margin-top: 0; - } -} +// .pseudo-input-container--compact { +// .pseudo-input-label { +// margin-top: 0; +// } +// } diff --git a/UI/WebServerResources/scss/styles.scss b/UI/WebServerResources/scss/styles.scss index 2fe8d39d9..2efadecbf 100755 --- a/UI/WebServerResources/scss/styles.scss +++ b/UI/WebServerResources/scss/styles.scss @@ -74,6 +74,7 @@ @import 'components/ripple/ripple'; @import 'components/timepicker/timepicker'; @import 'components/pseudo-input/pseudo-input'; +@import 'components/block-toggle/block-toggle'; @import 'views/view'; @import 'core/print';