[jboss-cvs] Picketbox SVN: r270 - tags/4.0.5.Final/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback.
jboss-cvs-commits at lists.jboss.org
jboss-cvs-commits at lists.jboss.org
Tue Oct 4 18:06:56 EDT 2011
Author: mmoyses
Date: 2011-10-04 18:06:56 -0400 (Tue, 04 Oct 2011)
New Revision: 270
Added:
tags/4.0.5.Final/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DigestCallbackHandler.java
tags/4.0.5.Final/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/RFC2617Digest.java
Log:
adding callbacks for digest authentication
Added: tags/4.0.5.Final/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DigestCallbackHandler.java
===================================================================
--- tags/4.0.5.Final/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DigestCallbackHandler.java (rev 0)
+++ tags/4.0.5.Final/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DigestCallbackHandler.java 2011-10-04 22:06:56 UTC (rev 270)
@@ -0,0 +1,80 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2011, Red Hat, Inc., 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.auth.callback;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.jboss.security.auth.callback.MapCallback;
+
+/**
+ * A CallbackHandler that is used to pass the RFC2617 parameters to the login module DigestCallback.
+ *
+ * @author Scott.Stark at jboss.org
+ */
+public class DigestCallbackHandler implements CallbackHandler {
+ private String username;
+ private String nonce;
+ private String nc;
+ private String cnonce;
+ private String qop;
+ private String realm;
+ private String md5a2;
+
+ DigestCallbackHandler(String username, String nonce, String nc, String cnonce, String qop, String realm, String md5a2) {
+ this.username = username;
+ this.nonce = nonce;
+ this.nc = nc;
+ this.cnonce = cnonce;
+ this.qop = qop;
+ this.realm = realm;
+ this.md5a2 = md5a2;
+ }
+
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ boolean foundCallback = false;
+ Callback firstUnknown = null;
+ int count = callbacks != null ? callbacks.length : 0;
+ for (int n = 0; n < count; n++) {
+ Callback c = callbacks[n];
+ if (c instanceof MapCallback) {
+ MapCallback mc = (MapCallback) c;
+ mc.setInfo(RFC2617Digest.USERNAME, username);
+ mc.setInfo(RFC2617Digest.CNONCE, cnonce);
+ mc.setInfo(RFC2617Digest.NONCE, nonce);
+ mc.setInfo(RFC2617Digest.NONCE_COUNT, nc);
+ mc.setInfo(RFC2617Digest.QOP, qop);
+ mc.setInfo(RFC2617Digest.REALM, realm);
+ mc.setInfo(RFC2617Digest.A2HASH, md5a2);
+ foundCallback = true;
+ } else if (firstUnknown == null) {
+ firstUnknown = c;
+ }
+ }
+ if (foundCallback == false)
+ throw new UnsupportedCallbackException(firstUnknown, "Unrecognized Callback");
+ }
+}
Added: tags/4.0.5.Final/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/RFC2617Digest.java
===================================================================
--- tags/4.0.5.Final/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/RFC2617Digest.java (rev 0)
+++ tags/4.0.5.Final/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/RFC2617Digest.java 2011-10-04 22:06:56 UTC (rev 270)
@@ -0,0 +1,363 @@
+/*
+ * 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.auth.callback;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+
+import javax.security.auth.callback.Callback;
+
+import org.jboss.crypto.digest.DigestCallback;
+import org.jboss.security.auth.callback.MapCallback;
+
+/**
+ An implementation of the DigestCallback that support the http digest auth as
+ described in RFC2617 (http://www.ietf.org/rfc/rfc2617.txt).
+
+ 3.2.2.1 Request-Digest
+
+ If the "qop" value is "auth" or "auth-int":
+
+ request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" nc-value ":"
+ unq(cnonce-value) ":" unq(qop-value) ":" H(A2) ) <">
+
+ If the "qop" directive is not present (this construction is for compatibility
+ with RFC 2069):
+
+ request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
+
+ See below for the definitions for A1 and A2.
+
+ 3.2.2.2 A1
+
+ If the "algorithm" directive's value is "MD5" or is unspecified, then A1 is:
+
+ A1 = unq(username-value) ":" unq(realm-value) ":" passwd
+
+ where
+
+ passwd = < user's password >
+
+ If the "algorithm" directive's value is "MD5-sess", then A1 is calculated only
+ once - on the first request by the client following receipt of a
+ WWW-Authenticate challenge from the server. It uses the server nonce from that
+ challenge, and the first client nonce value to construct A1 as follows:
+
+ A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd ) ":"
+ unq(nonce-value) ":" unq(cnonce-value)
+
+ This creates a 'session key' for the authentication of subsequent requests and
+ responses which is different for each "authentication session", thus limiting
+ the amount of material hashed with any one key. (Note: see further discussion
+ of the authentication session in section 3.3.) Because the server need only use
+ the hash of the user credentials in order to create the A1 value, this
+ construction could be used in conjunction with a third party authentication
+ service so that the web server would not need the actual password value. The
+ specification of such a protocol is beyond the scope of this specification.
+
+ 3.2.2.3 A2
+
+ If the "qop" directive's value is "auth" or is unspecified, then A2 is:
+
+ A2 = Method ":" digest-uri-value
+
+ If the "qop" value is "auth-int", then A2 is:
+
+ A2 = Method ":" digest-uri-value ":" H(entity-body)
+
+ 3.2.2.4 Directive values and quoted-string
+
+ Note that the value of many of the directives, such as "username- value", are
+ defined as a "quoted-string". However, the "unq" notation indicates that
+ surrounding quotation marks are removed in forming the string A1. Thus if the
+ Authorization header includes the fields
+
+ username="Mufasa", realm=myhost at testrealm.com
+
+ and the user Mufasa has password "Circle Of Life" then H(A1) would be
+ H(Mufasa:myhost at testrealm.com:Circle Of Life) with no quotation marks in the
+ digested string.
+
+ No white space is allowed in any of the strings to which the digest function H()
+ is applied unless that white space exists in the quoted strings or entity body
+ whose contents make up the string to be digested. For example, the string A1
+ illustrated above must be
+
+ Mufasa:myhost at testrealm.com:Circle Of Life
+
+ with no white space on either side of the colons, but with the white space
+ between the words used in the password value. Likewise, the other strings
+ digested by H() must not have white space on either side of the colons which
+ delimit their fields unless that white space was in the quoted strings or entity
+ body being digested.
+
+ Also note that if integrity protection is applied (qop=auth-int), the
+ H(entity-body) is the hash of the entity body, not the message body - it is
+ computed before any transfer encoding is applied by the sender and after it has
+ been removed by the recipient. Note that this includes multipart boundaries and
+ embedded headers in each part of any multipart content-type.
+
+ @author Scott.Stark at jboss.org
+ */
+public class RFC2617Digest implements DigestCallback {
+ /**
+ * String which can enable users to know which username and password to use, in case they might have different ones for
+ * different servers.
+ */
+ public static final String REALM = "realm";
+
+ /**
+ * The user's name in the specified realm.
+ */
+ public static final String USERNAME = "username";
+
+ /**
+ * The URI from Request-URI of the Request-Line; duplicated here because proxies are allowed to change the Request-Line in
+ * transit.
+ */
+ public static final String DIGEST_URI = "digest-uri";
+
+ /**
+ * A server-specified data string which MUST be different each time a digest-challenge is sent as part of initial
+ * authentication. It is recommended that this string be base64 or hexadecimal data. Note that since the string is passed as
+ * a quoted string, the double-quote character is not allowed unless escaped (see section 7.2). The contents of the nonce
+ * are implementation dependent. The
+ *
+ * security of the implementation depends on a good choice. It is RECOMMENDED that it contain at least 64 bits of entropy.
+ * The nonce is opaque to the client. This directive is required and MUST appear exactly once; if not present, or if
+ * multiple instances are present, the client should abort the authentication exchange.
+ */
+ public static final String NONCE = "nonce";
+
+ /**
+ * This MUST be specified if a qop directive is sent (see above), and MUST NOT be specified if the server did not send a qop
+ * directive in the WWW-Authenticate header field. The cnonce-value is an opaque quoted string value provided by the client
+ * and used by both client and server to avoid chosen plaintext attacks, to provide mutual authentication, and to provide
+ * some message integrity protection. See the descriptions below of the calculation of the response- digest and
+ * request-digest values.
+ */
+ public static final String CNONCE = "cnonce";
+
+ /**
+ * This MUST be specified if a qop directive is sent (see above), and MUST NOT be specified if the server did not send a qop
+ * directive in the WWW-Authenticate header field. The nc-value is the hexadecimal count of the number of requests
+ * (including the current request) that the client has sent with the nonce value in this request. For example, in the first
+ * request sent in response to a given nonce value, the client sends "nc=00000001". The purpose of this directive is to
+ * allow the server to detect request replays by maintaining its own copy of this count - if the same nc-value is seen
+ * twice, then the request is a replay. See the description below of the construction of the request-digest value.
+ */
+ public static final String NONCE_COUNT = "nc";
+
+ /**
+ * Indicates what "quality of protection" the client has applied to the message. If present, its value MUST be one of the
+ * alternatives the server indicated it supports in the WWW-Authenticate header. These values affect the computation of the
+ * request-digest. Note that this is a single token, not a quoted list of alternatives as in WWW- Authenticate. This
+ * directive is optional in order to preserve backward compatibility with a minimal implementation of RFC 2069 [6], but
+ * SHOULD be used if the server indicated that qop is supported by providing a qop directive in the WWW-Authenticate header
+ * field.
+ */
+ public static final String QOP = "qop";
+
+ /**
+ * A string indicating a pair of algorithms used to produce the digest and a checksum. If this is not present it is assumed
+ * to be "MD5". If the algorithm is not understood, the challenge should be ignored (and a different one used, if there is
+ * more than one).
+ *
+ * In this document the string obtained by applying the digest algorithm to the data "data" with secret "secret" will be
+ * denoted by KD(secret, data), and the string obtained by applying the checksum algorithm to the data "data" will be
+ * denoted H(data). The notation unq(X) means the value of the quoted-string X without the surrounding quotes.
+ */
+ public static final String ALGORITHM = "algorithm";
+
+ /**
+ * This directive allows for future extensions. Any unrecognized directive MUST be ignored.
+ */
+ public static final String AUTH_PARAM = "auth-param";
+
+ /**
+ * The http method type
+ */
+ public static final String METHOD = "method";
+
+ /**
+ * An explicit A2 digest
+ */
+ public static final String A2HASH = "a2hash";
+
+ /**
+ * The ASCII printable characters the MD5 digest maps to
+ */
+ private static char[] MD5_HEX = "0123456789abcdef".toCharArray();
+
+ private MapCallback info;
+
+ private String username;
+
+ private String password;
+
+ private boolean passwordIsA1Hash;
+
+ String rfc2617;
+
+ public void init(Map options) {
+ username = (String) options.get("javax.security.auth.login.name");
+ password = (String) options.get("javax.security.auth.login.password");
+ String flag = (String) options.get("passwordIsA1Hash");
+ if (flag != null)
+ passwordIsA1Hash = Boolean.valueOf(flag).booleanValue();
+
+ // Ask for MapCallback to obtain the digest parameters
+ info = new MapCallback();
+ Callback[] callbacks = { info };
+ options.put("callbacks", callbacks);
+ }
+
+ public void preDigest(MessageDigest digest) {
+ }
+
+ public void postDigest(MessageDigest digest) {
+ String qop = (String) info.getInfo(QOP);
+ String realm = (String) info.getInfo(REALM);
+ String algorithm = (String) info.getInfo(ALGORITHM);
+ String nonce = (String) info.getInfo(NONCE);
+ String cnonce = (String) info.getInfo(CNONCE);
+ String method = (String) info.getInfo(METHOD);
+ String nc = (String) info.getInfo(NONCE_COUNT);
+ String digestURI = (String) info.getInfo(DIGEST_URI);
+
+ if (algorithm == null)
+ algorithm = digest.getAlgorithm();
+ // This replaces the existing hash, it does not add to it
+ digest.reset();
+
+ String hA1 = null;
+ // 3.2.2.2 A1
+ if (algorithm == null || algorithm.equals("MD5")) {
+ if (passwordIsA1Hash)
+ hA1 = password;
+ else {
+ String A1 = username + ":" + realm + ":" + password;
+ hA1 = H(A1, digest);
+ }
+ } else if (algorithm.equals("MD5-sess")) {
+ if (passwordIsA1Hash) {
+ hA1 = password + ":" + nonce + ":" + cnonce;
+ } else {
+ String A1 = username + ":" + realm + ":" + password;
+ hA1 = H(A1, digest) + ":" + nonce + ":" + cnonce;
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported algorigthm: " + algorithm);
+ }
+
+ // 3.2.2.3 A2. First check to see if the A2 hash has been precomputed
+ String hA2 = (String) info.getInfo(A2HASH);
+ if (hA2 == null) {
+ // No, compute it based on qop
+ String A2 = null;
+ if (qop == null || qop.equals("auth")) {
+ A2 = method + ":" + digestURI;
+ } else {
+ throw new IllegalArgumentException("Unsupported qop=" + qop);
+ }
+ hA2 = H(A2, digest);
+ }
+
+ // 3.2.2.1 Request-Digest
+ if (qop == null) {
+ String extra = nonce + ":" + hA2;
+ KD(hA1, extra, digest);
+ } else if (qop.equals("auth")) {
+ String extra = nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + hA2;
+ KD(hA1, extra, digest);
+ }
+ }
+
+ public String getInfoDigest(MessageDigest digest) {
+ if (rfc2617 == null) {
+ byte[] data = digest.digest();
+ rfc2617 = cvtHex(data);
+ }
+ return rfc2617;
+ }
+
+ private static String H(String data, MessageDigest digest) {
+ digest.reset();
+ byte[] x = digest.digest(data.getBytes());
+ return cvtHex(x);
+ }
+
+ private static void KD(String secret, String data, MessageDigest digest) {
+ String x = secret + ":" + data;
+ digest.reset();
+ digest.update(x.getBytes());
+ }
+
+ /**
+ * 3.1.3 Representation of digest values
+ *
+ * An optional header allows the server to specify the algorithm used to create the checksum or digest. By default the MD5
+ * algorithm is used and that is the only algorithm described in this document.
+ *
+ * For the purposes of this document, an MD5 digest of 128 bits is represented as 32 ASCII printable characters. The bits in
+ * the 128 bit digest are converted from most significant to least significant bit, four bits at a time to their ASCII
+ * presentation as follows. Each four bits is represented by its familiar hexadecimal notation from the characters
+ * 0123456789abcdef. That is, binary 0000 getInfos represented by the character '0', 0001, by '1', and so on up to the
+ * representation of 1111 as 'f'.
+ *
+ * @param data - the raw MD5 hash data
+ * @return the encoded MD5 representation
+ */
+ static String cvtHex(byte[] data) {
+ char[] hash = new char[32];
+ for (int i = 0; i < 16; i++) {
+ int j = (data[i] >> 4) & 0xf;
+ hash[i * 2] = MD5_HEX[j];
+ j = data[i] & 0xf;
+ hash[i * 2 + 1] = MD5_HEX[j];
+ }
+ return new String(hash);
+ }
+
+ /**
+ * Compute the
+ *
+ * @param args
+ */
+ public static void main(String[] args) throws NoSuchAlgorithmException {
+ if (args.length != 3) {
+ System.err.println("Usage: RFC2617Digest username realm password");
+ System.err.println(" - username : the username");
+ System.err.println(" - realm : the web app realm name");
+ System.err.println(" - password : the plain text password");
+ System.exit(1);
+ }
+ String username = args[0];
+ String realm = args[1];
+ String password = args[2];
+ String A1 = username + ":" + realm + ":" + password;
+ MessageDigest digest = MessageDigest.getInstance("MD5");
+ String hA1 = H(A1, digest);
+ System.out.println("RFC2617 A1 hash: " + hA1);
+ }
+}
More information about the jboss-cvs-commits
mailing list