org.waveprotocol.wave.examples.fedone.waveserver.CertificateManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.waveprotocol.wave.examples.fedone.waveserver.CertificateManagerImpl.java

Source

/**
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.waveprotocol.wave.examples.fedone.waveserver;

import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;

import org.waveprotocol.wave.crypto.CertPathStore;
import org.waveprotocol.wave.crypto.SignatureException;
import org.waveprotocol.wave.crypto.SignerInfo;
import org.waveprotocol.wave.crypto.UnknownSignerException;
import org.waveprotocol.wave.crypto.WaveSignatureVerifier;
import org.waveprotocol.wave.examples.fedone.util.Log;
import org.waveprotocol.wave.federation.FederationErrorProto.FederationError;
import org.waveprotocol.wave.federation.FederationErrors;
import org.waveprotocol.wave.federation.Proto.ProtocolHashedVersion;
import org.waveprotocol.wave.federation.Proto.ProtocolSignature;
import org.waveprotocol.wave.federation.Proto.ProtocolSignedDelta;
import org.waveprotocol.wave.federation.Proto.ProtocolSignerInfo;
import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.waveserver.federation.WaveletFederationProvider;
import org.waveprotocol.wave.waveserver.federation.WaveletFederationProvider.DeltaSignerInfoResponseListener;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Default implementation of {@link CertificateManager}.
 */
public class CertificateManagerImpl implements CertificateManager {

    private static final Log LOG = Log.get(CertificateManagerImpl.class);

    private final SignatureHandler waveSigner;
    private final WaveSignatureVerifier verifier;
    private final CertPathStore certPathStore;
    private final boolean disableVerfication;

    /**
     * Map of signer ids to requests for the signer info for those ids.  Each signer id is mapped to
     * a multimap: a domain mapped to a list of callbacks for that domain, called when the signer info
     * is available for the signer id.  It is arranged by domain to facilitate the optimisation where
     * exactly 1 signer request is sent per domain.
     */
    private final Map<ByteString, Multimap<String, SignerInfoPrefetchResultListener>> signerInfoRequests;

    @Inject
    public CertificateManagerImpl(@Named("waveserver_disable_verification") boolean disableVerfication,
            SignatureHandler signer, WaveSignatureVerifier verifier, CertPathStore certPathStore) {
        this.disableVerfication = disableVerfication;
        this.waveSigner = signer;
        this.verifier = verifier;
        this.certPathStore = certPathStore;
        this.signerInfoRequests = Maps.newHashMap();

        if (disableVerfication) {
            LOG.warning("** SIGNATURE VERIFICATION DISABLED ** " + "see flag \"waveserver_disable_verification\"");
        }
    }

    @Override
    public Set<String> getLocalDomains() {

        // TODO: for now, we just support a single signer
        return ImmutableSet.of(waveSigner.getDomain());
    }

    @Override
    public SignatureHandler getLocalSigner() {
        return waveSigner;
    }

    @Override
    public ProtocolSignedDelta signDelta(ByteStringMessage<ProtocolWaveletDelta> delta) {

        // TODO: support extended address paths. For now, there will be exactly
        // one signature, and we don't support federated groups.
        Preconditions.checkState(delta.getMessage().getAddressPathCount() == 0);

        ProtocolSignedDelta.Builder signedDelta = ProtocolSignedDelta.newBuilder();

        signedDelta.setDelta(delta.getByteString());
        signedDelta.addAllSignature(waveSigner.sign(delta));
        return signedDelta.build();
    }

    @Override
    public ByteStringMessage<ProtocolWaveletDelta> verifyDelta(ProtocolSignedDelta signedDelta)
            throws SignatureException, UnknownSignerException {

        ByteStringMessage<ProtocolWaveletDelta> delta;
        try {
            delta = ByteStringMessage.from(ProtocolWaveletDelta.getDefaultInstance(), signedDelta.getDelta());
        } catch (InvalidProtocolBufferException e) {
            throw new IllegalArgumentException("signed delta does not contain valid delta", e);
        }

        if (disableVerfication) {
            return delta;
        }

        List<String> domains = getParticipantDomains(delta.getMessage());

        if (domains.size() != signedDelta.getSignatureCount()) {
            throw new SignatureException("found " + domains.size() + " domains in " + "extended address path, but "
                    + signedDelta.getSignatureCount() + " signatures.");
        }

        for (int i = 0; i < domains.size(); i++) {
            String domain = domains.get(i);
            ProtocolSignature signature = signedDelta.getSignature(i);
            verifySingleSignature(delta, signature, domain);
        }

        return delta;
    }

    /**
     * Verifies a single signature.
     * @param delta the payload that we're verifying the signature on.
     * @param signature the signature on the payload
     * @param domain the authority (domain name) that should have signed the
     *   payload.
     * @throws SignatureException if the signature doesn't verify.
     */
    private void verifySingleSignature(ByteStringMessage<ProtocolWaveletDelta> delta, ProtocolSignature signature,
            String domain) throws SignatureException, UnknownSignerException {
        verifier.verify(delta.getByteString().toByteArray(), signature, domain);
    }

    /**
     * Returns the domains of all the addresses in the extended address path.
     */
    private List<String> getParticipantDomains(ProtocolWaveletDelta delta) {
        Iterable<String> addresses = getExtendedAddressPath(delta);
        return getDeDupedDomains(addresses);
    }

    /**
     * Extracts the domains from user addresses, and removes duplicates.
     */
    private List<String> getDeDupedDomains(Iterable<String> addresses) {
        List<String> domains = Lists.newArrayList();
        for (String address : addresses) {
            String participantDomain = new ParticipantId(address).getDomain();
            if (!domains.contains(participantDomain)) {
                domains.add(participantDomain);
            }
        }
        return domains;
    }

    /**
     * Returns the extended address path, i.e., the addresses in the delta's
     * address path, plus the author of the delta.
     */
    private Iterable<String> getExtendedAddressPath(ProtocolWaveletDelta delta) {
        return Iterables.concat(delta.getAddressPathList(), ImmutableList.of(delta.getAuthor()));
    }

    @Override
    public synchronized void storeSignerInfo(ProtocolSignerInfo signerInfo) throws SignatureException {
        verifier.verifySignerInfo(new SignerInfo(signerInfo));
        certPathStore.putSignerInfo(signerInfo);
    }

    @Override
    public synchronized ProtocolSignerInfo retrieveSignerInfo(ByteString signerId) {
        SignerInfo signerInfo = certPathStore.getSignerInfo(signerId.toByteArray());
        // null is acceptable for retrieveSignerInfo.  The user of the certificate manager should call
        // prefetchDeltaSignerInfo for the mechanism to actually populate the certificate manager.
        return signerInfo == null ? null : signerInfo.toProtoBuf();
    }

    @Override
    public synchronized void prefetchDeltaSignerInfo(WaveletFederationProvider provider, ByteString signerId,
            WaveletName waveletName, ProtocolHashedVersion deltaEndVersion,
            SignerInfoPrefetchResultListener callback) {
        ProtocolSignerInfo signerInfo = retrieveSignerInfo(signerId);

        if (signerInfo != null) {
            callback.onSuccess(signerInfo);
        } else {
            enqueueSignerInfoRequest(provider, signerId, waveletName, deltaEndVersion, callback);
        }
    }

    /**
     * Enqueue a signer info request for a signed delta on a given domain.
     */
    private synchronized void enqueueSignerInfoRequest(final WaveletFederationProvider provider,
            final ByteString signerId, final WaveletName waveletName, ProtocolHashedVersion deltaEndVersion,
            SignerInfoPrefetchResultListener callback) {
        final String domain = waveletName.waveletId.getDomain();
        Multimap<String, SignerInfoPrefetchResultListener> domainCallbacks = signerInfoRequests.get(signerId);

        if (domainCallbacks == null) {
            domainCallbacks = ArrayListMultimap.create();
            signerInfoRequests.put(signerId, domainCallbacks);
        }

        // The thing is, we need to add multiple callbacks for the same domain, but we only want to
        // have one outstanding request per domain
        domainCallbacks.put(domain, callback);

        if (domainCallbacks.get(domain).size() == 1) {
            provider.getDeltaSignerInfo(signerId, waveletName, deltaEndVersion,
                    new DeltaSignerInfoResponseListener() {
                        @Override
                        public void onFailure(FederationError error) {
                            LOG.warning("getDeltaSignerInfo failed: " + error);
                            // Fail all requests on this domain
                            dequeueSignerInfoRequestForDomain(signerId, error, domain);
                        }

                        @Override
                        public void onSuccess(ProtocolSignerInfo signerInfo) {
                            try {
                                storeSignerInfo(signerInfo);
                                dequeueSignerInfoRequest(signerId, null);
                            } catch (SignatureException e) {
                                LOG.warning("Failed to verify signer info", e);
                                dequeueSignerInfoRequest(signerId, FederationErrors.badRequest(e.toString()));
                            }
                        }
                    });
        }
    }

    /**
     * Dequeue all signer info requests for a given signer id.
     *
     * @param signerId to dequeue requests for
     * @param error if there was an error, null for success
     */
    private synchronized void dequeueSignerInfoRequest(ByteString signerId, FederationError error) {
        List<String> domains = ImmutableList.copyOf(signerInfoRequests.get(signerId).keySet());
        for (String domain : domains) {
            dequeueSignerInfoRequestForDomain(signerId, error, domain);
        }
    }

    /**
     * Dequeue all signer info requests for a given signer id and a specific domain.
     *
     * @param signerId to dequeue requests for
     * @param error if there was an error, null for success
     * @param domain to dequeue the signer requests for
     */
    private synchronized void dequeueSignerInfoRequestForDomain(ByteString signerId, FederationError error,
            String domain) {
        Multimap<String, SignerInfoPrefetchResultListener> domainListeners = signerInfoRequests.get(signerId);
        if (domainListeners == null) {
            LOG.info("There are no domain listeners for signer " + signerId + " domain " + domain);
            return;
        } else {
            LOG.info("Dequeuing " + domainListeners.size() + " listeners for domain " + domain);
        }

        for (SignerInfoPrefetchResultListener listener : domainListeners.get(domain)) {
            if (error == null) {
                listener.onSuccess(retrieveSignerInfo(signerId));
            } else {
                listener.onFailure(error);
            }
        }

        domainListeners.removeAll(domain);
        if (domainListeners.isEmpty()) {
            // No listeners for any domains, delete the signer id for the overall map
            signerInfoRequests.remove(signerId);
        }
    }
}