[ http://issues.apache.org/jira/browse/WSS-59?page=all ]

Ruchith Udayanga Fernando updated WSS-59:
-----------------------------------------

    Priority: Minor  (was: Blocker)

Soumadeep ... shall we leave Merlin as it is, since one can implement the 
Crypto interface to secure the keystore password in any desired way.

IMHO this is not a blocker. Hence reducing priority.

Thanks,
Ruchith

> Crypto.properties doesn't allow encoded password for keystore - WSS4J1.5
> ------------------------------------------------------------------------
>
>                 Key: WSS-59
>                 URL: http://issues.apache.org/jira/browse/WSS-59
>             Project: WSS4J
>          Issue Type: New Feature
>         Environment: ALL
>            Reporter: Soumadeep
>         Assigned To: Davanum Srinivas
>            Priority: Minor
>
> Crypto.properties file doesn't allow encoded passwords for keystore and 
> alias. The plain text password would be unacceptable for most of the 
> enterprises.
> The load method of AbstractCrypto.java file actually picks up the passwords 
> from the crypto.properties file. I have made the required changes and am 
> putting it below. This only accounts for the keystore password as the alias 
> password would be usage specific. Please check the two files 
> crypto.properties and AbstractCrypto.java. The files are from the wss4j main 
> trunk - svn
> Thanks
> Soumadeep 
> Crypto.properties
> =============
> org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
> org.apache.ws.security.crypto.merlin.keystore.type=jks
> // This property has been added
> org.apache.ws.security.crypto.merlin.keystore.base64.encoded=false
> org.apache.ws.security.crypto.merlin.keystore.password=soumadeep
> org.apache.ws.security.crypto.merlin.keystore.alias=soumadeep
> org.apache.ws.security.crypto.merlin.alias.password=soumadeep
> org.apache.ws.security.crypto.merlin.file=resources/soumadeep.jks
> AbstractCrypto.java
> ===============
> /*
>  * Copyright  2003-2004 The Apache Software Foundation.
>  *
>  *  Licensed under the Apache License, Version 2.0 (the "License");
>  *  you may not use this file except in compliance with the License.
>  *  You may obtain a copy of the License at
>  *
>  *      http://www.apache.org/licenses/LICENSE-2.0
>  *
>  *  Unless required by applicable law or agreed to in writing, software
>  *  distributed under the License is distributed on an "AS IS" BASIS,
>  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
>  *  See the License for the specific language governing permissions and
>  *  limitations under the License.
>  *
>  */
> package org.apache.ws.security.components.crypto;
> import org.apache.commons.logging.Log;
> import org.apache.commons.logging.LogFactory;
> import org.apache.ws.security.WSSecurityException;
> import org.apache.ws.security.util.Base64;
> import org.apache.ws.security.util.Loader;
> import java.io.FileInputStream;
> import java.io.IOException;
> import java.io.InputStream;
> import java.math.BigInteger;
> import java.security.GeneralSecurityException;
> import java.security.Key;
> import java.security.KeyStore;
> import java.security.KeyStoreException;
> import java.security.MessageDigest;
> import java.security.NoSuchAlgorithmException;
> import java.security.NoSuchProviderException;
> import java.security.PrivateKey;
> import java.security.PublicKey;
> import java.security.cert.Certificate;
> import java.security.cert.CertificateEncodingException;
> import java.security.cert.CertificateException;
> import java.security.cert.CertificateFactory;
> import java.security.cert.X509Certificate;
> import java.security.interfaces.RSAPublicKey;
> import java.util.Arrays;
> import java.util.Enumeration;
> import java.util.Properties;
> import java.util.Vector;
> /**
>  * Created by IntelliJ IDEA.
>  * User: dims
>  * Date: Sep 15, 2005
>  * Time: 9:50:40 AM
>  * To change this template use File | Settings | File Templates.
>  */
> public abstract class AbstractCrypto implements Crypto {
>     private static Log log = LogFactory.getLog(AbstractCrypto.class);
>     protected static CertificateFactory certFact;
>     protected Properties properties = null;
>     protected KeyStore keystore = null;
>     static String SKI_OID = "2.5.29.14";
>     /**
>      * Constructor
>      *
>      * @param properties
>      */
>     public AbstractCrypto(Properties properties) throws CredentialException, 
> IOException {
>       this(properties,AbstractCrypto.class.getClassLoader());
>     }
>     /**
>      * This allows providing a custom class loader to load the resources, etc
>      * @param properties
>      * @param loader
>      * @throws CredentialException
>      * @throws IOException
>      */
>     public AbstractCrypto(Properties properties, ClassLoader loader) throws 
> CredentialException, IOException {
>         /*
>         * if no properties .. just return an instance, the rest will be
>         * done later or this instance is just used to handle certificate
>         * conversions in this implementatio
>         */
>         if (properties == null) {
>             return;
>         }
>         this.properties = properties;
>         String location = 
> this.properties.getProperty("org.apache.ws.security.crypto.merlin.file");
>               InputStream is = null;
>               java.net.URL url = Loader.getResource(loader, location);
>               if(url != null) {
>                       is =  url.openStream();
>               } else {
>                       is = new java.io.FileInputStream(location);
>               }
>         /**
>          * If we don't find it, then look on the file system.
>          */
>         if (is == null) {
>             try {
>                 is = new FileInputStream(location);
>             } catch (Exception e) {
>                 throw new CredentialException(3, "proxyNotFound", new 
> Object[]{location});
>             }
>         }
>         /**
>          * Load the keystore
>          */
>         try {
>             load(is);
>         } finally {
>             is.close();
>         }
>     }
>     
>     /**
>      * Singleton certificate factory for this Crypto instance.
>      * <p/>
>      *
>      * @return Returns a <code>CertificateFactory</code> to construct
>      *         X509 certficates
>      * @throws org.apache.ws.security.WSSecurityException
>      *
>      */
>     public synchronized CertificateFactory getCertificateFactory() throws 
> WSSecurityException {
>         if (certFact == null) {
>             try {
>                 String provider = 
> properties.getProperty("org.apache.ws.security.crypto.merlin.cert.provider");
>                 if (provider == null || provider.length() == 0) {
>                     certFact = CertificateFactory.getInstance("X.509");
>                 } else {
>                     certFact = CertificateFactory.getInstance("X.509", 
> provider);
>                 }
>             } catch (CertificateException e) {
>                 throw new 
> WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
>                         "unsupportedCertType");
>             } catch (NoSuchProviderException e) {
>                 throw new 
> WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
>                         "noSecProvider");
>             }
>         }
>         return certFact;
>     }
>     /**
>      * load a X509Certificate from the input stream.
>      * <p/>
>      *
>      * @param in The <code>InputStream</code> array containg the X509 data
>      * @return Returns a X509 certificate
>      * @throws org.apache.ws.security.WSSecurityException
>      *
>      */
>     public X509Certificate loadCertificate(InputStream in) throws 
> WSSecurityException {
>         X509Certificate cert = null;
>         try {
>             cert =
>                     (X509Certificate) 
> getCertificateFactory().generateCertificate(in);
>         } catch (CertificateException e) {
>             throw new 
> WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
>                     "parseError");
>         }
>         return cert;
>     }
>     /**
>      * Gets the private key identified by <code>alias</> and 
> <code>password</code>.
>      * <p/>
>      *
>      * @param alias    The alias (<code>KeyStore</code>) of the key owner
>      * @param password The password needed to access the private key
>      * @return The private key
>      * @throws Exception
>      */
>     public PrivateKey getPrivateKey(String alias, String password) throws 
> Exception {
>         if (alias == null) {
>             throw new Exception("alias is null");
>         }
>         boolean b = keystore.isKeyEntry(alias);
>         if (!b) {
>             log.error("Cannot find key for alias: " + alias);
>             throw new Exception("Cannot find key for alias: " + alias);
>         }
>         Key keyTmp = keystore.getKey(alias, password.toCharArray());
>         if (!(keyTmp instanceof PrivateKey)) {
>             throw new Exception("Key is not a private key, alias: " + alias);
>         }
>         return (PrivateKey) keyTmp;
>     }
>     private Vector splitAndTrim(String inString) {
>         X509NameTokenizer nmTokens = new X509NameTokenizer(inString);
>         Vector vr = new Vector();
>         while (nmTokens.hasMoreTokens()) {
>             vr.add(nmTokens.nextToken());
>         }
>         java.util.Collections.sort(vr);
>         return vr;
>     }
>     /**
>      * Lookup a X509 Certificate in the keystore according to a given
>      * the issuer of a Certficate.
>      * <p/>
>      * The search gets all alias names of the keystore and gets the 
> certificate chain
>      * for each alias. Then the Issuer fo each certificate of the chain
>      * is compared with the parameters.
>      *
>      * @param issuer The issuer's name for the certificate
>      * @return alias name of the certificate that matches the issuer name
>      *         or null if no such certificate was found.
>      */
>     public String getAliasForX509Cert(String issuer)
>             throws WSSecurityException {
>         return getAliasForX509Cert(issuer, null, false);
>     }
>     /**
>      * Lookup a X509 Certificate in the keystore according to a given serial 
> number and
>      * the issuer of a Certficate.
>      * <p/>
>      * The search gets all alias names of the keystore and gets the 
> certificate chain
>      * for each alias. Then the SerialNumber and Issuer fo each certificate 
> of the chain
>      * is compared with the parameters.
>      *
>      * @param issuer       The issuer's name for the certificate
>      * @param serialNumber The serial number of the certificate from the 
> named issuer
>      * @return alias name of the certificate that matches serialNumber and 
> issuer name
>      *         or null if no such certificate was found.
>      */
>     public String getAliasForX509Cert(String issuer, BigInteger serialNumber)
>             throws WSSecurityException {
>         return getAliasForX509Cert(issuer, serialNumber, true);
>     }
>     /*
>     * need to check if "getCertificateChain" also finds certificates that are
>     * used for enryption only, i.e. they may not be signed by a CA
>     * Otherwise we must define a restriction how to use certificate:
>     * each certificate must be signed by a CA or is a self signed Certificate
>     * (this should work as well).
>     * --- remains to be tested in several ways --
>     */
>     private String getAliasForX509Cert(String issuer, BigInteger serialNumber,
>                                        boolean useSerialNumber)
>             throws WSSecurityException {
>         Vector issuerRDN = splitAndTrim(issuer);
>         X509Certificate x509cert = null;
>         Vector certRDN = null;
>         Certificate cert = null;
>         try {
>             for (Enumeration e = keystore.aliases(); e.hasMoreElements();) {
>                 String alias = (String) e.nextElement();
>                 Certificate[] certs = keystore.getCertificateChain(alias);
>                 if (certs == null || certs.length == 0) {
>                     // no cert chain, so lets check if getCertificate gives 
> us a  result.
>                     cert = keystore.getCertificate(alias);
>                     if (cert == null) {
>                         return null;
>                     }
>                 } else {
>                     cert = certs[0];
>                 }
>                 if (!(cert instanceof X509Certificate)) {
>                     continue;
>                 }
>                 x509cert = (X509Certificate) cert;
>                 if (!useSerialNumber ||
>                         useSerialNumber && 
> x509cert.getSerialNumber().compareTo(serialNumber) == 0) {
>                     certRDN = splitAndTrim(x509cert.getIssuerDN().getName());
>                     if (certRDN.equals(issuerRDN)) {
>                         return alias;
>                     }
>                 }
>             }
>         } catch (KeyStoreException e) {
>             throw new WSSecurityException(WSSecurityException.FAILURE,
>                     "keystore");
>         }
>         return null;
>     }
>     /**
>      * Lookup a X509 Certificate in the keystore according to a given
>      * SubjectKeyIdentifier.
>      * <p/>
>      * The search gets all alias names of the keystore and gets the 
> certificate chain
>      * or certificate for each alias. Then the SKI for each user certificate
>      * is compared with the SKI parameter.
>      *
>      * @param skiBytes The SKI info bytes
>      * @return alias name of the certificate that matches serialNumber and 
> issuer name
>      *         or null if no such certificate was found.
>      * @throws org.apache.ws.security.WSSecurityException
>      *          if problems during keystore handling or wrong certificate (no 
> SKI data)
>      */
>     public String getAliasForX509Cert(byte[] skiBytes) throws 
> WSSecurityException {
>         Certificate cert = null;
>         boolean found = false;
>         try {
>             for (Enumeration e = keystore.aliases(); e.hasMoreElements();) {
>                 String alias = (String) e.nextElement();
>                 Certificate[] certs = keystore.getCertificateChain(alias);
>                 if (certs == null || certs.length == 0) {
>                     // no cert chain, so lets check if getCertificate gives 
> us a  result.
>                     cert = keystore.getCertificate(alias);
>                     if (cert == null) {
>                         return null;
>                     }
>                 } else {
>                     cert = certs[0];
>                 }
>                 if (!(cert instanceof X509Certificate)) {
>                     continue;
>                 }
>                 byte[] data = getSKIBytesFromCert((X509Certificate) cert);
>                 if (data.length != skiBytes.length) {
>                     continue;
>                 }
>                 if (Arrays.equals(data, skiBytes)) {
>                     return alias;
>                 }
>             }
>         } catch (KeyStoreException e) {
>             throw new WSSecurityException(WSSecurityException.FAILURE,
>                     "keystore");
>         }
>         return null;
>     }
>     /**
>      * Return a X509 Certificate alias in the keystore according to a given 
> Certificate
>      * <p/>
>      *
>      * @param cert The certificate to lookup
>      * @return alias name of the certificate that matches the given 
> certificate
>      *         or null if no such certificate was found.
>      */
> /*
>      * See comment above
>      */
>     public String getAliasForX509Cert(Certificate cert) throws 
> WSSecurityException {
>         try {
>             String alias = keystore.getCertificateAlias(cert);
>             if (alias != null)
>                 return alias;
>             // Use brute force search
>             Enumeration e = keystore.aliases();
>             while (e.hasMoreElements()) {
>                 alias = (String) e.nextElement();
>                 X509Certificate cert2 = (X509Certificate) 
> keystore.getCertificate(alias);
>                 if (cert2.equals(cert)) {
>                     return alias;
>                 }
>             }
>         } catch (KeyStoreException e) {
>             throw new WSSecurityException(WSSecurityException.FAILURE,
>                     "keystore");
>         }
>         return null;
>     }
>     /**
>      * Retrieves the alias name of the default certificate which has been
>      * specified as a property. This should be the certificate that is used 
> for
>      * signature and encryption. This alias corresponds to the certificate 
> that
>      * should be used whenever KeyInfo is not poresent in a signed or
>      * an encrypted message. May return null.
>      *
>      * @return alias name of the default X509 certificate
>      */
>     public String getDefaultX509Alias() {
>         if (properties == null) {
>             return null;
>         }
>         return 
> properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.alias");
>     }
>     /**
>      * Gets the list of certificates for a given alias.
>      * <p/>
>      *
>      * @param alias Lookup certificate chain for this alias
>      * @return Array of X509 certificates for this alias name, or
>      *         null if this alias does not exist in the keystore
>      */
>     public X509Certificate[] getCertificates(String alias) throws 
> WSSecurityException {
>         Certificate[] certs = null;
>         try {
>             certs = keystore.getCertificateChain(alias);
>             if (certs == null || certs.length == 0) {
>                 // no cert chain, so lets check if getCertificate gives us a  
> result.
>                 Certificate cert = keystore.getCertificate(alias);
>                 if (cert == null) {
>                     return null;
>                 }
>                 certs = new Certificate[]{cert};
>             }
>         } catch (KeyStoreException e) {
>             throw new WSSecurityException(WSSecurityException.FAILURE,
>                     "keystore");
>         }
>         X509Certificate[] x509certs = new X509Certificate[certs.length];
>         for (int i = 0; i < certs.length; i++) {
>             x509certs[i] = (X509Certificate) certs[i];
>         }
>         return x509certs;
>     }
>     /**
>      * Lookup a X509 Certificate in the keystore according to a given
>      * Thumbprint.
>      * <p/>
>      * The search gets all alias names of the keystore, then reads the 
> certificate chain
>      * or certificate for each alias. Then the thumbprint for each user 
> certificate
>      * is compared with the thumbprint parameter.
>      *
>      * @param thumb The SHA1 thumbprint info bytes
>      * @return alias name of the certificate that matches the thumbprint
>      *         or null if no such certificate was found.
>      * @throws org.apache.ws.security.WSSecurityException
>      *          if problems during keystore handling or wrong certificate
>      */
>     public String getAliasForX509CertThumb(byte[] thumb) throws 
> WSSecurityException {
>         Certificate cert = null;
>         MessageDigest sha = null;
>         try {
>             sha = MessageDigest.getInstance("SHA-1");
>         } catch (NoSuchAlgorithmException e1) {
>             throw new WSSecurityException(
>                     0,
>                     "noSHA1availabe");
>         }
>         try {
>             for (Enumeration e = keystore.aliases(); e.hasMoreElements();) {
>                 String alias = (String) e.nextElement();
>                 Certificate[] certs = keystore.getCertificateChain(alias);
>                 if (certs == null || certs.length == 0) {
>                     // no cert chain, so lets check if getCertificate gives 
> us a  result.
>                     cert = keystore.getCertificate(alias);
>                     if (cert == null) {
>                         return null;
>                     }
>                 } else {
>                     cert = certs[0];
>                 }
>                 if (!(cert instanceof X509Certificate)) {
>                     continue;
>                 }
>                 sha.reset();
>                 try {
>                     sha.update(cert.getEncoded());
>                 } catch (CertificateEncodingException e1) {
>                     throw new WSSecurityException(
>                             WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
>                             "encodeError");
>                 }
>                 byte[] data = sha.digest();
>                 if (Arrays.equals(data, thumb)) {
>                     return alias;
>                 }
>             }
>         } catch (KeyStoreException e) {
>             throw new WSSecurityException(WSSecurityException.FAILURE,
>                     "keystore");
>         }
>         return null;
>     }
>     /**
>      * A Hook for subclasses to set the keystore without having to
>      * load it from an <code>InputStream</code>.
>      *
>      * @param ks existing keystore
>      */
>     public void setKeyStore(KeyStore ks) {
>         keystore = ks;
>     }
>     /**
>      * Loads the the keystore from an <code>InputStream </code>.
>      * <p/>
>      *
>      * @param input <code>InputStream</code> to read from
>      * @throws CredentialException
>      */
>     public void load(InputStream input) throws CredentialException {
>         if (input == null) {
>             throw new IllegalArgumentException("input stream cannot be null");
>         }
>         try {
>             String provider = 
> properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.provider");
>             if (provider == null || provider.length() == 0) {
>                 keystore = KeyStore.getInstance
>                         
> (properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.type",
>                                 KeyStore.getDefaultType()));
>             } else {
>                 keystore = KeyStore.getInstance
>                         
> (properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.type",
>                                 KeyStore.getDefaultType()), provider);
>             }
>                       String password = null;
>                       String base64Encoded = 
> properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.base64.encoded","false");
>                       password = 
> properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.password",
>                         "security");
>                       if(base64Encoded.equalsIgnoreCase("true")){
>               password = new String(Base64.decode(password));
>             }
>                       
>             keystore.load(input, (password == null || password.length() == 0) 
> ? new char[0] : password.toCharArray());
>         } catch (IOException e) {
>             e.printStackTrace();
>             throw new CredentialException(3, "ioError00", e);
>         } catch (GeneralSecurityException e) {
>             e.printStackTrace();
>             throw new CredentialException(3, "secError00", e);
>         } catch (Exception e) {
>             e.printStackTrace();
>             throw new CredentialException(-1, "error00", e);
>         }
>     }
>     /**
>      * Reads the SubjectKeyIdentifier information from the certificate.
>      * <p/>
>      * If the the certificate does not contain a SKI extension then
>      * try to compute the SKI according to RFC3280 using the
>      * SHA-1 hash value of the public key. The second method described
>      * in RFC3280 is not support. Also only RSA public keys are supported.
>      * If we cannot compute the SKI throw a WSSecurityException.
>      *
>      * @param cert The certificate to read SKI
>      * @return The byte array conating the binary SKI data
>      */
>     public byte[] getSKIBytesFromCert(X509Certificate cert)
>             throws WSSecurityException {
>         /*
>            * Gets the DER-encoded OCTET string for the extension value 
> (extnValue)
>            * identified by the passed-in oid String. The oid string is 
> represented
>            * by a set of positive whole numbers separated by periods.
>            */
>         byte[] derEncodedValue = cert.getExtensionValue(SKI_OID);
>         if (cert.getVersion() < 3 || derEncodedValue == null) {
>             PublicKey key = cert.getPublicKey();
>             if (!(key instanceof RSAPublicKey)) {
>                 throw new WSSecurityException(
>                         1,
>                         "noSKIHandling",
>                         new Object[]{"Support for RSA key only"});
>             }
>             byte[] encoded = key.getEncoded();
>             // remove 22-byte algorithm ID and header
>             byte[] value = new byte[encoded.length - 22];
>             System.arraycopy(encoded, 22, value, 0, value.length);
>             MessageDigest sha;
>             try {
>                 sha = MessageDigest.getInstance("SHA-1");
>             } catch (NoSuchAlgorithmException ex) {
>                 throw new WSSecurityException(
>                         1,
>                         "noSKIHandling",
>                         new Object[]{"Wrong certificate version (<3) and no 
> SHA1 message digest availabe"});
>             }
>             sha.reset();
>             sha.update(value);
>             return sha.digest();
>         }
>         /**
>          * Strip away first four bytes from the DerValue (tag and length of
>          * ExtensionValue OCTET STRING and KeyIdentifier OCTET STRING)
>          */
>         byte abyte0[] = new byte[derEncodedValue.length - 4];
>         System.arraycopy(derEncodedValue, 4, abyte0, 0, abyte0.length);
>         return abyte0;
>     }
>     public KeyStore getKeyStore() {
>         return this.keystore;
>     }
>     /**
>      * Lookup X509 Certificates in the keystore according to a given DN of 
> the subject of the certificate
>      * <p/>
>      * The search gets all alias names of the keystore and gets the 
> certificate (chain)
>      * for each alias. Then the DN of the certificate is compared with the 
> parameters.
>      *
>      * @param subjectDN The DN of subject to look for in the keystore
>      * @return Vector with all alias of certificates with the same DN as 
> given in the parameters
>      * @throws org.apache.ws.security.WSSecurityException
>      *
>      */
>     public String[] getAliasesForDN(String subjectDN) throws 
> WSSecurityException {
>         // Store the aliases found
>         Vector aliases = new Vector();
>         Certificate cert = null;
>         // The DN to search the keystore for
>         Vector subjectRDN = splitAndTrim(subjectDN);
>         // Look at every certificate in the keystore
>         try {
>             for (Enumeration e = keystore.aliases(); e.hasMoreElements();) {
>                 String alias = (String) e.nextElement();
>                 Certificate[] certs = keystore.getCertificateChain(alias);
>                 if (certs == null || certs.length == 0) {
>                     // no cert chain, so lets check if getCertificate gives 
> us a  result.
>                     cert = keystore.getCertificate(alias);
>                     if (cert == null) {
>                         return null;
>                     }
>                     certs = new Certificate[]{cert};
>                 } else {
>                     cert = certs[0];
>                 }
>                 if (cert instanceof X509Certificate) {
>                     Vector foundRDN = splitAndTrim(((X509Certificate) 
> cert).getSubjectDN().getName());
>                     if (subjectRDN.equals(foundRDN)) {
>                         aliases.add(alias);
>                     }
>                 }
>             }
>         } catch (KeyStoreException e) {
>             throw new WSSecurityException(WSSecurityException.FAILURE,
>                     "keystore");
>         }
>         // Convert the vector into an array
>         String[] result = new String[aliases.size()];
>         for (int i = 0; i < aliases.size(); i++)
>             result[i] = (String) aliases.elementAt(i);
>         return result;
>     }
> }

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: 
http://issues.apache.org/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to