Package org.waveprotocol.box.server.waveserver

Source Code of org.waveprotocol.box.server.waveserver.CertificateManagerImplTest$WaveletSignerInfoProvider

/**
* 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 static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.waveprotocol.box.server.waveserver.Ticker.EASY_TICKS;

import com.google.common.collect.ImmutableList;
import com.google.protobuf.ByteString;

import junit.framework.TestCase;

import org.waveprotocol.box.server.common.CoreWaveletOperationSerializer;
import org.waveprotocol.box.server.persistence.memory.MemoryStore;
import org.waveprotocol.box.server.waveserver.CertificateManager.SignerInfoPrefetchResultListener;
import org.waveprotocol.box.server.waveserver.testing.Certificates;
import org.waveprotocol.wave.crypto.CachedCertPathValidator;
import org.waveprotocol.wave.crypto.CertPathStore;
import org.waveprotocol.wave.crypto.DefaultCacheImpl;
import org.waveprotocol.wave.crypto.DefaultTrustRootsProvider;
import org.waveprotocol.wave.crypto.DisabledCertPathValidator;
import org.waveprotocol.wave.crypto.SignatureException;
import org.waveprotocol.wave.crypto.SignerInfo;
import org.waveprotocol.wave.crypto.TimeSource;
import org.waveprotocol.wave.crypto.TrustRootsProvider;
import org.waveprotocol.wave.crypto.UnknownSignerException;
import org.waveprotocol.wave.crypto.VerifiedCertChainCache;
import org.waveprotocol.wave.crypto.WaveCertPathValidator;
import org.waveprotocol.wave.crypto.WaveSignatureVerifier;
import org.waveprotocol.wave.crypto.WaveSigner;
import org.waveprotocol.wave.crypto.WaveSignerFactory;
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.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.federation.Proto.ProtocolSignature.SignatureAlgorithm;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.version.HashedVersion;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

public class CertificateManagerImplTest extends TestCase {

  private static final String OTHER_DOMAIN = "other.org";

  private static final FederationError GENERIC_ERROR =
      FederationErrors.badRequest("It's not my fault!");

  private CertPathStore store;
  private CertificateManager manager;
  private Ticker ticker;

  /*
   * These belong to the example.com domain.
   */
  public static final String DOMAIN = "example.com";

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    store = new MemoryStore();
    manager = new CertificateManagerImpl(false, getSigner(), getVerifier(store, true), store);
    ticker = new Ticker();
  }

  /*
   * TESTS
   */
  public void testSignature() throws Exception {
    ProtocolWaveletDelta delta = ProtocolWaveletDelta.newBuilder()
        .setHashedVersion(getProtocolHashedVersion())
        .setAuthor("bob@example.com")
        .build();
    ByteStringMessage<ProtocolWaveletDelta> canonicalDelta = ByteStringMessage.serializeMessage(delta);

    ProtocolSignedDelta signedDelta = manager.signDelta(canonicalDelta);

    manager.storeSignerInfo(getSignerInfo().toProtoBuf());
    ByteStringMessage<ProtocolWaveletDelta> compare = manager.verifyDelta(signedDelta);

    assertEquals(canonicalDelta, compare);
  }

  public void testSignature_missingSignerInfo() throws Exception {
    ProtocolWaveletDelta delta = ProtocolWaveletDelta.newBuilder()
        .setHashedVersion(getProtocolHashedVersion())
        .setAuthor("bob@example.com")
        .build();
    ByteStringMessage<ProtocolWaveletDelta> canonicalDelta = ByteStringMessage.serializeMessage(delta);
    manager = new CertificateManagerImpl(false, getSigner(), getVerifier(store, false), store);
    ProtocolSignedDelta signedDelta = manager.signDelta(canonicalDelta);

    try {
      manager.verifyDelta(signedDelta);
      fail("expected UnknownSignerException, but didn't get it");
    } catch (UnknownSignerException e) {
      // expected
    } catch (Exception e) {
      fail("expected UnknownSignerExeception, but got " + e);
    }
  }

  public void testSignature_authorNotMatching() throws Exception {
    ProtocolWaveletDelta delta = ProtocolWaveletDelta.newBuilder()
        .setHashedVersion(getProtocolHashedVersion())
        .setAuthor("bob@someotherdomain.com")
        .build();
    ByteStringMessage<ProtocolWaveletDelta> canonicalDelta = ByteStringMessage.serializeMessage(delta);

    ProtocolSignedDelta signedDelta = manager.signDelta(canonicalDelta);

    manager.storeSignerInfo(getSignerInfo().toProtoBuf());

    try {
      manager.verifyDelta(signedDelta);
      fail("expected exception, but didn't get it");
    } catch (SignatureException e) {
      // expected
    }
  }

  public void testRealSignature() throws Exception {
    manager = new CertificateManagerImpl(false, getSigner(), getRealVerifier(store), store);
    manager.storeSignerInfo(Certificates.getRealSignerInfo().toProtoBuf());
    ByteStringMessage<ProtocolWaveletDelta> compare = manager.verifyDelta(getFakeSignedDelta());
    assertEquals(compare, getFakeDelta());
  }

  /**
   * Test prefetchDeltaSignerInfo for a single request on a single domain, and that subsequent
   * requests on the same domain return instantly.
   */
  public void test_prefetchDeltaSignerInfo1() throws Exception {
    SignerInfoPrefetchResultListener mockListener = mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(getSuccessfulProvider(), getRealSignerId(),
        getFakeWaveletName(CertificateManagerImplTest.DOMAIN), null, mockListener);
    verify(mockListener).onSuccess(Certificates.getRealSignerInfo().toProtoBuf());

    // Shouldn't get a NPE from the null provider because the callback should not be used

    manager.prefetchDeltaSignerInfo(null, getRealSignerId(), getFakeWaveletName(CertificateManagerImplTest.DOMAIN), null,
        mockListener);
    verify(mockListener, times(2)).onSuccess(Certificates.getRealSignerInfo().toProtoBuf());
  }

  /**
   * Test prefetchDeltaSignerInfo for multiple requests on a single domain where the first one
   * does not terminate.  The entire request should fail.
   */
  public void test_prefetchDeltaSignerInfo2() throws Exception {
    // The dead listener won't return
    SignerInfoPrefetchResultListener deadListener = mock(SignerInfoPrefetchResultListener.class);
    manager.prefetchDeltaSignerInfo(getDeadProvider(), getRealSignerId(),
        getFakeWaveletName(CertificateManagerImplTest.DOMAIN), null, deadListener);
    verifyZeroInteractions(deadListener);

    // But this will.  However, it shouldn't be called since the other was added first, and only
    // 1 request is started per domain
    SignerInfoPrefetchResultListener aliveListener = mock(SignerInfoPrefetchResultListener.class);
    manager.prefetchDeltaSignerInfo(getSuccessfulProvider(), getRealSignerId(),
        getFakeWaveletName(CertificateManagerImplTest.DOMAIN), null, aliveListener);

    verifyZeroInteractions(aliveListener);
  }

  /**
   * Test prefetchDeltaSignerInfo for multiple requests on different domains where the first one
   * does not terminate.  However the second should terminate, and both callbacks called.
   */
  public void test_prefetchDeltaSignerInfo3() throws Exception {
    // This will never return, but the callback will run later
    SignerInfoPrefetchResultListener deadListener = mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(getDeadProvider(), getRealSignerId(),
        getFakeWaveletName(CertificateManagerImplTest.DOMAIN), null, deadListener);
    verifyZeroInteractions(deadListener);

    // This should succeed later, after some number of ticks
    SignerInfoPrefetchResultListener slowSuccessListener =
        mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(getSlowSuccessfulProvider(ticker, EASY_TICKS),
        getRealSignerId(), getFakeWaveletName(OTHER_DOMAIN), null, slowSuccessListener);
    verifyZeroInteractions(slowSuccessListener);

    // This would succeed right now if it didn't have to wait for the slow success
    SignerInfoPrefetchResultListener successListener = mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(getSuccessfulProvider(), getRealSignerId(),
        getFakeWaveletName(OTHER_DOMAIN), null, successListener);
    verifyZeroInteractions(successListener);

    // After ticking, each callback should run
    ticker.tick(EASY_TICKS);
    verify(deadListener).onSuccess(Certificates.getRealSignerInfo().toProtoBuf());
    verify(slowSuccessListener).onSuccess(Certificates.getRealSignerInfo().toProtoBuf());
    verify(successListener).onSuccess(Certificates.getRealSignerInfo().toProtoBuf());

    // Subsequent calls should also succeed immediately without calling the callback
    SignerInfoPrefetchResultListener nullListener = mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(null, getRealSignerId(), getFakeWaveletName(CertificateManagerImplTest.DOMAIN), null,
        nullListener);
    verify(nullListener).onSuccess(Certificates.getRealSignerInfo().toProtoBuf());
  }

  /**
   * Test prefetchDeltaSignerInfo for failing requests -- the failure should be propagated to
   * the prefetch listener, and requests on the same domain should fail.
   */
  public void test_prefetchDeltaSignerInfo4() throws Exception {
    // This will fail later
    SignerInfoPrefetchResultListener failListener = mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(getSlowFailingProvider(ticker, EASY_TICKS), getRealSignerId(),
        getFakeWaveletName(CertificateManagerImplTest.DOMAIN), null, failListener);

    // This would succeed later if it weren't for the previous one failing
    SignerInfoPrefetchResultListener successListener = mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(getSuccessfulProvider(), getRealSignerId(),
        getFakeWaveletName(CertificateManagerImplTest.DOMAIN), null, successListener);
    verifyZeroInteractions(failListener);
    verifyZeroInteractions(successListener);

    // Both callbacks should fail after ticking
    ticker.tick(EASY_TICKS);
    verify(failListener).onFailure(GENERIC_ERROR);
    verify(successListener).onFailure(GENERIC_ERROR);
  }

  /**
   * Test prefetchDeltaSignerInfo for failing requests where a previous request on a different
   * domain has already succeeded.  The failing request should also appear to succeed.
   */
  public void test_prefetchDeltaSignerInfo5() throws Exception {
    // This would fail if the next (immediate) request didn't succeed
    SignerInfoPrefetchResultListener failListener = mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(getSlowFailingProvider(ticker, EASY_TICKS), getRealSignerId(),
        getFakeWaveletName(CertificateManagerImplTest.DOMAIN), getHashedVersion(), failListener);
    verifyZeroInteractions(failListener);

    // This will succeed immediately
    SignerInfoPrefetchResultListener successListener = mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(getSuccessfulProvider(), getRealSignerId(),
        getFakeWaveletName(OTHER_DOMAIN), getHashedVersion(), successListener);

    verify(successListener).onSuccess(Certificates.getRealSignerInfo().toProtoBuf());
    verify(failListener).onSuccess(Certificates.getRealSignerInfo().toProtoBuf());

    // The failing listener shouldn't do anything, even after the ticks
    ticker.tick(EASY_TICKS);
    verifyNoMoreInteractions(failListener);
    verifyNoMoreInteractions(successListener);
  }

  /**
   * Test prefetchDeltaSignerInfo for failing requests -- even though the first request fails,
   * the second request on a different domain should succeed.
   */
  public void test_prefetchDeltaSignerInfo6() throws Exception {
    // This will fail later
    SignerInfoPrefetchResultListener failListener = mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(getSlowFailingProvider(ticker, EASY_TICKS), getRealSignerId(),
        getFakeWaveletName(CertificateManagerImplTest.DOMAIN), getHashedVersion(), failListener);
    verifyZeroInteractions(failListener);

    // This will succeed later, after the failing one fails
    SignerInfoPrefetchResultListener successListener = mock(SignerInfoPrefetchResultListener.class);

    manager.prefetchDeltaSignerInfo(getSlowSuccessfulProvider(ticker, EASY_TICKS * 2),
        getRealSignerId(), getFakeWaveletName(OTHER_DOMAIN), getHashedVersion(), successListener);
    verifyZeroInteractions(successListener);

    // The failing request should fail, but successful request left alone
    ticker.tick(EASY_TICKS);
    verifyZeroInteractions(successListener);
    verify(failListener).onFailure(GENERIC_ERROR);

    // The successful request should now succeed
    ticker.tick(EASY_TICKS);
    verify(successListener).onSuccess(Certificates.getRealSignerInfo().toProtoBuf());
    verifyNoMoreInteractions(failListener);
  }


  /*
   * UTILITIES
   */

  private HashedVersion getHashedVersion() {
    return HashedVersion.unsigned(3L);
  }

  private ProtocolHashedVersion getProtocolHashedVersion() {
    return CoreWaveletOperationSerializer.serialize(getHashedVersion());
  }

  private WaveSignatureVerifier getRealVerifier(CertPathStore store) throws Exception {
    TrustRootsProvider trustRoots = new DefaultTrustRootsProvider();
    VerifiedCertChainCache cache = new DefaultCacheImpl(getFakeTimeSource());
    WaveCertPathValidator validator = new CachedCertPathValidator(
      cache, getFakeTimeSource(), trustRoots);

    return new WaveSignatureVerifier(validator, store);
  }

  private WaveSignatureVerifier getVerifier(CertPathStore store,
      boolean disableSignerVerification) {
    VerifiedCertChainCache cache = new DefaultCacheImpl(getFakeTimeSource());
    WaveCertPathValidator validator;
    if (disableSignerVerification) {
      validator = new DisabledCertPathValidator();
    } else {
      validator = new CachedCertPathValidator(
          cache, getFakeTimeSource(), getTrustRootsProvider());
    }
    return new WaveSignatureVerifier(validator, store);
  }

  private TrustRootsProvider getTrustRootsProvider() {
    return new TrustRootsProvider() {
      @Override
      public Collection<X509Certificate> getTrustRoots() {
        try {
          return getSigner().getSignerInfo().getCertificates();
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    };
  }

  private SigningSignatureHandler getSigner() throws Exception {
    InputStream keyStream = new ByteArrayInputStream(Certificates.EXAMPLE_PRIVATE_KEY.getBytes());
    InputStream certStream = new ByteArrayInputStream(Certificates.EXAMPLE_CERTIFICATE.getBytes());
    List<InputStream> certStreams = ImmutableList.of(certStream);

    WaveSignerFactory factory = new WaveSignerFactory();
    WaveSigner signer = factory.getSigner(keyStream, certStreams, CertificateManagerImplTest.DOMAIN);
    return new SigningSignatureHandler(signer);
  }

  private SignerInfo getSignerInfo() throws Exception {
    return getSigner().getSignerInfo();
  }

  private TimeSource getFakeTimeSource() {
    return new TimeSource() {
      @Override
      public Date now() {
        return new Date(currentTimeMillis());
      }

      @Override
      public long currentTimeMillis() {
        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        cal.set(2009, 11, 1);
        return cal.getTimeInMillis();
      }
    };
  }

  private ProtocolSignedDelta getFakeSignedDelta() throws Exception {
    return ProtocolSignedDelta.newBuilder()
        .setDelta(getFakeDelta().getByteString())
        .addSignature(getRealSignature())
        .build();
  }

  private ByteStringMessage<ProtocolWaveletDelta> getFakeDelta() throws Exception {
    ProtocolWaveletDelta delta = ProtocolWaveletDelta.newBuilder()
        .setHashedVersion(getProtocolHashedVersion())
        .setAuthor("bob@initech-corp.com")
        .build();
    return ByteStringMessage.serializeMessage(delta);
  }

  private ProtocolSignature getRealSignature() throws Exception {
    return ProtocolSignature.newBuilder()
        .setSignerId(ByteString.copyFrom(Certificates.getRealSignerInfo().getSignerId()))
        .setSignatureAlgorithm(SignatureAlgorithm.SHA1_RSA)
        .setSignatureBytes(ByteString.copyFrom(Certificates.REAL_SIGNATURE))
        .build();
  }

  private WaveletName getFakeWaveletName(String domain) {
    return WaveletName.of(WaveId.of(domain, "wave"), WaveletId.of(domain, "wavelet"));
  }

  private ByteString getRealSignerId() throws Exception {
    return ByteString.copyFrom(Certificates.getRealSignerInfo().getSignerId());
  }


  /*
   * Fake WaveletFederationProviders.
   */

  private abstract class WaveletSignerInfoProvider implements WaveletFederationProvider {
    @Override public void postSignerInfo(String destinationDomain, ProtocolSignerInfo signerInfo,
        PostSignerInfoResponseListener listener) {
      throw new UnsupportedOperationException();
    }

    @Override  public void requestHistory(WaveletName waveletName, String domain,
        ProtocolHashedVersion startVersion, ProtocolHashedVersion endVersion, long lengthLimit,
        HistoryResponseListener listener) {
      throw new UnsupportedOperationException();
    }

    @Override public void submitRequest(WaveletName waveletName, ProtocolSignedDelta delta,
        SubmitResultListener listener) {
      throw new UnsupportedOperationException();
    }
  }

  private WaveletFederationProvider getSuccessfulProvider() {
    return new WaveletSignerInfoProvider() {
      @Override public void getDeltaSignerInfo(ByteString signerId, WaveletName waveletName,
          ProtocolHashedVersion deltaEndVersion, DeltaSignerInfoResponseListener listener) {
        try {
          listener.onSuccess(Certificates.getRealSignerInfo().toProtoBuf());
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    };
  }

  private WaveletFederationProvider getSlowSuccessfulProvider(final Ticker ticker,
      final int ticks) {
    return new WaveletSignerInfoProvider() {
      @Override public void getDeltaSignerInfo(ByteString signerId, WaveletName waveletName,
          ProtocolHashedVersion deltaEndVersion, final DeltaSignerInfoResponseListener listener) {
        ticker.runAt(ticks, new Runnable() {
          @Override public void run() {
            try {
              listener.onSuccess(Certificates.getRealSignerInfo().toProtoBuf());
            } catch (Exception e) {
              throw new RuntimeException(e);
            }
          }
        });
      }
    };
  }

  private WaveletFederationProvider getSlowFailingProvider(final Ticker ticker, final int ticks) {
    return new WaveletSignerInfoProvider() {
      @Override public void getDeltaSignerInfo(ByteString signerId, WaveletName waveletName,
          ProtocolHashedVersion deltaEndVersion, final DeltaSignerInfoResponseListener listener) {
        ticker.runAt(ticks, new Runnable() {
          @Override public void run() {
            listener.onFailure(GENERIC_ERROR);
          }
        });
      }
    };
  }

  private WaveletFederationProvider getDeadProvider() {
    return new WaveletSignerInfoProvider() {
      @Override public void getDeltaSignerInfo(ByteString signerId, WaveletName waveletName,
          ProtocolHashedVersion deltaEndVersion, DeltaSignerInfoResponseListener listener) {
        // Never calls the callback
      }
    };
  }
}
TOP

Related Classes of org.waveprotocol.box.server.waveserver.CertificateManagerImplTest$WaveletSignerInfoProvider

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.