my_bool ma_schannel_verify_certs(SC_CTX *sctx, DWORD dwCertFlags) { SECURITY_STATUS sRet; DWORD flags; MARIADB_PVIO *pvio= sctx->mysql->net.pvio; PCCERT_CONTEXT pServerCert= NULL; if ((sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert)) != SEC_E_OK) { ma_schannel_set_sec_error(pvio, sRet); return 0; } flags= CERT_STORE_SIGNATURE_FLAG | CERT_STORE_TIME_VALIDITY_FLAG; if (sctx->client_ca_ctx) { if (!(sRet= CertVerifySubjectCertificateContext(pServerCert, sctx->client_ca_ctx, &flags))) { ma_schannel_set_win_error(pvio); return 0; } if (flags) { if ((flags & CERT_STORE_SIGNATURE_FLAG) != 0) pvio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Certificate signature check failed"); else if ((flags & CERT_STORE_REVOCATION_FLAG) != 0) pvio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "certificate was revoked"); else if ((flags & CERT_STORE_TIME_VALIDITY_FLAG) != 0) pvio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "certificate has expired"); else pvio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown error during certificate validation"); return 0; } } /* Check if none of the certificates in the certificate chain have been revoked. */ if (sctx->client_crl_ctx) { PCRL_INFO Info[1]; Info[0]= sctx->client_crl_ctx->pCrlInfo; if (!(CertVerifyCRLRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pServerCert->pCertInfo, 1, Info)) ) { pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "CRL Revocation failed"); return 0; } } return 1; }
SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, PCredHandle phCreds, CtxtHandle * phContext, DWORD *DecryptLength, uchar *ReadBuffer, DWORD ReadBufferSize) { DWORD dwBytesRead= 0; DWORD dwOffset= 0; SC_CTX *sctx; SECURITY_STATUS sRet= 0; SecBufferDesc Msg; SecBuffer Buffers[4], *pData, *pExtra; int i; if (!pvio || !pvio->methods || !pvio->methods->read || !pvio->ctls || !DecryptLength) return SEC_E_INTERNAL_ERROR; sctx= (SC_CTX *)pvio->ctls->ssl; *DecryptLength= 0; while (1) { if (!dwBytesRead || sRet == SEC_E_INCOMPLETE_MESSAGE) { dwBytesRead= (DWORD)pvio->methods->read(pvio, sctx->IoBuffer + dwOffset, (size_t)(sctx->IoBufferSize - dwOffset)); if (dwBytesRead == 0) { /* server closed connection */ // todo: error return SEC_E_INVALID_HANDLE; } if (dwBytesRead < 0) { /* socket error */ // todo: error return SEC_E_INVALID_HANDLE; } dwOffset+= dwBytesRead; } ZeroMemory(Buffers, sizeof(SecBuffer) * 4); Buffers[0].pvBuffer= sctx->IoBuffer; Buffers[0].cbBuffer= dwOffset; Buffers[0].BufferType= SECBUFFER_DATA; Buffers[1].BufferType= Buffers[2].BufferType= Buffers[3].BufferType= SECBUFFER_EMPTY; Msg.ulVersion= SECBUFFER_VERSION; // Version number Msg.cBuffers= 4; Msg.pBuffers= Buffers; sRet = DecryptMessage(phContext, &Msg, 0, NULL); /* Check for possible errors: we continue in case context has expired or renogitiation is required */ if (sRet != SEC_E_OK && sRet != SEC_I_CONTEXT_EXPIRED && sRet != SEC_I_RENEGOTIATE && sRet != SEC_E_INCOMPLETE_MESSAGE) { ma_schannel_set_sec_error(pvio, sRet); return sRet; } pData= pExtra= NULL; for (i=0; i < 4; i++) { if (!pData && Buffers[i].BufferType == SECBUFFER_DATA) pData= &Buffers[i]; if (!pExtra && Buffers[i].BufferType == SECBUFFER_EXTRA) pExtra= &Buffers[i]; if (pData && pExtra) break; } if (pData && pData->cbBuffer) { memcpy(ReadBuffer + *DecryptLength, pData->pvBuffer, pData->cbBuffer); *DecryptLength+= pData->cbBuffer; return sRet; } if (pExtra) { MoveMemory(sctx->IoBuffer, pExtra->pvBuffer, pExtra->cbBuffer); dwOffset= pExtra->cbBuffer; } else dwOffset= 0; } }
SECURITY_STATUS ma_schannel_client_handshake(MARIADB_TLS *ctls) { MARIADB_PVIO *pvio; SECURITY_STATUS sRet; DWORD OutFlags; DWORD r; SC_CTX *sctx; SecBuffer ExtraData; DWORD SFlags= ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | ISC_REQ_USE_SUPPLIED_CREDS | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; SecBufferDesc BufferOut; SecBuffer BuffersOut; if (!ctls || !ctls->pvio) return 1; pvio= ctls->pvio; sctx= (SC_CTX *)ctls->ssl; /* Initialie securifty context */ BuffersOut.BufferType= SECBUFFER_TOKEN; BuffersOut.cbBuffer= 0; BuffersOut.pvBuffer= NULL; BufferOut.cBuffers= 1; BufferOut.pBuffers= &BuffersOut; BufferOut.ulVersion= SECBUFFER_VERSION; sRet = InitializeSecurityContext(&sctx->CredHdl, NULL, pvio->mysql->host, SFlags, 0, SECURITY_NATIVE_DREP, NULL, 0, &sctx->ctxt, &BufferOut, &OutFlags, NULL); if(sRet != SEC_I_CONTINUE_NEEDED) { ma_schannel_set_sec_error(pvio, sRet); return sRet; } /* send client hello packaet */ if(BuffersOut.cbBuffer != 0 && BuffersOut.pvBuffer != NULL) { r= (DWORD)pvio->methods->write(pvio, (uchar *)BuffersOut.pvBuffer, (size_t)BuffersOut.cbBuffer); if (r <= 0) { sRet= SEC_E_INTERNAL_ERROR; goto end; } } sRet= ma_schannel_handshake_loop(pvio, TRUE, &ExtraData); /* allocate IO-Buffer for write operations: After handshake was successfull, we are able now to calculate payload */ if ((sRet = QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_STREAM_SIZES, &sctx->Sizes ))) goto end; sctx->IoBufferSize= SCHANNEL_PAYLOAD(sctx->Sizes); if (!(sctx->IoBuffer= (PUCHAR)LocalAlloc(0, sctx->IoBufferSize))) { sRet= SEC_E_INSUFFICIENT_MEMORY; goto end; } return sRet; end: LocalFree(sctx->IoBuffer); sctx->IoBufferSize= 0; FreeContextBuffer(BuffersOut.pvBuffer); DeleteSecurityContext(&sctx->ctxt); return sRet; }
SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRead, SecBuffer *pExtraData) { SecBufferDesc OutBuffer, InBuffer; SecBuffer InBuffers[2], OutBuffers; DWORD dwSSPIFlags, dwSSPIOutFlags, cbData, cbIoBuffer; TimeStamp tsExpiry; SECURITY_STATUS rc; PUCHAR IoBuffer; BOOL fDoRead; MARIADB_TLS *ctls= pvio->ctls; SC_CTX *sctx= (SC_CTX *)ctls->ssl; dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; /* Allocate data buffer */ if (!(IoBuffer = LocalAlloc(LMEM_FIXED, SC_IO_BUFFER_SIZE))) return SEC_E_INSUFFICIENT_MEMORY; cbIoBuffer = 0; fDoRead = InitialRead; /* handshake loop: We will leave a handshake is finished or an error occurs */ rc = SEC_I_CONTINUE_NEEDED; while (rc == SEC_I_CONTINUE_NEEDED || rc == SEC_E_INCOMPLETE_MESSAGE || rc == SEC_I_INCOMPLETE_CREDENTIALS ) { /* Read data */ if (rc == SEC_E_INCOMPLETE_MESSAGE || !cbIoBuffer) { if(fDoRead) { cbData = (DWORD)pvio->methods->read(pvio, IoBuffer + cbIoBuffer, (size_t)(SC_IO_BUFFER_SIZE - cbIoBuffer)); if (cbData == SOCKET_ERROR || cbData == 0) { rc = SEC_E_INTERNAL_ERROR; break; } cbIoBuffer += cbData; } else fDoRead = TRUE; } /* input buffers First buffer stores data received from server. leftover data will be stored in second buffer with BufferType SECBUFFER_EXTRA */ InBuffers[0].pvBuffer = IoBuffer; InBuffers[0].cbBuffer = cbIoBuffer; InBuffers[0].BufferType = SECBUFFER_TOKEN; InBuffers[1].pvBuffer = NULL; InBuffers[1].cbBuffer = 0; InBuffers[1].BufferType = SECBUFFER_EMPTY; InBuffer.cBuffers = 2; InBuffer.pBuffers = InBuffers; InBuffer.ulVersion = SECBUFFER_VERSION; /* output buffer */ OutBuffers.pvBuffer = NULL; OutBuffers.BufferType= SECBUFFER_TOKEN; OutBuffers.cbBuffer = 0; OutBuffer.cBuffers = 1; OutBuffer.pBuffers = &OutBuffers; OutBuffer.ulVersion = SECBUFFER_VERSION; rc = InitializeSecurityContextA(&sctx->CredHdl, &sctx->ctxt, NULL, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, &InBuffer, 0, NULL, &OutBuffer, &dwSSPIOutFlags, &tsExpiry ); if (rc == SEC_E_OK || rc == SEC_I_CONTINUE_NEEDED || FAILED(rc) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR)) { if(OutBuffers.cbBuffer && OutBuffers.pvBuffer) { cbData= (DWORD)pvio->methods->write(pvio, (uchar *)OutBuffers.pvBuffer, (size_t)OutBuffers.cbBuffer); if(cbData == SOCKET_ERROR || cbData == 0) { FreeContextBuffer(OutBuffers.pvBuffer); DeleteSecurityContext(&sctx->ctxt); return SEC_E_INTERNAL_ERROR; } /* Free output context buffer */ FreeContextBuffer(OutBuffers.pvBuffer); OutBuffers.pvBuffer = NULL; } } /* check if we need to read more data */ switch (rc) { case SEC_E_INCOMPLETE_MESSAGE: /* we didn't receive all data, so just continue loop */ continue; break; case SEC_E_OK: /* handshake completed, but we need to check if extra data was sent (which contains encrypted application data) */ if (InBuffers[1].BufferType == SECBUFFER_EXTRA) { if (!(pExtraData->pvBuffer= LocalAlloc(0, InBuffers[1].cbBuffer))) return SEC_E_INSUFFICIENT_MEMORY; MoveMemory(pExtraData->pvBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer ); pExtraData->BufferType = SECBUFFER_TOKEN; pExtraData->cbBuffer = InBuffers[1].cbBuffer; } else { pExtraData->BufferType= SECBUFFER_EMPTY; pExtraData->pvBuffer= NULL; pExtraData->cbBuffer= 0; } break; case SEC_I_INCOMPLETE_CREDENTIALS: /* Provided credentials didn't contain a valid client certificate. We will try to connect anonymously, using current credentials */ fDoRead= FALSE; rc= SEC_I_CONTINUE_NEEDED; continue; break; default: if (FAILED(rc)) { ma_schannel_set_sec_error(pvio, rc); goto loopend; } break; } if ( InBuffers[1].BufferType == SECBUFFER_EXTRA ) { MoveMemory( IoBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer ); cbIoBuffer = InBuffers[1].cbBuffer; } cbIoBuffer = 0; } loopend: if (FAILED(rc)) DeleteSecurityContext(&sctx->ctxt); LocalFree(IoBuffer); return rc; }
int ma_tls_verify_server_cert(MARIADB_TLS *ctls) { SC_CTX *sctx= (SC_CTX *)ctls->ssl; MARIADB_PVIO *pvio= ctls->pvio; int rc= 1; char *szName= NULL; char *pszServerName= pvio->mysql->host; /* check server name */ if (pszServerName && (sctx->mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT)) { PCCERT_CONTEXT pServerCert; DWORD NameSize= 0; char *p1, *p2; SECURITY_STATUS sRet; if ((sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert)) != SEC_E_OK) { ma_schannel_set_sec_error(pvio, sRet); return 1; } if (!(NameSize= CertNameToStr(pServerCert->dwCertEncodingType, &pServerCert->pCertInfo->Subject, CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, NULL, 0))) { pvio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Can't retrieve name of server certificate"); return 1; } if (!(szName= (char *)LocalAlloc(0, NameSize + 1))) { pvio->set_error(sctx->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); goto end; } if (!CertNameToStr(pServerCert->dwCertEncodingType, &pServerCert->pCertInfo->Subject, CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, szName, NameSize)) { pvio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Can't retrieve name of server certificate"); goto end; } if ((p1 = strstr(szName, "CN="))) { p1+= 3; if ((p2= strstr(p1, ", "))) *p2= 0; if (!strcmp(pszServerName, p1)) { rc= 0; goto end; } pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Name of server certificate didn't match"); } } end: if (szName) LocalFree(szName); return rc; }
my_bool ma_tls_connect(MARIADB_TLS *ctls) { my_bool blocking; MYSQL *mysql; SCHANNEL_CRED Cred; MARIADB_PVIO *pvio; my_bool rc= 1; SC_CTX *sctx; SECURITY_STATUS sRet; PCCERT_CONTEXT pRemoteCertContext = NULL, pLocalCertContext= NULL; ALG_ID AlgId[MAX_ALG_ID]= {0}; if (!ctls || !ctls->pvio) return 1;; pvio= ctls->pvio; sctx= (SC_CTX *)ctls->ssl; /* Set socket to blocking if not already set */ if (!(blocking= pvio->methods->is_blocking(pvio))) pvio->methods->blocking(pvio, TRUE, 0); mysql= pvio->mysql; if (ma_tls_set_client_certs(ctls)) goto end; ZeroMemory(&Cred, sizeof(SCHANNEL_CRED)); /* Set cipher */ if (mysql->options.ssl_cipher) { WORD validTokens = 0; char *token = strtok(mysql->options.ssl_cipher, ":"); while (token) { struct st_cipher_suite *valid; for (valid = valid_ciphers; valid && valid->aid; valid++) { if (!strcmp(token, valid->cipher)) { AlgId[validTokens++] = valid->aid; break; } } token = strtok(NULL, ":"); } } Cred.palgSupportedAlgs = (ALG_ID *)&AlgId; Cred.dwVersion= SCHANNEL_CRED_VERSION; if (mysql->options.extension) Cred.dwMinimumCipherStrength = MAX(128, mysql->options.extension->tls_cipher_strength); else Cred.dwMinimumCipherStrength = 128; Cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK | SCH_SEND_ROOT_CERT | SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION; if (sctx->client_cert_ctx) { Cred.cCreds = 1; Cred.paCred = &sctx->client_cert_ctx; } if (mysql->options.extension && mysql->options.extension->tls_version) { Cred.grbitEnabledProtocols= 0; if (strstr("TLSv1.0", mysql->options.extension->tls_version)) Cred.grbitEnabledProtocols|= SP_PROT_TLS1_0; if (strstr("TLSv1.1", mysql->options.extension->tls_version)) Cred.grbitEnabledProtocols|= SP_PROT_TLS1_1; if (strstr("TLSv1.2", mysql->options.extension->tls_version)) Cred.grbitEnabledProtocols|= SP_PROT_TLS1_2; } else Cred.grbitEnabledProtocols = SP_PROT_TLS1_0 | SP_PROT_TLS1_1 | SP_PROT_TLS1_2; if ((sRet= AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL)) != SEC_E_OK) { ma_schannel_set_sec_error(pvio, sRet); goto end; } sctx->FreeCredHdl= 1; if (ma_schannel_client_handshake(ctls) != SEC_E_OK) goto end; sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext); if (sRet != SEC_E_OK) { ma_schannel_set_sec_error(pvio, sRet); goto end; } if (!ma_schannel_verify_certs(sctx, 0)) goto end; return 0; end: if (pRemoteCertContext) CertFreeCertificateContext(pRemoteCertContext); if (rc && sctx->IoBufferSize) LocalFree(sctx->IoBuffer); sctx->IoBufferSize= 0; if (sctx->client_cert_ctx) CertFreeCertificateContext(sctx->client_cert_ctx); sctx->client_cert_ctx= 0; return 1; }