OpenSSL / java / wiki bits

Ken Horn ken.horn at clara.co.uk
Thu Jun 2 05:33:16 PDT 2005


Just as an fyi and update -- thanks to Nathan's debugging help, I found 
a donkey error and fixed the intermittent error (it was an escaping 
combination i had wrong). The DSA was trivial in Java, the code sent 
previously was correct for that part, I juts can't read simple code 
anymore...

I've attached my code just as an example of a very simple Java server 
(that accepts any identity). It's a single class - (non-servlet), which 
makes it lightweight to run as a background daemon etc.


Ken Horn wrote:

> Thanks. I seem to be able to actually get multple different errors, 
> but just submitting the page more than once (and no, I'm waiting for 
> the requests to finish before retrying). My prog is basically only 
> using a single key pair per startup, and verifies anyone (might as 
> well get the protocol working prior to bells and whistles), so there's 
> only one key (unless the consumer is caching, despite asking for it).
>
> What might help, would be if someone could generate demo keys / hashes 
> / tokens etc and display them, so that other implementations could 
> verify the crypto stuff without the added moving parts. Anyone with a 
> working version fancy it? Of course, with the various file formats and 
> libs involved this may still be non-trivial to do cross language. 
> Hrmf, why aren't crypto formats more self describing?
>
>
> Martin Atkins wrote:
>
>> Ken Horn wrote:
>>
>>>      Error from demo page:
>>> *    **Error:* Error in DSA_verify: error:0D07209B:asn1 encoding 
>>> routines:ASN1_get_object:too long at 
>>> /usr/share/perl5/Net/OpenID/Consumer.pm line 401.
>>> /[runtime_error]/
>>
>>
>> [snip]
>>
>>>      If I miss out the sha1, I get: *Error:* DSA signature 
>>> verification failed /[verify_failed]  --  is this any better?/
>>
>>
>>
>> I think the second error is better -- for some value of "better", at 
>> least. The first error looks like your key is actually broken in some 
>> way, while the second error is the standard response you get if the 
>> signature is structured correctly but doesn't match the expected 
>> result with the given key.
>>
>> I don't know enough about Java's stuff to comment any further, but I 
>> think that in the second case you're closer to the answer. Make sure 
>> now that you are signing the right string, that the consumer is 
>> getting the right key, and so forth.
>>
>> Someone else can probably do better than this, but I hope this helps 
>> for now. :)
>>
>> _______________________________________________
>> yadis mailing list
>> yadis at lists.danga.com
>> http://lists.danga.com/mailman/listinfo/yadis
>>
>>
>>
>
> _______________________________________________
> yadis mailing list
> yadis at lists.danga.com
> http://lists.danga.com/mailman/listinfo/yadis
>
>
>
-------------- next part --------------
/*
 * Created on 20-May-2005
 * (c) Ken Horn 2005
 *
 * BSD License
 * 
 * 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 the author 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.
 * 
 * 
 */
package org.kenhorn.openid;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.text.SimpleDateFormat;
import java.util.Date;

import sun.misc.BASE64Encoder;

/**
 * Hopelessly naff server. 
 * 
 * @author Ken Horn
 */
public class NaffServer {

    private int port;

    public static void main(String[] args) throws Exception {
        new NaffServer(82).listen();
    }

    public NaffServer(int port)  {
        this.port = port;
        KeyPairGenerator keypairgenerator;
        try {
            keypairgenerator = KeyPairGenerator.getInstance("DSA");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }

        keyPair = keypairgenerator.generateKeyPair();
        
        System.out.println("public key format: "+keyPair.getPublic().getFormat());
        System.out.println("public key algthm: "+keyPair.getPublic().getAlgorithm());
        System.out.println("public key: \n"+getPublicKeyAsString());
        try {
            PrintWriter printWriter = new PrintWriter("publickey.txt");
            printWriter.print(getPublicKeyAsString());
            printWriter.close();
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }
    }

    /**
     * @return
     */
    private String getPublicKeyAsString() {
        byte[] encoded = keyPair.getPublic().getEncoded();
        return "-----BEGIN PUBLIC KEY-----\r\n"+base64(encoded)+"\r\n-----END PUBLIC KEY-----\r\n";
    }

    /**
     * @throws IOException
     */
    public void listen() throws IOException {
        final ServerSocket sock = new ServerSocket(port);

        new Thread(new Runnable() {
            public void run() {
                while (true) {
                    System.out.println(port + " - " + "waiting for a connection...");
                    try {
                        final Socket clientSocket = sock.accept();
                        BufferedReader br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                        OutputStream os = clientSocket.getOutputStream();
                        //os.write("HTTP/1.0 500 <a
                        // href=\"javascript:alert('xss!!');\">hold on</a>, i'm
                        // coming".getBytes()); os.flush();

                        try {
                            processConnection(br, os);
                        } catch (RuntimeException e1) {
                            e1.printStackTrace();
                            // last gasp to tell the client
                            os.write("HTTP/1.x 500 Barfed\r\n\r\n".getBytes());
                        }

                        os.flush();
                        os.close();
                        clientSocket.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("--");
                }
            }

        }).start();
    }

    /**
     * openid.sig, the DSA signature of form
     * BASE64(DSA(SHA1("[timestamp]::assert_identity::[identity_url]::[return_url]")))
     * where the DSA function returns a DER encoding of the ASN1 schema
     * SEQUENCE(INTEGER,INTEGER) where the two integers are "r" and "s" from
     * DSA. And of course, the entire thing is URL-escaped.
     * 
     * @param timeStamp
     * @param identity
     * @param trustRoot
     * @param returnLoc
     * @return
     */
    protected String getSignature(String timeStamp, String identity, String trustRoot, String returnLoc) {
        // "[timestamp]::assert_identity::[identity_url]::[return_url]"
        String token = timeStamp + "::" + "assert_identity" + "::" + identity + "::" + returnLoc ;
        System.out.println("Token#"+token+"#");
        return base64(dsaWsha(token.getBytes()));
    }


    private String base64(byte[] token) {
        String encoded = new BASE64Encoder().encode(token);
        return encoded;
    }

    /**
     * @param object
     * @return
     */
    private byte[] dsaWsha(byte[] token) {
        try {
            Signature signature = Signature.getInstance("SHA1withDSA");
            signature.initSign(getPrivateKey());
            signature.update(token);
            return signature.sign();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        }

        return new byte[] {};
    }

    private KeyPair keyPair;
    /**
     * @return
     * @throws NoSuchAlgorithmException
     */
    private PrivateKey getPrivateKey() throws NoSuchAlgorithmException {
        PrivateKey privatekey = keyPair.getPrivate();
        return privatekey;
    }


    /**
     * @param returnLoc
     * @param trustRoot
     * @param identity
     * @return
     */
    protected boolean checkPermissions(String returnLoc, String trustRoot, String identity) {
        return true;
    }

    /**
     * @param string
     * @param params
     * @return
     */
    protected String param(String string, String[] params) {
        for (int i = 0; i < params.length; i++) {
            String param = params[i];
            if (param.indexOf(string) == 0) {
                return param.substring(param.indexOf('=') + 1);
            }
        }
        return null;
    }

    protected String unescape(String s) {
        int pos = s.indexOf('%');
        int start = 0;
        if (pos == -1) {
            return s;
        }

        StringBuilder sb = new StringBuilder();
        while (pos > -1) {
            sb.append(s.substring(start, pos));
            String hex = s.substring(pos + 1, pos + 3);
            char ch = (char) Integer.parseInt(hex, 16);
            sb.append(ch);
            start = pos + 3;
            pos = s.indexOf('%', start);
        }
        sb.append(s.substring(start));

        return sb.toString();
    }

    protected String escape(String s) {
        try {
            String encoded = URLEncoder.encode(s, "UTF-8");
            System.out.println("pre esc="+s);
            System.out.println("pst esc="+encoded);
            return encoded;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return s;
        }
    }

    /**
     * @param br
     * @param os
     * @throws IOException
     */
    private void processConnection(BufferedReader br, OutputStream os) throws IOException {
        String commandLine = br.readLine();

        Date date = new Date();
        System.out.println(port + " - " + date + " - " + commandLine);
        String path = commandLine.split(" ")[1];
        String line = null;
        while ((line = br.readLine()) != null && line.trim().length() != 0) {
//            System.out.println(port + " - " + date + " - " + line);
        }

        PrintWriter pw = new PrintWriter(new OutputStreamWriter(os), true);
        String queryString = path.substring(path.indexOf('?') + 1);
        String[] params = queryString.split("&");

        String mode = param("openid.mode", params);

        String content;
        if (path.indexOf("/openid") == 0 && !"getpubkey".equals(mode)) {
            
            String redirect = processSigQuery(date, params, queryString);

            pw.print("HTTP/1.0 302 Moved\r\n");
            pw.print("Location: " + redirect + "\r\n");
            pw.print("Cache-Control: no-cache\r\n");

            System.out.println("Sending back location: " + redirect);

            content = "<html>\n" + "<head>" + "</head> \n" + "<body> \n" + "<p>okh.OpenID - /openid response <br>" + date
                    + "<p>\n" + "</body>" + "</html>";
        } else if ("getpubkey".equals(mode)){
            pw.print("HTTP/1.0 200 OK\r\n");
            content = getPublicKeyAsString();
        } else {
            pw.print("HTTP/1.0 200 OK\r\n");
            content = "<html>\n" + "<head>\r\n"
                    + "<link rel=\"openid.server\" href=\"http://itzu.homedns.org/openid.jsp\" /> \n" + "</head> \n"
                    + "<body> \n" + "okh.OpenID - " + date + "\n" + "</body>" + "</html>";
        }

        pw.print("Content-Type: text/html; charset=iso-8859-1\r\n");
        pw.print("Content-Length: " + content.length() + "\r\n");
        pw.print("Cache-Control: no-cache\r\n");

        pw.print("\r\n");
        pw.println(content);
        pw.flush();
    }

    /**
     * @param date
     * @param queryString
     * @return
     * @throws UnsupportedEncodingException
     */
    public String processSigQuery(Date date, String[] params, String queryString) throws UnsupportedEncodingException {
        
        String escapedReturnTo = param("openid.return_to", params);
        System.out.println("Return to url, trust?  " + escapedReturnTo);
        String unEscapedReturnTo = unescape(escapedReturnTo);
        System.out.println("      which is really  " + unEscapedReturnTo);

        String trustRoot = unescape(param("openid.trust_root", params));
        System.out.println("Do we trust this url?  " + trustRoot);

        String identity = unescape(param("openid.is_identity", params));
        System.out.println("Verify identity?       " + identity);

        if (trustRoot == null) {
            trustRoot = unEscapedReturnTo;
        }

        boolean granted = checkPermissions(unEscapedReturnTo, trustRoot, identity);

        String actionIfGranted = param("openid.post_grant", params);
//        System.out.println("Close or return? " + actionIfGranted);

        String redirect = unEscapedReturnTo;
        if (redirect.indexOf('?') > -1) {
            redirect += "&";
        } else {
            redirect += "?";
        }

        redirect += "openid.mode=id_res";

        if (!granted) {
            redirect += "&openid.user_setup_url=http://itzu.homedns.org/failedopenid?" + queryString;
        } else {
            redirect += "&openid.assert_identity=" + escape(identity);

            String timeStamp = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(date);
            System.out.println("Timestamp ==" + timeStamp);
            
            String signature = getSignature(timeStamp, identity, trustRoot, unEscapedReturnTo);
            redirect += "&openid.sig=" + escape(signature);
            redirect += "&openid.timestamp=" + timeStamp;
            redirect += "&openid.return_to=" + escapedReturnTo;
        }
        return redirect;
    }
}


More information about the yadis mailing list