package com.perforce.sso.server; /* Copyright (c) Perforce Software, Inc., 2011-2012. All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 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 PERFORCE SOFTWARE, INC. 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. User contributed content on the Perforce Public Depot is not supported by Perforce, although it may be supported by its author. This applies to all contributions even those submitted by Perforce employees. */ import java.io.BufferedReader; import java.io.InputStreamReader; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.FileAppender; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import org.apache.log4j.varia.NullAppender; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; /** * The server-side SSO trigger. Responsible for * accepting and validating the authentication ticket * passed from the client. * * Messages are generally logged to a file, with the exception of * error messages that the user needs to see. These are * also written to the console. * @author rdefauw * */ public class KerbServer { /** * logger */ private static Logger logger = null; /** * keyword for no log file */ private static final String NO_LOG = "NONE"; /** * keyword for appending to log file */ private static final String LOG_APPEND = "true"; /** * Expected arguments: * <ul> * <li>log file name, set to <code>NONE</code> for no logging * <li>append existing log: can be <code>true</code> or <code>false</code> * <li>user name * <li>Kerberos/AD realm * <li>Kerberos/AD domain controller * <li>Login configuration file * <li>Service name * </ul> * @param args Command line arguments */ public static void main(String[] args) { // check for log arguments if(args.length < 2) { System.out.println("Required logging argument missing"); System.exit(1); } // no log args try { // start logger String logFileName = args[0]; String appendLog = args[1]; if(logFileName.equals(NO_LOG)) { BasicConfigurator.configure(new NullAppender()); } // no logging else { FileAppender fileAppender = new FileAppender(new PatternLayout(PatternLayout.DEFAULT_CONVERSION_PATTERN), logFileName, appendLog.equals(LOG_APPEND)); BasicConfigurator.configure(fileAppender); } // use file log logger = Logger.getLogger(KerbServer.class.getName()); // verify rest of command line if(args.length != 7) { logger.error("Invalid command line: got " + args.length + " arguments"); System.out.println("Invalid command line: got " + args.length + " arguments"); System.exit(1); } // invalid command line String uid = requiredArg(args[2], "User ID"); String realm = requiredArg(args[3], "Realm"); String domainController = requiredArg(args[4], "Domain controller"); String loginConfFile = requiredArg(args[5], "Login configuration file"); String serviceName = requiredArg(args[6], "Perforce service name"); // basic Kerberos properties System.setProperty("java.security.krb5.realm", realm); System.setProperty("java.security.krb5.kdc", domainController); System.setProperty("java.security.auth.login.config", loginConfFile); logger.debug("Set Kerberos properties"); // create a LoginContext based on the entry in the login.conf file LoginContext lc = new LoginContext("ServicePrincipalLoginContext"); // login (effectively populating the Subject) lc.login(); logger.info("Logged in as service principal"); // get the Subject that represents the service Subject serviceSubject = lc.getSubject(); // read ticket from stdin Base64 b64 = new Base64(); byte[] serviceTicket = b64.decode((new BufferedReader(new InputStreamReader(System.in))).readLine()); logger.debug("Read service ticket from file"); // decode ticket ServiceTicketDecoder decoder = new ServiceTicketDecoder(serviceTicket, serviceName); String clientName = (String) Subject.doAs(serviceSubject, decoder); logger.info("Got service ticket for user " + clientName); // make sure ticket is for right user String clientNameBrief = clientName; int index = clientName.indexOf("@"); if(index > 0) { clientNameBrief = clientName.substring(0, index); } // strip out domain name if(false == uid.equals(clientNameBrief)) { logger.error("Ticket is for " + clientNameBrief + ", but request was for " + uid); System.out.println("Ticket is for " + clientNameBrief + ", but request was for " + uid); System.exit(1); } System.exit(0); } // try catch(Exception e) { if(null != logger) { logger.error(e.getMessage(), e); System.out.println(e.getMessage()); } // can use logger else { System.out.println("Unexpected error, probably while starting logging system: " + e.getMessage()); } // no logger System.exit(1); } // catch } // main /** * verifies that a required argument is present */ private static String requiredArg(String input, String paramName) { if(null == input || input.length() < 1) { logger.error(paramName + " is required argument to this program"); System.out.println(paramName + " is required argument to this program"); System.exit(1); } // no input return input; } // requiredArg } // class /** * Responsible for validating the service ticket * @author rdefauw * */ final class ServiceTicketDecoder implements PrivilegedExceptionAction<String> { /** * the service ticket */ protected byte[] serviceTicket; /** * service name */ private final String svcName; /** * logger */ private final Logger logger; /** * ctor: get service ticket and start logger * @param serviceTicket */ public ServiceTicketDecoder(byte[] serviceTicket, String svcName) { // the run() method does not support any arguments, so we pass the service ticket in via the constructor this.serviceTicket = serviceTicket; this.svcName = svcName; this.logger = Logger.getLogger(ServiceTicketDecoder.class.getName()); } // ctor public String run() throws Exception { try { // GSSAPI is generic, but if you give it the following Object ID, // it will decode Kerberos 5 service tickets Oid kerberos5Oid = new Oid("1.2.840.113554.1.2.2"); // create a GSSManager, which will do the work GSSManager gssManager = GSSManager.getInstance(); // tell the GSSManager the Kerberos name of the service (substitute your appropriate names here) GSSName serviceName = gssManager.createName(this.svcName, GSSName.NT_USER_NAME); // get the service's credentials. note that this run() method was called by Subject.doAs(), // so the service's credentials (Service Principal Name and password) are already available in the Subject GSSCredential serviceCredentials = gssManager.createCredential(serviceName, GSSCredential.INDEFINITE_LIFETIME, kerberos5Oid, GSSCredential.ACCEPT_ONLY); this.logger.debug("Created service credentials"); // create a security context for decrypting the service ticket GSSContext gssContext = gssManager.createContext(serviceCredentials); // decrypt the service ticket gssContext.acceptSecContext(this.serviceTicket, 0, this.serviceTicket.length); this.logger.debug("Accepted service ticket"); // get the client name from the decrypted service ticket // note that Active Directory created the service ticket, so we can trust it String clientName = gssContext.getSrcName().toString(); this.logger.debug("got client name " + clientName + " from service ticket"); // clean up the context gssContext.dispose(); // return the authenticated client name return clientName; } // try catch (Exception ex) { throw new PrivilegedActionException(ex); } // catch } // run } // class ServiceTicketDecoder
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 22152 | Randall Fong |
copying files from //guest/perforce_software/sso-p4/java/... to //guest/perforce_software/sso-p4/main/... |
||
//guest/perforce_software/sso-p4/java/KerbServer/src/com/perforce/sso/server/KerbServer.java | |||||
#1 | 12125 | alan_petersen | initial submit of sso-p4 |