001: /*
002: * JOSSO: Java Open Single Sign-On
003: *
004: * Copyright 2004-2008, Atricore, Inc.
005: *
006: * This is free software; you can redistribute it and/or modify it
007: * under the terms of the GNU Lesser General Public License as
008: * published by the Free Software Foundation; either version 2.1 of
009: * the License, or (at your option) any later version.
010: *
011: * This software is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this software; if not, write to the Free
018: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
019: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
020: */
021: package org.josso.auth.scheme;
022:
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025: import org.josso.auth.Credential;
026: import org.josso.auth.SimplePrincipal;
027: import org.josso.auth.CredentialProvider;
028: import org.josso.auth.exceptions.SSOAuthenticationException;
029:
030: import java.io.ByteArrayInputStream;
031: import java.security.Principal;
032: import java.security.cert.CertificateException;
033: import java.security.cert.CertificateFactory;
034: import java.security.cert.X509Certificate;
035: import java.util.HashMap;
036: import java.util.StringTokenizer;
037:
038: import sun.security.util.DerValue;
039:
040: /**
041: * Certificate-based Authentication Scheme.
042: *
043: * @author <a href="mailto:gbrigand@josso.org">Gianluca Brigandi</a>
044: * @version CVS $Id: X509CertificateAuthScheme.java 508 2008-02-18 13:32:29Z sgonzalez $
045: */
046:
047: public class X509CertificateAuthScheme extends
048: AbstractAuthenticationScheme {
049: private static final Log logger = LogFactory
050: .getLog(X509CertificateAuthScheme.class);
051:
052: /* Component Properties */
053: private String _name;
054: private String _uidOID;
055:
056: /**
057: * @throws SSOAuthenticationException
058: */
059: public boolean authenticate() throws SSOAuthenticationException {
060:
061: setAuthenticated(false);
062:
063: //String username = getUsername(_inputCredentials);
064: X509Certificate x509Certificate = getX509Certificate(_inputCredentials);
065:
066: // Check if all credentials are present.
067: if (x509Certificate == null) {
068:
069: if (logger.isDebugEnabled())
070: logger.debug("X.509 Certificate not provided");
071:
072: // We don't support empty values !
073: return false;
074: }
075:
076: //String knownUsername = getUsername(getKnownCredentials());
077: X509Certificate knownX509Certificate = getX509Certificate(getKnownCredentials());
078:
079: StringBuffer buf = new StringBuffer("\n\tSupplied Credential: ");
080: buf.append(x509Certificate.getSerialNumber().toString(16));
081: buf.append("\n\t\t");
082: buf.append(x509Certificate.getSubjectDN().getName());
083: buf.append("\n\n\tExisting Credential: ");
084: if (knownX509Certificate != null) {
085: buf.append(knownX509Certificate.getSerialNumber().toString(
086: 16));
087: buf.append("\n\t\t");
088: buf.append(knownX509Certificate.getSubjectDN().getName());
089: buf.append("\n");
090: }
091:
092: logger.debug(buf.toString());
093:
094: // Validate user identity ...
095: if (!validateX509Certificate(x509Certificate,
096: knownX509Certificate)) {
097: return false;
098: }
099:
100: if (logger.isDebugEnabled())
101: logger.debug("[authenticate()], Principal authenticated : "
102: + x509Certificate.getSubjectDN());
103:
104: // We have successfully authenticated this user.
105: setAuthenticated(true);
106: return true;
107: }
108:
109: /**
110: * Create a X.509 Certificate Credential Provider instance
111: * @return
112: */
113: protected CredentialProvider doMakeCredentialProvider() {
114: return new X509CertificateCredentialProvider();
115: }
116:
117: private X509Certificate buildX509Certificate(byte[] binaryCert) {
118: X509Certificate cert = null;
119:
120: try {
121: ByteArrayInputStream bais = new ByteArrayInputStream(
122: binaryCert);
123: CertificateFactory cf = CertificateFactory
124: .getInstance("X.509");
125:
126: cert = (X509Certificate) cf.generateCertificate(bais);
127:
128: if (logger.isDebugEnabled())
129: logger.debug("Building X.509 certificate result :\n "
130: + cert);
131:
132: } catch (CertificateException ce) {
133: logger.error("Error instantiating X.509 Certificate", ce);
134: }
135:
136: return cert;
137: }
138:
139: private X509Certificate buildX509Certificate(String plainCert) {
140: return buildX509Certificate(plainCert.getBytes());
141: }
142:
143: /**
144: * Returns the private input credentials.
145: *
146: * @return the private input credentials
147: */
148: public Credential[] getPrivateCredentials() {
149: Credential c = getX509CertificateCredential(_inputCredentials);
150:
151: if (c == null)
152: return new Credential[0];
153:
154: Credential[] r = { c };
155: return r;
156: }
157:
158: /**
159: * Returns the public input credentials.
160: *
161: * @return the public input credentials
162: */
163: public Credential[] getPublicCredentials() {
164: Credential c = getX509CertificateCredential(_inputCredentials);
165:
166: if (c == null)
167: return new Credential[0];
168:
169: Credential[] r = { c };
170: return r;
171: }
172:
173: /**
174: * Instantiates a Principal for the user X509 Certificate.
175: * Used as the primary key to obtain the known credentials from the associated
176: * store.
177: *
178: * @return the Principal associated with the input credentials.
179: */
180: public Principal getPrincipal() {
181: return getPrincipal(_inputCredentials);
182: }
183:
184: /**
185: * Instantiates a Principal for the user X509 Certificate.
186: * Used as the primary key to obtain the known credentials from the associated
187: * store.
188: *
189: * @return the Principal associated with the input credentials.
190: */
191: public Principal getPrincipal(Credential[] credentials) {
192: Principal p = getX509Certificate(credentials).getSubjectDN();
193: SimplePrincipal targetPrincipal = null;
194:
195: if (_uidOID == null) {
196: HashMap compoundName = parseCompoundName(p.getName());
197:
198: // Extract from the Distinguished Name (DN) only the Common Name (CN) since its
199: // the store who sets the root naming context to be used based on the
200: // store configuration.
201: String cn = (String) compoundName.get("cn");
202:
203: if (cn == null)
204: logger
205: .error("Invalid Subject DN. Cannot create Principal : "
206: + p.getName());
207:
208: targetPrincipal = new SimplePrincipal(cn);
209: } else {
210: try {
211: byte[] oidValue = getOIDBitStringValueFromCert(
212: getX509Certificate(credentials), _uidOID);
213:
214: if (oidValue == null)
215: logger.error("No value obtained for OID " + _uidOID
216: + ". Cannot create Principal : "
217: + p.getName());
218:
219: // TODO: what if the OID is a compound value?
220: targetPrincipal = new SimplePrincipal(new String(
221: oidValue));
222: } catch (Exception e) {
223: logger.error(
224: "Fatal error obtaining UID value using OID "
225: + _uidOID
226: + ". Cannot create Principal : "
227: + p.getName(), e);
228: }
229: }
230:
231: return targetPrincipal;
232: }
233:
234: /**
235: * Gets the credential that represents an X.509 Certificate.
236: */
237: protected X509CertificateCredential getX509CertificateCredential(
238: Credential[] credentials) {
239:
240: for (int i = 0; i < credentials.length; i++) {
241: if (credentials[i] instanceof X509CertificateCredential) {
242: return (X509CertificateCredential) credentials[i];
243: }
244: }
245: return null;
246: }
247:
248: /**
249: * Gets the X.509 certificate from the supplied credentials
250: *
251: * @param credentials
252: */
253: protected X509Certificate getX509Certificate(
254: Credential[] credentials) {
255: X509CertificateCredential c = getX509CertificateCredential(credentials);
256: if (c == null)
257: return null;
258:
259: return (X509Certificate) c.getValue();
260: }
261:
262: /**
263: * This method validates the input x509 certificate agaist the expected x509 certificate.
264: *
265: * @param inputX509Certificate the X.509 Certificate supplied on authentication.
266: * @param expectedX509Certificate the actual X.509 Certificate
267: * @return true if the certificates match or false otherwise.
268: */
269: protected boolean validateX509Certificate(
270: X509Certificate inputX509Certificate,
271: X509Certificate expectedX509Certificate) {
272:
273: if (inputX509Certificate == null
274: && expectedX509Certificate == null)
275: return false;
276:
277: return inputX509Certificate.equals(expectedX509Certificate);
278: }
279:
280: /**
281: * Parses a Compound name
282: * (ie. CN=Java Duke, OU=Java Software Division, O=Sun Microsystems Inc, C=US) and
283: * builds a HashMap object with key-value pairs.
284: *
285: * @param s a string containing the compound name to be parsed
286: * @return a HashMap object built from the parsed key-value pairs
287: * @throws IllegalArgumentException if the compound name
288: * is invalid
289: */
290: private HashMap parseCompoundName(String s) {
291:
292: String valArray[] = null;
293:
294: if (s == null) {
295: throw new IllegalArgumentException();
296: }
297: HashMap hm = new HashMap();
298: StringBuffer sb = new StringBuffer();
299: StringTokenizer st = new StringTokenizer(s, ",");
300: while (st.hasMoreTokens()) {
301: String pair = (String) st.nextToken();
302: int pos = pair.indexOf('=');
303: if (pos == -1) {
304: // XXX
305: // should give more detail about the illegal argument
306: throw new IllegalArgumentException();
307: }
308: String key = pair.substring(0, pos).trim().toLowerCase();
309: String val = pair.substring(pos + 1, pair.length()).trim();
310: hm.put(key, val);
311: }
312: return hm;
313: }
314:
315: private byte[] getOIDBitStringValueFromCert(X509Certificate cert,
316: String oid) throws Exception {
317:
318: byte[] derEncodedValue = cert.getExtensionValue(oid);
319: byte[] extensionValue = null;
320:
321: DerValue dervalue = new DerValue(derEncodedValue);
322: if (dervalue == null) {
323: throw new IllegalArgumentException(
324: "extension not found for OID : " + oid);
325: }
326: if (dervalue.tag != DerValue.tag_BitString) {
327: throw new IllegalArgumentException(
328: "extension vaue for OID not of type BIT_STRING: "
329: + oid);
330: }
331:
332: extensionValue = dervalue.getBitString();
333:
334: byte extensionValueBytes[] = new byte[extensionValue.length - 2];
335:
336: System.arraycopy(extensionValue, 2, extensionValueBytes, 0,
337: extensionValueBytes.length);
338:
339: return extensionValueBytes;
340: }
341:
342: /*------------------------------------------------------------ Properties
343:
344: /**
345: * Sets Authentication Scheme name
346: */
347: public void setName(String name) {
348: _name = name;
349: }
350:
351: /**
352: * Obtains the Authentication Scheme name
353: */
354: public String getName() {
355: return _name;
356: }
357:
358: /**
359: * Sets the OID for the UID
360: */
361: public void setUidOID(String uidOID) {
362: _uidOID = uidOID;
363: }
364:
365: /**
366: * Obtains the UID OID
367: */
368: public String getUidOID() {
369: return _uidOID;
370: }
371:
372: }
|