Commit f2993ece authored by Tigran Mkrtchyan's avatar Tigran Mkrtchyan
Browse files

rpc: gss: workaround linux kernel bug

code cleanup. Validate RPC header on GSS_DATA and GSS_DESTROY.
use login service for map kerberos principal to a local user.
parent 65f5344d
......@@ -40,6 +40,10 @@ public class RpcAuthVerifier implements XdrAble {
return _type;
}
public byte[] getBody() {
return _body;
}
public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException {
_type = xdr.xdrDecodeInt();
_body = xdr.xdrDecodeDynamicOpaque();
......
......@@ -20,11 +20,13 @@ import com.sun.grizzly.Context;
import com.sun.grizzly.ProtocolFilter;
import com.google.common.primitives.Ints;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dcache.utils.Bytes;
import org.dcache.xdr.OncRpcException;
import org.dcache.xdr.RpcAuthError;
import org.dcache.xdr.RpcAuthException;
import org.dcache.xdr.RpcAuthStat;
import org.dcache.xdr.RpcAuthType;
import org.dcache.xdr.RpcAuthVerifier;
......@@ -34,6 +36,7 @@ import org.dcache.xdr.RpcProtocolFilter;
import org.dcache.xdr.RpcRejectStatus;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.MessageProp;
/**
......@@ -82,30 +85,32 @@ public class GssProtocolFilter implements ProtocolFilter {
boolean hasContext = false;
try {
RpcAuthGss authGss = (RpcAuthGss) call.getCredential();
RpcGssContext cred = _gssSessionManager.getCredential(authGss);
if (cred == null) {
throw new RpcAuthException("No context found",
new RpcAuthError(RpcAuthStat.RPCSEC_GSS_CTXPROBLEM));
}
GSSContext gssContext = cred.getContext();
GSSContext gssContext = null;
int _sequence = authGss.getSequence();
switch (authGss.getProc()) {
case GssProc.RPCSEC_GSS_INIT:
UUID uuid = UUID.randomUUID();
byte[] handle = new byte[16];
Bytes.putLong(handle, 0, uuid.getLeastSignificantBits());
Bytes.putLong(handle, 8, uuid.getMostSignificantBits());
gssContext = _gssSessionManager.createContext(handle);
authGss.setHandle(handle);
// fall through
case GssProc.RPCSEC_GSS_CONTINUE_INIT:
if(gssContext == null)
gssContext = _gssSessionManager.getContext(authGss.getHandle());
GSSINITargs gssArgs = new GSSINITargs();
GSSINITres res = new GSSINITres();
call.retrieveCall(gssArgs);
byte[] inToken = gssArgs.getToken();
byte[] outToken = gssContext.acceptSecContext(inToken, 0, inToken.length);
res.setHandle(cred.getHandle());
res.setGssMajor(cred.getContext().isEstablished() ? COMPLETE : CONTINUE_NEEDED);
res.setHandle(authGss.getHandle());
res.setGssMajor(gssContext.isEstablished() ? COMPLETE : CONTINUE_NEEDED);
res.setGssMinor(0);
res.setToken(outToken);
if (gssContext.isEstablished()) {
// FIXME: hard coded number
_sequence = 2;
_sequence = 128;
res.setSequence(_sequence);
byte[] crc = Ints.toByteArray(_sequence);
crc = gssContext.getMIC(crc, 0, 4, new MessageProp(false));
......@@ -114,15 +119,23 @@ public class GssProtocolFilter implements ProtocolFilter {
call.reply(res);
break;
case GssProc.RPCSEC_GSS_DESTROY:
cred.getContext().dispose();
gssContext = _gssSessionManager.destroyContext(authGss.getHandle());
validateVerifier(authGss, gssContext);
gssContext.dispose();
break;
case GssProc.RPCSEC_GSS_DATA:
_log.log(Level.FINE, "RPCGSS_SEC: {0}",gssContext.getSrcName());
gssContext = _gssSessionManager.getEstablishedContext(authGss.getHandle());
validateVerifier(authGss, gssContext);
GSSName sourceName = gssContext.getSrcName();
authGss.getSubject()
.getPrincipals()
.addAll(_gssSessionManager.subjectOf(sourceName).getPrincipals());
_log.log(Level.FINE, "RPCGSS_SEC: {0}",sourceName);
byte[] crc = Ints.toByteArray(authGss.getSequence());
crc = gssContext.getMIC(crc, 0, 4, new MessageProp(false));
authGss.setVerifier(new RpcAuthVerifier(authGss.type(), crc));
context.setAttribute(RpcProtocolFilter.RPC_CALL,
new RpcGssCall(call, cred.getContext(), new MessageProp(false)));
new RpcGssCall(call, gssContext, new MessageProp(false)));
hasContext = true;
}
......@@ -142,6 +155,22 @@ public class GssProtocolFilter implements ProtocolFilter {
return hasContext;
}
/**
* According to rfc2203 verifier should contain the checksum of the RPC header
* up to and including the credential.
*
* @param auth
* @param context gss context
* @throws GSSException if cant validate the checksum
*/
private void validateVerifier(RpcAuthGss auth, GSSContext context) throws GSSException {
ByteBuffer header = auth.getHeader();
byte[] bb = new byte[header.remaining()];
header.get(bb);
context.verifyMIC(auth.getVerifier().getBody(), 0, auth.getVerifier().getBody().length,
bb, 0, bb.length, new MessageProp(false));
}
@Override
public boolean postExecute(Context cntxt) throws IOException {
return true;
......
......@@ -16,11 +16,10 @@
*/
package org.dcache.xdr.gss;
import java.security.Principal;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import org.dcache.chimera.nfs.v4.NfsLoginService;
import org.dcache.utils.Opaque;
......@@ -28,6 +27,7 @@ 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.Oid;
public class GssSessionManager {
......@@ -46,40 +46,38 @@ public class GssSessionManager {
krb5Mechanism, GSSCredential.ACCEPT_ONLY);
_loginService = loginService;
}
private final Map<Opaque, RpcGssContext> sessions = new ConcurrentHashMap<Opaque, RpcGssContext>();
private final Map<Opaque, GSSContext> sessions = new ConcurrentHashMap<Opaque, GSSContext>();
public RpcGssContext getCredential(RpcAuthGss gssAuth) throws GSSException {
public GSSContext createContext(byte[] handle) throws GSSException {
GSSContext context = gManager.createContext(_serviceCredential);
sessions.put(new Opaque(handle), context);
return context;
}
RpcGssContext cred;
public GSSContext getContext(byte[] handle) throws GSSException {
GSSContext context = sessions.get(new Opaque(handle));
if(context == null) {
throw new GSSException(GSSException.NO_CONTEXT);
}
return context;
}
public GSSContext getEstablishedContext(byte[] handle) throws GSSException {
GSSContext context = getContext(handle);
if (!context.isEstablished()) {
throw new GSSException(GSSException.NO_CONTEXT);
}
return context;
}
switch (gssAuth.getProc()) {
case GssProc.RPCSEC_GSS_INIT:
_log.fine("RPCSEC_GSS_INIT");
UUID id = UUID.randomUUID();
GSSContext context = gManager.createContext(_serviceCredential);
cred = new RpcGssContext(id.toString().getBytes(), context);
sessions.put(new Opaque(cred.getHandle()), cred);
break;
case GssProc.RPCSEC_GSS_DESTROY:
_log.fine("RPCSEC_GSS_DESTROY");
cred = sessions.remove(new Opaque(gssAuth.getHandle()));
break;
case GssProc.RPCSEC_GSS_CONTINUE_INIT:
_log.fine("RPCSEC_GSS_CONTINUE_INIT");
cred = sessions.get(new Opaque(gssAuth.getHandle()));
break;
case GssProc.RPCSEC_GSS_DATA:
_log.fine("RPCSEC_GSS_DATA");
cred = sessions.get(new Opaque(gssAuth.getHandle()));
if(cred == null) {
throw new GSSException(GSSException.NO_CONTEXT);
}
Principal principal = new KerberosPrincipal(cred.getContext().getSrcName().toString());
gssAuth.getSubject().getPrincipals().addAll(_loginService.login(principal).getPrincipals());
break;
default:
throw new RuntimeException("Invalid GssProc: " + gssAuth.getProc());
public GSSContext destroyContext(byte[] handle) throws GSSException {
GSSContext context = sessions.remove(new Opaque(handle));
if(!context.isEstablished()) {
throw new GSSException(GSSException.NO_CONTEXT);
}
return cred;
return context;
}
public Subject subjectOf(GSSName name) {
return _loginService.login( new KerberosPrincipal(name.toString()));
}
}
......@@ -17,12 +17,18 @@
package org.dcache.xdr.gss;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import org.dcache.xdr.OncRpcException;
import org.dcache.xdr.RpcAuth;
import org.dcache.xdr.RpcAuthError;
import org.dcache.xdr.RpcAuthException;
import org.dcache.xdr.RpcAuthStat;
import org.dcache.xdr.RpcAuthType;
import org.dcache.xdr.RpcAuthVerifier;
import org.dcache.xdr.Xdr;
import org.dcache.xdr.XdrAble;
import org.dcache.xdr.XdrDecodingStream;
import org.dcache.xdr.XdrEncodingStream;
......@@ -38,6 +44,8 @@ public class RpcAuthGss implements RpcAuth, XdrAble {
private int _sequence;
private int _service;
private byte[] _handle;
private ByteBuffer _header;
private Subject _subject = new Subject();
public byte[] getHandle() {
......@@ -95,13 +103,51 @@ public class RpcAuthGss implements RpcAuth, XdrAble {
return _sequence;
}
/**
* Get a read-only ByteBuffer containing RPC header including credential.
*/
ByteBuffer getHeader() {
return _header.asReadOnlyBuffer();
}
public void xdrDecode(XdrDecodingStream xdr) throws OncRpcException, IOException {
int len = xdr.xdrDecodeInt();
_header = ((Xdr) xdr).body().duplicate();
/*
* header size is RPC header + credential.
*
* rpc header is 7 int32: xid type rpcversion prog vers proc auth_flavour
* credential is 1 int32 + it's value : len + opaque
*
* set position to the beginning of rpc message and limit to the end of credential.
*/
_header.limit( _header.position() + len);
_header.position( _header.position() - 8*4);
_version = xdr.xdrDecodeInt();
_proc = xdr.xdrDecodeInt();
_sequence = xdr.xdrDecodeInt();
_service = xdr.xdrDecodeInt();
_handle = xdr.xdrDecodeDynamicOpaque();
{
/*
* workaround bug in linux kernel implementation:
* sometimes linux ( as of 3.0.0-rc3 ) sends crap instead of verifier.
*/
ByteBuffer b = ((Xdr) xdr).body().slice();
b.order(ByteOrder.BIG_ENDIAN);
if (b.remaining() < 4) {
throw new RpcAuthException("bad verifier (seal broken)", new RpcAuthError(RpcAuthStat.AUTH_BADVERF));
}
int verifierSize = b.getInt();
if (verifierSize < 0 || verifierSize > b.remaining()) {
throw new RpcAuthException("bad verifier (seal broken)", new RpcAuthError(RpcAuthStat.AUTH_BADVERF));
}
}
_verifier.xdrDecode(xdr);
}
......
/*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This library 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 Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program (see the file COPYING.LIB for more
* details); if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.dcache.xdr.gss;
import org.ietf.jgss.GSSContext;
public class RpcGssContext {
private final byte[] _handle;
private final GSSContext _context;
public RpcGssContext(byte[] handle, GSSContext context) {
_handle = handle;
_context = context;
}
public GSSContext getContext() {
return _context;
}
public byte[] getHandle() {
return _handle;
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment