Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client Certificate Authentication with R_usermap #371

Merged
merged 7 commits into from
Aug 11, 2023
81 changes: 67 additions & 14 deletions c/httpserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -2653,19 +2653,23 @@ static int safAuthenticate(HttpService *service, HttpRequest *request, AuthRespo
} else if (authDataFound){
ACEE *acee = NULL;
strupcase(request->username); /* upfold username */
if (request->password == NULL) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the password field to indicate whether this is a passwordless auth seems like a bad idea. If for some unexpected reason that field is NULL (is it NULL if a user just doesn't provide a password in the basic auth?), you'll assume this is just a passwordless auth and will use zisCheckUsername, which might say everything is okay.

This seems like an error prone solution with implicit logic.

Regardless of whether this approach with client certificates is correct in the first place, using a separate variable to explicitly indicate what needs to be checked to authenticate would probably be better.

zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_DEBUG3, "Password is null. Calling safAuthenticate without a password.\n");
} else {
#ifdef ENABLE_DANGEROUS_AUTH_TRACING
#ifdef METTLE
printf("SAF auth for user: '%s'\n", request->username);
printf("SAF auth for user: '%s'\n", request->username);
#else
printf("u: '%s' p: '%s'\n",request->username,request->password);
printf("u: '%s' p: '%s'\n",request->username,request->password);
#endif
#endif
if (isLowerCasePasswordAllowed() || isPassPhrase(request->password)) {
zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_DEBUG3, "mixed-case system or a pass phrase, not upfolding password\n");
/* don't upfold password */
} else {
zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_DEBUG3, "non-mixed-case system, not a pass phrase, upfolding password\n");
strupcase(request->password); /* upfold password */
if (isLowerCasePasswordAllowed() || isPassPhrase(request->password)) {
zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_DEBUG3, "mixed-case system or a pass phrase, not upfolding password\n");
/* don't upfold password */
} else {
zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_DEBUG3, "non-mixed-case system, not a pass phrase, upfolding password\n");
strupcase(request->password); /* upfold password */
}
Comment on lines +2661 to +2672
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these white space changes?

}

#if APF_AUTHORIZED
Expand All @@ -2675,10 +2679,17 @@ static int safAuthenticate(HttpService *service, HttpRequest *request, AuthRespo

CrossMemoryServerName *privilegedServerName = getConfiguredProperty(service->server, HTTP_SERVER_PRIVILEGED_SERVER_PROPERTY);
int pwdCheckRC = 0, pwdCheckRSN = 0;
pwdCheckRC = zisCheckUsernameAndPassword(privilegedServerName,
request->username, request->password, &status);
authResponse->type = AUTH_TYPE_RACF;
authResponse->responseDetails.safStatus = status.safStatus;
if (request->password != NULL) {
pwdCheckRC = zisCheckUsernameAndPassword(privilegedServerName,
request->username, request->password, &status);
authResponse->type = AUTH_TYPE_RACF;
authResponse->responseDetails.safStatus = status.safStatus;
} else {
pwdCheckRC = zisCheckUsername(privilegedServerName,
request->username, &status);
authResponse->type = AUTH_TYPE_RACF;
authResponse->responseDetails.safStatus = status.safStatus;
}
Comment on lines +2687 to +2692
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need more info about this and why we need this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Irek, we need either a new API, or a new arg in the existing API to say if password is required.


if (pwdCheckRC != 0) {
#ifdef DEBUG_AUTH
Expand Down Expand Up @@ -3142,7 +3153,7 @@ static int serviceAuthNativeWithSessionToken(HttpService *service, HttpRequest *
int authDataFound = FALSE;
HttpHeader *authenticationHeader = getHeader(request,"Authorization");
char *tokenCookieText = getCookieValue(request,getSessionTokenCookieName(service));

zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_DEBUG3,
"serviceAuthNativeWithSessionToken: authenticationHeader 0x%p\n",
"extractFunction 0x%p\n",
Expand All @@ -3162,9 +3173,51 @@ static int serviceAuthNativeWithSessionToken(HttpService *service, HttpRequest *
if (service->authExtractionFunction(service, request) == 0){
authDataFound = TRUE;
}
}
}

/* Doubtful that it would be greater than 8k... */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we know for sure? I.e. references to standards/docs would help here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the R_usermap doc for limits. Some 'net searching yielded 8K, blame me for this.


#define TLS_CLIENT_CERTIFICATE_MAX_LENGTH 8000

char clientCertificate[TLS_CLIENT_CERTIFICATE_MAX_LENGTH] = {0};
unsigned int clientCertificateLength = 0;

int rc = getClientCertificate(response->socket->tlsSocket->socketHandle, clientCertificate, sizeof(clientCertificate), &clientCertificateLength);
if (rc != 0) {
zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_DEBUG, "getClientCertificate - %d.\n", rc);
}

#ifdef ENABLE_DANGEROUS_AUTH_TRACING
/* We probably don't want to dump their certificate, right? */
dumpbuffer(clientCertificate, clientCertificateLength);
#endif

if (rc == 0 && clientCertificateLength > 0) {
zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_DEBUG, "There is a client certificate attached to the request.\n");
/*
* We don't want to do this if we already found authentication data.
*/
if (authDataFound == FALSE) {
#define TLS_USERID_LENGTH 9
char userid[TLS_USERID_LENGTH] = {0};
int racfReturnCode = 0, racfReasonCode = 0;
zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_INFO, "There was no token or credentials found in the request. Server is attempting to map the client certificate.\n");
int safReturnCode = getUseridByCertificate(clientCertificate, clientCertificateLength, userid, &racfReturnCode, &racfReasonCode);
if (safReturnCode == 0) {
request->username = userid;
zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_INFO, "Found user '%s' from client certificate.\n", request->username);
request->password = NULL;
// null password with a valid user tells the server we authenticated with a certificate
authDataFound = TRUE;
} else {
zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_INFO, "No user was found for client certificate. (rc = 0x%x racfRC = 0x%x racfRSN = 0x%x\n", safReturnCode, racfReturnCode, racfReasonCode);
}
} else {
zowelog(NULL, LOG_COMP_HTTPSERVER, ZOWE_LOG_INFO, "Client certificate was attached to request, but credentials are also attached. Server won't attempt to map the client certificate.\n");
}
}

response->sessionCookie = NULL;

AUTH_TRACE("AUTH: tokenCookieText: %s\n",(tokenCookieText ? tokenCookieText : "<noAuthToken>"));
Expand Down
63 changes: 59 additions & 4 deletions c/tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,40 @@
#include "fdpoll.h"
#include "tls.h"

int getClientCertificate(gsk_handle soc_handle, char *clientCertificate, unsigned int clientCertificateBufferSize, unsigned int *clientCertificateLength) {

if (clientCertificate == NULL || clientCertificateBufferSize <= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: clientCertificateBufferSize can't be less than 0.

return -1;
}

memset(clientCertificate, 0, clientCertificateBufferSize);
*clientCertificateLength = 0;

gsk_cert_data_elem *gskCertificateArray = NULL;
int gskCertificateArrayElementCount = 0;

int rc = gsk_attribute_get_cert_info(soc_handle, GSK_PARTNER_CERT_INFO, &gskCertificateArray, &gskCertificateArrayElementCount);

if (rc != 0) {
return rc;
}

gsk_cert_data_elem *tmp = gskCertificateArray;

for (int i = 0; i++ < gskCertificateArrayElementCount; tmp++) {
Copy link
Contributor

@ifakhrutdinov ifakhrutdinov Apr 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not increment in a condition; this seems error prone.

What about this?

for (int i = 0; i < gskCertificateArrayElementCount; i++) {
  const gsk_cert_data_elem *elem = &gskCertificateArray[i];
  . . .
}

if (tmp->cert_data_id == CERT_BODY_DER) {
if (clientCertificateBufferSize >= tmp->cert_data_l) {
memcpy(clientCertificate, tmp->cert_data_p, tmp->cert_data_l);
*clientCertificateLength = tmp->cert_data_l;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point we've copied the data and continue. Is it okay to overwrite it if one of the next array entries matches the condition or do we need to break here?

}
}
}

gsk_free_cert_data(gskCertificateArray, gskCertificateArrayElementCount);

return 0;
}

int tlsInit(TlsEnvironment **outEnv, TlsSettings *settings) {
int rc = 0;
TlsEnvironment *env = (TlsEnvironment *)safeMalloc(sizeof(*env), "Tls Environment");
Expand All @@ -29,6 +63,23 @@ int tlsInit(TlsEnvironment **outEnv, TlsSettings *settings) {
rc = rc || gsk_attribute_set_enum(env->envHandle, GSK_PROTOCOL_TLSV1_1, GSK_PROTOCOL_TLSV1_1_OFF);
rc = rc || gsk_attribute_set_enum(env->envHandle, GSK_PROTOCOL_TLSV1_2, GSK_PROTOCOL_TLSV1_2_ON);
rc = rc || gsk_attribute_set_enum(env->envHandle, GSK_SERVER_EPHEMERAL_DH_GROUP_SIZE, GSK_SERVER_EPHEMERAL_DH_GROUP_SIZE_2048);

/*
* Don't validate certificates, maybe put behind a dangerous ifdef.
*
* rc = rc || gsk_attribute_set_enum(env->envHandle, GSK_CLIENT_AUTH_TYPE, GSK_CLIENT_AUTH_PASSTHRU_TYPE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code?

*/

#ifdef DEV_DO_NOT_VALIDATE_CLIENT_CERTIFICATES
rc = rc || gsk_attribute_set_enum(env->envHandle, GSK_CLIENT_AUTH_TYPE, GSK_CLIENT_AUTH_PASSTHRU_TYPE);
#endif

/*
* Only allow requests with client certificates, maybe put behind a different ifdef.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code?

*
* rc = rc || gsk_attribute_set_enum(env->envHandle, GSK_CLIENT_AUTH_ALERT, GSK_CLIENT_AUTH_NOCERT_ALERT_ON);
*/

rc = rc || gsk_attribute_set_buffer(env->envHandle, GSK_KEYRING_FILE, settings->keyring, 0);
if (settings->stash) {
rc = rc || gsk_attribute_set_buffer(env->envHandle, GSK_KEYRING_STASH_FILE, settings->stash, 0);
Expand All @@ -43,13 +94,15 @@ int tlsInit(TlsEnvironment **outEnv, TlsSettings *settings) {
safeFree((char*)env, sizeof(*env));
*outEnv = NULL;
}
//printf("tlsInit - rc=%d\n", rc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please clean up dead code.

return rc;
}

int tlsDestroy(TlsEnvironment *env) {
int rc = 0;
rc = gsk_environment_close(env->envHandle);
safeFree((char*)env, sizeof(*env));
//printf("tlsDestroy - rc=%d\n", rc);
return rc;
}

Expand All @@ -72,6 +125,7 @@ static int secureSocketRecv(int fd, void *data, int len, char *userData) {
break;
}
}
//printf("secureSocketRecv = %d\n", rc);
return rc;
}

Expand All @@ -92,11 +146,12 @@ static int secureSocketSend(int fd, void *data, int len, char *userData) {
break;
}
}
//printf("secureSocketSend = %d\n", rc);
return rc;
}

int tlsSocketInit(TlsEnvironment *env, TlsSocket **outSocket, int fd, bool isServer) {
int rc = 0;
int rc = 0;
gsk_iocallback ioCallbacks = {secureSocketRecv, secureSocketSend, NULL, NULL, NULL, NULL};
TlsSocket *socket = (TlsSocket*)safeMalloc(sizeof(TlsSocket), "Tls Socket");
if (!socket) {
Expand All @@ -109,8 +164,7 @@ int tlsSocketInit(TlsEnvironment *env, TlsSocket **outSocket, int fd, bool isSer
if (label) {
rc = rc || gsk_attribute_set_buffer(socket->socketHandle, GSK_KEYRING_LABEL, label, 0);
}
rc = rc || gsk_attribute_set_enum(socket->socketHandle, GSK_SESSION_TYPE,
isServer ? GSK_SERVER_SESSION : GSK_CLIENT_SESSION);
rc = rc || gsk_attribute_set_enum(socket->socketHandle, GSK_SESSION_TYPE, isServer ? GSK_SERVER_SESSION_WITH_CL_AUTH : GSK_CLIENT_SESSION);
Copy link
Contributor

@ifakhrutdinov ifakhrutdinov Apr 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we never be able to use this without a client certificate on the server side? Asking because it's either GSK_SERVER_SESSION_WITH_CL_AUTH or GSK_CLIENT_SESSION.

I can see cases where one would want to use this code just for a secure connection but without client authentication required.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my understanding and testing, it just allows client authentication to be an option if a client certificate is attached. It behaves the same way as usual if you don't provide a client certificate.

if (ciphers) {
rc = rc || gsk_attribute_set_buffer(socket->socketHandle, GSK_V3_CIPHER_SPECS_EXPANDED, ciphers, 0);
rc = rc || gsk_attribute_set_enum(socket->socketHandle, GSK_V3_CIPHERS, GSK_V3_CIPHERS_CHAR4);
Expand All @@ -123,6 +177,7 @@ int tlsSocketInit(TlsEnvironment *env, TlsSocket **outSocket, int fd, bool isSer
safeFree((char*)socket, sizeof(*socket));
*outSocket = NULL;
}
//printf("tlsSocketInit - rc=%d\n", rc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this.

return rc;
}

Expand Down
1 change: 1 addition & 0 deletions h/tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ int tlsSocketClose(TlsSocket *socket);
int tlsRead(TlsSocket *socket, const char *buf, int size, int *outLength);
int tlsWrite(TlsSocket *socket, const char *buf, int size, int *outLength);
const char *tlsStrError(int rc);
int getClientCertificate(gsk_handle soc_handle, char *clientCertificate, unsigned int clientCertificateBufferSize, unsigned int *clientCertificateLength);

#define TLS_ALLOC_ERROR (-1)

Expand Down