/** Create LockssKeystores from config subtree below {@link #PARAM_KEYSTORE} */
  void configureKeyStores(Configuration config) {
    Configuration allKs = config.getConfigTree(PARAM_KEYSTORE);
    for (Iterator iter = allKs.nodeIterator(); iter.hasNext(); ) {
      String id = (String) iter.next();
      Configuration oneKs = allKs.getConfigTree(id);
      try {
        LockssKeyStore lk = createLockssKeyStore(oneKs);
        String name = lk.getName();
        if (name == null) {
          log.error("KeyStore definition missing name: " + oneKs);
          continue;
        }
        LockssKeyStore old = keystoreMap.get(name);
        if (old != null && !lk.equals(old)) {
          log.warning(
              "Keystore "
                  + name
                  + " redefined.  "
                  + "New definition may not take effect until daemon restart");
        }

        log.debug("Adding keystore " + name);
        keystoreMap.put(name, lk);

      } catch (Exception e) {
        log.error("Couldn't create keystore: " + oneKs, e);
      }
    }
  }
示例#2
0
  /**
   * Handles a specific <tt>IOException</tt> which was thrown during the execution of {@link
   * #runInConnectThread(DTLSProtocol, TlsPeer, DatagramTransport)} while trying to establish a DTLS
   * connection
   *
   * @param ioe the <tt>IOException</tt> to handle
   * @param msg the human-readable message to log about the specified <tt>ioe</tt>
   * @param i the number of tries remaining after the current one
   * @return <tt>true</tt> if the specified <tt>ioe</tt> was successfully handled; <tt>false</tt>,
   *     otherwise
   */
  private boolean handleRunInConnectThreadException(IOException ioe, String msg, int i) {
    // SrtpControl.start(MediaType) starts its associated TransformEngine.
    // We will use that mediaType to signal the normal stop then as well
    // i.e. we will ignore exception after the procedure to stop this
    // PacketTransformer has begun.
    if (mediaType == null) return false;

    if (ioe instanceof TlsFatalAlert) {
      TlsFatalAlert tfa = (TlsFatalAlert) ioe;
      short alertDescription = tfa.getAlertDescription();

      if (alertDescription == AlertDescription.unexpected_message) {
        msg += " Received fatal unexpected message.";
        if (i == 0
            || !Thread.currentThread().equals(connectThread)
            || connector == null
            || mediaType == null) {
          msg += " Giving up after " + (CONNECT_TRIES - i) + " retries.";
        } else {
          msg += " Will retry.";
          logger.error(msg, ioe);

          return true;
        }
      } else {
        msg += " Received fatal alert " + alertDescription + ".";
      }
    }

    logger.error(msg, ioe);
    return false;
  }
示例#3
0
    /**
     * Add a named logger.  This does nothing and returns false if a logger
     * with the same name is already registered.
     * <p>
     * The Logger factory methods call this method to register each
     * newly created Logger.
     * <p>
     * The application should retain its own reference to the Logger 
     * object to avoid it being garbage collected.  The LogManager
     * may only retain a weak reference.
     *
     * @param   logger the new logger.
     * @return  true if the argument logger was registered successfully,
     *          false if a logger of that name already exists.
     * @exception NullPointerException if the logger name is null.
     */
    public synchronized boolean addLogger(Logger logger) {
	String name = logger.getName();
	if (name == null) {
	    throw new NullPointerException();
	}

	Logger old = (Logger) loggers.get(name);
	if (old != null) {
	    // We already have a registered logger with the given name.
	    return false;
	}

	// We're adding a new logger.
	// Note that we are creating a strong reference here that will
	// keep the Logger in existence indefinitely.
	loggers.put(name, logger);

	// Apply any initial level defined for the new logger.
	Level level = getLevelProperty(name+".level", null);
	if (level != null) {
	    doSetLevel(logger, level);
	}

	// If any of the logger's parents have levels defined,
	// make sure they are instantiated.
	int ix = 1;
	for (;;) {
	    int ix2 = name.indexOf(".", ix);
	    if (ix2 < 0) {
		break;
	    }
	    String pname = name.substring(0,ix2);
	    if (getProperty(pname+".level") != null) {
		// This pname has a level definition.  Make sure it exists.
		Logger plogger = Logger.getLogger(pname);
	    }
	    ix = ix2+1;
	}

	// Find the new node and its parent.
	LogNode node = findNode(name);
	node.logger = logger;
	Logger parent = null;
	LogNode nodep = node.parent;
	while (nodep != null) {
	    if (nodep.logger != null) {
		parent = nodep.logger;
		break;
	    }
	    nodep = nodep.parent;
	}

	if (parent != null) {
            doSetParent(logger, parent);
	}
	// Walk over the children and tell them we are their new parent.
	node.walkAndSetParent(logger);

	return true;
    }
  /**
   * Returns true if the user represented by the current request plays the named role.
   *
   * @param role the named role to test.
   * @return true if the user plays the role.
   */
  public boolean isUserInRole(String role) {
    ServletInvocation invocation = getInvocation();

    if (invocation == null) {
      if (getRequest() != null) return getRequest().isUserInRole(role);
      else return false;
    }

    HashMap<String, String> roleMap = invocation.getSecurityRoleMap();

    if (roleMap != null) {
      String linkRole = roleMap.get(role);

      if (linkRole != null) role = linkRole;
    }

    String runAs = getRunAs();

    if (runAs != null) return runAs.equals(role);

    WebApp webApp = getWebApp();

    Principal user = getUserPrincipal();

    if (user == null) {
      if (log.isLoggable(Level.FINE)) log.fine(this + " no user for isUserInRole");

      return false;
    }

    RoleMapManager roleManager = webApp != null ? webApp.getRoleMapManager() : null;

    if (roleManager != null) {
      Boolean result = roleManager.isUserInRole(role, user);

      if (result != null) {
        if (log.isLoggable(Level.FINE)) log.fine(this + " userInRole(" + role + ")->" + result);

        return result;
      }
    }

    Login login = webApp == null ? null : webApp.getLogin();

    boolean inRole = login != null && login.isUserInRole(user, role);

    if (log.isLoggable(Level.FINE)) {
      if (login == null) log.fine(this + " no Login for isUserInRole");
      else if (user == null) log.fine(this + " no user for isUserInRole");
      else if (inRole) log.fine(this + " " + user + " is in role: " + role);
      else log.fine(this + " failed " + user + " in role: " + role);
    }

    return inRole;
  }
 protected void checkContent(SimulatedArchivalUnit sau) throws IOException {
   log.debug("checkContent()");
   checkRoot(sau);
   checkLeaf(sau);
   checkStoredContent(sau);
   checkDepth(sau);
 }
示例#6
0
 /** Stops this <tt>PacketTransformer</tt>. */
 private synchronized void stop() {
   if (connectThread != null) connectThread = null;
   try {
     // The dtlsTransport and _srtpTransformer SHOULD be closed, of
     // course. The datagramTransport MUST be closed.
     if (dtlsTransport != null) {
       try {
         dtlsTransport.close();
       } catch (IOException ioe) {
         logger.error("Failed to (properly) close " + dtlsTransport.getClass(), ioe);
       }
       dtlsTransport = null;
     }
     if (_srtpTransformer != null) {
       _srtpTransformer.close();
       _srtpTransformer = null;
     }
   } finally {
     try {
       closeDatagramTransport();
     } finally {
       notifyAll();
     }
   }
 }
示例#7
0
  public void setCurrentContact(Contact contact, String resourceName) {
    if (contact == null) {
      this.otrContact = null;
      this.setPolicy(null);
      this.setStatus(ScSessionStatus.PLAINTEXT);
      return;
    }

    if (resourceName == null) {
      OtrContact otrContact = OtrContactManager.getOtrContact(contact, null);
      if (this.otrContact == otrContact) return;
      this.otrContact = otrContact;
      this.setStatus(OtrActivator.scOtrEngine.getSessionStatus(otrContact));
      this.setPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact));
      return;
    }
    for (ContactResource resource : contact.getResources()) {
      if (resource.getResourceName().equals(resourceName)) {
        OtrContact otrContact = OtrContactManager.getOtrContact(contact, resource);
        if (this.otrContact == otrContact) return;
        this.otrContact = otrContact;
        this.setStatus(OtrActivator.scOtrEngine.getSessionStatus(otrContact));
        this.setPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact));
        return;
      }
    }
    logger.debug("Could not find resource for contact " + contact);
  }
 protected void checkLeaf(SimulatedArchivalUnit sau) {
   log.debug("checkLeaf()");
   String parent = sau.getUrlRoot() + "/branch1";
   CachedUrlSetSpec spec = new RangeCachedUrlSetSpec(parent);
   CachedUrlSet set = sau.makeCachedUrlSet(spec);
   Iterator setIt = set.contentHashIterator();
   ArrayList childL = new ArrayList(16);
   while (setIt.hasNext()) {
     childL.add(((CachedUrlSetNode) setIt.next()).getUrl());
   }
   String[] expectedA =
       new String[] {
         parent,
         parent + "/001file.html",
         parent + "/001file.txt",
         parent + "/002file.html",
         parent + "/002file.txt",
         parent + "/branch1",
         parent + "/branch1/001file.html",
         parent + "/branch1/001file.txt",
         parent + "/branch1/002file.html",
         parent + "/branch1/002file.txt",
         parent + "/branch1/index.html",
         parent + "/branch2",
         parent + "/branch2/001file.html",
         parent + "/branch2/001file.txt",
         parent + "/branch2/002file.html",
         parent + "/branch2/002file.txt",
         parent + "/branch2/index.html",
         parent + "/index.html",
       };
   assertIsomorphic(expectedA, childL);
 }
  /** Create LockssKeystore from a config subtree */
  LockssKeyStore createLockssKeyStore(Configuration config) {
    log.debug2("Creating LockssKeyStore from config: " + config);
    String name = config.get(KEYSTORE_PARAM_NAME);
    LockssKeyStore lk = new LockssKeyStore(name);

    String file = config.get(KEYSTORE_PARAM_FILE);
    String resource = config.get(KEYSTORE_PARAM_RESOURCE);
    String url = config.get(KEYSTORE_PARAM_URL);

    if (!StringUtil.isNullString(file)) {
      lk.setLocation(file, LocationType.File);
    } else if (!StringUtil.isNullString(resource)) {
      lk.setLocation(resource, LocationType.Resource);
    } else if (!StringUtil.isNullString(url)) {
      lk.setLocation(url, LocationType.Url);
    }

    lk.setType(config.get(KEYSTORE_PARAM_TYPE, defaultKeyStoreType));
    lk.setProvider(config.get(KEYSTORE_PARAM_PROVIDER, defaultKeyStoreProvider));
    lk.setPassword(config.get(KEYSTORE_PARAM_PASSWORD));
    lk.setKeyPassword(config.get(KEYSTORE_PARAM_KEY_PASSWORD));
    lk.setKeyPasswordFile(config.get(KEYSTORE_PARAM_KEY_PASSWORD_FILE));
    lk.setMayCreate(config.getBoolean(KEYSTORE_PARAM_CREATE, DEFAULT_CREATE));
    return lk;
  }
  protected void checkRoot(SimulatedArchivalUnit sau) {
    log.debug("checkRoot()");
    CachedUrlSet set = sau.getAuCachedUrlSet();
    Iterator setIt = set.flatSetIterator();
    ArrayList childL = new ArrayList(1);
    CachedUrlSet cus = null;
    while (setIt.hasNext()) {
      cus = (CachedUrlSet) setIt.next();
      childL.add(cus.getUrl());
    }

    String urlRoot = sau.getUrlRoot();

    String[] expectedA = new String[1];
    expectedA[0] = urlRoot;
    assertIsomorphic(expectedA, childL);

    setIt = cus.flatSetIterator();
    childL = new ArrayList(7);
    while (setIt.hasNext()) {
      childL.add(((CachedUrlSetNode) setIt.next()).getUrl());
    }

    expectedA =
        new String[] {
          urlRoot + "/001file.html",
          urlRoot + "/001file.txt",
          urlRoot + "/002file.html",
          urlRoot + "/002file.txt",
          urlRoot + "/branch1",
          urlRoot + "/branch2",
          urlRoot + "/index.html"
        };
    assertIsomorphic(expectedA, childL);
  }
示例#11
0
  @Override
  public boolean login(boolean isFail) {
    try {
      WebApp webApp = getWebApp();

      if (webApp == null) {
        if (log.isLoggable(Level.FINE)) log.finer("authentication failed, no web-app found");

        getResponse().sendError(HttpServletResponse.SC_FORBIDDEN);

        return false;
      }

      // If the authenticator can find the user, return it.
      Login login = webApp.getLogin();

      if (login != null) {
        Principal user = login.login(this, getResponse(), isFail);

        return user != null;
        /*
        if (user == null)
          return false;

        setAttribute(AbstractLogin.LOGIN_NAME, user);

        return true;
        */
      } else if (isFail) {
        if (log.isLoggable(Level.FINE))
          log.finer("authentication failed, no login module found for " + webApp);

        getResponse().sendError(HttpServletResponse.SC_FORBIDDEN);

        return false;
      } else {
        // if a non-failure, then missing login is fine

        return false;
      }
    } catch (IOException e) {
      log.log(Level.FINE, e.toString(), e);

      return false;
    }
  }
 protected void checkDepth(SimulatedArchivalUnit sau) {
   log.debug("checkDepth()");
   String URL_ROOT = sau.getUrlRoot();
   assertEquals(0, sau.getLinkDepth(URL_ROOT + "/index.html"));
   assertEquals(0, sau.getLinkDepth(URL_ROOT + "/"));
   assertEquals(1, sau.getLinkDepth(URL_ROOT + "/001file.html"));
   assertEquals(1, sau.getLinkDepth(URL_ROOT + "/branch1/index.html"));
   assertEquals(1, sau.getLinkDepth(URL_ROOT + "/branch1/"));
   assertEquals(2, sau.getLinkDepth(URL_ROOT + "/branch1/001file.html"));
 }
示例#13
0
  /**
   * Computes and returns the hash of the specified <tt>capsString</tt> using the specified
   * <tt>hashAlgorithm</tt>.
   *
   * @param hashAlgorithm the name of the algorithm to be used to generate the hash
   * @param capsString the capabilities string that we'd like to compute a hash for.
   * @return the hash of <tt>capsString</tt> computed by the specified <tt>hashAlgorithm</tt> or
   *     <tt>null</tt> if generating the hash has failed
   */
  private static String capsToHash(String hashAlgorithm, String capsString) {
    try {
      MessageDigest md = MessageDigest.getInstance(hashAlgorithm);
      byte[] digest = md.digest(capsString.getBytes());

      return Base64.encodeBytes(digest);
    } catch (NoSuchAlgorithmException nsae) {
      logger.error("Unsupported XEP-0115: Entity Capabilities hash algorithm: " + hashAlgorithm);
      return null;
    }
  }
 void loadKeyStores() {
   List<LockssKeyStore> lst = new ArrayList<LockssKeyStore>(keystoreMap.values());
   for (LockssKeyStore lk : lst) {
     try {
       lk.load();
     } catch (Exception e) {
       log.error("Can't load keystore " + lk.getName(), e);
       keystoreMap.remove(lk.getName());
     }
   }
 }
  static {
    try {
      File file = new File("/dev/urandom");
      // This stream is deliberately leaked.
      urandom = new FileInputStream(file);
      if (urandom.read() == -1) throw new RuntimeException("/dev/urandom not readable?");
      // Now override the default SecureRandom implementation with this one.
      int position = Security.insertProviderAt(new LinuxSecureRandomProvider(), 1);

      if (position != -1) log.info("Secure randomness will be read from {} only.", file);
      else log.info("Randomness is already secure.");
    } catch (FileNotFoundException e) {
      // Should never happen.
      log.error("/dev/urandom does not appear to exist or is not openable");
      throw new RuntimeException(e);
    } catch (IOException e) {
      log.error("/dev/urandom does not appear to be readable");
      throw new RuntimeException(e);
    }
  }
  protected void hashContent(SimulatedArchivalUnit sau) throws Exception {
    log.debug("hashContent()");
    measureHashSpeed(sau);

    // If any changes are made to the contents or shape of the simulated
    // content tree, these hash values will have to be changed
    checkHashSet(sau, true, false, fromHex("6AB258B4E1FFD9F9B45316B4F54111FF5E5948D2"));
    checkHashSet(sau, true, true, fromHex("6AB258B4E1FFD9F9B45316B4F54111FF5E5948D2"));
    checkHashSet(sau, false, false, fromHex("409893F1A603F4C276632694DB1621B639BD5164"));
    checkHashSet(sau, false, true, fromHex("85E6213C3771BEAC5A4602CAF7982C6C222800D5"));
  }
  protected void createContent(SimulatedArchivalUnit sau) {
    log.debug("createContent()");
    scgen = sau.getContentGenerator();
    scgen.setFileTypes(
        SimulatedContentGenerator.FILE_TYPE_HTML + SimulatedContentGenerator.FILE_TYPE_TXT);
    scgen.setAbnormalFile("1,1", 1);
    scgen.setOddBranchesHaveContent(true);

    sau.deleteContentTree();
    sau.generateContentTree();
    assertTrue(scgen.isContentTree());
  }
示例#18
0
    // dispose of this LoggerWeakRef object
    void dispose() {
      if (node != null) {
        // if we have a LogNode, then we were a named Logger
        // so clear namedLoggers weak ref to us
        manager.namedLoggers.remove(name);
        name = null; // clear our ref to the Logger's name

        node.loggerRef = null; // clear LogNode's weak ref to us
        node = null; // clear our ref to LogNode
      }

      if (parentRef != null) {
        // this LoggerWeakRef has or had a parent Logger
        Logger parent = parentRef.get();
        if (parent != null) {
          // the parent Logger is still there so clear the
          // parent Logger's weak ref to us
          parent.removeChildLogger(this);
        }
        parentRef = null; // clear our weak ref to the parent Logger
      }
    }
示例#19
0
    // Private method to be called when the configuration has
    // changed to apply any level settings to any pre-existing loggers.
    synchronized private void setLevelsOnExistingLoggers() {
	Enumeration enum = props.propertyNames();
	while (enum.hasMoreElements()) {
	    String key = (String)enum.nextElement();
	    if (!key.endsWith(".level")) {
		// Not a level definition.
		continue;
	    }
	    int ix = key.length() - 6;
	    String name = key.substring(0, ix);
	    Level level = getLevelProperty(key, null);
	    if (level == null) {
		System.err.println("Bad level value for property: " + key);
		continue;
	    }
	    Logger l = getLogger(name);
	    if (l == null) {
		continue;
	    }
	    l.setLevel(level);
	}
    }
示例#20
0
 public static void load(Collection<FileDesc> files, Path root, int blocSize, Pattern pattern)
     throws IOException {
   root = root.toAbsolutePath().normalize();
   Visitor visitor = new Visitor(root, blocSize, pattern);
   Files.walkFileTree(root, visitor);
   for (Future<FileDesc> future : visitor.futures()) {
     try {
       files.add(future.get());
     } catch (Exception e) {
       log.error("", e);
     }
   }
 }
示例#21
0
 /**
  * Closes {@link #datagramTransport} if it is non-<tt>null</tt> and logs and swallows any
  * <tt>IOException</tt>.
  */
 private void closeDatagramTransport() {
   if (datagramTransport != null) {
     try {
       datagramTransport.close();
     } catch (IOException ioe) {
       // DatagramTransportImpl has no reason to fail because it is
       // merely an adapter of #connector and this PacketTransformer to
       // the terms of the Bouncy Castle Crypto API.
       logger.error("Failed to (properly) close " + datagramTransport.getClass(), ioe);
     }
     datagramTransport = null;
   }
 }
示例#22
0
    // Private method to reset an invidual target logger.
    private void resetLogger(String name) {
	Logger logger = getLogger(name);
	if (logger == null) {
	    return;
	}
	// Close all the Logger's handlers.
	Handler[] targets = logger.getHandlers();
	for (int i = 0; i < targets.length; i++) {
	    Handler h = targets[i];
	    logger.removeHandler(h);
	    try {
	        h.close();
	    } catch (Exception ex) {
		// Problems closing a handler?  Keep going...
	    }
	}
	if (name != null && name.equals("")) {
	    // This is the root logger.
	    logger.setLevel(defaultLevel);
	} else {
	    logger.setLevel(null);
	}
    }
示例#23
0
    // Private method to set a parent on a logger.
    // If necessary, we raise privilege before doing the setParent call.
    private static void doSetParent(final Logger logger, final Logger parent) {
	SecurityManager sm = System.getSecurityManager();
	if (sm == null) {
	    // There is no security manager, so things are easy.
	    logger.setParent(parent);
	    return;
	} 
	// There is a security manager.  Raise privilege before
	// calling setParent.
	AccessController.doPrivileged(new PrivilegedAction() {
	    public Object run() {
		logger.setParent(parent);
		return null;
	    }});
    }
  protected void checkFilter(SimulatedArchivalUnit sau) throws Exception {
    log.debug("checkFilter()");
    CachedUrl cu = sau.makeCachedUrl(sau.getUrlRoot() + "/001file.html");

    enableFilter(sau, true);
    InputStream is = cu.openForHashing();
    String expected = "001file.html This is file 1, depth 0, branch 0. foobar ";
    assertEquals(expected, StringUtil.fromInputStream(is));
    is.close();
    enableFilter(sau, false);
    cu = sau.makeCachedUrl(sau.getUrlRoot() + "/001file.html");
    is = cu.openForHashing();
    expected =
        "<HTML><HEAD><TITLE>001file.html</TITLE></HEAD><BODY>\n"
            + "This is file 1, depth 0, branch 0.<br><!-- comment -->    "
            + "Citation String   foobar<br><script>"
            + "(defun fact (n) (cond ((= n 0) 1) (t (fact (sub1 n)))))</script>\n"
            + "</BODY></HTML>";
    assertEquals(expected, StringUtil.fromInputStream(is));
    is.close();
  }
示例#25
0
  /**
   * Sends the data contained in a specific byte array as application data through the DTLS
   * connection of this <tt>DtlsPacketTransformer</tt>.
   *
   * @param buf the byte array containing data to send.
   * @param off the offset in <tt>buf</tt> where the data begins.
   * @param len the length of data to send.
   */
  public void sendApplicationData(byte[] buf, int off, int len) {
    DTLSTransport dtlsTransport = this.dtlsTransport;
    Throwable throwable = null;

    if (dtlsTransport != null) {
      try {
        dtlsTransport.send(buf, off, len);
      } catch (IOException ioe) {
        throwable = ioe;
      }
    } else {
      throwable = new NullPointerException("dtlsTransport");
    }
    if (throwable != null) {
      // SrtpControl.start(MediaType) starts its associated
      // TransformEngine. We will use that mediaType to signal the normal
      // stop then as well i.e. we will ignore exception after the
      // procedure to stop this PacketTransformer has begun.
      if (mediaType != null && !tlsPeerHasRaisedCloseNotifyWarning) {
        logger.error("Failed to send application data over DTLS transport: ", throwable);
      }
    }
  }
  private void checkFact(Object fact, String name, String criticalServiceName, String message) {
    if (fact == null && criticalServiceName != null) {
      String msg =
          StringUtil.isNullString(name)
              ? ("No keystore name given for critical keystore"
                  + " needed for service "
                  + criticalServiceName
                  + ", daemon exiting")
              : ("Critical keystore "
                  + name
                  + " "
                  + ((message != null) ? message : "not found or not loadable")
                  + " for service "
                  + criticalServiceName
                  + ", daemon exiting");
      log.critical(msg);

      if (paramExitIfMissingKeyStore) {
        System.exit(Constants.EXIT_CODE_KEYSTORE_MISSING);
      } else {
        throw new IllegalArgumentException(msg);
      }
    }
  }
示例#27
0
  /** Starts this <tt>PacketTransformer</tt>. */
  private synchronized void start() {
    if (this.datagramTransport != null) {
      if (this.connectThread == null && dtlsTransport == null) {
        logger.warn(
            getClass().getName()
                + " has been started but has failed to establish"
                + " the DTLS connection!");
      }
      return;
    }

    if (rtcpmux && Component.RTCP == componentID) {
      // In the case of rtcp-mux, the RTCP transformer does not create
      // a DTLS session. The SRTP context (_srtpTransformer) will be
      // initialized on demand using initializeSRTCPTransformerFromRtp().
      return;
    }

    AbstractRTPConnector connector = this.connector;

    if (connector == null) throw new NullPointerException("connector");

    DtlsControl.Setup setup = this.setup;
    SecureRandom secureRandom = DtlsControlImpl.createSecureRandom();
    final DTLSProtocol dtlsProtocolObj;
    final TlsPeer tlsPeer;

    if (DtlsControl.Setup.ACTIVE.equals(setup)) {
      dtlsProtocolObj = new DTLSClientProtocol(secureRandom);
      tlsPeer = new TlsClientImpl(this);
    } else {
      dtlsProtocolObj = new DTLSServerProtocol(secureRandom);
      tlsPeer = new TlsServerImpl(this);
    }
    tlsPeerHasRaisedCloseNotifyWarning = false;

    final DatagramTransportImpl datagramTransport = new DatagramTransportImpl(componentID);

    datagramTransport.setConnector(connector);

    Thread connectThread =
        new Thread() {
          @Override
          public void run() {
            try {
              runInConnectThread(dtlsProtocolObj, tlsPeer, datagramTransport);
            } finally {
              if (Thread.currentThread().equals(DtlsPacketTransformer.this.connectThread)) {
                DtlsPacketTransformer.this.connectThread = null;
              }
            }
          }
        };

    connectThread.setDaemon(true);
    connectThread.setName(DtlsPacketTransformer.class.getName() + ".connectThread");

    this.connectThread = connectThread;
    this.datagramTransport = datagramTransport;

    boolean started = false;

    try {
      connectThread.start();
      started = true;
    } finally {
      if (!started) {
        if (connectThread.equals(this.connectThread)) this.connectThread = null;
        if (datagramTransport.equals(this.datagramTransport)) this.datagramTransport = null;
      }
    }

    notifyAll();
  }
示例#28
0
  /** {@inheritDoc} */
  @Override
  public RawPacket reverseTransform(RawPacket pkt) {
    byte[] buf = pkt.getBuffer();
    int off = pkt.getOffset();
    int len = pkt.getLength();

    if (isDtlsRecord(buf, off, len)) {
      if (rtcpmux && Component.RTCP == componentID) {
        // This should never happen.
        logger.warn(
            "Dropping a DTLS record, because it was received on the"
                + " RTCP channel while rtcpmux is in use.");
        return null;
      }

      boolean receive;

      synchronized (this) {
        if (datagramTransport == null) {
          receive = false;
        } else {
          datagramTransport.queueReceive(buf, off, len);
          receive = true;
        }
      }
      if (receive) {
        DTLSTransport dtlsTransport = this.dtlsTransport;

        if (dtlsTransport == null) {
          // The specified pkt looks like a DTLS record and it has
          // been consumed for the purposes of the secure channel
          // represented by this PacketTransformer.
          pkt = null;
        } else {
          try {
            int receiveLimit = dtlsTransport.getReceiveLimit();
            int delta = receiveLimit - len;

            if (delta > 0) {
              pkt.grow(delta);
              buf = pkt.getBuffer();
              off = pkt.getOffset();
              len = pkt.getLength();
            } else if (delta < 0) {
              pkt.shrink(-delta);
              buf = pkt.getBuffer();
              off = pkt.getOffset();
              len = pkt.getLength();
            }

            int received = dtlsTransport.receive(buf, off, len, DTLS_TRANSPORT_RECEIVE_WAITMILLIS);

            if (received <= 0) {
              // No application data was decoded.
              pkt = null;
            } else {
              delta = len - received;
              if (delta > 0) pkt.shrink(delta);
            }
          } catch (IOException ioe) {
            pkt = null;
            // SrtpControl.start(MediaType) starts its associated
            // TransformEngine. We will use that mediaType to signal
            // the normal stop then as well i.e. we will ignore
            // exception after the procedure to stop this
            // PacketTransformer has begun.
            if (mediaType != null && !tlsPeerHasRaisedCloseNotifyWarning) {
              logger.error("Failed to decode a DTLS record!", ioe);
            }
          }
        }
      } else {
        // The specified pkt looks like a DTLS record but it is
        // unexpected in the current state of the secure channel
        // represented by this PacketTransformer. This PacketTransformer
        // has not been started (successfully) or has been closed.
        pkt = null;
      }
    } else if (transformEngine.isSrtpDisabled()) {
      // In pure DTLS mode only DTLS records pass through.
      pkt = null;
    } else {
      // DTLS-SRTP has not been initialized yet or has failed to
      // initialize.
      SinglePacketTransformer srtpTransformer = waitInitializeAndGetSRTPTransformer();

      if (srtpTransformer != null) pkt = srtpTransformer.reverseTransform(pkt);
      else if (DROP_UNENCRYPTED_PKTS) pkt = null;
      // XXX Else, it is our explicit policy to let the received packet
      // pass through and rely on the SrtpListener to notify the user that
      // the session is not secured.
    }
    return pkt;
  }
示例#29
0
/**
 * Implements {@link PacketTransformer} for DTLS-SRTP. It's capable of working in pure DTLS mode if
 * appropriate flag was set in <tt>DtlsControlImpl</tt>.
 *
 * @author Lyubomir Marinov
 */
public class DtlsPacketTransformer extends SinglePacketTransformer {
  private static final long CONNECT_RETRY_INTERVAL = 500;

  /**
   * The maximum number of times that {@link #runInConnectThread(DTLSProtocol, TlsPeer,
   * DatagramTransport)} is to retry the invocations of {@link DTLSClientProtocol#connect(TlsClient,
   * DatagramTransport)} and {@link DTLSServerProtocol#accept(TlsServer, DatagramTransport)} in
   * anticipation of a successful connection.
   */
  private static final int CONNECT_TRIES = 3;

  /**
   * The indicator which determines whether unencrypted packets sent or received through
   * <tt>DtlsPacketTransformer</tt> are to be dropped. The default value is <tt>false</tt>.
   *
   * @see #DROP_UNENCRYPTED_PKTS_PNAME
   */
  private static final boolean DROP_UNENCRYPTED_PKTS;

  /**
   * The name of the <tt>ConfigurationService</tt> and/or <tt>System</tt> property which indicates
   * whether unencrypted packets sent or received through <tt>DtlsPacketTransformer</tt> are to be
   * dropped. The default value is <tt>false</tt>.
   */
  private static final String DROP_UNENCRYPTED_PKTS_PNAME =
      DtlsPacketTransformer.class.getName() + ".dropUnencryptedPkts";

  /** The length of the header of a DTLS record. */
  static final int DTLS_RECORD_HEADER_LENGTH = 13;

  /**
   * The number of milliseconds a <tt>DtlsPacketTransform</tt> is to wait on its {@link
   * #dtlsTransport} in order to receive a packet.
   */
  private static final int DTLS_TRANSPORT_RECEIVE_WAITMILLIS = -1;

  /**
   * The <tt>Logger</tt> used by the <tt>DtlsPacketTransformer</tt> class and its instances to print
   * debug information.
   */
  private static final Logger logger = Logger.getLogger(DtlsPacketTransformer.class);

  static {
    ConfigurationService cfg = LibJitsi.getConfigurationService();
    boolean dropUnencryptedPkts = false;

    if (cfg == null) {
      String s = System.getProperty(DROP_UNENCRYPTED_PKTS_PNAME);

      if (s != null) dropUnencryptedPkts = Boolean.parseBoolean(s);
    } else {
      dropUnencryptedPkts = cfg.getBoolean(DROP_UNENCRYPTED_PKTS_PNAME, dropUnencryptedPkts);
    }
    DROP_UNENCRYPTED_PKTS = dropUnencryptedPkts;
  }

  /**
   * Determines whether a specific array of <tt>byte</tt>s appears to contain a DTLS record.
   *
   * @param buf the array of <tt>byte</tt>s to be analyzed
   * @param off the offset within <tt>buf</tt> at which the analysis is to start
   * @param len the number of bytes within <tt>buf</tt> starting at <tt>off</tt> to be analyzed
   * @return <tt>true</tt> if the specified <tt>buf</tt> appears to contain a DTLS record
   */
  public static boolean isDtlsRecord(byte[] buf, int off, int len) {
    boolean b = false;

    if (len >= DTLS_RECORD_HEADER_LENGTH) {
      short type = TlsUtils.readUint8(buf, off);

      switch (type) {
        case ContentType.alert:
        case ContentType.application_data:
        case ContentType.change_cipher_spec:
        case ContentType.handshake:
          int major = buf[off + 1] & 0xff;
          int minor = buf[off + 2] & 0xff;
          ProtocolVersion version = null;

          if ((major == ProtocolVersion.DTLSv10.getMajorVersion())
              && (minor == ProtocolVersion.DTLSv10.getMinorVersion())) {
            version = ProtocolVersion.DTLSv10;
          }
          if ((version == null)
              && (major == ProtocolVersion.DTLSv12.getMajorVersion())
              && (minor == ProtocolVersion.DTLSv12.getMinorVersion())) {
            version = ProtocolVersion.DTLSv12;
          }
          if (version != null) {
            int length = TlsUtils.readUint16(buf, off + 11);

            if (DTLS_RECORD_HEADER_LENGTH + length <= len) b = true;
          }
          break;
        default:
          // Unless a new ContentType has been defined by the Bouncy
          // Castle Crypto APIs, the specified buf does not represent a
          // DTLS record.
          break;
      }
    }
    return b;
  }

  /** The ID of the component which this instance works for/is associated with. */
  private final int componentID;

  /** The <tt>RTPConnector</tt> which uses this <tt>PacketTransformer</tt>. */
  private AbstractRTPConnector connector;

  /** The background <tt>Thread</tt> which initializes {@link #dtlsTransport}. */
  private Thread connectThread;

  /**
   * The <tt>DatagramTransport</tt> implementation which adapts {@link #connector} and this
   * <tt>PacketTransformer</tt> to the terms of the Bouncy Castle Crypto APIs.
   */
  private DatagramTransportImpl datagramTransport;

  /**
   * The <tt>DTLSTransport</tt> through which the actual packet transformations are being performed
   * by this instance.
   */
  private DTLSTransport dtlsTransport;

  /** The <tt>MediaType</tt> of the stream which this instance works for/is associated with. */
  private MediaType mediaType;

  /**
   * Whether rtcp-mux is in use.
   *
   * <p>If enabled, and this is the transformer for RTCP, it will not establish a DTLS session on
   * its own, but rather wait for the RTP transformer to do so, and reuse it to initialize the SRTP
   * transformer.
   */
  private boolean rtcpmux = false;

  /**
   * The value of the <tt>setup</tt> SDP attribute defined by RFC 4145 &quot;TCP-Based Media
   * Transport in the Session Description Protocol (SDP)&quot; which determines whether this
   * instance acts as a DTLS client or a DTLS server.
   */
  private DtlsControl.Setup setup;

  /** The {@code SRTPTransformer} (to be) used by this instance. */
  private SinglePacketTransformer _srtpTransformer;

  /**
   * The indicator which determines whether the <tt>TlsPeer</tt> employed by this
   * <tt>PacketTransformer</tt> has raised an <tt>AlertDescription.close_notify</tt>
   * <tt>AlertLevel.warning</tt> i.e. the remote DTLS peer has closed the write side of the
   * connection.
   */
  private boolean tlsPeerHasRaisedCloseNotifyWarning;

  /** The <tt>TransformEngine</tt> which has initialized this instance. */
  private final DtlsTransformEngine transformEngine;

  /**
   * Initializes a new <tt>DtlsPacketTransformer</tt> instance.
   *
   * @param transformEngine the <tt>TransformEngine</tt> which is initializing the new instance
   * @param componentID the ID of the component for which the new instance is to work
   */
  public DtlsPacketTransformer(DtlsTransformEngine transformEngine, int componentID) {
    this.transformEngine = transformEngine;
    this.componentID = componentID;
  }

  /** {@inheritDoc} */
  @Override
  public synchronized void close() {
    // SrtpControl.start(MediaType) starts its associated TransformEngine.
    // We will use that mediaType to signal the normal stop then as well
    // i.e. we will call setMediaType(null) first.
    setMediaType(null);
    setConnector(null);
  }

  /**
   * Closes {@link #datagramTransport} if it is non-<tt>null</tt> and logs and swallows any
   * <tt>IOException</tt>.
   */
  private void closeDatagramTransport() {
    if (datagramTransport != null) {
      try {
        datagramTransport.close();
      } catch (IOException ioe) {
        // DatagramTransportImpl has no reason to fail because it is
        // merely an adapter of #connector and this PacketTransformer to
        // the terms of the Bouncy Castle Crypto API.
        logger.error("Failed to (properly) close " + datagramTransport.getClass(), ioe);
      }
      datagramTransport = null;
    }
  }

  /**
   * Determines whether {@link #runInConnectThread(DTLSProtocol, TlsPeer, DatagramTransport)} is to
   * try to establish a DTLS connection.
   *
   * @param i the number of tries remaining after the current one
   * @param datagramTransport
   * @return <tt>true</tt> to try to establish a DTLS connection; otherwise, <tt>false</tt>
   */
  private boolean enterRunInConnectThreadLoop(int i, DatagramTransport datagramTransport) {
    if (i < 0 || i > CONNECT_TRIES) {
      return false;
    } else {
      Thread currentThread = Thread.currentThread();

      synchronized (this) {
        if (i > 0 && i < CONNECT_TRIES - 1) {
          boolean interrupted = false;

          try {
            wait(CONNECT_RETRY_INTERVAL);
          } catch (InterruptedException ie) {
            interrupted = true;
          }
          if (interrupted) currentThread.interrupt();
        }

        return currentThread.equals(this.connectThread)
            && datagramTransport.equals(this.datagramTransport);
      }
    }
  }

  /**
   * Gets the <tt>DtlsControl</tt> implementation associated with this instance.
   *
   * @return the <tt>DtlsControl</tt> implementation associated with this instance
   */
  DtlsControlImpl getDtlsControl() {
    return getTransformEngine().getDtlsControl();
  }

  /**
   * Gets the <tt>TransformEngine</tt> which has initialized this instance.
   *
   * @return the <tt>TransformEngine</tt> which has initialized this instance
   */
  DtlsTransformEngine getTransformEngine() {
    return transformEngine;
  }

  /**
   * Handles a specific <tt>IOException</tt> which was thrown during the execution of {@link
   * #runInConnectThread(DTLSProtocol, TlsPeer, DatagramTransport)} while trying to establish a DTLS
   * connection
   *
   * @param ioe the <tt>IOException</tt> to handle
   * @param msg the human-readable message to log about the specified <tt>ioe</tt>
   * @param i the number of tries remaining after the current one
   * @return <tt>true</tt> if the specified <tt>ioe</tt> was successfully handled; <tt>false</tt>,
   *     otherwise
   */
  private boolean handleRunInConnectThreadException(IOException ioe, String msg, int i) {
    // SrtpControl.start(MediaType) starts its associated TransformEngine.
    // We will use that mediaType to signal the normal stop then as well
    // i.e. we will ignore exception after the procedure to stop this
    // PacketTransformer has begun.
    if (mediaType == null) return false;

    if (ioe instanceof TlsFatalAlert) {
      TlsFatalAlert tfa = (TlsFatalAlert) ioe;
      short alertDescription = tfa.getAlertDescription();

      if (alertDescription == AlertDescription.unexpected_message) {
        msg += " Received fatal unexpected message.";
        if (i == 0
            || !Thread.currentThread().equals(connectThread)
            || connector == null
            || mediaType == null) {
          msg += " Giving up after " + (CONNECT_TRIES - i) + " retries.";
        } else {
          msg += " Will retry.";
          logger.error(msg, ioe);

          return true;
        }
      } else {
        msg += " Received fatal alert " + alertDescription + ".";
      }
    }

    logger.error(msg, ioe);
    return false;
  }

  /**
   * Tries to initialize {@link #_srtpTransformer} by using the <tt>DtlsPacketTransformer</tt> for
   * RTP.
   *
   * @return the (possibly updated) value of {@link #_srtpTransformer}.
   */
  private SinglePacketTransformer initializeSRTCPTransformerFromRtp() {
    DtlsPacketTransformer rtpTransformer =
        (DtlsPacketTransformer) getTransformEngine().getRTPTransformer();

    // Prevent recursion (that is pretty much impossible to ever happen).
    if (rtpTransformer != this) {
      PacketTransformer srtpTransformer = rtpTransformer.waitInitializeAndGetSRTPTransformer();

      if (srtpTransformer != null && srtpTransformer instanceof SRTPTransformer) {
        synchronized (this) {
          if (_srtpTransformer == null) {
            _srtpTransformer = new SRTCPTransformer((SRTPTransformer) srtpTransformer);
            // For the sake of completeness, we notify whenever we
            // assign to _srtpTransformer.
            notifyAll();
          }
        }
      }
    }

    return _srtpTransformer;
  }

  /**
   * Initializes a new <tt>SRTPTransformer</tt> instance with a specific (negotiated)
   * <tt>SRTPProtectionProfile</tt> and the keying material specified by a specific
   * <tt>TlsContext</tt>.
   *
   * @param srtpProtectionProfile the (negotiated) <tt>SRTPProtectionProfile</tt> to initialize the
   *     new instance with
   * @param tlsContext the <tt>TlsContext</tt> which represents the keying material
   * @return a new <tt>SRTPTransformer</tt> instance initialized with <tt>srtpProtectionProfile</tt>
   *     and <tt>tlsContext</tt>
   */
  private SinglePacketTransformer initializeSRTPTransformer(
      int srtpProtectionProfile, TlsContext tlsContext) {
    boolean rtcp;

    switch (componentID) {
      case Component.RTCP:
        rtcp = true;
        break;
      case Component.RTP:
        rtcp = false;
        break;
      default:
        throw new IllegalStateException("componentID");
    }

    int cipher_key_length;
    int cipher_salt_length;
    int cipher;
    int auth_function;
    int auth_key_length;
    int RTCP_auth_tag_length, RTP_auth_tag_length;

    switch (srtpProtectionProfile) {
      case SRTPProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_32:
        cipher_key_length = 128 / 8;
        cipher_salt_length = 112 / 8;
        cipher = SRTPPolicy.AESCM_ENCRYPTION;
        auth_function = SRTPPolicy.HMACSHA1_AUTHENTICATION;
        auth_key_length = 160 / 8;
        RTCP_auth_tag_length = 80 / 8;
        RTP_auth_tag_length = 32 / 8;
        break;
      case SRTPProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80:
        cipher_key_length = 128 / 8;
        cipher_salt_length = 112 / 8;
        cipher = SRTPPolicy.AESCM_ENCRYPTION;
        auth_function = SRTPPolicy.HMACSHA1_AUTHENTICATION;
        auth_key_length = 160 / 8;
        RTCP_auth_tag_length = RTP_auth_tag_length = 80 / 8;
        break;
      case SRTPProtectionProfile.SRTP_NULL_HMAC_SHA1_32:
        cipher_key_length = 0;
        cipher_salt_length = 0;
        cipher = SRTPPolicy.NULL_ENCRYPTION;
        auth_function = SRTPPolicy.HMACSHA1_AUTHENTICATION;
        auth_key_length = 160 / 8;
        RTCP_auth_tag_length = 80 / 8;
        RTP_auth_tag_length = 32 / 8;
        break;
      case SRTPProtectionProfile.SRTP_NULL_HMAC_SHA1_80:
        cipher_key_length = 0;
        cipher_salt_length = 0;
        cipher = SRTPPolicy.NULL_ENCRYPTION;
        auth_function = SRTPPolicy.HMACSHA1_AUTHENTICATION;
        auth_key_length = 160 / 8;
        RTCP_auth_tag_length = RTP_auth_tag_length = 80 / 8;
        break;
      default:
        throw new IllegalArgumentException("srtpProtectionProfile");
    }

    byte[] keyingMaterial =
        tlsContext.exportKeyingMaterial(
            ExporterLabel.dtls_srtp, null, 2 * (cipher_key_length + cipher_salt_length));
    byte[] client_write_SRTP_master_key = new byte[cipher_key_length];
    byte[] server_write_SRTP_master_key = new byte[cipher_key_length];
    byte[] client_write_SRTP_master_salt = new byte[cipher_salt_length];
    byte[] server_write_SRTP_master_salt = new byte[cipher_salt_length];
    byte[][] keyingMaterialValues = {
      client_write_SRTP_master_key,
      server_write_SRTP_master_key,
      client_write_SRTP_master_salt,
      server_write_SRTP_master_salt
    };

    for (int i = 0, keyingMaterialOffset = 0; i < keyingMaterialValues.length; i++) {
      byte[] keyingMaterialValue = keyingMaterialValues[i];

      System.arraycopy(
          keyingMaterial, keyingMaterialOffset, keyingMaterialValue, 0, keyingMaterialValue.length);
      keyingMaterialOffset += keyingMaterialValue.length;
    }

    SRTPPolicy srtcpPolicy =
        new SRTPPolicy(
            cipher,
            cipher_key_length,
            auth_function,
            auth_key_length,
            RTCP_auth_tag_length,
            cipher_salt_length);
    SRTPPolicy srtpPolicy =
        new SRTPPolicy(
            cipher,
            cipher_key_length,
            auth_function,
            auth_key_length,
            RTP_auth_tag_length,
            cipher_salt_length);
    SRTPContextFactory clientSRTPContextFactory =
        new SRTPContextFactory(
            /* sender */ tlsContext instanceof TlsClientContext,
            client_write_SRTP_master_key,
            client_write_SRTP_master_salt,
            srtpPolicy,
            srtcpPolicy);
    SRTPContextFactory serverSRTPContextFactory =
        new SRTPContextFactory(
            /* sender */ tlsContext instanceof TlsServerContext,
            server_write_SRTP_master_key,
            server_write_SRTP_master_salt,
            srtpPolicy,
            srtcpPolicy);
    SRTPContextFactory forwardSRTPContextFactory;
    SRTPContextFactory reverseSRTPContextFactory;

    if (tlsContext instanceof TlsClientContext) {
      forwardSRTPContextFactory = clientSRTPContextFactory;
      reverseSRTPContextFactory = serverSRTPContextFactory;
    } else if (tlsContext instanceof TlsServerContext) {
      forwardSRTPContextFactory = serverSRTPContextFactory;
      reverseSRTPContextFactory = clientSRTPContextFactory;
    } else {
      throw new IllegalArgumentException("tlsContext");
    }

    SinglePacketTransformer srtpTransformer;

    if (rtcp) {
      srtpTransformer = new SRTCPTransformer(forwardSRTPContextFactory, reverseSRTPContextFactory);
    } else {
      srtpTransformer = new SRTPTransformer(forwardSRTPContextFactory, reverseSRTPContextFactory);
    }
    return srtpTransformer;
  }

  /**
   * Notifies this instance that the DTLS record layer associated with a specific <tt>TlsPeer</tt>
   * has raised an alert.
   *
   * @param tlsPeer the <tt>TlsPeer</tt> whose associated DTLS record layer has raised an alert
   * @param alertLevel {@link AlertLevel}
   * @param alertDescription {@link AlertDescription}
   * @param message a human-readable message explaining what caused the alert. May be <tt>null</tt>.
   * @param cause the exception that caused the alert to be raised. May be <tt>null</tt>.
   */
  void notifyAlertRaised(
      TlsPeer tlsPeer, short alertLevel, short alertDescription, String message, Exception cause) {
    if (AlertLevel.warning == alertLevel && AlertDescription.close_notify == alertDescription) {
      tlsPeerHasRaisedCloseNotifyWarning = true;
    }
  }

  /** {@inheritDoc} */
  @Override
  public RawPacket reverseTransform(RawPacket pkt) {
    byte[] buf = pkt.getBuffer();
    int off = pkt.getOffset();
    int len = pkt.getLength();

    if (isDtlsRecord(buf, off, len)) {
      if (rtcpmux && Component.RTCP == componentID) {
        // This should never happen.
        logger.warn(
            "Dropping a DTLS record, because it was received on the"
                + " RTCP channel while rtcpmux is in use.");
        return null;
      }

      boolean receive;

      synchronized (this) {
        if (datagramTransport == null) {
          receive = false;
        } else {
          datagramTransport.queueReceive(buf, off, len);
          receive = true;
        }
      }
      if (receive) {
        DTLSTransport dtlsTransport = this.dtlsTransport;

        if (dtlsTransport == null) {
          // The specified pkt looks like a DTLS record and it has
          // been consumed for the purposes of the secure channel
          // represented by this PacketTransformer.
          pkt = null;
        } else {
          try {
            int receiveLimit = dtlsTransport.getReceiveLimit();
            int delta = receiveLimit - len;

            if (delta > 0) {
              pkt.grow(delta);
              buf = pkt.getBuffer();
              off = pkt.getOffset();
              len = pkt.getLength();
            } else if (delta < 0) {
              pkt.shrink(-delta);
              buf = pkt.getBuffer();
              off = pkt.getOffset();
              len = pkt.getLength();
            }

            int received = dtlsTransport.receive(buf, off, len, DTLS_TRANSPORT_RECEIVE_WAITMILLIS);

            if (received <= 0) {
              // No application data was decoded.
              pkt = null;
            } else {
              delta = len - received;
              if (delta > 0) pkt.shrink(delta);
            }
          } catch (IOException ioe) {
            pkt = null;
            // SrtpControl.start(MediaType) starts its associated
            // TransformEngine. We will use that mediaType to signal
            // the normal stop then as well i.e. we will ignore
            // exception after the procedure to stop this
            // PacketTransformer has begun.
            if (mediaType != null && !tlsPeerHasRaisedCloseNotifyWarning) {
              logger.error("Failed to decode a DTLS record!", ioe);
            }
          }
        }
      } else {
        // The specified pkt looks like a DTLS record but it is
        // unexpected in the current state of the secure channel
        // represented by this PacketTransformer. This PacketTransformer
        // has not been started (successfully) or has been closed.
        pkt = null;
      }
    } else if (transformEngine.isSrtpDisabled()) {
      // In pure DTLS mode only DTLS records pass through.
      pkt = null;
    } else {
      // DTLS-SRTP has not been initialized yet or has failed to
      // initialize.
      SinglePacketTransformer srtpTransformer = waitInitializeAndGetSRTPTransformer();

      if (srtpTransformer != null) pkt = srtpTransformer.reverseTransform(pkt);
      else if (DROP_UNENCRYPTED_PKTS) pkt = null;
      // XXX Else, it is our explicit policy to let the received packet
      // pass through and rely on the SrtpListener to notify the user that
      // the session is not secured.
    }
    return pkt;
  }

  /**
   * Runs in {@link #connectThread} to initialize {@link #dtlsTransport}.
   *
   * @param dtlsProtocol
   * @param tlsPeer
   * @param datagramTransport
   */
  private void runInConnectThread(
      DTLSProtocol dtlsProtocol, TlsPeer tlsPeer, DatagramTransport datagramTransport) {
    DTLSTransport dtlsTransport = null;
    final boolean srtp = !transformEngine.isSrtpDisabled();
    int srtpProtectionProfile = 0;
    TlsContext tlsContext = null;

    // DTLS client
    if (dtlsProtocol instanceof DTLSClientProtocol) {
      DTLSClientProtocol dtlsClientProtocol = (DTLSClientProtocol) dtlsProtocol;
      TlsClientImpl tlsClient = (TlsClientImpl) tlsPeer;

      for (int i = CONNECT_TRIES - 1; i >= 0; i--) {
        if (!enterRunInConnectThreadLoop(i, datagramTransport)) break;
        try {
          dtlsTransport = dtlsClientProtocol.connect(tlsClient, datagramTransport);
          break;
        } catch (IOException ioe) {
          if (!handleRunInConnectThreadException(
              ioe, "Failed to connect this DTLS client to a DTLS" + " server!", i)) {
            break;
          }
        }
      }
      if (dtlsTransport != null && srtp) {
        srtpProtectionProfile = tlsClient.getChosenProtectionProfile();
        tlsContext = tlsClient.getContext();
      }
    }
    // DTLS server
    else if (dtlsProtocol instanceof DTLSServerProtocol) {
      DTLSServerProtocol dtlsServerProtocol = (DTLSServerProtocol) dtlsProtocol;
      TlsServerImpl tlsServer = (TlsServerImpl) tlsPeer;

      for (int i = CONNECT_TRIES - 1; i >= 0; i--) {
        if (!enterRunInConnectThreadLoop(i, datagramTransport)) break;
        try {
          dtlsTransport = dtlsServerProtocol.accept(tlsServer, datagramTransport);
          break;
        } catch (IOException ioe) {
          if (!handleRunInConnectThreadException(
              ioe, "Failed to accept a connection from a DTLS client!", i)) {
            break;
          }
        }
      }
      if (dtlsTransport != null && srtp) {
        srtpProtectionProfile = tlsServer.getChosenProtectionProfile();
        tlsContext = tlsServer.getContext();
      }
    } else {
      // It MUST be either a DTLS client or a DTLS server.
      throw new IllegalStateException("dtlsProtocol");
    }

    SinglePacketTransformer srtpTransformer =
        (dtlsTransport == null || !srtp)
            ? null
            : initializeSRTPTransformer(srtpProtectionProfile, tlsContext);
    boolean closeSRTPTransformer;

    synchronized (this) {
      if (Thread.currentThread().equals(this.connectThread)
          && datagramTransport.equals(this.datagramTransport)) {
        this.dtlsTransport = dtlsTransport;
        _srtpTransformer = srtpTransformer;
        notifyAll();
      }
      closeSRTPTransformer = (_srtpTransformer != srtpTransformer);
    }
    if (closeSRTPTransformer && srtpTransformer != null) srtpTransformer.close();
  }

  /**
   * Sends the data contained in a specific byte array as application data through the DTLS
   * connection of this <tt>DtlsPacketTransformer</tt>.
   *
   * @param buf the byte array containing data to send.
   * @param off the offset in <tt>buf</tt> where the data begins.
   * @param len the length of data to send.
   */
  public void sendApplicationData(byte[] buf, int off, int len) {
    DTLSTransport dtlsTransport = this.dtlsTransport;
    Throwable throwable = null;

    if (dtlsTransport != null) {
      try {
        dtlsTransport.send(buf, off, len);
      } catch (IOException ioe) {
        throwable = ioe;
      }
    } else {
      throwable = new NullPointerException("dtlsTransport");
    }
    if (throwable != null) {
      // SrtpControl.start(MediaType) starts its associated
      // TransformEngine. We will use that mediaType to signal the normal
      // stop then as well i.e. we will ignore exception after the
      // procedure to stop this PacketTransformer has begun.
      if (mediaType != null && !tlsPeerHasRaisedCloseNotifyWarning) {
        logger.error("Failed to send application data over DTLS transport: ", throwable);
      }
    }
  }

  /**
   * Sets the <tt>RTPConnector</tt> which is to use or uses this <tt>PacketTransformer</tt>.
   *
   * @param connector the <tt>RTPConnector</tt> which is to use or uses this
   *     <tt>PacketTransformer</tt>
   */
  void setConnector(AbstractRTPConnector connector) {
    if (this.connector != connector) {
      this.connector = connector;

      DatagramTransportImpl datagramTransport = this.datagramTransport;

      if (datagramTransport != null) datagramTransport.setConnector(connector);
    }
  }

  /**
   * Sets the <tt>MediaType</tt> of the stream which this instance is to work for/be associated
   * with.
   *
   * @param mediaType the <tt>MediaType</tt> of the stream which this instance is to work for/be
   *     associated with
   */
  synchronized void setMediaType(MediaType mediaType) {
    if (this.mediaType != mediaType) {
      MediaType oldValue = this.mediaType;

      this.mediaType = mediaType;

      if (oldValue != null) stop();
      if (this.mediaType != null) start();
    }
  }

  /**
   * Enables/disables rtcp-mux.
   *
   * @param rtcpmux whether to enable or disable.
   */
  void setRtcpmux(boolean rtcpmux) {
    this.rtcpmux = rtcpmux;
  }

  /**
   * Sets the DTLS protocol according to which this <tt>DtlsPacketTransformer</tt> is to act either
   * as a DTLS server or a DTLS client.
   *
   * @param setup the value of the <tt>setup</tt> SDP attribute to set on this instance in order to
   *     determine whether this instance is to act as a DTLS client or a DTLS server
   */
  void setSetup(DtlsControl.Setup setup) {
    if (this.setup != setup) this.setup = setup;
  }

  /** Starts this <tt>PacketTransformer</tt>. */
  private synchronized void start() {
    if (this.datagramTransport != null) {
      if (this.connectThread == null && dtlsTransport == null) {
        logger.warn(
            getClass().getName()
                + " has been started but has failed to establish"
                + " the DTLS connection!");
      }
      return;
    }

    if (rtcpmux && Component.RTCP == componentID) {
      // In the case of rtcp-mux, the RTCP transformer does not create
      // a DTLS session. The SRTP context (_srtpTransformer) will be
      // initialized on demand using initializeSRTCPTransformerFromRtp().
      return;
    }

    AbstractRTPConnector connector = this.connector;

    if (connector == null) throw new NullPointerException("connector");

    DtlsControl.Setup setup = this.setup;
    SecureRandom secureRandom = DtlsControlImpl.createSecureRandom();
    final DTLSProtocol dtlsProtocolObj;
    final TlsPeer tlsPeer;

    if (DtlsControl.Setup.ACTIVE.equals(setup)) {
      dtlsProtocolObj = new DTLSClientProtocol(secureRandom);
      tlsPeer = new TlsClientImpl(this);
    } else {
      dtlsProtocolObj = new DTLSServerProtocol(secureRandom);
      tlsPeer = new TlsServerImpl(this);
    }
    tlsPeerHasRaisedCloseNotifyWarning = false;

    final DatagramTransportImpl datagramTransport = new DatagramTransportImpl(componentID);

    datagramTransport.setConnector(connector);

    Thread connectThread =
        new Thread() {
          @Override
          public void run() {
            try {
              runInConnectThread(dtlsProtocolObj, tlsPeer, datagramTransport);
            } finally {
              if (Thread.currentThread().equals(DtlsPacketTransformer.this.connectThread)) {
                DtlsPacketTransformer.this.connectThread = null;
              }
            }
          }
        };

    connectThread.setDaemon(true);
    connectThread.setName(DtlsPacketTransformer.class.getName() + ".connectThread");

    this.connectThread = connectThread;
    this.datagramTransport = datagramTransport;

    boolean started = false;

    try {
      connectThread.start();
      started = true;
    } finally {
      if (!started) {
        if (connectThread.equals(this.connectThread)) this.connectThread = null;
        if (datagramTransport.equals(this.datagramTransport)) this.datagramTransport = null;
      }
    }

    notifyAll();
  }

  /** Stops this <tt>PacketTransformer</tt>. */
  private synchronized void stop() {
    if (connectThread != null) connectThread = null;
    try {
      // The dtlsTransport and _srtpTransformer SHOULD be closed, of
      // course. The datagramTransport MUST be closed.
      if (dtlsTransport != null) {
        try {
          dtlsTransport.close();
        } catch (IOException ioe) {
          logger.error("Failed to (properly) close " + dtlsTransport.getClass(), ioe);
        }
        dtlsTransport = null;
      }
      if (_srtpTransformer != null) {
        _srtpTransformer.close();
        _srtpTransformer = null;
      }
    } finally {
      try {
        closeDatagramTransport();
      } finally {
        notifyAll();
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public RawPacket transform(RawPacket pkt) {
    byte[] buf = pkt.getBuffer();
    int off = pkt.getOffset();
    int len = pkt.getLength();

    // If the specified pkt represents a DTLS record, then it should pass
    // through this PacketTransformer (e.g. it has been sent through
    // DatagramTransportImpl).
    if (isDtlsRecord(buf, off, len)) return pkt;

    // SRTP
    if (!transformEngine.isSrtpDisabled()) {
      // DTLS-SRTP has not been initialized yet or has failed to
      // initialize.
      SinglePacketTransformer srtpTransformer = waitInitializeAndGetSRTPTransformer();

      if (srtpTransformer != null) pkt = srtpTransformer.transform(pkt);
      else if (DROP_UNENCRYPTED_PKTS) pkt = null;
      // XXX Else, it is our explicit policy to let the received packet
      // pass through and rely on the SrtpListener to notify the user that
      // the session is not secured.
    }
    // Pure/non-SRTP DTLS
    else {
      // The specified pkt will pass through this PacketTransformer only
      // if it gets transformed into a DTLS record.
      pkt = null;

      sendApplicationData(buf, off, len);
    }
    return pkt;
  }

  /**
   * Gets the {@code SRTPTransformer} used by this instance. If {@link #_srtpTransformer} does not
   * exist (yet) and the state of this instance indicates that its initialization is in progess,
   * then blocks until {@code _srtpTransformer} is initialized and returns it.
   *
   * @return the {@code SRTPTransformer} used by this instance
   */
  private SinglePacketTransformer waitInitializeAndGetSRTPTransformer() {
    SinglePacketTransformer srtpTransformer = _srtpTransformer;

    if (srtpTransformer != null) return srtpTransformer;

    if (rtcpmux && Component.RTCP == componentID) return initializeSRTCPTransformerFromRtp();

    // XXX It is our explicit policy to rely on the SrtpListener to notify
    // the user that the session is not secure. Unfortunately, (1) the
    // SrtpListener is not supported by this DTLS SrtpControl implementation
    // and (2) encrypted packets may arrive soon enough to be let through
    // while _srtpTransformer is still initializing. Consequently, we will
    // block and wait for _srtpTransformer to initialize.
    boolean interrupted = false;

    try {
      synchronized (this) {
        do {
          srtpTransformer = _srtpTransformer;
          if (srtpTransformer != null) break; // _srtpTransformer is initialized

          if (connectThread == null) {
            // Though _srtpTransformer is NOT initialized, there is
            // no point in waiting because there is no one to
            // initialize it.
            break;
          }

          try {
            // It does not really matter (enough) how much we wait
            // here because we wait in a loop.
            long timeout = CONNECT_TRIES * CONNECT_RETRY_INTERVAL;

            wait(timeout);
          } catch (InterruptedException ie) {
            interrupted = true;
          }
        } while (true);
      }
    } finally {
      if (interrupted) Thread.currentThread().interrupt();
    }

    return srtpTransformer;
  }
}
/** Central repository of loaded keystores */
public class LockssKeyStoreManager extends BaseLockssDaemonManager implements ConfigurableManager {

  protected static Logger log = Logger.getLogger("LockssKeyStoreManager");

  static final String PREFIX = Configuration.PREFIX + "keyMgr.";

  /** Default type for newly created keystores. */
  public static final String PARAM_DEFAULT_KEYSTORE_TYPE = PREFIX + "defaultKeyStoreType";

  public static final String DEFAULT_DEFAULT_KEYSTORE_TYPE = "JCEKS";

  /** Default keystore provider. */
  public static final String PARAM_DEFAULT_KEYSTORE_PROVIDER = PREFIX + "defaultKeyStoreProvider";

  public static final String DEFAULT_DEFAULT_KEYSTORE_PROVIDER = null;

  /**
   * Root of keystore definitions. For each keystore, pick a unique identifier and use it in place
   * of &lt;id&gt; in the following
   */
  public static final String PARAM_KEYSTORE = PREFIX + "keystore";

  /** If true the daemon will exit if a critical keystore is missing. */
  public static final String PARAM_EXIT_IF_MISSING_KEYSTORE = PREFIX + "exitIfMissingKeystore";

  public static final boolean DEFAULT_EXIT_IF_MISSING_KEYSTORE = true;

  /** keystore name, used by clients to refer to it */
  public static final String KEYSTORE_PARAM_NAME = "name";
  /** keystore file. Only one of file, resource or url should be set */
  public static final String KEYSTORE_PARAM_FILE = "file";
  /** keystore resource. Only one of file, resource or url should be set */
  public static final String KEYSTORE_PARAM_RESOURCE = "resource";
  /** keystore url. Only one of file, resource or url should be set */
  public static final String KEYSTORE_PARAM_URL = "url";
  /** keystore type */
  public static final String KEYSTORE_PARAM_TYPE = "type";
  /** keystore provider */
  public static final String KEYSTORE_PARAM_PROVIDER = "provider";
  /** keystore password */
  public static final String KEYSTORE_PARAM_PASSWORD = "******";
  /** private key password */
  public static final String KEYSTORE_PARAM_KEY_PASSWORD = "******";
  /** private key password file */
  public static final String KEYSTORE_PARAM_KEY_PASSWORD_FILE = "keyPasswordFile";
  /**
   * If true, and the keystore doesn't exist, a keystore with a self-signed certificate will be be
   * created.
   */
  public static final String KEYSTORE_PARAM_CREATE = "create";

  protected String defaultKeyStoreType = DEFAULT_DEFAULT_KEYSTORE_TYPE;
  protected String defaultKeyStoreProvider = DEFAULT_DEFAULT_KEYSTORE_PROVIDER;
  protected boolean paramExitIfMissingKeyStore = DEFAULT_EXIT_IF_MISSING_KEYSTORE;

  // Pseudo params for param doc
  public static final String DOC_PREFIX = PARAM_KEYSTORE + ".<id>.";

  /** Name by which daemon component(s) refer to this keystore */
  public static final String PARAM_KEYSTORE_NAME = DOC_PREFIX + KEYSTORE_PARAM_NAME;
  /** Keystore filename. Only one of file, resource or url should be set */
  public static final String PARAM_KEYSTORE_FILE = DOC_PREFIX + KEYSTORE_PARAM_FILE;
  /** Keystore resource. Only one of file, resource or url should be set */
  public static final String PARAM_KEYSTORE_RESOURCE = DOC_PREFIX + KEYSTORE_PARAM_RESOURCE;
  /** Keystore url. Only one of file, resource or url should be set */
  public static final String PARAM_KEYSTORE_URL = DOC_PREFIX + KEYSTORE_PARAM_URL;
  /** Keystore type (JKS, JCEKS, etc.) */
  public static final String PARAM_KEYSTORE_TYPE = DOC_PREFIX + KEYSTORE_PARAM_TYPE;
  /** Keystore provider (SunJCE, etc.) */
  public static final String PARAM_KEYSTORE_PROVIDER = DOC_PREFIX + KEYSTORE_PARAM_PROVIDER;
  /** Keystore password. Default is machine's fqdn */
  public static final String PARAM_KEYSTORE_PASSWORD = DOC_PREFIX + KEYSTORE_PARAM_PASSWORD;
  /** Private key password */
  public static final String PARAM_KEYSTORE_KEY_PASSWORD = DOC_PREFIX + KEYSTORE_PARAM_KEY_PASSWORD;
  /** private key password file */
  public static final String PARAM_KEYSTORE_KEY_PASSWORD_FILE =
      DOC_PREFIX + KEYSTORE_PARAM_KEY_PASSWORD_FILE;
  /**
   * If true, and the keystore doesn't exist, a keystore with a self-signed certificate will be be
   * created.
   */
  public static final String PARAM_KEYSTORE_CREATE = DOC_PREFIX + KEYSTORE_PARAM_CREATE;

  public static boolean DEFAULT_CREATE = false;

  protected Map<String, LockssKeyStore> keystoreMap = new HashMap<String, LockssKeyStore>();

  public void startService() {
    super.startService();
    loadKeyStores();
  }

  public synchronized void setConfig(
      Configuration config, Configuration prevConfig, Configuration.Differences changedKeys) {
    if (changedKeys.contains(PREFIX)) {
      defaultKeyStoreType = config.get(PARAM_DEFAULT_KEYSTORE_TYPE, DEFAULT_DEFAULT_KEYSTORE_TYPE);
      defaultKeyStoreProvider =
          config.get(PARAM_DEFAULT_KEYSTORE_PROVIDER, DEFAULT_DEFAULT_KEYSTORE_PROVIDER);
      paramExitIfMissingKeyStore =
          config.getBoolean(PARAM_EXIT_IF_MISSING_KEYSTORE, DEFAULT_EXIT_IF_MISSING_KEYSTORE);

      if (changedKeys.contains(PARAM_KEYSTORE)) {
        configureKeyStores(config);
        // defer initial set of keystore loading until startService
        if (isInited()) {
          // load any newly added keystores
          loadKeyStores();
        }
      }
    }
  }

  /**
   * Return the named LockssKeyStore or null
   *
   * @param name the keystore name
   */
  public LockssKeyStore getLockssKeyStore(String name) {
    return getLockssKeyStore(name, null);
  }

  /**
   * Return the named LockssKeyStore
   *
   * @param name the keystore name
   * @param criticalServiceName if non-null, this is a criticial keystore whose unavailability
   *     should cause the daemon to exit (if org.lockss.keyMgr.exitIfMissingKeystore is true)
   */
  public LockssKeyStore getLockssKeyStore(String name, String criticalServiceName) {
    LockssKeyStore res = keystoreMap.get(name);
    checkFact(res, name, criticalServiceName, null);
    return res;
  }

  /**
   * Convenience method to return the KeyManagerFactory from the named LockssKeyStore, or null
   *
   * @param name the keystore name
   */
  public KeyManagerFactory getKeyManagerFactory(String name) {
    return getKeyManagerFactory(name, null);
  }

  /**
   * Convenience method to return the KeyManagerFactory from the named LockssKeyStore.
   *
   * @param name the keystore name
   * @param criticalServiceName if non-null, this is a criticial keystore whose unavailability
   *     should cause the daemon to exit (if org.lockss.keyMgr.exitIfMissingKeystore is true)
   */
  public KeyManagerFactory getKeyManagerFactory(String name, String criticalServiceName) {
    LockssKeyStore lk = getLockssKeyStore(name, criticalServiceName);
    if (lk != null) {
      KeyManagerFactory fact = lk.getKeyManagerFactory();
      checkFact(fact, name, criticalServiceName, "found but contains no private keys");

      return fact;
    }
    return null;
  }

  /** Convenience method to return the TrustManagerFactory from the named LockssKeyStore, or null */
  public TrustManagerFactory getTrustManagerFactory(String name) {
    return getTrustManagerFactory(name, null);
  }

  /**
   * Convenience method to return the TrustManagerFactory from the named LockssKeyStore.
   *
   * @param name the keystore name
   * @param criticalServiceName if non-null, this is a criticial keystore whose unavailability
   *     should cause the daemon to exit (if org.lockss.keyMgr.exitIfMissingKeystore is true)
   */
  public TrustManagerFactory getTrustManagerFactory(String name, String criticalServiceName) {
    LockssKeyStore lk = getLockssKeyStore(name, criticalServiceName);
    if (lk != null) {
      TrustManagerFactory fact = lk.getTrustManagerFactory();
      checkFact(fact, name, criticalServiceName, "found but contains no trusted certificates");
      return fact;
    }
    return null;
  }

  private void checkFact(Object fact, String name, String criticalServiceName, String message) {
    if (fact == null && criticalServiceName != null) {
      String msg =
          StringUtil.isNullString(name)
              ? ("No keystore name given for critical keystore"
                  + " needed for service "
                  + criticalServiceName
                  + ", daemon exiting")
              : ("Critical keystore "
                  + name
                  + " "
                  + ((message != null) ? message : "not found or not loadable")
                  + " for service "
                  + criticalServiceName
                  + ", daemon exiting");
      log.critical(msg);

      if (paramExitIfMissingKeyStore) {
        System.exit(Constants.EXIT_CODE_KEYSTORE_MISSING);
      } else {
        throw new IllegalArgumentException(msg);
      }
    }
  }

  /** Create LockssKeystores from config subtree below {@link #PARAM_KEYSTORE} */
  void configureKeyStores(Configuration config) {
    Configuration allKs = config.getConfigTree(PARAM_KEYSTORE);
    for (Iterator iter = allKs.nodeIterator(); iter.hasNext(); ) {
      String id = (String) iter.next();
      Configuration oneKs = allKs.getConfigTree(id);
      try {
        LockssKeyStore lk = createLockssKeyStore(oneKs);
        String name = lk.getName();
        if (name == null) {
          log.error("KeyStore definition missing name: " + oneKs);
          continue;
        }
        LockssKeyStore old = keystoreMap.get(name);
        if (old != null && !lk.equals(old)) {
          log.warning(
              "Keystore "
                  + name
                  + " redefined.  "
                  + "New definition may not take effect until daemon restart");
        }

        log.debug("Adding keystore " + name);
        keystoreMap.put(name, lk);

      } catch (Exception e) {
        log.error("Couldn't create keystore: " + oneKs, e);
      }
    }
  }

  /** Create LockssKeystore from a config subtree */
  LockssKeyStore createLockssKeyStore(Configuration config) {
    log.debug2("Creating LockssKeyStore from config: " + config);
    String name = config.get(KEYSTORE_PARAM_NAME);
    LockssKeyStore lk = new LockssKeyStore(name);

    String file = config.get(KEYSTORE_PARAM_FILE);
    String resource = config.get(KEYSTORE_PARAM_RESOURCE);
    String url = config.get(KEYSTORE_PARAM_URL);

    if (!StringUtil.isNullString(file)) {
      lk.setLocation(file, LocationType.File);
    } else if (!StringUtil.isNullString(resource)) {
      lk.setLocation(resource, LocationType.Resource);
    } else if (!StringUtil.isNullString(url)) {
      lk.setLocation(url, LocationType.Url);
    }

    lk.setType(config.get(KEYSTORE_PARAM_TYPE, defaultKeyStoreType));
    lk.setProvider(config.get(KEYSTORE_PARAM_PROVIDER, defaultKeyStoreProvider));
    lk.setPassword(config.get(KEYSTORE_PARAM_PASSWORD));
    lk.setKeyPassword(config.get(KEYSTORE_PARAM_KEY_PASSWORD));
    lk.setKeyPasswordFile(config.get(KEYSTORE_PARAM_KEY_PASSWORD_FILE));
    lk.setMayCreate(config.getBoolean(KEYSTORE_PARAM_CREATE, DEFAULT_CREATE));
    return lk;
  }

  void loadKeyStores() {
    List<LockssKeyStore> lst = new ArrayList<LockssKeyStore>(keystoreMap.values());
    for (LockssKeyStore lk : lst) {
      try {
        lk.load();
      } catch (Exception e) {
        log.error("Can't load keystore " + lk.getName(), e);
        keystoreMap.remove(lk.getName());
      }
    }
  }
}