All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jadex.platform.service.security.SecurityService Maven / Gradle / Ivy

package jadex.platform.service.security;

import java.net.InetAddress;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import jadex.bridge.IComponentIdentifier;
import jadex.bridge.IExternalAccess;
import jadex.bridge.IInternalAccess;
import jadex.bridge.ImmediateComponentStep;
import jadex.bridge.SFuture;
import jadex.bridge.service.IServiceIdentifier;
import jadex.bridge.service.RequiredServiceInfo;
import jadex.bridge.service.annotation.SecureTransmission;
import jadex.bridge.service.annotation.Security;
import jadex.bridge.service.annotation.Service;
import jadex.bridge.service.annotation.ServiceComponent;
import jadex.bridge.service.annotation.ServiceIdentifier;
import jadex.bridge.service.annotation.ServiceShutdown;
import jadex.bridge.service.annotation.ServiceStart;
import jadex.bridge.service.component.IRequiredServicesFeature;
import jadex.bridge.service.types.context.IContextService;
import jadex.bridge.service.types.security.IAuthorizable;
import jadex.bridge.service.types.security.ISecurityService;
import jadex.bridge.service.types.security.KeyStoreEntry;
import jadex.bridge.service.types.security.MechanismInfo;
import jadex.bridge.service.types.settings.ISettingsService;
import jadex.commons.ChangeEvent;
import jadex.commons.IPropertiesProvider;
import jadex.commons.Properties;
import jadex.commons.Property;
import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.commons.Tuple2;
import jadex.commons.future.DelegationResultListener;
import jadex.commons.future.ExceptionDelegationResultListener;
import jadex.commons.future.Future;
import jadex.commons.future.IFuture;
import jadex.commons.future.IResultListener;
import jadex.commons.future.ISubscriptionIntermediateFuture;
import jadex.commons.future.SubscriptionIntermediateFuture;
import jadex.commons.future.TerminationCommand;
import jadex.commons.security.SSecurity;


@Service
public class SecurityService implements ISecurityService
{
	//-------- constants --------
	
	/** Properties id for the settings service. */
	public static final String	PROEPRTIES_ID	= "securityservice";

	//-------- attributes --------
	
	/** The component. */
	@ServiceComponent
	protected IInternalAccess	component;
	
	/** The service id. */
	@ServiceIdentifier
	protected IServiceIdentifier sid;
	
	/** Flag to enable / disable password protection. */
	protected boolean	usepass;
	
	/** Determines if password was specified during creation. (i.e. in Platform Configuration) */
	protected boolean argsusepass;
	
	/** Print password on startup or change. */
	protected boolean	printpass;
	
	/** The local password (if any). */
	protected String	password;
	
	/** The stored passwords. */
	protected Map	platformpasses;
	
	/** The stored passwords. */
	protected Map	networkpasses;
	
	/** The trusted lan mode. */
	protected boolean trustedlan;
	
	/** Determines if trusted lan was specified during creation. */
	protected boolean argstrustedlan;
	
	/** The default message validity duration. */
	protected long valdur;
	
	
	/** The path to the keystore. */
	protected String storepath;
	
	/** The keystore password. */
	protected String storepass;
	
	/** The key password. */
	protected String keypass;
	
	/** The ContextService. */
	protected IContextService contextser;
	
	/** The currently valid digests. (secret -> timestamp, digest)*/
	protected Map> digests;

	
	/** The keystore. */
	protected KeyStore keystore;
	
	/** The list of key aquire mechanisms. */
	protected List mechanisms;
	
	/** The currently selected mechanism. */
	protected int selmech;
	
	/** The futures of active subscribers. */
	protected Set>> subscribers;
	
	
	/** The mappings of virtual names to platform names. */
	protected Map> virtualsmap;
	
	
	/** The network ips, cached for speed. */
	protected List networkips;
	
	//-------- setup --------
	
	/**
	 *  Create a security service.
	 */
	public SecurityService()
	{
		this(Boolean.TRUE, true, Boolean.FALSE, null, null, null, null, null);
	}

	/**
	 *  Create a security service.
	 */
	public SecurityService(Boolean usepass, boolean printpass, Boolean trustedlan, 
		String[] networknames, String[] networkpasses)
	{
		this(usepass, printpass, trustedlan, networknames, networkpasses, null, null, null);
	}
	
	/**
	 *  Create a security service.
	 */
	public SecurityService(Boolean usepass, boolean printpass, Boolean trustedlan, 
		String[] networknames, String[] networkpasses, AAcquisitionMechanism[] mechanisms, 
		Map> namemap, Long valdur)
	{
		this.valdur = valdur==null? 5*65536: valdur.longValue(); // 5 min default
		this.virtualsmap = namemap==null? new HashMap>(): namemap;
		this.subscribers = new LinkedHashSet>>();
		this.platformpasses	= new LinkedHashMap();
		this.networkpasses	= new LinkedHashMap();
		
		if(networknames!=null)
		{
			for(int i=0; i>();
		this.usepass = usepass!=null? usepass.booleanValue(): true;
		this.argsusepass = usepass != null;
		this.printpass = printpass;
		this.trustedlan = trustedlan!=null? trustedlan.booleanValue(): false;
		this.argstrustedlan = trustedlan != null;
		this.storepath = "./keystore";
		this.storepass = "keystore";
		this.keypass = "keystore";
		this.mechanisms = new ArrayList();
		if(mechanisms!=null)
		{
			for(AAcquisitionMechanism mech: mechanisms)
			{
				mech.init(this);
				this.mechanisms.add(mech);
			}
		}
		else
		{
			AAcquisitionMechanism mech = new DecentralizedAcquisitionMechanism();
			mech.init(this);
			this.mechanisms.add(mech);
			mech = new TTPAcquisitionMechanism();
			mech.init(this);
			this.mechanisms.add(mech);
		}
	}
	
	/**
	 *  Start the service.
	 */
	@ServiceStart
	public IFuture	start()
	{
		final Future	ret	= new Future();
//		this.trustednets = new ArrayList();
		
		component.getComponentFeature(IRequiredServicesFeature.class).searchService(IContextService.class, RequiredServiceInfo.SCOPE_PLATFORM)
			.addResultListener(new ExceptionDelegationResultListener(ret)
		{
			public void customResultAvailable(IContextService result)
			{
				contextser = result;
				
				contextser.getNetworkIps().addResultListener(new ExceptionDelegationResultListener, Void>(ret)
				{
					public void customResultAvailable(List ips)
					{
						networkips = ips;
						
						setTrustedLanMode(trustedlan);
					
						getSettingsService().addResultListener(new ExceptionDelegationResultListener(ret)
						{
							public void customResultAvailable(final ISettingsService settings)
							{
								if(settings==null)
								{
									// generate new password, if no security settings exist, yet.
									password	= UUID.randomUUID().toString().substring(0, 12);
			//						usepass	= true;
									ret.setResult(null);
								}
								else
								{
									settings.getProperties(PROEPRTIES_ID)
										.addResultListener(new ExceptionDelegationResultListener(ret)
									{
										public void customResultAvailable(final Properties props)
										{
											// generate new password, if no security settings exist, yet.
											final boolean	genpass	= props==null || props.getProperty("password")==null; 
											if(genpass)
											{
												password	= UUID.randomUUID().toString().substring(0, 12);
					//							usepass	= true;
											}
	
											if(props!=null)
											{
												selmech = props.getIntProperty("selected_mechanism"); 
	//											System.out.println("selm: "+selmech);
												publishEvent(new ChangeEvent(null, PROPERTY_SELECTEDMECHANISM, Integer.valueOf(selmech)));
											}
											
											final IExternalAccess	access	= component.getExternalAccess();
											
											settings.registerPropertiesProvider(PROEPRTIES_ID, new IPropertiesProvider()
											{
												public IFuture setProperties(final Properties props)
												{
													return access.scheduleStep(new ImmediateComponentStep()
													{
														public IFuture execute(IInternalAccess ia)
														{
															String spa = props.getStringProperty("storepath");
															if(spa!=null && spa.length()>0)
																storepath = spa;
															String sps = props.getStringProperty("storepass");
															if(sps!=null && spa.length()>0)
																storepass = sps;
															String kp = props.getStringProperty("keypass");
															if(kp!=null && kp.length()>0)
																keypass = kp;
															long vd = props.getLongProperty("validityduration");
															if(vd>0)
																valdur = vd;
															
															if(!argsusepass)
															{
																usepass = props.getBooleanProperty("usepass");
					//											System.out.println("usepass: "+usepass);
															}
															password	= props.getStringProperty("password");
															
															if(!argstrustedlan)
															{
																setTrustedLanMode(props.getBooleanProperty("trustedlan"));
															}
															
															Property[]	passes	= props.getProperties("passwords");
					//										platformpasses	= new LinkedHashMap();
															for(int i=0; i addrs = contextser.getNetworkIps();
															Set trs = new HashSet();
															for(InetAddress addr: networkips)
															{
																trs.add(addr.getHostAddress());
															}
																
															Property[]	networks	= props.getProperties("networks");
					//										networkpasses	= new LinkedHashMap();
															for(int i=0; i getProperties()
												{
													return access.scheduleStep(new ImmediateComponentStep()
													{
														public IFuture execute(IInternalAccess ia)
														{
															Properties	ret	= new Properties();
															ret.addProperty(new Property("validityduration", ""+valdur));
															ret.addProperty(new Property("usepass", ""+usepass));
															ret.addProperty(new Property("password", password));
															ret.addProperty(new Property("selected_mechanism", ""+selmech));
															if(platformpasses!=null)
															{
																for(String platform: platformpasses.keySet())
																{
																	ret.addProperty(new Property(platform, "passwords", platformpasses.get(platform)));
																}
															}
															if(networkpasses!=null)
															{
																for(String network: networkpasses.keySet())
																{
																	ret.addProperty(new Property(network, "networks", networkpasses.get(network)));
																}
															}
															ret.addProperty(new Property("trustedlan", ""+trustedlan));
															
															ret.addProperty(new Property("storepath", storepath));
															ret.addProperty(new Property("storepass", storepass));
															ret.addProperty(new Property("keypass", keypass));
															
															ret.addProperty(new Property("selmech", ""+selmech));
															
															if(mechanisms!=null)
															{
																for(AAcquisitionMechanism mech: mechanisms)
																{
																	ret.addSubproperties(SReflect.getInnerClassName(mech.getClass()), mech.getProperties());
																}
															}
															
															if(virtualsmap!=null && !virtualsmap.isEmpty())
															{
																Properties sb = new Properties();
																ret.addSubproperties("virtuals", sb);
																for(Map.Entry> virtual: virtualsmap.entrySet())
																{
																	Set vals = virtual.getValue();
																	if(vals==null || vals.isEmpty())
																	{
																		sb.addProperty(new Property(virtual.getKey(), null));
																	}
																	else
																	{
																		for(String value: virtual.getValue())
																		{
																			sb.addProperty(new Property(virtual.getKey(), value));
																		}
																	}
																}
															}
															
	//														System.out.println("fini2");
															
															return new Future(ret);
														}
													});
												}
											}).addResultListener(new DelegationResultListener(ret)
											{
												public void customResultAvailable(Void result)
												{
													// If new password was generated, save settings such that new platform instances use it.
													if(genpass)
													{
														if(printpass && usepass)
														{
															System.out.println("Generated platform password: "+password);
														}
														settings.saveProperties().addResultListener(new DelegationResultListener(ret));
													}
													else
													{
														if(printpass && usepass)
														{
															System.out.println("Using stored platform password: "+password);
														}
														super.customResultAvailable(result);
													}
												}
											});
										}
									});
								}
							}
						});
					}
				});
			}
		});
		
	
		
		return ret;
	}

	/**
	 *  Get the settings service.
	 */
	public IFuture getSettingsService()
	{
		final Future ret = new Future();
		IFuture fut = component.getComponentFeature(IRequiredServicesFeature.class).searchService(ISettingsService.class, RequiredServiceInfo.SCOPE_PLATFORM);
		fut.addResultListener(new DelegationResultListener(ret)
		{
			public void exceptionOccurred(Exception exception)
			{
				ret.setResult(null);
			}
		});
		return ret;
	}
	
	/**
	 *  Shutdown the service.
	 */
	@ServiceShutdown
	public IFuture shutdown()
	{
		final Future	ret	= new Future();
		
		// Save keystore on disk
		saveKeyStore();
		
		// Save settings
		component.getComponentFeature(IRequiredServicesFeature.class).searchService(ISettingsService.class, RequiredServiceInfo.SCOPE_PLATFORM)
			.addResultListener(new IResultListener()
		{
			public void resultAvailable(ISettingsService settings)
			{
				settings.deregisterPropertiesProvider(PROEPRTIES_ID)
					.addResultListener(new DelegationResultListener(ret)
				{
					public void customResultAvailable(Void result)
					{
						SecurityService.this.platformpasses	= null;
						ret.setResult(null);
					}
				});
			}
			
			public void exceptionOccurred(Exception exception)
			{
				// No settings service: ignore.
				SecurityService.this.platformpasses	= null;
				ret.setResult(null);
			}
		});
		
		return ret;
	}
	
	/**
	 *  Get the keystore.
	 */
	protected KeyStore getKeyStore()
	{
		if(keystore==null)
		{
			// Fetch keystore and possible auto-generate self-signed certificate
			String name = component.getComponentIdentifier().getPlatformPrefix();
			this.keystore = SSecurity.getKeystore(storepath, storepass, keypass, name);
		}
		
		return keystore;
	}
	
	//-------- password management --------
	
	/**
	 *  Check if password protection is enabled.
	 *  @return	True, if password protection is enabled.
	 */
	public IFuture	isUsePassword()
	{
		return new Future(usepass);
	}

	/**
	 *  Enable / disable password protection.
	 *  @param enable	If true, password protection is enabled, otherwise disabled.
	 *  @throws Exception, when enable is true and no password is set.
	 */
	public IFuture	setUsePassword(boolean enable)
	{
		IFuture	ret;
		if(enable && password==null)
		{
			ret	= new Future(new IllegalStateException("Cannot enable password protection, no password set."));
		}
		else
		{
			this.usepass	= enable;
			ret	= IFuture.DONE;
			
			if(printpass && usepass)
			{
				System.out.println("Using stored platform password: "+password);
			}
			
			publishEvent(new ChangeEvent(null, PROPERTY_USEPASS, enable));
		}
		
		return ret;
	}

	/**
	 *  Get the local password.
	 *  @return	The password of the local platform (if any).
	 */
	@SecureTransmission
	// Todo: password is transferred in plain text unless transport uses encryption.
	public IFuture	getLocalPassword()
	{
		return new Future(password);
	}

	/**
	 *  Set the local password.
	 *  @param password	The password of the local platform. 
	 *  @throws  Exception, when a null password is provided and use password is true.
	 */
	@SecureTransmission
	// Todo: password is transferred in plain text unless transport uses encryption.
	public IFuture	setLocalPassword(String password)
	{
		IFuture	ret;
		if(password==null && usepass)
		{
			ret	= new Future(new IllegalStateException("Cannot set password to null, when password protection is enabled."));
		}
		else
		{
			this.password	= password;
			ret	= IFuture.DONE;
			if(printpass && usepass)
			{
				System.out.println("Using new platform password: "+password);
			}
			
			publishEvent(new ChangeEvent(null, PROPERTY_LOCALPASS, password));
		}
		return ret;
	}
	
	/**
	 *  Get the validity duration.
	 *  @return The validityduration.
	 */
	public IFuture getValidityDuration()
	{
		return new Future(Long.valueOf(valdur));
	}

	/**
	 *  Set the validity duration.
	 *  @param validityduration The validityduration to set.
	 */
	public IFuture setValidityDuration(long validityduration)
	{
		this.valdur = validityduration;
		publishEvent(new ChangeEvent(null, PROPERTY_VALIDITYDURATION, Long.valueOf(valdur)));
		return IFuture.DONE;
	}

	/**
	 *  Get the password for a target component.
	 *  @param target	The id of the target component.
	 *  @return	The stored password. Returns null if no password is stored, unless the
	 *    component is a local component in which case the local password (if any) is returned.
	 */
	@SecureTransmission
	// Todo: password is transferred in plain text unless transport uses encryption.
	public IFuture	getPlatformPassword(IComponentIdentifier target)
	{
		String	starget	= target.getPlatformPrefix();
		String	ret	= platformpasses.get(starget);
		if(ret==null && starget.equals(component.getComponentIdentifier().getPlatformPrefix()))
		{
			ret	= this.password;
		}
		return new Future(ret);
	}

	/**
	 *  Set the password for a target component.
	 *  Note that passwords are currently stored on a per platform basis,
	 *  i.e. there is only one stored password for all components of the same platform.
	 *  Moreover, the security service strips the auto-generated extension from the platform
	 *  name and therefore can reuse the password for different instances of the same platform.
	 *  @param target	The id of the target component.
	 *  @param password	The password or null if no password should be used.
	 */
	@SecureTransmission
	// Todo: password is transferred in plain text unless transport uses encryption.
	public IFuture	setPlatformPassword(IComponentIdentifier target, String password)
	{
		if(password!=null)
		{
			platformpasses.put(target.getPlatformPrefix(), password);
		}
		else
		{
			// Use remove to avoid keeping old mappings forever (name would still be stored otherwise)
			platformpasses.remove(target.getPlatformPrefix());
		}
		
		publishEvent(new ChangeEvent(null, PROPERTY_PLATFORMPASS, platformpasses));

		return IFuture.DONE;		
	}

	/**
	 *  Get the password for a network.
	 *  @param targetName	The id of the target component.
	 *  @return	The stored password. Returns null if no password is stored, unless the
	 *    component is a local component in which case the local password (if any) is returned.
	 */
	@SecureTransmission
	// Todo: password is transferred in plain text unless transport uses encryption.
	public IFuture	getNetworkPassword(String network)
	{
		String	ret	= networkpasses.get(network);
		return new Future(ret);
	}

	/**
	 *  Set the password for a network.
	 *  @param network	The id of the network.
	 *  @param password	The password or null if no password should be used.
	 */
	@SecureTransmission
	// Todo: password is transferred in plain text unless transport uses encryption.
	public IFuture	setNetworkPassword(String network, String password)
	{
		if(password!=null)
		{
			networkpasses.put(network, password);			
		}
		else
		{
			// Use remove to avoid keeping old mappings forever (name would still be stored otherwise)
			networkpasses.remove(network);
		}
		
		publishEvent(new ChangeEvent(null, PROPERTY_NETWORKPASS, networkpasses));

		return IFuture.DONE;	
	}
	
	/**
	 *  Get all stored passwords.
	 *  @return A map containing the stored passwords as pairs (platform name -> password).
	 */
	@SecureTransmission
	// Todo: passwords are transferred in plain text unless transport uses encryption.
	public IFuture>	getPlatformPasswords()
	{
		return new Future>(platformpasses);
	}	
	
	/**
	 *  Get all stored network passwords.
	 *  @return A map containing the stored passwords as pairs (network name -> password).
	 */
	@SecureTransmission
	// Todo: passwords are transferred in plain text unless transport uses encryption.
	public IFuture>	getNetworkPasswords()
	{
		return new Future>(networkpasses);
	}
	
	/**
	 *  Set the trusted lan mode.
	 *  @param allowed The flag if it is allowed.
	 */
	public IFuture setTrustedLanMode(boolean allowed)
	{
		List addrs = networkips;//contextser.getNetworkIps();
		
		if(allowed)
		{
			for(InetAddress addr: addrs)
			{
				if(!networkpasses.keySet().contains(addr.getHostAddress()))
				{
					setNetworkPassword(addr.getHostAddress(), "");
				}
			}
		}
		else if(!allowed)
		{
			for(InetAddress addr: addrs)
			{
				setNetworkPassword(addr.getHostAddress(), null);
			}
		}
		
		this.trustedlan = allowed;
		
		publishEvent(new ChangeEvent(null, PROPERTY_TRUSTEDLAN, allowed));
		
		return IFuture.DONE;
	}
	
	/**
	 *  Get the trusted lan mode.
	 *  @return True if is in trusted lan mode.
	 */
	public IFuture isTrustedLanMode()
	{
		return new Future(trustedlan? Boolean.TRUE: Boolean.FALSE);
	}
	
	/**
	 *  Set the keystore info.
	 *  @return The path to the keystore. The password of the store. The password of the key.
	 */
	public IFuture getKeystoreInfo()
	{
		return new Future(new String[]{storepath, storepass, keypass});
	}
	
	/**
	 *  Set the keystore info.
	 *  @param storepath The path to the keystore.
	 *  @param storepass The password of the store.
	 *  @param keypass The password of the key.
	 */
	public IFuture setKeystoreInfo(String storepath, String storepass, String keypass)
	{
		if(storepath!=null)
			this.storepath = storepath;
		if(storepass!=null)
			this.storepass = storepass;
		if(keypass!=null)
			this.keypass = keypass;
		
		// reset keystore
		this.keystore = null;
		
		publishEvent(new ChangeEvent(null, PROPERTY_KEYSTORESETTINGS, new String[]{storepath, storepass, keypass}));
		
		return IFuture.DONE;
	}

	//-------- request validation --------
	
	/**
	 *  Validate a request.
	 *  @param request	The request to be validated.
	 *  @throws	SecurityException, when request is not valid.
	 */
	public IFuture	validateRequest(IAuthorizable request)
	{
		String	error	= null;
		if(Security.PASSWORD.equals(request.getSecurityLevel()) && usepass && password!=null)
		{
			if(request.getAuthenticationData()!=null)
			{
				error = checkDigests(request, password, networkpasses);
			}
			else
			{
				error	= "Shared secret required.";
			}
		}
		
		return error==null ? new Future((Void)null) : new Future(new SecurityException(error+" "+request));
	}
	
	/**
	 *  Get the digest.
	 */
	public byte[] getDigest(long timestamp, String secret)
	{
		byte[] ret;
		// Get the digest that belongs to the secret
		Tuple2 tst = digests.get(secret);
		Long ts = Long.valueOf(timestamp);
		
		// Check if the timestamp of the digest is still ok
		if(tst!=null && tst.getFirstEntity().equals(ts))
		{
//			System.out.println("reuse: "+timestamp+" "+secret);
			ret = tst.getSecondEntity();
		}
		else
		{
			ret = buildDigest(timestamp, secret);
			digests.put(secret, new Tuple2(ts, ret));
		}
		return ret;
	}
	
	/**
	 *  Check if the test digest in contained in the digest list. 
	 */
	public static boolean checkDigest(byte[] test, List digests)
	{
		boolean ret = false;
		for(byte[] dig: digests)
		{
			ret = Arrays.equals(dig, test);
			if(ret)
				break;
		}
		return ret;
	}
	
	/**
	 *  Check if there is a shared secret.
	 */
	public String checkDigests(IAuthorizable request, String password, Map networkpasses)
	{
		String ret = null;
		
		List digests = request.getAuthenticationData();
		long timestamp = request.getTimestamp();
		long vd = request.getValidityDuration()==0? valdur: request.getValidityDuration();
		
		String prefix = request.getValidityDuration()==0? request.getDigestContent(): 
			request.getValidityDuration()+request.getDigestContent();
//		String prefix = request.getValidityDuration()+request.getDigestContent();
		
		boolean tst = false;
		// because timestamp is stripped, validity duration needs to be extended into future,
		// i.e. timestamp is valid in relative range -1..2 as follows:
		// |- vd - | - stripped timestamp in vd range -|- vd -|
		// -1      0                                   1      2
		long	dt	= System.currentTimeMillis()-timestamp;
		if(dt<0 && dt>-vd || dt>=0 && dt<2*vd)
		{
			// test if other knows my password
			tst = checkDigest(buildDigest(timestamp, prefix+password), digests);
			
			// test if other shares one of my networks
			if(!tst)
			{
				for(String net: networkpasses.keySet())
				{
					byte[] netdig = buildDigest(timestamp, prefix+net+networkpasses.get(net));
					tst = checkDigest(netdig, digests);
					if(tst)
						break;
				}
			}
			
			if(!tst)
			{
				ret = "No shared secret.";
			}
		}
		else
		{
//			System.out.println("Timestamp too old: "+timestamp+", vd="+vd);
			ret = "Timestamp too old.";
		}
		
		return ret;
	}
	
	/**
	 *  Preprocess a request.
	 *  Adds authentication data to the request, if required by the intended target.
	 *  @param request	The request to be preprocessed.
	 *  @param target	The target to which the request should be sent later.
	 */
	public IFuture	preprocessRequest(IAuthorizable request, IComponentIdentifier target)
	{
		long	timestamp	= System.currentTimeMillis();
//		System.out.println("ts1: "+SUtil.arrayToString(SUtil.longToBytes(timestamp)));
//		timestamp = timestamp>>>16<<16; // New digest every minute
		long vd = request.getValidityDuration()==0? valdur: request.getValidityDuration();
		int num = SUtil.log2(vd);
		for(int i=0; i>>= 1;
		}
		for(int i=0; i authdata = new ArrayList();

		String prefix = request.getValidityDuration()==0? request.getDigestContent(): 
			request.getValidityDuration()+request.getDigestContent();
		
		if(target!=null)
		{
			// First password of target
			String	stripped	= target.getPlatformPrefix();
			// Use stored password or local password for local targets.  
			String	pw	= platformpasses.containsKey(stripped)? platformpasses.get(stripped)
				: stripped.equals(component.getComponentIdentifier().getPlatformPrefix())? password : null;
	
			if(pw!=null)
			{
				authdata.add(getDigest(timestamp, prefix+pw));
	//			System.out.println("sending auth data: "+new String(Base64.encode(request.getAuthenticationData()))+", "+pw+", "+timestamp);
			}
		}
		else
		{
			// + own
			authdata.add(getDigest(timestamp, prefix+password));
			
			// Add all password authentications
			for(String name: platformpasses.keySet())
			{
				authdata.add(getDigest(timestamp, prefix+name+platformpasses.get(name)));
			}
		}
		
		// Add all network authentications
		for(String net: networkpasses.keySet())
		{
			authdata.add(getDigest(timestamp, prefix+net+networkpasses.get(net)));
		}
		
		// Add trusted authentications (in case other has turned on lan trusted and this one not)
		if(!trustedlan)
		{
			for(InetAddress addr: networkips)//contextser.getNetworkIps())
			{
				authdata.add(getDigest(timestamp, prefix+addr.getHostAddress()));
			}
		}
		
		request.setAuthenticationData(authdata);
		
//		System.out.println(authdata.size());
		
		return IFuture.DONE;
	}
	
	/**
	 *  Sign a byte[] with the platform key that is stored in the
	 *  keystore under the platform prefix name.
	 */
	public IFuture signCall(byte[] content)
	{
		Future ret = new Future();
		
		try
		{
//			Provider[] provs = java.security.Security.getProviders();
//			System.out.println("prov: "+SUtil.arrayToString(provs));
//			java.security.Security.addProvider(new BouncyCastleProvider());
//			provs = java.security.Security.getProviders();
//			System.out.println("prov: "+SUtil.arrayToString(provs));
			
			String name = component.getComponentIdentifier().getPlatformPrefix();
			Key key = getKeyStore().getKey(name, keypass.toCharArray());
			Certificate cert = getKeyStore().getCertificate(name);
			Signature eng = Signature.getInstance(SSecurity.getAlgorithm(cert));
			byte[] signed = SSecurity.signContent((PrivateKey)key, eng, content);
		    ret.setResult(signed);
		}
		catch(Exception e)
		{
			ret.setException(e);
		}
		
		return ret;
	}
	
	/**
	 *  Check if the name belongs to the mappings of one
	 *  of the virtual names.
	 *  @param virtuals The virtual names.
	 *  @param name The name to check.
	 *  @return True, if name is ok.
	 */
	public IFuture checkVirtual(String[] virtuals, String name)
	{
		boolean ret = false;
		
		if(virtuals!=null)
		{
			for(int i=0; i names = virtualsmap.get(virtuals[i]);
				if(names!=null)
				{
					ret = names.contains(name);
				}
			}
		}
		
		return ret? new Future((Void)null): new Future(new SecurityException("Name not mapped by virtual names: "+name));
	}
	
	/**
	 *  Add a name to the mappings of a virtual name.
	 *  @param virtual The virtual name.
	 *  @param name The name to add.
	 */
	public IFuture addVirtual(String virtual, String name)
	{
		Future ret = new Future();
		
		Set names = virtualsmap.get(virtual);
		if(names==null)
		{
			names	= new HashSet();
			virtualsmap.put(virtual, names);
		}
		if(name!=null)
		{
			if(!names.add(name))
			{
				ret.setException(new RuntimeException("Virtual name already assigned."));
			}
			else
			{
				ret.setResult(null);
			}
		}
		else
		{
			ret.setResult(null);
		}

		return ret;
	}
	
	/**
	 *  Remove a name from the mappings of a virtual name.
	 *  @param virtual The virtual name.
	 *  @param name The name to remove.
	 */
	public IFuture removeVirtual(String virtual, String name)
	{
		Set names = virtualsmap.get(virtual);
		if(names!=null)
		{
			if(name==null)
			{
				virtualsmap.remove(virtual);
			}
			else
			{
				names.remove(name);
			}
//			if(names.isEmpty())
//			{
//				virtualsmap.remove(virtual);
//			}
		}

		return IFuture.DONE;		
	}
	
	/**
	 *  Get the virtual names and their contents.
	 *  @return The map of virtual names and their platform mappings.
	 */
	@SecureTransmission
	public IFuture>> getVirtuals()
	{
		return new Future>>(virtualsmap);
	}

	
	/**
	 *  Verify an authenticated service call.
	 *  @param content The content that should be checked.
	 *  @param signed The desired output hash.
	 *  @param name The callers name (used to find the certificate and public key). 
	 */
	public IFuture verifyCall(final byte[] content, final byte[] signed, final String name)
	{
		final Future ret = new Future();
		
		getCertificate(name).addResultListener(new ExceptionDelegationResultListener(ret)
		{
			public void customResultAvailable(Certificate cert)
			{
				try
				{
					if(verifyCall(content, signed, cert))
					{
						ret.setResult(null);
					}
					else
					{
						ret.setException(new SecurityException("Authentication exception."));
					}
				}
				catch(Exception e)
				{
					e.printStackTrace();
					ret.setException(e);
				}
			}
		});
		
		return ret;
	}
	
	/**
	 *  Get the certificate of a platform.
	 *  @param cid The platform component identifier (null for own certificate).
	 *  @return The certificate.
	 */
	public IFuture getPlatformCertificate(IComponentIdentifier cid)
	{
		return getCertificate(cid==null? null: cid.getPlatformPrefix());
	}
	
	/**
	 *  Get info about the current keystore that is used.
	 */
	public IFuture> getKeystoreDetails()
	{
		Future> ret = new Future>();
		Map res = new HashMap();
		
		try
		{
			KeyStore ks = getKeyStore();
			Enumeration en = ks.aliases();
			while(en.hasMoreElements())
			{
				String alias = en.nextElement();
			
				KeyStoreEntry kse = new KeyStoreEntry();
				
				try
				{
					ks.getEntry(alias, null);
				}
				catch(Exception e)
				{
					kse.setProtected(true);
				}
				
				if(ks.isCertificateEntry(alias))
				{
					kse.setType(ISecurityService.CERTIFICATE);
				}
				else if(!ks.isKeyEntry(alias) && ks.getCertificateChain(alias)!=null 
					&& ks.getCertificateChain(alias).length!=0)
				{
					kse.setType(ISecurityService.TRUSTED_CERTIFICATE);
				}
				else 
				{
					kse.setType(ISecurityService.KEYPAIR);
				}
				
				Certificate cert = ks.getCertificate(alias);
				if(cert instanceof X509Certificate)
				{
					X509Certificate xcert = (X509Certificate)cert;
//					kse.setAlgorithm(xcert.getSigAlgName());
					Date from = xcert.getNotBefore();
					kse.setFrom(from.getTime());
					Date to = xcert.getNotAfter();
					kse.setTo(to.getTime());
				}
				
				if(cert!=null)
				{
					kse.setAlgorithm(cert.getPublicKey().getAlgorithm()+" "+SSecurity.getKeyLength(cert.getPublicKey()));
					Certificate[] chain = ks.getCertificateChain(alias);
					kse.setCertificates(chain!=null? chain: new Certificate[]{cert});
				}
				
				kse.setAlias(alias);
				kse.setDate(ks.getCreationDate(alias).getTime());
				res.put(alias, kse);
				
			}
		}
		catch(Exception e)
		{
			ret.setException(e);
		}
		
		ret.setResult(res);
		
		return ret;
	}
	
	/**
	 *  Remove a key store entry.
	 *  @param String alias The alias name.
	 */
	public IFuture removeKeyStoreEntry(String alias)
	{
		try
		{
			KeyStore ks = getKeyStore();
			ks.deleteEntry(alias);
			publishEvent(new ChangeEvent(null, PROPERTY_KEYSTOREENTRIES, null));
			saveKeyStore();
			return IFuture.DONE;
		}
		catch(Exception e)
		{
			return new Future(e);
		}
	}
	
	/**
	 *  Internal verify method that just checks if f-pubkey(content)=signed.
	 */
	public boolean verifyCall(byte[] content, byte[] signed, Certificate cert)
	{
		boolean ret = false;
		
		try
		{
			Signature eng = Signature.getInstance(SSecurity.getAlgorithm(cert));
			ret = SSecurity.verifyContent(cert.getPublicKey(), eng, content, signed);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		return ret;
	}
	
	/**
	 *  Add a trusted certificate of a platform.
	 *  @param name The entry name.
	 *  @param cert The certificate.
	 */
	public IFuture addPlatformCertificate(IComponentIdentifier cid, Certificate cert)
	{
		try
		{
			KeyStore ks = getKeyStore();
			ks.setCertificateEntry(cid.getPlatformPrefix(), cert);
			publishEvent(new ChangeEvent(null, PROPERTY_KEYSTOREENTRIES, null));
//			publishEvent(new ChangeEvent(null, PROPERTY_KEYSTOREENTRIES, getKeystoreDetails()));
			saveKeyStore();
			return IFuture.DONE;
		}
		catch(Exception e)
		{
			return new Future(e);
		}
	}
	
	/**
	 *  Get a certificate with an alias name.
	 *  Uses a mechanism to acquire a certificate.
	 *  @param name The alias name.
	 *  @return The certificate.
	 */
	protected IFuture getCertificate(final String name)
	{
		final Future ret = new Future();
			
		try
		{
			Certificate cert = null;
			
			// null can be used for own platform name
			String prefix = component.getComponentIdentifier().getPlatformPrefix();
			if(name==null || prefix.equals(name))
			{
				cert = getKeyStore().getCertificate(prefix);
				ret.setResult(cert); // should never be null
			}
			else
			{
				cert = getKeyStore().getCertificate(name);
				if(cert!=null)
				{
					ret.setResult(cert);
				}
				else
				{
					acquireCertificate(name).addResultListener(new DelegationResultListener(ret)
					{
						public void customResultAvailable(Certificate cert)
						{
							try
							{
								// Store certificate in store
								getKeyStore().setCertificateEntry(name, cert);
								publishEvent(new ChangeEvent(null, PROPERTY_KEYSTOREENTRIES, null));
								saveKeyStore();
								super.customResultAvailable(cert);
							}
							catch(Exception e)
							{
								super.exceptionOccurred(e);
							}
						}
					});
				}
			}
		}
		catch(Exception e)
		{
			ret.setException(e);
		}
		
		return ret;
	}
	
	/**
	 *  Create a key pair entry (with associated certificate).
	 *  @param cid The entry name.
	 *  @param algorithm The algorithm.
	 *  @param keysize The key size (in bits).
	 */
	public IFuture createKeyPair(IComponentIdentifier cid, String algorithm, int keysize, String pass, int validity)
	{
		try
		{
			KeyPair keys = SSecurity.generateKeyPair(algorithm, keysize);
			Certificate cert = SSecurity.generateCertificate(keys, validity, "MD5With"+algorithm);
			getKeyStore().setKeyEntry(cid.getPlatformPrefix(), keys.getPrivate(), pass==null? null: pass.toCharArray(), new Certificate[]{cert});
			publishEvent(new ChangeEvent(null, PROPERTY_KEYSTOREENTRIES, null));
			saveKeyStore();
			return IFuture.DONE;
		}
		catch(Exception e)
		{
			return new Future(e);
		}
	}

	/**
	 *  Delegates an acquire certificate call to the selected
	 *  (or no) mechanism.
	 */
	protected IFuture acquireCertificate(final String name)
	{
		if(selmech>-1)
		{
			return mechanisms.get(selmech).acquireCertificate(name);
		}
		else
		{
			return new Future(new SecurityException("No certificate and aquiring disabled."));
		}
	}
	
	
	
	/**
	 *  Get the component.
	 *  @return The component.
	 */
	public IInternalAccess getComponent()
	{
		return component;
	}
	
	/**
	 *  Get the service identifier.
	 *  @return The service identifier.
	 */
	public IServiceIdentifier getServiceIdentifier()
	{
		return sid;
	}
	
	/**
	 *  Set a mechanism parameter.
	 */
	public IFuture setAcquisitionMechanismParameterValue(Class type, String name, Object value)
	{
		Future ret = new Future();
		AAcquisitionMechanism mech = getMechanism(type);
		if(mech!=null)
		{
			mech.setParameterValue(name, value);
			ret.setResult(null);
		}
		else
		{
			ret.setException(new RuntimeException("Mechanism not found: "+type));
		}
		return ret;
	}
	
	/**
	 *  Get the supported certificate acquire mechanism infos.
	 */
	public IFuture> getAcquisitionMechanisms()
	{
		List ret = new ArrayList();
		
		for(AAcquisitionMechanism mech: mechanisms)
		{
			ret.add(mech.getMechanismInfo());
		}
		
		return new Future>(ret);
	}
	
	/**
	 *  Get the active acquisition mechanism.
	 */
	public IFuture getSelectedAcquisitionMechanism()
	{
		return new Future(selmech);
	}
	
	/**
	 *  Set the acquire mechanism.
	 */
	public IFuture setAcquisitionMechanism(Class type)
	{
		int oldselmech = selmech;
		
		if(type==null)
		{
			selmech = -1;
		}
		else
		{
			for(int i=0; i(null, PROPERTY_SELECTEDMECHANISM, Integer.valueOf(selmech)));
		
		return IFuture.DONE;
	}
	
	/**
	 *  Subscribe to changes.
	 */
	public ISubscriptionIntermediateFuture> subscribeToEvents()
	{
//		final SubscriptionIntermediateFuture> ret = new SubscriptionIntermediateFuture>();
		final SubscriptionIntermediateFuture> ret	= (SubscriptionIntermediateFuture>)SFuture.getNoTimeoutFuture(SubscriptionIntermediateFuture.class, component);

		ret.setTerminationCommand(new TerminationCommand()
		{
			public void terminated(Exception reason)
			{
				subscribers.remove(ret);
			}
		});
		subscribers.add(ret);

		// signal with null subscription done
		ret.addIntermediateResultIfUndone(null);
		
		// Signal current state
		ret.addIntermediateResultIfUndone(new ChangeEvent(null, PROPERTY_KEYSTORESETTINGS, new String[]{storepath, storepass, keypass}));
		ret.addIntermediateResultIfUndone(new ChangeEvent(null, PROPERTY_KEYSTOREENTRIES, null));
		ret.addIntermediateResultIfUndone(new ChangeEvent(null, PROPERTY_PLATFORMPASS, platformpasses));
		ret.addIntermediateResultIfUndone(new ChangeEvent(null, PROPERTY_NETWORKPASS, networkpasses));
		ret.addIntermediateResultIfUndone(new ChangeEvent(null, PROPERTY_LOCALPASS, password));
		ret.addIntermediateResultIfUndone(new ChangeEvent(null, PROPERTY_USEPASS, usepass? Boolean.TRUE: Boolean.FALSE));
		ret.addIntermediateResultIfUndone(new ChangeEvent(null, PROPERTY_TRUSTEDLAN, trustedlan? Boolean.TRUE: Boolean.FALSE));
		ret.addIntermediateResultIfUndone(new ChangeEvent(null, PROPERTY_SELECTEDMECHANISM, selmech));
		ret.addIntermediateResultIfUndone(new ChangeEvent(null, PROPERTY_VALIDITYDURATION, Long.valueOf(valdur)));
		ret.addIntermediateResultIfUndone(new ChangeEvent(null, PROPERTY_VIRTUALS, virtualsmap));

//		System.out.println("ret fut is: "+ret+" "+ret.hashCode());
		
		return ret;
	}
	
	/**
	 *  Get the mechanism for a type.
	 *  @param type The type.
	 *  @return The mechanism.
	 */
	protected AAcquisitionMechanism getMechanism(Class type)
	{
		AAcquisitionMechanism ret = null;
		for(AAcquisitionMechanism mech: mechanisms)
		{
			if(mech.getClass().equals(type))
			{
				ret = mech;
				break;
			}
		}
		if(ret==null)
			throw new RuntimeException("Mechanism not found: "+type);
		return ret;
	}
	
	/**
	 *  Publish events to all subscribers.
	 */
	protected void publishEvent(ChangeEvent event)
	{
		for(SubscriptionIntermediateFuture> sub: subscribers)
		{
			sub.addIntermediateResult(event);
		}
	}
	
	/**
	 *  Save the keystore.
	 */
	protected void saveKeyStore()
	{
		if(keystore!=null)
		{
			SSecurity.saveKeystore(keystore, storepath, storepass);
		}
	}
	
	/**
	 *  Publish the current state.
	 */
	protected void publishCurrentState()
	{
		publishEvent(new ChangeEvent(null, PROPERTY_KEYSTORESETTINGS, new String[]{storepath, storepass, keypass}));
		publishEvent(new ChangeEvent(null, PROPERTY_KEYSTOREENTRIES, null));
		publishEvent(new ChangeEvent(null, PROPERTY_PLATFORMPASS, platformpasses));
		publishEvent(new ChangeEvent(null, PROPERTY_NETWORKPASS, networkpasses));
		publishEvent(new ChangeEvent(null, PROPERTY_LOCALPASS, password));
		publishEvent(new ChangeEvent(null, PROPERTY_USEPASS, usepass? Boolean.TRUE: Boolean.FALSE));
		publishEvent(new ChangeEvent(null, PROPERTY_TRUSTEDLAN, trustedlan? Boolean.TRUE: Boolean.FALSE));
		publishEvent(new ChangeEvent(null, PROPERTY_SELECTEDMECHANISM, selmech));
		publishEvent(new ChangeEvent(null, PROPERTY_VALIDITYDURATION, Long.valueOf(valdur)));
		publishEvent(new ChangeEvent(null, PROPERTY_VIRTUALS, virtualsmap));
	}
	
	//-------- static part --------

	/**
	 *  Build the digest given the timestamp and password.
	 */
	public static byte[] buildDigest(long timestamp, String secret)
	{
//		System.out.println("build digest: "+timestamp+" "+secret);
		byte[]	input	= (byte[])SUtil.joinArrays(secret.getBytes(), SUtil.longToBytes(timestamp));
		return buildDigest(input);
	}
	
	/**
	 *  Build the digest given the timestamp and password.
	 */
	public static byte[] buildDigest(byte[] input)
	{
//		System.out.println("build digest: "+timestamp+" "+secret);
		try
		{
			MessageDigest	md	= MessageDigest.getInstance("SHA-384");
			byte[]	output	= md.digest(input);
			return output;
		}
		catch(NoSuchAlgorithmException e)
		{
			// Shouldn't happen?
			throw new RuntimeException(e);
		}
	}
	
	/**
	 *  Main for testing.
	 */
	public static void	main(String args[]) throws Exception
	{
//		long	timestamp	= System.currentTimeMillis();
////		System.out.println("ts1: "+SUtil.arrayToString(SUtil.longToBytes(timestamp)));
//		
//		timestamp = timestamp>>>16<<16; // New digest every minute
//		long ts2 = timestamp>>>16;
//		long ts = timestamp;
//		for(int i=0; i<16; i++)
//		{
//			ts >>>= 1;
//		}
//		
//		// Test performance of algorithms
//		// See http://download.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest
//		String[]	names	= new String[]
//		{
//			"MD2",
//			"MD5",
//			"SHA-1",
//			"SHA-256",
//			"SHA-384",
//			"SHA-512"
//		};
		
//		String	pw	= "platformpass";
//		IComponentIdentifier	cid	= new ComponentIdentifier("platform_xyz", new String[]{"hasfgjdlah", "t4qohnc37rtcb0q479tfb", "3t7qh90c3tq0dch9347qgbz0234", "w34q256vz348956qfhz03489fh6c"});
//		byte[]	cidbytes	= JavaWriter.objectToByteArray(cid, AbstractRemoteCommand.class.getClassLoader());
//		for(int runs=0; runs<3; runs++)
//		{
//			for(int i=0; i