001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.security.realm.providers; 019 020 import java.io.IOException; 021 import java.security.Principal; 022 import java.security.cert.Certificate; 023 import java.security.cert.X509Certificate; 024 import java.util.Arrays; 025 import java.util.Collections; 026 import java.util.HashSet; 027 import java.util.List; 028 import java.util.Map; 029 import java.util.Set; 030 import javax.security.auth.Subject; 031 import javax.security.auth.callback.Callback; 032 import javax.security.auth.callback.CallbackHandler; 033 import javax.security.auth.callback.UnsupportedCallbackException; 034 import javax.security.auth.login.FailedLoginException; 035 import javax.security.auth.login.LoginException; 036 import javax.security.auth.spi.LoginModule; 037 import javax.security.auth.x500.X500Principal; 038 039 import org.apache.commons.logging.Log; 040 import org.apache.commons.logging.LogFactory; 041 import org.apache.geronimo.security.jaas.JaasLoginModuleUse; 042 import org.apache.geronimo.security.jaas.WrappingLoginModule; 043 044 045 /** 046 * An example LoginModule that authenticates based on a client certificate. 047 * Authentication is provided by the SSL layer supplying the client certificate. 048 * All we check is that it is present. Expects 049 * to be run by a GenericSecurityRealm (doesn't work on its own). 050 * 051 * This login module checks security credentials so the lifecycle methods must return true to indicate success 052 * or throw LoginException to indicate failure. 053 * 054 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $ 055 */ 056 public class CertificateChainLoginModule implements LoginModule { 057 private static Log log = LogFactory.getLog(CertificateChainLoginModule.class); 058 059 // Note: If this LoginModule supports any options, the Collections.EMPTY_LIST in the following should be 060 // replaced with the list of supported options for e.g. Arrays.asList(option1, option2, ...) etc. 061 public final static List<String> supportedOptions = Collections.unmodifiableList(Collections.EMPTY_LIST); 062 private Subject subject; 063 private CallbackHandler handler; 064 private X500Principal principal; 065 private boolean loginSucceeded; 066 private final Set<Principal> allPrincipals = new HashSet<Principal>(); 067 068 public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { 069 this.subject = subject; 070 this.handler = callbackHandler; 071 for(Object option: options.keySet()) { 072 if(!supportedOptions.contains(option) && !JaasLoginModuleUse.supportedOptions.contains(option) 073 && !WrappingLoginModule.supportedOptions.contains(option)) { 074 log.warn("Ignoring option: "+option+". Not supported."); 075 } 076 } 077 } 078 079 /** 080 * This LoginModule is not to be ignored. So, this method should never return false. 081 * @return true if authentication succeeds, or throw a LoginException such as FailedLoginException 082 * if authentication fails 083 */ 084 public boolean login() throws LoginException { 085 loginSucceeded = false; 086 Callback[] callbacks = new Callback[1]; 087 088 callbacks[0] = new CertificateChainCallback(); 089 try { 090 handler.handle(callbacks); 091 } catch (IOException ioe) { 092 throw (LoginException) new LoginException().initCause(ioe); 093 } catch (UnsupportedCallbackException uce) { 094 throw (LoginException) new LoginException().initCause(uce); 095 } 096 assert callbacks.length == 1; 097 Certificate[] certificateChain = ((CertificateChainCallback)callbacks[0]).getCertificateChain(); 098 if (certificateChain == null || certificateChain.length == 0) { 099 throw new FailedLoginException(); 100 } 101 if (!(certificateChain[0] instanceof X509Certificate)) { 102 throw new FailedLoginException(); 103 } 104 //TODO actually validate chain 105 principal = ((X509Certificate)certificateChain[0]).getSubjectX500Principal(); 106 107 loginSucceeded = true; 108 return true; 109 } 110 111 /* 112 * @exception LoginException if login succeeded but commit failed. 113 * 114 * @return true if login succeeded and commit succeeded, or false if login failed but commit succeeded. 115 */ 116 public boolean commit() throws LoginException { 117 if(loginSucceeded) { 118 allPrincipals.add(principal); 119 allPrincipals.add(new GeronimoUserPrincipal(principal.getName())); 120 subject.getPrincipals().addAll(allPrincipals); 121 } 122 // Clear out the private state 123 principal = null; 124 125 return loginSucceeded; 126 } 127 128 public boolean abort() throws LoginException { 129 if(loginSucceeded) { 130 // Clear out the private state 131 principal = null; 132 allPrincipals.clear(); 133 } 134 return loginSucceeded; 135 } 136 137 public boolean logout() throws LoginException { 138 // Clear out the private state 139 loginSucceeded = false; 140 principal = null; 141 if(!subject.isReadOnly()) { 142 // Remove principals added by this LoginModule 143 subject.getPrincipals().removeAll(allPrincipals); 144 } 145 allPrincipals.clear(); 146 return true; 147 } 148 149 }