Skip to content

Commit

Permalink
Add support for retrieving user certificates
Browse files Browse the repository at this point in the history
It's possible to add a public S/MIME certificate for a user to the GAL. These
are used when a user wants to encrypt a mail to another user, or validate their
signature. The public certificate of the recipient is required. Being able to
look them up rather than engage in a manual or offline synchornisation process
makes this easier, as well as fetching updated certificates when they're
changed.

The bulk of this work was done by @krutelp in
#98 I merely extended it to support the
UserSMIMECertificate field in addition to the MSExchangeCertificate field.

These are both part of the EWS Contact:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.exchange.webservices.data.contact?view=exchange-ews-api

I tried to do it without using ContactDataShape.AllProperties but like @krutelp
couldn't find a method that would return the certificates.

I extended the ignored tags based on what was returned by our Microsoft365
instance, but not all of the fields listed under the Contact type above.

I slightly modified the original PR to use Dos line endings in
ResolveNamesMethod.java so exact changes could be observed instead of the
entire file being changed.

I also added the keys to the contact in ExchangeSession.java as KEY1 and KEY2.

Finally, I undid the small changes in LdapConnection.java to the isMatch()
methods to have them take an ExchangeSession.Contact and put them back to
Map<String, String>. This was mostly done to limit the changes in the patch to
those necessary.
  • Loading branch information
singe committed May 13, 2024
1 parent 3b79fdb commit c08d243
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/java/davmail/exchange/ExchangeSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -2020,6 +2020,9 @@ public String getBody() {
writer.writeLine("PHOTO;TYPE=" + contactPhoto.contentType + ";ENCODING=BASE64:");
writer.writeLine(contactPhoto.content, true);
}

writer.appendProperty("KEY1;X509;ENCODING=BASE64", get("msexchangecertificate"));
writer.appendProperty("KEY2;X509;ENCODING=BASE64", get("usersmimecertificate"));

writer.endCard();
return writer.toString();
Expand Down Expand Up @@ -2798,6 +2801,10 @@ protected ItemResult createOrUpdateContact(String folderPath, String itemName, S
} else if ("PHOTO".equals(property.getKey())) {
properties.put("photo", property.getValue());
properties.put("haspicture", "true");
} else if ("KEY1".equals(property.getKey())) {
properties.put("msexchangecertificate", property.getValue());
} else if ("KEY2".equals(property.getKey())) {
properties.put("usersmimecertificate", property.getValue());
}
}
LOGGER.debug("Create or update contact " + itemName + ": " + properties);
Expand Down Expand Up @@ -3012,6 +3019,8 @@ public String getAlias() {
CONTACT_ATTRIBUTES.add("private");
CONTACT_ATTRIBUTES.add("sensitivity");
CONTACT_ATTRIBUTES.add("fburl");
CONTACT_ATTRIBUTES.add("msexchangecertificate");
CONTACT_ATTRIBUTES.add("usersmimecertificate");
}

protected static final Set<String> DISTRIBUTION_LIST_ATTRIBUTES = new HashSet<>();
Expand Down
2 changes: 2 additions & 0 deletions src/java/davmail/exchange/ews/EwsExchangeSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -2975,6 +2975,8 @@ protected void internalExecuteMethod(EWSMethod ewsMethod) throws IOException {

GALFIND_ATTRIBUTE_MAP.put("homePhone", "HomePhone");
GALFIND_ATTRIBUTE_MAP.put("pager", "Pager");
GALFIND_ATTRIBUTE_MAP.put("msexchangecertificate", "MSExchangeCertificate");
GALFIND_ATTRIBUTE_MAP.put("usersmimecertificate", "UserSMIMECertificate");
}

protected static final HashSet<String> IGNORE_ATTRIBUTE_SET = new HashSet<>();
Expand Down
3 changes: 3 additions & 0 deletions src/java/davmail/exchange/ews/Field.java
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ private Field() {
// attachments
FIELD_MAP.put("attachments", new UnindexedFieldURI("item:Attachments"));

// user certificate
FIELD_MAP.put("msexchangecertificate", new UnindexedFieldURI("contacts:MSExchangeCertificate"));
FIELD_MAP.put("usersmimecertificate", new UnindexedFieldURI("contacts:UserSMIMECertificate"));
}

/**
Expand Down
53 changes: 53 additions & 0 deletions src/java/davmail/exchange/ews/ResolveNamesMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public ResolveNamesMethod(String value) {
super("Contact", "ResolveNames", "ResolutionSet");
addMethodOption(SearchScope.ActiveDirectory);
addMethodOption(RETURN_FULL_CONTACT_DATA);
addMethodOption(ContactDataShape.AllProperties);
unresolvedEntry = new ElementOption("m:UnresolvedEntry", value);
}

Expand Down Expand Up @@ -88,6 +89,27 @@ protected void handleContact(XMLStreamReader reader, Item responseItem) throws X
handlePhysicalAddresses(reader, responseItem);
} else if ("PhoneNumbers".equals(tagLocalName)) {
handlePhoneNumbers(reader, responseItem);
} else if ("MSExchangeCertificate".equals(tagLocalName)
|| "UserSMIMECertificate".equals(tagLocalName)) {
handleUserCertificate(reader, responseItem, tagLocalName);
} else if ("ManagerMailbox".equals(tagLocalName)
|| "Attachments".equals(tagLocalName)
|| "Photo".equals(tagLocalName)
|| "Notes".equals(tagLocalName)
|| "HasPicture".equals(tagLocalName)
|| "DirectoryId".equals(tagLocalName)
|| "Alias".equals(tagLocalName)
|| "Categories".equals(tagLocalName)
|| "InternetMessageHeaders".equals(tagLocalName)
|| "ResponseObjects".equals(tagLocalName)
|| "ExtendedProperty".equals(tagLocalName)
|| "EffectiveRights".equals(tagLocalName)
|| "CompleteName".equals(tagLocalName)
|| "Children".equals(tagLocalName)
|| "Companies".equals(tagLocalName)
|| "ImAddresses".equals(tagLocalName)
|| "DirectReports".equals(tagLocalName)) {
skipTag(reader, tagLocalName);
} else {
responseItem.put(tagLocalName, XMLStreamUtil.getElementText(reader));
}
Expand Down Expand Up @@ -133,6 +155,37 @@ protected void handlePhoneNumbers(XMLStreamReader reader, Item responseItem) thr
}
}

protected void handleUserCertificate(XMLStreamReader reader, Item responseItem, String contextTagLocalName) throws XMLStreamException {
boolean firstValueRead = false;
String certificate = "";
while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, contextTagLocalName)) {
reader.next();
if (XMLStreamUtil.isStartTag(reader)) {
String tagLocalName = reader.getLocalName();
if ("Base64Binary".equals(tagLocalName)) {
String value = reader.getElementText();
if ((value != null) && !value.isEmpty()) {
if (!firstValueRead) {
// Only first certificate value will be read
certificate = value;
} else {
LOGGER.debug("ResolveNames multiple certificates found, tagLocaleName="
+ contextTagLocalName + " Certificate [" + value + "] ignored");
}
}
}
}
}
responseItem.put(contextTagLocalName, certificate);
}

protected void skipTag(XMLStreamReader reader, String tagLocalName) throws XMLStreamException {
LOGGER.debug("ResolveNames tag parsing skipped. tagLocalName=" + tagLocalName);
while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, tagLocalName)) {
reader.next();
}
}

@Override
protected void handleEmailAddresses(XMLStreamReader reader, Item responseItem) throws XMLStreamException {
while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, "EmailAddresses")) {
Expand Down
17 changes: 17 additions & 0 deletions src/java/davmail/ldap/LdapConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import davmail.exchange.ExchangeSessionFactory;
import davmail.exchange.dav.DavExchangeSession;
import davmail.ui.tray.DavGatewayTray;
import davmail.util.IOUtil;
import org.apache.log4j.Logger;

import javax.naming.InvalidNameException;
Expand Down Expand Up @@ -122,6 +123,8 @@ public class LdapConnection extends AbstractConnection {
CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeStreet", "mozillahomestreet");
CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("businesshomepage", "mozillaworkurl");
CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("nickname", "mozillanickname");
CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("msexchangecertificate", "msexchangecertificate;binary");
CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("usersmimecertificate", "usersmimecertificate;binary");
}

/**
Expand Down Expand Up @@ -287,6 +290,10 @@ public class LdapConnection extends AbstractConnection {

// iCal search attribute
LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-serviceslocator", "apple-serviceslocator");

LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("msexchangecertificate;binary", "msexchangecertificate");
LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("usersmimecertificate;binary", "usersmimecertificate");

}

/**
Expand Down Expand Up @@ -967,6 +974,8 @@ protected void sendEntry(int currentMessageId, String dn, Map<String, Object> at
for (Object value : (Iterable) values) {
responseBer.encodeString((String) value, isLdapV3());
}
} else if (values instanceof byte[]) {
responseBer.encodeOctetString((byte[])values, BerEncoder.ASN_OCTET_STR);
} else {
throw new DavMailException("EXCEPTION_UNSUPPORTED_VALUE", values);
}
Expand Down Expand Up @@ -1688,6 +1697,14 @@ protected void sendPersons(int currentMessageId, String baseContext, Map<String,
ldapPerson.put("apple-generateduid", ldapPerson.get("uid"));
}
}
if (ldapPerson.containsKey("msexchangecertificate;binary")) {
String certificate = (String) ldapPerson.get("msexchangecertificate;binary");
ldapPerson.put("msexchangecertificate;binary", IOUtil.decodeBase64(certificate));
}
if (ldapPerson.containsKey("usersmimecertificate;binary")) {
String certificate = (String) ldapPerson.get("usersmimecertificate;binary");
ldapPerson.put("usersmimecertificate;binary", IOUtil.decodeBase64(certificate));
}

// iCal: replace current user alias with login name
if (session.getAlias().equals(ldapPerson.get("uid"))) {
Expand Down

0 comments on commit c08d243

Please sign in to comment.