Package org.waveprotocol.box.server.waveserver

Source Code of org.waveprotocol.box.server.waveserver.CertificateManagerImpl

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.box.server.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.apache.commons.codec.binary.Hex;
import org.waveprotocol.box.server.CoreSettings;
import org.waveprotocol.box.server.common.CoreWaveletOperationSerializer;
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.federation.FederationErrors;
import org.waveprotocol.wave.federation.WaveletFederationProvider;
import org.waveprotocol.wave.federation.FederationErrorProto.FederationError;
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.federation.WaveletFederationProvider.DeltaSignerInfoResponseListener;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.util.logging.Log;

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

/**
* 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 ImmutableSet<String> localDomains;
  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(CoreSettings.WAVESERVER_DISABLE_VERIFICATION) boolean disableVerfication,
      SignatureHandler signer, WaveSignatureVerifier verifier, CertPathStore certPathStore) {
    this.disableVerfication = disableVerfication;
    this.waveSigner = signer;
    // for now, we just support a single signer
    this.localDomains = ImmutableSet.of(signer.getDomain());
    this.verifier = verifier;
    this.certPathStore = certPathStore;
    this.signerInfoRequests = Maps.newHashMap();

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

  @Override
  public ImmutableSet<String> getLocalDomains() {
    return localDomains;
  }

  @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.parseProtocolWaveletDelta(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;
    try {
      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();
    } catch (SignatureException e) {
      /*
       * TODO: This may result in the server endlessly requesting the signer info from the
       * remote server, a more graceful failure needs to be implemented.
       */
      LOG.severe("Failed to retreive signer info for "
          + new String(Hex.encodeHex(signerId.toByteArray())), e);
      return null;
    }
  }

  @Override
  public synchronized void prefetchDeltaSignerInfo(WaveletFederationProvider provider,
      ByteString signerId, WaveletName waveletName, HashedVersion 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,
      HashedVersion 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 == null)
                ? null : CoreWaveletOperationSerializer.serialize(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);
    }
  }
}
TOP

Related Classes of org.waveprotocol.box.server.waveserver.CertificateManagerImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.