/******************************************************************************/
/* */
/* X r d S e c P r o t e c t . c c */
/* */
/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */
/* Produced by Andrew Hanushevsky for Stanford University under contract */
/* DE-AC02-76-SFO0515 with the Department of Energy */
/* */
/* This file is part of the XRootD software suite. */
/* */
/* XRootD is free software: you can redistribute it and/or modify it under */
/* the terms of the GNU Lesser General Public License as published by the */
/* Free Software Foundation, either version 3 of the License, or (at your */
/* option) any later version. */
/* */
/* XRootD 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 Lesser General Public */
/* License for more details. */
/* */
/* You should have received a copy of the GNU Lesser General Public License */
/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
/* COPYING (GPL license). If not, see . */
/* */
/* The copyright holder's institutional names and contributor's names may not */
/* be used to endorse or promote products derived from this software without */
/* specific prior written permission of the institution or contributor. */
/******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#ifdef __APPLE__
#define COMMON_DIGEST_FOR_OPENSSL
#include "CommonCrypto/CommonDigest.h"
#else
#include "openssl/sha.h"
#endif
#include "XrdVersion.hh"
#include "XProtocol/XProtocol.hh"
#include "XrdSec/XrdSecInterface.hh"
#include "XrdSec/XrdSecProtect.hh"
#include "XrdSec/XrdSecProtector.hh"
#include "XrdSys/XrdSysAtomics.hh"
#include "XrdSys/XrdSysPlatform.hh"
#include "XrdSys/XrdSysPthread.hh"
/******************************************************************************/
/* S t r u c t X r d S e c R e q */
/******************************************************************************/
namespace XrdSecProtection
{
struct XrdSecReq
{
SecurityRequest secReq;
unsigned char secSig; // The encrypted hash follows starting here
};
}
using namespace XrdSecProtection; // Fix warnings from slc5 compiler!
/******************************************************************************/
/* C l a s s X r d S e c V e c */
/******************************************************************************/
namespace
{
class XrdSecVec
{
public:
char Vec[XrdSecProtectParms::secFence-1][kXR_REQFENCE-kXR_auth];
XrdSecVec(int arg, ...)
{va_list ap;
int reqCode, sVal;
memset(Vec, 0, sizeof(Vec));
va_start(ap, arg);
reqCode = va_arg(ap, int);
while(reqCode >= kXR_auth && reqCode < kXR_REQFENCE)
{for (int i=0; i < (int)XrdSecProtectParms::secFence-1; i++)
{sVal = va_arg(ap, int);
Vec[i][reqCode-kXR_auth] = static_cast(sVal);
}
reqCode = va_arg(ap, int);
}
}
~XrdSecVec() {}
};
}
/******************************************************************************/
/* S e c u r i t y T a b l e */
/******************************************************************************/
namespace
{
XrdSecVec secTable(0,
// Compatible Standard Intense Pedantic
kXR_admin, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded,
kXR_auth, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore,
kXR_bind, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded,
kXR_chmod, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded,
kXR_close, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded,
kXR_decrypt, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore,
kXR_dirlist, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded,
kXR_endsess, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded,
kXR_getfile, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded,
kXR_locate, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded,
kXR_login, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore,
kXR_mkdir, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded,
kXR_mv, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded,
kXR_open, kXR_signLikely, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded,
kXR_ping, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore,
kXR_prepare, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded,
kXR_protocol, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore,
kXR_putfile, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded,
kXR_query, kXR_signIgnore, kXR_signIgnore, kXR_signLikely, kXR_signNeeded,
kXR_read, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded,
kXR_readv, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded,
kXR_rm, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded,
kXR_rmdir, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded,
kXR_set, kXR_signLikely, kXR_signLikely, kXR_signNeeded, kXR_signNeeded,
kXR_sigver, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore,
kXR_stat, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded,
kXR_statx, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded,
kXR_sync, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded,
kXR_truncate, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded,
kXR_verifyw, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded,
kXR_write, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded,
0);
}
/******************************************************************************/
/* Private: G e t S H A 2 */
/******************************************************************************/
bool XrdSecProtect::GetSHA2(unsigned char *hBuff, struct iovec *iovP, int iovN)
{
SHA256_CTX sha256;
// Initialize the hash calculattion
//
if (0 == SHA256_Init(&sha256)) return false;
// Go through the iovec updating the hash
//
for (int i = 0; i < iovN; i++)
{if (1 != SHA256_Update(&sha256, iovP[i].iov_base, iovP[i].iov_len))
return false;
}
// Compute final hash and return result
//
return (1 == SHA256_Final(hBuff, &sha256));
}
/******************************************************************************/
/* Private: S c r e e n */
/******************************************************************************/
bool XrdSecProtect::Screen(ClientRequest &thereq)
{
static const int rwOpen = kXR_delete|kXR_new|kXR_open_apnd|kXR_open_updt;
kXR_unt16 reqCode = ntohs(thereq.header.requestid);
char theLvl;
// Validate the request code. Invalid codes are never secured
//
if (reqCode < kXR_auth || reqCode >= kXR_REQFENCE || !secVec) return false;
// Get the security level
//
theLvl = secVec[reqCode-kXR_auth];
// If we need not secure this or we definitely do then return result
//
if (theLvl == kXR_signIgnore) return false;
if (theLvl != kXR_signLikely) return true;
// Security is conditional based on open() trying to modify something.
//
if (reqCode == kXR_open)
{kXR_int16 opts = ntohs(thereq.open.options);
return (opts & rwOpen) != 0;
}
// Security is conditional based on query() trying to modify something.
//
if (reqCode == kXR_query)
{short qopt = (short)ntohs(thereq.query.infotype);
switch(qopt)
{case kXR_QStats: return false;
case kXR_Qcksum: return false;
case kXR_Qckscan: return false;
case kXR_Qconfig: return false;
case kXR_Qspace: return false;
case kXR_Qxattr: return false;
case kXR_Qopaque:
case kXR_Qopaquf: return true;
case kXR_Qopaqug: return true;
default: return false;
}
}
// Security is conditional based on set() trying to modify something.
//
if (reqCode == kXR_set) return thereq.set.modifier != 0;
// At this point we force security as we don't understand this code
//
return true;
}
/******************************************************************************/
/* S e c u r e */
/******************************************************************************/
int XrdSecProtect::Secure(SecurityRequest *&newreq,
ClientRequest &thereq,
const char *thedata)
{
static const ClientSigverRequest initSigVer = {{0,0}, htons(kXR_sigver),
0, kXR_secver_0, 0, 0,
kXR_SHA256, {0, 0, 0}, 0
};
struct buffHold {XrdSecReq *P;
XrdSecBuffer *bP;
buffHold() : P(0), bP(0) {}
~buffHold() {if (P) free(P); if (bP) delete bP;}
};
static const int iovNum = 3;
struct iovec iov[iovNum];
buffHold myReq;
kXR_unt64 mySeq;
const char *sigBuff, *payload = thedata;
unsigned char secHash[SHA256_DIGEST_LENGTH];
int sigSize, n, newSize, rc, paysize = 0;
bool nodata = false;
// Generate a new sequence number
//
mySeq = nextSeqno++;
mySeq = htonll(mySeq);
// Determine if we are going to sign the payload and its location
//
if (thereq.header.dlen)
{kXR_unt16 reqid = htons(thereq.header.requestid);
paysize = ntohl(thereq.header.dlen);
if (!payload) payload = ((char *)&thereq) + sizeof(ClientRequest);
if (reqid == kXR_write || reqid == kXR_verifyw) n = (secVerData ? 3 : 2);
else n = 3;
} else n = 2;
// Fill out the iovec
//
iov[0].iov_base = (char *)&mySeq;
iov[0].iov_len = sizeof(mySeq);
iov[1].iov_base = (char *)&thereq;
iov[1].iov_len = sizeof(ClientRequest);
if (n < 3) nodata = true;
else {iov[2].iov_base = (char *)payload;
iov[2].iov_len = paysize;
}
// Compute the hash
//
if (!GetSHA2(secHash, iov, n)) return -EDOM;
// Now encrypt the hash
//
if (edOK)
{rc = authProt->Encrypt((const char *)secHash,sizeof(secHash),&myReq.bP);
if (rc < 0) return rc;
sigSize = myReq.bP->size;
sigBuff = myReq.bP->buffer;
} else {
sigSize = sizeof(secHash);
sigBuff = (char *)secHash;
}
// Allocate a new request object
//
newSize = sizeof(SecurityRequest) + sigSize;
myReq.P = (XrdSecReq *)malloc(newSize);
if (!myReq.P) return -ENOMEM;
// Setup the security request (we only support signing)
//
memcpy(&(myReq.P->secReq), &initSigVer, sizeof(ClientSigverRequest));
memcpy(&(myReq.P->secReq.header.streamid ), thereq.header.streamid,
sizeof(myReq.P->secReq.header.streamid));
memcpy(&(myReq.P->secReq.sigver.expectrid),&thereq.header.requestid,
sizeof(myReq.P->secReq.sigver.expectrid));
myReq.P->secReq.sigver.seqno = mySeq;
if (nodata) myReq.P->secReq.sigver.flags |= kXR_nodata;
myReq.P->secReq.sigver.dlen = htonl(sigSize);
// Append the signature to the request
//
memcpy(&(myReq.P->secSig), sigBuff, sigSize);
// Return pointer to he security request and its size
//
newreq = &(myReq.P->secReq); myReq.P = 0;
return newSize;
}
/******************************************************************************/
/* Private: S e t P r o t e c t i o n */
/******************************************************************************/
void XrdSecProtect::SetProtection(const ServerResponseReqs_Protocol &inReqs)
{
unsigned int lvl, vsz;
// Check for no security, the simlplest case
//
if (inReqs.secvsz == 0 && inReqs.seclvl == 0)
{memset(&myReqs, 0, sizeof(myReqs));
secVec = 0;
secVerData = false;
return;
}
// Precheck the security level
//
lvl = inReqs.seclvl;
if (lvl > kXR_secPedantic) lvl = kXR_secPedantic;
// Perform the default setup (the usual case)
//
secVec = secTable.Vec[lvl-1];
myReqs.seclvl = lvl;
myReqs.secvsz = 0;
myReqs.secver = kXR_secver_0;
myReqs.secopt = inReqs.secopt;
// Set options
//
secVerData = (inReqs.secopt & kXR_secOData) != 0;
// Create a modified vectr if there are overrides
//
if (inReqs.secvsz != 0)
{const ServerResponseSVec_Protocol *urVec = &inReqs.secvec;
memcpy(myVec, secVec, maxRIX);
vsz = inReqs.secvsz;
for (unsigned int i = 0; i < vsz; i++, urVec++)
{if (urVec->reqindx < maxRIX)
{if (urVec->reqsreq > kXR_signNeeded)
myVec[urVec->reqindx] = kXR_signNeeded;
else myVec[urVec->reqindx] = urVec->reqsreq;
}
}
secVec = myVec;
}
}
/******************************************************************************/
/* V e r i f y */
/******************************************************************************/
const char *XrdSecProtect::Verify(SecurityRequest &secreq,
ClientRequest &thereq,
const char *thedata
)
{
struct buffHold {XrdSecBuffer *bP;
buffHold() : bP(0) {}
~buffHold() {if (bP) delete bP;}
};
static const int iovNum = 3;
struct iovec iov[iovNum];
buffHold myReq;
unsigned char *inHash, secHash[SHA256_DIGEST_LENGTH];
int dlen, n, rc;
// First check for replay attacks. The incomming sequence number must be greater
// the previous one we have seen. Since it is in network byte order we can use
// a simple byte for byte compare (no need for byte swapping).
//
if (memcmp(&lastSeqno, &secreq.sigver.seqno, sizeof(lastSeqno)) >= 0)
return "Incorrect signature sequence";
// Do basic verification for this request
//
if (memcmp(secreq.header.streamid, thereq.header.streamid,
sizeof(secreq.header.streamid)))
return "Signature streamid mismatch";
if (secreq.sigver.expectrid != thereq.header.requestid)
return "Signature requestid mismatch";
if (secreq.sigver.version != kXR_secver_0)
return "Unsupported signature version";
if ((secreq.sigver.crypto & kXR_HashMask) != kXR_SHA256)
return "Unsupported signature hash";
if (secreq.sigver.crypto & kXR_rsaKey)
return "Unsupported signature key";
// Now get the hash information
//
dlen = ntohl(secreq.header.dlen);
inHash = ((unsigned char *)&secreq)+sizeof(SecurityRequest);
// Now decrypt the hash
//
if (edOK)
{rc = authProt->Decrypt((const char *)inHash, dlen, &myReq.bP);
if (rc < 0) return strerror(-rc);
if (myReq.bP->size != (int)sizeof(secHash))
return "Invalid signature hash length";
inHash = (unsigned char *)myReq.bP->buffer;
} else {
if (dlen != (int)sizeof(secHash))
return "Invalid signature hash length";
}
// Fill out the iovec to recompute the hash
//
iov[0].iov_base = (char *)&secreq.sigver.seqno;
iov[0].iov_len = sizeof(secreq.sigver.seqno);
iov[1].iov_base = (char *)&thereq;
iov[1].iov_len = sizeof(ClientRequest);
if (thereq.header.dlen == 0 || secreq.sigver.flags & kXR_nodata) n = 2;
else {iov[2].iov_base = (char *)thedata;
iov[2].iov_len = ntohl(thereq.header.dlen);
n = 3;
}
// Compute the hash
//
if (!GetSHA2(secHash, iov, n))
return "Signature hash computation failed";
// Compare this hash with the hash we were given
//
if (memcmp(secHash, inHash, sizeof(secHash)))
return "Signature hash mismatch";
// This request has been verified (update the seqno)
//
lastSeqno = secreq.sigver.seqno;
return 0;
}