No more 'unable to find valid certification path to requested target'

Leeland's picture

I love google... I have been having an SSL issue on and off for years, especially in test environments. Usually I fire up Mozilla grab the cert from its cache and edit a few files.

Well that was fine until I started mucking with LDAP. Here I had to go to the server and get the cert myself. Edit the same set of files and once again I am off and running.

But, not today. No today I had to trouble shoot a secure LDAP issue to a server I am NOT allowed to get onto except through LDAP. I can't get secure LDAP to be happy without a way to snag that danged certificate. Google save me!

Ala-ka-sesame!! An article written no less then a month ago. The universe be praised. Thank you so much Andreas Sterbenz for the article "No more 'unable to find valid certification path to requested target'" which due to the internet frequency of "loss of valuable data I use to link to" I will now repeat here and I quote:


Posted at 22:28 Oct 09, 2006 by Andreas Sterbenz

No more 'unable to find valid certification path to requested target'

Some of you may be familiar with the (not very user friendly) exception message
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

when trying to open an SSL connection to a host using JSSE. What this usually means is that the server is using a test certificate (possibly generated using keytool) rather than a certificate from a well known commercial Certification Authority such as Verisign or GoDaddy. Web browsers display warning dialogs in this case, but since JSSE cannot assume an interactive user is present it just throws an exception by default.

Certificate validation is a very important part of SSL security, but I am not writing this entry to explain the details. If you are interested, you can start by reading the Wikipedia blurb. I am writing this entry to show a simple way to talk to that host with the test certificate, if you really want to.

Basically, you want to add the server's certificate to the KeyStore with your trusted certificates. There are any number of ways to achieve that, but a simple solution is to compile and run the attached program as java InstallCert hostname, for example:

% java InstallCert ecc.fedora.redhat.com
Loading KeyStore
 /usr/jdk/instances/jdk1.5.0/jre/lib/security/cacerts...
Opening connection to ecc.fedora.redhat.com:443...
Starting SSL handshake...

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException:
  PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
  unable to find valid certification path to requested target
  at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:150)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1476)
  at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:174)
  at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:168)
  at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:846)
  at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:106)
  at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:495)
  at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:433)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:815)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1025)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1038)
  at InstallCert.main(InstallCert.java:63)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed:
  sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid
  certification path to requested target
  at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:221)
  at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:145)
  at sun.security.validator.Validator.validate(Validator.java:203)
  at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:172)
  at InstallCert$SavingTrustManager.checkServerTrusted(InstallCert.java:158)
  at com.sun.net.ssl.internal.ssl.JsseX509TrustManager.checkServerTrusted(SSLContextImpl.java:320)
  at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:839)
  ... 7 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid
  certification path to requested target
  at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:236)
  at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:194)
  at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:216)
  ... 13 more

Server sent 2 certificate(s):

 1 Subject CN=ecc.fedora.redhat.com, O=example.com, C=US
   Issuer  CN=Certificate Shack, O=example.com, C=US
   sha1    2e 7f 76 9b 52 91 09 2e 5d 8f 6b 61 39 2d 5e 06 e4 d8 e9 c7
   md5     dd d1 a8 03 d7 6c 4b 11 a7 3d 74 28 89 d0 67 54

 2 Subject CN=Certificate Shack, O=example.com, C=US
   Issuer  CN=Certificate Shack, O=example.com, C=US
   sha1    fb 58 a7 03 c4 4e 3b 0e e3 2c 40 2f 87 64 13 4d df e1 a1 a6
   md5     72 a0 95 43 7e 41 88 18 ae 2f 6d 98 01 2c 89 68

Enter certificate to add to trusted keystore or 'q' to quit: [1]

What happened was that the program opened a connection to the specified host and started an SSL handshake. It printed the exception stack trace of the error that occured and shows you the certificates used by the server. Now it prompts you for the certificate you want to add to your trusted KeyStore. You should only do this if you are sure that this is the certificate of the trusted host you want to connect to. You may want to check the MD5 and SHA1 certificate fingerprints against a fingerprint generated on the server (e.g. using keytool) to make sure it is the correct certificate.

If you've changed your mind, enter 'q'. If you really want to add the certificate, enter '1'. (You could also add a CA certificate by entering a different certificate, but you usually don't want to do that'). Once you have made your choice, the program will print the following:


[
[
  Version: V3
  Subject: CN=ecc.fedora.redhat.com, O=example.com, C=US
  Signature Algorithm: MD5withRSA, OID = 1.2.840.113549.1.1.4

  Key:  SunPKCS11-Solaris RSA public key, 1024 bits
        (id 5158256, session object)
  modulus: 1402933022884660852748661816869706021655226675890
635441166580364941191074987345500771612454338502131694873337
233737712894815966313948609351561047977102880577818156814678
041303637255354084762814638611185951230474669455913908815827
173696651397340074281578017567044868711049821409365743953199
69584127568303024757
  public exponent: 65537
  Validity: [From: Wed Jan 18 13:16:12 PST 2006,
               To: Wed Apr 18 14:16:12 PDT 2007]
  Issuer: CN=Certificate Shack, O=example.com, C=US
  SerialNumber: [    0f]

Certificate Extensions: 2
[1]: ObjectId: 2.16.840.1.113730.1.1 Criticality=false
NetscapeCertType [
   SSL server
]

[2]: ObjectId: 2.5.29.15 Criticality=false
KeyUsage [
  Key_Encipherment
]

]
  Algorithm: [MD5withRSA]
  Signature:
0000: 6D F4 2A 63 76 2A 05 70   A2 21 0E 1E 4A 31 BE 6B  m.*cv*.p.!..J1.k
0010: 15 64 D8 BB 35 36 82 B0   0D 2A 96 FA 7A 9F A1 59  .d..56...*..z..Y
0020: CA 90 C3 28 C5 A6 9B 59   05 3B EB B2 8D C9 5E 38  ...(...Y.;....^8
0030: 62 ED 1A D7 93 DF 2A A5   D6 54 94 23 15 A2 0C E5  b.....*..T.#....
0040: 13 40 2C 3E 59 E4 2A EB   51 AC 9E 28 44 23 87 B1  .@,>Y.*.Q..(D#..
0050: 34 0B AC F3 E0 39 CA B8   35 B4 78 07 BF 28 4C C4  4....9..5.x..(L.
0060: 9A 2B A3 E9 04 26 78 19   F0 62 EA 0A B5 BB DC 0B  .+...&x..b......
0070: 90 59 E7 77 90 F8 BC 8A   1B 74 4B 4D C1 F8 3B 6C  .Y.w.....tKM..;l

]

Added certificate to keystore 'jssecacerts' using alias
'ecc.fedora.redhat.com-1'

It displayed the complete certificate and then added it to a Java KeyStore 'jssecacerts' in the current directory. To use it in your program, either configure JSSE to use it as its trust store (as explained in the documentation) or copy it into your $JAVA_HOME/jre/lib/security directory. If you want all Java applications to recognize the certificate as trusted and not just JSSE, you could also overwrite the cacerts file in that directory.

After all that, JSSE will be able to complete a handshake with the host, which you can verify by running the program again:

% java InstallCert ecc.fedora.redhat.com
Loading KeyStore jssecacerts...
Opening connection to ecc.fedora.redhat.com:443...
Starting SSL handshake...

No errors, certificate is already trusted

Server sent 2 certificate(s):

 1 Subject CN=ecc.fedora.redhat.com, O=example.com, C=US
   Issuer  CN=Certificate Shack, O=example.com, C=US
   sha1    2e 7f 76 9b 52 91 09 2e 5d 8f 6b 61 39 2d 5e 06 e4 d8 e9 c7
   md5     dd d1 a8 03 d7 6c 4b 11 a7 3d 74 28 89 d0 67 54

 2 Subject CN=Certificate Shack, O=example.com, C=US
   Issuer  CN=Certificate Shack, O=example.com, C=US
   sha1    fb 58 a7 03 c4 4e 3b 0e e3 2c 40 2f 87 64 13 4d df e1 a1 a6
   md5     72 a0 95 43 7e 41 88 18 ae 2f 6d 98 01 2c 89 68

Enter certificate to add to trusted keystore or
'q' to quit: [1]
q
KeyStore not changed

I hope that helps. For more information about the InstallCert program, have a look at the (**2011-10-11 edit the link now has a 404 (gone from the web) so I posted the source below) source code. I am sure you can figure out how it works.

Posted at 22:28 Oct 09, 2006 by Andreas Sterbenz


The source to InstallCert.java



http://blogs.oracle.com/gc/entry/unable_to_find_valid_certification


/*
 * Copyright 2006 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * Originally from:
 * http://blogs.sun.com/andreas/resource/InstallCert.java
 * Use:
 * java InstallCert hostname
 * Example:
 *% java InstallCert ecc.fedora.redhat.com
 */

import javax.net.ssl.*;
import java.io.*;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
 * Class used to add the server's certificate to the KeyStore
 * with your trusted certificates.
 */
public class InstallCert {

    public static void main(String[] args) throws Exception {
        String host;
        int port;
        char[] passphrase;
        if ((args.length == 1) || (args.length == 2)) {
            String[] c = args[0].split(":");
            host = c[0];
            port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
            String p = (args.length == 1) ? "changeit" : args[1];
            passphrase = p.toCharArray();
        } else {
            System.out.println("Usage: java InstallCert [:port] [passphrase]");
            return;
        }

        File file = new File("jssecacerts");
        if (file.isFile() == false) {
            char SEP = File.separatorChar;
            File dir = new File(System.getProperty("java.home") + SEP
                    + "lib" + SEP + "security");
            file = new File(dir, "jssecacerts");
            if (file.isFile() == false) {
                file = new File(dir, "cacerts");
            }
        }
        System.out.println("Loading KeyStore " + file + "...");
        InputStream in = new FileInputStream(file);
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(in, passphrase);
        in.close();

        SSLContext context = SSLContext.getInstance("TLS");
        TrustManagerFactory tmf =
                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
        SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
        context.init(null, new TrustManager[]{tm}, null);
        SSLSocketFactory factory = context.getSocketFactory();

        System.out.println("Opening connection to " + host + ":" + port + "...");
        SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
        socket.setSoTimeout(10000);
        try {
            System.out.println("Starting SSL handshake...");
            socket.startHandshake();
            socket.close();
            System.out.println();
            System.out.println("No errors, certificate is already trusted");
        } catch (SSLException e) {
            System.out.println();
            e.printStackTrace(System.out);
        }

        X509Certificate[] chain = tm.chain;
        if (chain == null) {
            System.out.println("Could not obtain server certificate chain");
            return;
        }

        BufferedReader reader =
                new BufferedReader(new InputStreamReader(System.in));

        System.out.println();
        System.out.println("Server sent " + chain.length + " certificate(s):");
        System.out.println();
        MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        for (int i = 0; i < chain.length; i++) {
            X509Certificate cert = chain[i];
            System.out.println
                    (" " + (i + 1) + " Subject " + cert.getSubjectDN());
            System.out.println("   Issuer  " + cert.getIssuerDN());
            sha1.update(cert.getEncoded());
            System.out.println("   sha1    " + toHexString(sha1.digest()));
            md5.update(cert.getEncoded());
            System.out.println("   md5     " + toHexString(md5.digest()));
            System.out.println();
        }

        System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
        String line = reader.readLine().trim();
        int k;
        try {
            k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
        } catch (NumberFormatException e) {
            System.out.println("KeyStore not changed");
            return;
        }

        X509Certificate cert = chain[k];
        String alias = host + "-" + (k + 1);
        ks.setCertificateEntry(alias, cert);

        OutputStream out = new FileOutputStream("jssecacerts");
        ks.store(out, passphrase);
        out.close();

        System.out.println();
        System.out.println(cert);
        System.out.println();
        System.out.println
                ("Added certificate to keystore 'jssecacerts' using alias '"
                        + alias + "'");
    }

    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

    private static String toHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 3);
        for (int b : bytes) {
            b &= 0xff;
            sb.append(HEXDIGITS[b >> 4]);
            sb.append(HEXDIGITS[b & 15]);
            sb.append(' ');
        }
        return sb.toString();
    }

    private static class SavingTrustManager implements X509TrustManager {

        private final X509TrustManager tm;
        private X509Certificate[] chain;

        SavingTrustManager(X509TrustManager tm) {
            this.tm = tm;
        }

        public X509Certificate[] getAcceptedIssuers() {
            throw new UnsupportedOperationException();
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
            throw new UnsupportedOperationException();
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
            this.chain = chain;
            tm.checkServerTrusted(chain, authType);
        }
    }
}

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Re: No more 'unable to find valid certification path to ...

I m getting this error how to solve
plese help
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at com.sun.net.ssl.internal.ssl.InputRecord.readFully(InputRecord.java:2
93)
at com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:331)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.j
ava:789)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SS
LSocketImpl.java:1112)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketIm
pl.java:1139)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketIm
pl.java:1123)
at InstallCert.main(InstallCert.java:89)

Re: No more 'unable to find valid certification path to ...

Thanks for the post! it is very helpful!
So, I got this exception is because I was trying to communicating with a server using a self-signed SSL using a Java based client.
Does it mean if my java based client was trying to communicating with a server using a SSL certificate signed by a certificate authority then I will not get this exception? If that's not true. Then does the InstallCert program will also work for server using a SSL certificate signed by a certificate authority?
Unfortunately I don't have a server using a SSL certificate signed by a certificate authority for me to test/verify. Can someone please clarify this for me? Thanks a lot!

Re: No more 'unable to find valid certification path to ...

I can't get passed the createSocket() line, it throws a connection timed out exception !!!!
to be sure, I am behind proxy but I thought I took care of that with setProperty,
help ! please !

Re: No more 'unable to find valid certification path to ...

works perfect!! thank you!

one question, this change will work for both versions 1.6.X and 1.7.X or i have to change everytime i run it en each system?

Re: No more 'unable to find valid certification path to ...

I used to get unknown_certificate issue,but when i ran the above program the issue is solved and i get the below exception.Please help
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source)
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.recvAlert(Unknown Source)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(Unknown Source)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at wavecrest.integration.util.InstallCert.main(InstallCert.java:61)

Re: No more 'unable to find valid certification path to ...

Seven (7) years later and this post has released me from intense pain. Thank you!

Leeland's picture

Re: No more 'unable to find valid certification path to ...

You, me and thousands of others. You'd think it would get better over time... SIGH...

Re: No more 'unable to find valid certification path to ...

first of all thanks for your great tutorial
i did this steps in my own computer and it work correctly
but when i do steps in my workplace computer , result is like this:
i run step 1 correctly and file jssecacerts create succsessfuly
but when i run java InstallCert pop.gmail.com:995 again to check connection i get this error

javan.net.SocketException:connection reset
at java.net.Socket input Stream.read

please help me its very necessary for me

Re: No more 'unable to find valid certification path to ...

Hi,
First of all thank you for sharing this piece of info.
I did follow each step and also renamed the "jssecacerts" as "cacerts" and replaced in the "%JAVA_HOME%\jre\lib\security" dir.

But again while running the command (java InstallCert ) for second time, I am getting below errors:

D:\MyEclipse8_WS\compAproj\src>java InstallCert 192.162.5.62
Loading KeyStore jssecacerts...
Opening connection to 192.162.5.62:443...
Starting SSL handshake...

javax.net.ssl.SSLException: java.lang.UnsupportedOperationException
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
at sun.security.ssl.SSLSocketImpl.handleException(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at InstallCert.main(InstallCert.java:100)
Caused by: java.lang.UnsupportedOperationException
at InstallCert$SavingTrustManager.getAcceptedIssuers(InstallCert.java:184)
at sun.security.ssl.AbstractTrustManagerWrapper.checkAlgorithmConstraints(Unknown Source)
at sun.security.ssl.AbstractTrustManagerWrapper.checkAdditionalTrust(Unknown Source)
at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(Unknown Source)
at sun.security.ssl.ClientHandshaker.serverCertificate(Unknown Source)
at sun.security.ssl.ClientHandshaker.processMessage(Unknown Source)
at sun.security.ssl.Handshaker.processLoop(Unknown Source)
at sun.security.ssl.Handshaker.process_record(Unknown Source)
at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
... 3 more

Server sent 1 certificate(s):

1 Subject CN=192.162.5.62, OU=DMS, O=RSI, L=Bangalore, ST=Karnatak, C=IN
Issuer CN=192.162.5.62, OU=DMS, O=RSI, L=Bangalore, ST=Karnatak, C=IN
sha1 6e ee e0 c7 19 5f ee ab 6f e0 bd 60 db 56 fa 7f 9a 82 dc 08
md5 31 b6 06 9f 77 58 33 3b b8 ad 79 9b 44 77 a9 b0

Enter certificate to add to trusted keystore or 'q' to quit: [1]

Any help please ??

Re: No more 'unable to find valid certification path to ...

Hi Shibaram,

These error messages are normal and when you get:
"Enter certificate to add to trusted keystore or 'q' to quit: [1]"
press 1 and hit enter.

It is not necessary to replace the cacerts by jssecacerts, simply add in your code (after running InstallCert):
System.setProperty ("javax.net.ssl.trustStore", "jssecacerts");

Good luck, :D
[ ]'s

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <em> <strong> <b> <i> <big> <small> <sub> <sup> <cite> <code> <ul> <ol> <li> <dl> <lh> <dt> <dd> <br> <p> <table> <th> <td> <tr> <pre> <blockquote> <h1> <h2> <h3> <h4> <h5> <h6> <hr>
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.

Thread Slivers eBook at Amazon