XrdSecProtocolgsi::stackCRL; // Stack of CRL in use
//
// GMAP control vars
time_t XrdSecProtocolgsi::lastGMAPCheck = -1; // Time of last check
XrdSysMutex XrdSecProtocolgsi::mutexGMAP; // Mutex to control GMAP reloads
//
// Running options / settings
int XrdSecProtocolgsi::Debug = 0; // [CS] Debug level
bool XrdSecProtocolgsi::Server = 1; // [CS] If server mode
int XrdSecProtocolgsi::TimeSkew = 300; // [CS] Allowed skew in secs for time stamps
//
// Debug an tracing
XrdSysError XrdSecProtocolgsi::eDest(0, "secgsi_");
XrdSysLogger XrdSecProtocolgsi::Logger;
XrdOucTrace *XrdSecProtocolgsi::GSITrace = 0;
XrdOucTrace *gsiTrace = 0;
/******************************************************************************/
/* S t a t i c F u n c t i o n s */
/******************************************************************************/
//_____________________________________________________________________________
static const char *ClientStepStr(int kclt)
{
// Return string with client step
static const char *ukn = "Unknown";
kclt = (kclt < 0) ? 0 : kclt;
kclt = (kclt > kXGC_reserved) ? 0 : kclt;
kclt = (kclt >= kXGC_certreq) ? (kclt - kXGC_certreq + 1) : kclt;
if (kclt < 0 || kclt > (kXGC_reserved - kXGC_certreq + 1))
return ukn;
else
return gsiClientSteps[kclt];
}
//_____________________________________________________________________________
static const char *ServerStepStr(int ksrv)
{
// Return string with server step
static const char *ukn = "Unknown";
ksrv = (ksrv < 0) ? 0 : ksrv;
ksrv = (ksrv > kXGS_reserved) ? 0 : ksrv;
ksrv = (ksrv >= kXGS_init) ? (ksrv - kXGS_init + 1) : ksrv;
if (ksrv < 0 || ksrv > (kXGS_reserved - kXGS_init + 1))
return ukn;
else
return gsiServerSteps[ksrv];
}
/******************************************************************************/
/* D u m p o f H a n d s h a k e v a r i a b l e s */
/******************************************************************************/
//_____________________________________________________________________________
void gsiHSVars::Dump(XrdSecProtocolgsi *p)
{
// Dump content
EPNAME("HSVars::Dump");
PRINT("----------------------------------------------------------------");
PRINT("protocol instance: "<TimeStamp = time(0);
// Local handshake variables
hs->Tty = (isatty(0) == 0 || isatty(1) == 0) ? 0 : 1;
} else {
PRINT("could not create handshake vars object");
}
// Set host name and address
// The hostname is critical for the GSI protocol; it must match the potential
// names on the remote EEC. We default to the hostname requested by the user to
// the client (or proxy). However, as we may have been redirected to an IP
// address instead of an actual hostname, we must fallback to a reverse DNS lookup.
// As of time of testing (June 2018), EOS will redirect to an IP address to handle
// metadata commands and rely on the reverse DNS lookup for GSI security to function.
// Hence, this fallback likely needs to be kept for some time.
//
// We provide servers a switch and clients an environment variable to override all
// usage of DNS (processed on XrdSecProtocolgsiInit).
// Default is to fallback to DNS lookups in limited
// cases for backward compatibility.
expectedHost = NULL;
if (TrustDNS) {
if (!hname || !XrdNetAddrInfo::isHostName(hname)) {
Entity.host = strdup(endPoint.Name(""));
} else {
// At this point, hname still may possibly be a non-qualified domain name.
// If there is a '.' character, then we assume it is a qualified domain name --
// otherwise, we use DNS.
//
// NOTE: We can definitively test whether this is a qualified domain name by
// simply appending a '.' to `hname` and performing a lookup. However, this
// causes DNS to be used by every lookup - meaning we rely on the security
// of DNS for all cases; we want to avoid this.
if (strchr(hname, '.')) {
// We have a valid hostname; proceed.
Entity.host = strdup(hname);
} else {
XrdNetAddr xrd_addr;
char canonname[256];
if (!xrd_addr.Set(hname) || (xrd_addr.Format(canonname, 256, XrdNetAddrInfo::fmtName, XrdNetAddrInfo::noPort) <= 0)) {
Entity.host = strdup(hname);
} else {
Entity.host = strdup(canonname);
}
}
}
} else {
// We have been told via environment variable to not trust DNS; use the exact
// hostname provided by the user.
// char dnBuff[256];
// getdomainname(dnBuff, sizeof(dnBuff));
Entity.host = strdup(hname);
expectedHost = strdup(hname);
}
epAddr = endPoint;
Entity.addrInfo = &epAddr;
// Init session variables
sessionCF = 0;
sessionKey = 0;
bucketKey = 0;
sessionMD = 0;
sessionKsig = 0;
sessionKver = 0;
sessionKver = 0;
proxyChain = 0;
useIV = false;
//
// Notify, if required
DEBUG("constructing: host: "<< Entity.host);
DEBUG("p: "<Parms = new XrdSutBuffer(p.c_str(), p.length());
}
}
// We are done
String vers = Version;
vers.insert('.',vers.length()-2);
vers.insert('.',vers.length()-5);
DEBUG("object created: v"< -1) ? opt.debug : Debug;
// We must have the tracing object at this point
// (initialized in XrdSecProtocolgsiInit)
if (!gsiTrace) {
ErrF(erp,kGSErrInit,"tracing object (gsiTrace) not initialized! cannot continue");
return Parms;
}
// Set debug mask ... also for auxilliary libs
int trace = 0, traceSut = 0, traceCrypto = 0;
if (Debug >= 3) {
trace = cryptoTRACE_Dump;
traceSut = sutTRACE_Dump;
traceCrypto = cryptoTRACE_Dump;
GSITrace->What = TRACE_ALL;
} else if (Debug >= 2) {
trace = cryptoTRACE_Debug;
traceSut = sutTRACE_Debug;
traceCrypto = cryptoTRACE_Debug;
GSITrace->What = TRACE_Debug;
GSITrace->What |= TRACE_Authen;
} else if (Debug >= 1) {
trace = cryptoTRACE_Debug;
traceSut = sutTRACE_Notify;
traceCrypto = cryptoTRACE_Notify;
GSITrace->What = TRACE_Debug;
}
// ... also for auxilliary libs
XrdSutSetTrace(traceSut);
XrdCryptoSetTrace(traceCrypto);
// Name hashing algorithm compatibility
if (opt.hashcomp == 0) HashCompatibility = 0;
//
// Operation mode
Server = (opt.mode == 's');
//
// CA verification level
//
// 0 do not verify
// 1 verify if self-signed; warn if not
// 2 verify in all cases; fail if not possible
//
if (opt.ca >= 0 && opt.ca <= 2)
CACheck = opt.ca;
DEBUG("option CACheck: "< 0) {
if (XrdSutExpand(dp) == 0) {
if (stat(dp.c_str(),&st) == -1) {
if (errno == ENOENT) {
ErrF(erp,kGSErrError,"CA directory non existing:",dp.c_str());
PRINT(erp->getErrText());
} else {
ErrF(erp,kGSErrError,"cannot stat CA directory:",dp.c_str());
PRINT(erp->getErrText());
}
} else {
if (!(dp.endswith('/'))) dp += '/';
if (!(CAtmp.endswith(','))) CAtmp += ',';
CAtmp += dp;
}
} else {
PRINT("Warning: could not expand: "< 0)
CAdir = CAtmp;
}
DEBUG("using CA dir(s): "<= 10) {
CRLDownload = 1;
opt.crl %= 10;
}
if (opt.crl >= 0 && opt.crl <= 3)
CRLCheck = opt.crl;
DEBUG("option CRLCheck: "< 0) {
if (XrdSutExpand(dp) == 0) {
if (stat(dp.c_str(),&st) == -1) {
if (errno == ENOENT) {
ErrF(erp,kGSErrError,"CRL directory non existing:",dp.c_str());
PRINT(erp->getErrText());
} else {
ErrF(erp,kGSErrError,"cannot stat CRL directory:",dp.c_str());
PRINT(erp->getErrText());
}
} else {
if (!(dp.endswith('/'))) dp += '/';
if (!(CRLtmp.endswith(','))) CRLtmp += ',';
CRLtmp += dp;
}
} else {
PRINT("Warning: could not expand: "< 0)
CRLdir = CRLtmp;
} else {
// Use CAdir
CRLdir = CAdir;
}
if (CRLCheck > 0)
DEBUG("using CRL dir(s): "< 0 && ncpt[0] != '-') {
// Try loading
if ((cf = XrdCryptoFactory::GetCryptoFactory(ncpt.c_str()))) {
// Add it to the list
cryptF[ncrypt] = cf;
cryptID[ncrypt] = cf->ID();
cryptName[ncrypt].insert(cf->Name(),0,strlen(cf->Name())+1);
cf->SetTrace(trace);
cf->Notify();
// Ref cipher
if (!(refcip[ncrypt] = cf->Cipher(0,0,0))) {
PRINT("ref cipher for module "<= XrdCryptoMax) {
PRINT("max number of crypto modules ("
<< XrdCryptoMax <<") reached ");
break;
}
if (cryptlist.length()) cryptlist += ":";
cryptlist += ncpt;
if (!cf->HasPaddingSupport()) cryptlist += gNoPadTag;
}
} else {
PRINT("cannot instantiate crypto factory "<getErrText());
return Parms;
}
//
// List of supported / wanted ciphers
if (opt.cipher)
DefCipher = opt.cipher;
// make sure we support all of them
String cip = "";
int from = 0;
while ((from = DefCipher.tokenize(cip, from, ':')) != -1) {
if (cip.length() > 0) {
int i = 0;
for (; i < ncrypt; i++) {
if (!(cryptF[i]->SupportedCipher(cip.c_str()))) {
// Not supported: drop from the list
DEBUG("cipher type not supported ("< 0) {
int i = 0;
for (; i < ncrypt; i++) {
if (!(cryptF[i]->SupportedMsgDigest(md.c_str()))) {
// Not supported: drop from the list
PRINT("MD type not supported ("<getErrText());
return Parms;
}
DEBUG("CA list: "<= 10) {
GMAPuseDNname = 1;
opt.ogmap %= 10;
}
if (opt.ogmap >= 0 && opt.ogmap <= 2)
GMAPOpt = opt.ogmap;
DEBUG("user mapping file option: "< 0) {
// Initialize the GMap service
//
String pars;
if (Debug) pars += "dbg|";
if (opt.gmapto > 0) { pars += "to="; pars += (int)opt.gmapto; }
if (!(servGMap = XrdOucgetGMap(&eDest, GMAPFile.c_str(), pars.c_str()))) {
if (GMAPOpt > 1) {
ErrF(erp,kGSErrError,"error loading grid map file:",GMAPFile.c_str());
PRINT(erp->getErrText());
return Parms;
} else {
NOTIFY("Grid map file: "< 0) {
if (!(GMAPFun = LoadGMAPFun((const char *) opt.gmapfun,
(const char *) opt.gmapfunparms))) {
ErrF(erp, kGSErrError, "GMAP plug-in could not be loaded", opt.gmapfun);
PRINT(erp->getErrText());
return Parms;
} else {
hasgmapfun = 1;
}
}
//
// Disable GMAP if neither a grid mapfile nor a GMAP function are available
if (!hasgmap && !hasgmapfun) {
if (GMAPOpt > 1) {
ErrF(erp,kGSErrError,"User mapping required, but neither a grid mapfile"
" nor a mapping function are available");
PRINT(erp->getErrText());
return Parms;
}
GMAPOpt = 0;
}
//
// Authorization function
bool hasauthzfun = 0;
if (opt.authzfun) {
if (!(AuthzFun = LoadAuthzFun((const char *) opt.authzfun,
(const char *) opt.authzfunparms, AuthzCertFmt))) {
ErrF(erp, kGSErrError, "Authz plug-in could not be loaded", opt.authzfun);
PRINT(erp->getErrText());
return Parms;
} else {
hasauthzfun = 1;
// Notify certificate format
if (AuthzCertFmt >= 0 && AuthzCertFmt <= 1) {
const char *ccfmt[] = { "raw", "PEM base64" };
DEBUG("authzfun: proxy certificate format: "< 0) {
AuthzCacheTimeOut = opt.authzto;
DEBUG("grid-map cache entries expire after "< 0 && !hasauthzfun && opt.gmapto > 0) {
GMAPCacheTimeOut = opt.gmapto;
DEBUG("grid-map cache entries expire after "< 0) {
AuthzPxyWhat = opt.authzpxy / 10;
AuthzPxyWhere = opt.authzpxy % 10;
// Some notification
const char *capxy_what = (AuthzPxyWhat == 1) ? "'last proxy only'"
: "'full proxy chain'";
const char *capxy_where = (AuthzPxyWhere == 1) ? "XrdSecEntity.creds"
: "XrdSecEntity.endorsements";
DEBUG("Export proxy for authorization in '"<";
}
PxyReqOpts |= kOptsPxFile;
DEBUG("File template for delegated proxy: "<= 0) ? opt.vomsat : VOMSAttrOpt;
//
// Alternative VOMS extraction function
if (opt.vomsfun) {
if (!(VOMSFun = LoadVOMSFun((const char *) opt.vomsfun,
(const char *) opt.vomsfunparms, VOMSCertFmt))) {
ErrF(erp, kGSErrError, "VOMS plug-in could not be loaded", opt.vomsfun);
PRINT(erp->getErrText());
return Parms;
} else {
// We at least check VOMS attributes if we have a function ...
if (VOMSAttrOpt < 1) VOMSAttrOpt = 1;
// Notify certificate format
if (VOMSCertFmt >= 0 && VOMSCertFmt <= 1) {
const char *ccfmt[] = { "raw", "PEM base64" };
DEBUG("vomsfun: proxy certificate format: "<,c:,ca:
Parms = new char[cryptlist.length()+3+12+certcalist.length()+5];
if (Parms) {
sprintf(Parms,"v:%d,c:%s,ca:%s",
Version,cryptlist.c_str(),certcalist.c_str());
} else {
ErrF(erp,kGSErrInit,"no system resources for 'Parms'");
PRINT(erp->getErrText());
}
// Some notification
DEBUG("available crypto modules: "<
struct passwd *pw = getpwuid(getuid());
if (!pw) {
NOTIFY("WARNING: cannot get user information (uid:"<pw_uid);
}
// Define user certificate file
if (opt.cert) {
String TmpCert = opt.cert;
if (XrdSutExpand(TmpCert) == 0) {
UsrCert = TmpCert;
} else {
PRINT("Could not expand: "< DefBits)
DefBits = opt.bits;
//
// Delegate proxy options
if (opt.dlgpxy > 0) {
PxyReqOpts |= kOptsSigReq;
if (opt.dlgpxy == 2) {
PxyReqOpts |= kOptsFwdPxy;
} else {
PxyReqOpts |= kOptsDlgPxy;
}
}
//
// Define valid CNs for the server certificates; default is null, which means that
// the server CN must be in the form "*/"
if (opt.srvnames)
SrvAllowedNames = opt.srvnames;
//
// Notify
TRACE(Authen, "using certificate file: "< 0) {
SafeFree(Entity.creds);
} else {
Entity.creds = 0;
}
Entity.credslen = 0;
SafeFree(Entity.moninfo);
// Cleanup the handshake variables, if still there
SafeDelete(hs);
// Cleanup any other instance specific to this protocol
SafeDelete(sessionKey); // Session Key (result of the handshake)
SafeDelete(bucketKey); // Bucket with the key in export form
SafeDelete(sessionMD); // Message Digest instance
SafeDelete(sessionKsig); // RSA key to sign
SafeDelete(sessionKver); // RSA key to verify
if (proxyChain) proxyChain->Cleanup(1);
SafeDelete(proxyChain); // Chain with delegated proxies
SafeDelete(expectedHost);
delete this;
}
/******************************************************************************/
/* E n c r y p t i o n R e l a t e d M e t h o d s */
/******************************************************************************/
//_____________________________________________________________________________
int XrdSecProtocolgsi::Encrypt(const char *inbuf, // Data to be encrypted
int inlen, // Length of data in inbuff
XrdSecBuffer **outbuf) // Returns encrypted data
{
// Encrypt data in inbuff and place it in outbuff.
//
// Returns: < 0 Failed, the return value is -errno of the reason. Typically,
// -EINVAL - one or more arguments are invalid.
// -ENOTSUP - encryption not supported by the protocol
// -EOVERFLOW - outbuff is too small to hold result
// -ENOENT - Context not initialized
// = 0 Success, outbuff contains a pointer to the encrypted data.
//
EPNAME("Encrypt");
// We must have a key
if (!sessionKey)
return -ENOENT;
// And something to encrypt
if (!inbuf || inlen <= 0 || !outbuf)
return -EINVAL;
// Regenerate IV
int liv = 0;
char *iv = 0;
if (useIV) {
iv = sessionKey->RefreshIV(liv);
sessionKey->SetIV(liv, iv);
}
// Get output buffer
char *buf = (char *)malloc(sessionKey->EncOutLength(inlen) + liv);
if (!buf)
return -ENOMEM;
// IV at beginning
memcpy(buf, iv, liv);
// Encrypt
int len = sessionKey->Encrypt(inbuf, inlen, buf + liv);
if (len <= 0) {
SafeFree(buf);
return -EINVAL;
}
// Create and fill output buffer
*outbuf = new XrdSecBuffer(buf, len);
// We are done
DEBUG("encrypted buffer has "<MaxIVLength() : 0;
int sz = inlen - liv;
// Get output buffer
char *buf = (char *)malloc(sessionKey->DecOutLength(sz) + liv);
if (!buf)
return -ENOMEM;
// Get and set IV
if (useIV) {
char *iv = new char[liv];
memcpy(iv, inbuf, liv);
sessionKey->SetIV(liv, iv);
delete[] iv;
}
// Decrypt
int len = sessionKey->Decrypt(inbuf + liv, sz, buf);
if (len <= 0) {
SafeFree(buf);
return -EINVAL;
}
// Create and fill output buffer
*outbuf = new XrdSecBuffer(buf, len);
// We are done
DEBUG("decrypted buffer has "<Reset(0);
// Calculate digest
sessionMD->Update(inbuf, inlen);
sessionMD->Final();
// Output length
int lmax = sessionKsig->GetOutlen(sessionMD->Length());
char *buf = (char *)malloc(lmax);
if (!buf)
return -ENOMEM;
// Sign
int len = sessionKsig->EncryptPrivate(sessionMD->Buffer(),
sessionMD->Length(),
buf, lmax);
if (len <= 0) {
SafeFree(buf);
return -EINVAL;
}
// Create and fill output buffer
*outbuf = new XrdSecBuffer(buf, len);
// We are done
DEBUG("signature has "< 0 Failed to verify, signature does not match inbuff data.
//
EPNAME("Verify");
// We must have a PKI and a digest
if (!sessionKver || !sessionMD)
return -ENOENT;
// And something to verify
if (!inbuf || inlen <= 0 || !sigbuf || siglen <= 0)
return -EINVAL;
// Reset digest
sessionMD->Reset(0);
// Calculate digest
sessionMD->Update(inbuf, inlen);
sessionMD->Final();
// Output length
int lmax = sessionKver->GetOutlen(siglen);
char *buf = new char[lmax];
if (!buf)
return -ENOMEM;
// Decrypt signature
int len = sessionKver->DecryptPublic(sigbuf, siglen, buf, lmax);
if (len <= 0) {
delete[] buf;
return -EINVAL;
}
// Verify signature
bool bad = 1;
if (len == sessionMD->Length()) {
if (!strncmp(buf, sessionMD->Buffer(), len)) {
// Signature matches
bad = 0;
DEBUG("signature successfully verified");
}
}
// Cleanup
if (buf) delete[] buf;
// We are done
return ((bad) ? 1 : 0);
}
//_____________________________________________________________________________
int XrdSecProtocolgsi::getKey(char *kbuf, int klen)
{
// Get the current encryption key
//
// Returns: < 0 Failed, returned value if -errno (see Encrypt)
// >= 0 The size of the encyption key. The supplied buffer of length
// size hold the key. If the buffer address is 0, only the
// size of the key is returned.
//
EPNAME("getKey");
// Check if we have to serialize the key
if (!bucketKey) {
// We must have a key for that
if (!sessionKey)
// Invalid call
return -ENOENT;
// Create bucket
bucketKey = sessionKey->AsBucket();
}
// Prepare output now, if we have any
if (bucketKey) {
// If are asked only the size, we are done
if (kbuf == 0)
return bucketKey->size;
// Check the size of the buffer
if (klen < bucketKey->size)
// Too small
return -EOVERFLOW;
// Copy the buffer
memcpy(kbuf, bucketKey->buffer, bucketKey->size);
// We are done
DEBUG("session key exported");
return bucketKey->size;
}
// Key exists but we could export it in bucket format
return -ENOMEM;
}
//_____________________________________________________________________________
int XrdSecProtocolgsi::setKey(char *kbuf, int klen)
{
// Set the current encryption key
//
// Returns: < 0 Failed, returned value if -errno (see Encrypt)
// 0 The new key has been set.
//
EPNAME("setKey");
// Make sur that we can initialize the new key
if (!kbuf || klen <= 0)
// Invalid inputs
return -EINVAL;
if (!sessionCF)
// Invalid context
return -ENOENT;
// Put the buffer key into a bucket
XrdSutBucket *bck = new XrdSutBucket();
if (!bck)
// Cannot get buffer: out-of-resources?
return -ENOMEM;
// Set key buffer
bck->SetBuf(kbuf, klen);
// Init a new cipher from the bucket
XrdCryptoCipher *newKey = sessionCF->Cipher(bck);
if (!newKey) {
SafeDelete(bck);
return -ENOMEM;
}
// Delete current key
SafeDelete(sessionKey);
// Set the new key
sessionKey = newKey;
// Cleanup
SafeDelete(bck);
// Ok
DEBUG("session key update");
return 0;
}
/******************************************************************************/
/* C l i e n t O r i e n t e d F u n c t i o n s */
/******************************************************************************/
/******************************************************************************/
/* g e t C r e d e n t i a l s */
/******************************************************************************/
XrdSecCredentials *XrdSecProtocolgsi::getCredentials(XrdSecParameters *parm,
XrdOucErrInfo *ei)
{
// Query client for the password; remote username and host
// are specified in 'parm'. File '.rootnetrc' is checked.
EPNAME("getCredentials");
// If we are a server the only reason to be here is to get the forwarded
// or saved client credentials
if (srvMode) {
XrdSecCredentials *creds = 0;
if (proxyChain) {
// Export the proxy chain into a bucket
XrdCryptoX509ExportChain_t ExportChain = sessionCF->X509ExportChain();
if (ExportChain) {
XrdSutBucket *bck = (*ExportChain)(proxyChain, 1);
if (bck) {
// We need to duplicate it because XrdSecCredentials uses
// {malloc, free} instead of {new, delete}
char *nbuf = (char *) malloc(bck->size);
if (nbuf) {
memcpy(nbuf, bck->buffer, bck->size);
// Import the buffer in a XrdSecCredentials object
creds = new XrdSecCredentials(nbuf, bck->size);
}
delete bck;
}
}
}
return creds;
}
// Handshake vars container must be initialized at this point
if (!hs)
return ErrC(ei,0,0,0,kGSErrError,
"handshake var container missing","getCredentials");
//
// Nothing to do if buffer is empty
if ((!parm && !hs->Parms) || (parm && (!(parm->buffer) || parm->size <= 0))) {
if (hs->Iter == 0)
return ErrC(ei,0,0,0,kGSErrNoBuffer,"missing parameters","getCredentials");
else
return (XrdSecCredentials *)0;
}
// We support passing the user {proxy, cert, key} paths via Url parameter
char *upp = (ei && ei->getEnv()) ? ei->getEnv()->Get("xrd.gsiusrpxy") : 0;
if (upp) UsrProxy = upp;
upp = (ei && ei->getEnv()) ? ei->getEnv()->Get("xrd.gsiusrcrt") : 0;
if (upp) UsrCert = upp;
upp = (ei && ei->getEnv()) ? ei->getEnv()->Get("xrd.gsiusrkey") : 0;
if (upp) UsrKey = upp;
// Count interations
(hs->Iter)++;
// Update time stamp
hs->TimeStamp = time(0);
// Local vars
int step = 0;
int nextstep = 0;
const char *stepstr = 0;
char *bpub = 0;
int lpub = 0;
String CryptoMod = "";
String Host = "";
String RemID = "";
String Emsg;
String specID = "";
String issuerHash = "";
// Buffer / Bucket related
XrdSutBuffer *bpar = 0; // Global buffer
XrdSutBuffer *bmai = 0; // Main buffer
XrdSutBucket *bck = 0; // Generic bucket
//
// Decode received buffer
bpar = hs->Parms;
if (!bpar && !(bpar = new XrdSutBuffer((const char *)parm->buffer,parm->size)))
return ErrC(ei,0,0,0,kGSErrDecodeBuffer,"global",stepstr);
// Ownership has been transferred
hs->Parms = 0;
//
// Check protocol ID name
if (strcmp(bpar->GetProtocol(),XrdSecPROTOIDENT))
return ErrC(ei,bpar,bmai,0,kGSErrBadProtocol,stepstr);
//
// The step indicates what we are supposed to do
if (!(step = bpar->GetStep())) {
// The first, fake, step
step = kXGS_init;
bpar->SetStep(step);
}
stepstr = ServerStepStr(step);
// Dump, if requested
XrdOucString bmsg;
if (QTRACE(Dump)) {
bmsg.form("IN: bpar: %s", stepstr);
bpar->Dump(bmsg.c_str());
}
//
// Parse input buffer
if (ParseClientInput(bpar, &bmai, Emsg) == -1) {
DEBUG(Emsg<<" CF: "<Dump(bmsg.c_str());
}
}
//
// Version
DEBUG("version run by server: "<< hs->RemVers);
//
// Check random challenge
if (!CheckRtag(bmai, Emsg))
return ErrC(ei,bpar,bmai,0,kGSErrBadRndmTag,Emsg.c_str(),stepstr);
//
// Login name if any
String user(Entity.name);
if (user.length() <= 0) user = getenv("XrdSecUSER");
//
// Now action depens on the step
nextstep = kXGC_none;
XrdCryptoX509 *c = 0;
switch (step) {
case kXGS_init:
//
// Add bucket with cryptomod to the global list
// (This must be always visible from now on)
CryptoMod = hs->CryptoMod;
if (hs->RemVers >= XrdSecgsiVersDHsigned && !(hs->HasPad)) CryptoMod += gNoPadTag;
if (bpar->AddBucket(CryptoMod,kXRS_cryptomod) != 0)
return ErrC(ei,bpar,bmai,0,
kGSErrCreateBucket,XrdSutBuckStr(kXRS_cryptomod),stepstr);
//
// Add bucket with our version to the main list
if (bpar->MarshalBucket(kXRS_version,(kXR_int32)(Version)) != 0)
return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket,
XrdSutBuckStr(kXRS_version),"global",stepstr);
//
// Add our issuer hash
c = hs->PxyChain->Begin();
if (c->type == XrdCryptoX509::kCA) {
issuerHash = c->SubjectHash();
if (HashCompatibility && c->SubjectHash(1)) {
issuerHash += "|"; issuerHash += c->SubjectHash(1); }
} else {
issuerHash = c->IssuerHash();
if (HashCompatibility && c->IssuerHash(1)
&& strcmp(c->IssuerHash(1),c->IssuerHash())) {
issuerHash += "|"; issuerHash += c->IssuerHash(1); }
}
while ((c = hs->PxyChain->Next()) != 0) {
if (c->type != XrdCryptoX509::kCA)
break;
issuerHash = c->SubjectHash();
if (HashCompatibility && c->SubjectHash(1)
&& strcmp(c->IssuerHash(1),c->IssuerHash())) {
issuerHash += "|"; issuerHash += c->SubjectHash(1); }
}
DEBUG("Client issuer hash: " << issuerHash);
if (bpar->AddBucket(issuerHash,kXRS_issuer_hash) != 0)
return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket,
XrdSutBuckStr(kXRS_issuer_hash),stepstr);
//
// Add bucket with our delegate proxy options
if (hs->RemVers >= 10100) {
if (bpar->MarshalBucket(kXRS_clnt_opts,(kXR_int32)(hs->Options)) != 0)
return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket,
XrdSutBuckStr(kXRS_clnt_opts),"global",stepstr);
}
//
nextstep = kXGC_certreq;
break;
case kXGS_cert:
//
// We must have a session cipher at this point
if (!(sessionKey))
return ErrC(ei,bpar,bmai,0,
kGSErrNoCipher,"session cipher",stepstr);
//
// Extract buffer with public info for the cipher agreement
if (!(bpub = sessionKey->Public(lpub)))
return ErrC(ei,bpar,bmai,0,
kGSErrNoPublic,"session",stepstr);
//
// If server supports decoding of signed DH, do sign them
if (hs->RemVers >= XrdSecgsiVersDHsigned) {
bck = new XrdSutBucket(bpub,lpub,kXRS_cipher);
if (sessionKsig) {
// Encrypt client DH public parameters with client private key
if (sessionKsig->EncryptPrivate(*bck) <= 0)
return ErrC(ei,bpar,bmai,0, kGSErrExportPuK,
"encrypting client DH public parameters",stepstr);
} else {
return ErrC(ei,bpar,bmai,0, kGSErrExportPuK,
"client signing key undefined!",stepstr);
}
//
// Add it to the global list
if (bpar->AddBucket(bck) != 0)
return ErrC(ei,bpar,bmai,0, kGSErrAddBucket, "main",stepstr);
//
// Export client public key
XrdOucString cpub;
if (sessionKsig->ExportPublic(cpub) < 0)
return ErrC(ei,bpar,bmai,0, kGSErrExportPuK,
"exporting client public key",stepstr);
// Add it to the global list
if (bpar->UpdateBucket(cpub.c_str(),cpub.length(),kXRS_puk) != 0)
return ErrC(ei,bpar,bmai,0, kGSErrAddBucket,
XrdSutBuckStr(kXRS_puk),"global",stepstr);
} else {
//
// Add it to the global list
if (bpar->UpdateBucket(bpub,lpub,kXRS_puk) != 0)
return ErrC(ei,bpar,bmai,0, kGSErrAddBucket,
XrdSutBuckStr(kXRS_puk),"global",stepstr);
delete[] bpub; // bpub is being duplicated inside of 'UpdateBucket'
}
//
// Add the proxy certificate
bmai->AddBucket(hs->Cbck);
//
// Add login name if any, needed while chosing where to export the proxies
if (user.length() > 0) {
if (bmai->AddBucket(user, kXRS_user) != 0)
return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket,
XrdSutBuckStr(kXRS_user),stepstr);
}
//
nextstep = kXGC_cert;
break;
case kXGS_pxyreq:
//
// If something went wrong, send explanation
if (Emsg.length() > 0) {
if (bmai->AddBucket(Emsg,kXRS_message) != 0)
return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket,
XrdSutBuckStr(kXRS_message),stepstr);
}
//
// Add login name if any, needed while chosing where to export the proxies
if (user.length() > 0) {
if (bmai->AddBucket(user, kXRS_user) != 0)
return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket,
XrdSutBuckStr(kXRS_user),stepstr);
}
//
// The relevant buckets should already be in the buffers
nextstep = kXGC_sigpxy;
break;
default:
return ErrC(ei,bpar,bmai,0, kGSErrBadOpt,stepstr);
}
//
// Serialize and encrypt
if (AddSerialized('c', nextstep, hs->ID,
bpar, bmai, kXRS_main, sessionKey) != 0) {
return ErrC(ei,bpar,bmai,0,
kGSErrSerialBuffer,"main",stepstr);
}
//
// Serialize the global buffer
char *bser = 0;
int nser = bpar->Serialized(&bser,'f');
if (QTRACE(Authen)) {
bmsg.form("OUT: bpar: %s", ClientStepStr(bpar->GetStep()));
bpar->Dump(bmsg.c_str());
bmsg.form("OUT: bmai: %s", ClientStepStr(bpar->GetStep()));
bmai->Dump(bmsg.c_str());
}
//
// We may release the buffers now
REL2(bpar,bmai);
//
// Return serialized buffer
if (nser > 0) {
DEBUG("returned " << nser <<" bytes of credentials");
return new XrdSecCredentials(bser, nser);
} else {
NOTIFY("problems with final serialization");
return (XrdSecCredentials *)0;
}
}
/******************************************************************************/
/* S e r v e r O r i e n t e d M e t h o d s */
/******************************************************************************/
//_____________________________________________________________________________
static bool AuthzFunCheck(XrdSutCacheEntry *e, void *a) {
int st_ref = (*((XrdSutCacheArg_t *)a)).arg1;
time_t ts_ref = (time_t)(*((XrdSutCacheArg_t *)a)).arg2;
long to_ref = (*((XrdSutCacheArg_t *)a)).arg3;
int st_exp = (*((XrdSutCacheArg_t *)a)).arg4;
if (e && (e->status == st_ref)) {
// Check expiration, if required
bool expired = 0;
if (to_ref > 0 && (ts_ref - e->mtime) > to_ref) expired = 1;
int notafter = *((int *) e->buf2.buf);
if (to_ref > notafter) expired = 1;
if (expired) {
// Invalidate the entry, if the case
e->status = st_exp;
} else {
return true;
}
}
return false;
}
/******************************************************************************/
/* A u t h e n t i c a t e */
/******************************************************************************/
int XrdSecProtocolgsi::Authenticate(XrdSecCredentials *cred,
XrdSecParameters **parms,
XrdOucErrInfo *ei)
{
//
// Check if we have any credentials or if no credentials really needed.
// In either case, use host name as client name
EPNAME("Authenticate");
//
// If cred buffer is two small or empty assume host protocol
if (cred->size <= (int)XrdSecPROTOIDLEN || !cred->buffer) {
strncpy(Entity.prot, "host", sizeof(Entity.prot));
return 0;
}
// Handshake vars conatiner must be initialized at this point
if (!hs)
return ErrS(Entity.tident,ei,0,0,0,kGSErrError,
"handshake var container missing",
"protocol initialization problems");
// Update time stamp
hs->TimeStamp = time(0);
//
// ID of this handshaking
if (hs->ID.length() <= 0)
hs->ID = Entity.tident;
DEBUG("handshaking ID: " << hs->ID);
// Local vars
int kS_rc = kgST_more;
int step = 0;
int nextstep = 0;
char *bpub = 0;
int lpub = 0;
const char *stepstr = 0;
String Message;
String CryptList;
String Ciphers;
String Host;
String SrvPuKExp;
String Salt;
String RndmTag;
String ClntMsg(256);
// Buffer related
XrdSutBuffer *bpar = 0; // Global buffer
XrdSutBuffer *bmai = 0; // Main buffer
XrdSutBucket *bck = 0; // Generic bucket
// Proxy export related
XrdOucString spxy;
XrdSutBucket *bpxy = 0;
//
// Decode received buffer
if (!(bpar = new XrdSutBuffer((const char *)cred->buffer,cred->size)))
return ErrS(hs->ID,ei,0,0,0,kGSErrDecodeBuffer,"global",stepstr);
//
// Check protocol ID name
if (strcmp(bpar->GetProtocol(),XrdSecPROTOIDENT))
return ErrS(hs->ID,ei,bpar,bmai,0,kGSErrBadProtocol,stepstr);
//
// The step indicates what we are supposed to do
step = bpar->GetStep();
stepstr = ClientStepStr(step);
// Dump, if requested
XrdOucString bmsg;
if (QTRACE(Dump)) {
bmsg.form("IN: bpar: %s", stepstr);
bpar->Dump(bmsg.c_str());
}
//
// Parse input buffer
if (ParseServerInput(bpar, &bmai, ClntMsg) == -1) {
DEBUG(ClntMsg);
return ErrS(hs->ID,ei,bpar,bmai,0,kGSErrParseBuffer,ClntMsg.c_str(),stepstr);
}
//
// Version
DEBUG("version run by client: "<< hs->RemVers);
DEBUG("options req by client: "<< hs->Options);
//
// Dump, if requested
if (QTRACE(Dump)) {
if (bmai) {
bmsg.form("IN: bmai: %s", stepstr);
bmai->Dump(bmsg.c_str());
}
}
//
// Check random challenge
if (!CheckRtag(bmai, ClntMsg))
return ErrS(hs->ID,ei,bpar,bmai,0,kGSErrBadRndmTag,stepstr,ClntMsg.c_str());
// Extract the VOMS attrbutes, if required
XrdCryptoX509ExportChain_t X509ExportChain = (sessionCF) ? sessionCF->X509ExportChain() : 0;
if (!X509ExportChain) {
// Error
return ErrS(hs->ID,ei,0,0,0,kGSErrError,
"crypto factory function for chain export not found");
}
//
// Now action depens on the step
switch (step) {
case kXGC_certreq:
//
// Client required us to send our certificate and cipher DH public parameters:
// add first this last one.
// Extract buffer with public info for the cipher agreement
if (!(bpub = hs->Rcip->Public(lpub)))
return ErrS(hs->ID,ei,bpar,bmai,0, kGSErrNoPublic,
"session",stepstr);
// If client supports decoding of signed DH, do sign them
if (hs->RemVers >= XrdSecgsiVersDHsigned) {
bck = new XrdSutBucket(bpub,lpub,kXRS_cipher);
if (sessionKsig) {
//
// Encrypt server DH public parameters with server key
if (sessionKsig->EncryptPrivate(*bck) <= 0)
return ErrS(hs->ID,ei,bpar,bmai,0, kGSErrExportPuK,
"encrypting server DH public parameters",stepstr);
} else {
return ErrS(hs->ID,ei,bpar,bmai,0, kGSErrExportPuK,
"server signing key undefined!",stepstr);
}
} else {
// Previous naming
bck = new XrdSutBucket(bpub,lpub,kXRS_puk);
}
//
// Add it to the global list
if (bpar->AddBucket(bck) != 0)
return ErrS(hs->ID,ei,bpar,bmai,0, kGSErrAddBucket,
"main",stepstr);
//
// Add bucket with list of supported ciphers
if (bpar->AddBucket(DefCipher,kXRS_cipher_alg) != 0)
return ErrS(hs->ID,ei,bpar,bmai,0,
kGSErrAddBucket,XrdSutBuckStr(kXRS_cipher_alg),stepstr);
//
// Add bucket with list of supported MDs
if (bpar->AddBucket(DefMD,kXRS_md_alg) != 0)
return ErrS(hs->ID,ei,bpar,bmai,0,
kGSErrAddBucket,XrdSutBuckStr(kXRS_md_alg),stepstr);
//
// Add the server certificate
bpar->AddBucket(hs->Cbck);
// We are done for the moment
nextstep = kXGS_cert;
break;
case kXGC_cert:
//
// Client sent its own credentials: their are checked in
// ParseServerInput, so if we are here they are OK
kS_rc = kgST_ok;
nextstep = kXGS_none;
if (GMAPOpt > 0) {
// Get name from gridmap
String name;
QueryGMAP(hs->Chain, hs->TimeStamp, name);
DEBUG("username(s) associated with this DN: "<GetBucket(kXRS_user))) {
bck->ToString(user);
bmai->Deactivate(kXRS_user);
}
DEBUG("target user: "< 0) {
// Check if the wanted username is authorized
String u;
int from = 0;
bool ok = 0;
while ((from = name.tokenize(u, from, ',')) != -1) {
if (user == u) { ok = 1; break; }
}
if (ok) {
name = u;
DEBUG("DN mapping: requested user is authorized: name is '"<Chain->EEChash()) {
Entity.name = strdup(hs->Chain->EEChash());
} else if (GMAPuseDNname && hs->Chain->EECname()) {
Entity.name = strdup(hs->Chain->EECname());
} else {
PRINT("WARNING: DN missing: corruption? ");
}
}
// Add the DN as default moninfo if requested (the authz plugin may change this)
if (MonInfoOpt > 0) {
Entity.moninfo = strdup(hs->Chain->EECname());
}
if (VOMSAttrOpt > 0) {
if (VOMSFun) {
// Fill the information needed by the external function
if (VOMSCertFmt == 1) {
// PEM base64
bpxy = (*X509ExportChain)(hs->Chain, true);
bpxy->ToString(spxy);
delete bpxy;
Entity.creds = strdup(spxy.c_str());
Entity.credslen = spxy.length();
} else {
// Raw (opaque) format, to be used with XrdCrypto
Entity.creds = (char *) hs->Chain;
Entity.credslen = 0;
}
if ((*VOMSFun)(Entity) != 0 && VOMSAttrOpt == 2) {
// Error
kS_rc = kgST_error;
PRINT("ERROR: the VOMS extraction plug-in reported a failure for this handshake");
break;
}
} else {
// Lite version (no validations whatsover)
if (ExtractVOMS(hs->Chain, Entity) != 0 && VOMSAttrOpt == 2) {
// Error
kS_rc = kgST_error;
PRINT("ERROR: VOMS attributes required but not found (default lite-extraction technology)");
break;
}
}
NOTIFY("VOMS: Entity.vorg: "<< (Entity.vorg ? Entity.vorg : ""));
NOTIFY("VOMS: Entity.grps: "<< (Entity.grps ? Entity.grps : ""));
NOTIFY("VOMS: Entity.role: "<< (Entity.role ? Entity.role : ""));
NOTIFY("VOMS: Entity.endorsements: "<< (Entity.endorsements ? Entity.endorsements : ""));
}
// Here prepare/extract the information for authorization
spxy = "";
bpxy = 0;
if (AuthzFun && AuthzKey) {
// Fill the information needed by the external function
if (AuthzCertFmt == 1) {
// May have been already done
if (!Entity.creds || (Entity.creds && Entity.credslen == 0)) {
// PEM base64
bpxy = (*X509ExportChain)(hs->Chain, true);
bpxy->ToString(spxy);
Entity.creds = strdup(spxy.c_str());
Entity.credslen = spxy.length();
// If not empty Entity.creds is a pointer to hs->Chain and
// we need not to free it
}
} else {
// May have been already done
if (Entity.creds && Entity.credslen > 0) {
// Entity.creds is in PEM form, we need to free it
free(Entity.creds);
// Raw (opaque) format, to be used with XrdCrypto
Entity.creds = (char *) hs->Chain;
Entity.credslen = 0;
}
}
// Get the key
char *key = 0;
int lkey = 0;
if ((lkey = (*AuthzKey)(Entity, &key)) < 0) {
// Fatal error
kS_rc = kgST_error;
PRINT("ERROR: unable to get the key associated to this user");
break;
}
const char *dn = (const char *)key;
time_t now = hs->TimeStamp;
// We may have it in the cache
XrdSutCERef ceref;
bool rdlock = false;
XrdSutCacheArg_t arg = {kCE_ok, now, AuthzCacheTimeOut, kCE_disabled};
XrdSutCacheEntry *cent = cacheAuthzFun.Get(dn, rdlock, AuthzFunCheck, (void *) &arg);
if (!cent) {
// Fatal error
kS_rc = kgST_error;
PRINT("ERROR: unable to get cache entry for dn: "<rwmtx));
if (!rdlock) {
if (cent->buf1.buf)
FreeEntity((XrdSecEntity *) cent->buf1.buf);
SafeDelete(cent->buf1.buf);
SafeDelete(cent->buf2.buf);
}
if (cent->status != kCE_ok) {
int authzrc = 0;
if ((authzrc = (*AuthzFun)(Entity)) != 0) {
// Error
kS_rc = kgST_error;
PRINT("ERROR: the authorization plug-in reported a failure for this handshake");
SafeDelete(key);
ceref.UnLock();
break;
} else {
cent->status = kCE_ok;
// Save a copy of the relevant Entity fields
XrdSecEntity *se = new XrdSecEntity();
int slen = 0;
CopyEntity(&Entity, se, &slen);
FreeEntity((XrdSecEntity *) cent->buf1.buf);
SafeDelete(cent->buf1.buf);
cent->buf1.buf = (char *) se;
cent->buf1.len = slen;
// Proxy expiration time
int notafter = hs->Chain->End() ? hs->Chain->End()->NotAfter() : -1;
cent->buf2.buf = (char *) new int(notafter);
cent->buf2.len = sizeof(int);
// Fill up the rest
cent->cnt = 0;
cent->mtime = now; // creation time
// Notify
DEBUG("Saved Entity to cacheAuthzFun ("<buf1.buf, &Entity, &slen);
// Notify
DEBUG("Got Entity from cacheAuthzFun ("<= 0) {
if (bpxy && AuthzPxyWhat == 1) {
SafeDelete(bpxy); spxy = "";
SafeFree(Entity.creds);
Entity.credslen = 0;
}
if (!bpxy) {
if (AuthzPxyWhat == 1 && hs->Chain->End()) {
bpxy = hs->Chain->End()->Export();
} else {
bpxy = (*X509ExportChain)(hs->Chain, true);
}
bpxy->ToString(spxy);
}
if (AuthzPxyWhere == 1) {
Entity.creds = strdup(spxy.c_str());
Entity.credslen = spxy.length();
} else {
// This should be deprecated
Entity.endorsements = strdup(spxy.c_str());
}
delete bpxy;
NOTIFY("Entity.endorsements: "<<(void *)Entity.endorsements);
NOTIFY("Entity.creds: "<<(void *)Entity.creds);
NOTIFY("Entity.credslen: "<RemVers >= 10100) {
if (hs->PxyChain) {
// The client is going to send over info for delegation
kS_rc = kgST_more;
nextstep = kXGS_pxyreq;
}
}
break;
case kXGC_sigpxy:
//
// Nothing to do after this
kS_rc = kgST_ok;
nextstep = kXGS_none;
//
// If something went wrong, print explanation
if (ClntMsg.length() > 0) {
PRINT(ClntMsg);
}
break;
default:
return ErrS(hs->ID,ei,bpar,bmai,0, kGSErrBadOpt, stepstr);
}
if (kS_rc == kgST_more) {
//
// Add message to client
if (ClntMsg.length() > 0)
if (bmai->AddBucket(ClntMsg,kXRS_message) != 0) {
NOTIFY("problems adding bucket with message for client");
}
//
// Serialize, encrypt and add to the global list
if (AddSerialized('s', nextstep, hs->ID,
bpar, bmai, kXRS_main, sessionKey) != 0) {
return ErrS(hs->ID,ei,bpar,bmai,0, kGSErrSerialBuffer,
"main / session cipher",stepstr);
}
//
// Serialize the global buffer
char *bser = 0;
int nser = bpar->Serialized(&bser,'f');
//
// Dump, if requested
if (QTRACE(Authen)) {
bmsg.form("OUT: bpar: %s", ServerStepStr(bpar->GetStep()));
bpar->Dump(bmsg.c_str());
bmsg.form("OUT: bmai: %s", ServerStepStr(bpar->GetStep()));
bmai->Dump(bmsg.c_str());
}
//
// Create buffer for client
*parms = new XrdSecParameters(bser,nser);
} else {
//
// Cleanup handshake vars
SafeDelete(hs);
}
//
// We may release the buffers now
REL2(bpar,bmai);
//
// All done
return kS_rc;
}
/******************************************************************************/
/* C o p y E n t i ty */
/******************************************************************************/
void XrdSecProtocolgsi::CopyEntity(XrdSecEntity *in, XrdSecEntity *out, int *lout)
{
// Copy relevant fields of 'in' into 'out'; return length of 'out'
if (!in || !out) return;
int slen = sizeof(XrdSecEntity);
if (in->name) { out->name = strdup(in->name); slen += strlen(in->name); }
if (in->host) { out->host = strdup(in->host); slen += strlen(in->host); }
if (in->vorg) { out->vorg = strdup(in->vorg); slen += strlen(in->vorg); }
if (in->role) { out->role = strdup(in->role); slen += strlen(in->role); }
if (in->grps) { out->grps = strdup(in->grps); slen += strlen(in->grps); }
if (in->creds && in->credslen > 0) {
out->creds = strdup(in->creds); slen += in->credslen;
out->credslen = in->credslen; }
if (in->endorsements) { out->endorsements = strdup(in->endorsements);
slen += strlen(in->endorsements); }
if (in->moninfo) { out->moninfo = strdup(in->moninfo);
slen += strlen(in->moninfo); }
// Save length, if required
if (lout) *lout = slen;
// Done
return;
}
/******************************************************************************/
/* F r e e E n t i ty */
/******************************************************************************/
void XrdSecProtocolgsi::FreeEntity(XrdSecEntity *in)
{
// Free relevant fields of 'in';
if (!in) return;
if (in->name) SafeFree(in->name);
if (in->host) SafeFree(in->host);
if (in->vorg) SafeFree(in->vorg);
if (in->role) SafeFree(in->role);
if (in->grps) SafeFree(in->grps);
if (in->creds && in->credslen > 0) { SafeFree(in->creds); in->credslen = 0; }
if (in->endorsements) SafeFree(in->endorsements);
if (in->moninfo) SafeFree(in->moninfo);
// Done
return;
}
/******************************************************************************/
/* E x t r a c t V O M S */
/******************************************************************************/
int XrdSecProtocolgsi::ExtractVOMS(X509Chain *c, XrdSecEntity &ent)
{
// Get the VOMS attributes from proxy file(s) in chain 'c' (either the proxy
// or the limited proxy) and fill the relevant fields in 'ent'
EPNAME("ExtractVOMS");
if (!c) return -1;
XrdCryptoX509 *xp = c->End();
if (!xp) return -1;
// Extractor
XrdCryptoX509GetVOMSAttr_t X509GetVOMSAttr = sessionCF->X509GetVOMSAttr();
if (!X509GetVOMSAttr) return -1;
// Extract the information
XrdOucString vatts;
int rc = 0;
if ((rc = (*X509GetVOMSAttr)(xp, vatts)) != 0) {
if (strstr(xp->Subject(), "CN=limited proxy")) {
xp = c->SearchBySubject(xp->Issuer());
rc = (*X509GetVOMSAttr)(xp, vatts);
}
if (rc != 0) {
if (rc > 0) {
NOTIFY("No VOMS attributes in proxy chain");
} else {
PRINT("ERROR: problem extracting VOMS attributes");
}
return -1;
}
}
int from = 0;
XrdOucString vat;
while ((from = vatts.tokenize(vat, from, ',')) != -1) {
XrdOucString vo, role, grp;
if (vat.length() > 0) {
// The attribute is in the form
// /VO[/group[/subgroup(s)]][/Role=role][/Capability=cap]
int isl = vat.find('/', 1);
if (isl != STR_NPOS) vo.assign(vat, 1, isl - 1);
int igr = vat.find("/Role=", 1);
if (igr != STR_NPOS) grp.assign(vat, 0, igr - 1);
int irl = vat.find("Role=");
if (irl != STR_NPOS) {
role.assign(vat, irl + 5);
isl = role.find('/');
role.erase(isl);
}
if (ent.vorg) {
if (vo != (const char *) ent.vorg) {
DEBUG("WARNING: found a second VO ('"< 0) ent.vorg = strdup(vo.c_str());
}
if (grp.length() > 0
&& (!ent.grps || grp.length() > int(strlen(ent.grps)))) {
SafeFree(ent.grps);
ent.grps = strdup(grp.c_str());
}
if (role.length() > 0 && role != "NULL" && !ent.role) {
ent.role = strdup(role.c_str());
}
}
}
// Save the whole string in endorsements
SafeFree(ent.endorsements);
if (vatts.length() > 0) ent.endorsements = strdup(vatts.c_str());
// Notify if did not find the main info (the VO ...)
if (!ent.vorg) PRINT("WARNING: no VO found! (VOMS attributes: '"< 0) POPTS(t, " CRL refresh time: "<< crlrefresh);
if (mode == 'c') {
POPTS(t, " Certificate: " << (cert ? cert : XrdSecProtocolgsi::UsrCert));
POPTS(t, " Key: " << (key ? key : XrdSecProtocolgsi::UsrKey));
POPTS(t, " Proxy file: " << XrdSecProtocolgsi::UsrProxy);
POPTS(t, " Proxy validity: " << (valid ? valid : XrdSecProtocolgsi::PxyValid));
POPTS(t, " Proxy dep length: " << deplen);
POPTS(t, " Proxy bits: " << bits);
POPTS(t, " Proxy sign option: "<< sigpxy);
POPTS(t, " Proxy delegation option: "<< dlgpxy);
POPTS(t, " Allowed server names: "<< (srvnames ? srvnames : "[*/][/*]"));
} else {
POPTS(t, " Certificate: " << (cert ? cert : XrdSecProtocolgsi::SrvCert));
POPTS(t, " Key: " << (key ? key : XrdSecProtocolgsi::SrvKey));
POPTS(t, " Proxy delegation option: "<< dlgpxy);
if (dlgpxy > 1)
POPTS(t, " Template for exported proxy: "<< (exppxy ? exppxy : gUsrPxyDef));
POPTS(t, " GRIDmap file: " << (gridmap ? gridmap : XrdSecProtocolgsi::GMAPFile));
POPTS(t, " GRIDmap option: "<< ogmap);
POPTS(t, " GRIDmap cache entries expiration (secs): "<< gmapto);
if (gmapfun) {
POPTS(t, " DN mapping function: " << gmapfun);
if (gmapfunparms) POPTS(t, " DN mapping function parms: " << gmapfunparms);
} else {
if (gmapfunparms) POPTS(t, " DN mapping function parms: ignored (no mapping function defined)");
}
if (authzfun) {
POPTS(t, " Authorization function: " << authzfun);
if (authzfunparms) POPTS(t, " Authorization function parms: " << authzfunparms);
POPTS(t, " Authorization cache entries expiration (secs): " << authzto);
} else {
if (authzfunparms) POPTS(t, " Authorization function parms: ignored (no authz function defined)");
}
POPTS(t, " Client proxy availability in XrdSecEntity.endorsement: "<< authzpxy);
POPTS(t, " VOMS option: "<< vomsat);
if (vomsfun) {
POPTS(t, " VOMS extraction function: " << vomsfun);
if (vomsfunparms) POPTS(t, " VOMS extraction function parms: " << vomsfunparms);
} else {
if (vomsfunparms) POPTS(t, " VOMS extraction function parms: ignored (no VOMS extraction function defined)");
}
POPTS(t, " MonInfo option: "<< moninfo);
if (!hashcomp)
POPTS(t, " Name hashing algorithm compatibility OFF");
}
// Crypto options
POPTS(t, " Crypto modules: "<< (clist ? clist : XrdSecProtocolgsi::DefCrypto));
POPTS(t, " Ciphers: "<< (cipher ? cipher : XrdSecProtocolgsi::DefCipher));
POPTS(t, " MDigests: "<< (md ? md : XrdSecProtocolgsi::DefMD));
if (trustdns) {
POPTS(t, " Trusting DNS for hostname checking");
} else {
POPTS(t, " Untrusting DNS for hostname checking");
}
POPTS(t, "*** ------------------------------------------------------------ ***");
}
/******************************************************************************/
/* X r d S e c P r o t o c o l g s i I n i t */
/******************************************************************************/
extern "C"
{
char *XrdSecProtocolgsiInit(const char mode,
const char *parms, XrdOucErrInfo *erp)
{
// One-time protocol initialization, filling the static flags and options
// of the protocol.
// For clients (mode == 'c') we use values in envs.
// For servers (mode == 's') the command line options are passed through
// parms.
EPNAME("ProtocolgsiInit");
gsiOptions opts;
char *rc = (char *)"";
char *cenv = 0;
// Initiate error logging and tracing
gsiTrace = XrdSecProtocolgsi::EnableTracing();
//
// Clients first
if (mode == 'c') {
//
// Decode envs:
// "XrdSecDEBUG" debug flag ("0","1","2","3")
// "XrdSecGSICADIR" full path to an alternative path
// containing the CA info
// [/etc/grid-security/certificates]
// "XrdSecGSICRLDIR" full path to an alternative path
// containing the CRL info
// [/etc/grid-security/certificates]
// "XrdSecGSICRLEXT" default extension of CRL files [.r0]
// "XrdSecGSIUSERCERT" full path to an alternative file
// containing the user certificate
// [$HOME/.globus/usercert.pem]
// "XrdSecGSIUSERKEY" full path to an alternative file
// containing the user key
// [$HOME/.globus/userkey.pem]
// "XrdSecGSIUSERPROXY" full path to an alternative file
// containing the user proxy
// [/tmp/x509up_u]
// "XrdSecGSIPROXYVALID" validity of proxies in the
// grid-proxy-init format
// ["12:00", i.e. 12 hours]
// "XrdSecGSIPROXYDEPLEN" depth of signature path for proxies;
// use -1 for unlimited [0]
// "XrdSecGSIPROXYKEYBITS" bits in PKI for proxies [512]
// "XrdSecGSICACHECK" CA check level [1]:
// 0 do not verify;
// 1 verify if self-signed, warn if not;
// 2 verify in all cases, fail if not possible
// "XrdSecGSICRLCHECK" CRL check level [2]:
// 0 don't care;
// 1 use if available;
// 2 require,
// 3 require non-expired CRL
// "XrdSecGSIDELEGPROXY" Forwarding of credentials option:
// 0 deny; 1 sign request created
// by server; 2 forward local proxy
// (include private key) [1]
// "XrdSecGSISRVNAMES" Server names allowed: if the server CN
// does not match any of these, or it is
// explicitely denied by these, or it is
// not in the form "*/", the
// handshake fails.
// "XrdSecGSIUSEDEFAULTHASH" If this variable is set only the default
// name hashing algorithm is used
//
opts.mode = mode;
// debug
cenv = getenv("XrdSecDEBUG");
if (cenv)
{if (cenv[0] >= 49 && cenv[0] <= 51) opts.debug = atoi(cenv);
else {PRINT("unsupported debug value from env XrdSecDEBUG: "<]
// [-c:[-]ssl[:[-]]
// [-crldir:]
// [-crlext:]
// [-cert:]
// [-key:]
// [-cipher:]
// [-md:]
// [-ca:]
// [-crl:]
// [-crlrefresh:]
// [-gridmap:]
// [-gmapfun:]
// [-gmapfunparms:]
// [-authzfun:]
// [-authzfunparms:]
// [-authzto:]
// [-gmapto:]
// [-gmapopt:]
// [-dlgpxy:]
// [-exppxy:]
// [-authzpxy]
// [-vomsat:]
// [-vomsfun:]
// [-vomsfunparms:]
// [-defaulthash]
// [-trustdns:<0|1>]
//
int debug = -1;
String clist = "";
String certdir = "";
String crldir = "";
String crlext = "";
String cert = "";
String key = "";
String cipher = "";
String md = "";
String gridmap = "";
String gmapfun = "";
String gmapfunparms = "";
String authzfun = "";
String authzfunparms = "";
String vomsfun = "";
String vomsfunparms = "";
String exppxy = "";
int ca = 1;
int crl = 1;
int crlrefresh = 86400;
int ogmap = 1;
int gmapto = 600;
int authzto = -1;
int dlgpxy = 0;
int authzpxy = 0;
int vomsat = 1;
int moninfo = 0;
int hashcomp = 1;
int trustdns = 1;
char *op = 0;
while (inParms.GetLine()) {
while ((op = inParms.GetToken())) {
if (!strncmp(op, "-d:",3)) {
debug = atoi(op+3);
} else if (!strncmp(op, "-c:",3)) {
clist = (const char *)(op+3);
} else if (!strncmp(op, "-certdir:",9)) {
certdir = (const char *)(op+9);
} else if (!strncmp(op, "-crldir:",8)) {
crldir = (const char *)(op+8);
} else if (!strncmp(op, "-crlext:",8)) {
crlext = (const char *)(op+8);
} else if (!strncmp(op, "-cert:",6)) {
cert = (const char *)(op+6);
} else if (!strncmp(op, "-key:",5)) {
key = (const char *)(op+5);
} else if (!strncmp(op, "-cipher:",8)) {
cipher = (const char *)(op+8);
} else if (!strncmp(op, "-md:",4)) {
md = (const char *)(op+4);
} else if (!strncmp(op, "-ca:",4)) {
ca = atoi(op+4);
} else if (!strncmp(op, "-crl:",5)) {
crl = atoi(op+5);
} else if (!strncmp(op, "-crlrefresh:",12)) {
crlrefresh = atoi(op+12);
} else if (!strncmp(op, "-gmapopt:",9)) {
ogmap = atoi(op+9);
} else if (!strncmp(op, "-gridmap:",9)) {
gridmap = (const char *)(op+9);
} else if (!strncmp(op, "-gmapfun:",9)) {
gmapfun = (const char *)(op+9);
} else if (!strncmp(op, "-gmapfunparms:",14)) {
gmapfunparms = (const char *)(op+14);
} else if (!strncmp(op, "-authzfun:",10)) {
authzfun = (const char *)(op+10);
} else if (!strncmp(op, "-authzfunparms:",15)) {
authzfunparms = (const char *)(op+15);
} else if (!strncmp(op, "-authzto:",9)) {
authzto = atoi(op+9);
} else if (!strncmp(op, "-gmapto:",8)) {
gmapto = atoi(op+8);
} else if (!strncmp(op, "-dlgpxy:",8)) {
dlgpxy = atoi(op+8);
} else if (!strncmp(op, "-exppxy:",8)) {
exppxy = (const char *)(op+8);
} else if (!strncmp(op, "-authzpxy:",10)) {
authzpxy = atoi(op+10);
} else if (!strncmp(op, "-authzpxy",9)) {
authzpxy = 11;
} else if (!strncmp(op, "-vomsat:",8)) {
vomsat = atoi(op+8);
} else if (!strncmp(op, "-vomsfun:",9)) {
vomsfun = (const char *)(op+9);
} else if (!strncmp(op, "-vomsfunparms:",14)) {
vomsfunparms = (const char *)(op+14);
} else if (!strcmp(op, "-moninfo")) {
moninfo = 1;
} else if (!strncmp(op, "-moninfo:",9)) {
moninfo = atoi(op+9);
} else if (!strcmp(op, "-defaulthash")) {
hashcomp = 0;
} else if (!strncmp(op, "-trustdns:",10)) {
trustdns = atoi(op+10);
} else {
PRINT("ignoring unknown switch: "< -1) ? debug : opts.debug;
opts.mode = 's';
opts.ca = ca;
opts.crl = crl;
opts.crlrefresh = crlrefresh;
opts.ogmap = ogmap;
opts.gmapto = gmapto;
opts.authzto = authzto;
opts.dlgpxy = (dlgpxy >= 0 && dlgpxy <= 1) ? dlgpxy : 0;
opts.authzpxy = authzpxy;
opts.vomsat = vomsat;
opts.moninfo = moninfo;
opts.hashcomp = hashcomp;
opts.trustdns = (trustdns <= 0) ? false : true;
if (clist.length() > 0)
opts.clist = (char *)clist.c_str();
if (certdir.length() > 0)
opts.certdir = (char *)certdir.c_str();
if (crldir.length() > 0)
opts.crldir = (char *)crldir.c_str();
if (crlext.length() > 0)
opts.crlext = (char *)crlext.c_str();
if (cert.length() > 0)
opts.cert = (char *)cert.c_str();
if (key.length() > 0)
opts.key = (char *)key.c_str();
if (cipher.length() > 0)
opts.cipher = (char *)cipher.c_str();
if (md.length() > 0)
opts.md = (char *)md.c_str();
if (gridmap.length() > 0)
opts.gridmap = (char *)gridmap.c_str();
if (gmapfun.length() > 0)
opts.gmapfun = (char *)gmapfun.c_str();
if (gmapfunparms.length() > 0)
opts.gmapfunparms = (char *)gmapfunparms.c_str();
if (authzfun.length() > 0)
opts.authzfun = (char *)authzfun.c_str();
if (authzfunparms.length() > 0)
opts.authzfunparms = (char *)authzfunparms.c_str();
if (exppxy.length() > 0)
opts.exppxy = (char *)exppxy.c_str();
if (vomsfun.length() > 0)
opts.vomsfun = (char *)vomsfun.c_str();
if (vomsfunparms.length() > 0)
opts.vomsfunparms = (char *)vomsfunparms.c_str();
// Notify init options, if required
opts.Print(gsiTrace);
//
// Setup the plug-in with the chosen options
return XrdSecProtocolgsi::Init(opts,erp);
}
// Notify init options, if required
opts.Print(gsiTrace);
//
// Setup the plug-in with the defaults
return XrdSecProtocolgsi::Init(opts,erp);
}}
/******************************************************************************/
/* X r d S e c P r o t o c o l g s i O b j e c t */
/******************************************************************************/
XrdVERSIONINFO(XrdSecProtocolgsiObject,secgsi);
namespace
{XrdVersionInfo *gsiVersion = &XrdVERSIONINFOVAR(XrdSecProtocolgsiObject);}
extern "C"
{
XrdSecProtocol *XrdSecProtocolgsiObject(const char mode,
const char *hostname,
XrdNetAddrInfo &endPoint,
const char *parms,
XrdOucErrInfo *erp)
{
XrdSecProtocolgsi *prot;
int options = XrdSecNOIPCHK;
//
// Get a new protocol object
if (!(prot = new XrdSecProtocolgsi(options, hostname, endPoint, parms))) {
const char *msg = "Secgsi: Insufficient memory for protocol.";
if (erp)
erp->setErrInfo(ENOMEM, msg);
else
cerr < 0) {
bls->SetStep(step);
buf->SetStep(step);
hs->LastStep = step;
}
//
// If a random tag has been sent and we have a session cipher,
// we sign it
XrdSutBucket *brt = buf->GetBucket(kXRS_rtag);
if (brt && sessionKsig) {
//
// Encrypt random tag with session cipher
if (sessionKsig->EncryptPrivate(*brt) <= 0) {
PRINT("error encrypting random tag");
return -1;
}
//
// Update type
brt->type = kXRS_signed_rtag;
}
//
// Add an random challenge: if a next exchange is required this will
// allow to prove authenticity of counter part
//
// Generate new random tag and create a bucket
if (!(opt == 'c' && step == kXGC_sigpxy)) {
String RndmTag;
XrdSutRndm::GetRndmTag(RndmTag);
//
// Get bucket
brt = 0;
if (!(brt = new XrdSutBucket(RndmTag,kXRS_rtag))) {
PRINT("error creating random tag bucket");
return -1;
}
buf->AddBucket(brt);
}
//
// Get cache entry
if (!hs->Cref) {
PRINT("cache entry not found: protocol error");
return -1;
}
//
// Add random tag to the cache and update timestamp
hs->Cref->buf1.SetBuf(brt->buffer,brt->size);
hs->Cref->mtime = (kXR_int32)hs->TimeStamp;
//
// Now serialize the buffer ...
char *bser = 0;
int nser = buf->Serialized(&bser);
//
// Update bucket with this content
XrdSutBucket *bck = 0;;
if (!(bck = bls->GetBucket(type))) {
// or create new bucket, if not existing
if (!(bck = new XrdSutBucket(bser,nser,type))) {
PRINT("error creating bucket "
<<" - type: "<AddBucket(bck);
} else {
bck->Update(bser,nser);
}
//
// Encrypted the bucket
if (cip) {
if (cip->Encrypt(*bck, useIV) == 0) {
PRINT("error encrypting bucket - cipher "
<<" - type: "<GetStep();
// Do the right action
switch (step) {
case kXGS_init:
// Process message
if (ClientDoInit(br, bm, cmsg) != 0)
return -1;
break;
case kXGS_cert:
// Process message
if (ClientDoCert(br, bm, cmsg) != 0)
return -1;
break;
case kXGS_pxyreq:
// Process message
if (ClientDoPxyreq(br, bm, cmsg) != 0)
return -1;
break;
default:
cmsg = "protocol error: unknown action: "; cmsg += step;
return -1;
break;
}
// We are done
return 0;
}
//_________________________________________________________________________
int XrdSecProtocolgsi::ClientDoInit(XrdSutBuffer *br, XrdSutBuffer **bm,
String &emsg)
{
// Client side: process a kXGS_init message.
// Return 0 on success, -1 on error. If the case, a message is returned
// in cmsg.
EPNAME("ClientDoInit");
//
// Create the main buffer as a copy of the buffer received
if (!((*bm) = new XrdSutBuffer(br->GetProtocol(),br->GetOptions()))) {
emsg = "error instantiating main buffer";
return -1;
}
//
// Extract server version from options
String opts = br->GetOptions();
int ii = opts.find("v:");
if (ii >= 0) {
String sver(opts,ii+2);
sver.erase(sver.find(','));
hs->RemVers = atoi(sver.c_str());
} else {
hs->RemVers = Version;
emsg = "server version information not found in options:"
" assume same as local";
}
// Set use IV depending on the remote version
useIV = false;
if (hs->RemVers >= XrdSecgsiVersDHsigned) {
// Supports setting a unique IV in enc/dec operations
useIV = true;
}
//
// Create cache
if (!(hs->Cref = new XrdSutPFEntry("c"))) {
emsg = "error creating cache";
return -1;
}
//
// Save server version in cache
hs->Cref->status = hs->RemVers;
//
// Set options
hs->Options = PxyReqOpts;
//
// Extract list of crypto modules
String clist;
ii = opts.find("c:");
if (ii >= 0) {
clist.assign(opts, ii+2);
clist.erase(clist.find(','));
} else {
NOTIFY("Crypto list missing: protocol error? (use defaults)");
clist = DefCrypto;
}
// Parse the list loading the first we can
if (ParseCrypto(clist) != 0) {
emsg = "cannot find / load crypto requested modules :";
emsg += clist;
return -1;
}
//
// Extract server certificate CA hashes
String srvca;
ii = opts.find("ca:");
if (ii >= 0) {
srvca.assign(opts, ii+3);
srvca.erase(srvca.find(','));
}
// Parse the list loading the first we can
if (ParseCAlist(srvca) != 0) {
emsg = "unknown CA: cannot verify server certificate";
hs->Chain = 0;
return -1;
}
//
// Resolve place-holders in cert, key and proxy file paths, if any
if (XrdSutResolve(UsrCert, Entity.host, Entity.vorg, Entity.grps, Entity.name) != 0) {
PRINT("Problems resolving templates in "<PxyChain, sessionKsig, hs->Cbck };
if (QueryProxy(1, &cachePxy, "Proxy:0",
sessionCF, hs->TimeStamp, &pi, &po) != 0) {
emsg = "error getting user proxies";
hs->Chain = 0;
return -1;
}
// Save the result
hs->PxyChain = po.chain;
hs->Cbck = new XrdSutBucket(*((XrdSutBucket *)(po.cbck)));
if (!(sessionKsig = sessionCF->RSA(*(po.ksig)))) {
emsg = "could not get a copy of the signing key:";
hs->Chain = 0;
return -1;
}
//
// And we are done;
return 0;
}
//_________________________________________________________________________
int XrdSecProtocolgsi::ClientDoCert(XrdSutBuffer *br, XrdSutBuffer **bm,
String &emsg)
{
// Client side: process a kXGS_cert message.
// Return 0 on success, -1 on error. If the case, a message is returned
// in cmsg.
EPNAME("ClientDoCert");
XrdSutBucket *bck = 0;
//
// make sure the cache is still there
if (!hs->Cref) {
emsg = "cache entry not found";
hs->Chain = 0;
return -1;
}
//
// make sure is not too old
int reftime = hs->TimeStamp - TimeSkew;
if (hs->Cref->mtime < reftime) {
emsg = "cache entry expired";
// Remove: should not be checked a second time
SafeDelete(hs->Cref);
hs->Chain = 0;
return -1;
}
//
// Get from cache version run by server
hs->RemVers = hs->Cref->status;
//
// Extract list of cipher algorithms supported by the server
String cip = "";
if ((bck = br->GetBucket(kXRS_cipher_alg))) {
String ciplist;
bck->ToString(ciplist);
// Parse the list
int from = 0;
while ((from = ciplist.tokenize(cip, from, ':')) != -1) {
if (cip.length() > 0)
if (sessionCF->SupportedCipher(cip.c_str()))
break;
cip = "";
}
// Must have a common cipher algorithm
if (cip.length() <= 0) {
emsg = "no common cipher algorithm";
hs->Chain = 0;
return -1;
}
} else {
NOTIFY("WARNING: list of ciphers supported by server missing"
" - using default");
}
//
// Extract server certificate
if (!(bck = br->GetBucket(kXRS_x509))) {
emsg = "server certificate missing";
hs->Chain = 0;
return -1;
}
//
// Finalize chain: get a copy of it (we do not touch the reference)
hs->Chain = new X509Chain(hs->Chain);
if (!(hs->Chain)) {
emsg = "cannot duplicate reference chain";
return -1;
}
// The new chain must be deleted at destruction
hs->Options |= kOptsDelChn;
// Get hook to parsing function
XrdCryptoX509ParseBucket_t ParseBucket = sessionCF->X509ParseBucket();
if (!ParseBucket) {
emsg = "cannot attach to ParseBucket function!";
return -1;
}
// Parse bucket
int nci = (*ParseBucket)(bck, hs->Chain);
if (nci != 1) {
emsg += nci;
emsg += " vs 1 expected)";
return -1;
}
//
// Verify the chain
x509ChainVerifyOpt_t vopt = {0,static_cast(hs->TimeStamp),-1,hs->Crl};
XrdCryptoX509Chain::EX509ChainErr ecode = XrdCryptoX509Chain::kNone;
if (!(hs->Chain->Verify(ecode, &vopt))) {
emsg = "certificate chain verification failed: ";
emsg += hs->Chain->LastError();
return -1;
}
//
// Verify server identity using RFC2818 method
//
// First we check the SAN. If the check succeeds then we are all done.
// Otherwise, if there is no SAN extension or if trustDNS is in effect,
// we check if the common name matches.
//
DEBUG("Checking cert is for host " <Chain->End()->MatchesSAN(Entity.host, hasSAN))
{if (hasSAN && !TrustDNS)
{emsg = "Unable to verify server hostname '"; emsg += wantHost;
emsg+= "' using SAN extension; common name fallback disallowed.";
return -1;
}
// If the common name check fails, TrustDNS allows fallback
if (!ServerCertNameOK(hs->Chain->End()->Subject(),Entity.host,emsg))
{if (!TrustDNS || Entity.addrInfo == 0 || expectedHost)
{emsg = "Unable to verify server hostname '"; emsg += wantHost;
emsg+= "' using common name; DNS fallback prohibited.";
return -1;
}
// Use DNS to resolve possible alias name
const char *name = Entity.addrInfo->Name();
if (name == NULL)
{emsg = "Unable to verify server hostname '"; emsg += wantHost;
emsg+= "'; DNS fallback translation failed.";
return -1;
}
DEBUG("TrustDNS: checking if cert is for host " <Chain->End()->Subject(),name,emsg)
|| (hasSAN && !hs->Chain->End()->MatchesSAN(name,hasSAN));
if (!hostOK) return -1;
}
}
// If we used the DNS then we must prohibit proxy delegation of any kind
//
if (usedDNS)
{if (hs->Options & (kOptsFwdPxy | kOptsSigReq))
{hs->Options &= ~(kOptsFwdPxy | kOptsSigReq);
std::cerr <<"secgsi: proxy delegation forbidden when trusting DNS!\n"
<RSA(*(hs->Chain->End()->PKI()));
if (!sessionKver || !sessionKver->IsValid()) {
emsg = "server certificate contains an invalid key";
return -1;
}
// Move next part to here, after sessionKver set, in order to
// verify the signature of DH parameters
//
// If client supports decoding of signed DH, do sign them
if (hs->RemVers >= XrdSecgsiVersDHsigned) {
// Extract server public part for session cipher
if (!(bck = br->GetBucket(kXRS_cipher))) {
emsg = "server public part for session cipher missing";
hs->Chain = 0;
return -1;
}
// Encrypt server DH public parameters with server key
if (sessionKver->DecryptPublic(*bck) <= 0) {
emsg = "decrypting server DH public parameters";
return -1;
}
} else {
// Extract server public part for session cipher
if (!(bck = br->GetBucket(kXRS_puk))) {
emsg = "server public part for session cipher missing";
hs->Chain = 0;
return -1;
}
// If the server doesn't provide signed DH parameter, disable proxy delegation
if (hs->Options & (kOptsFwdPxy | kOptsSigReq)) {
hs->Options &= ~(kOptsFwdPxy | kOptsSigReq);
PRINT("no signed DH parameters from " << Entity.host
<< ". Will not delegate x509 proxy to it");
}
}
//
// Initialize session cipher
SafeDelete(sessionKey);
if (!(sessionKey =
sessionCF->Cipher(hs->HasPad, 0,bck->buffer,bck->size,cip.c_str())) || !(sessionKey->IsValid())) {
PRINT("could not instantiate session cipher "
"using cipher public info from server");
emsg = "could not instantiate session cipher ";
return -1;
}
//
// Communicate the cipher name to server
if (hs->RemVers >= XrdSecgsiVersDHsigned) {
// Including the length of the IV if supported
String cipiv;
String::form(cipiv, "%s#%d", cip.c_str(), sessionKey->MaxIVLength());
br->UpdateBucket(cipiv, kXRS_cipher_alg);
} else {
br->UpdateBucket(cip, kXRS_cipher_alg);
}
// Deactivate what not needed any longer
if (hs->RemVers >= XrdSecgsiVersDHsigned) {
br->Deactivate(kXRS_cipher);
} else {
br->Deactivate(kXRS_puk);
}
br->Deactivate(kXRS_x509);
//
// Extract list of MD algorithms supported by the server
String md = "";
if ((bck = br->GetBucket(kXRS_md_alg))) {
String mdlist;
bck->ToString(mdlist);
// Parse the list
int from = 0;
while ((from = mdlist.tokenize(md, from, ':')) != -1) {
if (md.length() > 0)
if (sessionCF->SupportedMsgDigest(md.c_str()))
break;
md = "";
}
} else {
NOTIFY("WARNING: list of digests supported by server missing"
" - using default");
md = "sha256";
}
if (!(sessionMD = sessionCF->MsgDigest(md.c_str()))) {
emsg = "could not instantiate digest object";
return -1;
}
// Communicate choice to server
br->UpdateBucket(md, kXRS_md_alg);
//
// Extract the main buffer (it contains the random challenge
// and will contain our credentials encrypted)
XrdSutBucket *bckm = 0;
if (!(bckm = br->GetBucket(kXRS_main))) {
emsg = "main buffer missing";
return -1;
}
//
// Deserialize main buffer
if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) {
emsg = "error deserializing main buffer";
return -1;
}
//
// And we are done;
return 0;
}
//_________________________________________________________________________
int XrdSecProtocolgsi::ClientDoPxyreq(XrdSutBuffer *br, XrdSutBuffer **bm,
String &emsg)
{
// Client side: process a kXGS_pxyreq message.
// Return 0 on success, -1 on error. If the case, a message is returned
// in cmsg.
XrdSutBucket *bck = 0;
//
// Extract the main buffer (it contains the random challenge
// and will contain our credentials encrypted)
XrdSutBucket *bckm = 0;
if (!(bckm = br->GetBucket(kXRS_main))) {
emsg = "main buffer missing";
return -1;
}
//
// Decrypt the main buffer with the session cipher, if available
if (sessionKey) {
if (!(sessionKey->Decrypt(*bckm, useIV))) {
emsg = "error with session cipher";
return -1;
}
}
//
// Deserialize main buffer
if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) {
emsg = "error deserializing main buffer";
return -1;
}
//
// Check if we are ready to proces this
if ((hs->Options & kOptsFwdPxy)) {
// We have to send the private key of our proxy
XrdCryptoX509 *pxy = 0;
XrdCryptoRSA *kpxy = 0;
if (!(hs->PxyChain) ||
!(pxy = hs->PxyChain->End()) || !(kpxy = pxy->PKI())) {
emsg = "local proxy info missing or corrupted";
return 0;
}
// Send back the signed request as bucket
String pri;
if (kpxy->ExportPrivate(pri) != 0) {
emsg = "problems exporting private key";
return 0;
}
// Add it to the main list
if ((*bm)->AddBucket(pri, kXRS_x509) != 0) {
emsg = "problem adding bucket with private key to main buffer";
return 0;
}
} else {
// Proxy request: check if we are allowed to sign it
if (!(hs->Options & kOptsSigReq)) {
emsg = "Not allowed to sign proxy requests";
return 0;
}
// Get the request
if (!(bck = (*bm)->GetBucket(kXRS_x509_req))) {
emsg = "bucket with proxy request missing";
return 0;
}
XrdCryptoX509Req *req = sessionCF->X509Req(bck);
if (!req) {
emsg = "could not resolve proxy request";
return 0;
}
req->SetVersion(hs->RemVers);
// Get our proxy and its private key
XrdCryptoX509 *pxy = 0;
XrdCryptoRSA *kpxy = 0;
if (!(hs->PxyChain) ||
!(pxy = hs->PxyChain->End()) || !(kpxy = pxy->PKI())) {
emsg = "local proxy info missing or corrupted";
return 0;
}
// Sign the request
XrdCryptoX509SignProxyReq_t X509SignProxyReq = (sessionCF) ? sessionCF->X509SignProxyReq() : 0;
if (!X509SignProxyReq) {
emsg = "problems getting method to sign request";
return 0;
}
XrdCryptoX509 *npxy = 0;
if ((*X509SignProxyReq)(pxy, kpxy, req, &npxy) != 0) {
emsg = "problems signing the request";
return 0;
}
delete req;
(*bm)->Deactivate(kXRS_x509_req);
// Send back the signed request as bucket
if ((bck = npxy->Export())) {
// Add it to the main list
if ((*bm)->AddBucket(bck) != 0) {
emsg = "problem adding signed request to main buffer";
return 0;
}
}
delete npxy; // has been allocated in *X509SignProxyReq
}
//
// And we are done;
return 0;
}
//_________________________________________________________________________
int XrdSecProtocolgsi::ParseServerInput(XrdSutBuffer *br, XrdSutBuffer **bm,
String &cmsg)
{
// Parse received buffer b, extracting and decrypting the main
// buffer *bm and extracting the session
// cipher, random tag buckets and user name, if any.
// Results used to fill the local handshake variables
EPNAME("ParseServerInput");
// Space for pointer to main buffer must be already allocated
if (!br || !bm) {
PRINT("invalid inputs ("<
GetStep();
// Do the right action
switch (step) {
case kXGC_certreq:
// Process message
if (ServerDoCertreq(br, bm, cmsg) != 0)
return -1;
break;
case kXGC_cert:
// Process message
if (ServerDoCert(br, bm, cmsg) != 0)
return -1;
break;
case kXGC_sigpxy:
// Process message
if (ServerDoSigpxy(br, bm, cmsg) != 0)
return -1;
break;
default:
cmsg = "protocol error: unknown action: "; cmsg += step;
return -1;
break;
}
//
// We are done
return 0;
}
//_________________________________________________________________________
int XrdSecProtocolgsi::ServerDoCertreq(XrdSutBuffer *br, XrdSutBuffer **bm,
String &cmsg)
{
// Server side: process a kXGC_certreq message.
// Return 0 on success, -1 on error. If the case, a message is returned
// in cmsg.
XrdSutCERef ceref;
XrdSutBucket *bck = 0;
XrdSutBucket *bckm = 0;
//
// Get version run by client, if there
if (br->UnmarshalBucket(kXRS_version,hs->RemVers) != 0) {
hs->RemVers = Version;
cmsg = "client version information not found in options:"
" assume same as local";
} else {
br->Deactivate(kXRS_version);
}
// Reset use IV; will be set in next round depending on the remote version
useIV = false;
//
// Extract the main buffer
if (!(bckm = br->GetBucket(kXRS_main))) {
cmsg = "main buffer missing";
return -1;
}
//
// Extract bucket with crypto module
if (!(bck = br->GetBucket(kXRS_cryptomod))) {
cmsg = "crypto module specification missing";
return -1;
}
String cmod;
bck->ToString(cmod);
// Parse the list loading the first we can
if (ParseCrypto(cmod) != 0) {
cmsg = "cannot find / load crypto requested module :";
cmsg += cmod;
return -1;
}
//
// Extract bucket with client issuer hash
if (!(bck = br->GetBucket(kXRS_issuer_hash))) {
cmsg = "client issuer hash missing";
return -1;
}
String cahash;
bck->ToString(cahash);
//
// Check if we know it
if (ParseCAlist(cahash) != 0) {
cmsg = "unknown CA: cannot verify client credentials";
return -1;
}
// Find our certificate in cache
String cadum;
XrdSutCacheEntry *cent = GetSrvCertEnt(ceref, sessionCF, hs->TimeStamp, cadum);
if (!cent) {
cmsg = "cannot find certificate: corruption?";
return -1;
}
// Fill some relevant handshake variables
sessionKsig = sessionCF->RSA(*((XrdCryptoRSA *)(cent->buf2.buf)));
hs->Cbck = new XrdSutBucket(*((XrdSutBucket *)(cent->buf3.buf)));
ceref.UnLock();
// Create a handshake cache
if (!(hs->Cref = new XrdSutPFEntry(hs->ID.c_str()))) {
cmsg = "cannot create cache entry";
return -1;
}
//
// Deserialize main buffer
if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) {
cmsg = "error deserializing main buffer";
return -1;
}
// Deactivate what not need any longer
br->Deactivate(kXRS_issuer_hash);
//
// Get options, if any
if (br->UnmarshalBucket(kXRS_clnt_opts, hs->Options) == 0)
br->Deactivate(kXRS_clnt_opts);
// We are done
return 0;
}
//_________________________________________________________________________
int XrdSecProtocolgsi::ServerDoCert(XrdSutBuffer *br, XrdSutBuffer **bm,
String &cmsg)
{
// Server side: process a kXGC_cert message.
// Return 0 on success, -1 on error. If the case, a message is returned
// in cmsg.
EPNAME("ServerDoCert");
XrdSutBucket *bck = 0;
XrdSutBucket *bckm = 0;
//
// Extract the main buffer
if (!(bckm = br->GetBucket(kXRS_main))) {
cmsg = "main buffer missing";
return -1;
}
//
// Extract cipher algorithm chosen by the client
int lenIV = 0;
String cip = "";
if ((bck = br->GetBucket(kXRS_cipher_alg))) {
bck->ToString(cip);
// Extract IV length, if any
int piv = cip.find('#');
if (piv >= 0) {
String siv(cip, piv+1);
if (siv.isdigit()) lenIV = siv.atoi();
cip.erase(piv);
}
// Parse the list
if (DefCipher.find(cip) == -1) {
cmsg = "unsupported cipher chosen by the client";
hs->Chain = 0;
return -1;
}
// Deactivate the bucket
br->Deactivate(kXRS_cipher_alg);
} else {
NOTIFY("WARNING: client choice for cipher missing"
" - using default");
}
XrdOucString cpub;
if (hs->RemVers >= XrdSecgsiVersDHsigned) {
// Supports setting a unique IV in enc/dec operations
useIV = true;
// First get the client public key
if (!(bck = br->GetBucket(kXRS_puk))) {
cmsg = "bucket with client public key missing";
return -1;
}
bck->ToString(cpub);
sessionKver = sessionCF->RSA(cpub.c_str(), cpub.length());
if (!sessionKver || !sessionKver->IsValid()) {
cmsg = "bucket with client public key contains an invalid key";
return -1;
}
// Get the client DH parameters
if (!(bck = br->GetBucket(kXRS_cipher))) {
cmsg = "bucket with client DH parameters missing";
return -1;
}
// Decrypt client DH public parameters with client key
if (sessionKver->DecryptPublic(*bck) <= 0) {
cmsg = "decrypting client DH public parameters";
return -1;
}
} else {
// Get the client DH parameters
if (!(bck = br->GetBucket(kXRS_puk))) {
cmsg = "bucket with client DH parameters missing";
return -1;
}
// If the client doesn't provide signed DH parameter, disable proxy delegation
if ((PxyReqOpts & kOptsSrvReq) ||
hs->Options & (kOptsDlgPxy | kOptsSigReq | kOptsFwdPxy))
PRINT("no signed DH parameters from client:" << Entity.tident <<
" : will not delegate x509 proxy to it");
if ((PxyReqOpts & kOptsSrvReq)) PxyReqOpts &= ~kOptsSrvReq;
if (hs->Options & (kOptsDlgPxy | kOptsSigReq | kOptsFwdPxy))
hs->Options &= ~(kOptsDlgPxy | kOptsSigReq | kOptsFwdPxy);
}
// Get the session cipher
if (bck) {
//
// Cleanup
SafeDelete(sessionKey);
//
// Prepare cipher agreement: make sure we have the reference cipher
if (!hs->Rcip) {
cmsg = "reference cipher missing";
hs->Chain = 0;
return -1;
}
sessionKey = hs->Rcip;
//
// Instantiate the session cipher
if (!(sessionKey->Finalize(hs->HasPad,bck->buffer,bck->size,cip.c_str()))) {
cmsg = "cannot finalize session cipher";
hs->Chain = 0;
return -1;
}
// Set IV length, if any
if (lenIV > 0) sessionKey->SetIV(lenIV, (const char *)0);
} else {
cmsg = "bucket with DH parameters not found or invalid: cannot finalize session cipher";
return -1;
}
//
// We need it only once
if (hs->RemVers >= XrdSecgsiVersDHsigned) br->Deactivate(kXRS_cipher);
br->Deactivate(kXRS_puk);
//
// Decrypt the main buffer with the session cipher, if available
if (sessionKey) {
if (!(sessionKey->Decrypt(*bckm, useIV))) {
cmsg = "error decrypting main buffer with session cipher";
hs->Chain = 0;
return -1;
}
}
//
// Deserialize main buffer
if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) {
cmsg = "error deserializing main buffer";
hs->Chain = 0;
return -1;
}
//
// Get version run by client, if there
if (hs->RemVers == -1) {
if ((*bm)->UnmarshalBucket(kXRS_version,hs->RemVers) != 0) {
hs->RemVers = Version;
cmsg = "client version information not found in options:"
" assume same as local";
} else {
(*bm)->Deactivate(kXRS_version);
}
}
//
// Get cache entry
if (!hs->Cref) {
cmsg = "session cache has gone";
hs->Chain = 0;
return -1;
}
//
// make sure cache is not too old
int reftime = hs->TimeStamp - TimeSkew;
if (hs->Cref->mtime < reftime) {
cmsg = "cache entry expired";
SafeDelete(hs->Cref);
hs->Chain = 0;
return -1;
}
//
// Extract the client certificate
if (!(bck = (*bm)->GetBucket(kXRS_x509))) {
cmsg = "client certificate missing";
SafeDelete(hs->Cref);
hs->Chain = 0;
return -1;
}
//
// Finalize chain: get a copy of it (we do not touch the reference)
hs->Chain = new X509Chain(hs->Chain);
if (!(hs->Chain)) {
cmsg = "cannot duplicate reference chain";
return -1;
}
// The new chain must be deleted at destruction
hs->Options |= kOptsDelChn;
// Get hook to parsing function
XrdCryptoX509ParseBucket_t ParseBucket = sessionCF->X509ParseBucket();
if (!ParseBucket) {
cmsg = "cannot attach to ParseBucket function!";
return -1;
}
// Parse bucket
int nci = (*ParseBucket)(bck, hs->Chain);
if (nci < 2) {
cmsg = "wrong number of certificates in received bucket (";
cmsg += nci;
cmsg += " > 1 expected)";
return -1;
}
//
// Verify the chain
x509ChainVerifyOpt_t vopt = {0,static_cast(hs->TimeStamp),-1,hs->Crl};
XrdCryptoX509Chain::EX509ChainErr ecode = XrdCryptoX509Chain::kNone;
if (!(hs->Chain->Verify(ecode, &vopt))) {
cmsg = "certificate chain verification failed: ";
cmsg += hs->Chain->LastError();
return -1;
}
//
// Extract the client public key from the certificate
XrdCryptoRSA *ckey = sessionCF->RSA(*(hs->Chain->End()->PKI()));
if (!ckey || !ckey->IsValid()) {
cmsg = "client certificate contains an invalid key";
return -1;
}
if (hs->RemVers >= XrdSecgsiVersDHsigned) {
// For new clients, make sure it is the same we got from the bucket
XrdOucString cpubcert;
if ((ckey->ExportPublic(cpubcert) < 0)) {
cmsg = "exporting client public key";
return -1;
}
if (cpubcert != cpub) {
cmsg = "client public key does not match the one from the bucket!";
return -1;
}
delete ckey;
} else {
// For old clients, set the client public key from the certificate
sessionKver = ckey;
}
// Deactivate certificate buffer
(*bm)->Deactivate(kXRS_x509);
//
// Check if there will be delegated proxies; these can be through
// normal request+signature, or just forwarded by the client.
// In both cases we need to save the proxy chain. If we need a
// request, we have to prepare it and send it back to the client.
// Get hook to parsing function
XrdCryptoX509CreateProxyReq_t X509CreateProxyReq = sessionCF->X509CreateProxyReq();
if (!X509CreateProxyReq) {
cmsg = "cannot attach to X509CreateProxyReq function!";
return -1;
}
bool needReq =
((PxyReqOpts & kOptsSrvReq) && (hs->Options & kOptsSigReq)) ||
(hs->Options & kOptsDlgPxy);
if (needReq || (hs->Options & kOptsFwdPxy)) {
// Create a new proxy chain
hs->PxyChain = new X509Chain();
// Add the current proxy
if ((*ParseBucket)(bck, hs->PxyChain) > 1) {
// Reorder it
hs->PxyChain->Reorder();
if (needReq) {
// Create the request
XrdCryptoX509Req *rPXp = (XrdCryptoX509Req *) &(hs->RemVers);
XrdCryptoRSA *krPXp = 0;
if ((*X509CreateProxyReq)(hs->PxyChain->End(), &rPXp, &krPXp) == 0) {
// Save key in the cache
hs->Cref->buf4.buf = (char *)krPXp;
// Prepare export bucket for request
XrdSutBucket *bckr = rPXp->Export();
// Add it to the main list
if ((*bm)->AddBucket(bckr) != 0) {
SafeDelete(hs->PxyChain);
NOTIFY("WARNING: proxy req: problem adding bucket to main buffer");
}
delete rPXp;
} else {
SafeDelete(hs->PxyChain);
NOTIFY("WARNING: proxy req: problem creating request");
}
}
} else {
SafeDelete(hs->PxyChain);
NOTIFY("WARNING: proxy req: wrong number of certificates");
}
}
//
// Extract the MD algorithm chosen by the client
String md = "";
if ((bck = br->GetBucket(kXRS_md_alg))) {
String mdlist;
bck->ToString(md);
// Parse the list
if (DefMD.find(md) == -1) {
cmsg = "unsupported MD chosen by the client";
return -1;
}
// Deactivate
br->Deactivate(kXRS_md_alg);
} else {
NOTIFY("WARNING: client choice for digests missing"
" - using default");
md = "md5";
}
if (!(sessionMD = sessionCF->MsgDigest(md.c_str()))) {
cmsg = "could not instantiate digest object";
return -1;
}
// We are done
return 0;
}
//_________________________________________________________________________
int XrdSecProtocolgsi::ServerDoSigpxy(XrdSutBuffer *br, XrdSutBuffer **bm,
String &cmsg)
{
// Server side: process a kXGC_sigpxy message.
// Return 0 on success, -1 on error. If the case, a message is returned
// in cmsg.
EPNAME("ServerDoSigpxy");
XrdSutBucket *bck = 0;
XrdSutBucket *bckm = 0;
//
// Extract the main buffer
if (!(bckm = br->GetBucket(kXRS_main))) {
cmsg = "main buffer missing";
return 0;
}
//
// Decrypt the main buffer with the session cipher, if available
if (sessionKey) {
if (!(sessionKey->Decrypt(*bckm, useIV))) {
cmsg = "error decrypting main buffer with session cipher";
return 0;
}
}
//
// Deserialize main buffer
if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) {
cmsg = "error deserializing main buffer";
return 0;
}
// Get the bucket
if (!(bck = (*bm)->GetBucket(kXRS_x509))) {
cmsg = "buffer with requested info missing";
// Is there a message from the client?
if (!(bck = (*bm)->GetBucket(kXRS_message))) {
// Yes: decode it and print it
String m;
bck->ToString(m);
DEBUG("msg from client: "<PxyChain;
if (!pxyc) {
cmsg = "the proxy chain is gone";
return 0;
}
// Action depend on the type of message
if ((hs->Options & kOptsFwdPxy)) {
// The bucket contains a private key to be added to the proxy
// public key
XrdCryptoRSA *kpx = pxyc->End()->PKI();
if (kpx->ImportPrivate(bck->buffer, bck->size) != 0) {
cmsg = "problems importing private key";
return 0;
}
} else {
// The bucket contains our request signed by the client
// The full key is in the cache
if (!hs->Cref) {
cmsg = "session cache has gone";
return 0;
}
// Get the signed certificate
XrdCryptoX509 *npx = sessionCF->X509(bck);
if (!npx) {
cmsg = "could not resolve signed request";
return 0;
}
// Set full PKI
XrdCryptoRSA *knpx = (XrdCryptoRSA *)(hs->Cref->buf4.buf);
npx->SetPKI((XrdCryptoX509data)(knpx->Opaque()));
// Add the new proxy ecert to the chain
pxyc->PushBack(npx);
}
// Save the chain in the instance
proxyChain = pxyc;
hs->PxyChain = 0;
// Notify
if (QTRACE(Authen)) { proxyChain->Dump(); }
// Check if the proxy chain is to become the actual credentials
//
if ((PxyReqOpts & kOptsPxCred)) {
XrdCryptoX509ExportChain_t c2mem =
(sessionCF) ? sessionCF->X509ExportChain() : 0;
if (!c2mem) {
cmsg = "chain exporter not found; proxy chain not exported";
return 0;
}
XrdOucString spxy;
XrdSutBucket *bpxy = (*c2mem)(proxyChain, true);
bpxy->ToString(spxy);
if (Entity.credslen > 0) SafeFree(Entity.creds);
Entity.creds = strdup(spxy.c_str());
Entity.credslen = spxy.length();
DEBUG("proxy chain exported in Entity.creds (" << Entity.credslen << " bytes)");
DEBUG("\n\n" << spxy.c_str() << "\n\n");
delete bpxy;
return 0;
}
//
// Extract user login name, if any
String user;
if ((bck = (*bm)->GetBucket(kXRS_user))) {
bck->ToString(user);
(*bm)->Deactivate(kXRS_user);
}
if (user.length() <= 0) user = Entity.name;
// Dump to file if required
if ((PxyReqOpts & kOptsPxFile)) {
if (user.length() > 0) {
String pxfile = UsrProxy, name;
struct passwd *pw = getpwnam(user.c_str());
if (pw) {
name = pw->pw_name;
} else {
// Get Hash of the subject
XrdCryptoX509 *c = proxyChain->SearchBySubject(proxyChain->EECname());
if (c) {
name = c->SubjectHash();
} else {
cmsg = "proxy chain not dumped to file: could not find subject hash";
return 0;
}
}
if (XrdSutResolve(pxfile, Entity.host,
Entity.vorg, Entity.grps, name.c_str()) != 0) {
PRINT("Problems resolving templates in "< placeholder
if (pw && pxfile.find("") != STR_NPOS) {
String suid; suid += (int) pw->pw_uid;
pxfile.replace("", suid.c_str());
}
// Get the function
XrdCryptoX509ChainToFile_t ctofile = sessionCF->X509ChainToFile();
if ((*ctofile)(proxyChain,pxfile.c_str()) != 0) {
cmsg = "problems dumping proxy chain to file ";
cmsg += pxfile;
return 0;
}
PRINT("proxy chain dumped to "<< pxfile);
} else {
cmsg = "proxy chain not dumped to file: entity name undefined";
return 0;
}
}
// We are done
return 0;
}
//__________________________________________________________________
void XrdSecProtocolgsi::ErrF(XrdOucErrInfo *einfo, kXR_int32 ecode,
const char *msg1, const char *msg2,
const char *msg3)
{
// Filling the error structure
EPNAME("ErrF");
char *msgv[12];
int k, i = 0, sz = strlen("Secgsi");
//
// Code message, if any
int cm = (ecode >= kGSErrParseBuffer &&
ecode <= kGSErrError) ? (ecode-kGSErrParseBuffer) : -1;
const char *cmsg = (cm > -1) ? gGSErrStr[cm] : 0;
//
// Build error message array
msgv[i++] = (char *)"Secgsi"; //0
if (cmsg) {msgv[i++] = (char *)": "; //1
msgv[i++] = (char *)cmsg; //2
sz += strlen(msgv[i-1]) + 2;
}
if (msg1) {msgv[i++] = (char *)": "; //3
msgv[i++] = (char *)msg1; //4
sz += strlen(msgv[i-1]) + 2;
}
if (msg2) {msgv[i++] = (char *)": "; //5
msgv[i++] = (char *)msg2; //6
sz += strlen(msgv[i-1]) + 2;
}
if (msg3) {msgv[i++] = (char *)": "; //7
msgv[i++] = (char *)msg3; //8
sz += strlen(msgv[i-1]) + 2;
}
// save it (or print it)
if (einfo) {
einfo->setErrInfo(ecode, (const char **)msgv, i);
}
if (QTRACE(Debug)) {
char *bout = new char[sz+10];
if (bout) {
bout[0] = 0;
for (k = 0; k < i; k++)
strcat(bout, msgv[k]);
DEBUG(bout);
} else {
for (k = 0; k < i; k++)
DEBUG(msgv[k]);
}
}
}
//__________________________________________________________________
XrdSecCredentials *XrdSecProtocolgsi::ErrC(XrdOucErrInfo *einfo,
XrdSutBuffer *b1,
XrdSutBuffer *b2,
XrdSutBuffer *b3,
kXR_int32 ecode,
const char *msg1,
const char *msg2,
const char *msg3)
{
// Error logging client method
// Fill the error structure
ErrF(einfo, ecode, msg1, msg2, msg3);
// Release buffers
REL3(b1,b2,b3);
// We are done
return (XrdSecCredentials *)0;
}
//__________________________________________________________________
int XrdSecProtocolgsi::ErrS(String ID, XrdOucErrInfo *einfo,
XrdSutBuffer *b1, XrdSutBuffer *b2,
XrdSutBuffer *b3, kXR_int32 ecode,
const char *msg1, const char *msg2,
const char *msg3)
{
// Error logging server method
// Fill the error structure
ErrF(einfo, ecode, msg1, msg2, msg3);
// Release buffers
REL3(b1,b2,b3);
// We are done
return kgST_error;
}
//______________________________________________________________________________
bool XrdSecProtocolgsi::CheckRtag(XrdSutBuffer *bm, String &emsg)
{
// Check random tag signature if it was sent with previous packet
EPNAME("CheckRtag");
// Make sure we got a buffer
if (!bm) {
emsg = "Buffer not defined";
return 0;
}
//
// If we sent out a random tag check its signature
if (hs->Cref && hs->Cref->buf1.len > 0) {
XrdSutBucket *brt = 0;
if ((brt = bm->GetBucket(kXRS_signed_rtag))) {
// Make sure we got the right key to decrypt
if (!(sessionKver)) {
emsg = "Session cipher undefined";
return 0;
}
// Decrypt it with the counter part public key
if (sessionKver->DecryptPublic(*brt) <= 0) {
emsg = "error decrypting random tag with public key";
return 0;
}
} else {
emsg = "random tag missing - protocol error";
return 0;
}
//
// Random tag cross-check: content
if (memcmp(brt->buffer,hs->Cref->buf1.buf,hs->Cref->buf1.len)) {
emsg = "random tag content mismatch";
SafeDelete(hs->Cref);
// Remove: should not be checked a second time
return 0;
}
//
// Reset the cache entry but we will not use the info a second time
memset(hs->Cref->buf1.buf,0,hs->Cref->buf1.len);
hs->Cref->buf1.SetBuf();
//
// Flag successful check
hs->RtagOK = 1;
bm->Deactivate(kXRS_signed_rtag);
DEBUG("Random tag successfully checked");
} else {
DEBUG("Nothing to check");
}
// We are done
return 1;
}
//______________________________________________________________________________
XrdCryptoX509Crl *XrdSecProtocolgsi::LoadCRL(XrdCryptoX509 *xca, const char *subjhash,
XrdCryptoFactory *CF, int dwld, int &errcrl)
{
// Scan crldir for a valid CRL certificate associated to CA whose
// certificate is xca. If 'dwld' is true try to download the CRL from
// the relevant URI, if any.
// If the CRL is found and is valid according
// to the chosen option, return its content in a X509Crl object.
// Return 0 in any other case
EPNAME("LoadCRL");
XrdCryptoX509Crl *crl = 0;
errcrl = 0;
// make sure we got what we need
if (!xca || !CF) {
PRINT("Invalid inputs");
errcrl = -1;
return crl;
}
// Get the CA hash
String cahash(subjhash);
int hashalg = 0;
if (strcmp(subjhash, xca->SubjectHash())) hashalg = 1;
// Drop the extension (".0")
String caroot(cahash, 0, cahash.find(".0")-1);
// The dir
String crlext = XrdSecProtocolgsi::DefCRLext;
String crldir;
int from = 0;
while ((from = CRLdir.tokenize(crldir, from, ',')) != -1) {
if (crldir.length() <= 0) continue;
// Add the default CRL extension and the dir
String crlfile = crldir + caroot;
crlfile += crlext;
DEBUG("target file: "<X509Crl(crlfile.c_str()))) {
if ((errcrl = VerifyCRL(crl, xca, crldir, CF, hashalg)) == 0) return crl;
}
SafeDelete(crl);
}
// If not required, we are done
if (CRLCheck < 2 || (dwld == 0)) {
// Done
return crl;
}
// If in 'required' mode, we will also try to load the CRL from the
// information found in the CA certificate or in the certificate directory.
// To avoid this overload, the CRL information should be installed offline, e.g. with
// utils/getCRLcert
errcrl = 0;
// Try to retrieve it from the URI in the CA certificate, if any
if ((crl = CF->X509Crl(xca))) {
if ((errcrl = VerifyCRL(crl, xca, crldir, CF, hashalg)) == 0) return crl;
SafeDelete(crl);
}
// Finally try the ".crl_url" file
from = 0;
while ((from = CRLdir.tokenize(crldir, from, ',')) != -1) {
if (crldir.length() <= 0) continue;
SafeDelete(crl);
String crlurl = crldir + caroot;
crlurl += ".crl_url";
DEBUG("target file: "<X509Crl(line, 1))) {
if ((errcrl = VerifyCRL(crl, xca, crldir, CF, hashalg)) == 0) return crl;
SafeDelete(crl);
}
}
}
// We need to parse the full dirs: make some cleanup first
from = 0;
while ((from = CRLdir.tokenize(crldir, from, ',')) != -1) {
if (crldir.length() <= 0) continue;
SafeDelete(crl);
// Open directory
DIR *dd = opendir(crldir.c_str());
if (!dd) {
PRINT("could not open directory: "<d_name)) continue;
// File name contain the root CA hash
if (!strstr(dent->d_name,caroot.c_str())) continue;
// candidate name
String crlfile = crldir + dent->d_name;
DEBUG("analysing entry "<X509Crl(crlfile.c_str()))) {
if ((errcrl = VerifyCRL(crl, xca, crldir, CF, hashalg)) == 0) break;
SafeDelete(crl);
}
}
// Close dir
closedir(dd);
// Are we done?
if (crl) break;
}
// We are done
return crl;
}
//______________________________________________________________________________
int XrdSecProtocolgsi::VerifyCRL(XrdCryptoX509Crl *crl, XrdCryptoX509 *xca, String crldir,
XrdCryptoFactory *CF, int hashalg)
{
EPNAME("VerifyCRL");
int rc = 0;
// Make sure they have the same issuer
if (!strcmp(xca->SubjectHash(hashalg), crl->IssuerHash(hashalg))) {
// Signing certificate file
String casigfile = crldir + crl->IssuerHash(hashalg);
DEBUG("CA signing certificate file = "<X509(casigfile.c_str()))) {
if (CRLCheck >= 2) {
PRINT("CA certificate to verify the signature ("<IssuerHash(hashalg)<<
") could not be loaded - exit");
} else {
DEBUG("CA certificate to verify the signature could not be loaded - verification skipped");
}
rc = -3;
} else {
// Verify signature
if (crl->Verify(xcasig)) {
// Ok, we are done
if (CRLCheck >= 3 && crl && crl->IsExpired()) {
rc = -5;
NOTIFY("CRL is expired (CRLCheck: "<SubjectHash(hashalg)<<
" does not match CRL issuer "<IssuerHash(hashalg)<<"! ");
}
return rc;
}
//______________________________________________________________________________
String XrdSecProtocolgsi::GetCApath(const char *cahash)
{
// Look in the paths defined by CAdir for the certificate file related to
// 'cahash', in the form /.0
String path;
String ent;
int from = 0;
while ((from = CAdir.tokenize(ent, from, ',')) != -1) {
if (ent.length() > 0) {
path = ent;
if (!path.endswith('/'))
path += "/";
path += cahash;
if (!path.endswith(".0"))
path += ".0";
if (!access(path.c_str(), R_OK))
break;
}
path = "";
}
// Done
return path;
}
//______________________________________________________________________________
bool XrdSecProtocolgsi::VerifyCA(int opt, X509Chain *cca, XrdCryptoFactory *CF)
{
// Verify the CA in 'cca' according to 'opt':
// opt = 2 full check
// 1 only if self-signed
// 0 no check
EPNAME("VerifyCA");
bool verified = 0;
XrdCryptoX509Chain::ECAStatus st = XrdCryptoX509Chain::kUnknown;
cca->SetStatusCA(st);
// We nust have got a chain
if (!cca) {
PRINT("Invalid input ");
return 0;
}
// Get the parse function
XrdCryptoX509ParseFile_t ParseFile = CF->X509ParseFile();
if (!ParseFile) {
PRINT("Cannot attach to the ParseFile function");
return 0;
}
// Point to the certificate
XrdCryptoX509 *xc = cca->Begin();
if (!xc) {
PRINT("Cannot attach to first certificate in chain");
return 0;
}
// Make sure it is valid
if (!(xc->IsValid())) {
PRINT("CA certificate is expired ("<SubjectHash()<<", not_before: "<NotBefore()<<" secs UTC )");
return 0;
}
// Is it self-signed ?
bool self = (!strcmp(xc->IssuerHash(), xc->SubjectHash())) ? 1 : 0;
if (!self) {
String inam;
if (opt == 2) {
// We are requested to verify it
bool notdone = 1;
// We need to load the issuer(s) CA(s)
XrdCryptoX509 *xd = xc;
while (notdone) {
X509Chain *ch = 0;
int ncis = -1;
for (int ha = 0; ha < 2; ha++) {
inam = GetCApath(xd->IssuerHash(ha));
if (inam.length() <= 0) continue;
ch = new X509Chain();
ncis = (*ParseFile)(inam.c_str(), ch);
if (ncis >= 1) break;
SafeDelete(ch);
}
if (ncis < 1) break;
XrdCryptoX509 *xi = ch->Begin();
while (xi) {
if (!strcmp(xd->IssuerHash(), xi->SubjectHash()))
break;
xi = ch->Next();
}
if (xi) {
// Add the certificate to the requested CA chain
ch->Remove(xi);
cca->PutInFront(xi);
SafeDelete(ch);
// We may be over
if (!strcmp(xi->IssuerHash(), xi->SubjectHash())) {
notdone = 0;
break;
} else {
// This becomes the daughter
xd = xi;
}
} else {
break;
}
}
if (!notdone) {
// Verify the chain
X509Chain::EX509ChainErr e;
x509ChainVerifyOpt_t vopt = {kOptsCheckSubCA, 0, -1, 0};
if (!(verified = cca->Verify(e, &vopt)))
PRINT("CA certificate not self-signed: verification failed for '"<SubjectHash()<<"': error: "<< cca->X509ChainError(e));
} else {
PRINT("CA certificate not self-signed: cannot verify integrity ("<SubjectHash()<<")");
}
} else {
// Fill CA information
cca->CheckCA(0);
// Set OK in any case
verified = 1;
// Notify if some sort of check was required
if (opt == 1) {
NOTIFY("Warning: CA certificate not self-signed and"
" integrity not checked: assuming OK ("<SubjectHash()<<")");
}
}
} else {
if (CACheck > 0) {
// Check self-signature
if (!(verified = cca->CheckCA()))
PRINT("CA certificate self-signed: integrity check failed ("<SubjectHash()<<")");
} else {
// Set OK in any case
verified = 1;
// Notify if some sort of check was required
NOTIFY("Warning: CA certificate self-signed but"
" integrity not checked: assuming OK ("<SubjectHash()<<")");
}
}
// Set the status in the chain
st = (verified) ? XrdCryptoX509Chain::kValid : st;
cca->SetStatusCA(st);
// Done
return verified;
}
//_____________________________________________________________________________
static bool GetCACheck(XrdSutCacheEntry *e, void *a) {
EPNAME("GetCACheck");
int crl_check = (*((XrdSutCacheArg_t *)a)).arg1;
int crl_refresh = (*((XrdSutCacheArg_t *)a)).arg2;
time_t ts_ref = (time_t)(*((XrdSutCacheArg_t *)a)).arg3;
if (!e) return false;
X509Chain *chain = 0;
// If we had already something, check it, as we may be done
bool goodca = 0;
if ((chain = (X509Chain *)(e->buf1.buf))) {
// Check the validity of the certificates in the chain; if a certificate became invalid,
// we need to reload a valid one for the same CA.
if (chain->CheckValidity() == 0) {
goodca = 1;
} else {
PRINT("CA entry for '"<name<<"' needs refreshing: clean the related entry cache first");
return false;
}
}
if (goodca) {
XrdCryptoX509Crl *crl = (XrdCryptoX509Crl *)(e->buf2.buf);
bool goodcrl = 1;
if ((crl_check == 2 && !crl) || (crl_check == 3 && crl->IsExpired())) goodcrl = 0;
if (crl_refresh > 0 && ((ts_ref - e->mtime) > crl_refresh)) goodcrl = 0;
if (goodcrl) {
return true;
} else if (crl) {
PRINT("CRL entry for '"<name<<"' needs refreshing: clean the related entry cache first ("</.0 .
// If 'hs' is defined, store pointers to chain and crl into 'hs'.
// Return 0 if ok, -1 if not available, -2 if CRL not ok
EPNAME("GetCA");
XrdSutCERef ceref;
int rc = 0;
// We nust have got a CA hash
if (!cahash || !cf) {
PRINT("Invalid input ");
return -1;
}
// Timestamp
time_t timestamp = (hs) ? hs->TimeStamp : time(0);
// The tag
String tag(cahash,20);
tag += ':';
tag += cf->ID();
DEBUG("Querying cache for tag: "<rwmtx));
// Point to the content
X509Chain *chain = (X509Chain *)(cent->buf1.buf);
XrdCryptoX509Crl *crl = (XrdCryptoX509Crl *)(cent->buf2.buf);
// If invalid we fail
if (cent->status == kCE_inactive) {
// Cleanup and remove existing invalid entries
if (chain) stackCA.Del(chain);
if (crl) stackCRL.Del(crl);
PRINT("unable to get a valid entry from cache for " << tag);
return -1;
}
// Check if we are done
if (rdlock) {
// Save chain
if (hs) hs->Chain = chain;
stackCA.Add(chain);
// Save crl
if (crl) {
if (hs) hs->Crl = crl;
// Add to the stack for proper cleaning of invalidated CRLs
stackCRL.Add(crl);
}
return 0;
}
// Cleanup and remove existing invalid entries
if (chain) stackCA.Del(chain);
if (crl) stackCRL.Del(crl);
chain = 0;
crl = 0;
cent->buf1.buf = 0;
cent->buf2.buf = 0;
// If not, prepare the file name
String fnam = GetCApath(cahash);
DEBUG("trying to load CA certificate from "<Chain) ? 0 : 1;
chain = (createchain) ? new X509Chain() : hs->Chain;
if (!chain) {
PRINT("could not attach-to or create new GSI chain");
rc = -1;
}
// Get the parse function
XrdCryptoX509ParseFile_t ParseFile = cf->X509ParseFile();
if (rc == 0 && ParseFile) {
int nci = (createchain) ? (*ParseFile)(fnam.c_str(), chain) : 1;
bool ok = 0, verified = 0;
if (nci == 1) {
// Verify the CA
verified = VerifyCA(CACheck, chain, cf);
XrdCryptoX509Crl *crl = 0;
if (verified) {
// Get CRL, if required
ok = 1;
if (CRLCheck > 0) {
int errcrl = 0;
if ((crl = LoadCRL(chain->EffCA(), cahash, cf, CRLDownload, errcrl))) {
// Good CA
DEBUG("CRL successfully loaded");
} else {
String em = "missing or expired: ignoring";
if ((CRLCheck == 1 && errcrl != 0 && errcrl != -5) || (CRLCheck >= 2 && errcrl != 0)) {
ok = 0;
em = "invalid: failing";
} else if (CRLCheck >= 2) {
ok = 0;
em = "missing or expired: failing";
}
NOTIFY("CRL is "<buf1.buf = (char *)(chain);
cent->buf1.len = 0; // Just a flag
stackCA.Add(chain);
if (crl) {
cent->buf2.buf = (char *)(crl);
cent->buf2.len = 0; // Just a flag
stackCRL.Add(crl);
}
cent->mtime = timestamp;
cent->status = kCE_ok;
cent->cnt = 0;
// Fill output, if required
if (hs) {
hs->Chain = chain;
hs->Crl = crl;
if (strcmp(cahash, chain->Begin()->SubjectHash())) hs->HashAlg = 1;
}
} else {
SafeDelete(crl);
SafeDelete(chain);
rc = -2;
}
} else {
SafeDelete(chain);
NOTIFY("certificate not found or invalid (nci: "<key, &st) != 0) {
DEBUG("cannot access private key file: "<key);
return 1;
}
if (!S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) ||
(st.st_mode & (S_IWGRP | S_IWOTH)) != 0 ||
(st.st_mode & (S_IRGRP | S_IROTH)) != 0) {
DEBUG("wrong permissions for file: "<key<< " (should be 0600)");
return 1;
}
//
// Validity
int valid = (pi->valid) ? XrdSutParseTime(pi->valid, 1) : -1;
//
// Options
XrdProxyOpt_t pxopt = {pi->bits, // bits in key
valid, // duration validity in secs
pi->deplen}; // signature path depth
//
// Init now
XrdCryptoX509CreateProxy_t X509CreateProxy = cf->X509CreateProxy();
if (!X509CreateProxy) {
PRINT("cannot attach to X509CreateProxy function!");
return 1;
}
rc = (*X509CreateProxy)(pi->cert, pi->key, &pxopt, ch, kp, pi->out);
#else
// command string
String cmd(kMAXBUFLEN);
// Check if GLOBUS_LOCATION is defined
if (getenv("GLOBUS_LOCATION"))
cmd = "source $GLOBUS_LOCATION/etc/globus-user-env.sh;";
// Add main command
cmd += " grid-proxy-init";
// Add user cert
cmd += " -cert ";
cmd += pi->cert;
// Add user key
cmd += " -key ";
cmd += pi->key;
// Add CA dir (no support for multi-dirs)
String cdir(pi->certdir);
cdir.erase(cdir.find(','));
cmd += " -certdir ";
cmd += cdir;
// Add validity
if (pi->valid) {
cmd += " -valid ";
cmd += pi->valid;
}
// Add number of bits in key
if (pi->bits > 512) {
cmd += " -bits ";
cmd += pi->bits;
}
// Add depth of signature path
if (pi->deplen > -1) {
cmd += " -path-length ";
cmd += pi->deplen;
}
// Add output proxy coordinates
if (pi->out) {
cmd += " -out ";
cmd += pi->out;
}
// Notify
DEBUG("executing: " << cmd);
// Execute
rc = system(cmd.c_str());
DEBUG("return code: "<< rc << " (0x"<<(int *)rc<<")");
#endif
// We are done
return rc;
}
//__________________________________________________________________________
int XrdSecProtocolgsi::ParseCAlist(String calist)
{
// Parse received ca list, find the first available CA in the list
// and return a chain initialized with such a CA.
// If nothing found return 0.
EPNAME("ParseCAlist");
// Check inputs
if (calist.length() <= 0) {
PRINT("nothing to parse");
return -1;
}
DEBUG("parsing list: "<Chain = 0;
String cahash = "";
// Parse list
if (calist.length()) {
int from = 0;
while ((from = calist.tokenize(cahash, from, '|')) != -1) {
// Check this hash
if (cahash.length()) {
// Make sure the extension ".0" if there, as external implementations may not
// include it
if (!cahash.endswith(".0")) cahash += ".0";
// Get the CA chain
if (GetCA(cahash.c_str(), sessionCF, hs) == 0)
return 0;
}
}
}
// We did not find it
return -1;
}
//__________________________________________________________________________
int XrdSecProtocolgsi::ParseCrypto(String clist)
{
// Parse crypto list clist, extracting the first available module
// and getting a related local cipher and a related reference
// cipher to be used to agree the session cipher; the local lists
// crypto info is updated, if needed
// The results are used to fill the handshake part of the protocol
// instance.
EPNAME("ParseCrypto");
// Check inputs
if (clist.length() <= 0) {
NOTIFY("empty list: nothing to parse");
return -1;
}
DEBUG("parsing list: "<CryptoMod = "";
// Parse list
int from = 0;
while ((from = clist.tokenize(hs->CryptoMod, from, '|')) != -1) {
// Check this module
if (hs->CryptoMod.length() > 0) {
DEBUG("found module: "<CryptoMod);
// Padding support?
bool otherHasPad = true;
if (hs->RemVers >= XrdSecgsiVersDHsigned) {
if (hs->CryptoMod.endswith(gNoPadTag)) {
otherHasPad = false;
hs->CryptoMod.replace(gNoPadTag, "");
}
} else {
otherHasPad = false;
}
// Load the crypto factory
if ((sessionCF =
XrdCryptoFactory::GetCryptoFactory(hs->CryptoMod.c_str()))) {
sessionCF->SetTrace(GSITrace->What);
if (QTRACE(Debug)) sessionCF->Notify();
if (otherHasPad && sessionCF->HasPaddingSupport()) hs->HasPad = 1;
int fid = sessionCF->ID();
int i = 0;
// Retrieve the index in local table
while (i < ncrypt) {
if (cryptID[i] == fid) break;
i++;
}
if (i >= ncrypt) {
if (ncrypt == XrdCryptoMax) {
DEBUG("max number of crypto slots reached - do nothing");
return 0;
} else {
// Add new entry
cryptF[i] = sessionCF;
cryptID[i] = fid;
ncrypt++;
}
}
// On servers the ref cipher should be defined at this point
hs->Rcip = sessionCF->Cipher(hs->HasPad, 0,0,0);
// we are done
return 0;
}
}
}
// Nothing found
return -1;
}
//_____________________________________________________________________________
static bool QueryProxyCheck(XrdSutCacheEntry *e, void *a) {
time_t ts_ref = (time_t)(*((XrdSutCacheArg_t *)a)).arg1;
if (e && e->buf1.buf) {
X509Chain *chain = (X509Chain *)(e->buf1.buf);
if (chain->CheckValidity(1, ts_ref) == 0) return true;
}
return false;
}
//__________________________________________________________________________
int XrdSecProtocolgsi::QueryProxy(bool checkcache, XrdSutCache *cache,
const char *tag, XrdCryptoFactory *cf,
time_t timestamp, ProxyIn_t *pi, ProxyOut_t *po)
{
// Query users proxies, initializing if needed
EPNAME("QueryProxy");
XrdSutCERef ceref;
bool hasproxy = 0;
// We may already loaded valid proxies
bool rdlock = false;
XrdSutCacheArg_t arg = {timestamp, -1, -1, -1};
XrdSutCacheEntry *cent = cache->Get(tag, rdlock, QueryProxyCheck, (void *) &arg);
if (!cent) {
PRINT("cannot get cache entry for: "<rwmtx));
if (checkcache && rdlock) {
po->chain = (X509Chain *)(cent->buf1.buf);
po->ksig = (XrdCryptoRSA *)(cent->buf2.buf);
po->cbck = (XrdSutBucket *)(cent->buf3.buf);
// We are done
ceref.UnLock();
return 0;
}
// Cleanup the chain
po->chain = (X509Chain *)(cent->buf1.buf);
if (po->chain) po->chain->Cleanup();
SafeDelete(po->chain);
// Cleanup cache entry
cent->buf1.buf = 0;
cent->buf1.len = 0;
// The key is deleted by the certificate destructor
// Just reset the buffer
cent->buf2.buf = 0;
cent->buf2.len = 0;
// and the related bucket
if (cent->buf3.buf)
delete (XrdSutBucket *)(cent->buf3.buf);
cent->buf3.buf = 0;
cent->buf3.len = 0;
//
// We do not have good proxies, try load (user may have initialized
// them in the meanwhile)
// Create a new chain first, if needed
if (!(po->chain))
po->chain = new X509Chain();
if (!(po->chain)) {
PRINT("cannot create new chain!");
return -1;
}
int ntry = 3;
bool parsefile = 1;
bool exportbucket = 0;
XrdCryptoX509ParseFile_t ParseFile = 0;
XrdCryptoX509ParseBucket_t ParseBucket = 0;
while (!hasproxy && ntry > 0) {
// Try init as last option
if (ntry == 1) {
// Cleanup the chain
po->chain->Cleanup();
if (InitProxy(pi, cf, po->chain, &(po->ksig)) != 0) {
NOTIFY("problems initializing proxy via external shell");
ntry--;
continue;
}
// We need to explicitely export the proxy in a bucket
exportbucket = 1;
#ifndef HASGRIDPROXYINIT
// Chain is already loaded if we used the internal function
// to initialize the proxies
parsefile = 0;
timestamp = time(0);
#endif
}
ntry--;
//
// A proxy chain may have been passed via XrdSecCREDS: check that first
if (ntry == 2) {
char *cbuf = getenv("XrdSecCREDS");
if (cbuf) {
// Import into a bucket
XrdSutBucket xbck(0, 0, kXRS_x509);
// Fill bucket
xbck.SetBuf(cbuf, strlen(cbuf));
// Parse the bucket
if (!(ParseBucket = cf->X509ParseBucket())) {
PRINT("cannot attach to ParseBucket function!");
continue;
}
int nci = (*ParseBucket)(&xbck, po->chain);
if (nci < 2) {
NOTIFY("proxy bucket must have at least two certificates"
" (found: "<X509ParseFile())) {
PRINT("cannot attach to ParseFile function!");
continue;
}
}
// Parse the proxy file
int nci = (*ParseFile)(pi->out, po->chain);
if (nci < 2) {
DEBUG("proxy files must have at least two certificates"
" (found: "< 1) ? 1 : 0;
po->chain->CheckCA(checkselfsigned);
exportbucket = 1;
}
}
// Check validity in time
if (po->chain->CheckValidity(1, timestamp) != 0) {
NOTIFY("proxy files contains expired certificates");
continue;
}
// Reorder chain
if (po->chain->Reorder() != 0) {
NOTIFY("proxy files contains inconsistent certificates");
continue;
}
// Check key
po->ksig = po->chain->End()->PKI();
if (po->ksig->status != XrdCryptoRSA::kComplete) {
NOTIFY("proxy files contain invalid key pair");
continue;
}
XrdCryptoX509ExportChain_t ExportChain = cf->X509ExportChain();
if (!ExportChain) {
PRINT("cannot attach to ExportChain function!");
continue;
}
// Create bucket for export
if (exportbucket) {
po->cbck = (*ExportChain)(po->chain, 0);
if (!(po->cbck)) {
PRINT("could not create bucket for export");
continue;
}
}
// Save info in cache
cent->mtime = po->chain->End()->NotAfter(); // the expiring time
cent->status = kCE_special; // distinguish from normal certs
cent->cnt = 0;
// The chain
cent->buf1.buf = (char *)(po->chain);
cent->buf1.len = 0; // Just a flag
// The key
cent->buf2.buf = (char *)(po->chain->End()->PKI());
cent->buf2.len = 0; // Just a flag
// The export bucket
cent->buf3.buf = (char *)(po->cbck);
cent->buf3.len = 0; // Just a flag
// Set the positive flag
hasproxy = 1;
}
// Always unlock
ceref.UnLock();
// We are done
if (!hasproxy) {
// Some cleanup
po->chain->Cleanup();
SafeDelete(po->chain);
SafeDelete(po->cbck);
return -1;
}
return 0;
}
//_____________________________________________________________________________
static bool QueryGMAPCheck(XrdSutCacheEntry *e, void *a) {
int st_ref = (*((XrdSutCacheArg_t *)a)).arg1;
time_t ts_ref = (time_t)(*((XrdSutCacheArg_t *)a)).arg2;
long to_ref = (*((XrdSutCacheArg_t *)a)).arg3;
if (e) {
// Check expiration, if required
if ((e->status != st_ref) ||
((e->status == st_ref) &&
(to_ref > 0) &&
((ts_ref - e->mtime) > to_ref))) {
return false;
} else {
return true;
}
}
return false;
}
//__________________________________________________________________________
void XrdSecProtocolgsi::QueryGMAP(XrdCryptoX509Chain *chain, int now, String &usrs)
{
// Resolve usernames associated with this proxy. The lookup is typically
// based on the 'dn' (either in the grid mapfile or via the 'GMAPFun' plugin) but
// it can also be based on the full proxy via the AuthzFun plugin.
// For 'grid mapfile' and 'GMAPFun' the result is kept valid for a certain amount
// of time, hashed on the 'dn'.
// On return, an empty string in 'usrs' indicates failure.
// Note that 'usrs' can be a comma-separated list of usernames.
EPNAME("QueryGMAP");
// List of user names attached to the entity
usrs = "";
// The chain must be defined
if (!chain) {
PRINT("input chain undefined!");
return;
}
// Now we check the DN-mapping function and eventually the gridmap file.
// The result can be cached for a while.
const char *dn = chain->EECname();
if (GMAPFun) {
XrdSutCERef ceref;
bool rdlock = false;
XrdSutCacheArg_t arg = {kCE_ok, now, GMAPCacheTimeOut, -1};
XrdSutCacheEntry *cent = cacheGMAPFun.Get(dn, rdlock, QueryGMAPCheck, (void *) &arg);
if (!cent) {
PRINT("unable to get a valid entry from cache for dn: " << dn);
return;
}
ceref.Set(&(cent->rwmtx));
// Check if we need to get/update the content
if (!rdlock) {
// Run the search via the external function
char *name = (*GMAPFun)(dn, now);
if (name) {
cent->status = kCE_ok;
// Add username
SafeDelArray(cent->buf1.buf);
cent->buf1.buf = name;
cent->buf1.len = strlen(name);
}
// Fill up the rest
cent->cnt = 0;
cent->mtime = now; // creation time
}
// Retrieve result form cache
usrs = cent->buf1.buf;
// We are done with the cache
ceref.UnLock();
}
// Check the map file, if any
//
if (servGMap) {
char u[65];
if (servGMap->dn2user(dn, u, sizeof(u), now) == 0) {
if (usrs.length() > 0) usrs += ",";
usrs += (const char *)u;
}
}
// Done
return;
}
//_____________________________________________________________________________
XrdSecgsiGMAP_t XrdSecProtocolgsi::LoadGMAPFun(const char *plugin,
const char *parms)
{
// Load the DN-Username mapping function from the specified plug-in
EPNAME("LoadGMAPFun");
char errBuff[2048];
// Make sure the input config file is defined
if (!plugin || strlen(plugin) <= 0) {
PRINT("plug-in file undefined");
return (XrdSecgsiGMAP_t)0;
}
// Create the plug-in instance
XrdOucPinLoader gmapLib(errBuff,sizeof(errBuff),gsiVersion,"gmaplib",plugin);
// Use global symbols?
bool useglobals = 0;
XrdOucString params, ps(parms), p;
int from = 0;
while ((from = ps.tokenize(p, from, '|')) != -1) {
if (p == "useglobals") {
useglobals = 1;
} else {
if (params.length() > 0) params += " ";
params += p;
}
}
DEBUG("params: '"<< params<<"'; useglobals: "< 0) params += " ";
params += p;
}
}
DEBUG("params: '"<< params<<"'; useglobals: "< 0) params += " ";
params += p;
}
}
DEBUG("params: '"<< params<<"'; useglobals: "<[/*]"
if (hname) {
size_t ih = srvcn.find("/");
if (ih != std::string::npos) {
srvcn.erasefromstart(ih + 1);
}
allowed = XrdCryptoX509::MatchHostnames(srvcn.c_str(), hname);
// Update the error msg, if the case
if (!allowed) {
if (emsg.length() <= 0) {
emsg = "server certificate CN '"; emsg += srvcn;
emsg += "' does not match the expected format(s):";
}
String defcn("[*/]"); defcn += hname; defcn += "[/*]";
emsg += " '"; emsg += defcn; emsg += "' (default)";
}
}
// Take into account specific requests, if any
if (SrvAllowedNames.length() > 0) {
// The SrvAllowedNames string contains the allowed formats separated by a '|'.
// The specifications can contain the or placeholders which
// are replaced by hname; they can also contain the '*' wildcard, in
// which case XrdOucString::matches is used. A '-' before the specification
// will deny the matching CN's; the last matching wins.
String allowedfmts(SrvAllowedNames);
allowedfmts.replace("", hname);
allowedfmts.replace("", hname);
int from = 0;
String fmt;
while ((from = allowedfmts.tokenize(fmt, from, '|')) != -1) {
// Check if this should be denied
bool deny = 0;
if (fmt.beginswith("-")) {
deny = 1;
fmt.erasefromstart(1);
}
if (srvcn.matches(fmt.c_str()) > 0) allowed = (deny) ? 0 : 1;
}
// Update the error msg, if the case
if (!allowed) {
if (emsg.length() <= 0) {
emsg = "server certificate CN '"; emsg += srvcn;
emsg += "' does not match the expected format:";
}
emsg += " '"; emsg += SrvAllowedNames; emsg += "' (exceptions)";
}
}
// Reset error msg, if the match was successful
if (allowed)
emsg = "";
else
emsg += "; exceptions are controlled by the env XrdSecGSISRVNAMES";
// Done
return allowed;
}
//_____________________________________________________________________________
static bool GetSrvCertEntCheck(XrdSutCacheEntry *e, void *a) {
int st_ref = (*((XrdSutCacheArg_t *)a)).arg1;
time_t ts_ref = (time_t)(*((XrdSutCacheArg_t *)a)).arg2;
if (e) {
if (e->status > st_ref) {
if (e->mtime >= ts_ref)
return true;
}
}
return false;
}
//_____________________________________________________________________________
XrdSutCacheEntry *XrdSecProtocolgsi::GetSrvCertEnt(XrdSutCERef &ceref,
XrdCryptoFactory *cf,
time_t timestamp, String &certcalist)
{
// Get cache entry for server certificate. This function checks the cache
// and loads or re-loads the certificate form the specified files if required.
// make sure we got what we need
EPNAME("GetSrvCertEnt");
if (!cf) {
PRINT("Invalid inputs");
return (XrdSutCacheEntry *)0;
}
bool rdlock = false;
XrdSutCacheArg_t arg = {kCE_allowed, timestamp, -1, -1};
XrdSutCacheEntry *cent = cacheCert.Get(cf->Name(), rdlock, GetSrvCertEntCheck, (void *) &arg);
if (!cent) {
PRINT("unable to get a valid entry from cache for " << cf->Name());
return (XrdSutCacheEntry *)0;
}
ceref.Set(&(cent->rwmtx));
// Are we done ?
if (rdlock) return cent;
if (cent->buf1.buf) PRINT("entry has expired: trying to renew ...");
// Try get one or renew-it
if (cent->status == kCE_special) {
// Try init proxies
ProxyIn_t pi = {SrvCert.c_str(), SrvKey.c_str(), CAdir.c_str(),
UsrProxy.c_str(), PxyValid.c_str(), 0, 512};
X509Chain *ch = 0;
XrdCryptoRSA *k = 0;
XrdSutBucket *b = 0;
ProxyOut_t po = {ch, k, b };
// We lock inside
ceref.UnLock(false);
if (QueryProxy(0, &cacheCert, cf->Name(), cf, timestamp, &pi, &po) != 0) {
PRINT("proxy expired and cannot be renewed");
return (XrdSutCacheEntry *)0;
}
// When successful we return read-locked (this flow needs checking; but it is not mainstream)
ceref.ReadLock();
return cent;
}
// Reset the entry
delete (XrdCryptoX509 *) cent->buf1.buf; // Destroys also xsrv->PKI() pointed in cent->buf2.buf
delete (XrdSutBucket *) cent->buf3.buf;
cent->buf1.buf = 0;
cent->buf2.buf = 0;
cent->buf3.buf = 0;
//
// Get the IDs of the file: we need them to acquire the right privileges when opening
// the certificate
uid_t gsi_uid = geteuid();
gid_t gsi_gid = getegid();
struct stat st;
if (!stat(SrvKey.c_str(), &st)) {
if (st.st_uid != gsi_uid || st.st_gid != gsi_gid) {
gsi_uid = st.st_uid;
gsi_gid = st.st_gid;
}
}
// Check normal certificates
XrdCryptoX509 *xsrv = cf->X509(SrvCert.c_str(), SrvKey.c_str());
if (xsrv) {
// Must be of EEC type
if (xsrv->type != XrdCryptoX509::kEEC) {
PRINT("problems loading srv cert: not EEC but: "<Type());
SafeDelete(xsrv);
ceref.UnLock();
return (XrdSutCacheEntry *)0;
}
// Must be valid
if (!(xsrv->IsValid())) {
PRINT("problems loading srv cert: invalid");
SafeDelete(xsrv);
ceref.UnLock();
return (XrdSutCacheEntry *)0;
}
// PKI must have been successfully initialized
if (!xsrv->PKI() || xsrv->PKI()->status != XrdCryptoRSA::kComplete) {
PRINT("problems loading srv cert: invalid PKI");
SafeDelete(xsrv);
ceref.UnLock();
return (XrdSutCacheEntry *)0;
}
// Must be exportable
XrdSutBucket *xbck = xsrv->Export();
if (!xbck) {
PRINT("problems loading srv cert: cannot export into bucket");
SafeDelete(xsrv);
ceref.UnLock();
return (XrdSutCacheEntry *)0;
}
// We must have the issuing CA certificate
int rcgetca = 0;
if ((rcgetca = GetCA(xsrv->IssuerHash(), cf)) != 0) {
String emsg(xsrv->IssuerHash());
// Try different name hash, if it makes sense
if (strcmp(xsrv->IssuerHash(1), xsrv->IssuerHash(0))) {
if ((rcgetca = GetCA(xsrv->IssuerHash(1), cf)) != 0) {
emsg += "|";
emsg += xsrv->IssuerHash(1);
}
}
if (rcgetca != 0) {
// We do not have it, really
if (rcgetca == -1) {
PRINT("do not have certificate for the issuing CA '"<status = kCE_ok;
cent->cnt = 0;
cent->mtime = xsrv->NotAfter(); // expiration time
// Save pointer to certificate (destroys also xsrv->PKI())
if (cent->buf1.buf) delete (XrdCryptoX509 *) cent->buf1.buf;
cent->buf1.buf = (char *)xsrv;
cent->buf1.len = 0; // just a flag
// Save pointer to key
cent->buf2.buf = 0;
cent->buf2.buf = (char *)(xsrv->PKI());
cent->buf2.len = 0; // just a flag
// Save pointer to bucket
if (cent->buf3.buf) delete (XrdSutBucket *) cent->buf3.buf;
cent->buf3.buf = (char *)(xbck);
cent->buf3.len = 0; // just a flag
// Save CA hash in list to communicate to clients
if (certcalist.find(xsrv->IssuerHash()) == STR_NPOS) {
if (certcalist.length() > 0) certcalist += "|";
certcalist += xsrv->IssuerHash();
}
// Save also old CA hash in list to communicate to clients, if relevant
if (HashCompatibility && xsrv->IssuerHash(1) &&
strcmp(xsrv->IssuerHash(1),xsrv->IssuerHash())) {
if (certcalist.find(xsrv->IssuerHash(1)) == STR_NPOS) {
if (certcalist.length() > 0) certcalist += "|";
certcalist += xsrv->IssuerHash(1);
}
}
} else {
PRINT("failed to load certificate from files ("<< SrvCert <<","<