diff --git a/SoObjects/Mailer/NSData+SMIME.h b/SoObjects/Mailer/NSData+SMIME.h index 11404aaac..9e6edc2c9 100644 --- a/SoObjects/Mailer/NSData+SMIME.h +++ b/SoObjects/Mailer/NSData+SMIME.h @@ -28,13 +28,15 @@ @interface NSData (SOGoMailSMIME) - (NSData *) signUsingCertificateAndKey: (NSData *) theData; -- (NSData *) encryptUsingCertificate: (NSData *) theData; +- (NSData *) encryptUsingCertificate: (NSData *) theData + andAlgos: (NSArray *) theAlgos; - (NSData *) decryptUsingCertificate: (NSData *) theData; - (NGMimeMessage *) messageFromEncryptedDataAndCertificate: (NSData *) theCertificate; - (NSData *) embeddedContent; - (NGMimeMessage *) messageFromOpaqueSignedData; - (NSData *) convertPKCS12ToPEMUsingPassword: (NSString *) thePassword; -- (NSData *) signersFromPKCS7; +- (NSData *) signersFromCMS; +- (NSArray *) algosFromCMS; - (NSDictionary *) certificateDescription; @end diff --git a/SoObjects/Mailer/NSData+SMIME.m b/SoObjects/Mailer/NSData+SMIME.m index fa08d7032..3fa611faa 100644 --- a/SoObjects/Mailer/NSData+SMIME.m +++ b/SoObjects/Mailer/NSData+SMIME.m @@ -18,6 +18,7 @@ * Boston, MA 02111-1307, USA. */ +#import #import #import #import @@ -27,12 +28,13 @@ #import #import +#import #import #if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) #include #include -#include +#include #include #include #endif @@ -54,18 +56,18 @@ X509 *link = NULL; STACK_OF(X509) *chain = NULL; EVP_PKEY *skey = NULL; - PKCS7 *p7 = NULL; + CMS_ContentInfo *cms = NULL; BUF_MEM *bptr; - + unsigned int len, slen; const char* bytes; const char* sbytes; - int flags = PKCS7_STREAM | PKCS7_DETACHED | PKCS7_CRLFEOL; - + int flags = CMS_STREAM | CMS_DETACHED | CMS_CRLFEOL; + OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); - + bytes = [theData bytes]; len = [theData length]; tbio = BIO_new_mem_buf((void *)bytes, len); @@ -77,13 +79,13 @@ NSLog(@"FATAL: failed to read certificate for signing."); goto cleanup; } - + chain = sk_X509_new_null(); while (link = PEM_read_bio_X509_AUX(tbio, NULL, 0, NULL)) sk_X509_unshift(chain, link); BIO_reset(tbio); - + skey = PEM_read_bio_PrivateKey(tbio, NULL, 0, NULL); if (!skey) @@ -91,34 +93,34 @@ NSLog(@"FATAL: failed to read private key for signing."); goto cleanup; } - + // We sign sbytes = [self bytes]; slen = [self length]; sbio = BIO_new_mem_buf((void *)sbytes, slen); - p7 = PKCS7_sign(scert, skey, (sk_X509_num(chain) > 0) ? chain : NULL, sbio, flags); + cms = CMS_sign(scert, skey, (sk_X509_num(chain) > 0) ? chain : NULL, sbio, flags); - if (!p7) + if (!cms) { NSLog(@"FATAL: failed to sign message."); goto cleanup; } - + // We output obio = BIO_new(BIO_s_mem()); - SMIME_write_PKCS7(obio, p7, sbio, flags); + SMIME_write_CMS(obio, cms, sbio, flags); BIO_get_mem_ptr(obio, &bptr); output = [NSData dataWithBytes: bptr->data length: bptr->length]; cleanup: - PKCS7_free(p7); + CMS_ContentInfo_free(cms); sk_X509_pop_free(chain, X509_free); - X509_free(scert); + X509_free(scert); BIO_free(tbio); BIO_free(sbio); BIO_free(obio); - + return output; } @@ -126,21 +128,25 @@ // // - (NSData *) encryptUsingCertificate: (NSData *) theData + andAlgos: (NSArray *) theAlgos { NSData *output = NULL; BUF_MEM *bptr = NULL; BIO *tbio = NULL, *sbio = NULL, *obio = NULL; X509 *rcert = NULL; - PKCS7 *p7 = NULL; + CMS_ContentInfo *cms = NULL; STACK_OF(X509) *recips = NULL; - + + int i; + const EVP_CIPHER *cipher = NULL; + unsigned int len, slen; const char* bytes; const char* sbytes; - int flags = PKCS7_STREAM; - + int flags = CMS_STREAM; + OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); @@ -162,9 +168,9 @@ NSLog(@"FATAL: unable to read certificate for encryption"); goto cleanup; } - + recips = sk_X509_new_null(); - + if (!recips || !sk_X509_push(recips, rcert)) { NSLog(@"FATAL: unable to push certificate into stack"); @@ -173,15 +179,70 @@ rcert = NULL; + if (theAlgos) + { + // pick first supported cipher suggested by peer + for (i = 0; cipher == NULL && i < [theAlgos count]; i++) + { + int nid = [[theAlgos objectAtIndex: i] intValue]; + switch (nid) + { + // ciphers from RFC8551 + //No support for AuthEnvelopedData in OpenSSL yet + //case NID_chacha20_poly1305: + //case NID_aes_256_gcm: + //case NID_aes_128_gcm: +#ifdef NID_aes_128_cbc + case NID_aes_128_cbc: +#endif + // plus ciphers from RFC5751 +#ifdef NID_aes_192_cbc + case NID_aes_192_cbc: +#endif +#ifdef NID_aes_256_cbc + case NID_aes_256_cbc: +#endif +#ifdef NID_des_ede3_cbc + case NID_des_ede3_cbc: +#endif + cipher = EVP_get_cipherbynid(nid); + break; + default: + break; + } + } + + // no matching cipher - use default cipher + if (cipher == NULL) +#ifndef OPENSSL_NO_AES + cipher = EVP_aes_128_cbc(); +#elif !defined(OPENSSL_NO_DES) + cipher = EVP_des_ede3_cbc(); +#else +#error "Neither AES nor 3DES available" +#endif + } + else + { + // ATM theAlgos == NULL means we're storing a draft with the writer's own key +#ifndef OPENSSL_NO_AES + cipher = EVP_aes_128_cbc(); +#elif !defined(OPENSSL_NO_DES) + cipher = EVP_des_ede3_cbc(); +#else +#error "Neither AES nor 3DES available" +#endif + } + // Get the bytes to encrypt sbytes = [self bytes]; slen = [self length]; sbio = BIO_new_mem_buf((void *)sbytes, slen); // Encrypt - p7 = PKCS7_encrypt(recips, sbio, EVP_des_ede3_cbc(), flags); + cms = CMS_encrypt(recips, sbio, cipher, flags); - if (!p7) + if (!cms) { NSLog(@"FATAL: unable to encrypt message"); goto cleanup; @@ -189,19 +250,19 @@ // We output the S/MIME encrypted message obio = BIO_new(BIO_s_mem()); - if (!SMIME_write_PKCS7(obio, p7, sbio, flags)) + if (!SMIME_write_CMS(obio, cms, sbio, flags)) { - NSLog(@"FATAL: unable to write PKCS7 output"); + NSLog(@"FATAL: unable to write CMS output"); goto cleanup; } BIO_get_mem_ptr(obio, &bptr); - + output = [NSData dataWithBytes: bptr->data length: bptr->length]; cleanup: - PKCS7_free(p7); - X509_free(rcert); + CMS_ContentInfo_free(cms); + X509_free(rcert); BIO_free(tbio); BIO_free(sbio); BIO_free(obio); @@ -216,16 +277,16 @@ { NSData *output = NULL; - BIO *tbio, *sbio, *obio; + BIO *tbio, *sbio = NULL, *obio = NULL; BUF_MEM *bptr; X509 *scert = NULL; EVP_PKEY *skey = NULL; - PKCS7 *p7 = NULL; - + CMS_ContentInfo *cms = NULL; + unsigned int len, slen; const char* bytes; const char* sbytes; - + OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); @@ -241,8 +302,8 @@ { NSLog(@"FATAL: could not read certificate for decryption"); goto cleanup; - } - + } + BIO_reset(tbio); skey = PEM_read_bio_PrivateKey(tbio, NULL, 0, NULL); @@ -251,15 +312,15 @@ { NSLog(@"FATAL: could not read private key for decryption"); goto cleanup; - } - + } + sbytes = [self bytes]; slen = [self length]; sbio = BIO_new_mem_buf((void *)sbytes, slen); - p7 = SMIME_read_PKCS7(sbio, NULL); + cms = SMIME_read_CMS(sbio, NULL); - if (!p7) + if (!cms) { NSLog(@"FATAL: could not read the content to be decrypted"); goto cleanup; @@ -267,24 +328,24 @@ // We output the S/MIME encrypted message obio = BIO_new(BIO_s_mem()); - - if (!PKCS7_decrypt(p7, skey, scert, obio, 0)) + + if (!CMS_decrypt(cms, skey, scert, NULL, obio, 0)) { NSLog(@"FATAL: could not decrypt content"); goto cleanup; } - + BIO_get_mem_ptr(obio, &bptr); - + output = [NSData dataWithBytes: bptr->data length: bptr->length]; cleanup: - PKCS7_free(p7); - X509_free(scert); + CMS_ContentInfo_free(cms); + X509_free(scert); BIO_free(sbio); BIO_free(tbio); BIO_free(obio); - + return output; } @@ -295,10 +356,10 @@ { NGMimeMessageParser *parser; NGMimeMessage *message; - NSData *decryptedData; + NSData *decryptedData; NGMimeType *contentType; NSString *type, *subtype, *smimetype; - + decryptedData = [self decryptUsingCertificate: theCertificate]; parser = [[NGMimeMessageParser alloc] init]; message = [parser parsePartFromData: decryptedData]; @@ -329,15 +390,15 @@ { NSData *output = NULL; - BIO *sbio, *obio; + BIO *sbio, *obio = NULL; BUF_MEM *bptr; - PKCS7 *p7 = NULL; + CMS_ContentInfo *cms = NULL; sbio = BIO_new_mem_buf((void *)[self bytes], [self length]); - p7 = SMIME_read_PKCS7(sbio, NULL); + cms = SMIME_read_CMS(sbio, NULL); - if (!p7) + if (!cms) { NSLog(@"FATAL: could not read the signature"); goto cleanup; @@ -346,7 +407,7 @@ // We output the S/MIME encrypted message obio = BIO_new(BIO_s_mem()); - if (!PKCS7_verify(p7, NULL, NULL, NULL, obio, PKCS7_NOVERIFY|PKCS7_NOSIGS)) + if (!CMS_verify(cms, NULL, NULL, NULL, obio, CMS_NOVERIFY|CMS_NOSIGS)) { NSLog(@"FATAL: could not extract content"); goto cleanup; @@ -357,7 +418,7 @@ output = [NSData dataWithBytes: bptr->data length: bptr->length]; cleanup: - PKCS7_free(p7); + CMS_ContentInfo_free(cms); BIO_free(sbio); BIO_free(obio); @@ -388,7 +449,7 @@ { NSData *output = NULL; - BIO *ibio, *obio; + BIO *ibio, *obio = NULL; EVP_PKEY *pkey; BUF_MEM *bptr; PKCS12 *p12; @@ -396,7 +457,7 @@ const char* bytes; int i, len; - + STACK_OF(X509) *ca = NULL; OpenSSL_add_all_algorithms(); @@ -405,7 +466,7 @@ bytes = [self bytes]; len = [self length]; ibio = BIO_new_mem_buf((void *)bytes, len); - + p12 = d2i_PKCS12_bio(ibio, NULL); if (!p12) @@ -419,7 +480,7 @@ NSLog(@"FATAL: could not parse PKCS12 certificate with provided password"); return nil; } - + // We output everything in PEM obio = BIO_new(BIO_s_mem()); @@ -433,7 +494,7 @@ { PEM_write_bio_X509(obio, cert); } - + if (ca && sk_X509_num(ca)) { for (i = 0; i < sk_X509_num(ca); i++) @@ -446,46 +507,50 @@ cleanup: PKCS12_free(p12); - BIO_free(ibio); - BIO_free(obio); - + BIO_free(ibio); + BIO_free(obio); + return output; } // // // -- (NSData *) signersFromPKCS7 +- (NSData *) signersFromCMS { NSData *output = NULL; STACK_OF(X509) *certs = NULL; - BIO *ibio, *obio; + BIO *ibio, *obio = NULL, *dummybio = NULL; BUF_MEM *bptr; - PKCS7 *p7; + CMS_ContentInfo *cms; const char* bytes; int i, len; - + OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); bytes = [self bytes]; len = [self length]; ibio = BIO_new_mem_buf((void *)bytes, len); - - p7 = d2i_PKCS7_bio(ibio, NULL); - if (!p7) + cms = d2i_CMS_bio(ibio, NULL); + + if (!cms) { - NSLog(@"FATAL: could not read PKCS7 content"); + NSLog(@"FATAL: could not read CMS content"); goto cleanup; } - + + // before calling CMS_get0_signers(), CMS_verify() must be called + dummybio = BIO_new(BIO_s_mem()); + CMS_verify(cms, NULL, NULL, dummybio, NULL, CMS_NO_SIGNER_CERT_VERIFY | CMS_NO_ATTR_VERIFY | CMS_NO_CONTENT_VERIFY); + ERR_clear_error(); + // We output everything in PEM obio = BIO_new(BIO_s_mem()); - - certs = PKCS7_get0_signers(p7, NULL, 0); + certs = CMS_get0_signers(cms); if (certs != NULL) { X509 *x; @@ -503,13 +568,93 @@ output = [NSData dataWithBytes: bptr->data length: bptr->length]; cleanup: - PKCS7_free(p7); - BIO_free(ibio); - BIO_free(obio); - + CMS_ContentInfo_free(cms); + BIO_free(dummybio); + BIO_free(ibio); + BIO_free(obio); + return output; } +// Implementation based on "STACK_OF(X509_ALGOR) *PKCS7_get_smimecap(PKCS7_SIGNER_INFO *si)" +STACK_OF(X509_ALGOR) *CMS_get_smimecap(CMS_SignerInfo *si) +{ + X509_ATTRIBUTE *attr; + ASN1_TYPE *cap; + const unsigned char *p; + + attr = CMS_signed_get_attr(si, CMS_signed_get_attr_by_NID(si, NID_SMIMECapabilities, -1)); + if (!attr) + return NULL; + cap = X509_ATTRIBUTE_get0_type(attr, 0); + if (!cap || (cap->type != V_ASN1_SEQUENCE)) + return NULL; + p = cap->value.sequence->data; + return (STACK_OF(X509_ALGOR) *) + ASN1_item_d2i(NULL, &p, cap->value.sequence->length, + ASN1_ITEM_rptr(X509_ALGORS)); +} + +- (NSArray *) algosFromCMS +{ + NSMutableArray *algos = [[NSMutableArray alloc] initWithCapacity: 10]; + + STACK_OF(CMS_SignerInfo) *signerinfos; + BIO *ibio; + CMS_ContentInfo *cms; + + const ASN1_OBJECT *paobj; + int pptype; + int nid; + + const char* bytes; + int i, j, len; + + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + + bytes = [self bytes]; + len = [self length]; + ibio = BIO_new_mem_buf((void *)bytes, len); + + cms = d2i_CMS_bio(ibio, NULL); + + if (!cms) + { + NSLog(@"FATAL: could not read CMS content"); + goto cleanup; + } + + signerinfos = CMS_get0_SignerInfos(cms); + if (signerinfos != NULL) + { + for (i = 0; i < sk_CMS_SignerInfo_num(signerinfos); i++) + { + CMS_SignerInfo *si = sk_CMS_SignerInfo_value(signerinfos, i); + STACK_OF(X509_ALGOR) *smimecap = CMS_get_smimecap(si); + + if (smimecap != NULL) + { + for (j = 0; j < sk_X509_ALGOR_num(smimecap); j++) + { + X509_ALGOR_get0(&paobj, &pptype, NULL, sk_X509_ALGOR_value(smimecap, j)); + nid = OBJ_obj2nid(paobj); + // Of all ciphers commonly used for S/MIME only RC2 has a keylength parameter + // As RC2 is outdated it's ok to ignore all ciphers with parameter + if (nid != NID_undef && pptype == V_ASN1_UNDEF) + [algos addObject: [NSNumber numberWithInt: nid]]; + } + } + } + } + + cleanup: + CMS_ContentInfo_free(cms); + BIO_free(ibio); + + return algos; +} + /** * Extract usefull information from PEM certificate */ diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 9053dc049..d36dec8a5 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -1866,7 +1866,8 @@ static NSString *userAgent = nil; { NGMimeMessageGenerator *generator, *partGenerator; NGMimeMessage *mimeMessage; - NSData *certificate, *content; + NSData *certificate, *content, *p7s; + NSArray *algos; NGMutableHashMap *hashMap; NGMimeMessage *message; NSMutableData *d; @@ -1910,17 +1911,22 @@ static NSString *userAgent = nil; lookupName: @"Contacts" inContext: context acquire: NO]; - certificate = [[contactFolders certificateForEmail: theRecipient] signersFromPKCS7]; + p7s = [contactFolders certificateForEmail: theRecipient]; + certificate = [p7s signersFromCMS]; + algos = [p7s algosFromCMS]; } else - certificate = [[self mailAccountFolder] certificate]; + { + certificate = [[self mailAccountFolder] certificate]; + algos = nil; + } // We check if we have a valid certificate. We can have nil here coming from [[self mailAccountFolder] certificate]. // This can happen if one sends an encrypted mail, but actually never uploaded // a PKCS#12 file to SOGo for his/her own usage and we're trying to save an encrypted // version of the message in the current user's Sent folder if (certificate) - content = [content encryptUsingCertificate: certificate]; + content = [content encryptUsingCertificate: certificate andAlgos: algos]; } finish_smime: diff --git a/UI/Contacts/UIxContactActions.m b/UI/Contacts/UIxContactActions.m index aebebe9b1..1defa6c3c 100644 --- a/UI/Contacts/UIxContactActions.m +++ b/UI/Contacts/UIxContactActions.m @@ -160,7 +160,7 @@ if (pkcs7) { - data = [[pkcs7 signersFromPKCS7] certificateDescription]; + data = [[pkcs7 signersFromCMS] certificateDescription]; if (data) { response = [self responseWithStatus: 200 andJSONRepresentation: data]; diff --git a/UI/MailPartViewers/UIxMailPartEncryptedViewer.m b/UI/MailPartViewers/UIxMailPartEncryptedViewer.m index d4c1554dd..39703f0e6 100644 --- a/UI/MailPartViewers/UIxMailPartEncryptedViewer.m +++ b/UI/MailPartViewers/UIxMailPartEncryptedViewer.m @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #endif @@ -101,8 +101,8 @@ STACK_OF(X509) *certs; X509_STORE *x509Store; - BIO *msgBio, *obio; - PKCS7 *p7; + BIO *msgBio, *obio = NULL; + CMS_ContentInfo *cms; int err, i; ERR_clear_error(); @@ -110,21 +110,25 @@ msgBio = BIO_new_mem_buf ((void *) [signedData bytes], [signedData length]); output = NULL; - p7 = SMIME_read_PKCS7(msgBio, NULL); + cms = SMIME_read_CMS(msgBio, NULL); certs = NULL; certificates = [NSMutableArray array]; emails = [NSMutableArray array]; validationMessage = nil; - if (p7) + if (cms) { - if (OBJ_obj2nid(p7->type) == NID_pkcs7_signed) + if (OBJ_obj2nid(CMS_get0_type(cms)) == NID_pkcs7_signed) { NSString *subject, *issuer; X509 *x; - certs = p7->d.sign->cert; + BIO *dummybio = BIO_new(BIO_s_mem()); + CMS_verify(cms, NULL, NULL, dummybio, NULL, CMS_NO_SIGNER_CERT_VERIFY | CMS_NO_ATTR_VERIFY | CMS_NO_CONTENT_VERIFY); + ERR_clear_error(); + BIO_free(dummybio); + certs = CMS_get0_signers(cms); for (i = 0; i < sk_X509_num(certs); i++) { @@ -171,7 +175,7 @@ x509Store = [self _setupVerify]; obio = BIO_new(BIO_s_mem()); - validSignature = (PKCS7_verify(p7, NULL, x509Store, NULL, + validSignature = (CMS_verify(cms, NULL, x509Store, NULL, obio, 0) == 1); err = ERR_get_error(); @@ -214,7 +218,7 @@ } } - PKCS7_free(p7); + CMS_ContentInfo_free(cms); BIO_free (msgBio); BIO_free (obio); diff --git a/UI/MailPartViewers/UIxMailPartSignedViewer.m b/UI/MailPartViewers/UIxMailPartSignedViewer.m index 0818c426b..0cd3b9cf0 100644 --- a/UI/MailPartViewers/UIxMailPartSignedViewer.m +++ b/UI/MailPartViewers/UIxMailPartSignedViewer.m @@ -21,7 +21,7 @@ #if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) #include #include -#include +#include #include #include #endif @@ -99,7 +99,7 @@ STACK_OF(X509) *certs; X509_STORE *x509Store; BIO *msgBio, *inData; - PKCS7 *p7; + CMS_ContentInfo *cms; int err, i; ERR_clear_error(); @@ -112,21 +112,25 @@ msgBio = BIO_new_mem_buf ((void *) [signedData bytes], [signedData length]); inData = NULL; - p7 = SMIME_read_PKCS7(msgBio, &inData); + cms = SMIME_read_CMS(msgBio, &inData); certs = NULL; certificates = [NSMutableArray array]; emails = [NSMutableArray array]; validationMessage = nil; - if (p7) + if (cms) { - if (OBJ_obj2nid(p7->type) == NID_pkcs7_signed) + if (OBJ_obj2nid(CMS_get0_type(cms)) == NID_pkcs7_signed) { NSString *subject, *issuer; X509 *x; - certs = PKCS7_get0_signers(p7, NULL, 0); + BIO *dummybio = BIO_new(BIO_s_mem()); + CMS_verify(cms, NULL, NULL, dummybio, NULL, CMS_NO_SIGNER_CERT_VERIFY | CMS_NO_ATTR_VERIFY | CMS_NO_CONTENT_VERIFY); + ERR_clear_error(); + BIO_free(dummybio); + certs = CMS_get0_signers(cms); for (i = 0; i < sk_X509_num(certs); i++) { @@ -172,7 +176,7 @@ else { x509Store = [self _setupVerify]; - validSignature = (PKCS7_verify(p7, NULL, x509Store, inData, + validSignature = (CMS_verify(cms, NULL, x509Store, inData, NULL, PKCS7_DETACHED) == 1); err = ERR_get_error(); @@ -205,6 +209,7 @@ } + CMS_ContentInfo_free(cms); BIO_free (msgBio); if (inData) BIO_free (inData);