Package org.jboss.security.plugins

Source Code of org.jboss.security.plugins.JaasSecurityDomain

/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.security.plugins;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.security.auth.callback.CallbackHandler;

import org.jboss.crypto.CryptoUtil;
import org.jboss.managed.api.ManagedOperation.Impact;
import org.jboss.managed.api.annotation.ManagementComponent;
import org.jboss.managed.api.annotation.ManagementObject;
import org.jboss.managed.api.annotation.ManagementObjectID;
import org.jboss.managed.api.annotation.ManagementOperation;
import org.jboss.managed.api.annotation.ManagementParameter;
import org.jboss.managed.api.annotation.ManagementProperties;
import org.jboss.managed.api.annotation.ManagementProperty;
import org.jboss.managed.api.annotation.ViewUse;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.security.ISecurityManagement;
import org.jboss.security.SecurityDomain;
import org.jboss.security.Util;
import org.jboss.security.auth.callback.JBossCallbackHandler;
import org.jboss.security.integration.JNDIBasedSecurityManagement;

/**
* The JaasSecurityDomain is an extension of JaasSecurityManager that addes the notion of a KeyStore, and JSSE
* KeyManagerFactory and TrustManagerFactory for supporting SSL and other cryptographic use cases.
*
* Attributes:
* <ul>
* <li>KeyStoreType: The implementation type name being used, defaults to 'JKS'. </li>
*
* <li>KeyStoreURL: Set the KeyStore database URL string. This is used to obtain an InputStream to initialize the
* KeyStore. If the string is not a value URL, its treated as a file. </li>
*
* <li>KeyStorePass: the password used to load the KeyStore. Its format is one of:
* <ul>
* <li>The plaintext password for the KeyStore(or whatever format is used by the KeyStore). The toCharArray() value of
* the string is used without any manipulation. </li>
* <li>A command to execute to obtain the plaintext password. The format is '{EXT}...' where the '...' is the exact
* command line that will be passed to the Runtime.exec(String) method to execute a platform command. The first line of
* the command output is used as the password. </li>
* <li>A class to create to obtain the plaintext password. The format is '{CLASS}classname[:ctorarg]' where the
* '[:ctorarg]' is an optional string delimited by the ':' from the classname that will be passed to the classname ctor.
* The password is obtained from classname by invoking a 'char[] toCharArray()' method if found, otherwise, the 'String
* toString()' method is used. </li>
* </ul>
* The KeyStorePass is also used in combination with the Salt and IterationCount attributes to create a PBE secret key
* used with the encode/decode operations. </li>
*
* <li>ManagerServiceName: The JMX object name string of the security manager service that the domain registers with to
* function as a security manager for the security domain name passed to the ctor. The makes the JaasSecurityDomain
* available under the standard JNDI java:/jaas/(domain) binding. </li>
*
* <li>LoadSunJSSEProvider: A flag indicating if the Sun com.sun.net.ssl.internal.ssl.Provider security provider should
* be loaded on startup. This is needed when using the Sun JSSE jars without them installed as an extension with JDK
* 1.3. This should be set to false with JDK 1.4 or when using an alternate JSSE provider </li>
*
* <li>Salt: </li>
*
* <li>IterationCount: </li>
* </ul>
*
* @todo add support for encode/decode based on a SecretKey in the keystore.
*
* @author Scott.Stark@jboss.org
* @author <a href="mailto:jasone@greenrivercomputing.com">Jason Essington</a>
*
* @version $Revision: 88376 $
*/
@ManagementObject(componentType = @ManagementComponent(type = "MCBean", subtype = "Security"), properties = ManagementProperties.EXPLICIT)
public class JaasSecurityDomain extends JaasSecurityManager implements SecurityDomain, JaasSecurityDomainMBean
{
   /** The permission required to access encode, encode64 */
   private static final RuntimePermission encodePermission = new RuntimePermission(
         "org.jboss.security.plugins.JaasSecurityDomain.encode");

   /** The permission required to access decode, decode64 */
   private static final RuntimePermission decodePermission = new RuntimePermission(
         "org.jboss.security.plugins.JaasSecurityDomain.decode");

   /** The KeyStore associated with the security domain */
   private KeyStore keyStore;

   private KeyManagerFactory keyMgr;

   /** The KeyStore implementation type which defaults to 'JKS' */
   private String keyStoreType = "JKS";

   /** The resource for the keystore location */
   private URL keyStoreURL;

   /** The keystore password for loading */
   private char[] keyStorePassword;
  
   /** The alias of the KeyStore to be used */
   private String keyStoreAlias;
  
   /** The secret key that corresponds to the keystore password */
   private SecretKey cipherKey;

   /** The encode/decode cipher algorigthm */
   private String cipherAlgorithm = "PBEwithMD5andDES";

   private byte[] salt = {1, 2, 3, 4, 5, 6, 7, 8};

   private int iterationCount = 103;

   private PBEParameterSpec cipherSpec;

   /** The JMX object name of the security manager service */
   private ObjectName managerServiceName = JaasSecurityManagerServiceMBean.OBJECT_NAME;

   private KeyStore trustStore;

   private String trustStoreType = "JKS";

   private char[] trustStorePassword;

   private URL trustStoreURL;

   private TrustManagerFactory trustMgr;

   /** Specify the SecurityManagement instance */
   private ISecurityManagement securityManagement = new JNDIBasedSecurityManagement();

   /**
    * Creates a default JaasSecurityDomain for with a securityDomain name of 'other'.
    */
   public JaasSecurityDomain()
   {
      super();
   }

   /**
    * Creates a JaasSecurityDomain for with a securityDomain name of that given by the 'securityDomain' argument.
    *
    * @param securityDomain , the name of the security domain
    */
   public JaasSecurityDomain(String securityDomain)
   {
      this(securityDomain, new JBossCallbackHandler());
   }

   /**
    * Creates a JaasSecurityDomain for with a securityDomain name of that given by the 'securityDomain' argument.
    *
    * @param securityDomain , the name of the security domain
    * @param handler , the CallbackHandler to use to obtain login module info
    */
   public JaasSecurityDomain(String securityDomain, CallbackHandler handler)
   {
      super(securityDomain, handler);
   }

   @Override
   @ManagementObjectID(type = "SecurityDomain")
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The security domain name")
   public String getSecurityDomain()
   {
      return super.getSecurityDomain();
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#getKeyStoreType()
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The keystore implementation type - default is JKS")
   public String getKeyStoreType()
   {
      return this.keyStoreType;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#setKeyStoreType(java.lang.String)
    */
   public void setKeyStoreType(String type)
   {
      this.keyStoreType = type;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#getKeyStoreURL()
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The keystore location")
   public String getKeyStoreURL()
   {
      String url = null;
      if (keyStoreURL != null)
         url = keyStoreURL.toExternalForm();
      return url;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#setKeyStoreURL(java.lang.String)
    */
   public void setKeyStoreURL(String storeURL) throws IOException
   {
      this.keyStoreURL = this.validateStoreURL(storeURL);
      log.debug("Using KeyStore=" + keyStoreURL.toExternalForm());
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#setKeyStorePass(java.lang.String)
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The keystore password", mandatory = true)
   public void setKeyStorePass(String password) throws Exception
   {
      this.keyStorePassword = Util.loadPassword(password);
   }
  
   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#getKeyStoreAlias()
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The keystore alias with the certificate to be used")
   public String getKeyStoreAlias()
   {
      return this.keyStoreAlias;
   }
  
   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#setKeyStoreAlias(java.lang.String)
    */
   public void setKeyStoreAlias(String alias)
   {
      this.keyStoreAlias = alias;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#getTrustStoreType()
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The truststore implementation type - default is JKS")
   public String getTrustStoreType()
   {
      return this.trustStoreType;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#setTrustStoreType(java.lang.String)
    */
   public void setTrustStoreType(String type)
   {
      this.trustStoreType = type;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#getTrustStoreURL()
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The truststore location")
   public String getTrustStoreURL()
   {
      String url = null;
      if (trustStoreURL != null)
         url = trustStoreURL.toExternalForm();
      return url;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#setTrustStoreURL(java.lang.String)
    */
   public void setTrustStoreURL(String storeURL) throws IOException
   {
      this.trustStoreURL = validateStoreURL(storeURL);
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#setTrustStorePass(java.lang.String)
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The truststore password")
   public void setTrustStorePass(String password) throws Exception
   {
      this.trustStorePassword = Util.loadPassword(password);
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#setSalt(java.lang.String)
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The salt for password-based encryption (PBE)")
   public void setSalt(String salt)
   {
      this.salt = salt.getBytes();
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#setIterationCount(int)
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The iteration count for password-based encryption (PBE)")
   public void setIterationCount(int iterationCount)
   {
      this.iterationCount = iterationCount;
   }

   /**
    * <p>
    * Obtains the cypher algorithm used in then encode and decode operations.
    * </p>
    *
    * @return a {@code String} representing the name of the cipher algorithm.
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The cipher algorithm used in the encode/decode operations - default is PBEwithMD5andDES")
   public String getCipherAlgorithm()
   {
      return cipherAlgorithm;
   }

   /**
    * <p>
    * Sets the cipher algorithm to be used in the encode and decode operations.
    * </p>
    *
    * @param cipherAlgorithm a {@code String} representing the name of the cipher algorithm.
    */
   public void setCipherAlgorithm(String cipherAlgorithm)
   {
      this.cipherAlgorithm = cipherAlgorithm;
   }

   /**
    * The JMX object name string of the security manager service.
    *
    * @return The JMX object name string of the security manager service.
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The object name of the security manager service")
   public ObjectName getManagerServiceName()
   {
      return this.managerServiceName;
   }

   /**
    * Set the JMX object name string of the security manager service.
    */
   public void setManagerServiceName(ObjectName managerServiceName)
   {
      this.managerServiceName = managerServiceName;
   }

   /**
    * <p>
    * Obtains a reference to the {@code ISecurityManagement} implementation that registered this domain.
    * </p>
    *
    * @return a reference to the {@code ISecurityManagement} bean.
    */
   @ManagementProperty(use = {ViewUse.CONFIGURATION}, description = "The security manager service bean where this domain is registered")
   public ISecurityManagement getSecurityManagement()
   {
      return securityManagement;
   }

   /**
    * <p>
    * Sets the {@code ISecurityManagement} implementation that must be used to register this domain.
    * </p>
    *
    * @param securityManagement a reference to the {@code ISecurityManagement} be to be used.
    */
   public void setSecurityManagement(ISecurityManagement securityManagement)
   {
      this.securityManagement = securityManagement;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.system.ServiceMBeanSupport#getName()
    */
   @Override
   public String getName()
   {
      return "JaasSecurityDomain(" + getSecurityDomain() + ")";
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.SecurityDomain#getKeyStore()
    */
   @ManagementOperation(description = "Get the KeyStore constructed by this domain", impact = Impact.ReadOnly)
   public KeyStore getKeyStore() throws SecurityException
   {
      return keyStore;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.SecurityDomain#getKeyManagerFactory()
    */
   @ManagementOperation(description = "Get the KeyManagerFactory constructed by this domain", impact = Impact.ReadOnly)
   public KeyManagerFactory getKeyManagerFactory() throws SecurityException
   {
      return keyMgr;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.SecurityDomain#getTrustStore()
    */
   @ManagementOperation(description = "Get the TrustStore constructed by this domain", impact = Impact.ReadOnly)
   public KeyStore getTrustStore() throws SecurityException
   {
      return trustStore;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.SecurityDomain#getTrustManagerFactory()
    */
   @ManagementOperation(description = "Get the TrustManagerFactory constructed by this domain", impact = Impact.ReadOnly)
   public TrustManagerFactory getTrustManagerFactory() throws SecurityException
   {
      return trustMgr;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#encode(byte[])
    */
   @ManagementOperation(description = "Encode a secret using the cipher algorithm and the KeyStore password",
         params = {@ManagementParameter(name = "secret", description = "The secret to be encoded")},
         impact = Impact.ReadOnly)
   public byte[] encode(byte[] secret) throws Exception
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
      {
         if(log.isTraceEnabled())
            log.trace("Checking: " + encodePermission);
         sm.checkPermission(encodePermission);
      }

      Cipher cipher = Cipher.getInstance(cipherAlgorithm);
      cipher.init(Cipher.ENCRYPT_MODE, cipherKey, cipherSpec);
      byte[] encoding = cipher.doFinal(secret);
      return encoding;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#decode(byte[])
    */
   @ManagementOperation(description = "Decode a secret using the cipher algorithm and the KeyStore password",
         params = {@ManagementParameter(name = "secret", description = "The secret to be encoded")},
         impact = Impact.ReadOnly)
   public byte[] decode(byte[] secret) throws Exception
   {
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
         sm.checkPermission(decodePermission);

      Cipher cipher = Cipher.getInstance(cipherAlgorithm);
      cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherSpec);
      byte[] decode = cipher.doFinal(secret);
      return decode;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#encode64(byte[])
    */
   @ManagementOperation(description = "Encode a secret as a base64 string using the cipher algorithm and the KeyStore password",
         params = {@ManagementParameter(name = "secret", description = "The secret to be encoded")},
         impact = Impact.ReadOnly)
   public String encode64(byte[] secret) throws Exception
   {
      byte[] encoding = encode(secret);
      String b64 = CryptoUtil.tob64(encoding);
      return b64;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#decode64(java.lang.String)
    */
   @ManagementOperation(description = "Decode a base64 secret using the cipher algorithm and the KeyStore password",
         params = {@ManagementParameter(name = "secret", description = "The secret to be encoded")},
         impact = Impact.ReadOnly)
   public byte[] decode64(String secret) throws Exception
   {
      byte[] encoding = CryptoUtil.fromb64(secret);
      byte[] decode = decode(encoding);
      return decode;
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.security.plugins.JaasSecurityDomainMBean#reloadKeyAndTrustStore()
    */
   @ManagementOperation(description = "Reload the key and trust stores", impact = Impact.WriteOnly)
   public void reloadKeyAndTrustStore() throws Exception
   {
      loadKeyAndTrustStore();
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.system.ServiceMBeanSupport#startService()
    */
   @Override
   @ManagementOperation(description = "Service lifecycle operation", impact = Impact.WriteOnly)
   protected void startService() throws Exception
   {
      // Load the secret key
      loadPBESecretKey();

      // Load the key and/or truststore into memory
      loadKeyAndTrustStore();

      // Only register with the JaasSecurityManagerService if its defined
      if (managerServiceName != null)
      {
         /*
          * Register with the JaasSecurityManagerServiceMBean. This allows this JaasSecurityDomain to function as the
          * security manager for security-domain elements that declare java:/jaas/xxx for our security domain name.
          */
         MBeanServer server = MBeanServerLocator.locateJBoss();
         Object[] params = {getSecurityDomain(), this};
         String[] signature = new String[]{"java.lang.String", "org.jboss.security.SecurityDomain"};
         server.invoke(managerServiceName, "registerSecurityDomain", params, signature);
      }
      // Register yourself with the security management
      if (securityManagement instanceof JNDIBasedSecurityManagement)
      {
         JNDIBasedSecurityManagement jbs = (JNDIBasedSecurityManagement) securityManagement;
         jbs.registerJaasSecurityDomainInstance(this);
      }
   }

   /*
    * (non-Javadoc)
    *
    * @see org.jboss.system.ServiceMBeanSupport#stopService()
    */
   @Override
   @ManagementOperation(description = "Service lifecycle operation", impact = Impact.WriteOnly)
   protected void stopService()
   {
      if (keyStorePassword != null)
      {
         Arrays.fill(keyStorePassword, '\0');
         keyStorePassword = null;
      }
      cipherKey = null;

      // Deregister yourself with the security management
      if (securityManagement instanceof JNDIBasedSecurityManagement)
      {
         JNDIBasedSecurityManagement jbs = (JNDIBasedSecurityManagement) securityManagement;
         jbs.deregisterJaasSecurityDomainInstance(getSecurityDomain());
      }
   }

   /**
    * <p>
    * Loads the PBE secret key.
    * </p>
    *
    * @throws Exception if an error ocurrs when loading the PBE key.
    */
   private void loadPBESecretKey() throws Exception
   {
      // Create the PBE secret key
      cipherSpec = new PBEParameterSpec(salt, iterationCount);
      PBEKeySpec keySpec = new PBEKeySpec(keyStorePassword);
      SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEwithMD5andDES");
      cipherKey = factory.generateSecret(keySpec);
   }

   private void loadKeyAndTrustStore() throws Exception
   {
      if (keyStorePassword != null)
      {
         keyStore = KeyStore.getInstance(keyStoreType);
         InputStream is = null;
         if (!"PKCS11".equalsIgnoreCase(keyStoreType) && keyStoreURL != null)
         {
            is = keyStoreURL.openStream();
         }
         keyStore.load(is, keyStorePassword);
         if (keyStoreAlias != null && !keyStore.isKeyEntry(keyStoreAlias))
         {
            throw new IOException("Cannot find key entry with alias " + keyStoreAlias + " in the keyStore");
         }
         String algorithm = KeyManagerFactory.getDefaultAlgorithm();
         keyMgr = KeyManagerFactory.getInstance(algorithm);
         keyMgr.init(keyStore, keyStorePassword);
         if (keyStoreAlias != null)
         {
            KeyManager[] keyManagers = keyMgr.getKeyManagers();
            for (int i = 0; i < keyManagers.length; i++)
            {
               keyManagers[i] = new SecurityKeyManager((X509KeyManager) keyManagers[i], keyStoreAlias);
            }
         }
      }
      if (trustStorePassword != null)
      {
         trustStore = KeyStore.getInstance(trustStoreType);
         InputStream is = null;
         if (!"PKCS11".equalsIgnoreCase(trustStoreType) && trustStoreURL != null)
         {
            is = trustStoreURL.openStream();
         }
         trustStore.load(is, trustStorePassword);
         String algorithm = TrustManagerFactory.getDefaultAlgorithm();
         trustMgr = TrustManagerFactory.getInstance(algorithm);
         trustMgr.init(trustStore);
      }
      else if (keyStore != null)
      {
         trustStore = keyStore;
         String algorithm = TrustManagerFactory.getDefaultAlgorithm();
         trustMgr = TrustManagerFactory.getInstance(algorithm);
         trustMgr.init(trustStore);
      }
   }

   private URL validateStoreURL(String storeURL) throws IOException
   {
      URL url = null;
      // First see if this is a URL
      try
      {
         url = new URL(storeURL);
      }
      catch (MalformedURLException e)
      {
         // Not a URL or a protocol without a handler
      }

      // Next try to locate this as file path
      if (url == null)
      {
         File tst = new File(storeURL);
         if (tst.exists() == true)
            url = tst.toURL();
      }

      // Last try to locate this as a classpath resource
      if (url == null)
      {
         ClassLoader loader = SubjectActions.getContextClassLoader();
         url = loader.getResource(storeURL);
      }

      // Fail if no valid key store was located
      if (url == null)
      {
         String msg = "Failed to find url=" + storeURL + " as a URL, file or resource";
         throw new MalformedURLException(msg);
      }
      return url;
   }
}
TOP

Related Classes of org.jboss.security.plugins.JaasSecurityDomain

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