/* * * * Copyright (C) 2014 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * / */ package org.anhonesteffort.flock.sync.addressbook; import ezvcard.Ezvcard; import ezvcard.VCard; import ezvcard.parameter.ImageType; import ezvcard.property.Photo; import ezvcard.property.RawProperty; import ezvcard.property.StructuredName; import org.anhonesteffort.flock.util.guava.Optional; import org.anhonesteffort.flock.sync.DecryptedMultiStatusResult; import org.anhonesteffort.flock.sync.InvalidLocalComponentException; import org.anhonesteffort.flock.sync.InvalidRemoteComponentException; import org.anhonesteffort.flock.webdav.MultiStatusResult; import org.anhonesteffort.flock.webdav.carddav.CardDavConstants; import org.anhonesteffort.flock.crypto.InvalidMacException; import org.anhonesteffort.flock.crypto.MasterCipher; import org.anhonesteffort.flock.sync.HidingDavCollection; import org.anhonesteffort.flock.sync.HidingDavCollectionMixin; import org.anhonesteffort.flock.crypto.HidingUtil; import org.anhonesteffort.flock.webdav.ComponentETagPair; import org.anhonesteffort.flock.webdav.InvalidComponentException; import org.anhonesteffort.flock.webdav.PropertyParseException; import org.anhonesteffort.flock.webdav.carddav.CardDavCollection; import org.anhonesteffort.flock.webdav.carddav.CardDavStore; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.LinkedList; import java.util.List; /** * Programmer: rhodey */ public class HidingCardDavCollection extends CardDavCollection implements HidingDavCollection<VCard> { private static final String TAG = "org.anhonesteffort.flock.sync.addressbook.HidingCardDavCollection"; private static final String PROPERTY_NAME_FLOCK_HIDDEN = "X-FLOCK-HIDDEN"; private static final String PARAMETER_NAME_FLOCK_HIDDEN_PHOTO = "X-FLOCK-HIDDEN-PHOTO"; private MasterCipher masterCipher; private HidingDavCollectionMixin delegate; protected HidingCardDavCollection(CardDavStore cardDavStore, String path, MasterCipher masterCipher) { super(cardDavStore, path); this.masterCipher = masterCipher; this.delegate = new HidingDavCollectionMixin(this, masterCipher); } protected HidingCardDavCollection(CardDavCollection cardDavCollection, MasterCipher masterCipher) { super((CardDavStore) cardDavCollection.getStore(), cardDavCollection.getPath(), cardDavCollection.getProperties()); this.masterCipher = masterCipher; this.delegate = new HidingDavCollectionMixin(this, masterCipher); } @Override protected DavPropertyNameSet getPropertyNamesForFetch() { DavPropertyNameSet addressbookProps = super.getPropertyNamesForFetch(); DavPropertyNameSet hidingProps = delegate.getPropertyNamesForFetch(); addressbookProps.addAll(hidingProps); return addressbookProps; } @Override public boolean isFlockCollection() throws PropertyParseException { return delegate.isFlockCollection(); } @Override public void makeFlockCollection(String displayName) throws DavException, IOException, GeneralSecurityException { delegate.makeFlockCollection(displayName, new DavPropertyNameSet()); } @Override public Optional<String> getHiddenDisplayName() throws PropertyParseException, InvalidMacException, GeneralSecurityException, IOException { return delegate.getHiddenDisplayName(); } @Override public void setHiddenDisplayName(String displayName) throws DavException, IOException, GeneralSecurityException { delegate.setHiddenDisplayName(displayName); } protected ComponentETagPair<VCard> getHiddenComponent(ComponentETagPair<VCard> exposedComponentPair) throws InvalidRemoteComponentException, InvalidMacException, GeneralSecurityException, IOException { VCard exposedVCard = exposedComponentPair.getComponent(); RawProperty protectedVCard = exposedVCard.getExtendedProperty(PROPERTY_NAME_FLOCK_HIDDEN); if (protectedVCard == null) return exposedComponentPair; String recoveredVCardText = HidingUtil.decodeAndDecryptIfNecessary(masterCipher, protectedVCard.getValue()); try { VCard recoveredVCard = Ezvcard.parse(recoveredVCardText.replace("\\n", "\n")).first(); if (exposedVCard.getPhotos().size() > 0) { Photo protectedPhoto = exposedVCard.getPhotos().get(0); String parameterFlockHiddenPhoto = protectedPhoto.getParameter(PARAMETER_NAME_FLOCK_HIDDEN_PHOTO); if (parameterFlockHiddenPhoto != null && parameterFlockHiddenPhoto.equals("true")) { byte[] recoveredPhotoData = HidingUtil.decodeAndDecryptIfNecessary(masterCipher, protectedPhoto.getData()); Photo recoveredPhoto = new Photo(recoveredPhotoData, ImageType.PNG); recoveredVCard.addPhoto(recoveredPhoto); } } return new ComponentETagPair<VCard>(recoveredVCard, exposedComponentPair.getETag()); } catch (RuntimeException e) { if (exposedVCard.getUid() != null) { throw new InvalidRemoteComponentException("caught exception while parsing vcard from multi-status response", CardDavConstants.CARDDAV_NAMESPACE, getPath(), exposedVCard.getUid().getValue(), e); } throw new InvalidRemoteComponentException("caught exception while parsing vcard from multi-status response", CardDavConstants.CARDDAV_NAMESPACE, getPath(), e); } } @Override public Optional<ComponentETagPair<VCard>> getHiddenComponent(String uid) throws InvalidRemoteComponentException, DavException, InvalidMacException, GeneralSecurityException, IOException { try { Optional<ComponentETagPair<VCard>> originalComponentPair = super.getComponent(uid); if (!originalComponentPair.isPresent()) return Optional.absent(); return Optional.of(getHiddenComponent(originalComponentPair.get())); } catch (InvalidComponentException e) { throw new InvalidRemoteComponentException(e); } } @Override public DecryptedMultiStatusResult<VCard> getHiddenComponents(List<String> uids) throws DavException, GeneralSecurityException, IOException { MultiStatusResult<VCard> exposedComponentPairs = super.getComponents(uids); DecryptedMultiStatusResult<VCard> recoveredComponentPairs = new DecryptedMultiStatusResult<VCard>( new LinkedList<ComponentETagPair<VCard>>(), exposedComponentPairs.getInvalidComponentExceptions(), new LinkedList<InvalidMacException>() ); for (ComponentETagPair<VCard> exposedComponentPair : exposedComponentPairs.getComponentETagPairs()) { try { recoveredComponentPairs.getComponentETagPairs().add(getHiddenComponent(exposedComponentPair)); } catch (InvalidRemoteComponentException e) { recoveredComponentPairs.getInvalidComponentExceptions().add(e); } catch (InvalidMacException e) { recoveredComponentPairs.getInvalidMacExceptions().add(e); } } return recoveredComponentPairs; } @Override public DecryptedMultiStatusResult<VCard> getHiddenComponents() throws DavException, GeneralSecurityException, IOException { MultiStatusResult<VCard> exposedComponentPairs = super.getComponents(); DecryptedMultiStatusResult<VCard> recoveredComponentPairs = new DecryptedMultiStatusResult<VCard>( new LinkedList<ComponentETagPair<VCard>>(), exposedComponentPairs.getInvalidComponentExceptions(), new LinkedList<InvalidMacException>() ); for (ComponentETagPair<VCard> exposedComponentPair : exposedComponentPairs.getComponentETagPairs()) { try { recoveredComponentPairs.getComponentETagPairs().add(getHiddenComponent(exposedComponentPair)); } catch (InvalidRemoteComponentException e) { recoveredComponentPairs.getInvalidComponentExceptions().add(e); } catch (InvalidMacException e) { recoveredComponentPairs.getInvalidMacExceptions().add(e); } } return recoveredComponentPairs; } protected void putHiddenComponentToServer(VCard exposedVCard, Optional<String> ifMatchETag) throws InvalidLocalComponentException, GeneralSecurityException, IOException, DavException { if (exposedVCard.getUid() == null) throw new InvalidLocalComponentException("Cannot put a VCard to server without UID!", CardDavConstants.CARDDAV_NAMESPACE, getPath()); VCard protectedVCard = new VCard(); protectedVCard.setVersion(exposedVCard.getVersion()); protectedVCard.setUid(exposedVCard.getUid()); StructuredName structuredName = new StructuredName(); structuredName.setGiven("Open"); structuredName.addAdditional("Whisper"); structuredName.setFamily("Systems"); protectedVCard.setStructuredName(structuredName); protectedVCard.setFormattedName("Open Whisper Systems"); if (exposedVCard.getPhotos().size() > 0) { Photo exposedPhoto = exposedVCard.getPhotos().get(0); byte[] protectedPhotoData = HidingUtil.encryptEncodeAndPrefix(masterCipher, exposedPhoto.getData()); Photo protectedPhoto = new Photo(protectedPhotoData, ImageType.PNG); exposedVCard.removeProperties(Photo.class); protectedPhoto.addParameter(PARAMETER_NAME_FLOCK_HIDDEN_PHOTO, "true"); protectedVCard.addPhoto( protectedPhoto); } protectedVCard.addExtendedProperty(PROPERTY_NAME_FLOCK_HIDDEN, HidingUtil.encryptEncodeAndPrefix(masterCipher, Ezvcard.write(exposedVCard).go())); try { super.putComponentToServer(protectedVCard, ifMatchETag); } catch (InvalidComponentException e) { throw new InvalidLocalComponentException(e); } } @Override public void addHiddenComponent(VCard component) throws InvalidLocalComponentException, DavException, GeneralSecurityException, IOException { putHiddenComponentToServer(component, Optional.<String>absent()); } @Override public void updateHiddenComponent(ComponentETagPair<VCard> component) throws InvalidLocalComponentException, DavException, GeneralSecurityException, IOException { putHiddenComponentToServer(component.getComponent(), component.getETag()); } @Override public void closeHttpConnection() { getStore().closeHttpConnection(); } }