Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZCS-13176 : Add mail recall message verification header #1468

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

shubhamCS03
Copy link
Contributor

@shubhamCS03 shubhamCS03 commented Mar 23, 2023

Requirement of ticket [ZCS-13176]

Requirements:

  • To make sure we don't recall a message accidentally or spoofed, we should add a header to outgoing email.
  • We need to a header field, Message-Verification , and verify the message-Id while recalling a mail.
  • Need to Hash data by using algorithm SHA256 and further encrypt the data by using base64.

Solution:

  • A new header field is added while sending mail, Message-Verification which contains hash(algorithm name) and guid(base64 of the guid's hash).
  • The Message-Verification header field contains the following required tags, separated by semicolons (with no white space within):
    hash=[string] --the algorithm used to produce the hash value in the "guid" tag. Current valid values are "SHA1" and "SHA256" [[SHA]].
    guid=[base64] --the base64 encoding [[Base64]] of the hash of a globally unique ID (GUID) for the message, using the hash algorithm specified in the "hash" tag. The actual GUID (the pre-image of the hash) is kept secret by the sending side.
  • We have created one LDAP attribute secretKeyForMailRecall at global level to store secretKey.
  • We have created one SOAP API to generate secretKey.
  • We have generate the secretKey using this SHA1PRNG and saved in LDAP attribute secretKeyForMailRecall .
  • GUID(globally unique ID) = Message-Id + Date + From + secretKey.

Testing:

  • Changes are tested by sending an email and by checking its show original in the header field we are able to see Message-Verification .
  • Also tested by decrypting the hash by using base64 we get the same value.

@CLAassistant
Copy link

CLAassistant commented Mar 23, 2023

CLA assistant check
All committers have signed the CLA.

@shubhamCS03 shubhamCS03 force-pushed the ZCS-13176 branch 3 times, most recently from d156509 to 01c1c45 Compare March 23, 2023 11:34
@shubhamCS03 shubhamCS03 marked this pull request as ready for review March 23, 2023 11:50
store/src/java/com/zimbra/cs/mailbox/MailSender.java Outdated Show resolved Hide resolved
store/src/java/com/zimbra/cs/mailbox/MailSender.java Outdated Show resolved Hide resolved
store/src/java/com/zimbra/cs/mailbox/MailSender.java Outdated Show resolved Hide resolved
store/src/java/com/zimbra/cs/mailbox/MailSender.java Outdated Show resolved Hide resolved
store/src/java/com/zimbra/cs/mailbox/MailSender.java Outdated Show resolved Hide resolved
store/conf/attrs/zimbra-attrs.xml Outdated Show resolved Hide resolved
@swaatiTelus
Copy link
Contributor

The mail recall feature is not planned in the upcoming release so this PR should not be merged.

*/
public static String generateRandomString() throws ServiceException {
try {
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using:
SecureRandom random = new SecureRandom();

https://stackoverflow.com/questions/27622625/securerandom-with-nativeprng-vs-sha1prng

SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
byte[] key = new byte[KEY_SIZE_BYTES];
random.nextBytes(key);
return new String(Hex.encodeHex(key));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using Base64 for encoding to make the key more compact and URL-safe.

try {
MessageDigest md = MessageDigest.getInstance(MSGVRFY_ALGORITHM_NAME);
byte[] messageDigest = md.digest(input.getBytes(StandardCharsets.UTF_8));
BigInteger number = new BigInteger(1, messageDigest);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You convert the BigInteger to a string and then encode it to Base64. This could be simplified:
return Base64.getEncoder().encodeToString(messageDigest);

*/
public static String getMessageVerificationHeaderValue(String id, String date, String from) throws MessagingException, ServiceException {
String secretKey = Provisioning.getInstance().getConfig().getFeatureMailRecallSecretKey();
String guid = (id + date + from + secretKey);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated use of the + operator can degrade performance as strings are immutable in Java. Each concatenation creates a new String object, leaving old strings for garbage collection, which can increase memory usage and slow down execution. Leverage Java 8 and above features.

String guidHash = getHashForMessageVerification(guid);
String hash = MSGVRFY_HEADER_PREFIX + guidHash;
return hash;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can use Optional.ofNullable(...).orElseThrow(...) to ensure secretKey is not null and throw an exception if it is, to avoid potential null pointer and String.join(...) for concatenation.

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use implicit imports

import com.zimbra.cs.account.Domain;
import com.zimbra.cs.account.Identity;
import com.zimbra.cs.account.Provisioning;
import com.zimbra.cs.account.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use implicit imports

Date date = new Date();
mm.setSentDate(date);

String value = SecretKey.getMessageVerificationHeaderValue(mm.getMessageID(), mailDateFormat.format(date), from.getAddress());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the code more expressive, and ensure it's null-safe by leveraging Java 8+ features such as Optional, method references etc.

}
} catch (ServiceException e) {
throw ServiceException.FAILURE("Unable to initialize SecureRandom for mail recall", e);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please optimize the code to improve the readability and efficiency of the code. Consider using Java 8 and above features such as:

  • Optional to wrap the value and filter it to only proceed if it equals TRUE,
  • method references,
  • try-with-resources
  • lambda expression to encapsulate the logic for handling the secret key and its invocation, to reduce nested conditions, improve resource management.

Also, consider catching specific exceptions.

} catch (ServiceException e) {
ZimbraLog.misc.warn("Encountered exception during FlushCache after creating Secret Key", e);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using Try-with-Resources

while (hexString.length() < 64) {
hexString.insert(0, '0');
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use String.format("%064x", ...) to format the BigInteger directly into a zero-padded hexadecimal string of the desired length and remove StringBuilder.

@@ -303,6 +303,10 @@ public static ServiceException LICENSE_ERROR(String message, Throwable cause) {
return new ServiceException("license error: "+message, LICENSE_ERROR, RECEIVERS_FAULT, cause);
}

public static ServiceException ERROR_MESSAGE(String str, Throwable cause){
return new ServiceException("error: " + str, null, SENDERS_FAULT, cause);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using + operator for String concatenation, consider using String.format or formatted if upgraded to Java 15 or above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.