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