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 Encryption: Adds fix to allow partition key path and id to be part of client encryption policy. #33648

Merged
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8f007b2
pushing all changes
aayush3011 Feb 21, 2023
f268e33
Updating the change log
aayush3011 Feb 21, 2023
c51c685
Merge branch 'main' into users/akataria/AddEncryptionIdAndPartitionKey
aayush3011 Mar 1, 2023
5e98ed1
Adding code for bulk and batch id and partition key encryption
aayush3011 Mar 2, 2023
ddbf13b
Merge branch 'users/akataria/AddEncryptionIdAndPartitionKey' of githu…
aayush3011 Mar 2, 2023
9cefac2
Update version_client.txt
aayush3011 Mar 2, 2023
2f50dc7
Fixing test cases
aayush3011 Mar 2, 2023
0e6250d
Merge branch 'users/akataria/AddEncryptionIdAndPartitionKey' of githu…
aayush3011 Mar 2, 2023
814b797
Fixing test cases
aayush3011 Mar 2, 2023
37afd72
Merge branch 'main' into users/akataria/AddEncryptionIdAndPartitionKey
aayush3011 Mar 9, 2023
9045c38
Merge branch 'Azure:main' into users/akataria/AddEncryptionIdAndParti…
aayush3011 Mar 16, 2023
633ebd6
Fixing test cases
aayush3011 Mar 16, 2023
43d00a7
Fixing test cases
aayush3011 Mar 16, 2023
c8f5bbf
Fixing linting error
aayush3011 Mar 16, 2023
be7de62
Merge branch 'main' into users/akataria/AddEncryptionIdAndPartitionKey
aayush3011 Mar 17, 2023
9d377f8
Merge branch 'main' into users/akataria/AddEncryptionIdAndPartitionKey
aayush3011 Mar 20, 2023
92c605e
Fixing test cases
aayush3011 Apr 7, 2023
1ecc7f0
Merge branch 'users/akataria/AddEncryptionIdAndPartitionKey' of githu…
aayush3011 Apr 7, 2023
f1601f6
Update version_client.txt
aayush3011 Apr 10, 2023
474632f
Resolving merge conflicts
aayush3011 Apr 10, 2023
c44984e
Merge branch 'users/akataria/AddEncryptionIdAndPartitionKey' of githu…
aayush3011 Apr 10, 2023
617c3ad
Resolving merge conflicts
aayush3011 Apr 10, 2023
0d769cc
Merge branch 'Azure:main' into users/akataria/AddEncryptionIdAndParti…
aayush3011 Apr 10, 2023
458fe05
Resolving merge conflicts
aayush3011 Apr 10, 2023
68d6001
Adding test cases, and moving query test cases to a new class
aayush3011 Apr 10, 2023
dc44e05
Merge branch 'main' into users/akataria/AddEncryptionIdAndPartitionKey
aayush3011 Apr 13, 2023
5d008ed
Resolving comments
aayush3011 Apr 13, 2023
4832c1f
Merge branch 'users/akataria/AddEncryptionIdAndPartitionKey' of githu…
aayush3011 Apr 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/jacoco-test-coverage/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-cosmos-encryption</artifactId>
<version>1.13.0-beta.1</version> <!-- {x-version-update;com.azure:azure-cosmos-encryption;current} -->
<version>2.0.0-beta.1</version> <!-- {x-version-update;com.azure:azure-cosmos-encryption;current} -->
</dependency>
<dependency>
<groupId>com.azure</groupId>
Expand Down
2 changes: 1 addition & 1 deletion eng/versioning/version_client.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ com.azure.cosmos.spark:azure-cosmos-spark_3_2-12;1.0.0-beta.1;1.0.0-beta.1
com.azure.cosmos.spark:azure-cosmos-spark_3-1_2-12;4.17.2;4.18.0-beta.1
com.azure.cosmos.spark:azure-cosmos-spark_3-2_2-12;4.17.2;4.18.0-beta.1
com.azure.cosmos.spark:azure-cosmos-spark_3-3_2-12;4.17.2;4.18.0-beta.1
com.azure:azure-cosmos-encryption;1.12.0;1.13.0-beta.1
com.azure:azure-cosmos-encryption;1.12.0;2.0.0-beta.1
com.azure:azure-cosmos-test;1.0.0-beta.1;1.0.0-beta.2
com.azure:azure-data-appconfiguration;1.4.3;1.5.0-beta.1
com.azure:azure-data-appconfiguration-perf;1.0.0-beta.1;1.0.0-beta.1
Expand Down
2 changes: 1 addition & 1 deletion sdk/cosmos/azure-cosmos-benchmark/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Licensed under the MIT License.
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-cosmos-encryption</artifactId>
<version>1.13.0-beta.1</version> <!-- {x-version-update;com.azure:azure-cosmos-encryption;current} -->
<version>2.0.0-beta.1</version> <!-- {x-version-update;com.azure:azure-cosmos-encryption;current} -->
</dependency>

<dependency>
Expand Down
8 changes: 3 additions & 5 deletions sdk/cosmos/azure-cosmos-encryption/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
## Release History

### 1.13.0-beta.1 (Unreleased)
### 2.0.0-beta.1 (Unreleased)

#### Features Added
* Added support for allowing partition key path and id to be part of client encryption policy - See [PR 33648](https://github.com/Azure/azure-sdk-for-java/pull/33648)

#### Breaking Changes

#### Bugs Fixed

#### Other Changes
* Adds code to support ParititonKey and Id encryption, when the PolicyFormatVersion is set to 2 - See [PR 33648](https://github.com/Azure/azure-sdk-for-java/pull/33648)
aayush3011 marked this conversation as resolved.
Show resolved Hide resolved

### 1.12.0 (2023-03-17)

Expand Down
3 changes: 2 additions & 1 deletion sdk/cosmos/azure-cosmos-encryption/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Licensed under the MIT License.

<groupId>com.azure</groupId>
<artifactId>azure-cosmos-encryption</artifactId>
<version>1.13.0-beta.1</version> <!-- {x-version-update;com.azure:azure-cosmos-encryption;current} -->
<version>2.0.0-beta.1</version> <!-- {x-version-update;com.azure:azure-cosmos-encryption;current} -->
<name>Encryption Plugin for Azure Cosmos DB SDK</name>
<description>This Package contains Encryption Plugin for Microsoft Azure Cosmos SDK</description>
<packaging>jar</packaging>
Expand Down Expand Up @@ -48,6 +48,7 @@ Licensed under the MIT License.
--add-opens com.azure.cosmos.encryption/com.azure.cosmos.encryption.keyprovider=ALL-UNNAMED
--add-opens com.azure.cosmos.encryption/com.azure.cosmos.encryption.util=ALL-UNNAMED
--add-opens com.azure.cosmos.encryption/com.azure.cosmos.encryption.models=ALL-UNNAMED
--add-opens com.azure.cosmos/com.azure.cosmos.implementation=ALL-UNNAMED
</javaModulesSurefireArgLine>
</properties>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ private CosmosContainerProperties getContainerPropertiesWithVersionValidation(Co
throw new IllegalArgumentException("Container without client encryption policy cannot be used");
}

if (cosmosContainerResponse.getProperties().getClientEncryptionPolicy().getPolicyFormatVersion() > 1) {
if (cosmosContainerResponse.getProperties().getClientEncryptionPolicy().getPolicyFormatVersion() > 2) {
throw new UnsupportedOperationException("This version of the Encryption library cannot be used with this " +
"container. Please upgrade to the latest version of the same.");
}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ public class Constants {
public static final String ALLOW_CACHED_READS_HEADER = "x-ms-cosmos-allow-cachedreads";

public static final String DATABASE_RID_HEADER = "x-ms-cosmos-database-rid";

public static final String PROPERTY_NAME_ID = "id";
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.DoubleNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.LongNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
Expand All @@ -41,6 +42,7 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
Expand All @@ -58,6 +60,7 @@ public class EncryptionProcessor {
private ClientEncryptionPolicy clientEncryptionPolicy;
private String containerRid;
private String databaseRid;
private List<String> partitionKeyPaths;
private CosmosClientEncryptionKeyProperties cosmosClientEncryptionKeyProperties;
private final EncryptionKeyStoreProviderImpl encryptionKeyStoreProviderImpl;
private final static ImplementationBridgeHelpers.CosmosContainerPropertiesHelper.CosmosContainerPropertiesAccessor cosmosContainerPropertiesAccessor = ImplementationBridgeHelpers.CosmosContainerPropertiesHelper.getCosmosContainerPropertiesAccessor();
Expand Down Expand Up @@ -99,7 +102,15 @@ public Mono<Void> initializeEncryptionSettingsAsync(boolean isRetry) {
{
this.containerRid = cosmosContainerProperties.getResourceId();
this.databaseRid = cosmosContainerPropertiesAccessor.getSelfLink(cosmosContainerProperties).split("/")[1];

if (cosmosContainerProperties.getPartitionKeyDefinition().getPaths().isEmpty()) {
aayush3011 marked this conversation as resolved.
Show resolved Hide resolved
this.partitionKeyPaths = cosmosContainerProperties.getPartitionKeyDefinition().getPaths();
} else {
this.partitionKeyPaths = new ArrayList<>();
}

this.encryptionSettings.setDatabaseRid(this.databaseRid);
this.encryptionSettings.setPartitionKeyPaths(partitionKeyPaths);
if (cosmosContainerProperties.getClientEncryptionPolicy() == null) {
this.isEncryptionSettingsInitDone.set(true);
return Mono.empty();
Expand Down Expand Up @@ -206,7 +217,7 @@ public Mono<Void> initEncryptionSettingsIfNotInitializedAsync() {
return Mono.empty();
}

ClientEncryptionPolicy getClientEncryptionPolicy() {
public ClientEncryptionPolicy getClientEncryptionPolicy() {
return clientEncryptionPolicy;
}

Expand Down Expand Up @@ -438,16 +449,38 @@ public void encryptAndSerializeProperty(EncryptionSettings encryptionSettings, J
}
}

public String encryptAndSerializeValue(EncryptionSettings encryptionSettings, String propertyValue, String propertyName) throws MicrosoftDataEncryptionException {
JsonNode propertyValueHolder = toJsonNode(propertyValue.getBytes(StandardCharsets.US_ASCII), TypeMarker.STRING);
return new String(encryptAndSerializeValue(encryptionSettings, null, propertyValueHolder, propertyName), StandardCharsets.US_ASCII);
}


public byte[] encryptAndSerializeValue(EncryptionSettings encryptionSettings, ObjectNode objectNode,
JsonNode propertyValueHolder, String propertyName) throws MicrosoftDataEncryptionException {
byte[] cipherText;
byte[] cipherTextWithTypeMarker;
if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
if (propertyValueHolder.getNodeType() != JsonNodeType.STRING) {
throw new IllegalArgumentException("Unsupported argument type. The value to escape has to be string " +
"type. Please refer to https://aka.ms/CosmosClientEncryption for more details.");
}
}
Pair<TypeMarker, byte[]> typeMarkerPair = toByteArray(propertyValueHolder);
cipherText =
encryptionSettings.getAeadAes256CbcHmac256EncryptionAlgorithm().encrypt(typeMarkerPair.getRight());
cipherTextWithTypeMarker = new byte[cipherText.length + 1];
cipherTextWithTypeMarker[0] = (byte) typeMarkerPair.getLeft().getValue();
System.arraycopy(cipherText, 0, cipherTextWithTypeMarker, 1, cipherText.length);

if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
// case: id does not support '/','\','?','#'. Convert Base64 string to Uri safe string
String base64UriSafeString = convertToBase64UriSafeString(cipherTextWithTypeMarker);
if (objectNode != null && !objectNode.isNull()) {
objectNode.put(propertyName, base64UriSafeString);
}
return base64UriSafeString.getBytes(StandardCharsets.UTF_8);
}

if (objectNode != null && !objectNode.isNull()) {
objectNode.put(propertyName, cipherTextWithTypeMarker);
}
Expand Down Expand Up @@ -567,7 +600,14 @@ public JsonNode decryptAndSerializeValue(EncryptionSettings encryptionSettings,
JsonNode propertyValueHolder, String propertyName) throws MicrosoftDataEncryptionException, IOException {
byte[] cipherText;
byte[] cipherTextWithTypeMarker;
cipherTextWithTypeMarker = propertyValueHolder.binaryValue();
if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
if (propertyValueHolder.getNodeType() == JsonNodeType.NULL) {
return null;
}
cipherTextWithTypeMarker = convertFromBase64UriSafeString(propertyValueHolder.asText());
} else {
cipherTextWithTypeMarker = propertyValueHolder.binaryValue();
}
cipherText = new byte[cipherTextWithTypeMarker.length - 1];
System.arraycopy(cipherTextWithTypeMarker, 1, cipherText, 0,
cipherTextWithTypeMarker.length - 1);
Expand Down Expand Up @@ -627,6 +667,18 @@ public static JsonNode toJsonNode(byte[] serializedBytes, TypeMarker typeMarker)
throw BridgeInternal.createCosmosException(0, "Invalid or Unsupported Data Type Passed " + typeMarker);
}

private String convertToBase64UriSafeString(byte[] bytesToProcess) {
// Base 64 Encoding with URL and Filename Safe Alphabet https://datatracker.ietf.org/doc/html/rfc4648#section-5
// https://docs.microsoft.com/en-us/azure/cosmos-db/concepts-limits#per-item-limits, due to base64 conversion and encryption
// the permissible size of the property will further reduce.
return Base64.getUrlEncoder().encodeToString(bytesToProcess);
}

private byte[] convertFromBase64UriSafeString(String base64UriSafeString) {
return Base64.getUrlDecoder().decode(base64UriSafeString);
}


public enum TypeMarker {
NULL(1), // not used
BOOLEAN(2),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.security.InvalidKeyException;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

Expand All @@ -39,6 +40,7 @@ public final class EncryptionSettings {
private EncryptionType encryptionType;
private String databaseRid;
private CosmosClientEncryptionKeyProperties cosmosClientEncryptionKeyProperties;
private List<String> partitionKeyPaths;
private final static EncryptionImplementationBridgeHelpers.CosmosEncryptionAsyncClientHelper.CosmosEncryptionAsyncClientAccessor cosmosEncryptionAsyncClientAccessor =
EncryptionImplementationBridgeHelpers.CosmosEncryptionAsyncClientHelper.getCosmosEncryptionAsyncClientAccessor();

Expand Down Expand Up @@ -226,6 +228,14 @@ public void setEncryptionSettingForProperty(String propertyName, EncryptionSetti
this.encryptionSettingCacheByPropertyName.set(propertyName, cachedEncryptionSettings);
}

public List<String> getPartitionKeyPaths() {
return partitionKeyPaths;
}

public void setPartitionKeyPaths(List<String> partitionKeyPaths) {
this.partitionKeyPaths = partitionKeyPaths;
}

static EncryptionSettings create(
EncryptionSettings settingsForKey,
EncryptionType encryptionType) throws MicrosoftDataEncryptionException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package com.azure.cosmos.encryption.models;

import com.azure.cosmos.encryption.CosmosEncryptionAsyncContainer;
import com.azure.cosmos.encryption.implementation.Constants;
import com.azure.cosmos.encryption.implementation.EncryptionImplementationBridgeHelpers;
import com.azure.cosmos.encryption.implementation.EncryptionProcessor;
import com.azure.cosmos.encryption.implementation.EncryptionUtils;
Expand All @@ -17,6 +18,8 @@
import com.fasterxml.jackson.databind.JsonNode;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;

Expand Down Expand Up @@ -56,11 +59,11 @@ Mono<Void> addEncryptionParameterAsync(String path, SqlParameter sqlParameter,
List<SqlParameter> parameters = sqlQuerySpec.getParameters();
if (parameters != null) {
return cosmosEncryptionAsyncContainerAccessor.getEncryptionProcessor(cosmosEncryptionAsyncContainer)
.initEncryptionSettingsIfNotInitializedAsync().then(Mono.defer(() -> {

.initEncryptionSettingsIfNotInitializedAsync().then(Mono.defer(() -> {
String propertyName = path.substring(1);
return cosmosEncryptionAsyncContainerAccessor.getEncryptionProcessor(cosmosEncryptionAsyncContainer)
.getEncryptionSettings()
.getEncryptionSettingForPropertyAsync(sqlParameter.getName().substring(1),
.getEncryptionSettingForPropertyAsync(propertyName,
cosmosEncryptionAsyncContainerAccessor.getEncryptionProcessor(cosmosEncryptionAsyncContainer)).flatMap(encryptionSettings -> { // encryptionSettings.
if (encryptionSettings == null) {
// property not encrypted.
Expand All @@ -73,6 +76,12 @@ Mono<Void> addEncryptionParameterAsync(String path, SqlParameter sqlParameter,
"query because of randomized encryption", path)));
}
try {
if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
if (sqlParameter.getValue(Object.class).getClass() != String.class) {
throw new IllegalArgumentException("Unsupported argument type. The value to escape has to be string " +
"type. Please refer to https://aka.ms/CosmosClientEncryption for more details.");
}
}
byte[] valueByte =
EncryptionUtils.serializeJsonToByteArray(EncryptionUtils.getSimpleObjectMapper(),
sqlParameter.getValue(Object.class));
Expand All @@ -84,9 +93,21 @@ Mono<Void> addEncryptionParameterAsync(String path, SqlParameter sqlParameter,
byte[] cipherTextWithTypeMarker = new byte[cipherText.length + 1];
cipherTextWithTypeMarker[0] = (byte) typeMarkerPair.getLeft().getValue();
System.arraycopy(cipherText, 0, cipherTextWithTypeMarker, 1, cipherText.length);
SqlParameter encryptedParameter = new SqlParameter(sqlParameter.getName(),
cipherTextWithTypeMarker);

SqlParameter encryptedParameter;
if (propertyName.equals(Constants.PROPERTY_NAME_ID)) {
// case: id does not support '/','\','?','#'. Convert Base64 string to Uri safe string
String base64UriSafeString = convertToBase64UriSafeString(cipherTextWithTypeMarker);
System.out.println("encrypted id " + base64UriSafeString);
aayush3011 marked this conversation as resolved.
Show resolved Hide resolved
encryptedParameter = new SqlParameter(sqlParameter.getName(),
base64UriSafeString.getBytes(StandardCharsets.UTF_8));

} else {
encryptedParameter = new SqlParameter(sqlParameter.getName(),
cipherTextWithTypeMarker);
}
parameters.add(encryptedParameter);

} catch (MicrosoftDataEncryptionException ex) {
return Mono.error(ex);
}
Expand All @@ -98,6 +119,13 @@ Mono<Void> addEncryptionParameterAsync(String path, SqlParameter sqlParameter,
return Mono.empty();
}

private String convertToBase64UriSafeString(byte[] bytesToProcess) {
// Base 64 Encoding with URL and Filename Safe Alphabet https://datatracker.ietf.org/doc/html/rfc4648#section-5
// https://docs.microsoft.com/en-us/azure/cosmos-db/concepts-limits#per-item-limits, due to base64 conversion and encryption
// the permissible size of the property will further reduce.
return Base64.getUrlEncoder().encodeToString(bytesToProcess);
aayush3011 marked this conversation as resolved.
Show resolved Hide resolved
}

HashMap<String, SqlParameter> getEncryptionParamMap() {
return encryptionParamMap;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void before_CosmosItemTest() {
CosmosEncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256.getName(), metadata2).block();

//Create collection with clientEncryptionPolicy
ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths());
ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(getPaths(1), 1);
CosmosContainerProperties containerProperties = new CosmosContainerProperties("TestCollForEncryptionCacheTest"
, "/mypk");
containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void before_CosmosItemTest() throws IOException {
cosmosEncryptionAsyncClient.getCosmosEncryptionAsyncDatabase(cosmosAsyncDatabase.getId());

ClientEncryptionPolicy clientEncryptionPolicy =
new ClientEncryptionPolicy(getPaths());
new ClientEncryptionPolicy(getPaths(2), 2);
String containerId = UUID.randomUUID().toString();
CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerId, "/mypk");
containerProperties.setClientEncryptionPolicy(clientEncryptionPolicy);
Expand Down Expand Up @@ -100,12 +100,13 @@ public void createItemEncrypt_readItemDecrypt() throws IOException {
this.cosmosEncryptionAsyncContainer.getCosmosAsyncContainer().createItem(dotNetEncryptedPocoJsonNode,
partitionKey, requestOptions).block();

JsonNode dotNetPOCOJsonNode = MAPPER.readTree(new File("src/test/resources/dotnetEncryption/POCO.json"));
partitionKey = new PartitionKey(dotNetPOCOJsonNode.get("mypk").asText());
//reading above saved .net encrypted json via java encryption library
EncryptionPojo unencryptedPojo =
this.cosmosEncryptionAsyncContainer.readItem(dotNetEncryptedPocoJsonNode.get("id").asText(), partitionKey
this.cosmosEncryptionAsyncContainer.readItem(dotNetPOCOJsonNode.get("id").asText(), partitionKey
, new CosmosItemRequestOptions(), EncryptionPojo.class).block().getItem();

JsonNode dotNetPOCOJsonNode = MAPPER.readTree(new File("src/test/resources/dotnetEncryption/POCO.json"));
EncryptionPojo unencryptedPoco = MAPPER.treeToValue(dotNetPOCOJsonNode, EncryptionPojo.class);

//validating java decrypted pojo similar to original .net unencrypted poco
Expand Down
Loading