Skip to content

Commit ed1b3c5

Browse files
BUG: System DSNs decryptable by system users (#24)
One feature blocking the ODBC driver from working on the Power BI Gateway server was that System DSNs were usable by anyone on the system, but the client secret decryption was only possible by the user who created the DSN. This meant that when the Power BI Gateway Server tried to use the DSN via its service account, the client secret could not be decrypted. With this commit in place, I have observed queries running via the Power BI Gateway server both for on-demand as well as scheduled data refreshes.
1 parent 7e2419d commit ed1b3c5

File tree

7 files changed

+120
-21
lines changed

7 files changed

+120
-21
lines changed

src/driver/config/configDSN.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,46 @@
1212
#include "dsnConfigForm.hpp"
1313
#include "profileReader.hpp"
1414

15+
bool isSystemDSN() {
16+
/*
17+
System DSN secrets need to be decryptable for anyone on the system.
18+
User DSN secrets only need to be decryptable for the current user.
19+
To tell them apart, we need to use SQLGetConfigMode.
20+
21+
There exists a ODBC_BOTH_DSN option, which we will treat the same
22+
as a system DSN in terms of encryption/decryption of secrets.
23+
*/
24+
UWORD configMode;
25+
SQLGetConfigMode(&configMode);
26+
27+
bool isSystemDSN = configMode != ODBC_USER_DSN;
28+
WriteLog(LL_INFO, "System DSN: " + std::to_string(isSystemDSN));
29+
return isSystemDSN;
30+
}
31+
1532
void writeToProfile(DriverConfig result,
1633
std::string section,
1734
std::string value) {
1835
std::string dsn = result.getDSN();
1936
if (section == "clientSecret") {
37+
std::function<std::string(const std::string&)> encryptionFunc = nullptr;
38+
std::string encryptionLevel;
39+
if (isSystemDSN()) {
40+
encryptionFunc = systemEncryptString;
41+
encryptionLevel = "system";
42+
} else {
43+
encryptionFunc = userEncryptString;
44+
encryptionLevel = "user";
45+
}
2046
SQLWritePrivateProfileString(dsn.c_str(),
2147
"encryptedClientSecret",
22-
encryptString(value).c_str(),
48+
encryptionFunc(value).c_str(),
49+
"ODBC.INI");
50+
SQLWritePrivateProfileString(dsn.c_str(),
51+
"secretEncryptionLevel",
52+
encryptionLevel.c_str(),
2353
"ODBC.INI");
54+
2455
} else {
2556
SQLWritePrivateProfileString(
2657
dsn.c_str(), section.c_str(), value.c_str(), "ODBC.INI");

src/driver/config/driverConfig.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ std::map<std::string, std::string> DRIVER_CONFIG_DEFAULT_VALUES = {
5656
std::make_pair("clientId", ""),
5757
std::make_pair("clientSecret", ""),
5858
std::make_pair("oidcScope", ""),
59+
std::make_pair("secretEncryptionLevel", "user"),
5960
};
6061

6162
// DSN

src/driver/config/profileReader.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,20 @@ DriverConfig readDriverConfigFromProfile(std::string dsn) {
4343
config.setClientId(readFromPrivateProfile(dsn, "clientId"));
4444
config.setOidcScope(readFromPrivateProfile(dsn, "oidcScope"));
4545

46-
// Handle the encrypted data
46+
std::string secretEncryptionLevel =
47+
readFromPrivateProfile(dsn, "secretEncryptionLevel");
4748
std::string encryptedClientSecret =
4849
readFromPrivateProfile(dsn, "encryptedClientSecret");
49-
config.setClientSecret(decryptString(encryptedClientSecret));
50+
51+
// Handle the encrypted data
52+
if (secretEncryptionLevel == "user") {
53+
config.setClientSecret(userDecryptString(encryptedClientSecret));
54+
} else if (secretEncryptionLevel == "system") {
55+
config.setClientSecret(systemDecryptString(encryptedClientSecret));
56+
} else {
57+
throw std::runtime_error("Unknown secret encryption level: " +
58+
secretEncryptionLevel);
59+
}
60+
5061
return config;
5162
}

src/trinoAPIWrapper/authProvider/tokens/tokenCache.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ TokenCacheEntry readTokenCache(const std::string& tokenId) {
137137
tokenData.value("encryptedAccessToken", "");
138138
std::string encryptedRefreshToken =
139139
tokenData.value("encryptedRefreshToken", "");
140-
std::string accessToken = decryptString(encryptedAccessToken);
141-
std::string refreshToken = decryptString(encryptedRefreshToken);
140+
std::string accessToken = userDecryptString(encryptedAccessToken);
141+
std::string refreshToken = userDecryptString(encryptedRefreshToken);
142142

143143
// Return a cache entry for these.
144144
WriteLog(LL_TRACE, " Token cache read successfully");
@@ -167,9 +167,9 @@ void writeTokenCache(TokenCacheEntry cacheEntry) {
167167
std::string tokenId = cacheEntry.getTokenId();
168168
inputJsonData[tokenId] = json::object();
169169
inputJsonData[tokenId]["encryptedAccessToken"] =
170-
encryptString(cacheEntry.getAccessToken());
170+
userEncryptString(cacheEntry.getAccessToken());
171171
inputJsonData[tokenId]["encryptedRefreshToken"] =
172-
encryptString(cacheEntry.getRefreshToken());
172+
userEncryptString(cacheEntry.getRefreshToken());
173173

174174
// Last, modify it and write it back with an update token cache.
175175
std::ofstream outputFile(filePath);

src/util/cryptUtils.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,13 @@ std::vector<BYTE> fromBase64(const std::string& data) {
6565
return decodedData;
6666
}
6767

68-
std::string encryptString(const std::string& text) {
69-
/* Encrypt a string using the user's login data.
68+
std::string encryptString(const std::string& text, DWORD flags) {
69+
/*
7070
* Based on the API documentation for DPAPI CryptProtectData function
7171
* https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata
7272
* Adds base64 encoding to the output to avoid returning non printing
7373
* characters or other tricky stuff in the response.
7474
*/
75-
7675
if (text.empty()) {
7776
return "";
7877
}
@@ -85,7 +84,7 @@ std::string encryptString(const std::string& text) {
8584
dataIn.pbData = pbDataInput;
8685
dataIn.cbData = cbDataInput;
8786

88-
if (!CryptProtectData(&dataIn, NULL, NULL, NULL, NULL, 0, &dataOut)) {
87+
if (!CryptProtectData(&dataIn, NULL, NULL, NULL, NULL, flags, &dataOut)) {
8988
throw std::runtime_error("Encryption Failed");
9089
}
9190
std::vector<BYTE> encryptedData(dataOut.pbData,
@@ -95,8 +94,8 @@ std::string encryptString(const std::string& text) {
9594
return toBase64(encryptedData);
9695
}
9796

98-
std::string decryptString(const std::string& text) {
99-
/* Decrypt a string using the user's login data.
97+
std::string decryptString(const std::string& text, DWORD flags) {
98+
/*
10099
* Based on the API documentation for DPAPI CryptUnprotectData function
101100
* https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptunprotectdata
102101
* Adds base64 decoding to the input to match the encryptString
@@ -124,3 +123,23 @@ std::string decryptString(const std::string& text) {
124123

125124
return decryptedString;
126125
}
126+
127+
std::string userEncryptString(const std::string& text) {
128+
// Encrypt a string using the user's login data.
129+
return encryptString(text, 0);
130+
}
131+
132+
std::string systemEncryptString(const std::string& text) {
133+
// Encrypt a string using the current computer as context.
134+
return encryptString(text, CRYPTPROTECT_LOCAL_MACHINE);
135+
}
136+
137+
std::string userDecryptString(const std::string& text) {
138+
// Decrypt a string using the user's login data.
139+
return decryptString(text, 0);
140+
}
141+
142+
std::string systemDecryptString(const std::string& text) {
143+
// Decrypt a string using the user's login data.
144+
return decryptString(text, 0);
145+
}

src/util/cryptUtils.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@
22

33
#include <string>
44

5-
std::string encryptString(const std::string& text);
6-
std::string decryptString(const std::string& text);
5+
std::string userEncryptString(const std::string& text);
6+
std::string userDecryptString(const std::string& text);
7+
std::string systemEncryptString(const std::string& text);
8+
std::string systemDecryptString(const std::string& text);

test/unit/util/cryptUtilsTest.cpp

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,53 @@
33

44
#include "../../../src/util/cryptUtils.hpp"
55

6-
TEST(CryptUtilsTest, RoundTripText) {
6+
TEST(CryptUtilsTest, RoundTripUserText) {
77
std::string secret = "thisIsASecret";
8-
std::string encrypted = encryptString(secret);
9-
std::string decrypted = decryptString(encrypted);
8+
std::string encrypted = userEncryptString(secret);
9+
std::string decrypted = userDecryptString(encrypted);
1010

1111
EXPECT_EQ(secret, decrypted);
1212
}
1313

14-
TEST(CryptUtilsTest, RoundTripEmptyString) {
14+
TEST(CryptUtilsTest, RoundTripUsesrEmptyString) {
1515
std::string secret = "";
16-
std::string encrypted = encryptString(secret);
17-
std::string decrypted = decryptString(encrypted);
16+
std::string encrypted = userEncryptString(secret);
17+
std::string decrypted = userDecryptString(encrypted);
1818

1919
EXPECT_EQ(secret, decrypted);
2020
}
21+
22+
TEST(CryptUtilsTest, RoundTripSystemText) {
23+
std::string secret = "thisIsASecret";
24+
std::string encrypted = systemEncryptString(secret);
25+
std::string decrypted = systemDecryptString(encrypted);
26+
27+
EXPECT_EQ(secret, decrypted);
28+
}
29+
30+
TEST(CryptUtilsTest, RoundTripSystemEmptyString) {
31+
std::string secret = "";
32+
std::string encrypted = systemEncryptString(secret);
33+
std::string decrypted = systemDecryptString(encrypted);
34+
35+
EXPECT_EQ(secret, decrypted);
36+
}
37+
38+
TEST(CryptUtilsTest, UserAndSystemSecretsAreDifferent) {
39+
std::string secret = "hello";
40+
std::string userEncrypted = userEncryptString(secret);
41+
std::string systemEncrypted = systemEncryptString(secret);
42+
43+
// The secrets should be encrypted differently depending on
44+
// whether they are user secrets or system secrets.
45+
EXPECT_NE(userEncrypted, systemEncrypted);
46+
47+
std::string userDecrypted = userDecryptString(userEncrypted);
48+
std::string systemDecrypted = systemDecryptString(systemEncrypted);
49+
50+
// After decryption, the values should all be the same again
51+
// since we started from the same secret string.
52+
EXPECT_EQ(userDecrypted, systemDecrypted);
53+
EXPECT_EQ(userDecrypted, secret);
54+
EXPECT_EQ(systemDecrypted, secret);
55+
}

0 commit comments

Comments
 (0)