blob: 0c8db68639e5741c943fb7fc50d0ffd4103856cb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.server.auth.gss;
import java.util.Objects;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.apache.sshd.server.auth.AbstractUserAuth;
import org.apache.sshd.server.session.ServerSession;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.MessageProp;
import org.ietf.jgss.Oid;
/**
* <p>
* Prototype user authentication handling gssapi-with-mic. Implements <code>HandshakingUserAuth</code> because the
* process involves several steps.
* </p>
*
* <p>
* Several methods are available for overriding in specific circumstances.
* </p>
*/
public class UserAuthGSS extends AbstractUserAuth {
public static final String NAME = UserAuthGSSFactory.NAME;
// Oids for the Kerberos 5 mechanism and principal
public static final Oid KRB5_MECH = createOID("1.2.840.113554.1.2.2");
public static final Oid KRB5_NT_PRINCIPAL = createOID("1.2.840.113554.1.2.2.1");
// The on-going GSS context.
private GSSContext context;
// Identity from context
private String identity;
public UserAuthGSS() {
super(NAME);
}
@Override
protected Boolean doAuth(Buffer buffer, boolean initial) throws Exception {
ServerSession session = getServerSession();
GSSAuthenticator auth = Objects.requireNonNull(session.getGSSAuthenticator(), "No GSSAuthenticator configured");
String user = getUsername();
boolean debugEnabled = log.isDebugEnabled();
if (initial) {
// Get mechanism count from buffer and look for Kerberos 5.
int num = buffer.getInt();
// Protect against malicious or corrupted packets
if ((num < 0) || (num > SshConstants.SSH_REQUIRED_PAYLOAD_PACKET_LENGTH_SUPPORT)) {
log.error("doAuth({}@{}) Illogical OID entries count: {}", user, session, num);
throw new IndexOutOfBoundsException("Illogical OID entries count: " + num);
}
boolean traceEnabled = log.isTraceEnabled();
for (int i = 1; i <= num; i++) {
Oid oid = new Oid(buffer.getBytes());
if (!oid.equals(KRB5_MECH)) {
if (traceEnabled) {
log.trace("doAuth({}@{}) skip OID {}/{}: {}", user, session, i, num, oid);
}
continue;
}
if (debugEnabled) {
log.debug("doAuth({}@{}) found Kerberos 5 after {}/{} OID(s)", user, session, i, num);
}
// Validate initial user before proceeding
if (!auth.validateInitialUser(session, user)) {
return Boolean.FALSE;
}
GSSManager mgr = auth.getGSSManager();
GSSCredential creds = auth.getGSSCredential(mgr);
if (creds == null) {
return Boolean.FALSE;
}
context = mgr.createContext(creds);
// Send the matching mechanism back to the client
byte[] out = oid.getDER();
Buffer b = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST, out.length + Integer.SIZE);
b.putBytes(out);
session.writePacket(b);
return null;
}
// No matching mechanism found
return Boolean.FALSE;
} else {
int msg = buffer.getUByte();
if (!((msg == SshConstants.SSH_MSG_USERAUTH_INFO_RESPONSE)
|| ((msg == SshConstants.SSH_MSG_USERAUTH_GSSAPI_MIC) && context.isEstablished()))) {
throw new SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR,
"Packet not supported by user authentication method: " + SshConstants.getCommandMessageName(msg));
}
if (debugEnabled) {
log.debug("doAuth({}@{}) In krb5.next: msg = {}", user, session, SshConstants.getCommandMessageName(msg));
}
// If the context is established, this must be a MIC message
if (context.isEstablished()) {
if (msg != SshConstants.SSH_MSG_USERAUTH_GSSAPI_MIC) {
return Boolean.FALSE;
}
// Make the MIC message so the token can be verified
Buffer msgbuf = new ByteArrayBuffer();
msgbuf.putBytes(ValidateUtils.checkNotNullAndNotEmpty(session.getSessionId(), "No current session ID"));
msgbuf.putByte(SshConstants.SSH_MSG_USERAUTH_REQUEST);
msgbuf.putString(getUsername());
msgbuf.putString(getService());
msgbuf.putString(getName());
byte[] msgbytes = msgbuf.getCompactData();
byte[] inmic = buffer.getBytes();
try {
context.verifyMIC(inmic, 0, inmic.length, msgbytes, 0, msgbytes.length, new MessageProp(false));
if (debugEnabled) {
log.debug("doAuth({}@{}) MIC verified", getUsername(), session);
}
return Boolean.TRUE;
} catch (GSSException e) {
if (debugEnabled) {
log.debug("doAuth({}@{}) GSS verification {} error: {}",
user, session, e.getClass().getSimpleName(), e.getMessage());
}
return Boolean.FALSE;
}
} else {
// Not established - new token to process
byte[] tok = buffer.getBytes();
byte[] out = context.acceptSecContext(tok, 0, tok.length);
boolean established = context.isEstablished();
// Validate identity if context is now established
if (established && (identity == null)) {
GSSName srcName = context.getSrcName();
identity = srcName.toString();
if (debugEnabled) {
log.debug("doAuth({}@{}) GSS identity is {}", user, session, identity);
}
if (!auth.validateIdentity(session, identity)) {
return Boolean.FALSE;
}
}
// Send return token if necessary
if (NumberUtils.length(out) > 0) {
Buffer b = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_INFO_RESPONSE, out.length + Integer.SIZE);
b.putBytes(out);
session.writePacket(b);
return null;
} else {
return established;
}
}
}
}
/**
* Free any system resources used by the module.
*/
@Override
public void destroy() {
if (context != null) {
try {
context.dispose();
} catch (GSSException e) {
if (log.isDebugEnabled()) {
log.debug("Failed ({}) to dispose of context: {}", e.getClass().getSimpleName(), e.getMessage());
}
} finally {
context = null;
}
}
}
/**
* Utility to construct an Oid from a string, ignoring the annoying exception.
*
* @param rep The string form
* @return The Oid
*/
public static Oid createOID(String rep) {
try {
return new Oid(rep);
} catch (GSSException e) {
// won't happen
return null;
}
}
}