diff --git a/benchkit-backend/LICENSES.txt b/benchkit-backend/LICENSES.txt index 84ada8eb05..b4988d7cfd 100644 --- a/benchkit-backend/LICENSES.txt +++ b/benchkit-backend/LICENSES.txt @@ -11,6 +11,7 @@ Apache Software License, Version 2.0 Neo4j Bolt Connection (Pooled Source impl) Neo4j Bolt Connection (Provider SPI) Neo4j Bolt Connection (Routed Source impl) + Neo4j Bolt Connection Codec Netty/Buffer Netty/Codec/Base Netty/Codec/Compression diff --git a/benchkit-backend/NOTICE.txt b/benchkit-backend/NOTICE.txt index d6d78a0026..bd4ddfe421 100644 --- a/benchkit-backend/NOTICE.txt +++ b/benchkit-backend/NOTICE.txt @@ -26,6 +26,7 @@ Apache Software License, Version 2.0 Neo4j Bolt Connection (Pooled Source impl) Neo4j Bolt Connection (Provider SPI) Neo4j Bolt Connection (Routed Source impl) + Neo4j Bolt Connection Codec Netty/Buffer Netty/Codec/Base Netty/Codec/Compression diff --git a/benchkit-backend/pom.xml b/benchkit-backend/pom.xml index 26799245f0..883e080fcb 100644 --- a/benchkit-backend/pom.xml +++ b/benchkit-backend/pom.xml @@ -7,7 +7,7 @@ neo4j-java-driver-parent org.neo4j.driver - 6.2-SNAPSHOT + 6.3-SNAPSHOT benchkit-backend diff --git a/bom/pom.xml b/bom/pom.xml index c856cac6f0..1c22f9be4f 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-parent - 6.2-SNAPSHOT + 6.3-SNAPSHOT neo4j-java-driver-bom @@ -42,6 +42,31 @@ neo4j-java-driver-observation-micrometer ${project.version} + + org.neo4j.driver + neo4j-java-driver-encryption-google-cloud-kms + ${project.version} + + + org.neo4j.driver + neo4j-java-driver-encryption-aws-kms + ${project.version} + + + org.neo4j.driver + neo4j-java-driver-encryption-azure-keyvault + ${project.version} + + + org.neo4j.driver + neo4j-java-driver-encryption-local + ${project.version} + + + org.neo4j.driver + neo4j-java-driver-encryption-kyber + ${project.version} + org.neo4j.bolt neo4j-bolt-connection-bom diff --git a/bundle/LICENSES.txt b/bundle/LICENSES.txt index 5b15672369..b0aaed3c77 100644 --- a/bundle/LICENSES.txt +++ b/bundle/LICENSES.txt @@ -8,6 +8,7 @@ Apache Software License, Version 2.0 Neo4j Bolt Connection (Pooled Source impl) Neo4j Bolt Connection (Provider SPI) Neo4j Bolt Connection (Routed Source impl) + Neo4j Bolt Connection Codec Netty/Buffer Netty/Codec/Base Netty/Common diff --git a/bundle/NOTICE.txt b/bundle/NOTICE.txt index 1e10d1ddd8..01c648f02a 100644 --- a/bundle/NOTICE.txt +++ b/bundle/NOTICE.txt @@ -23,6 +23,7 @@ Apache Software License, Version 2.0 Neo4j Bolt Connection (Pooled Source impl) Neo4j Bolt Connection (Provider SPI) Neo4j Bolt Connection (Routed Source impl) + Neo4j Bolt Connection Codec Netty/Buffer Netty/Codec/Base Netty/Common diff --git a/bundle/pom.xml b/bundle/pom.xml index a50f42e61f..a4e9d85e8a 100644 --- a/bundle/pom.xml +++ b/bundle/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-parent - 6.2-SNAPSHOT + 6.3-SNAPSHOT .. diff --git a/driver-it/encryption-aws-kms-it/LICENSES.txt b/driver-it/encryption-aws-kms-it/LICENSES.txt new file mode 100644 index 0000000000..f8e0fd3292 --- /dev/null +++ b/driver-it/encryption-aws-kms-it/LICENSES.txt @@ -0,0 +1,5 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + + diff --git a/driver-it/encryption-aws-kms-it/NOTICE.txt b/driver-it/encryption-aws-kms-it/NOTICE.txt new file mode 100644 index 0000000000..c3bf48c6fc --- /dev/null +++ b/driver-it/encryption-aws-kms-it/NOTICE.txt @@ -0,0 +1,20 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + diff --git a/driver-it/encryption-aws-kms-it/pom.xml b/driver-it/encryption-aws-kms-it/pom.xml new file mode 100644 index 0000000000..5b78ebea6a --- /dev/null +++ b/driver-it/encryption-aws-kms-it/pom.xml @@ -0,0 +1,92 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-it + 6.3-SNAPSHOT + + + neo4j-java-driver-encryption-aws-kms-it + + jar + Neo4j Java Driver (Encryption AWS KMS) + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + + + org.neo4j.driver + neo4j-java-driver + test + + + org.neo4j.driver + neo4j-java-driver-encryption-aws-kms + test + + + org.neo4j.driver + neo4j-java-driver-encryption-common-it + ${project.version} + test-jar + test + + + org.junit.jupiter + junit-jupiter + test + + + org.testcontainers + testcontainers-junit-jupiter + test + + + org.testcontainers + testcontainers-neo4j + test + + + + + + + org.neo4j.driver + neo4j-java-driver-bom + pom + import + ${project.version} + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:none + + + + + + + + diff --git a/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/AsyncEnvelopeEncryptionIT.java b/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/AsyncEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..29a776245c --- /dev/null +++ b/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/AsyncEnvelopeEncryptionIT.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.aws_kms; + +import java.security.NoSuchAlgorithmException; +import org.neo4j.driver.it.encryption.common.AbstractAsyncEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.aws_kms.AWSKeyEncapsulationService; +import org.neo4j.driver.property_encryption.aws_kms.AwsKeyEncapsulationOptions; + +final class AsyncEnvelopeEncryptionIT extends AbstractAsyncEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + return new AWSKeyEncapsulationService(AwsKeyEncapsulationOptions.of("34bec748-e9b0-4be7-99c1-9731ff08b73b")); + } +} diff --git a/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/EnvelopeEncryptionIT.java b/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/EnvelopeEncryptionIT.java new file mode 100644 index 0000000000..240d90c082 --- /dev/null +++ b/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/EnvelopeEncryptionIT.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.aws_kms; + +import java.security.NoSuchAlgorithmException; +import org.neo4j.driver.it.encryption.common.AbstractEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.aws_kms.AWSKeyEncapsulationService; +import org.neo4j.driver.property_encryption.aws_kms.AwsKeyEncapsulationOptions; + +class EnvelopeEncryptionIT extends AbstractEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + return new AWSKeyEncapsulationService(AwsKeyEncapsulationOptions.of("34bec748-e9b0-4be7-99c1-9731ff08b73b")); + } +} diff --git a/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/ReactiveEnvelopeEncryptionIT.java b/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/ReactiveEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..0193a98ccf --- /dev/null +++ b/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/ReactiveEnvelopeEncryptionIT.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.aws_kms; + +import java.security.NoSuchAlgorithmException; +import org.neo4j.driver.it.encryption.common.AbstractReactiveEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.aws_kms.AWSKeyEncapsulationService; +import org.neo4j.driver.property_encryption.aws_kms.AwsKeyEncapsulationOptions; + +public final class ReactiveEnvelopeEncryptionIT extends AbstractReactiveEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + return new AWSKeyEncapsulationService(AwsKeyEncapsulationOptions.of("34bec748-e9b0-4be7-99c1-9731ff08b73b")); + } +} diff --git a/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/ReactiveStreamsEnvelopeEncryptionIT.java b/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/ReactiveStreamsEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..4c59b0742a --- /dev/null +++ b/driver-it/encryption-aws-kms-it/src/test/java/org/neo4j/driver/it/encryption/aws_kms/ReactiveStreamsEnvelopeEncryptionIT.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.aws_kms; + +import java.security.NoSuchAlgorithmException; +import org.neo4j.driver.it.encryption.common.AbstractReactiveStreamsEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.aws_kms.AWSKeyEncapsulationService; +import org.neo4j.driver.property_encryption.aws_kms.AwsKeyEncapsulationOptions; + +final class ReactiveStreamsEnvelopeEncryptionIT extends AbstractReactiveStreamsEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + return new AWSKeyEncapsulationService(AwsKeyEncapsulationOptions.of("34bec748-e9b0-4be7-99c1-9731ff08b73b")); + } +} diff --git a/driver-it/encryption-azure-keyvault-it/LICENSES.txt b/driver-it/encryption-azure-keyvault-it/LICENSES.txt new file mode 100644 index 0000000000..f8e0fd3292 --- /dev/null +++ b/driver-it/encryption-azure-keyvault-it/LICENSES.txt @@ -0,0 +1,5 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + + diff --git a/driver-it/encryption-azure-keyvault-it/NOTICE.txt b/driver-it/encryption-azure-keyvault-it/NOTICE.txt new file mode 100644 index 0000000000..c3bf48c6fc --- /dev/null +++ b/driver-it/encryption-azure-keyvault-it/NOTICE.txt @@ -0,0 +1,20 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + diff --git a/driver-it/encryption-azure-keyvault-it/pom.xml b/driver-it/encryption-azure-keyvault-it/pom.xml new file mode 100644 index 0000000000..2782c07782 --- /dev/null +++ b/driver-it/encryption-azure-keyvault-it/pom.xml @@ -0,0 +1,92 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-it + 6.3-SNAPSHOT + + + neo4j-java-driver-encryption-azure-keyvault-it + + jar + Neo4j Java Driver (Encryption Azure Key Vault) + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + + + org.neo4j.driver + neo4j-java-driver + test + + + org.neo4j.driver + neo4j-java-driver-encryption-azure-keyvault + test + + + org.neo4j.driver + neo4j-java-driver-encryption-common-it + ${project.version} + test-jar + test + + + org.junit.jupiter + junit-jupiter + test + + + org.testcontainers + testcontainers-junit-jupiter + test + + + org.testcontainers + testcontainers-neo4j + test + + + + + + + org.neo4j.driver + neo4j-java-driver-bom + pom + import + ${project.version} + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:none + + + + + + + + diff --git a/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/AsyncEnvelopeEncryptionIT.java b/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/AsyncEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..237d83b932 --- /dev/null +++ b/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/AsyncEnvelopeEncryptionIT.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.azure_keyvault; + +import java.security.NoSuchAlgorithmException; +import org.junit.jupiter.api.Disabled; +import org.neo4j.driver.it.encryption.common.AbstractAsyncEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.azure_keyvault.AzureEncapsulationOptions; +import org.neo4j.driver.property_encryption.azure_keyvault.AzureKeyEncapsulationService; + +@Disabled +final class AsyncEnvelopeEncryptionIT extends AbstractAsyncEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + return new AzureKeyEncapsulationService(AzureEncapsulationOptions.of( + "https://drivers-vault.vault.azure.net/keys/drivers-key/e787f754d7b64583b2749a55e9186096")); + } +} diff --git a/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/EnvelopeEncryptionIT.java b/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/EnvelopeEncryptionIT.java new file mode 100644 index 0000000000..ea15741241 --- /dev/null +++ b/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/EnvelopeEncryptionIT.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.azure_keyvault; + +import java.security.NoSuchAlgorithmException; +import org.junit.jupiter.api.Disabled; +import org.neo4j.driver.it.encryption.common.AbstractEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.azure_keyvault.AzureEncapsulationOptions; +import org.neo4j.driver.property_encryption.azure_keyvault.AzureKeyEncapsulationService; + +@Disabled +class EnvelopeEncryptionIT extends AbstractEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + return new AzureKeyEncapsulationService(AzureEncapsulationOptions.of( + "https://drivers-vault.vault.azure.net/keys/drivers-key/e787f754d7b64583b2749a55e9186096")); + } +} diff --git a/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/ReactiveEnvelopeEncryptionIT.java b/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/ReactiveEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..e6bdc861d4 --- /dev/null +++ b/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/ReactiveEnvelopeEncryptionIT.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.azure_keyvault; + +import java.security.NoSuchAlgorithmException; +import org.junit.jupiter.api.Disabled; +import org.neo4j.driver.it.encryption.common.AbstractReactiveEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.azure_keyvault.AzureEncapsulationOptions; +import org.neo4j.driver.property_encryption.azure_keyvault.AzureKeyEncapsulationService; + +@Disabled +public final class ReactiveEnvelopeEncryptionIT extends AbstractReactiveEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + return new AzureKeyEncapsulationService(AzureEncapsulationOptions.of( + "https://drivers-vault.vault.azure.net/keys/drivers-key/e787f754d7b64583b2749a55e9186096")); + } +} diff --git a/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/ReactiveStreamsEnvelopeEncryptionIT.java b/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/ReactiveStreamsEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..0f2014df52 --- /dev/null +++ b/driver-it/encryption-azure-keyvault-it/src/test/java/org/neo4j/driver/it/encryption/azure_keyvault/ReactiveStreamsEnvelopeEncryptionIT.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.azure_keyvault; + +import java.security.NoSuchAlgorithmException; +import org.junit.jupiter.api.Disabled; +import org.neo4j.driver.it.encryption.common.AbstractReactiveStreamsEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.azure_keyvault.AzureEncapsulationOptions; +import org.neo4j.driver.property_encryption.azure_keyvault.AzureKeyEncapsulationService; + +@Disabled +final class ReactiveStreamsEnvelopeEncryptionIT extends AbstractReactiveStreamsEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + return new AzureKeyEncapsulationService(AzureEncapsulationOptions.of( + "https://drivers-vault.vault.azure.net/keys/drivers-key/e787f754d7b64583b2749a55e9186096")); + } +} diff --git a/driver-it/encryption-common-it/LICENSES.txt b/driver-it/encryption-common-it/LICENSES.txt new file mode 100644 index 0000000000..f8e0fd3292 --- /dev/null +++ b/driver-it/encryption-common-it/LICENSES.txt @@ -0,0 +1,5 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + + diff --git a/driver-it/encryption-common-it/NOTICE.txt b/driver-it/encryption-common-it/NOTICE.txt new file mode 100644 index 0000000000..c3bf48c6fc --- /dev/null +++ b/driver-it/encryption-common-it/NOTICE.txt @@ -0,0 +1,20 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + diff --git a/driver-it/encryption-common-it/pom.xml b/driver-it/encryption-common-it/pom.xml new file mode 100644 index 0000000000..e7e6224cd3 --- /dev/null +++ b/driver-it/encryption-common-it/pom.xml @@ -0,0 +1,82 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-it + 6.3-SNAPSHOT + + + neo4j-java-driver-encryption-common-it + + jar + Neo4j Java Driver (Encryption Common) + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + + + org.neo4j.driver + neo4j-java-driver + ${project.version} + test + + + org.junit.jupiter + junit-jupiter + test + + + org.testcontainers + testcontainers-junit-jupiter + test + ${testcontainers.version} + + + org.testcontainers + testcontainers-neo4j + test + ${testcontainers.version} + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:none + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + + + + diff --git a/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractAsyncEnvelopeEncryptionIT.java b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractAsyncEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..fedeb55a6d --- /dev/null +++ b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractAsyncEnvelopeEncryptionIT.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.common; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption; + +public abstract class AbstractAsyncEnvelopeEncryptionIT + extends AbstractBaseEnvelopeEncryptionIT { + @Override + protected Class encryptionClass() { + return AsyncPropertyEncryption.class; + } + + @Override + protected EncapsulatedKey createKey(String keyAlias) { + return encryption + .keyManager() + .createAsync(keyAlias) + .toCompletableFuture() + .join(); + } + + @Override + protected void updateAliasById(String id, String alias) { + encryption + .keyManager() + .updateAliasByIdAsync(id, alias) + .toCompletableFuture() + .join(); + } + + @Override + protected void deleteAliasById(String id) { + encryption.keyManager().deleteAliasByIdAsync(id).toCompletableFuture().join(); + } + + @Override + protected void deleteKey() { + encryption + .keyManager() + .findByAliasAsync(keyAlias) + .thenCompose(key -> key == null + ? CompletableFuture.completedStage(null) + : encryption.keyManager().deleteByIdAsync(key.id())); + } + + @Override + protected byte[] encrypt(PropertyEncryptionRequest request) { + return encryption.encryptToBytesAsync(request).toCompletableFuture().join(); + } + + @Override + protected Value decrypt(PropertyDecryptionRequest request) { + return encryption.decryptAsync(request).toCompletableFuture().join(); + } + + @Override + protected Optional findByAlias(String alias) { + return Optional.ofNullable(encryption + .keyManager() + .findByAliasAsync(alias) + .toCompletableFuture() + .join()); + } +} diff --git a/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractBaseEnvelopeEncryptionIT.java b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractBaseEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..c9b52f8606 --- /dev/null +++ b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractBaseEnvelopeEncryptionIT.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.common; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Config; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.EnvelopePropertyEncryptionProfile; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +@Testcontainers +abstract class AbstractBaseEnvelopeEncryptionIT { + @SuppressWarnings("resource") + @Container + private static final Neo4jContainer neo4jContainer = new Neo4jContainer<>( + DockerImageName.parse("neo4j:%s-enterprise" + .formatted(Optional.ofNullable(System.getenv("NEO4J_VERSION")) + .orElse("2025.04.0")))) + .withEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes"); + + KeyEncapsulationService keyEncapsulationService; + String keyAlias = "main-key"; + Driver keyDriver; + Driver driver; + T encryption; + + @BeforeEach + void beforeEach() throws IOException, NoSuchAlgorithmException { + keyEncapsulationService = keyEncapsulationService(); + + keyDriver = GraphDatabase.driver( + neo4jContainer.getBoltUrl(), AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword())); + var keyRepository = new Neo4JKeyRecordRepository(keyDriver, "neo4j"); + keyRepository.createConstraints(); + + var provider = provider(); + var builder = EnvelopePropertyEncryptionProfile.builder("reference", keyEncapsulationService, keyRepository); + if (provider != null) { + builder = builder.withCryptoContext(provider, secureRandomIV(provider)); + } + var encryptionProfile = builder.build(); + var config = Config.builder() + .withPropertyEncryptionProfiles(encryptionProfile) + .build(); + driver = GraphDatabase.driver( + neo4jContainer.getBoltUrl(), AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword()), config); + encryption = driver.propertyEncryption(encryptionClass()); + createKey(keyAlias); + } + + @Test + void shouldUpdateAlias() { + var originalAlias = "alias"; + var key = createKey(originalAlias); + + deleteAliasById(key.id()); + var updatedAlias = "updatedAlias"; + updateAliasById(key.id(), updatedAlias); + + var updatedKey = findByAlias(updatedAlias).orElseThrow(); + + assertEquals(key.id(), updatedKey.id()); + assertEquals(updatedAlias, updatedKey.alias().orElse(null)); + } + + @Test + void shouldEncryptAndDecrypt() { + var name = "username"; + var phone = "00000000"; + + // WRITE TO DATABASE + var encryptRequest = PropertyEncryptionRequest.builder() + .fromValue(phone) + .withAAD(name) + .usingKeyAlias(keyAlias) + .build(); + var encryptedPhone = encrypt(encryptRequest); + var result = driver.executableQuery("CREATE (user:User {name: $name, phone: $phone}) RETURN user") + .withParameters(Map.of("name", name, "phone", encryptedPhone)) + .execute(); + + // READ ENCRYPTED DATA + var user = result.records().get(0).get("user"); + var decryptRequest = PropertyDecryptionRequest.builder() + .fromValue(user.get("phone").asByteArray()) + .withAAD(name) + .build(); + var decryptedPhone = decrypt(decryptRequest).asString(); + + assertEquals(phone, decryptedPhone); + } + + @AfterEach + void afterEach() { + deleteKey(); + driver.close(); + } + + protected abstract KeyEncapsulationService keyEncapsulationService() throws IOException, NoSuchAlgorithmException; + + protected abstract Class encryptionClass(); + + protected abstract EncapsulatedKey createKey(String alias); + + protected abstract void updateAliasById(String id, String alias); + + protected abstract void deleteAliasById(String id); + + protected abstract Optional findByAlias(String alias); + + protected abstract void deleteKey(); + + protected abstract byte[] encrypt(PropertyEncryptionRequest request); + + protected abstract Value decrypt(PropertyDecryptionRequest request); + + protected Provider provider() { + return null; + } + + protected SecureRandom secureRandomIV(Provider provider) throws NoSuchAlgorithmException { + return null; + } +} diff --git a/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractEnvelopeEncryptionIT.java b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..6f409ee93a --- /dev/null +++ b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractEnvelopeEncryptionIT.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.common; + +import java.util.Optional; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; + +public abstract class AbstractEnvelopeEncryptionIT extends AbstractBaseEnvelopeEncryptionIT { + + @Override + protected Class encryptionClass() { + return PropertyEncryption.class; + } + + @Override + protected EncapsulatedKey createKey(String keyAlias) { + return encryption.keyManager().create(keyAlias); + } + + @Override + protected void updateAliasById(String id, String alias) { + encryption.keyManager().updateAliasById(id, alias); + } + + @Override + protected void deleteAliasById(String id) { + encryption.keyManager().deleteAliasById(id); + } + + @Override + protected void deleteKey() { + encryption.keyManager().findByAlias(keyAlias).ifPresent(key -> encryption + .keyManager() + .deleteById(key.id())); + } + + @Override + protected Optional findByAlias(String alias) { + return encryption.keyManager().findByAlias(alias); + } + + @Override + protected byte[] encrypt(PropertyEncryptionRequest request) { + return encryption.encryptToBytes(request); + } + + @Override + protected Value decrypt(PropertyDecryptionRequest request) { + return encryption.decrypt(request); + } +} diff --git a/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractReactiveEnvelopeEncryptionIT.java b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractReactiveEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..b01c5540b8 --- /dev/null +++ b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractReactiveEnvelopeEncryptionIT.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.common; + +import java.util.Optional; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption; +import reactor.adapter.JdkFlowAdapter; + +public abstract class AbstractReactiveEnvelopeEncryptionIT + extends AbstractBaseEnvelopeEncryptionIT { + + @Override + protected Class encryptionClass() { + return ReactivePropertyEncryption.class; + } + + @Override + protected EncapsulatedKey createKey(String keyAlias) { + return JdkFlowAdapter.flowPublisherToFlux(encryption.keyManager().create(keyAlias)) + .blockFirst(); + } + + @Override + protected void updateAliasById(String id, String alias) { + JdkFlowAdapter.flowPublisherToFlux(encryption.keyManager().updateAliasById(id, alias)) + .blockFirst(); + } + + @Override + protected void deleteAliasById(String id) { + JdkFlowAdapter.flowPublisherToFlux(encryption.keyManager().deleteAliasById(id)) + .blockFirst(); + } + + @Override + protected void deleteKey() { + JdkFlowAdapter.flowPublisherToFlux(encryption.keyManager().findByAlias(keyAlias)) + .flatMap(key -> JdkFlowAdapter.flowPublisherToFlux( + encryption.keyManager().deleteById(key.id()))) + .blockFirst(); + } + + @Override + protected byte[] encrypt(PropertyEncryptionRequest request) { + return JdkFlowAdapter.flowPublisherToFlux(encryption.encryptToBytes(request)) + .blockFirst(); + } + + @Override + protected Value decrypt(PropertyDecryptionRequest request) { + return JdkFlowAdapter.flowPublisherToFlux(encryption.decrypt(request)).blockFirst(); + } + + @Override + protected Optional findByAlias(String alias) { + return Optional.ofNullable( + JdkFlowAdapter.flowPublisherToFlux(encryption.keyManager().findByAlias(alias)) + .blockFirst()); + } +} diff --git a/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractReactiveStreamsEnvelopeEncryptionIT.java b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractReactiveStreamsEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..1c9fc267aa --- /dev/null +++ b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/AbstractReactiveStreamsEnvelopeEncryptionIT.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.common; + +import java.util.Optional; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption; +import reactor.core.publisher.Mono; + +public abstract class AbstractReactiveStreamsEnvelopeEncryptionIT + extends AbstractBaseEnvelopeEncryptionIT { + + @Override + protected Class encryptionClass() { + return ReactivePropertyEncryption.class; + } + + @Override + protected EncapsulatedKey createKey(String keyAlias) { + return Mono.fromDirect(encryption.keyManager().create(keyAlias)).block(); + } + + @Override + protected void updateAliasById(String id, String alias) { + Mono.from(encryption.keyManager().updateAliasById(id, alias)).block(); + } + + @Override + protected void deleteAliasById(String id) { + Mono.from(encryption.keyManager().deleteAliasById(id)).block(); + } + + @Override + protected void deleteKey() { + Mono.fromDirect(encryption.keyManager().findByAlias(keyAlias)) + .flatMap(key -> Mono.fromDirect(encryption.keyManager().deleteById(key.id()))) + .block(); + } + + @Override + protected byte[] encrypt(PropertyEncryptionRequest request) { + return Mono.fromDirect(encryption.encryptToBytes(request)).block(); + } + + @Override + protected Value decrypt(PropertyDecryptionRequest request) { + return Mono.fromDirect(encryption.decrypt(request)).block(); + } + + @Override + protected Optional findByAlias(String alias) { + return Optional.ofNullable( + Mono.from(encryption.keyManager().findByAlias(alias)).block()); + } +} diff --git a/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/Neo4JKeyRecordRepository.java b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/Neo4JKeyRecordRepository.java new file mode 100644 index 0000000000..09cc7041dc --- /dev/null +++ b/driver-it/encryption-common-it/src/test/java/org/neo4j/driver/it/encryption/common/Neo4JKeyRecordRepository.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.common; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import org.neo4j.driver.Driver; +import org.neo4j.driver.QueryConfig; +import org.neo4j.driver.Record; +import org.neo4j.driver.SessionConfig; +import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.async.ResultCursor; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecord; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecordRepository; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecords; +import org.neo4j.driver.types.TypeSystem; + +final class Neo4JKeyRecordRepository implements EncapsulatedKeyRecordRepository { + private final Driver driver; + private final String database; + + Neo4JKeyRecordRepository(Driver driver, String database) { + this.driver = Objects.requireNonNull(driver); + this.database = database; + } + + public void createConstraints() { + var builder = QueryConfig.builder(); + if (database != null) { + builder.withDatabase(database); + } + var config = builder.build(); + driver.executableQuery(""" + CREATE CONSTRAINT key_id IF NOT EXISTS + FOR (k:Key) + REQUIRE k.id IS UNIQUE + """).withConfig(config).execute(); + driver.executableQuery(""" + CREATE CONSTRAINT key_aliases IF NOT EXISTS + FOR (k:Key) + REQUIRE k.aliases IS UNIQUE + """).withConfig(config).execute(); + } + + @Override + public CompletionStage findById(String id) { + return session().executeReadAsync(tx -> tx.runAsync("MATCH (key:Key {id: $id}) RETURN key", Map.of("id", id)) + .thenCompose(resultCursor -> resultCursor.nextAsync().thenApply(this::toKey))); + } + + @Override + public CompletionStage findByAlias(String alias) { + return session().executeReadAsync(tx -> tx.runAsync( + "MATCH (key:Key) WHERE key.alias=$alias RETURN key", Map.of("alias", alias)) + .thenCompose(resultCursor -> resultCursor.nextAsync().thenApply(this::toKey))); + } + + @Override + public CompletionStage save( + String alias, byte[] encapsulation, Map metadata) { + var id = UUID.randomUUID().toString(); + var properties = new HashMap(); + properties.put("id", id); + properties.put("alias", alias); + properties.put("encapsulation", encapsulation); + for (var entry : metadata.entrySet()) { + properties.put("metadata." + entry.getKey(), entry.getValue()); + } + + return session().executeWriteAsync(tx -> tx.runAsync( + "MERGE (k:Key {id: $properties.id}) SET k = $properties", Map.of("properties", properties)) + .thenCompose(ResultCursor::consumeAsync) + .thenApply(summary -> EncapsulatedKeyRecords.create(id, alias, encapsulation, metadata))); + } + + @Override + public CompletionStage updateAliasById(String id, String alias) { + return session().executeWriteAsync(tx -> tx.runAsync("MATCH (key:Key {id: $id}) RETURN key", Map.of("id", id)) + .thenCompose(resultCursor -> resultCursor.nextAsync().thenCompose(record -> { + if (record == null) { + throw new ClientException("No key found"); + } else { + var key = toKey(record); + if (alias == null) { + return tx.runAsync("MERGE (k:Key {id: $id}) REMOVE k.alias", Map.of("id", id)) + .thenCompose(ResultCursor::consumeAsync) + .thenApply(summary -> null); + } else { + return tx.runAsync( + "MERGE (k:Key {id: $id}) SET k.alias = $alias", + Map.of("id", id, "alias", alias)) + .thenCompose(ResultCursor::consumeAsync) + .thenApply(summary -> null); + } + } + }))); + } + + @Override + public CompletionStage deleteById(String id) { + return session().executeWriteAsync(tx -> tx.runAsync("MATCH (key:Key {id: $id}) DELETE key", Map.of("id", id)) + .thenCompose(ResultCursor::consumeAsync) + .thenApply(ignored -> null)); + } + + private AsyncSession session() { + var builder = SessionConfig.builder(); + if (database != null) { + builder.withDatabase(database); + } + return driver.session(AsyncSession.class, builder.build()); + } + + private EncapsulatedKeyRecord toKey(Record record) { + var key = record.get("key"); + var id = key.get("id").asString(); + var aliasValue = key.get("alias"); + var alias = aliasValue != null && TypeSystem.getDefault().STRING().isTypeOf(aliasValue) + ? aliasValue.asString() + : null; + var encapsulation = key.get("encapsulation").asByteArray(); + var managerMetadata = new HashMap(); + for (var field : key.keys()) { + if (field.startsWith("metadata.")) { + managerMetadata.put( + field.replace("metadata.", ""), key.get(field).asString()); + } + } + return EncapsulatedKeyRecords.create(id, alias, encapsulation, Map.copyOf(managerMetadata)); + } +} diff --git a/driver-it/encryption-google-cloud-kms-it/LICENSES.txt b/driver-it/encryption-google-cloud-kms-it/LICENSES.txt new file mode 100644 index 0000000000..f8e0fd3292 --- /dev/null +++ b/driver-it/encryption-google-cloud-kms-it/LICENSES.txt @@ -0,0 +1,5 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + + diff --git a/driver-it/encryption-google-cloud-kms-it/NOTICE.txt b/driver-it/encryption-google-cloud-kms-it/NOTICE.txt new file mode 100644 index 0000000000..c3bf48c6fc --- /dev/null +++ b/driver-it/encryption-google-cloud-kms-it/NOTICE.txt @@ -0,0 +1,20 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + diff --git a/driver-it/encryption-google-cloud-kms-it/pom.xml b/driver-it/encryption-google-cloud-kms-it/pom.xml new file mode 100644 index 0000000000..2bcbb906ed --- /dev/null +++ b/driver-it/encryption-google-cloud-kms-it/pom.xml @@ -0,0 +1,92 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-it + 6.3-SNAPSHOT + + + neo4j-java-driver-encryption-google-cloud-kms-it + + jar + Neo4j Java Driver (Encryption Google Cloud KMS) + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + + + org.neo4j.driver + neo4j-java-driver + test + + + org.neo4j.driver + neo4j-java-driver-encryption-google-cloud-kms + test + + + org.neo4j.driver + neo4j-java-driver-encryption-common-it + ${project.version} + test-jar + test + + + org.junit.jupiter + junit-jupiter + test + + + org.testcontainers + testcontainers-junit-jupiter + test + + + org.testcontainers + testcontainers-neo4j + test + + + + + + + org.neo4j.driver + neo4j-java-driver-bom + pom + import + ${project.version} + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:none + + + + + + + + diff --git a/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/AsyncEnvelopeEncryptionIT.java b/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/AsyncEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..2c95d0a069 --- /dev/null +++ b/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/AsyncEnvelopeEncryptionIT.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.google_cloud_kms; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import org.neo4j.driver.it.encryption.common.AbstractAsyncEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.google_cloud_kms.CloudKmsKeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.google_cloud_kms.GoogleCloudKeyEncapsulationService; + +final class AsyncEnvelopeEncryptionIT extends AbstractAsyncEnvelopeEncryptionIT { + @Override + protected KeyEncapsulationService keyEncapsulationService() throws IOException, NoSuchAlgorithmException { + var defaultOptions = CloudKmsKeyEncapsulationOptions.of("keys-learning", "global", "test", "quickstart"); + return new GoogleCloudKeyEncapsulationService(defaultOptions); + } +} diff --git a/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/EnvelopeEncryptionIT.java b/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/EnvelopeEncryptionIT.java new file mode 100644 index 0000000000..ce3a23adaf --- /dev/null +++ b/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/EnvelopeEncryptionIT.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.google_cloud_kms; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import org.neo4j.driver.it.encryption.common.AbstractEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.google_cloud_kms.CloudKmsKeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.google_cloud_kms.GoogleCloudKeyEncapsulationService; + +final class EnvelopeEncryptionIT extends AbstractEnvelopeEncryptionIT { + @Override + protected KeyEncapsulationService keyEncapsulationService() throws IOException, NoSuchAlgorithmException { + var defaultOptions = CloudKmsKeyEncapsulationOptions.of("keys-learning", "global", "test", "quickstart"); + return new GoogleCloudKeyEncapsulationService(defaultOptions); + } +} diff --git a/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/ReactiveEnvelopeEncryptionIT.java b/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/ReactiveEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..e99c86a150 --- /dev/null +++ b/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/ReactiveEnvelopeEncryptionIT.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.google_cloud_kms; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import org.neo4j.driver.it.encryption.common.AbstractReactiveEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.google_cloud_kms.CloudKmsKeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.google_cloud_kms.GoogleCloudKeyEncapsulationService; + +public final class ReactiveEnvelopeEncryptionIT extends AbstractReactiveEnvelopeEncryptionIT { + @Override + protected KeyEncapsulationService keyEncapsulationService() throws IOException, NoSuchAlgorithmException { + var defaultOptions = CloudKmsKeyEncapsulationOptions.of("keys-learning", "global", "test", "quickstart"); + return new GoogleCloudKeyEncapsulationService(defaultOptions); + } +} diff --git a/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/ReactiveStreamsEnvelopeEncryptionIT.java b/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/ReactiveStreamsEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..66ada08ea1 --- /dev/null +++ b/driver-it/encryption-google-cloud-kms-it/src/test/java/org/neo4j/driver/it/encryption/google_cloud_kms/ReactiveStreamsEnvelopeEncryptionIT.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.google_cloud_kms; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import org.neo4j.driver.it.encryption.common.AbstractReactiveStreamsEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.google_cloud_kms.CloudKmsKeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.google_cloud_kms.GoogleCloudKeyEncapsulationService; + +final class ReactiveStreamsEnvelopeEncryptionIT extends AbstractReactiveStreamsEnvelopeEncryptionIT { + @Override + protected KeyEncapsulationService keyEncapsulationService() throws IOException, NoSuchAlgorithmException { + var defaultOptions = CloudKmsKeyEncapsulationOptions.of("keys-learning", "global", "test", "quickstart"); + return new GoogleCloudKeyEncapsulationService(defaultOptions); + } +} diff --git a/driver-it/encryption-kyber-it/LICENSES.txt b/driver-it/encryption-kyber-it/LICENSES.txt new file mode 100644 index 0000000000..f8e0fd3292 --- /dev/null +++ b/driver-it/encryption-kyber-it/LICENSES.txt @@ -0,0 +1,5 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + + diff --git a/driver-it/encryption-kyber-it/NOTICE.txt b/driver-it/encryption-kyber-it/NOTICE.txt new file mode 100644 index 0000000000..c3bf48c6fc --- /dev/null +++ b/driver-it/encryption-kyber-it/NOTICE.txt @@ -0,0 +1,20 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + diff --git a/driver-it/encryption-kyber-it/pom.xml b/driver-it/encryption-kyber-it/pom.xml new file mode 100644 index 0000000000..c505c1a906 --- /dev/null +++ b/driver-it/encryption-kyber-it/pom.xml @@ -0,0 +1,98 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-it + 6.3-SNAPSHOT + + + neo4j-java-driver-encryption-kyber-it + + jar + Neo4j Java Driver (Encryption Kyber) + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + + + org.neo4j.driver + neo4j-java-driver + test + + + org.neo4j.driver + neo4j-java-driver-encryption-kyber + test + + + org.bouncycastle + bcprov-jdk18on + 1.83 + test + + + org.neo4j.driver + neo4j-java-driver-encryption-common-it + ${project.version} + test-jar + test + + + org.junit.jupiter + junit-jupiter + test + + + org.testcontainers + testcontainers-junit-jupiter + test + + + org.testcontainers + testcontainers-neo4j + test + + + + + + + org.neo4j.driver + neo4j-java-driver-bom + pom + import + ${project.version} + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:none + + + + + + + + diff --git a/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/AsyncEnvelopeEncryptionIT.java b/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/AsyncEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..3b1c51f1e2 --- /dev/null +++ b/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/AsyncEnvelopeEncryptionIT.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.local; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import org.bouncycastle.pqc.jcajce.interfaces.KyberPrivateKey; +import org.bouncycastle.pqc.jcajce.interfaces.KyberPublicKey; +import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider; +import org.bouncycastle.pqc.jcajce.spec.KyberParameterSpec; +import org.neo4j.driver.it.encryption.common.AbstractAsyncEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.kyber.KyberEncapsulationService; + +final class AsyncEnvelopeEncryptionIT extends AbstractAsyncEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + // 1. Add Bouncy Castle PQC provider + Security.addProvider(new BouncyCastlePQCProvider()); + + // 2. Initialize KeyPairGenerator for Kyber (choose kyber512, kyber768, kyber1024) + KeyPairGenerator kpg; + try { + kpg = KeyPairGenerator.getInstance("Kyber", "BCPQC"); + kpg.initialize(KyberParameterSpec.kyber768); // example + } catch (NoSuchProviderException | InvalidAlgorithmParameterException e) { + throw new RuntimeException(e); + } + + // 3. Generate key pair + var keyPair = kpg.generateKeyPair(); + + // 4. Extract typed Kyber keys + var publicKey = (KyberPublicKey) keyPair.getPublic(); + var privateKey = (KyberPrivateKey) keyPair.getPrivate(); + return new KyberEncapsulationService(publicKey, privateKey); + } +} diff --git a/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/EnvelopeEncryptionIT.java b/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/EnvelopeEncryptionIT.java new file mode 100644 index 0000000000..0e97777b3c --- /dev/null +++ b/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/EnvelopeEncryptionIT.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.local; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import org.bouncycastle.pqc.jcajce.interfaces.KyberPrivateKey; +import org.bouncycastle.pqc.jcajce.interfaces.KyberPublicKey; +import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider; +import org.bouncycastle.pqc.jcajce.spec.KyberParameterSpec; +import org.neo4j.driver.it.encryption.common.AbstractEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.kyber.KyberEncapsulationService; + +class EnvelopeEncryptionIT extends AbstractEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + // 1. Add Bouncy Castle PQC provider + Security.addProvider(new BouncyCastlePQCProvider()); + + // 2. Initialize KeyPairGenerator for Kyber (choose kyber512, kyber768, kyber1024) + KeyPairGenerator kpg; + try { + kpg = KeyPairGenerator.getInstance("Kyber", "BCPQC"); + kpg.initialize(KyberParameterSpec.kyber768); // example + } catch (NoSuchProviderException | InvalidAlgorithmParameterException e) { + throw new RuntimeException(e); + } + + // 3. Generate key pair + var keyPair = kpg.generateKeyPair(); + + // 4. Extract typed Kyber keys + var publicKey = (KyberPublicKey) keyPair.getPublic(); + var privateKey = (KyberPrivateKey) keyPair.getPrivate(); + return new KyberEncapsulationService(publicKey, privateKey); + } +} diff --git a/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveEnvelopeEncryptionIT.java b/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..5f2845e241 --- /dev/null +++ b/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveEnvelopeEncryptionIT.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.local; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import org.bouncycastle.pqc.jcajce.interfaces.KyberPrivateKey; +import org.bouncycastle.pqc.jcajce.interfaces.KyberPublicKey; +import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider; +import org.bouncycastle.pqc.jcajce.spec.KyberParameterSpec; +import org.neo4j.driver.it.encryption.common.AbstractReactiveEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.kyber.KyberEncapsulationService; + +public final class ReactiveEnvelopeEncryptionIT extends AbstractReactiveEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + // 1. Add Bouncy Castle PQC provider + Security.addProvider(new BouncyCastlePQCProvider()); + + // 2. Initialize KeyPairGenerator for Kyber (choose kyber512, kyber768, kyber1024) + KeyPairGenerator kpg; + try { + kpg = KeyPairGenerator.getInstance("Kyber", "BCPQC"); + kpg.initialize(KyberParameterSpec.kyber768); // example + } catch (NoSuchProviderException | InvalidAlgorithmParameterException e) { + throw new RuntimeException(e); + } + + // 3. Generate key pair + var keyPair = kpg.generateKeyPair(); + + // 4. Extract typed Kyber keys + var publicKey = (KyberPublicKey) keyPair.getPublic(); + var privateKey = (KyberPrivateKey) keyPair.getPrivate(); + return new KyberEncapsulationService(publicKey, privateKey); + } +} diff --git a/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveStreamsEnvelopeEncryptionIT.java b/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveStreamsEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..150453eef4 --- /dev/null +++ b/driver-it/encryption-kyber-it/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveStreamsEnvelopeEncryptionIT.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.local; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import org.bouncycastle.pqc.jcajce.interfaces.KyberPrivateKey; +import org.bouncycastle.pqc.jcajce.interfaces.KyberPublicKey; +import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider; +import org.bouncycastle.pqc.jcajce.spec.KyberParameterSpec; +import org.neo4j.driver.it.encryption.common.AbstractReactiveStreamsEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.kyber.KyberEncapsulationService; + +final class ReactiveStreamsEnvelopeEncryptionIT extends AbstractReactiveStreamsEnvelopeEncryptionIT { + + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + // 1. Add Bouncy Castle PQC provider + Security.addProvider(new BouncyCastlePQCProvider()); + + // 2. Initialize KeyPairGenerator for Kyber (choose kyber512, kyber768, kyber1024) + KeyPairGenerator kpg; + try { + kpg = KeyPairGenerator.getInstance("Kyber", "BCPQC"); + kpg.initialize(KyberParameterSpec.kyber768); // example + } catch (NoSuchProviderException | InvalidAlgorithmParameterException e) { + throw new RuntimeException(e); + } + + // 3. Generate key pair + var keyPair = kpg.generateKeyPair(); + + // 4. Extract typed Kyber keys + var publicKey = (KyberPublicKey) keyPair.getPublic(); + var privateKey = (KyberPrivateKey) keyPair.getPrivate(); + return new KyberEncapsulationService(publicKey, privateKey); + } +} diff --git a/driver-it/encryption-local-bc-fips/LICENSES.txt b/driver-it/encryption-local-bc-fips/LICENSES.txt new file mode 100644 index 0000000000..f8e0fd3292 --- /dev/null +++ b/driver-it/encryption-local-bc-fips/LICENSES.txt @@ -0,0 +1,5 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + + diff --git a/driver-it/encryption-local-bc-fips/NOTICE.txt b/driver-it/encryption-local-bc-fips/NOTICE.txt new file mode 100644 index 0000000000..c3bf48c6fc --- /dev/null +++ b/driver-it/encryption-local-bc-fips/NOTICE.txt @@ -0,0 +1,20 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + diff --git a/driver-it/encryption-local-bc-fips/pom.xml b/driver-it/encryption-local-bc-fips/pom.xml new file mode 100644 index 0000000000..c5c78e16a8 --- /dev/null +++ b/driver-it/encryption-local-bc-fips/pom.xml @@ -0,0 +1,93 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-it + 6.3-SNAPSHOT + + + neo4j-java-driver-encryption-local-bc-fips + + jar + Neo4j Java Driver (Encryption Local BC FIPS) + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + + + org.neo4j.driver + neo4j-java-driver + test + + + org.neo4j.driver + neo4j-java-driver-encryption-common-it + ${project.version} + test-jar + test + + + org.bouncycastle + bc-fips + 2.1.3 + test + + + org.junit.jupiter + junit-jupiter + test + + + org.testcontainers + testcontainers-junit-jupiter + test + + + org.testcontainers + testcontainers-neo4j + test + + + + + + + org.neo4j.driver + neo4j-java-driver-bom + pom + import + ${project.version} + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:none + + + + + + + + diff --git a/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/AsyncEnvelopeEncryptionIT.java b/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/AsyncEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..9b06101e72 --- /dev/null +++ b/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/AsyncEnvelopeEncryptionIT.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.local; + +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import javax.crypto.KeyGenerator; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.neo4j.driver.it.encryption.common.AbstractAsyncEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.KeyEncapsulationServices; + +final class AsyncEnvelopeEncryptionIT extends AbstractAsyncEnvelopeEncryptionIT { + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + var provider = provider(); + var keyGenerator = KeyGenerator.getInstance("AES", provider); + keyGenerator.init(256); + var masterKey = keyGenerator.generateKey(); + return KeyEncapsulationServices.local(masterKey, provider, secureRandomIV(provider)); + } + + @Override + protected Provider provider() { + return new BouncyCastleFipsProvider(); + } + + @Override + protected SecureRandom secureRandomIV(Provider provider) throws NoSuchAlgorithmException { + return SecureRandom.getInstance("NONCEANDIV", provider); + } +} diff --git a/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/EnvelopeEncryptionIT.java b/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/EnvelopeEncryptionIT.java new file mode 100644 index 0000000000..4ad555de5e --- /dev/null +++ b/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/EnvelopeEncryptionIT.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.local; + +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import javax.crypto.KeyGenerator; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.neo4j.driver.it.encryption.common.AbstractEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.KeyEncapsulationServices; + +class EnvelopeEncryptionIT extends AbstractEnvelopeEncryptionIT { + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + var provider = provider(); + var keyGenerator = KeyGenerator.getInstance("AES", provider); + keyGenerator.init(256); + var masterKey = keyGenerator.generateKey(); + return KeyEncapsulationServices.local(masterKey, provider, secureRandomIV(provider)); + } + + @Override + protected Provider provider() { + return new BouncyCastleFipsProvider(); + } + + @Override + protected SecureRandom secureRandomIV(Provider provider) throws NoSuchAlgorithmException { + return SecureRandom.getInstance("NONCEANDIV", provider); + } +} diff --git a/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveEnvelopeEncryptionIT.java b/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..006dec19da --- /dev/null +++ b/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveEnvelopeEncryptionIT.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.local; + +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import javax.crypto.KeyGenerator; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.neo4j.driver.it.encryption.common.AbstractReactiveEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.KeyEncapsulationServices; + +public final class ReactiveEnvelopeEncryptionIT extends AbstractReactiveEnvelopeEncryptionIT { + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + var provider = provider(); + var keyGenerator = KeyGenerator.getInstance("AES", provider); + keyGenerator.init(256); + var masterKey = keyGenerator.generateKey(); + return KeyEncapsulationServices.local(masterKey, provider, secureRandomIV(provider)); + } + + @Override + protected Provider provider() { + return new BouncyCastleFipsProvider(); + } + + @Override + protected SecureRandom secureRandomIV(Provider provider) throws NoSuchAlgorithmException { + return SecureRandom.getInstance("NONCEANDIV", provider); + } +} diff --git a/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveStreamsEnvelopeEncryptionIT.java b/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveStreamsEnvelopeEncryptionIT.java new file mode 100644 index 0000000000..f338aab158 --- /dev/null +++ b/driver-it/encryption-local-bc-fips/src/test/java/org/neo4j/driver/it/encryption/local/ReactiveStreamsEnvelopeEncryptionIT.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.it.encryption.local; + +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import javax.crypto.KeyGenerator; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.neo4j.driver.it.encryption.common.AbstractReactiveStreamsEnvelopeEncryptionIT; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.KeyEncapsulationServices; + +final class ReactiveStreamsEnvelopeEncryptionIT extends AbstractReactiveStreamsEnvelopeEncryptionIT { + @Override + protected KeyEncapsulationService keyEncapsulationService() throws NoSuchAlgorithmException { + var provider = provider(); + var keyGenerator = KeyGenerator.getInstance("AES", provider); + keyGenerator.init(256); + var masterKey = keyGenerator.generateKey(); + return KeyEncapsulationServices.local(masterKey, provider, secureRandomIV(provider)); + } + + @Override + protected Provider provider() { + return new BouncyCastleFipsProvider(); + } + + @Override + protected SecureRandom secureRandomIV(Provider provider) throws NoSuchAlgorithmException { + return SecureRandom.getInstance("NONCEANDIV", provider); + } +} diff --git a/driver-it/jul-to-slf4j-log4j-it/pom.xml b/driver-it/jul-to-slf4j-log4j-it/pom.xml index c82cb7cd4a..efcdb4d505 100644 --- a/driver-it/jul-to-slf4j-log4j-it/pom.xml +++ b/driver-it/jul-to-slf4j-log4j-it/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-it - 6.2-SNAPSHOT + 6.3-SNAPSHOT neo4j-java-driver-jul-to-slf4j-log4j-it diff --git a/driver-it/jul-to-slf4j-logback-it/pom.xml b/driver-it/jul-to-slf4j-logback-it/pom.xml index 87bc6be8fa..39d41b8389 100644 --- a/driver-it/jul-to-slf4j-logback-it/pom.xml +++ b/driver-it/jul-to-slf4j-logback-it/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-it - 6.2-SNAPSHOT + 6.3-SNAPSHOT neo4j-java-driver-jul-to-slf4j-logback-it diff --git a/driver-it/log4j-it/pom.xml b/driver-it/log4j-it/pom.xml index 4e5c0c8c10..7c908bdf68 100644 --- a/driver-it/log4j-it/pom.xml +++ b/driver-it/log4j-it/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-it - 6.2-SNAPSHOT + 6.3-SNAPSHOT neo4j-java-driver-log4j-it diff --git a/driver-it/pom.xml b/driver-it/pom.xml index b45d531c0d..0a844fb278 100644 --- a/driver-it/pom.xml +++ b/driver-it/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-parent - 6.2-SNAPSHOT + 6.3-SNAPSHOT neo4j-java-driver-it @@ -21,6 +21,12 @@ log4j-it slf4j-log4j-it jul-to-slf4j-log4j-it + encryption-common-it + encryption-google-cloud-kms-it + encryption-aws-kms-it + encryption-azure-keyvault-it + encryption-local-bc-fips + encryption-kyber-it @@ -32,6 +38,18 @@ pom import + + org.testcontainers + junit-jupiter + test + ${testcontainers.version} + + + org.testcontainers + neo4j + test + ${testcontainers.version} + diff --git a/driver-it/slf4j-log4j-it/pom.xml b/driver-it/slf4j-log4j-it/pom.xml index bb26762ba5..a29aecec98 100644 --- a/driver-it/slf4j-log4j-it/pom.xml +++ b/driver-it/slf4j-log4j-it/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-it - 6.2-SNAPSHOT + 6.3-SNAPSHOT neo4j-java-driver-slf4j-log4j-it diff --git a/driver-it/slf4j-logback-it/pom.xml b/driver-it/slf4j-logback-it/pom.xml index 340ea39990..93bf09aaa4 100644 --- a/driver-it/slf4j-logback-it/pom.xml +++ b/driver-it/slf4j-logback-it/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-it - 6.2-SNAPSHOT + 6.3-SNAPSHOT neo4j-java-driver-slf4j-logback-it diff --git a/driver/LICENSES.txt b/driver/LICENSES.txt index 5b15672369..b0aaed3c77 100644 --- a/driver/LICENSES.txt +++ b/driver/LICENSES.txt @@ -8,6 +8,7 @@ Apache Software License, Version 2.0 Neo4j Bolt Connection (Pooled Source impl) Neo4j Bolt Connection (Provider SPI) Neo4j Bolt Connection (Routed Source impl) + Neo4j Bolt Connection Codec Netty/Buffer Netty/Codec/Base Netty/Common diff --git a/driver/NOTICE.txt b/driver/NOTICE.txt index 1e10d1ddd8..01c648f02a 100644 --- a/driver/NOTICE.txt +++ b/driver/NOTICE.txt @@ -23,6 +23,7 @@ Apache Software License, Version 2.0 Neo4j Bolt Connection (Pooled Source impl) Neo4j Bolt Connection (Provider SPI) Neo4j Bolt Connection (Routed Source impl) + Neo4j Bolt Connection Codec Netty/Buffer Netty/Codec/Base Netty/Common diff --git a/driver/clirr-ignored-differences.xml b/driver/clirr-ignored-differences.xml index c0e4a96908..acf38f1be6 100644 --- a/driver/clirr-ignored-differences.xml +++ b/driver/clirr-ignored-differences.xml @@ -977,4 +977,16 @@ java.util.Optional queryProfile() + + org/neo4j/driver/Driver + 7012 + org.neo4j.driver.property_encryption.PropertyEncryption propertyEncryption() + + + + org/neo4j/driver/Driver + 7012 + org.neo4j.driver.property_encryption.BasePropertyEncryption propertyEncryption(java.lang.Class) + + diff --git a/driver/pom.xml b/driver/pom.xml index 6c579f35b1..b69a68aa0b 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-parent - 6.2-SNAPSHOT + 6.3-SNAPSHOT neo4j-java-driver @@ -33,6 +33,10 @@ org.neo4j.bolt neo4j-bolt-connection + + org.neo4j.bolt + neo4j-bolt-connection-codec + org.neo4j.bolt neo4j-bolt-connection-netty diff --git a/driver/src/main/java/module-info.java b/driver/src/main/java/module-info.java index a93e0a0641..4d3589f1cf 100644 --- a/driver/src/main/java/module-info.java +++ b/driver/src/main/java/module-info.java @@ -34,10 +34,12 @@ exports org.neo4j.driver.internal.observation to org.neo4j.driver.observation.metrics, org.neo4j.driver.observation.micrometer; + exports org.neo4j.driver.property_encryption; requires org.neo4j.bolt.connection; requires org.neo4j.bolt.connection.pooled; requires org.neo4j.bolt.connection.routed; + requires org.neo4j.bolt.connection.codec; requires reactor.core; requires transitive java.logging; requires transitive org.reactivestreams; @@ -47,4 +49,8 @@ requires static reactor.blockhound; uses org.neo4j.bolt.connection.BoltConnectionProviderFactory; + uses org.neo4j.bolt.connection.codec.packstream.PackStreamEncoderFactory; + uses org.neo4j.bolt.connection.codec.packstream.PackStreamDecoderFactory; + uses org.neo4j.bolt.connection.codec.value_encoding.ValueEncoderFactory; + uses org.neo4j.bolt.connection.codec.value_encoding.ValueDecoderFactory; } diff --git a/driver/src/main/java/org/neo4j/driver/Config.java b/driver/src/main/java/org/neo4j/driver/Config.java index 269d8501fc..c157aed531 100644 --- a/driver/src/main/java/org/neo4j/driver/Config.java +++ b/driver/src/main/java/org/neo4j/driver/Config.java @@ -25,6 +25,7 @@ import java.net.InetAddress; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -40,6 +41,7 @@ import org.neo4j.driver.internal.retry.ExponentialBackoffRetryLogic; import org.neo4j.driver.net.ServerAddressResolver; import org.neo4j.driver.observation.ObservationProvider; +import org.neo4j.driver.property_encryption.PropertyEncryptionProfile; import org.neo4j.driver.util.Experimental; import org.neo4j.driver.util.Immutable; import org.neo4j.driver.util.Preview; @@ -181,6 +183,13 @@ public final class Config implements Serializable { */ private final boolean autoCommitRetriesDisabled; + /** + * The list of {@link PropertyEncryptionProfile} instances. + * @since 6.3.0 + */ + @Preview(name = "Property Encryption") + private final transient Set propertyEncryptionProfiles; + private Config(ConfigBuilder builder) { this.logging = builder.logging; this.logLeakedSessions = builder.logLeakedSessions; @@ -205,6 +214,7 @@ private Config(ConfigBuilder builder) { this.observationProvider = builder.observationProvider; this.tryTcpFastOpen = builder.tryTcpFastOpen; this.autoCommitRetriesDisabled = builder.autoCommitRetriesDisabled; + this.propertyEncryptionProfiles = builder.propertyEncryptionProfiles; } /** @@ -451,6 +461,16 @@ public boolean isAutoCommitRetriesDisabled() { return autoCommitRetriesDisabled; } + /** + * Returns the set of {@link PropertyEncryptionProfile} instances. + * @return the set of property encryption profiles + * @since 6.3.0 + */ + @Preview(name = "Property Encryption") + public Set propertyEncryptionProfiles() { + return propertyEncryptionProfiles; + } + /** * Used to build new config instances */ @@ -475,6 +495,7 @@ public static final class ConfigBuilder { private ObservationProvider observationProvider; private boolean tryTcpFastOpen; private boolean autoCommitRetriesDisabled; + private Set propertyEncryptionProfiles = Set.of(); @SuppressWarnings("deprecation") private NotificationConfig notificationConfig = NotificationConfig.defaultConfig(); @@ -806,12 +827,37 @@ public ConfigBuilder withResolver(ServerAddressResolver resolver) { @Preview(name = "Observability") public ConfigBuilder withObservationProvider(ObservationProvider observationProvider) { if (observationProvider != null && !(observationProvider instanceof DriverObservationProvider)) { - throw new IllegalArgumentException("Unssupported observation provider"); + throw new IllegalArgumentException("Unsupported observation provider"); } this.observationProvider = observationProvider; return this; } + /** + * Sets {@link PropertyEncryptionProfile} instances that the driver should use. + * @param propertyEncryptionProfiles the property encryption profiles, all + * {@link PropertyEncryptionProfile#name()} values must be unique + * @return this builder + * @since 6.3.0 + */ + @Preview(name = "Property Encryption") + public ConfigBuilder withPropertyEncryptionProfiles(PropertyEncryptionProfile... propertyEncryptionProfiles) { + if (propertyEncryptionProfiles == null) { + this.propertyEncryptionProfiles = Set.of(); + } else { + var names = new HashSet(propertyEncryptionProfiles.length); + for (var profile : propertyEncryptionProfiles) { + Objects.requireNonNull(profile, "profile entries must not be null"); + if (!names.add(profile.name())) { + throw new IllegalArgumentException( + "Duplicate property encryption profile name found: " + profile.name()); + } + } + this.propertyEncryptionProfiles = Set.of(propertyEncryptionProfiles); + } + return this; + } + /** * Configure the event loop thread count. This specifies how many threads the driver can use to handle network I/O events * and user's events in driver's I/O threads. By default, 2 * NumberOfProcessors amount of threads will be used instead. diff --git a/driver/src/main/java/org/neo4j/driver/Driver.java b/driver/src/main/java/org/neo4j/driver/Driver.java index d9e1faf93e..0417e36f72 100644 --- a/driver/src/main/java/org/neo4j/driver/Driver.java +++ b/driver/src/main/java/org/neo4j/driver/Driver.java @@ -18,6 +18,9 @@ import java.util.concurrent.CompletionStage; import org.neo4j.driver.exceptions.UnsupportedFeatureException; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyEncryption; +import org.neo4j.driver.util.Preview; /** * Accessor for a specific Neo4j graph database. @@ -54,6 +57,29 @@ * */ public interface Driver extends AutoCloseable { + + /** + * Returns a new {@link PropertyEncryption} instance for Neo4j Property encryption. + * + * @return property encryption instance + * @since 6.3.0 + */ + @Preview(name = "Property Encryption") + default PropertyEncryption propertyEncryption() { + return propertyEncryption(PropertyEncryption.class); + } + + /** + * Returns a new {@link BasePropertyEncryption} instance of a supported subtype for Neo4j Property encryption. + * + * @param propertyEncryptionClass property encryption type class, must not be {@literal null} + * @return property encryption instance + * @param property encryption type + * @since 6.3.0 + */ + @Preview(name = "Property Encryption") + T propertyEncryption(Class propertyEncryptionClass); + /** * Creates a new {@link ExecutableQuery} instance that executes a query in a managed transaction with automatic retries on * retryable errors. diff --git a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java index 11ed7723b9..02010d7908 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -42,6 +43,9 @@ import org.neo4j.bolt.connection.LoggingProvider; import org.neo4j.bolt.connection.NotificationConfig; import org.neo4j.bolt.connection.RoutedBoltConnectionParameters; +import org.neo4j.bolt.connection.codec.packstream.struct.EncryptedStructureDecoder; +import org.neo4j.bolt.connection.codec.packstream.struct.EncryptedStructureEncoder; +import org.neo4j.bolt.connection.codec.value_encoding.ValueEncodingSchemeVersion; import org.neo4j.bolt.connection.pooled.PooledBoltConnectionSource; import org.neo4j.bolt.connection.pooled.SecurityPlanSupplier; import org.neo4j.bolt.connection.routed.BoltConnectionSourceFactory; @@ -53,6 +57,7 @@ import org.neo4j.driver.Driver; import org.neo4j.driver.Logging; import org.neo4j.driver.exceptions.AuthTokenManagerExecutionException; +import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.adaptedbolt.AdaptingDriverBoltConnectionSource; import org.neo4j.driver.internal.adaptedbolt.BoltAuthTokenManager; import org.neo4j.driver.internal.adaptedbolt.BoltConnectionProviderFactoryLoader; @@ -65,6 +70,13 @@ import org.neo4j.driver.internal.homedb.HomeDatabaseCache; import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.internal.observation.NoopObservationProvider; +import org.neo4j.driver.internal.property_encryption.AEADEncryption; +import org.neo4j.driver.internal.property_encryption.EnvelopePropertyEncryptionHandler; +import org.neo4j.driver.internal.property_encryption.PackStreamDecoderFactoryLoader; +import org.neo4j.driver.internal.property_encryption.PackStreamEncoderFactoryLoader; +import org.neo4j.driver.internal.property_encryption.PropertyEncryptionHandler; +import org.neo4j.driver.internal.property_encryption.ValueDecoderFactoryLoader; +import org.neo4j.driver.internal.property_encryption.ValueEncoderFactoryLoader; import org.neo4j.driver.internal.retry.ExponentialBackoffRetryLogic; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; @@ -73,6 +85,7 @@ import org.neo4j.driver.internal.util.DriverInfoUtil; import org.neo4j.driver.internal.value.BoltValueFactory; import org.neo4j.driver.net.ServerAddress; +import org.neo4j.driver.property_encryption.EnvelopePropertyEncryptionProfile; public class DriverFactory { public static final String NO_ROUTING_CONTEXT_ERROR_MESSAGE = @@ -425,13 +438,57 @@ protected SocketAddress localAddress() { @SuppressWarnings("deprecation") protected InternalDriver createDriver( BoltSecurityPlanManager securityPlanManager, SessionFactory sessionFactory, Config config) { + var aeadEncryption = createAEADEncryption(config); return new InternalDriver( securityPlanManager, sessionFactory, config.isTelemetryDisabled(), config.logging(), (DriverObservationProvider) - config.observationProvider().orElseGet(NoopObservationProvider::getInstance)); + config.observationProvider().orElseGet(NoopObservationProvider::getInstance), + createEncryptionHandlers(aeadEncryption, config), + aeadEncryption); + } + + private Map createEncryptionHandlers( + AEADEncryption aeadEncryption, Config config) { + return config.propertyEncryptionProfiles().stream() + .map(profile -> { + if (profile instanceof EnvelopePropertyEncryptionProfile encryptionProfile) { + return new EnvelopePropertyEncryptionHandler(encryptionProfile, aeadEncryption, createClock()); + } else { + throw new ClientException("Unknown profile: " + profile); + } + }) + .collect(Collectors.toMap( + PropertyEncryptionHandler::profileName, Function.identity(), (existing, duplicate) -> { + throw new ClientException( + "Duplicate property encryption profile name found: " + existing.profileName()); + })); + } + + private AEADEncryption createAEADEncryption(Config config) { + @SuppressWarnings("deprecation") + var logging = config.logging(); + + var valueEncoderFactoryLoader = new ValueEncoderFactoryLoader(logging); + var valueEncoderFactory = valueEncoderFactoryLoader.factory(); + var valueEncoder = valueEncoderFactory.create(ValueEncodingSchemeVersion.V1_0); + + var valueDecoderFactoryLoader = new ValueDecoderFactoryLoader(logging); + var valueDecoderFactory = valueDecoderFactoryLoader.factory(); + var valueDecoder = valueDecoderFactory.create(ValueEncodingSchemeVersion.V1_0, BoltValueFactory.getInstance()); + + var packStreamEncoderFactoryLoader = new PackStreamEncoderFactoryLoader(logging); + var packStreamEncoderFactory = packStreamEncoderFactoryLoader.factory(); + var packStreamEncoder = packStreamEncoderFactory.create(Set.of(EncryptedStructureEncoder.getInstance())); + + var packStreamDecoderFactoryLoader = new PackStreamDecoderFactoryLoader(logging); + var packStreamDecoderFactory = packStreamDecoderFactoryLoader.factory(); + var packStreamDecoder = packStreamDecoderFactory.create( + BoltValueFactory.getInstance(), Set.of(EncryptedStructureDecoder.getInstance())); + + return new AEADEncryption(valueEncoder, valueDecoder, packStreamEncoder, packStreamDecoder); } /** diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java index 3092a46645..b33570cf7d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java @@ -19,6 +19,7 @@ import static java.util.Objects.requireNonNull; import static org.neo4j.driver.internal.util.Futures.completedWithNull; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletionStage; @@ -43,8 +44,17 @@ import org.neo4j.driver.internal.async.InternalAsyncSession; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.property_encryption.AEADEncryption; +import org.neo4j.driver.internal.property_encryption.InternalPropertyEncryption; +import org.neo4j.driver.internal.property_encryption.PropertyEncryptionHandler; +import org.neo4j.driver.internal.property_encryption.async.InternalAsyncPropertyEncryption; +import org.neo4j.driver.internal.property_encryption.reactive.InternalReactivePropertyEncryption; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; import org.neo4j.driver.internal.util.Futures; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyEncryption; +import org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption; +import org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption; public class InternalDriver implements Driver { private static final Set INVALID_TOKEN_CODES = Set.of( @@ -57,6 +67,11 @@ public class InternalDriver implements Driver { private final BoltSecurityPlanManager securityPlanManager; private final SessionFactory sessionFactory; private final DriverObservationProvider observationProvider; + private final Map nameToPropertyEncryptionHandler; + private final AEADEncryption aeadEncryption; + + @SuppressWarnings("deprecation") + private final Logging logging; @SuppressWarnings("deprecation") private final Logger log; @@ -70,12 +85,43 @@ public class InternalDriver implements Driver { SessionFactory sessionFactory, boolean telemetryDisabled, @SuppressWarnings("deprecation") Logging logging, - DriverObservationProvider observationProvider) { + DriverObservationProvider observationProvider, + Map nameToPropertyEncryptionHandler, + AEADEncryption aeadEncryption) { this.securityPlanManager = securityPlanManager; this.sessionFactory = sessionFactory; + this.logging = logging; this.log = logging.getLog(getClass()); this.telemetryDisabled = telemetryDisabled; this.observationProvider = Objects.requireNonNull(observationProvider); + this.nameToPropertyEncryptionHandler = Objects.requireNonNull(nameToPropertyEncryptionHandler); + this.aeadEncryption = Objects.requireNonNull(aeadEncryption); + } + + @SuppressWarnings("unchecked") + @Override + public T propertyEncryption(Class propertyEncryptionClass) { + Objects.requireNonNull(propertyEncryptionClass); + if (PropertyEncryption.class.isAssignableFrom(propertyEncryptionClass)) { + return (T) new InternalPropertyEncryption( + nameToPropertyEncryptionHandler, aeadEncryption, observationProvider); + } else if (AsyncPropertyEncryption.class.isAssignableFrom(propertyEncryptionClass)) { + return (T) new InternalAsyncPropertyEncryption( + nameToPropertyEncryptionHandler, aeadEncryption, observationProvider); + } else if (ReactivePropertyEncryption.class.isAssignableFrom(propertyEncryptionClass)) { + return (T) new InternalReactivePropertyEncryption( + nameToPropertyEncryptionHandler, aeadEncryption, observationProvider); + } else if (org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption.class + .isAssignableFrom(propertyEncryptionClass)) { + return (T) + new org.neo4j.driver.internal.property_encryption.reactivestreams + .InternalReactivePropertyEncryption( + nameToPropertyEncryptionHandler, aeadEncryption, observationProvider); + } else { + throw new IllegalArgumentException(String.format( + "Unsupported %s type '%s'", + BasePropertyEncryption.class.getSimpleName(), propertyEncryptionClass.getCanonicalName())); + } } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalEncapsulatedKeyRecord.java b/driver/src/main/java/org/neo4j/driver/internal/InternalEncapsulatedKeyRecord.java new file mode 100644 index 0000000000..e1c5aeeb0c --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalEncapsulatedKeyRecord.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecord; + +public record InternalEncapsulatedKeyRecord( + String id, String aliasValue, byte[] encapsulation, Map metadata) + implements EncapsulatedKeyRecord { + public InternalEncapsulatedKeyRecord { + Objects.requireNonNull(id); + Objects.requireNonNull(encapsulation); + Objects.requireNonNull(metadata); + } + + @Override + public Optional alias() { + return Optional.ofNullable(aliasValue); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/DriverObservationProvider.java b/driver/src/main/java/org/neo4j/driver/internal/observation/DriverObservationProvider.java index 29020bfa0c..d1743854df 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/observation/DriverObservationProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/DriverObservationProvider.java @@ -23,6 +23,7 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.BaseSession; import org.neo4j.driver.observation.ObservationProvider; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; import org.neo4j.driver.types.MapAccessor; public interface DriverObservationProvider extends ObservationProvider { @@ -54,6 +55,22 @@ public interface DriverObservationProvider extends ObservationProvider { Observation resultRecords(Class resultType); + Observation encryptToBytes(Class propertyEncryptionType); + + Observation decrypt(Class propertyEncryptionType); + + Observation createEncapsulatedKey(Class encapsulatedKeyManagerType, String alias); + + Observation findEncapsulatedKeyByAlias(Class encapsulatedKeyManagerType, String alias); + + Observation updateEncapsulatedKeyAlias(Class encapsulatedKeyManagerType, String id, String alias); + + Observation deleteEncapsulatedKey(Class encapsulatedKeyManagerType, String id); + + // Observation accessKeyRepository(); + // + // Observation accessKeyEncapsulationService(); + Observation connectionPoolCreate(String id, URI uri, int maxSize); Observation connectionPoolClose(String id, URI uri); diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/NoopObservationProvider.java b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopObservationProvider.java index 6cd4b29716..915fe41e48 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/observation/NoopObservationProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopObservationProvider.java @@ -22,6 +22,7 @@ import org.neo4j.bolt.connection.BoltProtocolVersion; import org.neo4j.driver.AccessMode; import org.neo4j.driver.BaseSession; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; import org.neo4j.driver.types.MapAccessor; public class NoopObservationProvider implements DriverObservationProvider { @@ -103,6 +104,36 @@ public Observation resultRecords(Class resultType) { return NoopObservation.getInstance(); } + @Override + public Observation encryptToBytes(Class propertyEncryptionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation decrypt(Class propertyEncryptionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation createEncapsulatedKey(Class encapsulatedKeyManagerType, String alias) { + return NoopObservation.getInstance(); + } + + @Override + public Observation findEncapsulatedKeyByAlias(Class encapsulatedKeyManagerType, String alias) { + return NoopObservation.getInstance(); + } + + @Override + public Observation updateEncapsulatedKeyAlias(Class encapsulatedKeyManagerType, String id, String alias) { + return NoopObservation.getInstance(); + } + + @Override + public Observation deleteEncapsulatedKey(Class encapsulatedKeyManagerType, String id) { + return NoopObservation.getInstance(); + } + @Override public Observation connectionPoolCreate(String id, URI uri, int maxSize) { return NoopObservation.getInstance(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AEADEncryptedProperty.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AEADEncryptedProperty.java new file mode 100644 index 0000000000..7ebc8d1a11 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AEADEncryptedProperty.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Comparator; +import java.util.TreeMap; +import org.neo4j.bolt.connection.codec.packstream.struct.EncryptedStructure; +import org.neo4j.bolt.connection.codec.value_encoding.ValueEncoder; +import org.neo4j.bolt.connection.codec.value_encoding.ValueEncodingSchemeVersion; +import org.neo4j.bolt.connection.values.Value; +import org.neo4j.driver.Values; + +public record AEADEncryptedProperty( + String profileName, + byte[] cipherOutput, + byte[] iv, + ValueEncoder.Encoded encodedAad, + String keyId, + String typeName, + long typeEncodingSchemeMajor, + long typeEncodingSchemeMinor) { + private static final Comparator UTF8_COMPARATOR = + (a, b) -> Arrays.compareUnsigned(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8)); + private static final String IV = "iv"; + private static final String AAD = "aad"; + private static final String AAD_ENCODING_SCHEME_MAJOR = "aad_encoding_scheme_major"; + private static final String AAD_ENCODING_SCHEME_MINOR = "aad_encoding_scheme_minor"; + private static final String KEY_ID = "key_id"; + + EncryptedStructure toEncryptedStruct() { + var metadata = new TreeMap(UTF8_COMPARATOR); + metadata.put(IV, (Value) Values.value(iv)); + if (encodedAad != null) { + metadata.put(AAD, (Value) Values.value(encodedAad.bytes())); + metadata.put(AAD_ENCODING_SCHEME_MAJOR, (Value) + Values.value(encodedAad.baseVersion().majorVersion())); + metadata.put(AAD_ENCODING_SCHEME_MINOR, (Value) + Values.value(encodedAad.baseVersion().minorVersion())); + } + metadata.put(KEY_ID, (Value) Values.value(keyId)); + return new EncryptedStructure( + profileName, cipherOutput, typeName, typeEncodingSchemeMajor, typeEncodingSchemeMinor, metadata); + } + + static AEADEncryptedProperty fromEncryptedStruct(EncryptedStructure encryptedStruct) { + var metadata = encryptedStruct.metadata(); + var iv = metadata.get(IV).asByteArray(); + var aad = metadata.get(AAD); + ValueEncoder.Encoded encodedAad = null; + if (aad != null) { + var aadBytes = aad.asByteArray(); + var aadSerializationSchemeMajor = + metadata.get(AAD_ENCODING_SCHEME_MAJOR).asLong(); + var aadSerializationSchemeMinor = + metadata.get(AAD_ENCODING_SCHEME_MINOR).asLong(); + encodedAad = new ValueEncoder.Encoded( + aadBytes, new ValueEncodingSchemeVersion((int) aadSerializationSchemeMajor, (int) + aadSerializationSchemeMinor)); + } + var keyId = metadata.get(KEY_ID).asString(); + return new AEADEncryptedProperty( + encryptedStruct.profileName(), + encryptedStruct.cipherOutput(), + iv, + encodedAad, + keyId, + encryptedStruct.typeName(), + encryptedStruct.typeEncodingSchemeMajor(), + encryptedStruct.typeEncodingSchemeMinor()); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AEADEncryption.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AEADEncryption.java new file mode 100644 index 0000000000..47e7a992e7 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AEADEncryption.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import static javax.crypto.Cipher.DECRYPT_MODE; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; +import java.util.Objects; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import org.neo4j.bolt.connection.codec.ReadInputs; +import org.neo4j.bolt.connection.codec.WriteOutputs; +import org.neo4j.bolt.connection.codec.packstream.PackStreamDecoder; +import org.neo4j.bolt.connection.codec.packstream.PackStreamEncoder; +import org.neo4j.bolt.connection.codec.packstream.struct.EncryptedStructure; +import org.neo4j.bolt.connection.codec.value_encoding.ValueDecoder; +import org.neo4j.bolt.connection.codec.value_encoding.ValueEncoder; +import org.neo4j.bolt.connection.exception.BoltClientException; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.value.InternalValue; +import org.neo4j.driver.types.TypeSystem; + +public final class AEADEncryption { + private static final byte ENCODED_BYTES_VERSION = 1; + private static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding"; + private static final String ENCRYPTION_PURPOSE = "neo4j/property-encryption/v1"; + private static final String LIST_TYPE = "LIST"; + private static final Value EMPTY_AAD = Values.NULL; + private final ValueEncoder valueEncoder; + private final ValueDecoder valueDecoder; + private final PackStreamEncoder packStreamEncoder; + private final PackStreamDecoder packStreamDecoder; + + public AEADEncryption( + ValueEncoder valueEncoder, + ValueDecoder valueDecoder, + PackStreamEncoder packStreamEncoder, + PackStreamDecoder packStreamDecoder) { + this.valueEncoder = Objects.requireNonNull(valueEncoder); + this.valueDecoder = Objects.requireNonNull(valueDecoder); + this.packStreamEncoder = Objects.requireNonNull(packStreamEncoder); + this.packStreamDecoder = Objects.requireNonNull(packStreamDecoder); + } + + AEADEncryptedProperty encrypt( + InternalPropertyEncryptionRequest encryptionRequest, + SecretKey key, + String keyId, + String profileName, + Provider provider, + SecureRandom secureRandomIV) + throws NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException { + var plaintextValue = encryptionRequest.value; + var encodedPlaintext = encode(plaintextValue); + var encodedVersion = encodedPlaintext.baseVersion(); + var iv = encryptionRequest.iv != null + ? encryptionRequest.iv // for testing purposes only + : generateIV(secureRandomIV); + var gcmParameterSpec = new GCMParameterSpec(128, iv); + var encodedAad = encryptionRequest.aad != null ? encode(encryptionRequest.aad) : null; + // TODO decide if HKDF should be kept + var encryptionKey = Hkdf.deriveAesKey(key, ENCRYPTION_PURPOSE); + var cipher = prepareCipher( + Cipher.ENCRYPT_MODE, + encryptionKey, + gcmParameterSpec, + encodedAad != null ? encodedAad.bytes() : new byte[0], + provider); + var cipherOutput = cipher.doFinal(encodedPlaintext.bytes()); + + var plaintextValueType = plaintextValue.type().name(); + if (TypeSystem.getDefault().LIST().isTypeOf(plaintextValue)) { + plaintextValueType = LIST_TYPE; + } + + return new AEADEncryptedProperty( + profileName, + cipherOutput, + iv, + encodedAad, + keyId, + plaintextValueType, + encodedVersion.majorVersion(), + encodedVersion.minorVersion()); + } + + Value decrypt(AEADEncryptedProperty encryptedProperty, SecretKey key, Value aad, Provider provider) + throws InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, + InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + var iv = encryptedProperty.iv(); + var aadBytes = aad != null + ? encode(aad).bytes() + : encryptedProperty.encodedAad() != null + ? encryptedProperty.encodedAad().bytes() + : new byte[0]; + + var gcmParameterSpec = new GCMParameterSpec(128, iv); + // TODO decide if HKDF should be kept + var encryptionKey = Hkdf.deriveAesKey(key, ENCRYPTION_PURPOSE); + var cipher = prepareCipher(DECRYPT_MODE, encryptionKey, gcmParameterSpec, aadBytes, provider); + + var plaintext = cipher.doFinal(encryptedProperty.cipherOutput()); + return decode(plaintext); + } + + private ValueEncoder.Encoded encode(Value value) { + try { + return valueEncoder.encode((InternalValue) value); + } catch (IOException e) { + throw new ClientException("Failed to encode value", e); + } + } + + private Value decode(byte[] bytes) { + try { + return (Value) valueDecoder.decode(bytes); + } catch (IOException e) { + throw new ClientException("Failed to decode value", e); + } + } + + public byte[] encodeToEncryptedBytes(AEADEncryptedProperty encryptedProperty) { + var output = WriteOutputs.bytes(); + try { + packStreamEncoder.encode(encryptedProperty.toEncryptedStruct(), output); + } catch (IOException e) { + throw new ClientException("Failed to encode Encrypted Structure", e); + } + var packed = output.output(); + var result = new byte[packed.length + 1]; + + result[0] = ENCODED_BYTES_VERSION; + System.arraycopy(packed, 0, result, 1, packed.length); + return result; + } + + public AEADEncryptedProperty decodeEncryptedBytes(byte[] bytes) { + Objects.requireNonNull(bytes); + var version = bytes[0]; + if (version != ENCODED_BYTES_VERSION) { + throw new BoltClientException("Unsupported encrypted property version: " + version); + } + var input = ReadInputs.bytes(Arrays.copyOfRange(bytes, 1, bytes.length)); + EncryptedStructure encryptedStructure; + try { + encryptedStructure = packStreamDecoder.decodeStructure(input, EncryptedStructure.class); + } catch (Exception e) { + throw new ClientException("Failed to decode Encrypted Structure", e); + } + return AEADEncryptedProperty.fromEncryptedStruct(encryptedStructure); + } + + private byte[] generateIV(SecureRandom secureRandomIV) { + var iv = new byte[12]; + secureRandomIV.nextBytes(iv); + return iv; + } + + private Cipher prepareCipher(int opmode, Key key, AlgorithmParameterSpec params, byte[] aad, Provider provider) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, + InvalidKeyException { + var cipher = cipher(provider); + cipher.init(opmode, key, params); + cipher.updateAAD(aad); + return cipher; + } + + private Cipher cipher(Provider provider) throws NoSuchPaddingException, NoSuchAlgorithmException { + return provider == null + ? Cipher.getInstance(CIPHER_TRANSFORMATION) + : Cipher.getInstance(CIPHER_TRANSFORMATION, provider); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractEncapsulatedKeyManager.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractEncapsulatedKeyManager.java new file mode 100644 index 0000000000..d88b5d84ae --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractEncapsulatedKeyManager.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import javax.crypto.SecretKey; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecordRepository; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; + +public abstract class AbstractEncapsulatedKeyManager { + private final KeyEncapsulationService keyEncapsulationService; + private final EncapsulatedKeyRecordRepository keyRepository; + private final Cache aliasToIdCache; + private final Cache idToKeyCache; + + public AbstractEncapsulatedKeyManager( + KeyEncapsulationService keyEncapsulationService, + EncapsulatedKeyRecordRepository keyRepository, + Cache aliasToIdCache, + Cache idToKeyCache) { + this.keyEncapsulationService = Objects.requireNonNull(keyEncapsulationService); + this.keyRepository = Objects.requireNonNull(keyRepository); + this.aliasToIdCache = Objects.requireNonNull(aliasToIdCache); + this.idToKeyCache = Objects.requireNonNull(idToKeyCache); + } + + public CompletionStage createAsync(String alias, KeyEncapsulationOptions encapsulationOptions) { + return keyEncapsulationService + .encapsulate(encapsulationOptions) + .thenCompose(encapsulationResult -> keyRepository + .save(alias, encapsulationResult.encapsulation(), encapsulationResult.metadata()) + .thenApply(encapsulatedKeyRecord -> { + if (alias != null) { + aliasToIdCache.put(alias, encapsulatedKeyRecord.id()); + } + idToKeyCache.put(encapsulatedKeyRecord.id(), encapsulationResult.key()); + return encapsulatedKeyRecord; + })) + .thenApply(encapsulatedKey -> new EncapsulatedKeyRecord( + encapsulatedKey.id(), encapsulatedKey.alias().orElse(null))); + } + + public CompletionStage findByAliasAsync(String alias) { + if (alias == null) { + return CompletableFuture.failedStage(new NullPointerException("alias must not be null")); + } + return keyRepository + .findByAlias(alias) + .thenApply(encapsulatedKey -> new EncapsulatedKeyRecord( + encapsulatedKey.id(), encapsulatedKey.alias().orElse(null))); + } + + public CompletionStage updateAliasByIdAsync(String id, String alias) { + Objects.requireNonNull(id); + return keyRepository.updateAliasById(id, alias).thenApply(ignored -> { + if (alias != null) { + aliasToIdCache.put(alias, id); + } else { + aliasToIdCache.delete(alias); + } + // TODO delete old alias + return null; + }); + } + + public CompletionStage deleteByIdAsync(String id) { + return keyRepository.deleteById(id).thenApply(key -> { + idToKeyCache.delete(id); + return key; + }); + } + + private record EncapsulatedKeyRecord(String id, String aliasRef) implements EncapsulatedKey { + @Override + public Optional alias() { + return Optional.ofNullable(aliasRef); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractFactoryLoader.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractFactoryLoader.java new file mode 100644 index 0000000000..7b8d1a1338 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractFactoryLoader.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.util.Objects; +import java.util.Optional; +import java.util.ServiceLoader; +import org.neo4j.driver.Logger; + +abstract class AbstractFactoryLoader { + @SuppressWarnings("deprecation") + private final Logger logger; + + private final T providerFactory; + private final String defaultFactoryName; + private final Class factoryType; + + AbstractFactoryLoader( + @SuppressWarnings("deprecation") Logger logger, String defaultFactoryName, Class factoryType) { + this.logger = Objects.requireNonNull(logger); + this.defaultFactoryName = Objects.requireNonNull(defaultFactoryName); + this.factoryType = Objects.requireNonNull(factoryType); + this.providerFactory = findFactory().orElseThrow(() -> new IllegalStateException("No factory found")); + } + + public T factory() { + return providerFactory; + } + + Optional findFactory() { + var result = Optional.empty(); + try { + var serviceLoader = ServiceLoader.load(factoryType, this.getClass().getClassLoader()); + result = serviceLoader.stream().map(ServiceLoader.Provider::get).findFirst(); + } catch (Exception e) { + logger.warn("Loading of %s service has failed".formatted(factoryType.getCanonicalName()), e); + } + if (result.isEmpty()) { + try { + // an extra attempt in case the factory is visible + @SuppressWarnings("Java9ReflectionClassVisibility") + var factoryCls = Class.forName(defaultFactoryName); + if (factoryType.isAssignableFrom(factoryCls)) { + @SuppressWarnings("unchecked") + var factory = (T) factoryCls.getConstructor().newInstance(); + result = Optional.of(factory); + } + } catch (Exception e) { + logger.error("Failed to load default '%s' factory".formatted(defaultFactoryName), e); + } + } + result.ifPresentOrElse( + factory -> logger.trace("Selected '%s' factory", factory.getClass()), + () -> logger.warn("No factory has been found")); + return result; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractPropertyEncryption.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractPropertyEncryption.java new file mode 100644 index 0000000000..0cccbacda1 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractPropertyEncryption.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.neo4j.driver.Value; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; + +public abstract class AbstractPropertyEncryption implements BasePropertyEncryption { + protected final Map nameToHandler; + private final AEADEncryption aeadEncryption; + + protected AbstractPropertyEncryption( + Map nameToHandler, AEADEncryption aeadEncryption) { + this.nameToHandler = Objects.requireNonNull(nameToHandler); + this.aeadEncryption = Objects.requireNonNull(aeadEncryption); + } + + protected PropertyEncryptionHandler getHandler(String profileName) { + if (profileName != null) { + var handler = nameToHandler.get(profileName); + if (handler == null) { + throw new ClientException("No handler was found for profile name " + profileName); + } + return handler; + } else { + if (nameToHandler.size() == 1) { + return nameToHandler.values().iterator().next(); + } else { + throw new ClientException("Explicit profile name is required as multiple profiles are registered"); + } + } + } + + public CompletionStage encryptToBytesAsync(PropertyEncryptionRequest encryptRequest) { + Objects.requireNonNull(encryptRequest); + var request = (InternalPropertyEncryptionRequest) encryptRequest; + return getHandler(request.profileName).encrypt(request); + } + + public CompletionStage decryptAsync(PropertyDecryptionRequest decryptRequest) { + Objects.requireNonNull(decryptRequest); + var request = (InternalPropertyDecryptionRequest) decryptRequest; + AEADEncryptedProperty encryptedProperty; + try { + encryptedProperty = aeadEncryption.decodeEncryptedBytes(request.value); + } catch (Exception e) { + return CompletableFuture.failedStage(e); + } + return getHandler(encryptedProperty.profileName()).decrypt(request, encryptedProperty); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractPropertyRequest.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractPropertyRequest.java new file mode 100644 index 0000000000..376f310a58 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/AbstractPropertyRequest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.util.Objects; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.types.Type; +import org.neo4j.driver.types.TypeSystem; + +abstract class AbstractPropertyRequest { + protected static final TypeSystem TYPE_SYSTEM = TypeSystem.getDefault(); + + protected void validate(Value value) { + Objects.requireNonNull(value); + + if (isScalarPropertyValue(value)) { + return; + } + + if (TYPE_SYSTEM.LIST().isTypeOf(value)) { + validateList(value); + return; + } + + throw new IllegalArgumentException( + "Value of type " + value.type().name() + " cannot be stored as a Neo4j property"); + } + + private void validateList(Value value) { + var values = value.asList(Values::value); + + if (values.isEmpty()) { + return; + } + + Type elementType = null; + + for (var element : values) { + if (element == null || !isScalarPropertyValue(element)) { + throw new IllegalArgumentException("only scalar Neo4j property values are allowed in lists"); + } + if (TYPE_SYSTEM.NULL().isTypeOf(element)) { + throw new IllegalArgumentException("NULL values are not allowed in lists"); + } + if (TYPE_SYSTEM.VECTOR().isTypeOf(element)) { + throw new IllegalArgumentException("vector values are not allowed in lists"); + } + + var currentType = element.type(); + if (elementType == null) { + elementType = currentType; + } else if (!elementType.equals(currentType)) { + throw new IllegalArgumentException("Neo4j property lists must be homogeneous. " + + "Found both " + + elementType.name() + + " and " + + currentType.name()); + } + } + } + + protected void validateAad(Value value) { + Objects.requireNonNull(value); + + if (isScalarPropertyValue(value)) { + return; + } + + if (TYPE_SYSTEM.LIST().isTypeOf(value)) { + validateList(value); + return; + } + + throw new IllegalArgumentException( + "Value of type " + value.type().name() + " cannot be stored as a Neo4j property"); + } + + private boolean isScalarPropertyValue(Value value) { + return TYPE_SYSTEM.BOOLEAN().isTypeOf(value) + || TYPE_SYSTEM.STRING().isTypeOf(value) + || TYPE_SYSTEM.INTEGER().isTypeOf(value) + || TYPE_SYSTEM.FLOAT().isTypeOf(value) + || TYPE_SYSTEM.DATE().isTypeOf(value) + || TYPE_SYSTEM.TIME().isTypeOf(value) + || TYPE_SYSTEM.LOCAL_TIME().isTypeOf(value) + || TYPE_SYSTEM.LOCAL_DATE_TIME().isTypeOf(value) + || TYPE_SYSTEM.DATE_TIME().isTypeOf(value) + || TYPE_SYSTEM.DURATION().isTypeOf(value) + || TYPE_SYSTEM.POINT().isTypeOf(value) + || TYPE_SYSTEM.BYTES().isTypeOf(value) + || TYPE_SYSTEM.VECTOR().isTypeOf(value) + || TYPE_SYSTEM.NULL().isTypeOf(value); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/Cache.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/Cache.java new file mode 100644 index 0000000000..43fe47de0b --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/Cache.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.util.Optional; + +public interface Cache { + Optional get(String key); + + void put(String key, T value); + + void delete(String key); +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/CacheConfigRecord.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/CacheConfigRecord.java new file mode 100644 index 0000000000..cb33fc0e00 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/CacheConfigRecord.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.time.Duration; +import java.util.Objects; +import org.neo4j.driver.property_encryption.CacheConfig; + +record CacheConfigRecord(int maxSize, Duration ttl) implements CacheConfig { + CacheConfigRecord { + if (maxSize <= 0) { + throw new IllegalArgumentException("size must be greater than zero"); + } + Objects.requireNonNull(ttl); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/CryptoContextRecord.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/CryptoContextRecord.java new file mode 100644 index 0000000000..c141a95e1f --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/CryptoContextRecord.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.security.Provider; +import java.security.SecureRandom; +import java.util.Objects; +import org.neo4j.driver.property_encryption.CryptoContext; + +record CryptoContextRecord(Provider provider, SecureRandom ivSecureRandom) implements CryptoContext { + CryptoContextRecord { + Objects.requireNonNull(provider); + Objects.requireNonNull(ivSecureRandom); + + if (ivSecureRandom.getProvider() != provider) { + throw new IllegalArgumentException("SecureRandom must use the supplied provider"); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/EncryptionKeyFactory.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/EncryptionKeyFactory.java new file mode 100644 index 0000000000..8dc54e5636 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/EncryptionKeyFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.security.NoSuchAlgorithmException; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +public final class EncryptionKeyFactory { + private final KeyGenerator keyGenerator; + + public EncryptionKeyFactory() throws NoSuchAlgorithmException { + this.keyGenerator = KeyGenerator.getInstance("AES"); + } + + public SecretKey createAES256() { + return keyGenerator.generateKey(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/EnvelopePropertyEncryptionHandler.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/EnvelopePropertyEncryptionHandler.java new file mode 100644 index 0000000000..50ffc5eca1 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/EnvelopePropertyEncryptionHandler.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.time.Clock; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import javax.crypto.SecretKey; +import org.neo4j.driver.Value; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.property_encryption.CryptoContext; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecord; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecordRepository; +import org.neo4j.driver.property_encryption.EnvelopePropertyEncryptionProfile; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; + +public class EnvelopePropertyEncryptionHandler implements PropertyEncryptionHandler { + protected final String profileName; + private final EncapsulatedKeyRecordRepository keyRepository; + protected final KeyEncapsulationService keyEncapsulationService; + private final Provider provider; + private final SecureRandom secureRandomIV; + private final AEADEncryption aeadEncryption; + private final Cache aliasToIdCache; + private final Cache idToKeyCache; + + public EnvelopePropertyEncryptionHandler( + EnvelopePropertyEncryptionProfile profile, AEADEncryption aeadEncryption, Clock clock) { + Objects.requireNonNull(profile); + Objects.requireNonNull(clock); + this.profileName = Objects.requireNonNull(profile.name()); + this.keyEncapsulationService = Objects.requireNonNull(profile.keyEncapsulationService()); + this.keyRepository = Objects.requireNonNull(profile.keyRepository()); + this.provider = profile.cryptoContext().map(CryptoContext::provider).orElse(null); + this.secureRandomIV = + profile.cryptoContext().map(CryptoContext::ivSecureRandom).orElse(null); + this.aliasToIdCache = profile.keyAliasCacheConfig() + .map(config -> (Cache) new InMemoryCache(config.maxSize(), config.ttl(), clock)) + .orElseGet(NoopCache::new); + this.idToKeyCache = profile.keyCacheConfig() + .map(config -> (Cache) new InMemoryCache(config.maxSize(), config.ttl(), clock)) + .orElseGet(NoopCache::new); + this.aeadEncryption = Objects.requireNonNull(aeadEncryption); + } + + @Override + public String profileName() { + return profileName; + } + + @Override + public CompletionStage encrypt(InternalPropertyEncryptionRequest request) { + return findAndDecapsulateKey(request).thenApply(keyData -> { + try { + var encryptedProperty = aeadEncryption.encrypt( + request, keyData.key(), keyData.keyId(), profileName, provider, secureRandomIV()); + return aeadEncryption.encodeToEncryptedBytes(encryptedProperty); + } catch (Exception e) { + throw new ClientException("Error encrypting data: " + e.getMessage(), e); + } + }); + } + + @Override + public CompletionStage decrypt( + InternalPropertyDecryptionRequest decryptionRequest, AEADEncryptedProperty encryptedProperty) { + return getKey(encryptedProperty.keyId()).thenApply(dek -> { + try { + return aeadEncryption.decrypt(encryptedProperty, dek, decryptionRequest.aad, provider); + } catch (Exception e) { + throw new ClientException("Error decrypting data: " + e.getMessage(), e); + } + }); + } + + @Override + public KeyEncapsulationService keyEncapsulationService() { + return keyEncapsulationService; + } + + @Override + public EncapsulatedKeyRecordRepository keyRepository() { + return keyRepository; + } + + public Cache aliasToIdCache() { + return aliasToIdCache; + } + + public Cache idToKeyCache() { + return idToKeyCache; + } + + private CompletionStage findAndDecapsulateKey(InternalPropertyEncryptionRequest request) { + CompletionStage keyRecordStage; + try { + if (request.encryptionKeyId != null) { + var id = request.encryptionKeyId; + var cachedKey = idToKeyCache.get(id); + if (cachedKey.isPresent()) { + return CompletableFuture.completedStage(new KeyData(id, cachedKey.get())); + } else { + keyRecordStage = keyRepository.findById(id).thenApply(encapsulatedKeyRecord -> { + if (encapsulatedKeyRecord == null) { + throw new ClientException("No key found for id %s".formatted(id)); + } + return encapsulatedKeyRecord; + }); + } + } else { + if (request.encryptionKeyAlias != null) { + var alias = request.encryptionKeyAlias; + var cachedId = aliasToIdCache.get(alias); + var cachedKey = cachedId.flatMap(idToKeyCache::get); + if (cachedKey.isPresent()) { + return CompletableFuture.completedStage(new KeyData(cachedId.get(), cachedKey.get())); + } else { + keyRecordStage = keyRepository + .findByAlias(request.encryptionKeyAlias) + .thenApply(encapsulatedKeyRecord -> { + if (encapsulatedKeyRecord == null) { + throw new ClientException( + "No key found for alias %s".formatted(request.encryptionKeyAlias)); + } + aliasToIdCache.put(alias, encapsulatedKeyRecord.id()); + return encapsulatedKeyRecord; + }); + } + } else { + keyRecordStage = CompletableFuture.failedStage( + new ClientException("The encryption request does not have neither key id nor key alias")); + } + } + } catch (Exception e) { + keyRecordStage = CompletableFuture.failedStage(new ClientException("Key repository lookup has failed", e)); + } + + return keyRecordStage.thenCompose(encapsulatedKey -> { + try { + return keyEncapsulationService + .decapsulate(encapsulatedKey.encapsulation(), encapsulatedKey.metadata()) + .thenApply(key -> { + idToKeyCache.put(encapsulatedKey.id(), key); + return new KeyData(encapsulatedKey.id(), key); + }); + } catch (Exception e) { + throw new ClientException("Failed to decapsulate key", e); + } + }); + } + + private CompletionStage getKey(String keyId) { + var cachedKey = idToKeyCache.get(keyId); + if (cachedKey.isPresent()) { + return CompletableFuture.completedStage(cachedKey.get()); + } else { + var keyRecordStage = keyRepository.findById(keyId).thenApply(encapsulatedKeyRecord -> { + if (encapsulatedKeyRecord == null) { + throw new ClientException("No key found for id %s".formatted(keyId)); + } + return encapsulatedKeyRecord; + }); + return keyRecordStage.thenCompose(encapsulatedKey -> { + try { + return keyEncapsulationService.decapsulate( + encapsulatedKey.encapsulation(), encapsulatedKey.metadata()); + } catch (Exception e) { + throw new ClientException("Failed to decapsulate key", e); + } + }); + } + } + + private SecureRandom secureRandomIV() throws NoSuchAlgorithmException { + return secureRandomIV == null ? SecureRandom.getInstanceStrong() : secureRandomIV; + } + + private record KeyData(String keyId, SecretKey key) {} +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/Hkdf.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/Hkdf.java new file mode 100644 index 0000000000..3ac74e7a70 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/Hkdf.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +final class Hkdf { + private static final String HMAC_ALG = "HmacSHA256"; + private static final int HASH_LEN = 32; + + private Hkdf() {} + + public static byte[] derive(byte[] ikm, byte[] salt, byte[] info, int length) + throws NoSuchAlgorithmException, InvalidKeyException { + // ---- Extract ---- + + if (salt == null || salt.length == 0) { + salt = new byte[HASH_LEN]; + } + + var mac = Mac.getInstance(HMAC_ALG); + mac.init(new SecretKeySpec(salt, HMAC_ALG)); + + var prk = mac.doFinal(ikm); + + // ---- Expand ---- + + var okm = new byte[length]; + + var previous = new byte[0]; + var offset = 0; + byte counter = 1; + + while (offset < length) { + + mac.init(new SecretKeySpec(prk, HMAC_ALG)); + + mac.update(previous); + mac.update(info); + mac.update(counter); + + previous = mac.doFinal(); + + var remaining = length - offset; + var chunk = Math.min(remaining, previous.length); + + System.arraycopy(previous, 0, okm, offset, chunk); + + offset += chunk; + counter++; + } + + return okm; + } + + public static SecretKey deriveAesKey(SecretKey masterKey, String purpose) + throws NoSuchAlgorithmException, InvalidKeyException { + + var derived = derive(masterKey.getEncoded(), null, purpose.getBytes(StandardCharsets.UTF_8), 32); + + return new SecretKeySpec(derived, "AES"); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InMemoryCache.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InMemoryCache.java new file mode 100644 index 0000000000..3761d4dd1a --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InMemoryCache.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.time.Clock; +import java.time.Duration; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +final class InMemoryCache implements Cache { + private final Map> keyToEntry = new HashMap<>(); + private final int sizeLimit; + private final Duration ttl; + private final Clock clock; + + public InMemoryCache(int sizeLimit, Duration ttl, Clock clock) { + if (sizeLimit <= 0) { + throw new IllegalArgumentException("sizeLimit must be greater than zero"); + } + Objects.requireNonNull(ttl); + if (ttl.isNegative() || ttl.isZero()) { + throw new IllegalArgumentException("ttl must be greater than zero"); + } + + this.sizeLimit = sizeLimit; + this.ttl = ttl; + this.clock = Objects.requireNonNull(clock); + } + + @Override + public synchronized Optional get(String key) { + var entry = keyToEntry.get(key); + if (entry == null) { + return Optional.empty(); + } + + if (entry.isExpired(clock.millis())) { + keyToEntry.remove(key); + return Optional.empty(); + } + + return Optional.of(entry.value()); + } + + @Override + public synchronized void put(String key, T value) { + var now = clock.millis(); + + pruneExpired(now); + + if (!keyToEntry.containsKey(key) && keyToEntry.size() >= sizeLimit) { + evictOldest(); + } + + keyToEntry.put(key, new Entry<>(value, now + ttl.toMillis())); + } + + @Override + public synchronized void delete(String key) { + keyToEntry.remove(key); + } + + private void pruneExpired(long now) { + keyToEntry.entrySet().removeIf(entry -> entry.getValue().isExpired(now)); + } + + private void evictOldest() { + keyToEntry.entrySet().stream() + .min(Comparator.comparingLong(entry -> entry.getValue().expiresAt())) + .ifPresent(entry -> keyToEntry.remove(entry.getKey())); + } + + private record Entry(T value, long expiresAt) { + boolean isExpired(long now) { + return now >= expiresAt; + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalEncapsulatedKeyManager.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalEncapsulatedKeyManager.java new file mode 100644 index 0000000000..216d8c096d --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalEncapsulatedKeyManager.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observe; + +import java.util.Objects; +import java.util.Optional; +import javax.crypto.SecretKey; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.EncapsulatedKeyManager; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecordRepository; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; + +final class InternalEncapsulatedKeyManager extends AbstractEncapsulatedKeyManager implements EncapsulatedKeyManager { + private final DriverObservationProvider observationProvider; + + InternalEncapsulatedKeyManager( + KeyEncapsulationService keyEncapsulationService, + EncapsulatedKeyRecordRepository keyRepository, + Cache aliasToIdCache, + Cache idToKeyCache, + DriverObservationProvider observationProvider) { + super(keyEncapsulationService, keyRepository, aliasToIdCache, idToKeyCache); + this.observationProvider = Objects.requireNonNull(observationProvider); + } + + @Override + public EncapsulatedKey create(String alias, KeyEncapsulationOptions encapsulationOptions) { + var createObservation = observationProvider.createEncapsulatedKey(EncapsulatedKeyManager.class, alias); + return observe(createObservation, () -> createAsync(alias, encapsulationOptions) + .toCompletableFuture() + .join()); + } + + @Override + public Optional findByAlias(String alias) { + var findObservation = observationProvider.findEncapsulatedKeyByAlias(EncapsulatedKeyManager.class, alias); + return observe( + findObservation, + () -> Optional.ofNullable( + findByAliasAsync(alias).toCompletableFuture().join())); + } + + @Override + public void updateAliasById(String id, String alias) { + var updateObservation = observationProvider.updateEncapsulatedKeyAlias(EncapsulatedKeyManager.class, id, alias); + observe( + updateObservation, + () -> updateAliasByIdAsync(id, alias).toCompletableFuture().join()); + } + + @Override + public void deleteById(String id) { + var deleteObservation = observationProvider.deleteEncapsulatedKey(EncapsulatedKeyManager.class, id); + observe( + deleteObservation, + () -> deleteByIdAsync(id).toCompletableFuture().join()); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalEnvelopePropertyEncryptionProfile.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalEnvelopePropertyEncryptionProfile.java new file mode 100644 index 0000000000..1df6cd2fbf --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalEnvelopePropertyEncryptionProfile.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.security.Provider; +import java.security.SecureRandom; +import java.time.Duration; +import java.util.Objects; +import java.util.Optional; +import org.neo4j.driver.property_encryption.CacheConfig; +import org.neo4j.driver.property_encryption.CryptoContext; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecordRepository; +import org.neo4j.driver.property_encryption.EnvelopePropertyEncryptionProfile; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; + +public record InternalEnvelopePropertyEncryptionProfile( + String name, + KeyEncapsulationService keyEncapsulationService, + EncapsulatedKeyRecordRepository keyRepository, + CryptoContext cryptoContextRef, + CacheConfig keyCacheConfigRef, + CacheConfig keyAliasCacheConfigRef) + implements EnvelopePropertyEncryptionProfile { + public InternalEnvelopePropertyEncryptionProfile { + Objects.requireNonNull(name); + if (name.isEmpty()) { + throw new IllegalArgumentException("name must not be empty"); + } + Objects.requireNonNull(keyEncapsulationService); + Objects.requireNonNull(keyRepository); + } + + @Override + public Optional cryptoContext() { + return Optional.ofNullable(cryptoContextRef); + } + + @Override + public Optional keyCacheConfig() { + return Optional.ofNullable(keyCacheConfigRef); + } + + @Override + public Optional keyAliasCacheConfig() { + return Optional.ofNullable(keyAliasCacheConfigRef); + } + + public static class Builder implements EnvelopePropertyEncryptionProfile.Builder { + final String name; + final KeyEncapsulationService keyEncapsulationService; + final EncapsulatedKeyRecordRepository keyRepository; + CryptoContext cryptoContextRef; + CacheConfig keyCacheConfigRef = new CacheConfigRecord(100, Duration.ofMinutes(15)); + CacheConfig keyAliasCacheConfigRef = new CacheConfigRecord(100, Duration.ofSeconds(15)); + + public Builder( + String name, + KeyEncapsulationService keyEncapsulationService, + EncapsulatedKeyRecordRepository keyRepository) { + this.name = Objects.requireNonNull(name); + if (name.isEmpty()) { + throw new IllegalArgumentException("name must not be empty"); + } + this.keyEncapsulationService = Objects.requireNonNull(keyEncapsulationService); + this.keyRepository = Objects.requireNonNull(keyRepository); + } + + @Override + public Builder withCryptoContext(Provider provider, SecureRandom ivSecureRandom) { + this.cryptoContextRef = new CryptoContextRecord(provider, ivSecureRandom); + return this; + } + + @Override + public Builder withKeyCache(int maxSize, Duration ttl) { + this.keyCacheConfigRef = new CacheConfigRecord(maxSize, ttl); + return this; + } + + @Override + public Builder withoutKeyCache() { + this.keyCacheConfigRef = null; + return this; + } + + @Override + public Builder withKeyAliasCache(int maxSize, Duration ttl) { + this.keyAliasCacheConfigRef = new CacheConfigRecord(maxSize, ttl); + return this; + } + + @Override + public Builder withoutKeyAliasCache() { + this.keyAliasCacheConfigRef = null; + return this; + } + + @Override + public EnvelopePropertyEncryptionProfile build() { + return new InternalEnvelopePropertyEncryptionProfile( + name, + keyEncapsulationService, + keyRepository, + cryptoContextRef, + keyCacheConfigRef, + keyAliasCacheConfigRef); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalKeyEncapsulationResult.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalKeyEncapsulationResult.java new file mode 100644 index 0000000000..7046535e8e --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalKeyEncapsulationResult.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.util.Map; +import java.util.Objects; +import javax.crypto.SecretKey; +import org.neo4j.driver.property_encryption.KeyEncapsulationResult; + +public record InternalKeyEncapsulationResult(byte[] encapsulation, Map metadata, SecretKey key) + implements KeyEncapsulationResult { + public InternalKeyEncapsulationResult { + Objects.requireNonNull(encapsulation); + Objects.requireNonNull(metadata); + Objects.requireNonNull(key); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalPropertyDecryptionRequest.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalPropertyDecryptionRequest.java new file mode 100644 index 0000000000..d24ecad0d3 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalPropertyDecryptionRequest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.util.Objects; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; + +public final class InternalPropertyDecryptionRequest extends AbstractPropertyRequest + implements PropertyDecryptionRequest.ValueStep, + PropertyDecryptionRequest.AADStep, + PropertyDecryptionRequest.BuildStep, + PropertyDecryptionRequest { + byte[] value; + String encryptionKeyId; + String encryptionKeyAlias; + KeyEncapsulationOptions encryptionKeyEncapsulationOptions; + Value aad; + + @Override + public AADStep fromValue(byte[] value) { + this.value = Objects.requireNonNull(value); + return this; + } + + @Override + public BuildStep withAAD(Value aad) { + validate(aad); + this.aad = aad; + return this; + } + + @Override + public BuildStep withPersistedAAD() { + this.aad = null; + return this; + } + + @Override + public PropertyDecryptionRequest build() { + return this; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalPropertyEncryption.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalPropertyEncryption.java new file mode 100644 index 0000000000..44e6622bfb --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalPropertyEncryption.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observe; + +import java.util.Map; +import java.util.Objects; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.property_encryption.EncapsulatedKeyManager; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; + +public final class InternalPropertyEncryption extends AbstractPropertyEncryption + implements PropertyEncryption { + private final DriverObservationProvider observationProvider; + + public InternalPropertyEncryption( + Map nameToHandler, + AEADEncryption aeadEncryption, + DriverObservationProvider observationProvider) { + super(nameToHandler, aeadEncryption); + this.observationProvider = Objects.requireNonNull(observationProvider); + } + + @Override + public byte[] encryptToBytes(PropertyEncryptionRequest encryptRequest) { + var encryptObservation = observationProvider.encryptToBytes(PropertyEncryption.class); + return observe( + encryptObservation, + () -> encryptToBytesAsync(encryptRequest).toCompletableFuture().join()); + } + + @Override + public Value decrypt(PropertyDecryptionRequest decryptRequest) { + var decryptObservation = observationProvider.decrypt(PropertyEncryption.class); + return observe( + decryptObservation, + () -> decryptAsync(decryptRequest).toCompletableFuture().join()); + } + + @Override + public EncapsulatedKeyManager keyManager(String profileName) { + var handler = getHandler(profileName); + var keyRepository = handler.keyRepository(); + if (keyRepository == null) { + throw new IllegalStateException("Key Manager is not supported in this profile"); + } + var keyEncapsulationService = handler.keyEncapsulationService(); + return new InternalEncapsulatedKeyManager( + keyEncapsulationService, + keyRepository, + handler.aliasToIdCache(), + handler.idToKeyCache(), + observationProvider); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalPropertyEncryptionRequest.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalPropertyEncryptionRequest.java new file mode 100644 index 0000000000..2a3f505715 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/InternalPropertyEncryptionRequest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.util.Objects; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; + +public final class InternalPropertyEncryptionRequest extends AbstractPropertyRequest + implements PropertyEncryptionRequest.ProfileStep, + PropertyEncryptionRequest.ValueStep, + PropertyEncryptionRequest.EncryptionKeyReferenceStep, + PropertyEncryptionRequest.AADStep, + PropertyEncryptionRequest.BuildStep, + PropertyEncryptionRequest { + Value value; + String encryptionKeyId; + String encryptionKeyAlias; + Value aad; + String profileName; + byte[] iv; // for testing purposes only + + @Override + public EncryptionKeyReferenceStep usingProfile(String profileName) { + this.profileName = Objects.requireNonNull(profileName); + return this; + } + + @Override + public AADStep fromValue(Value value) { + validate(value); + this.value = Objects.requireNonNull(value); + return this; + } + + @Override + public BuildStep usingKeyId(String keyId) { + this.encryptionKeyId = Objects.requireNonNull(keyId); + return this; + } + + @Override + public BuildStep usingKeyAlias(String keyAlias) { + this.encryptionKeyAlias = Objects.requireNonNull(keyAlias); + return this; + } + + @Override + public AADStep withAAD(Value aad) { + if (value == null || TYPE_SYSTEM.NULL().isTypeOf(value)) { + // Both null and NULL value disable AAD + return this; + } + validateAad(aad); + this.aad = aad; + return this; + } + + @Override + public PropertyEncryptionRequest build() { + return this; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/LocalKeyEncapsulationService.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/LocalKeyEncapsulationService.java new file mode 100644 index 0000000000..c1ebe711ad --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/LocalKeyEncapsulationService.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import static javax.crypto.Cipher.DECRYPT_MODE; + +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.KeyEncapsulationResult; +import org.neo4j.driver.property_encryption.KeyEncapsulationResults; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; + +public final class LocalKeyEncapsulationService implements KeyEncapsulationService { + private static final String KEY_ALGORITHM = "AES"; + private static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding"; + private final SecretKey masterKey; + private final KeyGenerator keyGenerator; + private final Provider provider; + private final SecureRandom secureRandomIV; + + public LocalKeyEncapsulationService(SecretKey masterKey, Provider provider, SecureRandom secureRandomIV) + throws NoSuchAlgorithmException { + this.masterKey = Objects.requireNonNull(masterKey); + this.provider = provider; + this.keyGenerator = keyGenerator(provider); + this.keyGenerator.init(256); + this.secureRandomIV = secureRandomIV == null ? SecureRandom.getInstanceStrong() : secureRandomIV; + } + + @Override + public CompletionStage encapsulate(KeyEncapsulationOptions options) { + var dek = keyGenerator.generateKey(); + try { + var cipher = cipher(provider); + var iv = generateIV(); + var gcmSpec = new GCMParameterSpec(128, iv); + + cipher.init(Cipher.ENCRYPT_MODE, masterKey, gcmSpec); + var encryptedDek = cipher.doFinal(dek.getEncoded()); + + return CompletableFuture.completedStage(KeyEncapsulationResults.create( + encryptedDek, Map.of("iv", Base64.getEncoder().encodeToString(iv)), dek)); + } catch (Exception e) { + throw new ClientException("Failed to encapsulate key", e); + } + } + + @Override + public CompletionStage decapsulate(byte[] encapsulation, Map metadata) { + var iv = Base64.getDecoder().decode(metadata.get("iv")); + try { + var cipher = cipher(provider); + cipher.init(DECRYPT_MODE, masterKey, new GCMParameterSpec(128, iv)); + + var keyBytes = cipher.doFinal(encapsulation); + return CompletableFuture.completedStage(new SecretKeySpec(keyBytes, KEY_ALGORITHM)); + } catch (Exception e) { + throw new ClientException("Failed to decrypt data", e); + } + } + + private static KeyGenerator keyGenerator(Provider provider) throws NoSuchAlgorithmException { + return provider == null + ? KeyGenerator.getInstance(KEY_ALGORITHM) + : KeyGenerator.getInstance(KEY_ALGORITHM, provider); + } + + private byte[] generateIV() { + var iv = new byte[12]; + secureRandomIV.nextBytes(iv); + return iv; + } + + private Cipher cipher(Provider provider) throws NoSuchPaddingException, NoSuchAlgorithmException { + return provider == null + ? Cipher.getInstance(CIPHER_TRANSFORMATION) + : Cipher.getInstance(CIPHER_TRANSFORMATION, provider); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/NoopCache.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/NoopCache.java new file mode 100644 index 0000000000..a0d5bed663 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/NoopCache.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.util.Optional; + +final class NoopCache implements Cache { + @Override + public Optional get(String key) { + return Optional.empty(); + } + + @Override + public void put(String key, T value) {} + + @Override + public void delete(String key) {} +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/PackStreamDecoderFactoryLoader.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/PackStreamDecoderFactoryLoader.java new file mode 100644 index 0000000000..045dc930ea --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/PackStreamDecoderFactoryLoader.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import org.neo4j.bolt.connection.codec.packstream.PackStreamDecoderFactory; +import org.neo4j.driver.Logging; + +public final class PackStreamDecoderFactoryLoader extends AbstractFactoryLoader { + public PackStreamDecoderFactoryLoader(@SuppressWarnings("deprecation") Logging logging) { + super( + logging.getLog(PackStreamDecoderFactoryLoader.class), + "org.neo4j.bolt.connection.codec.impl.packstream.PackStreamDecoderFactoryImpl", + PackStreamDecoderFactory.class); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/PackStreamEncoderFactoryLoader.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/PackStreamEncoderFactoryLoader.java new file mode 100644 index 0000000000..a939712369 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/PackStreamEncoderFactoryLoader.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import org.neo4j.bolt.connection.codec.packstream.PackStreamEncoderFactory; +import org.neo4j.driver.Logging; + +public final class PackStreamEncoderFactoryLoader extends AbstractFactoryLoader { + public PackStreamEncoderFactoryLoader(@SuppressWarnings("deprecation") Logging logging) { + super( + logging.getLog(PackStreamDecoderFactoryLoader.class), + "org.neo4j.bolt.connection.codec.impl.packstream.PackStreamEncoderFactoryImpl", + PackStreamEncoderFactory.class); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/PropertyEncryptionHandler.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/PropertyEncryptionHandler.java new file mode 100644 index 0000000000..f5005fb7ac --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/PropertyEncryptionHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import java.util.concurrent.CompletionStage; +import javax.crypto.SecretKey; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecordRepository; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; + +public interface PropertyEncryptionHandler { + String profileName(); + + CompletionStage encrypt(InternalPropertyEncryptionRequest encryptionRequest); + + CompletionStage decrypt( + InternalPropertyDecryptionRequest decryptionRequest, AEADEncryptedProperty aadEncryptedProperty); + + KeyEncapsulationService keyEncapsulationService(); + + EncapsulatedKeyRecordRepository keyRepository(); + + Cache aliasToIdCache(); + + Cache idToKeyCache(); +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/ValueDecoderFactoryLoader.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/ValueDecoderFactoryLoader.java new file mode 100644 index 0000000000..cb2106c7ad --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/ValueDecoderFactoryLoader.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import org.neo4j.bolt.connection.codec.value_encoding.ValueDecoderFactory; +import org.neo4j.driver.Logging; + +public final class ValueDecoderFactoryLoader extends AbstractFactoryLoader { + public ValueDecoderFactoryLoader(@SuppressWarnings("deprecation") Logging logging) { + super( + logging.getLog(ValueDecoderFactoryLoader.class), + "org.neo4j.bolt.connection.codec.impl.value_encoding.ValueDecoderFactoryImpl", + ValueDecoderFactory.class); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/ValueEncoderFactoryLoader.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/ValueEncoderFactoryLoader.java new file mode 100644 index 0000000000..f5fb496cc6 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/ValueEncoderFactoryLoader.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption; + +import org.neo4j.bolt.connection.codec.value_encoding.ValueEncoderFactory; +import org.neo4j.driver.Logging; + +public final class ValueEncoderFactoryLoader extends AbstractFactoryLoader { + public ValueEncoderFactoryLoader(@SuppressWarnings("deprecation") Logging logging) { + super( + logging.getLog(ValueEncoderFactoryLoader.class), + "org.neo4j.bolt.connection.codec.impl.value_encoding.ValueEncoderFactoryImpl", + ValueEncoderFactory.class); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/async/InternalAsyncEncapsulatedKeyManager.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/async/InternalAsyncEncapsulatedKeyManager.java new file mode 100644 index 0000000000..fe60f3ca4a --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/async/InternalAsyncEncapsulatedKeyManager.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption.async; + +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeAsync; + +import java.util.Objects; +import java.util.concurrent.CompletionStage; +import javax.crypto.SecretKey; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.property_encryption.AbstractEncapsulatedKeyManager; +import org.neo4j.driver.internal.property_encryption.Cache; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecordRepository; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.async.AsyncEncapsulatedKeyManager; + +final class InternalAsyncEncapsulatedKeyManager extends AbstractEncapsulatedKeyManager + implements AsyncEncapsulatedKeyManager { + private final DriverObservationProvider observationProvider; + + public InternalAsyncEncapsulatedKeyManager( + KeyEncapsulationService keyEncapsulationService, + EncapsulatedKeyRecordRepository keyRepository, + Cache aliasToIdCache, + Cache idToKeyCache, + DriverObservationProvider observationProvider) { + super(keyEncapsulationService, keyRepository, aliasToIdCache, idToKeyCache); + this.observationProvider = Objects.requireNonNull(observationProvider); + } + + @Override + public CompletionStage createAsync(String alias, KeyEncapsulationOptions encapsulationOptions) { + var createObservation = observationProvider.createEncapsulatedKey(AsyncEncapsulatedKeyManager.class, alias); + return observeAsync(createObservation, () -> super.createAsync(alias, encapsulationOptions)); + } + + @Override + public CompletionStage findByAliasAsync(String alias) { + var findObservation = observationProvider.findEncapsulatedKeyByAlias(AsyncEncapsulatedKeyManager.class, alias); + return observeAsync(findObservation, () -> super.findByAliasAsync(alias)); + } + + @Override + public CompletionStage updateAliasByIdAsync(String id, String alias) { + var updateObservation = + observationProvider.updateEncapsulatedKeyAlias(AsyncEncapsulatedKeyManager.class, id, alias); + return observeAsync(updateObservation, () -> super.updateAliasByIdAsync(id, alias)); + } + + @Override + public CompletionStage deleteByIdAsync(String id) { + var deleteObservation = observationProvider.deleteEncapsulatedKey(AsyncEncapsulatedKeyManager.class, id); + return observeAsync(deleteObservation, () -> super.deleteByIdAsync(id)); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/async/InternalAsyncPropertyEncryption.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/async/InternalAsyncPropertyEncryption.java new file mode 100644 index 0000000000..2077316e21 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/async/InternalAsyncPropertyEncryption.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption.async; + +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeAsync; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletionStage; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.property_encryption.AEADEncryption; +import org.neo4j.driver.internal.property_encryption.AbstractPropertyEncryption; +import org.neo4j.driver.internal.property_encryption.PropertyEncryptionHandler; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.neo4j.driver.property_encryption.async.AsyncEncapsulatedKeyManager; +import org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption; + +public final class InternalAsyncPropertyEncryption extends AbstractPropertyEncryption + implements AsyncPropertyEncryption { + private final DriverObservationProvider observationProvider; + + public InternalAsyncPropertyEncryption( + Map nameToHandler, + AEADEncryption aeadEncryption, + DriverObservationProvider observationProvider) { + super(nameToHandler, aeadEncryption); + this.observationProvider = Objects.requireNonNull(observationProvider); + } + + @Override + public CompletionStage encryptToBytesAsync(PropertyEncryptionRequest encryptRequest) { + var encryptObservation = observationProvider.encryptToBytes(AsyncPropertyEncryption.class); + return observeAsync(encryptObservation, () -> super.encryptToBytesAsync(encryptRequest)); + } + + @Override + public CompletionStage decryptAsync(PropertyDecryptionRequest decryptRequest) { + var decryptObservation = observationProvider.decrypt(AsyncPropertyEncryption.class); + return observeAsync(decryptObservation, () -> super.decryptAsync(decryptRequest)); + } + + @Override + public AsyncEncapsulatedKeyManager keyManager(String profileName) { + var handler = getHandler(profileName); + var keyRepository = handler.keyRepository(); + if (keyRepository == null) { + throw new IllegalStateException("Key Manager is not supported in this profile"); + } + var keyEncapsulationService = handler.keyEncapsulationService(); + return new InternalAsyncEncapsulatedKeyManager( + keyEncapsulationService, + keyRepository, + handler.aliasToIdCache(), + handler.idToKeyCache(), + observationProvider); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactive/InternalReactiveEncapsulatedKeyManager.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactive/InternalReactiveEncapsulatedKeyManager.java new file mode 100644 index 0000000000..ea21544625 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactive/InternalReactiveEncapsulatedKeyManager.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption.reactive; + +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; +import static reactor.adapter.JdkFlowAdapter.publisherToFlowPublisher; + +import java.util.Objects; +import java.util.concurrent.Flow; +import javax.crypto.SecretKey; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.property_encryption.AbstractEncapsulatedKeyManager; +import org.neo4j.driver.internal.property_encryption.Cache; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecordRepository; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.reactive.ReactiveEncapsulatedKeyManager; +import reactor.core.publisher.Mono; + +final class InternalReactiveEncapsulatedKeyManager extends AbstractEncapsulatedKeyManager + implements ReactiveEncapsulatedKeyManager { + private final DriverObservationProvider observationProvider; + + public InternalReactiveEncapsulatedKeyManager( + KeyEncapsulationService keyEncapsulationService, + EncapsulatedKeyRecordRepository keyRepository, + Cache aliasToIdCache, + Cache idToKeyCache, + DriverObservationProvider observationProvider) { + super(keyEncapsulationService, keyRepository, aliasToIdCache, idToKeyCache); + this.observationProvider = Objects.requireNonNull(observationProvider); + } + + @Override + public Flow.Publisher create(String alias, KeyEncapsulationOptions encapsulationOptions) { + var createObservation = observationProvider.createEncapsulatedKey(ReactiveEncapsulatedKeyManager.class, alias); + return publisherToFlowPublisher(observeStreams( + createObservation, Mono.fromCompletionStage(() -> createAsync(alias, encapsulationOptions)))); + } + + @Override + public Flow.Publisher findByAlias(String alias) { + var findObservation = + observationProvider.findEncapsulatedKeyByAlias(ReactiveEncapsulatedKeyManager.class, alias); + return publisherToFlowPublisher( + observeStreams(findObservation, Mono.fromCompletionStage(() -> findByAliasAsync(alias)))); + } + + @Override + public Flow.Publisher updateAliasById(String id, String alias) { + var updateObservation = + observationProvider.updateEncapsulatedKeyAlias(ReactiveEncapsulatedKeyManager.class, id, alias); + return publisherToFlowPublisher( + observeStreams(updateObservation, Mono.fromCompletionStage(() -> updateAliasByIdAsync(id, alias)))); + } + + @Override + public Flow.Publisher deleteById(String id) { + var deleteObservation = observationProvider.deleteEncapsulatedKey(ReactiveEncapsulatedKeyManager.class, id); + return publisherToFlowPublisher( + observeStreams(deleteObservation, Mono.fromCompletionStage(() -> deleteByIdAsync(id)))); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactive/InternalReactivePropertyEncryption.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactive/InternalReactivePropertyEncryption.java new file mode 100644 index 0000000000..7b538b0e69 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactive/InternalReactivePropertyEncryption.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption.reactive; + +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; +import static reactor.adapter.JdkFlowAdapter.publisherToFlowPublisher; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Flow.Publisher; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.property_encryption.AEADEncryption; +import org.neo4j.driver.internal.property_encryption.AbstractPropertyEncryption; +import org.neo4j.driver.internal.property_encryption.PropertyEncryptionHandler; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.neo4j.driver.property_encryption.reactive.ReactiveEncapsulatedKeyManager; +import org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption; +import reactor.core.publisher.Mono; + +public final class InternalReactivePropertyEncryption extends AbstractPropertyEncryption + implements ReactivePropertyEncryption { + private final DriverObservationProvider observationProvider; + + public InternalReactivePropertyEncryption( + Map nameToHandler, + AEADEncryption aeadEncryption, + DriverObservationProvider observationProvider) { + super(nameToHandler, aeadEncryption); + this.observationProvider = Objects.requireNonNull(observationProvider); + } + + @Override + public Publisher encryptToBytes(PropertyEncryptionRequest encryptRequest) { + var encryptObservation = observationProvider.encryptToBytes(ReactivePropertyEncryption.class); + return publisherToFlowPublisher(observeStreams( + encryptObservation, Mono.fromCompletionStage(() -> encryptToBytesAsync(encryptRequest)))); + } + + @Override + public Publisher decrypt(PropertyDecryptionRequest decryptRequest) { + var decryptObservation = observationProvider.decrypt(ReactivePropertyEncryption.class); + return publisherToFlowPublisher( + observeStreams(decryptObservation, Mono.fromCompletionStage(() -> decryptAsync(decryptRequest)))); + } + + @Override + public ReactiveEncapsulatedKeyManager keyManager(String profileName) { + var handler = getHandler(profileName); + var keyRepository = handler.keyRepository(); + if (keyRepository == null) { + throw new IllegalStateException("Key Manager is not supported in this profile"); + } + var keyEncapsulationService = handler.keyEncapsulationService(); + return new InternalReactiveEncapsulatedKeyManager( + keyEncapsulationService, + keyRepository, + handler.aliasToIdCache(), + handler.idToKeyCache(), + observationProvider); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactivestreams/InternalReactiveEncapsulatedKeyManager.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactivestreams/InternalReactiveEncapsulatedKeyManager.java new file mode 100644 index 0000000000..ce8ed8b1ba --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactivestreams/InternalReactiveEncapsulatedKeyManager.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption.reactivestreams; + +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; + +import java.util.Objects; +import javax.crypto.SecretKey; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.property_encryption.AbstractEncapsulatedKeyManager; +import org.neo4j.driver.internal.property_encryption.Cache; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecordRepository; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.reactivestreams.ReactiveEncapsulatedKeyManager; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +final class InternalReactiveEncapsulatedKeyManager extends AbstractEncapsulatedKeyManager + implements ReactiveEncapsulatedKeyManager { + private final DriverObservationProvider observationProvider; + + public InternalReactiveEncapsulatedKeyManager( + KeyEncapsulationService keyEncapsulationService, + EncapsulatedKeyRecordRepository keyRepository, + Cache aliasToIdCache, + Cache idToKeyCache, + DriverObservationProvider observationProvider) { + super(keyEncapsulationService, keyRepository, aliasToIdCache, idToKeyCache); + this.observationProvider = Objects.requireNonNull(observationProvider); + } + + @Override + public Publisher create(String alias, KeyEncapsulationOptions encapsulationOptions) { + var createObservation = observationProvider.createEncapsulatedKey(ReactiveEncapsulatedKeyManager.class, alias); + return observeStreams( + createObservation, Mono.fromCompletionStage(() -> createAsync(alias, encapsulationOptions))); + } + + @Override + public Publisher findByAlias(String alias) { + var findObservation = + observationProvider.findEncapsulatedKeyByAlias(ReactiveEncapsulatedKeyManager.class, alias); + return observeStreams(findObservation, Mono.fromCompletionStage(() -> findByAliasAsync(alias))); + } + + @Override + public Publisher updateAliasById(String id, String alias) { + var updateObservation = + observationProvider.updateEncapsulatedKeyAlias(ReactiveEncapsulatedKeyManager.class, id, alias); + return observeStreams(updateObservation, Mono.fromCompletionStage(() -> updateAliasByIdAsync(id, alias))); + } + + @Override + public Publisher deleteById(String id) { + var deleteObservation = observationProvider.deleteEncapsulatedKey(ReactiveEncapsulatedKeyManager.class, id); + return observeStreams(deleteObservation, Mono.fromCompletionStage(() -> deleteByIdAsync(id))); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactivestreams/InternalReactivePropertyEncryption.java b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactivestreams/InternalReactivePropertyEncryption.java new file mode 100644 index 0000000000..8e36c01fa3 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/property_encryption/reactivestreams/InternalReactivePropertyEncryption.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.property_encryption.reactivestreams; + +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; + +import java.util.Map; +import java.util.Objects; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.property_encryption.AEADEncryption; +import org.neo4j.driver.internal.property_encryption.AbstractPropertyEncryption; +import org.neo4j.driver.internal.property_encryption.PropertyEncryptionHandler; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.neo4j.driver.property_encryption.reactivestreams.ReactiveEncapsulatedKeyManager; +import org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +public final class InternalReactivePropertyEncryption extends AbstractPropertyEncryption + implements ReactivePropertyEncryption { + private final DriverObservationProvider observationProvider; + + public InternalReactivePropertyEncryption( + Map nameToHandler, + AEADEncryption aeadEncryption, + DriverObservationProvider observationProvider) { + super(nameToHandler, aeadEncryption); + this.observationProvider = Objects.requireNonNull(observationProvider); + } + + @Override + public Publisher encryptToBytes(PropertyEncryptionRequest encryptRequest) { + var encryptObservation = observationProvider.encryptToBytes(ReactivePropertyEncryption.class); + return observeStreams(encryptObservation, Mono.fromCompletionStage(() -> encryptToBytesAsync(encryptRequest))); + } + + @Override + public Publisher decrypt(PropertyDecryptionRequest decryptRequest) { + var decryptObservation = observationProvider.decrypt(ReactivePropertyEncryption.class); + return observeStreams(decryptObservation, Mono.fromCompletionStage(() -> decryptAsync(decryptRequest))); + } + + @Override + public ReactiveEncapsulatedKeyManager keyManager(String profileName) { + var handler = getHandler(profileName); + var keyRepository = handler.keyRepository(); + if (keyRepository == null) { + throw new IllegalStateException("Key Manager is not supported in this profile"); + } + var keyEncapsulationService = handler.keyEncapsulationService(); + return new InternalReactiveEncapsulatedKeyManager( + keyEncapsulationService, + keyRepository, + handler.aliasToIdCache(), + handler.idToKeyCache(), + observationProvider); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/BasePropertyEncryption.java b/driver/src/main/java/org/neo4j/driver/property_encryption/BasePropertyEncryption.java new file mode 100644 index 0000000000..be3669b5cf --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/BasePropertyEncryption.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import org.neo4j.driver.util.Preview; + +/** + * A base interface for Neo4j Property encryption. + * @see PropertyEncryption + * @see org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption + * @see org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption + * @see org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface BasePropertyEncryption {} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/CacheConfig.java b/driver/src/main/java/org/neo4j/driver/property_encryption/CacheConfig.java new file mode 100644 index 0000000000..7c8ff722c2 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/CacheConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.time.Duration; +import org.neo4j.driver.util.Preview; + +/** + * A cache configuration. + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface CacheConfig { + /** + * Returns the maximum cache size. + * @return the maximum cache size + */ + int maxSize(); + + /** + * Returns the cache entry TTL after which entries are considered no longer valid. + * @return the cache entry TTL + */ + Duration ttl(); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/CryptoContext.java b/driver/src/main/java/org/neo4j/driver/property_encryption/CryptoContext.java new file mode 100644 index 0000000000..59b6580aac --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/CryptoContext.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.security.Provider; +import java.security.SecureRandom; +import org.neo4j.driver.util.Preview; + +/** + * A cryptographic context used for cryptographic purposes. + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface CryptoContext { + /** + * Returns the {@link Provider} that must be used for cryptographic purposes + * @return the provider + */ + Provider provider(); + + /** + * Returns the {@link SecureRandom} that must be used for IV generation. + * @return the secure random generator + */ + SecureRandom ivSecureRandom(); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKey.java b/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKey.java new file mode 100644 index 0000000000..c4f8ffaf70 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKey.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.util.Optional; +import org.neo4j.driver.util.Preview; + +/** + * An encapsulated key. + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface EncapsulatedKey { + /** + * Returns the key id. + * @return the key id + */ + String id(); + + /** + * Returns the key alias if assigned. + * @return the key alias or {@link Optional#empty()} otherwise + */ + Optional alias(); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyManager.java b/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyManager.java new file mode 100644 index 0000000000..35b37f7c63 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyManager.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.util.Optional; +import org.neo4j.driver.util.Preview; + +/** + * A manager for encapsulated keys. + * @see org.neo4j.driver.property_encryption.async.AsyncEncapsulatedKeyManager + * @see org.neo4j.driver.property_encryption.reactive.ReactiveEncapsulatedKeyManager + * @see org.neo4j.driver.property_encryption.reactivestreams.ReactiveEncapsulatedKeyManager + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface EncapsulatedKeyManager { + /** + * Creates a new encapsulated key without key alias. + * @return the encapsulated key + */ + default EncapsulatedKey create() { + return create(null); + } + + /** + * Creates a new encapsulated key with the provided key alias. + * @param alias the key alias, may be {@literal null} + * @return the encapsulated key + */ + default EncapsulatedKey create(String alias) { + return create(alias, null); + } + + /** + * Creates a new encapsulated key with the provided key alias and {@link KeyEncapsulationOptions}. + * @param alias the key alias, may be {@literal null} + * @param encapsulationOptions the key encapsulation options, may be {@literal null} + * @return the encapsulated key + */ + EncapsulatedKey create(String alias, KeyEncapsulationOptions encapsulationOptions); + + /** + * Finds encapsulated key by its alias. + * @param alias the key alias, must not be {@literal null} + * @return the encapsulated key or {@link Optional#empty()} otherwise + */ + Optional findByAlias(String alias); + + /** + * Updates encapsulated key alias by id. + * @param id the key id, must not be {@literal null} + * @param alias the new key alias, may be {@literal null} + */ + void updateAliasById(String id, String alias); + + /** + * Deletes encapsulated key alias by id. + * @param id the key id, must not be {@literal null} + */ + default void deleteAliasById(String id) { + updateAliasById(id, null); + } + + /** + * Deletes encapsulated key by id. + * @param id the key id, must not be {@literal null} + */ + void deleteById(String id); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyRecord.java b/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyRecord.java new file mode 100644 index 0000000000..f0b1edcfae --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyRecord.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.util.Map; +import org.neo4j.driver.util.Preview; + +/** + * An encapsulate key with associated data. + * @since 6.3.0 + * @see EncapsulatedKeyRecords + */ +@Preview(name = "Property Encryption") +public interface EncapsulatedKeyRecord extends EncapsulatedKey { + /** + * Returns the key encapsulation. + * + * @return the key encapsulation + */ + byte[] encapsulation(); + + /** + * Returns the key metadata. + * + * @return the key metadata + */ + Map metadata(); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyRecordRepository.java b/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyRecordRepository.java new file mode 100644 index 0000000000..eb28a97ca7 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyRecordRepository.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.util.Map; +import java.util.concurrent.CompletionStage; +import org.neo4j.driver.util.Preview; + +/** + * A repository for {@link EncapsulatedKeyRecord} data. + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface EncapsulatedKeyRecordRepository { + /** + * Finds and returns an {@link EncapsulatedKeyRecord} by its id. + * @param id the key id + * @return the key + */ + CompletionStage findById(String id); + + /** + * Finds and returns an {@link EncapsulatedKeyRecord} by its alias. + * @param alias the key alias + * @return the key + */ + CompletionStage findByAlias(String alias); + + /** + * Saves the encapsulation as a key and assigns it a unique id. + * @param alias the key alias, may be {@literal null} + * @param encapsulation the key encapsulation, must not be {@literal null} + * @param metadata the key metadata, must not be {@literal null} + * @return the key + */ + CompletionStage save(String alias, byte[] encapsulation, Map metadata); + + /** + * Updates key alias. + * @param id the key id, must not be {@literal null} + * @param alias the key alias, may be {@literal null} + * @return {@link Void} + */ + CompletionStage updateAliasById(String id, String alias); + + /** + * Deletes key by id. + * @param id the key id, must not be {@literal null} + * @return {@link Void} + */ + CompletionStage deleteById(String id); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyRecords.java b/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyRecords.java new file mode 100644 index 0000000000..cf62bfbec1 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/EncapsulatedKeyRecords.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.util.Map; +import org.neo4j.driver.internal.InternalEncapsulatedKeyRecord; +import org.neo4j.driver.util.Preview; + +/** + * A factory for {@link EncapsulatedKeyRecord}. + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public final class EncapsulatedKeyRecords { + private EncapsulatedKeyRecords() {} + + /** + * Returns a new instance of {@link EncapsulatedKeyRecord}. + * @param id the key id, must not be {@literal null} + * @param alias the key alias, may be {@literal null} + * @param encapsulation the key encapsulation, must not be {@literal null} + * @param metadata the key metadata, must not be {@literal null} + * @return the new instance of encapsulated key record + */ + public static EncapsulatedKeyRecord create( + String id, String alias, byte[] encapsulation, Map metadata) { + return new InternalEncapsulatedKeyRecord(id, alias, encapsulation, metadata); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/EnvelopePropertyEncryptionProfile.java b/driver/src/main/java/org/neo4j/driver/property_encryption/EnvelopePropertyEncryptionProfile.java new file mode 100644 index 0000000000..595fb5e792 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/EnvelopePropertyEncryptionProfile.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.security.Provider; +import java.security.SecureRandom; +import java.time.Duration; +import java.util.Optional; +import org.neo4j.driver.internal.property_encryption.InternalEnvelopePropertyEncryptionProfile; +import org.neo4j.driver.util.Preview; + +/** + * An encryption profile that enables Envelope Encryption for Neo4j Property Encryption. + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public sealed interface EnvelopePropertyEncryptionProfile extends PropertyEncryptionProfile + permits InternalEnvelopePropertyEncryptionProfile { + /** + * Returns a new builder for {@link EnvelopePropertyEncryptionProfile}. + * @param name the unique name of the profile instance, must not be {@literal null} or empty + * @param keyEncapsulationService the {@link KeyEncapsulationService} implementation, must not be {@literal null} + * @param keyRepository the {@link EncapsulatedKeyRecordRepository} implementation, must not be {@literal null} + * @return the new builder + */ + static Builder builder( + String name, + KeyEncapsulationService keyEncapsulationService, + EncapsulatedKeyRecordRepository keyRepository) { + return new InternalEnvelopePropertyEncryptionProfile.Builder(name, keyEncapsulationService, keyRepository); + } + + /** + * A builder for {@link EnvelopePropertyEncryptionProfile}. + */ + interface Builder { + /** + * Sets {@link CryptoContext} to be used for cryptographic purposes. + * @param provider the {@link Provider}, must not be {@literal null} + * @param ivSecureRandom the {@link SecureRandom} for IV generation, must not be {@literal null} and + * {@link SecureRandom#getProvider()} must resolve to the provider param + * @return this builder + */ + Builder withCryptoContext(Provider provider, SecureRandom ivSecureRandom); + + /** + * Sets cache config for key cache. + *

+ * The key cache is responsible for keeping key id resolution to decapsulated key. + *

+ * The cache is enabled by default with the maximum size of {@literal 100} entries and {@literal 15} minutes TTL. + * + * @param maxSize the maximum cache size, must be equal or greater than 1 + * @param ttl the entry TTL, must not be {@literal null} + * @return this builder + */ + Builder withKeyCache(int maxSize, Duration ttl); + + /** + * Disables key cache. + * @return this builder + */ + Builder withoutKeyCache(); + + /** + * Sets cache config for key alias cache. + *

+ * The key alias cache is responsible for keeping key alias resolution to key id. + *

+ * The cache is enabled by default with the maximum size of {@literal 100} entries and {@literal 15} seconds TTL. + * + * @param maxSize the maximum cache size, must be equal or greater than 1 + * @param ttl the entry TTL, must not be {@literal null} + * @return this builder + */ + Builder withKeyAliasCache(int maxSize, Duration ttl); + + /** + * Disables the key alias cache. + * @return this builder + */ + Builder withoutKeyAliasCache(); + + /** + * Returns a new instance of {@link EnvelopePropertyEncryptionProfile}. + * @return the new instance of profile + */ + EnvelopePropertyEncryptionProfile build(); + } + + /** + * Returns the {@link KeyEncapsulationService} used by this profile. + * @return the encapsulation service + */ + KeyEncapsulationService keyEncapsulationService(); + + /** + * Returns the {@link EncapsulatedKeyRecordRepository} used by this profile. + * @return the key repository + */ + EncapsulatedKeyRecordRepository keyRepository(); + + /** + * Returns {@link CryptoContext} if set. + * @return the crypto context + */ + Optional cryptoContext(); + + /** + * Returns key cache {@link CacheConfig} if enabled. + *

+ * The cache is enabled by default with the maximum size of {@literal 100} entries and {@literal 15} minutes TTL. + * + * @return the cache config + */ + Optional keyCacheConfig(); + + /** + * Returns key alias cache {@link CacheConfig} if enabled. + *

+ * The cache is enabled by default with the maximum size of {@literal 100} entries and {@literal 15} seconds TTL. + * + * @return the cache config + */ + Optional keyAliasCacheConfig(); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationOptions.java b/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationOptions.java new file mode 100644 index 0000000000..ba66cd5182 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationOptions.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.util.Map; +import org.neo4j.driver.util.Preview; + +/** + * Options required by {@link KeyEncapsulationService} in order to encapsulate and decapsulate keys. + * Most service implementations are expected have a dedicated subtype for options. + * @see KeyEncapsulationService + * @see EncapsulatedKey + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface KeyEncapsulationOptions { + /** + * Returns the options as a {@link Map}. + * @return the options as a map + */ + Map toMap(); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationResult.java b/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationResult.java new file mode 100644 index 0000000000..163df1c707 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationResult.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.util.Map; +import javax.crypto.SecretKey; +import org.neo4j.driver.util.Preview; + +/** + * A key encapsulation result. + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface KeyEncapsulationResult { + /** + * Returns the encapsulation bytes. + * @return the encapsulation bytes + */ + byte[] encapsulation(); + + /** + * Returns the encapsulation metadata. + * @return the encapsulation metadata + */ + Map metadata(); + + /** + * Returns the key. + * @return the key + */ + SecretKey key(); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationResults.java b/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationResults.java new file mode 100644 index 0000000000..b6cdb4ce8b --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationResults.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.util.Map; +import javax.crypto.SecretKey; +import org.neo4j.driver.internal.property_encryption.InternalKeyEncapsulationResult; +import org.neo4j.driver.util.Preview; + +/** + * A factory for {@link KeyEncapsulationResult}. + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public final class KeyEncapsulationResults { + private KeyEncapsulationResults() {} + + /** + * Creates a new encapsulation result. + * + * @param encapsulation the encapsulation bytes, must not be {@literal null} + * @param metadata the encapsulation metadata, must not be {@literal null} + * @param key the new key, must not be {@literal null} + * @return the new encapsulation result + */ + public static KeyEncapsulationResult create(byte[] encapsulation, Map metadata, SecretKey key) { + return new InternalKeyEncapsulationResult(encapsulation, metadata, key); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationService.java b/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationService.java new file mode 100644 index 0000000000..747c6c137e --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationService.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.util.Map; +import java.util.concurrent.CompletionStage; +import javax.crypto.SecretKey; +import org.neo4j.driver.util.Preview; + +/** + * A service responsible for encapsulating and decapsulating keys. + * @see KeyEncapsulationOptions + * @see EncapsulatedKey + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface KeyEncapsulationService { + /** + * Creates a new key, encapsulates it and returns the result. + * @param options the encapsulation options + * @return the encapsulation result + */ + CompletionStage encapsulate(KeyEncapsulationOptions options); + + /** + * Decapsulates encapsulated bytes. + * @param encapsulation the encapsulated bytes + * @param metadata the key metadata + * @return the key + */ + CompletionStage decapsulate(byte[] encapsulation, Map metadata); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationServices.java b/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationServices.java new file mode 100644 index 0000000000..6e35f838dc --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/KeyEncapsulationServices.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import javax.crypto.SecretKey; +import org.neo4j.driver.internal.property_encryption.LocalKeyEncapsulationService; +import org.neo4j.driver.util.Preview; + +/** + * A factory for {@link KeyEncapsulationService} implementation provided with the driver. + *

+ * Note that additional implementations are possible. + * @see KeyEncapsulationService + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public final class KeyEncapsulationServices { + private KeyEncapsulationServices() {} + + /** + * Returns a new {@link KeyEncapsulationService} implementation that uses the provided AES-256 {@link SecretKey} + * as a master key for encrypting and decrypting data keys. + * @param masterKey the master key + * @return the new implementation + * @throws NoSuchAlgorithmException if no AES algorithm is found + */ + public static KeyEncapsulationService local(SecretKey masterKey) throws NoSuchAlgorithmException { + return new LocalKeyEncapsulationService(masterKey, null, null); + } + + /** + * Returns a new {@link KeyEncapsulationService} implementation that uses the provided AES-256 {@link SecretKey} + * as a master key for encrypting and decrypting data keys. + * @param masterKey the master key + * @param provider the {@link Provider} to use for cryptographic operations, must not be {@literal null} + * @param secureRandomIV the {@link SecureRandom} to use for IV generation, must not be {@literal null} + * @return the new implementation + * @throws NoSuchAlgorithmException if no AES algorithm is found + */ + public static KeyEncapsulationService local(SecretKey masterKey, Provider provider, SecureRandom secureRandomIV) + throws NoSuchAlgorithmException { + return new LocalKeyEncapsulationService(masterKey, provider, secureRandomIV); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyDecryptionRequest.java b/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyDecryptionRequest.java new file mode 100644 index 0000000000..f33c26caed --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyDecryptionRequest.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.util.UUID; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.property_encryption.InternalPropertyDecryptionRequest; +import org.neo4j.driver.types.Point; +import org.neo4j.driver.types.TypeSystem; +import org.neo4j.driver.util.Preview; + +/** + * A Neo4j Property decryption request. + * @since 6.3.0 + * @see PropertyEncryption + * @see org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption + * @see org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption + * @see org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption + */ +@Preview(name = "Property Encryption") +public interface PropertyDecryptionRequest { + /** + * Returns a new instance of a build stage for {@link PropertyDecryptionRequest}. + * @return a new instance of a build stage + */ + static PropertyDecryptionRequest.ValueStep builder() { + return new InternalPropertyDecryptionRequest(); + } + + /** + * A builder step for setting value. + */ + interface ValueStep { + /** + * Sets the value to decrypt. + * @param value the value to decrypt + * @return the next builder step + */ + AADStep fromValue(byte[] value); + } + + /** + * A builder step for setting AAD. + */ + interface AADStep { + /** + * Adds the supplied value as AAD for decryption request. + *

+ * Note that only a subset of types is supported for AAD, they are listed below: + *

    + *
  • {@link TypeSystem#BOOLEAN()}
  • + *
  • {@link TypeSystem#BYTES()}
  • + *
  • {@link TypeSystem#STRING()}
  • + *
  • {@link TypeSystem#INTEGER()}
  • + *
  • {@link TypeSystem#POINT()}
  • + *
  • {@link TypeSystem#DATE()}
  • + *
  • {@link TypeSystem#TIME()}
  • + *
  • {@link TypeSystem#LOCAL_TIME()}
  • + *
  • {@link TypeSystem#UUID()}
  • + *
+ * + * @param aad the AAD value, both {@literal null} and {@link TypeSystem#NULL()} disable AAD + * @return the next builder step + */ + BuildStep withAAD(Value aad); + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value + * @return the next builder step + */ + default BuildStep withAAD(boolean aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default BuildStep withAAD(LocalDate aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default BuildStep withAAD(OffsetTime aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default BuildStep withAAD(LocalTime aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value + * @return the next builder step + */ + default BuildStep withAAD(double aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value + * @return the next builder step + */ + default BuildStep withAAD(int aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value + * @return the next builder step + */ + default BuildStep withAAD(long aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default BuildStep withAAD(Point aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default BuildStep withAAD(String aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default BuildStep withAAD(UUID aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for decryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default BuildStep withAAD(byte[] aad) { + return withAAD(Values.value(aad)); + } + + /** + * Enables using the persisted AAD. + * @return the next builder step + */ + BuildStep withPersistedAAD(); + } + + /** + * A builder step for building {@link PropertyDecryptionRequest}. + */ + interface BuildStep { + /** + * Builds and returns a new {@link PropertyDecryptionRequest} instance. + * @return the new request instance + */ + PropertyDecryptionRequest build(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyEncryption.java b/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyEncryption.java new file mode 100644 index 0000000000..6971d24350 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyEncryption.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import org.neo4j.driver.Value; +import org.neo4j.driver.util.Preview; + +/** + * A Neo4j Property encryption. + * @see org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption + * @see org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption + * @see org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface PropertyEncryption extends BasePropertyEncryption { + /** + * Handles the provided {@link PropertyEncryptionRequest}. + * @param encryptRequest the request, must not be {@literal null} + * @return the encrypted bytes + */ + byte[] encryptToBytes(PropertyEncryptionRequest encryptRequest); + + /** + * Handles the provided {@link PropertyDecryptionRequest}. + * @param decryptRequest the request, must not be {@literal null} + * @return the decrypted value + */ + Value decrypt(PropertyDecryptionRequest decryptRequest); + + /** + * Returns key manager. + * @return key manager + */ + default EncapsulatedKeyManager keyManager() { + return keyManager(null); + } + + /** + * Returns key manager for a given profile name. + * @param profileName the profile name + * @return key manager, may be {@literal null} when only a single profile is available + */ + EncapsulatedKeyManager keyManager(String profileName); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyEncryptionProfile.java b/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyEncryptionProfile.java new file mode 100644 index 0000000000..fec769c264 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyEncryptionProfile.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import org.neo4j.driver.util.Preview; + +/** + * A profile for Neo4j Property Encryption. + *

+ * Each profile instance represents a specific encryption configuration that MUST have a unique name, which is also + * used for cross-driver interoperability. + *

+ * While there may be several profile types in the future, only {@link EnvelopePropertyEncryptionProfile} is supported + * for now. + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public sealed interface PropertyEncryptionProfile permits EnvelopePropertyEncryptionProfile { + /** + * Returns the unique profile name. + * @return the profile name + */ + String name(); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyEncryptionRequest.java b/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyEncryptionRequest.java new file mode 100644 index 0000000000..8941c48d59 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/PropertyEncryptionRequest.java @@ -0,0 +1,517 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.UUID; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.property_encryption.InternalPropertyEncryptionRequest; +import org.neo4j.driver.types.IsoDuration; +import org.neo4j.driver.types.Point; +import org.neo4j.driver.types.TypeSystem; +import org.neo4j.driver.types.Vector; +import org.neo4j.driver.util.Preview; + +/** + * A Neo4j Property encryption request. + * @since 6.3.0 + * @see PropertyEncryption + * @see org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption + * @see org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption + * @see org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption + */ +@Preview(name = "Property Encryption") +public interface PropertyEncryptionRequest { + /** + * Returns a new instance of a build stage for {@link PropertyEncryptionRequest}. + * @return a new instance of a build stage + */ + static ValueStep builder() { + return new InternalPropertyEncryptionRequest(); + } + + /** + * A builder step for setting value. + */ + interface ValueStep { + /** + * Sets the value to encrypt. + *

+ * Note that the value MUST be of a supported Neo4j Property Type. + * + * @param value the value to encrypt + * @return the next builder step + */ + AADStep fromValue(Value value); + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(boolean value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(LocalDate value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(OffsetTime value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(LocalTime value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(LocalDateTime value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(OffsetDateTime value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(ZonedDateTime value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(Period value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(Duration value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(IsoDuration value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(double value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(int value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(long value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(Point value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(char value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(String value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(Vector value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the value to encrypt. + * + * @param value the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(UUID value) { + return fromValue(Values.value(value)); + } + + /** + * Sets the values to encrypt. + * + * @param values the values to encrypt + * @return the next builder step + */ + default AADStep fromValue(byte... values) { + return fromValue(Values.value(values)); + } + + /** + * Sets the values to encrypt. + * + * @param values the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(String... values) { + return fromValue(Values.value(values)); + } + + /** + * Sets the values to encrypt. + * + * @param values the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(boolean... values) { + return fromValue(Values.value(values)); + } + + /** + * Sets the values to encrypt. + * + * @param values the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(char... values) { + return fromValue(Values.value(values)); + } + + /** + * Sets the values to encrypt. + * + * @param values the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(short... values) { + return fromValue(Values.value(values)); + } + + /** + * Sets the values to encrypt. + * + * @param values the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(int... values) { + return fromValue(Values.value(values)); + } + + /** + * Sets the values to encrypt. + * + * @param values the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(long... values) { + return fromValue(Values.value(values)); + } + + /** + * Sets the values to encrypt. + * + * @param values the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(float... values) { + return fromValue(Values.value(values)); + } + + /** + * Sets the values to encrypt. + * + * @param values the value to encrypt + * @return the next builder step + */ + default AADStep fromValue(double... values) { + return fromValue(Values.value(values)); + } + } + + /** + * A builder step for setting AAD. + */ + interface AADStep extends ProfileStep { + /** + * Adds the supplied value as AAD for encryption request. + *

+ * Note that only a subset of types is supported for AAD, they are listed below: + *

    + *
  • {@link TypeSystem#BOOLEAN()}
  • + *
  • {@link TypeSystem#BYTES()}
  • + *
  • {@link TypeSystem#STRING()}
  • + *
  • {@link TypeSystem#INTEGER()}
  • + *
  • {@link TypeSystem#POINT()}
  • + *
  • {@link TypeSystem#DATE()}
  • + *
  • {@link TypeSystem#TIME()}
  • + *
  • {@link TypeSystem#LOCAL_TIME()}
  • + *
  • {@link TypeSystem#UUID()}
  • + *
+ * + * @param aad the AAD value, both {@literal null} and {@link TypeSystem#NULL()} disable AAD + * @return the next builder step + */ + ProfileStep withAAD(Value aad); + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value + * @return the next builder step + */ + default ProfileStep withAAD(boolean aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default ProfileStep withAAD(LocalDate aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default ProfileStep withAAD(OffsetTime aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default ProfileStep withAAD(LocalTime aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value + * @return the next builder step + */ + default ProfileStep withAAD(double aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value + * @return the next builder step + */ + default ProfileStep withAAD(int aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value + * @return the next builder step + */ + default ProfileStep withAAD(long aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default ProfileStep withAAD(Point aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default ProfileStep withAAD(String aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default ProfileStep withAAD(UUID aad) { + return withAAD(Values.value(aad)); + } + + /** + * Adds the supplied value as AAD for encryption request. + * + * @param aad the AAD value, {@literal null} disables AAD + * @return the next builder step + */ + default ProfileStep withAAD(byte[] aad) { + return withAAD(Values.value(aad)); + } + } + + /** + * A builder step for selection encryption profile. + */ + interface ProfileStep extends EncryptionKeyReferenceStep { + /** + * Sets the profile name to use. + * @param profileName the profile name + * @return the next builder step + */ + EncryptionKeyReferenceStep usingProfile(String profileName); + } + + /** + * A builder step for selecting key. + */ + interface EncryptionKeyReferenceStep { + /** + * Sets the key id to use. + * @param keyId the key id + * @return the next builder step + */ + BuildStep usingKeyId(String keyId); + + /** + * Sets the key alias to use. + * @param keyAlias the key alias + * @return the next builder step + */ + BuildStep usingKeyAlias(String keyAlias); + } + + /** + * A builder step for building {@link PropertyEncryptionRequest}. + */ + interface BuildStep { + /** + * Builds and returns a new {@link PropertyEncryptionRequest} instance. + * @return the new request instance + */ + PropertyEncryptionRequest build(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/async/AsyncEncapsulatedKeyManager.java b/driver/src/main/java/org/neo4j/driver/property_encryption/async/AsyncEncapsulatedKeyManager.java new file mode 100644 index 0000000000..df2d11f99f --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/async/AsyncEncapsulatedKeyManager.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.async; + +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.util.Preview; + +/** + * An asynchronous manager for encapsulated keys. + * @see org.neo4j.driver.property_encryption.EncapsulatedKeyManager + * @see org.neo4j.driver.property_encryption.reactive.ReactiveEncapsulatedKeyManager + * @see org.neo4j.driver.property_encryption.reactivestreams.ReactiveEncapsulatedKeyManager + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface AsyncEncapsulatedKeyManager { + /** + * Creates a new encapsulated key without key alias. + * @return the encapsulated key + */ + default CompletionStage createAsync() { + return createAsync(null); + } + + /** + * Creates a new encapsulated key with the provided key alias. + * @param alias the key alias, may be {@literal null} + * @return the encapsulated key + */ + default CompletionStage createAsync(String alias) { + return createAsync(alias, null); + } + + /** + * Creates a new encapsulated key with the provided key alias and {@link KeyEncapsulationOptions}. + * @param alias the key alias, may be {@literal null} + * @param encapsulationOptions the key encapsulation options, may be {@literal null} + * @return the encapsulated key + */ + CompletionStage createAsync(String alias, KeyEncapsulationOptions encapsulationOptions); + + /** + * Finds encapsulated key by its alias. + * @param alias the key alias, must not be {@literal null} + * @return the encapsulated key or {@link Optional#empty()} otherwise + */ + CompletionStage findByAliasAsync(String alias); + + /** + * Updates encapsulated key alias by id. + * @param id the key id, must not be {@literal null} + * @param alias the new key alias, may be {@literal null} + * @return {@link Void} + */ + CompletionStage updateAliasByIdAsync(String id, String alias); + + /** + * Deletes encapsulated key alias by id. + * @param id the key id, must not be {@literal null} + * @return {@link Void} + */ + default CompletionStage deleteAliasByIdAsync(String id) { + return updateAliasByIdAsync(id, null); + } + + /** + * Deletes encapsulated key by id. + * @param id the key id, must not be {@literal null} + * @return {@link Void} + */ + CompletionStage deleteByIdAsync(String id); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/async/AsyncPropertyEncryption.java b/driver/src/main/java/org/neo4j/driver/property_encryption/async/AsyncPropertyEncryption.java new file mode 100644 index 0000000000..83b73f14d2 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/async/AsyncPropertyEncryption.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.async; + +import java.util.concurrent.CompletionStage; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.neo4j.driver.util.Preview; + +/** + * An asynchronous Neo4j Property encryption. + * @see org.neo4j.driver.property_encryption.PropertyEncryption + * @see org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption + * @see org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface AsyncPropertyEncryption extends BasePropertyEncryption { + /** + * Handles the provided {@link PropertyEncryptionRequest}. + * @param encryptRequest the request, must not be {@literal null} + * @return the encrypted bytes + */ + CompletionStage encryptToBytesAsync(PropertyEncryptionRequest encryptRequest); + + /** + * Handles the provided {@link PropertyDecryptionRequest}. + * @param decryptRequest the request, must not be {@literal null} + * @return the decrypted value + */ + CompletionStage decryptAsync(PropertyDecryptionRequest decryptRequest); + + /** + * Returns key manager. + * @return key manager + */ + default AsyncEncapsulatedKeyManager keyManager() { + return keyManager(null); + } + + /** + * Returns key manager for a given profile name. + * @param profileName the profile name + * @return key manager, may be {@literal null} when only a single profile is available + */ + AsyncEncapsulatedKeyManager keyManager(String profileName); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/reactive/ReactiveEncapsulatedKeyManager.java b/driver/src/main/java/org/neo4j/driver/property_encryption/reactive/ReactiveEncapsulatedKeyManager.java new file mode 100644 index 0000000000..b7a722f812 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/reactive/ReactiveEncapsulatedKeyManager.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.reactive; + +import java.util.Optional; +import java.util.concurrent.Flow.Publisher; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.util.Preview; + +/** + * A reactive manager for encapsulated keys. + * + * @see org.neo4j.driver.property_encryption.EncapsulatedKeyManager + * @see org.neo4j.driver.property_encryption.async.AsyncEncapsulatedKeyManager + * @see org.neo4j.driver.property_encryption.reactivestreams.ReactiveEncapsulatedKeyManager + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface ReactiveEncapsulatedKeyManager { + /** + * Creates a new encapsulated key without key alias. + * @return the encapsulated key + */ + default Publisher create() { + return create(null); + } + + /** + * Creates a new encapsulated key with the provided key alias. + * @param alias the key alias, may be {@literal null} + * @return the encapsulated key + */ + default Publisher create(String alias) { + return create(alias, null); + } + + /** + * Creates a new encapsulated key with the provided key alias and {@link KeyEncapsulationOptions}. + * @param alias the key alias, may be {@literal null} + * @param encapsulationOptions the key encapsulation options, may be {@literal null} + * @return the encapsulated key + */ + Publisher create(String alias, KeyEncapsulationOptions encapsulationOptions); + + /** + * Finds encapsulated key by its alias. + * @param alias the key alias, must not be {@literal null} + * @return the encapsulated key or {@link Optional#empty()} otherwise + */ + Publisher findByAlias(String alias); + + /** + * Updates encapsulated key alias by id. + * @param id the key id, must not be {@literal null} + * @param alias the new key alias, may be {@literal null} + * @return {@link Void} + */ + Publisher updateAliasById(String id, String alias); + + /** + * Deletes encapsulated key alias by id. + * @param id the key id, must not be {@literal null} + * @return {@link Void} + */ + default Publisher deleteAliasById(String id) { + return updateAliasById(id, null); + } + + /** + * Deletes encapsulated key by id. + * @param id the key id, must not be {@literal null} + * @return {@link Void} + */ + Publisher deleteById(String id); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/reactive/ReactivePropertyEncryption.java b/driver/src/main/java/org/neo4j/driver/property_encryption/reactive/ReactivePropertyEncryption.java new file mode 100644 index 0000000000..062e7436e5 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/reactive/ReactivePropertyEncryption.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.reactive; + +import java.util.concurrent.Flow.Publisher; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.neo4j.driver.util.Preview; + +/** + * A reactive Neo4j Property encryption. + * @see org.neo4j.driver.property_encryption.PropertyEncryption + * @see org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption + * @see org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface ReactivePropertyEncryption extends BasePropertyEncryption { + /** + * Handles the provided {@link PropertyEncryptionRequest}. + * @param encryptRequest the request, must not be {@literal null} + * @return the encrypted bytes + */ + Publisher encryptToBytes(PropertyEncryptionRequest encryptRequest); + + /** + * Handles the provided {@link PropertyDecryptionRequest}. + * @param decryptRequest the request, must not be {@literal null} + * @return the decrypted value + */ + Publisher decrypt(PropertyDecryptionRequest decryptRequest); + + /** + * Returns key manager. + * @return key manager + */ + default ReactiveEncapsulatedKeyManager keyManager() { + return keyManager(null); + } + + /** + * Returns key manager for a given profile name. + * @param profileName the profile name + * @return key manager, may be {@literal null} when only a single profile is available + */ + ReactiveEncapsulatedKeyManager keyManager(String profileName); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/reactivestreams/ReactiveEncapsulatedKeyManager.java b/driver/src/main/java/org/neo4j/driver/property_encryption/reactivestreams/ReactiveEncapsulatedKeyManager.java new file mode 100644 index 0000000000..e92445e7d0 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/reactivestreams/ReactiveEncapsulatedKeyManager.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.reactivestreams; + +import java.util.Optional; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.util.Preview; +import org.reactivestreams.Publisher; + +/** + * A reactive manager for encapsulated keys. + * + * @see org.neo4j.driver.property_encryption.EncapsulatedKeyManager + * @see org.neo4j.driver.property_encryption.async.AsyncEncapsulatedKeyManager + * @see org.neo4j.driver.property_encryption.reactive.ReactiveEncapsulatedKeyManager + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface ReactiveEncapsulatedKeyManager { + /** + * Creates a new encapsulated key without key alias. + * @return the encapsulated key + */ + default Publisher create() { + return create(null); + } + + /** + * Creates a new encapsulated key with the provided key alias. + * @param alias the key alias, may be {@literal null} + * @return the encapsulated key + */ + default Publisher create(String alias) { + return create(alias, null); + } + + /** + * Creates a new encapsulated key with the provided key alias and {@link KeyEncapsulationOptions}. + * @param alias the key alias, may be {@literal null} + * @param encapsulationOptions the key encapsulation options, may be {@literal null} + * @return the encapsulated key + */ + Publisher create(String alias, KeyEncapsulationOptions encapsulationOptions); + + /** + * Finds encapsulated key by its alias. + * @param alias the key alias, must not be {@literal null} + * @return the encapsulated key or {@link Optional#empty()} otherwise + */ + Publisher findByAlias(String alias); + + /** + * Updates encapsulated key alias by id. + * @param id the key id, must not be {@literal null} + * @param alias the new key alias, may be {@literal null} + * @return {@link Void} + */ + Publisher updateAliasById(String id, String alias); + + /** + * Deletes encapsulated key alias by id. + * @param id the key id, must not be {@literal null} + * @return {@link Void} + */ + default Publisher deleteAliasById(String id) { + return updateAliasById(id, null); + } + + /** + * Deletes encapsulated key by id. + * @param id the key id, must not be {@literal null} + * @return {@link Void} + */ + Publisher deleteById(String id); +} diff --git a/driver/src/main/java/org/neo4j/driver/property_encryption/reactivestreams/ReactivePropertyEncryption.java b/driver/src/main/java/org/neo4j/driver/property_encryption/reactivestreams/ReactivePropertyEncryption.java new file mode 100644 index 0000000000..dbef77bda0 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/property_encryption/reactivestreams/ReactivePropertyEncryption.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.reactivestreams; + +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.neo4j.driver.util.Preview; +import org.reactivestreams.Publisher; + +/** + * A reactive Neo4j Property encryption. + * @see org.neo4j.driver.property_encryption.PropertyEncryption + * @see org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption + * @see org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption + * @since 6.3.0 + */ +@Preview(name = "Property Encryption") +public interface ReactivePropertyEncryption extends BasePropertyEncryption { + /** + * Handles the provided {@link PropertyEncryptionRequest}. + * @param encryptRequest the request, must not be {@literal null} + * @return the encrypted bytes + */ + Publisher encryptToBytes(PropertyEncryptionRequest encryptRequest); + + /** + * Handles the provided {@link PropertyDecryptionRequest}. + * @param decryptRequest the request, must not be {@literal null} + * @return the decrypted value + */ + Publisher decrypt(PropertyDecryptionRequest decryptRequest); + + /** + * Returns key manager. + * @return key manager + */ + default ReactiveEncapsulatedKeyManager keyManager() { + return keyManager(null); + } + + /** + * Returns key manager for a given profile name. + * @param profileName the profile name + * @return key manager, may be {@literal null} when only a single profile is available + */ + ReactiveEncapsulatedKeyManager keyManager(String profileName); +} diff --git a/driver/src/test/java/org/neo4j/driver/ConfigTest.java b/driver/src/test/java/org/neo4j/driver/ConfigTest.java index 254cb5bd10..c20f2f1048 100644 --- a/driver/src/test/java/org/neo4j/driver/ConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/ConfigTest.java @@ -30,6 +30,7 @@ import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -46,6 +47,8 @@ import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.net.ServerAddressResolver; import org.neo4j.driver.observation.ObservationProvider; +import org.neo4j.driver.property_encryption.EnvelopePropertyEncryptionProfile; +import org.neo4j.driver.property_encryption.PropertyEncryptionProfile; import org.neo4j.driver.testutil.TestUtil; class ConfigTest { @@ -568,4 +571,59 @@ void shouldAllowNullObservationProvider() { assertTrue(config.observationProvider().isEmpty()); } + + @Test + void shouldHaveNoEncryptionProfilesByDefault() { + // Given + var config = Config.defaultConfig(); + + // When & Then + assertTrue(config.propertyEncryptionProfiles().isEmpty()); + } + + @Test + void shouldSetEncryptionProfiles() { + // Given + var profiles = Set.of( + EnvelopePropertyEncryptionProfile.builder("profile-0", mock(), mock()) + .build(), + EnvelopePropertyEncryptionProfile.builder("profile-1", mock(), mock()) + .build()); + var config = Config.builder() + .withPropertyEncryptionProfiles(profiles.toArray(PropertyEncryptionProfile[]::new)) + .build(); + + // When + var actualProfiles = config.propertyEncryptionProfiles(); + + // Then + assertEquals(profiles, actualProfiles); + } + + @Test + void shouldRejectDuplicateEncryptionProfileNames() { + // Given + var profiles = Set.of( + EnvelopePropertyEncryptionProfile.builder("profile-0", mock(), mock()) + .build(), + EnvelopePropertyEncryptionProfile.builder("profile-1", mock(), mock()) + .build()); + + // When & Then + assertThrows(IllegalArgumentException.class, () -> Config.builder() + .withPropertyEncryptionProfiles(profiles.toArray(PropertyEncryptionProfile[]::new))); + } + + @Test + void shouldRejectNullEncryptionProfileElement() { + // Given + var profiles = new HashSet(); + profiles.add(EnvelopePropertyEncryptionProfile.builder("profile-0", mock(), mock()) + .build()); + profiles.add(null); + + // When & Then + assertThrows(NullPointerException.class, () -> Config.builder() + .withPropertyEncryptionProfiles(profiles.toArray(PropertyEncryptionProfile[]::new))); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalDriverTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalDriverTest.java index e94065345c..1bf2d86553 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalDriverTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalDriverTest.java @@ -18,6 +18,7 @@ import static java.util.concurrent.CompletableFuture.failedFuture; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -29,13 +30,20 @@ import static org.neo4j.driver.testutil.TestUtil.await; import java.util.Collections; +import java.util.Map; import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.driver.Config; import org.neo4j.driver.QueryConfig; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyEncryption; +import org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption; +import org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption; class InternalDriverTest { @Test @@ -100,13 +108,48 @@ void shouldCreateExecutableQuery() { assertEquals(QueryConfig.defaultConfig(), executableQuery.config()); } + @Test + void shouldReturnPropertyEncryption() { + // Given + var driver = newDriver(); + + // When + var propertyEncryption = driver.propertyEncryption(); + + // Then + assertNotNull(propertyEncryption); + assertInstanceOf(PropertyEncryption.class, propertyEncryption); + } + + @ParameterizedTest + @ValueSource( + classes = { + PropertyEncryption.class, + AsyncPropertyEncryption.class, + ReactivePropertyEncryption.class, + org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption.class + }) + void shouldReturnPropertyEncryption(Class propertyEncryptionClass) { + // Given + var driver = newDriver(); + + // When + var propertyEncryption = driver.propertyEncryption(propertyEncryptionClass); + + // Then + assertNotNull(propertyEncryption); + assertInstanceOf(propertyEncryptionClass, propertyEncryption); + } + private static InternalDriver newDriver(SessionFactory sessionFactory) { return new InternalDriver( BoltSecurityPlanManager.insecure(), sessionFactory, true, DEV_NULL_LOGGING, - NoopObservationProvider.getInstance()); + NoopObservationProvider.getInstance(), + Map.of(), + mock()); } private static SessionFactory sessionFactoryMock() { @@ -124,6 +167,8 @@ private static InternalDriver newDriver() { sessionFactory, true, DEV_NULL_LOGGING, - NoopObservationProvider.getInstance()); + NoopObservationProvider.getInstance(), + Map.of(), + mock()); } } diff --git a/encryption/LICENSES.txt b/encryption/LICENSES.txt new file mode 100644 index 0000000000..f8e0fd3292 --- /dev/null +++ b/encryption/LICENSES.txt @@ -0,0 +1,5 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + + diff --git a/encryption/NOTICE.txt b/encryption/NOTICE.txt new file mode 100644 index 0000000000..c3bf48c6fc --- /dev/null +++ b/encryption/NOTICE.txt @@ -0,0 +1,20 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + diff --git a/encryption/aws-kms/LICENSES.txt b/encryption/aws-kms/LICENSES.txt new file mode 100644 index 0000000000..73ffe5dc7e --- /dev/null +++ b/encryption/aws-kms/LICENSES.txt @@ -0,0 +1,292 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + +------------------------------------------------------------------------------ +Apache Software License, Version 2.0 + AWS Event Stream + AWS Java SDK :: Annotations + AWS Java SDK :: Auth + AWS Java SDK :: AWS Core + AWS Java SDK :: Checksums + AWS Java SDK :: Checksums SPI + AWS Java SDK :: Core :: Protocols :: AWS Json Protocol + AWS Java SDK :: Core :: Protocols :: Json Utils + AWS Java SDK :: Core :: Protocols :: Protocol Core + AWS Java SDK :: Endpoints SPI + AWS Java SDK :: HTTP Auth + AWS Java SDK :: HTTP Auth AWS + AWS Java SDK :: HTTP Auth Event Stream + AWS Java SDK :: HTTP Auth SPI + AWS Java SDK :: HTTP Client Interface + AWS Java SDK :: Identity SPI + AWS Java SDK :: Metrics SPI + AWS Java SDK :: Profiles + AWS Java SDK :: Regions + AWS Java SDK :: Retries + AWS Java SDK :: Retries API + AWS Java SDK :: SDK Core + AWS Java SDK :: Services :: AWS KMS + AWS Java SDK :: Third Party :: Jackson-core + AWS Java SDK :: Utilities + AWS Java SDK :: Utils Lite +------------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +------------------------------------------------------------------------------ +MIT License + SLF4J API Module +------------------------------------------------------------------------------ + +The MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +------------------------------------------------------------------------------ +MIT No Attribution License + reactive-streams +------------------------------------------------------------------------------ + +MIT No Attribution + +Copyright + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + + diff --git a/encryption/aws-kms/NOTICE.txt b/encryption/aws-kms/NOTICE.txt new file mode 100644 index 0000000000..7224d32537 --- /dev/null +++ b/encryption/aws-kms/NOTICE.txt @@ -0,0 +1,54 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + +Apache Software License, Version 2.0 + AWS Event Stream + AWS Java SDK :: Annotations + AWS Java SDK :: Auth + AWS Java SDK :: AWS Core + AWS Java SDK :: Checksums + AWS Java SDK :: Checksums SPI + AWS Java SDK :: Core :: Protocols :: AWS Json Protocol + AWS Java SDK :: Core :: Protocols :: Json Utils + AWS Java SDK :: Core :: Protocols :: Protocol Core + AWS Java SDK :: Endpoints SPI + AWS Java SDK :: HTTP Auth + AWS Java SDK :: HTTP Auth AWS + AWS Java SDK :: HTTP Auth Event Stream + AWS Java SDK :: HTTP Auth SPI + AWS Java SDK :: HTTP Client Interface + AWS Java SDK :: Identity SPI + AWS Java SDK :: Metrics SPI + AWS Java SDK :: Profiles + AWS Java SDK :: Regions + AWS Java SDK :: Retries + AWS Java SDK :: Retries API + AWS Java SDK :: SDK Core + AWS Java SDK :: Services :: AWS KMS + AWS Java SDK :: Third Party :: Jackson-core + AWS Java SDK :: Utilities + AWS Java SDK :: Utils Lite + +MIT License + SLF4J API Module + +MIT No Attribution License + reactive-streams + diff --git a/encryption/aws-kms/pom.xml b/encryption/aws-kms/pom.xml new file mode 100644 index 0000000000..f120181492 --- /dev/null +++ b/encryption/aws-kms/pom.xml @@ -0,0 +1,68 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-parent + 6.3-SNAPSHOT + ../../pom.xml + + + neo4j-java-driver-encryption-aws-kms + + Neo4j Java Driver (AWS KMS) + The Neo4j Java Driver Encryption module providing encryption using AWS KMS. + + + false + false + + + + + org.neo4j.driver + neo4j-java-driver + ${project.version} + provided + + + software.amazon.awssdk + kms + + + org.junit.jupiter + junit-jupiter + test + + + + + + + software.amazon.awssdk + bom + 2.40.4 + pom + import + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + diff --git a/encryption/aws-kms/src/main/java/org/neo4j/driver/property_encryption/aws_kms/AWSKeyEncapsulationService.java b/encryption/aws-kms/src/main/java/org/neo4j/driver/property_encryption/aws_kms/AWSKeyEncapsulationService.java new file mode 100644 index 0000000000..340e1a78ee --- /dev/null +++ b/encryption/aws-kms/src/main/java/org/neo4j/driver/property_encryption/aws_kms/AWSKeyEncapsulationService.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.aws_kms; + +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletionStage; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.KeyEncapsulationResult; +import org.neo4j.driver.property_encryption.KeyEncapsulationResults; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.kms.KmsAsyncClient; +import software.amazon.awssdk.services.kms.model.DecryptRequest; +import software.amazon.awssdk.services.kms.model.EncryptRequest; + +public final class AWSKeyEncapsulationService implements KeyEncapsulationService { + private final KmsAsyncClient kms; + private final AwsKeyEncapsulationOptions defaultOptions; + private final KeyGenerator keyGenerator; + + public AWSKeyEncapsulationService(AwsKeyEncapsulationOptions defaultOptions) throws NoSuchAlgorithmException { + this.kms = KmsAsyncClient.create(); + this.defaultOptions = Objects.requireNonNull(defaultOptions); + this.keyGenerator = KeyGenerator.getInstance("AES"); + this.keyGenerator.init(256); + } + + @Override + public CompletionStage encapsulate(KeyEncapsulationOptions options) { + var encapsulationOptions = Objects.requireNonNullElse(options, defaultOptions); + var kmsKeyId = ((AwsKeyEncapsulationOptions) encapsulationOptions).keyId(); + var key = keyGenerator.generateKey(); + var req = EncryptRequest.builder() + .keyId(kmsKeyId) + .plaintext(SdkBytes.fromByteArray(key.getEncoded())) + .build(); + return kms.encrypt(req) + .thenApply(encryptResponse -> KeyEncapsulationResults.create( + encryptResponse.ciphertextBlob().asByteArray(), encapsulationOptions.toMap(), key)); + } + + @Override + public CompletionStage decapsulate(byte[] ciphertext, Map metadata) { + var options = AwsKeyEncapsulationOptions.of(metadata); + var req = DecryptRequest.builder() + .keyId(options.keyId()) + .ciphertextBlob(SdkBytes.fromByteArray(ciphertext)) + .build(); + return kms.decrypt(req) + .thenApply(decryptResponse -> + new SecretKeySpec(decryptResponse.plaintext().asByteArray(), "AES")); + } +} diff --git a/encryption/aws-kms/src/main/java/org/neo4j/driver/property_encryption/aws_kms/AwsKeyEncapsulationOptions.java b/encryption/aws-kms/src/main/java/org/neo4j/driver/property_encryption/aws_kms/AwsKeyEncapsulationOptions.java new file mode 100644 index 0000000000..797ded51c2 --- /dev/null +++ b/encryption/aws-kms/src/main/java/org/neo4j/driver/property_encryption/aws_kms/AwsKeyEncapsulationOptions.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.aws_kms; + +import java.util.Map; +import java.util.Objects; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; + +public final class AwsKeyEncapsulationOptions implements KeyEncapsulationOptions { + public static final String KMS_KEY_ID = "kmsKeyId"; + + public static AwsKeyEncapsulationOptions of(String keyId) { + return new AwsKeyEncapsulationOptions(keyId); + } + + public static AwsKeyEncapsulationOptions of(Map metadata) { + var keyId = metadata.get(KMS_KEY_ID); + return new AwsKeyEncapsulationOptions(keyId); + } + + private final String keyId; + + private AwsKeyEncapsulationOptions(String keyId) { + this.keyId = Objects.requireNonNull(keyId); + } + + public String keyId() { + return keyId; + } + + @Override + public Map toMap() { + return Map.of(KMS_KEY_ID, keyId); + } +} diff --git a/encryption/azure-keyvault/LICENSES.txt b/encryption/azure-keyvault/LICENSES.txt new file mode 100644 index 0000000000..e783e6585e --- /dev/null +++ b/encryption/azure-keyvault/LICENSES.txt @@ -0,0 +1,302 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + +------------------------------------------------------------------------------ +Apache Software License, Version 2.0 + Core functionality for the Reactor Netty library + HTTP functionality for the Reactor Netty library + Jackson datatype: JSR310 + Jackson-annotations + Jackson-core + jackson-databind + Java Native Access + Java Native Access Platform + Netty/Buffer + Netty/Codec + Netty/Codec/Base + Netty/Codec/Compression + Netty/Codec/DNS + Netty/Codec/HTTP + Netty/Codec/HTTP2 + Netty/Codec/Marshalling + Netty/Codec/Protobuf + Netty/Codec/Socks + Netty/Common + Netty/Handler + Netty/Handler/Proxy + Netty/Resolver + Netty/Resolver/DNS + Netty/TomcatNative [BoringSSL - Static] + Netty/TomcatNative [OpenSSL - Classes] + Netty/Transport + Netty/Transport/Native/Unix/Common + Non-Blocking Reactive Foundation for the JVM +------------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +------------------------------------------------------------------------------ +MIT License + Microsoft Azure client library for Identity + Microsoft Azure client library for KeyVault Keys + Microsoft Azure Java Core Library + Microsoft Azure Java JSON Library + Microsoft Azure Java XML Library + Microsoft Azure Netty HTTP Client Library + msal4j + msal4j-persistence-extension + SLF4J API Module +------------------------------------------------------------------------------ + +The MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +------------------------------------------------------------------------------ +MIT No Attribution License + reactive-streams +------------------------------------------------------------------------------ + +MIT No Attribution + +Copyright + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + + diff --git a/encryption/azure-keyvault/NOTICE.txt b/encryption/azure-keyvault/NOTICE.txt new file mode 100644 index 0000000000..1904a567d9 --- /dev/null +++ b/encryption/azure-keyvault/NOTICE.txt @@ -0,0 +1,64 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + +Apache Software License, Version 2.0 + Core functionality for the Reactor Netty library + HTTP functionality for the Reactor Netty library + Jackson datatype: JSR310 + Jackson-annotations + Jackson-core + jackson-databind + Java Native Access + Java Native Access Platform + Netty/Buffer + Netty/Codec + Netty/Codec/Base + Netty/Codec/Compression + Netty/Codec/DNS + Netty/Codec/HTTP + Netty/Codec/HTTP2 + Netty/Codec/Marshalling + Netty/Codec/Protobuf + Netty/Codec/Socks + Netty/Common + Netty/Handler + Netty/Handler/Proxy + Netty/Resolver + Netty/Resolver/DNS + Netty/TomcatNative [BoringSSL - Static] + Netty/TomcatNative [OpenSSL - Classes] + Netty/Transport + Netty/Transport/Native/Unix/Common + Non-Blocking Reactive Foundation for the JVM + +MIT License + Microsoft Azure client library for Identity + Microsoft Azure client library for KeyVault Keys + Microsoft Azure Java Core Library + Microsoft Azure Java JSON Library + Microsoft Azure Java XML Library + Microsoft Azure Netty HTTP Client Library + msal4j + msal4j-persistence-extension + SLF4J API Module + +MIT No Attribution License + reactive-streams + diff --git a/encryption/azure-keyvault/pom.xml b/encryption/azure-keyvault/pom.xml new file mode 100644 index 0000000000..dfe0c543b9 --- /dev/null +++ b/encryption/azure-keyvault/pom.xml @@ -0,0 +1,72 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-parent + 6.3-SNAPSHOT + ../../pom.xml + + + neo4j-java-driver-encryption-azure-keyvault + + Neo4j Java Driver (Azure Key Vault) + The Neo4j Java Driver Encryption module providing encryption using Azure Key Vault. + + + false + false + + + + + org.neo4j.driver + neo4j-java-driver + ${project.version} + provided + + + com.azure + azure-security-keyvault-keys + + + com.azure + azure-identity + + + org.junit.jupiter + junit-jupiter + test + + + + + + + com.azure + azure-sdk-bom + 1.3.3 + pom + import + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + diff --git a/encryption/azure-keyvault/src/main/java/org/neo4j/driver/property_encryption/azure_keyvault/AzureEncapsulationOptions.java b/encryption/azure-keyvault/src/main/java/org/neo4j/driver/property_encryption/azure_keyvault/AzureEncapsulationOptions.java new file mode 100644 index 0000000000..a03b615a7f --- /dev/null +++ b/encryption/azure-keyvault/src/main/java/org/neo4j/driver/property_encryption/azure_keyvault/AzureEncapsulationOptions.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.azure_keyvault; + +import java.util.Map; +import java.util.Objects; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; + +public final class AzureEncapsulationOptions implements KeyEncapsulationOptions { + public static final String KEY_VAULT_KEY_ID = "keyVaultKeyId"; + + public static AzureEncapsulationOptions of(String keyId) { + return new AzureEncapsulationOptions(keyId); + } + + public static AzureEncapsulationOptions of(Map metadata) { + var keyId = metadata.get(KEY_VAULT_KEY_ID); + return new AzureEncapsulationOptions(keyId); + } + + private final String keyId; + + private AzureEncapsulationOptions(String keyId) { + this.keyId = Objects.requireNonNull(keyId); + } + + public String keyId() { + return keyId; + } + + @Override + public Map toMap() { + return Map.of(KEY_VAULT_KEY_ID, keyId); + } +} diff --git a/encryption/azure-keyvault/src/main/java/org/neo4j/driver/property_encryption/azure_keyvault/AzureKeyEncapsulationService.java b/encryption/azure-keyvault/src/main/java/org/neo4j/driver/property_encryption/azure_keyvault/AzureKeyEncapsulationService.java new file mode 100644 index 0000000000..78ac375779 --- /dev/null +++ b/encryption/azure-keyvault/src/main/java/org/neo4j/driver/property_encryption/azure_keyvault/AzureKeyEncapsulationService.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.azure_keyvault; + +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.security.keyvault.keys.cryptography.CryptographyAsyncClient; +import com.azure.security.keyvault.keys.cryptography.CryptographyClientBuilder; +import com.azure.security.keyvault.keys.cryptography.models.EncryptionAlgorithm; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletionStage; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.KeyEncapsulationResult; +import org.neo4j.driver.property_encryption.KeyEncapsulationResults; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; + +public final class AzureKeyEncapsulationService implements KeyEncapsulationService { + private final KeyGenerator keyGenerator; + private final AzureEncapsulationOptions defaultOptions; + + public AzureKeyEncapsulationService(AzureEncapsulationOptions defaultOptions) throws NoSuchAlgorithmException { + this.defaultOptions = Objects.requireNonNull(defaultOptions); + this.keyGenerator = KeyGenerator.getInstance("AES"); + this.keyGenerator.init(256); + } + + @Override + public CompletionStage encapsulate(KeyEncapsulationOptions options) { + var encapsulationOptions = Objects.requireNonNullElse(options, defaultOptions); + var kmsKeyId = ((AzureEncapsulationOptions) encapsulationOptions).keyId(); + var key = keyGenerator.generateKey(); + return forKey(kmsKeyId) + .encrypt(EncryptionAlgorithm.RSA_OAEP, key.getEncoded()) + .toFuture() + .thenApply(encryptResult -> KeyEncapsulationResults.create( + encryptResult.getCipherText(), encapsulationOptions.toMap(), key)); + } + + @Override + public CompletionStage decapsulate(byte[] ciphertext, Map metadata) { + var options = AzureEncapsulationOptions.of(metadata); + return forKey(options.keyId()) + .decrypt(EncryptionAlgorithm.RSA_OAEP, ciphertext) + .toFuture() + .thenApply(decryptResult -> new SecretKeySpec(decryptResult.getPlainText(), "AES")); + } + + private static CryptographyAsyncClient forKey(String keyVaultKeyId) { + return new CryptographyClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .keyIdentifier(keyVaultKeyId) + .buildAsyncClient(); + } +} diff --git a/encryption/google-cloud-kms/LICENSES.txt b/encryption/google-cloud-kms/LICENSES.txt new file mode 100644 index 0000000000..c777ab0548 --- /dev/null +++ b/encryption/google-cloud-kms/LICENSES.txt @@ -0,0 +1,1041 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + +------------------------------------------------------------------------------ +Apache Software License, Version 2.0 + Apache HttpClient + Apache HttpCore + AutoValue Annotations + error-prone annotations + FindBugs-jsr305 + Google Cloud KMS + Google HTTP Client Library for Java + Gson + GSON extensions to the Google HTTP Client Library for Java. + Guava InternalFutureFailureAccess and InternalFutures + Guava ListenableFuture only + Guava: Google Core Libraries for Java + io.grpc:grpc-alts + io.grpc:grpc-api + io.grpc:grpc-auth + io.grpc:grpc-context + io.grpc:grpc-core + io.grpc:grpc-grpclb + io.grpc:grpc-inprocess + io.grpc:grpc-netty-shaded + io.grpc:grpc-protobuf + io.grpc:grpc-stub + J2ObjC Annotations + JSpecify annotations + OpenCensus + org.conscrypt:conscrypt-openjdk-uber + proto-google-cloud-kms-v1 + proto-google-common-protos + proto-google-iam-v1 +------------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +------------------------------------------------------------------------------ +BSD License + API Common + GAX (Google Api eXtensions) for Java (Core) + GAX (Google Api eXtensions) for Java (gRPC) + GAX (Google Api eXtensions) for Java (HTTP JSON) + Google Auth Library for Java - Credentials + Protocol Buffers [Core] + Protocol Buffers [Util] + ThreeTen backport +------------------------------------------------------------------------------ + +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +------------------------------------------------------------------------------ +Common Development and Distribution License Version 1.1 + javax.annotation API +------------------------------------------------------------------------------ + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1 + +1. Definitions. + + 1.1. "Contributor" means each individual or entity that creates or + contributes to the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Software, prior Modifications used by a Contributor (if any), and the + Modifications made by that particular Contributor. + + 1.3. "Covered Software" means (a) the Original Software, or (b) + Modifications, or (c) the combination of files containing Original + Software with files containing Modifications, in each case including + portions thereof. + + 1.4. "Executable" means the Covered Software in any form other than + Source Code. + + 1.5. "Initial Developer" means the individual or entity that first makes + Original Software available under this License. + + 1.6. "Larger Work" means a work which combines Covered Software or + portions thereof with code not governed by the terms of this License. + + 1.7. "License" means this document. + + 1.8. "Licensable" means having the right to grant, to the maximum extent + possible, whether at the time of the initial grant or subsequently + acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means the Source Code and Executable form of any of + the following: + + A. Any file that results from an addition to, deletion from or + modification of the contents of a file containing Original Software or + previous Modifications; + + B. Any new file that contains any part of the Original Software or + previous Modification; or + + C. Any new file that is contributed or otherwise made available under + the terms of this License. + + 1.10. "Original Software" means the Source Code and Executable form of + computer software code that is originally released under this License. + + 1.11. "Patent Claims" means any patent claim(s), now owned or hereafter + acquired, including without limitation, method, process, and apparatus + claims, in any patent Licensable by grantor. + + 1.12. "Source Code" means (a) the common form of computer software code + in which modifications are made and (b) associated documentation + included in or with such code. + + 1.13. "You" (or "Your") means an individual or a legal entity exercising + rights under, and complying with all of the terms of, this License. For + legal entities, "You" includes any entity which controls, is controlled + by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + +2. License Grants. + + 2.1. The Initial Developer Grant. + + Conditioned upon Your compliance with Section 3.1 below and subject to + third party intellectual property claims, the Initial Developer hereby + grants You a world-wide, royalty-free, non-exclusive license: + + (a) under intellectual property rights (other than patent or trademark) + Licensable by Initial Developer, to use, reproduce, modify, display, + perform, sublicense and distribute the Original Software (or portions + thereof), with or without Modifications, and/or as part of a Larger + Work; and + + (b) under Patent Claims infringed by the making, using or selling of + Original Software, to make, have made, use, practice, sell, and offer + for sale, and/or otherwise dispose of the Original Software (or portions + thereof). + + (c) The licenses granted in Sections 2.1(a) and (b) are effective on the + date Initial Developer first distributes or otherwise makes the Original + Software available to a third party under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is granted: + (1) for code that You delete from the Original Software, or (2) for + infringements caused by: (i) the modification of the Original Software, + or (ii) the combination of the Original Software with other software or + devices. + + 2.2. Contributor Grant. + + Conditioned upon Your compliance with Section 3.1 below and subject to + third party intellectual property claims, each Contributor hereby grants + You a world-wide, royalty-free, non-exclusive license: + + (a) under intellectual property rights (other than patent or trademark) + Licensable by Contributor to use, reproduce, modify, display, perform, + sublicense and distribute the Modifications created by such Contributor + (or portions thereof), either on an unmodified basis, with other + Modifications, as Covered Software and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or selling of + Modifications made by that Contributor either alone and/or in + combination with its Contributor Version (or portions of such + combination), to make, use, sell, offer for sale, have made, and/or + otherwise dispose of: (1) Modifications made by that Contributor (or + portions thereof); and (2) the combination of Modifications made by that + Contributor with its Contributor Version (or portions of such + combination). + + (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on + the date Contributor first distributes or otherwise makes the + Modifications available to a third party. + + (d) Notwithstanding Section 2.2(b) above, no patent license is granted: + (1) for any code that Contributor has deleted from the Contributor + Version; (2) for infringements caused by: (i) third party modifications + of Contributor Version, or (ii) the combination of Modifications made by + that Contributor with other software (except as part of the Contributor + Version) or other devices; or (3) under Patent Claims infringed by + Covered Software in the absence of Modifications made by that + Contributor. + +3. Distribution Obligations. + + 3.1. Availability of Source Code. + + Any Covered Software that You distribute or otherwise make available in + Executable form must also be made available in Source Code form and that + Source Code form must be distributed only under the terms of this + License. You must include a copy of this License with every copy of the + Source Code form of the Covered Software You distribute or otherwise + make available. You must inform recipients of any such Covered Software + in Executable form as to how they can obtain such Covered Software in + Source Code form in a reasonable manner on or through a medium + customarily used for software exchange. + + 3.2. Modifications. + + The Modifications that You create or to which You contribute are + governed by the terms of this License. You represent that You believe + Your Modifications are Your original creation(s) and/or You have + sufficient rights to grant the rights conveyed by this License. + + 3.3. Required Notices. + + You must include a notice in each of Your Modifications that identifies + You as the Contributor of the Modification. You may not remove or alter + any copyright, patent or trademark notices contained within the Covered + Software, or any notices of licensing or any descriptive text giving + attribution to any Contributor or the Initial Developer. + + 3.4. Application of Additional Terms. + + You may not offer or impose any terms on any Covered Software in Source + Code form that alters or restricts the applicable version of this + License or the recipients' rights hereunder. You may choose to offer, + and to charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Software. However, you + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity or liability obligation is offered by + You alone, and You hereby agree to indemnify the Initial Developer and + every Contributor for any liability incurred by the Initial Developer or + such Contributor as a result of warranty, support, indemnity or + liability terms You offer. + + 3.5. Distribution of Executable Versions. + + You may distribute the Executable form of the Covered Software under the + terms of this License or under the terms of a license of Your choice, + which may contain terms different from this License, provided that You + are in compliance with the terms of this License and that the license + for the Executable form does not attempt to limit or alter the + recipient's rights in the Source Code form from the rights set forth in + this License. If You distribute the Covered Software in Executable form + under a different license, You must make it absolutely clear that any + terms which differ from this License are offered by You alone, not by + the Initial Developer or Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such terms + You offer. + + 3.6. Larger Works. + + You may create a Larger Work by combining Covered Software with other + code not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Software. + +4. Versions of the License. + + 4.1. New Versions. + + Oracle is the initial license steward and may publish revised and/or new + versions of this License from time to time. Each version will be given a + distinguishing version number. Except as provided in Section 4.3, no one + other than the license steward has the right to modify this License. + + 4.2. Effect of New Versions. + + You may always continue to use, distribute or otherwise make the Covered + Software available under the terms of the version of the License under + which You originally received the Covered Software. If the Initial + Developer includes a notice in the Original Software prohibiting it from + being distributed or otherwise made available under any subsequent + version of the License, You must distribute and make the Covered + Software available under the terms of the version of the License under + which You originally received the Covered Software. Otherwise, You may + also choose to use, distribute or otherwise make the Covered Software + available under the terms of any subsequent version of the License + published by the license steward. + + 4.3. Modified Versions. + + When You are an Initial Developer and You want to create a new license + for Your Original Software, You may create and use a modified version of + this License if You: (a) rename the license and remove any references to + the name of the license steward (except to note that the license differs + from this License); and (b) otherwise make it clear that the license + contains terms which differ from this License. + +5. DISCLAIMER OF WARRANTY. + + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED + SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY + RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME + THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS + DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO + USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER. + +6. TERMINATION. + + 6.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. Provisions + which, by their nature, must remain in effect beyond the termination of + this License shall survive. + + 6.2. If You assert a patent infringement claim (excluding declaratory + judgment actions) against Initial Developer or a Contributor (the + Initial Developer or Contributor against whom You assert such claim is + referred to as "Participant") alleging that the Participant Software + (meaning the Contributor Version where the Participant is a Contributor + or the Original Software where the Participant is the Initial Developer) + directly or indirectly infringes any patent, then any and all rights + granted directly or indirectly to You by such Participant, the Initial + Developer (if the Initial Developer is not the Participant) and all + Contributors under Sections 2.1 and/or 2.2 of this License shall, upon + 60 days notice from Participant terminate prospectively and + automatically at the expiration of such 60 day notice period, unless if + within such 60 day period You withdraw Your claim with respect to the + Participant Software against such Participant either unilaterally or + pursuant to a written agreement with Participant. + + 6.3. If You assert a patent infringement claim against Participant + alleging that the Participant Software directly or indirectly infringes + any patent where such claim is resolved (such as by license or + settlement) prior to the initiation of patent infringement litigation, + then the reasonable value of the licenses granted by such Participant + under Sections 2.1 or 2.2 shall be taken into account in determining the + amount or value of any payment or license. + + 6.4. In the event of termination under Sections 6.1 or 6.2 above, all + end user licenses that have been validly granted by You or any + distributor hereunder prior to termination (excluding licenses granted + to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED + SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY + PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF + GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL + OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION + OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION + AND LIMITATION MAY NOT APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + + The Covered Software is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" (as that term is defined at 48 C.F.R. § 252.227-7014(a)(1)) + and "commercial computer software documentation" as such terms are used + in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and + 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government + End Users acquire Covered Software with only those rights set forth + herein. This U.S. Government Rights clause is in lieu of, and + supersedes, any other FAR, DFAR, or other clause or provision that + addresses Government rights in computer software under this License. + +9. MISCELLANEOUS. + + This License represents the complete agreement concerning subject matter + hereof. If any provision of this License is held to be unenforceable, + such provision shall be reformed only to the extent necessary to make it + enforceable. This License shall be governed by the law of the + jurisdiction specified in a notice contained within the Original + Software (except to the extent applicable law, if any, provides + otherwise), excluding such jurisdiction's conflict-of-law provisions. + Any litigation relating to this License shall be subject to the + jurisdiction of the courts located in the jurisdiction and venue + specified in a notice contained within the Original Software, with the + losing party responsible for costs, including, without limitation, court + costs and reasonable attorneys' fees and expenses. The application of + the United Nations Convention on Contracts for the International Sale of + Goods is expressly excluded. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall + not apply to this License. You agree that You alone are responsible for + compliance with the United States export administration regulations (and + the export control laws and regulation of any other countries) when You + use, distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, out + of its utilization of rights under this License and You agree to work + with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION +LICENSE (CDDL) + +The code released under the CDDL shall be governed by the laws of the +State of California (excluding conflict-of-law provisions). Any +litigation relating to this License shall be subject to the jurisdiction +of the Federal Courts of the Northern District of California and the +state courts of the State of California, with venue lying in Santa Clara +County, California. + + + +------------------------------------------------------------------------------ +GNU General Public License, version 2 with the Classpath Exception + javax.annotation API +------------------------------------------------------------------------------ + +The GNU General Public License (GPL) Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, +Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to +share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free software--to +make sure the software is free for all its users. This General Public +License applies to most of the Free Software Foundation's software and +to any other program whose authors commit to using it. (Some other Free +Software Foundation software is covered by the GNU Library General +Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. +Our General Public Licenses are designed to make sure that you have the +freedom to distribute copies of free software (and charge for this +service if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone +to deny you these rights or to ask you to surrender the rights. These +restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis +or for a fee, you must give the recipients all the rights that you have. +You must make sure that they, too, receive or can get the source code. +And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + +Finally, any free program is threatened constantly by software patents. +We wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program +proprietary. To prevent this, we have made it clear that any patent must +be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and +modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a +notice placed by the copyright holder saying it may be distributed under +the terms of this General Public License. The "Program", below, refers +to any such program or work, and a "work based on the Program" means +either the Program or any derivative work under copyright law: that is +to say, a work containing the Program or a portion of it, either +verbatim or with modifications and/or translated into another language. +(Hereinafter, translation is included without limitation in the term +"modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of running +the Program is not restricted, and the output from the Program is +covered only if its contents constitute a work based on the Program +(independent of having been made by running the Program). Whether that +is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously +and appropriately publish on each copy an appropriate copyright notice +and disclaimer of warranty; keep intact all the notices that refer to +this License and to the absence of any warranty; and give any other +recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of +it, thus forming a work based on the Program, and copy and distribute +such modifications or work under the terms of Section 1 above, provided +that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole + or in part contains or is derived from the Program or any part thereof, + to be licensed as a whole at no charge to all third parties under the + terms of this License. + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use in + the most ordinary way, to print or display an announcement including an + appropriate copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may + redistribute the program under these conditions, and telling the user + how to view a copy of this License. (Exception: if the Program itself is + interactive but does not normally print such an announcement, your work + based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, and +can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based on +the Program, the distribution of the whole must be on the terms of this +License, whose permissions for other licensees extend to the entire +whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of a +storage or distribution medium does not bring the other work under the +scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections +1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 + above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to + give any third party, for a charge no more than your cost of physically + performing source distribution, a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only + for noncommercial distribution and only if you received the program in + object code or executable form with such an offer, in accord with + Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source code +means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to control +compilation and installation of the executable. However, as a special +exception, the source code distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies the +executable. + +If distribution of executable or object code is made by offering access +to copy from a designated place, then offering equivalent access to copy +the source code from the same place counts as distribution of the source +code, even though third parties are not compelled to copy the source +along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt otherwise +to copy, modify, sublicense or distribute the Program is void, and will +automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will +not have their licenses terminated so long as such parties remain in +full compliance. + +5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and all +its terms and conditions for copying, distributing or modifying the +Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further restrictions +on the recipients' exercise of the rights granted herein. You are not +responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot distribute +so as to satisfy simultaneously your obligations under this License and +any other pertinent obligations, then as a consequence you may not +distribute the Program at all. For example, if a patent license would +not permit royalty-free redistribution of the Program by all those who +receive copies directly or indirectly through you, then the only way you +could satisfy both it and this License would be to refrain entirely from +distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is implemented +by public license practices. Many people have made generous +contributions to the wide range of software distributed through that +system in reliance on consistent application of that system; it is up to +the author/donor to decide if he or she is willing to distribute +software through any other system and a licensee cannot impose that +choice. + +This section is intended to make thoroughly clear what is believed to be +a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License may +add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among countries +not thus excluded. In such case, this License incorporates the +limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a version +number of this License, you may choose any version ever published by the +Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the +author to ask for permission. For software which is copyrighted by the +Free Software Foundation, write to the Free Software Foundation; we +sometimes make exceptions for this. Our decision will be guided by the +two goals of preserving the free status of all derivatives of our free +software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH +YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR +DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM +(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF +THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR +OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + Copyright (C) + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision + comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is + free software, and you are welcome to redistribute it under certain + conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the commands +you use may be called something other than `show w' and `show c'; they +could even be mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications +with the library. If this is what you want to do, use the GNU Library +General Public License instead of this License. + +# + +"CLASSPATH" EXCEPTION TO THE GPL VERSION 2 + +Certain source files distributed by Oracle are subject to the following +clarification and special exception to the GPL Version 2, but only where +Oracle has expressly included in the particular source file's header the +words "Oracle designates this particular file as subject to the +"Classpath" exception as provided by Oracle in the License file that +accompanied this code." + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License Version 2 cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under terms +of your choice, provided that you also meet, for each linked independent +module, the terms and conditions of the license of that module. An +independent module is a module which is not derived from or based on +this library. If you modify this library, you may extend this exception +to your version of the library, but you are not obligated to do so. If +you do not wish to do so, delete this exception statement from your +version. + + + +------------------------------------------------------------------------------ +MIT License + Animal Sniffer Annotations +------------------------------------------------------------------------------ + +The MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + + +Dependencies with multiple licenses +----------------------------------- + +javax.annotation API + Common Development and Distribution License Version 1.1 + GNU General Public License, version 2 with the Classpath Exception + diff --git a/encryption/google-cloud-kms/NOTICE.txt b/encryption/google-cloud-kms/NOTICE.txt new file mode 100644 index 0000000000..197589c0db --- /dev/null +++ b/encryption/google-cloud-kms/NOTICE.txt @@ -0,0 +1,77 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + +Apache Software License, Version 2.0 + Apache HttpClient + Apache HttpCore + AutoValue Annotations + error-prone annotations + FindBugs-jsr305 + Google Cloud KMS + Google HTTP Client Library for Java + Gson + GSON extensions to the Google HTTP Client Library for Java. + Guava InternalFutureFailureAccess and InternalFutures + Guava ListenableFuture only + Guava: Google Core Libraries for Java + io.grpc:grpc-alts + io.grpc:grpc-api + io.grpc:grpc-auth + io.grpc:grpc-context + io.grpc:grpc-core + io.grpc:grpc-grpclb + io.grpc:grpc-inprocess + io.grpc:grpc-netty-shaded + io.grpc:grpc-protobuf + io.grpc:grpc-stub + J2ObjC Annotations + JSpecify annotations + OpenCensus + org.conscrypt:conscrypt-openjdk-uber + proto-google-cloud-kms-v1 + proto-google-common-protos + proto-google-iam-v1 + +BSD License + API Common + GAX (Google Api eXtensions) for Java (Core) + GAX (Google Api eXtensions) for Java (gRPC) + GAX (Google Api eXtensions) for Java (HTTP JSON) + Google Auth Library for Java - Credentials + Protocol Buffers [Core] + Protocol Buffers [Util] + ThreeTen backport + +Common Development and Distribution License Version 1.1 + javax.annotation API + +GNU General Public License, version 2 with the Classpath Exception + javax.annotation API + +MIT License + Animal Sniffer Annotations + +Dependencies with multiple licenses +----------------------------------- + +javax.annotation API + Common Development and Distribution License Version 1.1 + GNU General Public License, version 2 with the Classpath Exception + diff --git a/encryption/google-cloud-kms/pom.xml b/encryption/google-cloud-kms/pom.xml new file mode 100644 index 0000000000..4860307052 --- /dev/null +++ b/encryption/google-cloud-kms/pom.xml @@ -0,0 +1,68 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-parent + 6.3-SNAPSHOT + ../../pom.xml + + + neo4j-java-driver-encryption-google-cloud-kms + + Neo4j Java Driver (Google Cloud KMS) + The Neo4j Java Driver Encryption module providing encryption using Google Cloud KMS. + + + false + false + + + + + org.neo4j.driver + neo4j-java-driver + ${project.version} + provided + + + com.google.cloud + google-cloud-kms + + + org.junit.jupiter + junit-jupiter + test + + + + + + + com.google.cloud + libraries-bom + 26.72.0 + pom + import + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + diff --git a/encryption/google-cloud-kms/src/main/java/org/neo4j/driver/property_encryption/google_cloud_kms/CloudKmsKeyEncapsulationOptions.java b/encryption/google-cloud-kms/src/main/java/org/neo4j/driver/property_encryption/google_cloud_kms/CloudKmsKeyEncapsulationOptions.java new file mode 100644 index 0000000000..2e91fc5606 --- /dev/null +++ b/encryption/google-cloud-kms/src/main/java/org/neo4j/driver/property_encryption/google_cloud_kms/CloudKmsKeyEncapsulationOptions.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.google_cloud_kms; + +import com.google.cloud.kms.v1.CryptoKeyName; +import java.util.Map; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; + +public final class CloudKmsKeyEncapsulationOptions implements KeyEncapsulationOptions { + private static final String PROJECT = "project"; + private static final String LOCATION = "location"; + private static final String KEY_RING = "keyRing"; + private static final String CRYPTO_KEY = "cryptoKey"; + + public static CloudKmsKeyEncapsulationOptions of( + String project, String location, String keyRing, String cryptoKey) { + return new CloudKmsKeyEncapsulationOptions(project, location, keyRing, cryptoKey); + } + + public static CloudKmsKeyEncapsulationOptions of(Map metadata) { + var project = metadata.get(PROJECT); + var location = metadata.get(LOCATION); + var keyRing = metadata.get(KEY_RING); + var cryptoKey = metadata.get(CRYPTO_KEY); + return new CloudKmsKeyEncapsulationOptions(project, location, keyRing, cryptoKey); + } + + private final CryptoKeyName keyName; + + private CloudKmsKeyEncapsulationOptions(String project, String location, String keyRing, String cryptoKey) { + this.keyName = CryptoKeyName.of(project, location, keyRing, cryptoKey); + } + + public CryptoKeyName keyName() { + return keyName; + } + + @Override + public Map toMap() { + return Map.of( + PROJECT, + keyName.getProject(), + LOCATION, + keyName().getLocation(), + KEY_RING, + keyName().getKeyRing(), + CRYPTO_KEY, + keyName().getCryptoKey()); + } +} diff --git a/encryption/google-cloud-kms/src/main/java/org/neo4j/driver/property_encryption/google_cloud_kms/GoogleCloudKeyEncapsulationService.java b/encryption/google-cloud-kms/src/main/java/org/neo4j/driver/property_encryption/google_cloud_kms/GoogleCloudKeyEncapsulationService.java new file mode 100644 index 0000000000..78c799c8ac --- /dev/null +++ b/encryption/google-cloud-kms/src/main/java/org/neo4j/driver/property_encryption/google_cloud_kms/GoogleCloudKeyEncapsulationService.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.google_cloud_kms; + +import com.google.api.core.ApiFuture; +import com.google.cloud.kms.v1.DecryptRequest; +import com.google.cloud.kms.v1.EncryptRequest; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.KeyEncapsulationResult; +import org.neo4j.driver.property_encryption.KeyEncapsulationResults; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; + +public final class GoogleCloudKeyEncapsulationService implements KeyEncapsulationService { + private final KeyManagementServiceClient keyManagementServiceClient; + private final CloudKmsKeyEncapsulationOptions defaultOptions; + private final KeyGenerator keyGenerator; + + public GoogleCloudKeyEncapsulationService(CloudKmsKeyEncapsulationOptions defaultOptions) + throws IOException, NoSuchAlgorithmException { + this.keyManagementServiceClient = KeyManagementServiceClient.create(); + this.defaultOptions = Objects.requireNonNull(defaultOptions); + this.keyGenerator = KeyGenerator.getInstance("AES"); + this.keyGenerator.init(256); + } + + @Override + public CompletionStage encapsulate(KeyEncapsulationOptions options) { + var encapsulationOptions = Objects.requireNonNullElse(options, defaultOptions); + if (encapsulationOptions instanceof CloudKmsKeyEncapsulationOptions cloudOptions) { + var key = keyGenerator.generateKey(); + var req = EncryptRequest.newBuilder() + .setName(cloudOptions.keyName().toString()) + .setPlaintext(ByteString.copyFrom(key.getEncoded())) + .build(); + return toCompletionStage( + keyManagementServiceClient.encryptCallable().futureCall(req)) + .thenApply(resp -> KeyEncapsulationResults.create( + resp.getCiphertext().toByteArray(), encapsulationOptions.toMap(), key)); + } else { + return CompletableFuture.failedStage(new ClientException("Unsupported options")); + } + } + + @Override + public CompletionStage decapsulate(byte[] encapsulation, Map metadata) { + if (metadata == null) { + return CompletableFuture.failedStage(new ClientException("Missing options")); + } + var cloudOptions = CloudKmsKeyEncapsulationOptions.of(metadata); + var keyName = cloudOptions.keyName(); + var request = DecryptRequest.newBuilder() + .setName(keyName.toString()) + .setCiphertext(ByteString.copyFrom(encapsulation)) + .build(); + + return toCompletionStage(keyManagementServiceClient.decryptCallable().futureCall(request)) + .thenApply(resp -> new SecretKeySpec(resp.getPlaintext().toByteArray(), "AES")); + } + + private record EnvelopeCiphertext(byte[] encryptedDek, byte[] iv, byte[] ciphertext, byte[] tag) {} + + private static CompletionStage toCompletionStage(ApiFuture apiFuture) { + var cf = new CompletableFuture(); + apiFuture.addListener( + () -> { + try { + cf.complete(apiFuture.get()); + } catch (Exception e) { + cf.completeExceptionally(e); + } + }, + MoreExecutors.directExecutor()); + return cf; + } +} diff --git a/encryption/kyber/LICENSES.txt b/encryption/kyber/LICENSES.txt new file mode 100644 index 0000000000..4d53dd227d --- /dev/null +++ b/encryption/kyber/LICENSES.txt @@ -0,0 +1,37 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + +------------------------------------------------------------------------------ +Bouncy Castle License + Bouncy Castle Provider +------------------------------------------------------------------------------ + +Please note: our license is an adaptation of the MIT X11 License and should be +read as such. + +LICENSE + +Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle +(http://www.bouncycastle.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + + diff --git a/encryption/kyber/NOTICE.txt b/encryption/kyber/NOTICE.txt new file mode 100644 index 0000000000..7a8f5432cf --- /dev/null +++ b/encryption/kyber/NOTICE.txt @@ -0,0 +1,23 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://neo4j.com] + +This file is part of Neo4j. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + +Bouncy Castle License + Bouncy Castle Provider + diff --git a/encryption/kyber/pom.xml b/encryption/kyber/pom.xml new file mode 100644 index 0000000000..1cb6214efa --- /dev/null +++ b/encryption/kyber/pom.xml @@ -0,0 +1,58 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-parent + 6.3-SNAPSHOT + ../../pom.xml + + + neo4j-java-driver-encryption-kyber + + Neo4j Java Driver (Kyber) + The Neo4j Java Driver Encryption module providing encryption using kyber key. + + + false + false + + + + + org.neo4j.driver + neo4j-java-driver + ${project.version} + provided + + + org.bouncycastle + bcprov-jdk18on + 1.83 + compile + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + diff --git a/encryption/kyber/src/main/java/org/neo4j/driver/property_encryption/kyber/KyberEncapsulationService.java b/encryption/kyber/src/main/java/org/neo4j/driver/property_encryption/kyber/KyberEncapsulationService.java new file mode 100644 index 0000000000..8ff3a32050 --- /dev/null +++ b/encryption/kyber/src/main/java/org/neo4j/driver/property_encryption/kyber/KyberEncapsulationService.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.property_encryption.kyber; + +import java.security.SecureRandom; +import java.security.Security; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.generators.HKDFBytesGenerator; +import org.bouncycastle.crypto.params.HKDFParameters; +import org.bouncycastle.jcajce.SecretKeyWithEncapsulation; +import org.bouncycastle.jcajce.spec.KEMExtractSpec; +import org.bouncycastle.jcajce.spec.KEMGenerateSpec; +import org.bouncycastle.pqc.jcajce.interfaces.KyberPrivateKey; +import org.bouncycastle.pqc.jcajce.interfaces.KyberPublicKey; +import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.KeyEncapsulationResult; +import org.neo4j.driver.property_encryption.KeyEncapsulationResults; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; + +public final class KyberEncapsulationService implements KeyEncapsulationService { + + static { + Security.addProvider(new BouncyCastlePQCProvider()); + } + + private static final int AES_256_KEY_SIZE = 32; + + private final KyberPublicKey publicKey; + private final KyberPrivateKey privateKey; + private final SecureRandom secureRandom; + private final byte[] info; + + public KyberEncapsulationService(KyberPublicKey publicKey, KyberPrivateKey privateKey) { + if (publicKey == null || privateKey == null) { + throw new IllegalArgumentException("Both public and private keys are required"); + } + this.publicKey = publicKey; + this.privateKey = privateKey; + this.secureRandom = new SecureRandom(); + this.info = "aes-256-key".getBytes(); + } + + @Override + public CompletionStage encapsulate(KeyEncapsulationOptions options) { + return CompletableFuture.supplyAsync(() -> { + try { + // 🔹 Create generator for Kyber KEM + javax.crypto.KeyGenerator kg = javax.crypto.KeyGenerator.getInstance("Kyber", "BCPQC"); + + // 🔹 Build generation spec (recipient public key + info) + KEMGenerateSpec genSpec = new KEMGenerateSpec(publicKey, "AES", AES_256_KEY_SIZE * 8); + + // 🔹 Initialize generator with spec + kg.init(genSpec); + + // 🔹 Generate the key (returns SecretKeyWithEncapsulation) + SecretKeyWithEncapsulation skwe = (SecretKeyWithEncapsulation) kg.generateKey(); + + // 🔹 Derive AES key from shared secret + byte[] shared = skwe.getEncoded(); + SecretKey aes = deriveAesKey(shared); + zeroize(shared); + + // 🔹 Return encapsulation bytes and AES key + byte[] encapsulation = skwe.getEncapsulation(); + return KeyEncapsulationResults.create(encapsulation, Map.of(), aes); + } catch (Exception e) { + throw new ClientException("Failed to encapsulate with Kyber", e); + } + }); + } + + @Override + public CompletionStage decapsulate(byte[] encapsulation, Map metadata) { + return CompletableFuture.supplyAsync(() -> { + try { + // 🔹 Create generator for Kyber KEM + javax.crypto.KeyGenerator kg = javax.crypto.KeyGenerator.getInstance("Kyber", "BCPQC"); + + // 🔹 Build extraction spec (private key + encapsulation + info) + KEMExtractSpec extSpec = new KEMExtractSpec(privateKey, encapsulation, "AES"); + + // 🔹 Initialize generator with extraction spec + kg.init(extSpec); + + // 🔹 Generate the key (returns SecretKeyWithEncapsulation) + SecretKeyWithEncapsulation skwe = (SecretKeyWithEncapsulation) kg.generateKey(); + + // 🔹 Derive AES key from shared secret + byte[] shared = skwe.getEncoded(); + SecretKey aes = deriveAesKey(shared); + zeroize(shared); + return new SecretKeySpec(aes.getEncoded(), "AES"); + } catch (Exception e) { + throw new RuntimeException("Failed to decapsulate Kyber key", e); + } + }); + } + + private SecretKey deriveAesKey(byte[] secret) { + byte[] keyBytes = hkdfSha256(secret, info, AES_256_KEY_SIZE); + return new SecretKeySpec(keyBytes, "AES"); + } + + private static byte[] hkdfSha256(byte[] ikm, byte[] info, int length) { + HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest()); + hkdf.init(new HKDFParameters(ikm, null, info)); + byte[] out = new byte[length]; + hkdf.generateBytes(out, 0, out.length); + return out; + } + + private static void zeroize(byte[] data) { + if (data != null) Arrays.fill(data, (byte) 0); + } +} diff --git a/encryption/pom.xml b/encryption/pom.xml new file mode 100644 index 0000000000..31fdc8165a --- /dev/null +++ b/encryption/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-parent + 6.3-SNAPSHOT + + + neo4j-java-driver-encryption + + pom + Neo4j Java Driver (Encryption) + Parent project for encryption implementations. + + + google-cloud-kms + aws-kms + azure-keyvault + kyber + + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + diff --git a/examples/LICENSES.txt b/examples/LICENSES.txt index 0a33b0b1a2..efb0343a0f 100644 --- a/examples/LICENSES.txt +++ b/examples/LICENSES.txt @@ -8,6 +8,7 @@ Apache Software License, Version 2.0 Neo4j Bolt Connection (Pooled Source impl) Neo4j Bolt Connection (Provider SPI) Neo4j Bolt Connection (Routed Source impl) + Neo4j Bolt Connection Codec Netty/Buffer Netty/Codec/Base Netty/Common diff --git a/examples/NOTICE.txt b/examples/NOTICE.txt index fb63e48755..992ed8c53c 100644 --- a/examples/NOTICE.txt +++ b/examples/NOTICE.txt @@ -23,6 +23,7 @@ Apache Software License, Version 2.0 Neo4j Bolt Connection (Pooled Source impl) Neo4j Bolt Connection (Provider SPI) Neo4j Bolt Connection (Routed Source impl) + Neo4j Bolt Connection Codec Netty/Buffer Netty/Codec/Base Netty/Common diff --git a/examples/pom.xml b/examples/pom.xml index 5c4ff53e2b..b9eacdc97d 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-parent - 6.2-SNAPSHOT + 6.3-SNAPSHOT org.neo4j.doc.driver diff --git a/observation/metrics/pom.xml b/observation/metrics/pom.xml index 8b32092841..b039be637b 100644 --- a/observation/metrics/pom.xml +++ b/observation/metrics/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-parent - 6.2-SNAPSHOT + 6.3-SNAPSHOT ../../pom.xml diff --git a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/DriverMetricsObservationProvider.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/DriverMetricsObservationProvider.java index b22b369a8e..c72e31d97a 100644 --- a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/DriverMetricsObservationProvider.java +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/DriverMetricsObservationProvider.java @@ -35,6 +35,7 @@ import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.observation.metrics.Metrics; import org.neo4j.driver.observation.metrics.MetricsObservationProvider; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; import org.neo4j.driver.types.MapAccessor; public final class DriverMetricsObservationProvider implements MetricsObservationProvider, DriverObservationProvider { @@ -121,6 +122,36 @@ public Observation resultRecords(Class resultType) { return NoopObservation.getInstance(); } + @Override + public Observation encryptToBytes(Class propertyEncryptionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation decrypt(Class propertyEncryptionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation createEncapsulatedKey(Class encapsulatedKeyManagerType, String alias) { + return NoopObservation.getInstance(); + } + + @Override + public Observation findEncapsulatedKeyByAlias(Class encapsulatedKeyManagerType, String alias) { + return NoopObservation.getInstance(); + } + + @Override + public Observation updateEncapsulatedKeyAlias(Class encapsulatedKeyManagerType, String id, String alias) { + return NoopObservation.getInstance(); + } + + @Override + public Observation deleteEncapsulatedKey(Class encapsulatedKeyManagerType, String id) { + return NoopObservation.getInstance(); + } + @Override public Observation connectionPoolCreate(String id, URI uri, int maxSize) { return new PoolCreateObservation(metrics, id); diff --git a/observation/micrometer/pom.xml b/observation/micrometer/pom.xml index c063ae0197..94754909f3 100644 --- a/observation/micrometer/pom.xml +++ b/observation/micrometer/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-parent - 6.2-SNAPSHOT + 6.3-SNAPSHOT ../../pom.xml diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/CreateEncapsulatedKeyContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/CreateEncapsulatedKeyContext.java new file mode 100644 index 0000000000..5ddf10fe5a --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/CreateEncapsulatedKeyContext.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.Objects; +import java.util.Optional; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class CreateEncapsulatedKeyContext extends Observation.Context { + private final Class encapsulatedKeyManagerType; + private final String alias; + + public CreateEncapsulatedKeyContext(Class encapsulatedKeyManagerType, String alias) { + this.encapsulatedKeyManagerType = Objects.requireNonNull(encapsulatedKeyManagerType); + this.alias = alias; + } + + public Class encapsulatedKeyManagerType() { + return encapsulatedKeyManagerType; + } + + public Optional alias() { + return Optional.ofNullable(alias); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/CreateEncapsulatedKeyConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/CreateEncapsulatedKeyConvention.java new file mode 100644 index 0000000000..961748ef2b --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/CreateEncapsulatedKeyConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public interface CreateEncapsulatedKeyConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof CreateEncapsulatedKeyContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DecryptContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DecryptContext.java new file mode 100644 index 0000000000..93418c1256 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DecryptContext.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.Objects; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class DecryptContext extends Observation.Context { + private final Class propertyEncryptionType; + + public DecryptContext(Class propertyEncryptionType) { + this.propertyEncryptionType = Objects.requireNonNull(propertyEncryptionType); + } + + public Class propertyEncryptionType() { + return propertyEncryptionType; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DecryptConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DecryptConvention.java new file mode 100644 index 0000000000..78c0a15fde --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DecryptConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public interface DecryptConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof DecryptContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultCreateEncapsulatedKeyConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultCreateEncapsulatedKeyConvention.java new file mode 100644 index 0000000000..bf640ecfc1 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultCreateEncapsulatedKeyConvention.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class DefaultCreateEncapsulatedKeyConvention implements CreateEncapsulatedKeyConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.CreateEncapsulatedKeyLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultCreateEncapsulatedKeyConvention INSTANCE = new DefaultCreateEncapsulatedKeyConvention(); + + public DefaultCreateEncapsulatedKeyConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.property.encryption.create.encapsulated.key.duration"; + } + + @Override + public String getContextualName(CreateEncapsulatedKeyContext context) { + return "create encapsulated key"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(CreateEncapsulatedKeyContext context) { + return KeyValues.of(DB_SYSTEM_NAME, encapsulatedKeyManagerType(context)); + } + + @Override + public KeyValues getHighCardinalityKeyValues(CreateEncapsulatedKeyContext context) { + return KeyValues.of(alias(context)); + } + + private KeyValue encapsulatedKeyManagerType(CreateEncapsulatedKeyContext context) { + return Neo4jDriverDocumentation.CreateEncapsulatedKeyLowCardinalityKeyNames.ENCAPSULATED_KEY_MANAGER_TYPE + .withValue(context.encapsulatedKeyManagerType().getSimpleName()); + } + + private KeyValue alias(CreateEncapsulatedKeyContext context) { + // todo deal with none value + return Neo4jDriverDocumentation.CreateEncapsulatedKeyHighCardinalityKeyNames.KEY_ALIAS.withValue( + context.alias().orElse(KeyValue.NONE_VALUE)); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultDecryptConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultDecryptConvention.java new file mode 100644 index 0000000000..6754e741e3 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultDecryptConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class DefaultDecryptConvention implements DecryptConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.DecryptLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultDecryptConvention INSTANCE = new DefaultDecryptConvention(); + + public DefaultDecryptConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.property.encryption.decrypt.duration"; + } + + @Override + public String getContextualName(DecryptContext context) { + return "decrypt"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(DecryptContext context) { + return KeyValues.of(DB_SYSTEM_NAME, propertyEncryptionType(context)); + } + + private KeyValue propertyEncryptionType(DecryptContext context) { + return Neo4jDriverDocumentation.DecryptLowCardinalityKeyNames.PROPERTY_ENCRYPTION_TYPE.withValue( + context.propertyEncryptionType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultDeleteEncapsulatedKeyConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultDeleteEncapsulatedKeyConvention.java new file mode 100644 index 0000000000..bdea5bfe67 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultDeleteEncapsulatedKeyConvention.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; + +public class DefaultDeleteEncapsulatedKeyConvention implements DeleteEncapsulatedKeyConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.DeleteEncapsulatedKeyLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultDeleteEncapsulatedKeyConvention INSTANCE = new DefaultDeleteEncapsulatedKeyConvention(); + + public DefaultDeleteEncapsulatedKeyConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.property.encryption.delete.encapsulated.key.duration"; + } + + @Override + public String getContextualName(DeleteEncapsulatedKeyContext context) { + return "delete encapsulated key"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(DeleteEncapsulatedKeyContext context) { + return KeyValues.of(DB_SYSTEM_NAME, encapsulatedKeyManagerType(context)); + } + + @Override + public KeyValues getHighCardinalityKeyValues(DeleteEncapsulatedKeyContext context) { + return KeyValues.of(id(context)); + } + + private KeyValue encapsulatedKeyManagerType(DeleteEncapsulatedKeyContext context) { + return Neo4jDriverDocumentation.DeleteEncapsulatedKeyLowCardinalityKeyNames.ENCAPSULATED_KEY_MANAGER_TYPE + .withValue(context.encapsulatedKeyManagerType().getSimpleName()); + } + + private KeyValue id(DeleteEncapsulatedKeyContext context) { + return Neo4jDriverDocumentation.DeleteEncapsulatedKeyHighCardinalityKeyNames.KEY_ID.withValue(context.id()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultEncryptToBytesConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultEncryptToBytesConvention.java new file mode 100644 index 0000000000..482cbc8af8 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultEncryptToBytesConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class DefaultEncryptToBytesConvention implements EncryptToBytesConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.EncryptToBytesLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultEncryptToBytesConvention INSTANCE = new DefaultEncryptToBytesConvention(); + + public DefaultEncryptToBytesConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.property.encryption.encrypt.to.bytes.duration"; + } + + @Override + public String getContextualName(EncryptToBytesContext context) { + return "encrypt to bytes"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(EncryptToBytesContext context) { + return KeyValues.of(DB_SYSTEM_NAME, propertyEncryptionType(context)); + } + + private KeyValue propertyEncryptionType(EncryptToBytesContext context) { + return Neo4jDriverDocumentation.EncryptToBytesLowCardinalityKeyNames.PROPERTY_ENCRYPTION_TYPE.withValue( + context.propertyEncryptionType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultFindEncapsulatedKeyByAliasConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultFindEncapsulatedKeyByAliasConvention.java new file mode 100644 index 0000000000..56317f089b --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultFindEncapsulatedKeyByAliasConvention.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class DefaultFindEncapsulatedKeyByAliasConvention implements FindEncapsulatedKeyByAliasConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.FindEncapsulatedKeyByAliasLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultFindEncapsulatedKeyByAliasConvention INSTANCE = + new DefaultFindEncapsulatedKeyByAliasConvention(); + + public DefaultFindEncapsulatedKeyByAliasConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.property.encryption.find.encapsulated.key.by.alias.duration"; + } + + @Override + public String getContextualName(FindEncapsulatedKeyByAliasContext context) { + return "find encapsulated key by alias"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(FindEncapsulatedKeyByAliasContext context) { + return KeyValues.of(DB_SYSTEM_NAME, encapsulatedKeyManagerType(context)); + } + + @Override + public KeyValues getHighCardinalityKeyValues(FindEncapsulatedKeyByAliasContext context) { + return KeyValues.of(alias(context)); + } + + private KeyValue encapsulatedKeyManagerType(FindEncapsulatedKeyByAliasContext context) { + return Neo4jDriverDocumentation.FindEncapsulatedKeyByAliasLowCardinalityKeyNames.ENCAPSULATED_KEY_MANAGER_TYPE + .withValue(context.encapsulatedKeyManagerType().getSimpleName()); + } + + private KeyValue alias(FindEncapsulatedKeyByAliasContext context) { + return Neo4jDriverDocumentation.FindEncapsulatedKeyByAliasHighCardinalityKeyNames.KEY_ALIAS.withValue( + context.alias()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultUpdateEncapsulatedKeyAliasConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultUpdateEncapsulatedKeyAliasConvention.java new file mode 100644 index 0000000000..be914c5c44 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultUpdateEncapsulatedKeyAliasConvention.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class DefaultUpdateEncapsulatedKeyAliasConvention implements UpdateEncapsulatedKeyAliasConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.UpdateEncapsulatedKeyAliasLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultUpdateEncapsulatedKeyAliasConvention INSTANCE = + new DefaultUpdateEncapsulatedKeyAliasConvention(); + + public DefaultUpdateEncapsulatedKeyAliasConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.property.encryption.update.encapsulated.key.alias.duration"; + } + + @Override + public String getContextualName(UpdateEncapsulatedKeyAliasContext context) { + return "update encapsulated key alias"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(UpdateEncapsulatedKeyAliasContext context) { + return KeyValues.of(DB_SYSTEM_NAME, encapsulatedKeyManagerType(context)); + } + + @Override + public KeyValues getHighCardinalityKeyValues(UpdateEncapsulatedKeyAliasContext context) { + return KeyValues.of(id(context), alias(context)); + } + + private KeyValue encapsulatedKeyManagerType(UpdateEncapsulatedKeyAliasContext context) { + return Neo4jDriverDocumentation.UpdateEncapsulatedKeyAliasLowCardinalityKeyNames.ENCAPSULATED_KEY_MANAGER_TYPE + .withValue(context.encapsulatedKeyManagerType().getSimpleName()); + } + + private KeyValue id(UpdateEncapsulatedKeyAliasContext context) { + return Neo4jDriverDocumentation.UpdateEncapsulatedKeyAliasHighCardinalityKeyNames.KEY_ID.withValue( + context.id()); + } + + private KeyValue alias(UpdateEncapsulatedKeyAliasContext context) { + return Neo4jDriverDocumentation.UpdateEncapsulatedKeyAliasHighCardinalityKeyNames.KEY_ALIAS.withValue( + context.alias().orElse(KeyValue.NONE_VALUE)); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DeleteEncapsulatedKeyContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DeleteEncapsulatedKeyContext.java new file mode 100644 index 0000000000..890658492c --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DeleteEncapsulatedKeyContext.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.Objects; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class DeleteEncapsulatedKeyContext extends Observation.Context { + private final Class encapsulatedKeyManagerType; + private final String id; + + public DeleteEncapsulatedKeyContext(Class encapsulatedKeyManagerType, String id) { + this.encapsulatedKeyManagerType = Objects.requireNonNull(encapsulatedKeyManagerType); + this.id = Objects.requireNonNull(id); + } + + public Class encapsulatedKeyManagerType() { + return encapsulatedKeyManagerType; + } + + public String id() { + return id; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DeleteEncapsulatedKeyConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DeleteEncapsulatedKeyConvention.java new file mode 100644 index 0000000000..1b2d30363c --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DeleteEncapsulatedKeyConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +interface DeleteEncapsulatedKeyConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof DeleteEncapsulatedKeyContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DriverMicrometerObservationProvider.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DriverMicrometerObservationProvider.java index cb338241f8..132909bcca 100644 --- a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DriverMicrometerObservationProvider.java +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DriverMicrometerObservationProvider.java @@ -32,6 +32,7 @@ import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.internal.observation.HttpExchangeObservation; import org.neo4j.driver.internal.observation.Observation; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; import org.neo4j.driver.types.MapAccessor; final class DriverMicrometerObservationProvider implements MicrometerObservationProvider, DriverObservationProvider { @@ -128,6 +129,44 @@ public Observation resultRecords(Class resultType) { return from(DefaultResultRecordsConvention.INSTANCE, () -> new ResultRecordsContext(resultType)); } + @Override + public Observation encryptToBytes(Class propertyEncryptionType) { + return from(DefaultEncryptToBytesConvention.INSTANCE, () -> new EncryptToBytesContext(propertyEncryptionType)); + } + + @Override + public Observation decrypt(Class propertyEncryptionType) { + return from(DefaultDecryptConvention.INSTANCE, () -> new DecryptContext(propertyEncryptionType)); + } + + @Override + public Observation createEncapsulatedKey(Class encapsulatedKeyManagerType, String alias) { + return from( + DefaultCreateEncapsulatedKeyConvention.INSTANCE, + () -> new CreateEncapsulatedKeyContext(encapsulatedKeyManagerType, alias)); + } + + @Override + public Observation findEncapsulatedKeyByAlias(Class encapsulatedKeyManagerType, String alias) { + return from( + DefaultFindEncapsulatedKeyByAliasConvention.INSTANCE, + () -> new FindEncapsulatedKeyByAliasContext(encapsulatedKeyManagerType, alias)); + } + + @Override + public Observation updateEncapsulatedKeyAlias(Class encapsulatedKeyManagerType, String id, String alias) { + return from( + DefaultUpdateEncapsulatedKeyAliasConvention.INSTANCE, + () -> new UpdateEncapsulatedKeyAliasContext(encapsulatedKeyManagerType, id, alias)); + } + + @Override + public Observation deleteEncapsulatedKey(Class encapsulatedKeyManagerType, String id) { + return from( + DefaultDeleteEncapsulatedKeyConvention.INSTANCE, + () -> new DeleteEncapsulatedKeyContext(encapsulatedKeyManagerType, id)); + } + @Override public Observation connectionPoolCreate(String id, URI uri, int maxSize) { return from( diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/EncryptToBytesContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/EncryptToBytesContext.java new file mode 100644 index 0000000000..ca39947217 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/EncryptToBytesContext.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.Objects; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class EncryptToBytesContext extends Observation.Context { + private final Class propertyEncryptionType; + + public EncryptToBytesContext(Class propertyEncryptionType) { + this.propertyEncryptionType = Objects.requireNonNull(propertyEncryptionType); + } + + public Class propertyEncryptionType() { + return propertyEncryptionType; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/EncryptToBytesConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/EncryptToBytesConvention.java new file mode 100644 index 0000000000..9564126f29 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/EncryptToBytesConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public interface EncryptToBytesConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof EncryptToBytesContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/FindEncapsulatedKeyByAliasContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/FindEncapsulatedKeyByAliasContext.java new file mode 100644 index 0000000000..08f00ef8f9 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/FindEncapsulatedKeyByAliasContext.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.Objects; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class FindEncapsulatedKeyByAliasContext extends Observation.Context { + private final Class encapsulatedKeyManagerType; + private final String alias; + + public FindEncapsulatedKeyByAliasContext(Class encapsulatedKeyManagerType, String alias) { + this.encapsulatedKeyManagerType = Objects.requireNonNull(encapsulatedKeyManagerType); + this.alias = Objects.requireNonNull(alias); + } + + public Class encapsulatedKeyManagerType() { + return encapsulatedKeyManagerType; + } + + public String alias() { + return alias; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/FindEncapsulatedKeyByAliasConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/FindEncapsulatedKeyByAliasConvention.java new file mode 100644 index 0000000000..327c2ebee2 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/FindEncapsulatedKeyByAliasConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public interface FindEncapsulatedKeyByAliasConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof FindEncapsulatedKeyByAliasContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/Neo4jDriverDocumentation.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/Neo4jDriverDocumentation.java index 2ee931d616..6cd5d3931e 100644 --- a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/Neo4jDriverDocumentation.java +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/Neo4jDriverDocumentation.java @@ -25,6 +25,11 @@ import org.neo4j.driver.Session; import org.neo4j.driver.Transaction; import org.neo4j.driver.TransactionCallback; +import org.neo4j.driver.property_encryption.EncapsulatedKeyManager; +import org.neo4j.driver.property_encryption.KeyEncapsulationOptions; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.PropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; import org.neo4j.driver.reactivestreams.ReactiveResult; enum Neo4jDriverDocumentation implements ObservationDocumentation { @@ -274,6 +279,123 @@ public KeyName[] getLowCardinalityKeyNames() { return ResultConsumeLowCardinalityKeyNames.values(); } }, + /** + * Observes {@link PropertyEncryption#encryptToBytes(PropertyEncryptionRequest)} execution. + *

+ * This also applies to the alternative property encryption types. + */ + PROPERTY_ENCRYPTION_ENCRYPT_TO_BYTES { + @Override + public Class> getDefaultConvention() { + return DefaultEncryptToBytesConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return EncryptToBytesLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link PropertyEncryption#decrypt(PropertyDecryptionRequest)} execution. + *

+ * This also applies to the alternative property encryption types. + */ + PROPERTY_ENCRYPTION_DECRYPT { + @Override + public Class> getDefaultConvention() { + return DefaultDecryptConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return DecryptLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link EncapsulatedKeyManager#create(String, KeyEncapsulationOptions)} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative encapsulated key + * manager types. + */ + CREATE_ENCAPSULATED_KEY { + @Override + public Class> getDefaultConvention() { + return DefaultCreateEncapsulatedKeyConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return CreateEncapsulatedKeyLowCardinalityKeyNames.values(); + } + + @Override + public KeyName[] getHighCardinalityKeyNames() { + return CreateEncapsulatedKeyHighCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link EncapsulatedKeyManager#findByAlias(String)} execution. + *

+ * This also applies to the alternative encapsulated key manager types. + */ + FIND_ENCAPSULATED_KEY { + @Override + public Class> getDefaultConvention() { + return DefaultFindEncapsulatedKeyByAliasConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return FindEncapsulatedKeyByAliasLowCardinalityKeyNames.values(); + } + + @Override + public KeyName[] getHighCardinalityKeyNames() { + return FindEncapsulatedKeyByAliasHighCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link EncapsulatedKeyManager#updateAliasById(String, String)} execution. + *

+ * This also applies to the alternative encapsulated key manager types. + */ + UPDATE_ENCAPSULATED_KEY_ALIAS { + @Override + public Class> getDefaultConvention() { + return DefaultUpdateEncapsulatedKeyAliasConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return UpdateEncapsulatedKeyAliasLowCardinalityKeyNames.values(); + } + + @Override + public KeyName[] getHighCardinalityKeyNames() { + return UpdateEncapsulatedKeyAliasHighCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link EncapsulatedKeyManager#findByAlias(String)} execution. + *

+ * This also applies to the alternative encapsulated key manager types. + */ + DELETE_ENCAPSULATED_KEY { + @Override + public Class> getDefaultConvention() { + return DefaultDeleteEncapsulatedKeyConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return DeleteEncapsulatedKeyLowCardinalityKeyNames.values(); + } + + @Override + public KeyName[] getHighCardinalityKeyNames() { + return DeleteEncapsulatedKeyHighCardinalityKeyNames.values(); + } + }, /** * Observes a new connection pool creation. */ @@ -790,6 +912,204 @@ public String asString() { } } + enum EncryptToBytesLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The property encryption type. + */ + PROPERTY_ENCRYPTION_TYPE { + @Override + public String asString() { + return "neo4j.property.encryption.type"; + } + } + } + + enum DecryptLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The property encryption type. + */ + PROPERTY_ENCRYPTION_TYPE { + @Override + public String asString() { + return "neo4j.property.encryption.type"; + } + } + } + + enum CreateEncapsulatedKeyLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The encapsulated key manager type. + */ + ENCAPSULATED_KEY_MANAGER_TYPE { + @Override + public String asString() { + return "neo4j.property.encryption.encapsulated.key.manager.type"; + } + } + } + + enum CreateEncapsulatedKeyHighCardinalityKeyNames implements KeyName { + /** + * The key alias if available. + */ + KEY_ALIAS { + @Override + public String asString() { + return "neo4j.property.encryption.encapsulated.key.alias"; + } + + @Override + public boolean isRequired() { + return false; + } + } + } + + enum FindEncapsulatedKeyByAliasLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The encapsulated key manager type. + */ + ENCAPSULATED_KEY_MANAGER_TYPE { + @Override + public String asString() { + return "neo4j.property.encryption.encapsulated.key.manager.type"; + } + } + } + + enum FindEncapsulatedKeyByAliasHighCardinalityKeyNames implements KeyName { + /** + * The key alias if available. + */ + KEY_ALIAS { + @Override + public String asString() { + return "neo4j.property.encryption.encapsulated.key.alias"; + } + + @Override + public boolean isRequired() { + return false; + } + } + } + + enum DeleteEncapsulatedKeyLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The encapsulated key manager type. + */ + ENCAPSULATED_KEY_MANAGER_TYPE { + @Override + public String asString() { + return "neo4j.property.encryption.encapsulated.key.manager.type"; + } + } + } + + enum DeleteEncapsulatedKeyHighCardinalityKeyNames implements KeyName { + /** + * The key id. + */ + KEY_ID { + @Override + public String asString() { + return "neo4j.property.encryption.encapsulated.key.id"; + } + } + } + + enum UpdateEncapsulatedKeyAliasLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The encapsulated key manager type. + */ + ENCAPSULATED_KEY_MANAGER_TYPE { + @Override + public String asString() { + return "neo4j.property.encryption.encapsulated.key.manager.type"; + } + } + } + + enum UpdateEncapsulatedKeyAliasHighCardinalityKeyNames implements KeyName { + /** + * The key id. + */ + KEY_ID { + @Override + public String asString() { + return "neo4j.property.encryption.encapsulated.key.id"; + } + }, + /** + * The key alias if available. + */ + KEY_ALIAS { + @Override + public String asString() { + return "neo4j.property.encryption.encapsulated.key.alias"; + } + + @Override + public boolean isRequired() { + return false; + } + } + } + enum ConnectionPoolCloseLowCardinalityKeyNames implements KeyName { /** * The DBMS product name. It is always neo4j. diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/UpdateEncapsulatedKeyAliasContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/UpdateEncapsulatedKeyAliasContext.java new file mode 100644 index 0000000000..2eebcfca6a --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/UpdateEncapsulatedKeyAliasContext.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.Objects; +import java.util.Optional; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public class UpdateEncapsulatedKeyAliasContext extends Observation.Context { + private final Class encapsulatedKeyManagerType; + private final String id; + private final String alias; + + public UpdateEncapsulatedKeyAliasContext(Class encapsulatedKeyManagerType, String id, String alias) { + this.encapsulatedKeyManagerType = Objects.requireNonNull(encapsulatedKeyManagerType); + this.id = Objects.requireNonNull(id); + this.alias = alias; + } + + public Class encapsulatedKeyManagerType() { + return encapsulatedKeyManagerType; + } + + public String id() { + return id; + } + + public Optional alias() { + return Optional.ofNullable(alias); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/UpdateEncapsulatedKeyAliasConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/UpdateEncapsulatedKeyAliasConvention.java new file mode 100644 index 0000000000..b03c855aa2 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/UpdateEncapsulatedKeyAliasConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.3.0 + */ +@Preview(name = "Observability") +public interface UpdateEncapsulatedKeyAliasConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof UpdateEncapsulatedKeyAliasContext; + } +} diff --git a/observation/pom.xml b/observation/pom.xml index 5fe6051646..46efe92cda 100644 --- a/observation/pom.xml +++ b/observation/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-parent - 6.2-SNAPSHOT + 6.3-SNAPSHOT neo4j-java-driver-observation diff --git a/pom.xml b/pom.xml index a53fff4455..a67724d7b0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.neo4j.driver neo4j-java-driver-parent - 6.2-SNAPSHOT + 6.3-SNAPSHOT pom Neo4j Java Driver Project @@ -35,12 +35,12 @@ true - 12.0.0 + 12.1-SNAPSHOT 1.0.4 - 4.2.16.Final + 4.2.17.Final @@ -48,7 +48,7 @@ 2.0.18 3.0 5.23.0 - 6.1.2 + 6.1.3 7.12.0 1.2.0 @@ -76,6 +76,7 @@ driver bundle observation + encryption driver-it examples testkit-backend diff --git a/testkit-backend/LICENSES.txt b/testkit-backend/LICENSES.txt index 9949059506..dcfdbc7847 100644 --- a/testkit-backend/LICENSES.txt +++ b/testkit-backend/LICENSES.txt @@ -11,6 +11,7 @@ Apache Software License, Version 2.0 Neo4j Bolt Connection (Pooled Source impl) Neo4j Bolt Connection (Provider SPI) Neo4j Bolt Connection (Routed Source impl) + Neo4j Bolt Connection Codec Netty/Buffer Netty/Codec/Base Netty/Common @@ -226,6 +227,38 @@ Apache Software License, Version 2.0 +------------------------------------------------------------------------------ +Bouncy Castle License + Bouncy Castle Provider (FIPS Distribution) +------------------------------------------------------------------------------ + +Please note: our license is an adaptation of the MIT X11 License and should be +read as such. + +LICENSE + +Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle +(http://www.bouncycastle.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + ------------------------------------------------------------------------------ MIT No Attribution License reactive-streams diff --git a/testkit-backend/NOTICE.txt b/testkit-backend/NOTICE.txt index 0b4ad33765..4c41fac1c5 100644 --- a/testkit-backend/NOTICE.txt +++ b/testkit-backend/NOTICE.txt @@ -26,6 +26,7 @@ Apache Software License, Version 2.0 Neo4j Bolt Connection (Pooled Source impl) Neo4j Bolt Connection (Provider SPI) Neo4j Bolt Connection (Routed Source impl) + Neo4j Bolt Connection Codec Netty/Buffer Netty/Codec/Base Netty/Common @@ -36,6 +37,9 @@ Apache Software License, Version 2.0 Netty/Transport/Native/Unix/Common Non-Blocking Reactive Foundation for the JVM +Bouncy Castle License + Bouncy Castle Provider (FIPS Distribution) + MIT No Attribution License reactive-streams diff --git a/testkit-backend/pom.xml b/testkit-backend/pom.xml index e382cfe516..dc23fd862b 100644 --- a/testkit-backend/pom.xml +++ b/testkit-backend/pom.xml @@ -7,7 +7,7 @@ neo4j-java-driver-parent org.neo4j.driver - 6.2-SNAPSHOT + 6.3-SNAPSHOT testkit-backend @@ -49,6 +49,11 @@ org.projectlombok lombok + + org.bouncycastle + bc-fips + 2.1.3 + diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CreateEncapsulatedKey.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CreateEncapsulatedKey.java new file mode 100644 index 0000000000..db4bf09345 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CreateEncapsulatedKey.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests; + +import static reactor.adapter.JdkFlowAdapter.flowPublisherToFlux; + +import java.util.concurrent.CompletionStage; +import lombok.Getter; +import lombok.Setter; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; +import org.neo4j.driver.property_encryption.EncapsulatedKey; +import org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption; +import org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption; +import reactor.core.publisher.Mono; + +@Setter +@Getter +public class CreateEncapsulatedKey implements TestkitRequest { + private CreateEncapsulatedKeyBody data; + + @Override + public TestkitResponse process(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(); + var keyManager = encryption.keyManager(data.getProfileName()); + var key = keyManager.create(data.getAlias()); + return createResponse(key); + } + + @Override + public CompletionStage processAsync(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(AsyncPropertyEncryption.class); + var keyManager = encryption.keyManager(data.getProfileName()); + return keyManager.createAsync(data.getAlias()).thenApply(this::createResponse); + } + + @Override + public Mono processReactive(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(ReactivePropertyEncryption.class); + var keyManager = encryption.keyManager(data.getProfileName()); + return Mono.fromDirect(flowPublisherToFlux(keyManager.create(data.getAlias()))) + .map(this::createResponse); + } + + @Override + public Mono processReactiveStreams(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption( + org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption.class); + var keyManager = encryption.keyManager(data.getProfileName()); + return Mono.fromDirect(keyManager.create(data.getAlias())).map(this::createResponse); + } + + private neo4j.org.testkit.backend.messages.responses.EncapsulatedKey createResponse(EncapsulatedKey key) { + return neo4j.org.testkit.backend.messages.responses.EncapsulatedKey.builder() + .data(neo4j.org.testkit.backend.messages.responses.EncapsulatedKey.EncapsulatedKeyBody.builder() + .id(key.id()) + .alias(key.alias().orElse(null)) + .build()) + .build(); + } + + @Setter + @Getter + public static class CreateEncapsulatedKeyBody { + private String driverId; + private String alias; + private String profileName; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/Decrypt.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/Decrypt.java new file mode 100644 index 0000000000..9a49f2aee3 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/Decrypt.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests; + +import static reactor.adapter.JdkFlowAdapter.flowPublisherToFlux; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.concurrent.CompletionStage; +import lombok.Getter; +import lombok.Setter; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.requests.deserializer.HexByteArrayDeserializer; +import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherValueDeserializer; +import neo4j.org.testkit.backend.messages.responses.DecryptedValue; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; +import org.neo4j.driver.Value; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyDecryptionRequest; +import org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption; +import org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption; +import reactor.core.publisher.Mono; + +@Setter +@Getter +public class Decrypt implements TestkitRequest { + private DecryptBody data; + + @Override + public TestkitResponse process(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(); + var request = propertyDecryptRequest(encryption, data); + var decrypted = encryption.decrypt(request); + return createResponse(decrypted); + } + + @Override + public CompletionStage processAsync(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(AsyncPropertyEncryption.class); + var request = propertyDecryptRequest(encryption, data); + return encryption.decryptAsync(request).thenApply(this::createResponse); + } + + @Override + public Mono processReactive(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(ReactivePropertyEncryption.class); + var request = propertyDecryptRequest(encryption, data); + return Mono.fromDirect(flowPublisherToFlux(encryption.decrypt(request))).map(this::createResponse); + } + + @Override + public Mono processReactiveStreams(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption( + org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption.class); + var request = propertyDecryptRequest(encryption, data); + return Mono.fromDirect(encryption.decrypt(request)).map(this::createResponse); + } + + private PropertyDecryptionRequest propertyDecryptRequest(BasePropertyEncryption encryption, DecryptBody data) { + PropertyDecryptionRequest request; + + var aadStep = PropertyDecryptionRequest.builder().fromValue(data.getValue()); + + var persistAADStep = data.isUsePersistedAad() ? aadStep.withPersistedAAD() : aadStep.withAAD(data.getAad()); + + request = persistAADStep.build(); + + return request; + } + + private DecryptedValue createResponse(Value decrypted) { + return DecryptedValue.builder() + .data(DecryptedValue.EncryptedValueBody.builder() + .decryptedValue(decrypted) + .build()) + .build(); + } + + @Setter + @Getter + public static class DecryptBody { + private String driverId; + + @JsonDeserialize(using = HexByteArrayDeserializer.class) + private byte[] value; + + @JsonDeserialize(using = TestkitCypherValueDeserializer.class) + private Value aad; + + private boolean usePersistedAad; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/EncryptToBytes.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/EncryptToBytes.java new file mode 100644 index 0000000000..904b28ffd2 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/EncryptToBytes.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests; + +import static reactor.adapter.JdkFlowAdapter.flowPublisherToFlux; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import lombok.Getter; +import lombok.Setter; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.requests.deserializer.HexByteArrayDeserializer; +import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherValueDeserializer; +import neo4j.org.testkit.backend.messages.responses.EncryptedValue; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.property_encryption.InternalPropertyEncryptionRequest; +import org.neo4j.driver.property_encryption.BasePropertyEncryption; +import org.neo4j.driver.property_encryption.PropertyEncryptionRequest; +import org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption; +import org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption; +import reactor.core.publisher.Mono; + +@Setter +@Getter +public class EncryptToBytes implements TestkitRequest { + private EncryptToBytesBody data; + + @Override + public TestkitResponse process(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(); + var request = propertyEncryptRequest(encryption, data); + var encrypted = encryption.encryptToBytes(request); + return createResponse(encrypted); + } + + @Override + public CompletionStage processAsync(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(AsyncPropertyEncryption.class); + var request = propertyEncryptRequest(encryption, data); + return encryption.encryptToBytesAsync(request).thenApply(this::createResponse); + } + + @Override + public Mono processReactive(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(ReactivePropertyEncryption.class); + var request = propertyEncryptRequest(encryption, data); + return Mono.fromDirect(flowPublisherToFlux(encryption.encryptToBytes(request))) + .map(this::createResponse); + } + + @Override + public Mono processReactiveStreams(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption( + org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption.class); + var request = propertyEncryptRequest(encryption, data); + return Mono.fromDirect(encryption.encryptToBytes(request)).map(this::createResponse); + } + + private PropertyEncryptionRequest propertyEncryptRequest( + BasePropertyEncryption encryption, EncryptToBytesBody data) { + PropertyEncryptionRequest request; + var value = Optional.ofNullable(data.getValue()).orElse(Values.NULL); + var aadStep = PropertyEncryptionRequest.builder().fromValue(value); + + var profileStep = data.getAad() != null ? aadStep.withAAD(data.getAad()) : aadStep; + var buildStep = data.getProfileName() != null ? profileStep.usingProfile(data.getProfileName()) : profileStep; + if (data.getKeyAlias() != null) { + request = buildStep.usingKeyAlias(data.getKeyAlias()).build(); + } else if (data.getKeyId() != null) { + request = buildStep.usingKeyId(data.getKeyId()).build(); + } else { + throw new IllegalStateException("No key or key alias provided"); + } + + if (data.getIv() != null) { + var iv = data.getIv(); + try { + var field = InternalPropertyEncryptionRequest.class.getDeclaredField("iv"); + field.setAccessible(true); + field.set(request, iv); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + return request; + } + + private EncryptedValue createResponse(byte[] encrypted) { + return EncryptedValue.builder() + .data(EncryptedValue.EncryptedValueBody.builder() + .encryptedBytes(encrypted) + .build()) + .build(); + } + + @Setter + @Getter + public static class EncryptToBytesBody { + private String driverId; + + @JsonDeserialize(using = TestkitCypherValueDeserializer.class) + private Value value; + + @JsonDeserialize(using = TestkitCypherValueDeserializer.class) + private Value aad; + + private String profileName; + private String keyAlias; + private String keyId; + + @JsonDeserialize(using = HexByteArrayDeserializer.class) + private byte[] iv; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java index 59e4760e8d..293fcd3402 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java @@ -82,7 +82,8 @@ public class GetFeatures implements TestkitRequest { "Feature:Bolt:HandshakeManifestV1", "Feature:API:Type.UnsupportedType", "Feature:IdempotentRetries", - "Feature:API:Type.UUID")); + "Feature:API:Type.UUID", + "Feature:API:PropertyEncryption")); private static final Set SYNC_FEATURES = new HashSet<>(Arrays.asList( "Feature:Bolt:3.0", diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ImportEncapsulatedKey.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ImportEncapsulatedKey.java new file mode 100644 index 0000000000..90e77851a8 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ImportEncapsulatedKey.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import lombok.Getter; +import lombok.Setter; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.requests.deserializer.HexByteArrayDeserializer; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; +import org.neo4j.driver.internal.property_encryption.AbstractEncapsulatedKeyManager; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecord; +import org.neo4j.driver.property_encryption.async.AsyncPropertyEncryption; +import org.neo4j.driver.property_encryption.reactive.ReactivePropertyEncryption; +import reactor.core.publisher.Mono; + +@Setter +@Getter +public class ImportEncapsulatedKey implements TestkitRequest { + private ImportEncapsulatedKeyBody data; + + @Override + public TestkitResponse process(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(); + var keyManager = encryption.keyManager(data.getProfileName()); + var repository = repository(keyManager); + var key = repository + .save(data.getId(), data.getAlias(), data.getEncapsulation(), data.getMetadata()) + .toCompletableFuture() + .join(); + return createResponse(key); + } + + @Override + public CompletionStage processAsync(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(AsyncPropertyEncryption.class); + var keyManager = encryption.keyManager(data.getProfileName()); + var repository = repository(keyManager); + var key = repository + .save(data.getId(), data.getAlias(), data.getEncapsulation(), data.getMetadata()) + .toCompletableFuture() + .join(); + return CompletableFuture.completedStage(createResponse(key)); + } + + @Override + public Mono processReactive(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption(ReactivePropertyEncryption.class); + var keyManager = encryption.keyManager(data.getProfileName()); + var repository = repository(keyManager); + var key = repository + .save(data.getId(), data.getAlias(), data.getEncapsulation(), data.getMetadata()) + .toCompletableFuture() + .join(); + return Mono.just(createResponse(key)); + } + + @Override + public Mono processReactiveStreams(TestkitState testkitState) { + @SuppressWarnings("resource") + var driver = testkitState.getDriverHolder(data.getDriverId()).driver(); + var encryption = driver.propertyEncryption( + org.neo4j.driver.property_encryption.reactivestreams.ReactivePropertyEncryption.class); + var keyManager = encryption.keyManager(data.getProfileName()); + var repository = repository(keyManager); + var key = repository + .save(data.getId(), data.getAlias(), data.getEncapsulation(), data.getMetadata()) + .toCompletableFuture() + .join(); + return Mono.just(createResponse(key)); + } + + private NewDriver.InMemoryKeyRecordRepository repository(Object keyManager) { + try { + var field = AbstractEncapsulatedKeyManager.class.getDeclaredField("keyRepository"); + field.setAccessible(true); + return (NewDriver.InMemoryKeyRecordRepository) field.get(keyManager); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private neo4j.org.testkit.backend.messages.responses.EncapsulatedKey createResponse(EncapsulatedKeyRecord key) { + return neo4j.org.testkit.backend.messages.responses.EncapsulatedKey.builder() + .data(neo4j.org.testkit.backend.messages.responses.EncapsulatedKey.EncapsulatedKeyBody.builder() + .id(key.id()) + .alias(key.alias().orElse(null)) + .build()) + .build(); + } + + @Setter + @Getter + public static class ImportEncapsulatedKeyBody { + private String driverId; + private String id; + private String alias; + + @JsonDeserialize(using = HexByteArrayDeserializer.class) + private byte[] encapsulation; + + private String profileName; + private Map metadata; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java index 259cf2dfca..39a23d0a5b 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java @@ -16,34 +16,46 @@ */ package neo4j.org.testkit.backend.messages.requests; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.io.File; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.time.Clock; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import neo4j.org.testkit.backend.AuthTokenUtil; +import neo4j.org.testkit.backend.CustomDriverError; import neo4j.org.testkit.backend.TestkitClock; import neo4j.org.testkit.backend.TestkitState; import neo4j.org.testkit.backend.holder.DriverHolder; +import neo4j.org.testkit.backend.messages.requests.deserializer.HexByteArrayDeserializer; import neo4j.org.testkit.backend.messages.responses.DomainNameResolutionRequired; import neo4j.org.testkit.backend.messages.responses.Driver; import neo4j.org.testkit.backend.messages.responses.DriverError; import neo4j.org.testkit.backend.messages.responses.ResolverResolutionRequired; import neo4j.org.testkit.backend.messages.responses.TestkitCallback; import neo4j.org.testkit.backend.messages.responses.TestkitResponse; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; import org.neo4j.bolt.connection.DefaultDomainNameResolver; import org.neo4j.bolt.connection.DomainNameResolver; import org.neo4j.driver.AuthTokenManager; @@ -60,6 +72,13 @@ import org.neo4j.driver.internal.security.StaticAuthTokenManager; import org.neo4j.driver.net.ServerAddressResolver; import org.neo4j.driver.observation.metrics.MetricsObservationProvider; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecord; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecordRepository; +import org.neo4j.driver.property_encryption.EncapsulatedKeyRecords; +import org.neo4j.driver.property_encryption.EnvelopePropertyEncryptionProfile; +import org.neo4j.driver.property_encryption.KeyEncapsulationService; +import org.neo4j.driver.property_encryption.KeyEncapsulationServices; +import org.neo4j.driver.property_encryption.PropertyEncryptionProfile; import reactor.core.publisher.Mono; @Setter @@ -123,6 +142,12 @@ public TestkitResponse process(TestkitState testkitState) { certificateData.getPassword())) .map(ClientCertificateManagers::rotating)) .orElse(null); + try { + configBuilder.withPropertyEncryptionProfiles( + configurePropertyEncryptionProfiles(data.getPropertyEncryptionProfiles())); + } catch (IllegalArgumentException e) { + throw new CustomDriverError(e); + } org.neo4j.driver.Driver driver; var config = configBuilder.build(); try { @@ -142,6 +167,45 @@ public TestkitResponse process(TestkitState testkitState) { return Driver.builder().data(Driver.DriverBody.builder().id(id).build()).build(); } + private PropertyEncryptionProfile[] configurePropertyEncryptionProfiles(Set profiles) { + if (profiles == null) { + return null; + } + var provider = new BouncyCastleFipsProvider(); + SecureRandom secureRandom; + try { + secureRandom = SecureRandom.getInstance("NONCEANDIV", provider); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + return profiles.stream() + .map(profile -> { + var masterKey = Optional.ofNullable(profile.getKek()) + .map(bytes -> (SecretKey) new SecretKeySpec(bytes, "AES")) + .orElseGet(() -> { + KeyGenerator keyGenerator; + try { + keyGenerator = KeyGenerator.getInstance("AES"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + keyGenerator.init(256); + return keyGenerator.generateKey(); + }); + KeyEncapsulationService encapsulationService; + try { + encapsulationService = KeyEncapsulationServices.local(masterKey, provider, secureRandom); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + return EnvelopePropertyEncryptionProfile.builder( + profile.getName(), encapsulationService, new InMemoryKeyRecordRepository()) + .withCryptoContext(provider, secureRandom) + .build(); + }) + .toArray(PropertyEncryptionProfile[]::new); + } + @Override public CompletionStage processAsync(TestkitState testkitState) { return CompletableFuture.completedFuture(process(testkitState)); @@ -300,6 +364,68 @@ public static class NewDriverBody { private String clientCertificateProviderId; private Long maxConnectionLifetimeMs; private boolean disableAutoCommitRetries; + private Set propertyEncryptionProfiles; + } + + @Setter + @Getter + public static class EncryptionProfile { + private String name; + + @JsonDeserialize(using = HexByteArrayDeserializer.class) + private byte[] kek; + } + + public static class InMemoryKeyRecordRepository implements EncapsulatedKeyRecordRepository { + private Map idToKey = new HashMap<>(); + private Map aliasToId = new HashMap<>(); + + @Override + public CompletionStage findById(String id) { + var key = idToKey.get(id); + if (key == null) { + return CompletableFuture.completedFuture(null); + } + var alias = aliasToId.entrySet().stream() + .filter(entry -> entry.getValue().equals(id)) + .map(Map.Entry::getKey) + .findFirst() + .orElse(null); + return CompletableFuture.completedStage( + EncapsulatedKeyRecords.create(id, alias, key.encapsulation(), key.metadata())); + } + + @Override + public CompletionStage findByAlias(String alias) { + var id = aliasToId.get(alias); + return findById(id); + } + + public CompletionStage save( + String id, String alias, byte[] encapsulation, Map metadata) { + idToKey.put(id, new Key(encapsulation, metadata)); + aliasToId.put(alias, id); + return CompletableFuture.completedStage(EncapsulatedKeyRecords.create(id, alias, encapsulation, metadata)); + } + + @Override + public CompletionStage save( + String alias, byte[] encapsulation, Map metadata) { + return save(UUID.randomUUID().toString(), alias, encapsulation, metadata); + } + + @Override + public CompletionStage updateAliasById(String id, String alias) { + return null; + } + + @Override + public CompletionStage deleteById(String id) { + idToKey.remove(id); + return CompletableFuture.completedStage(null); + } + + private record Key(byte[] encapsulation, Map metadata) {} } @RequiredArgsConstructor diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java index 7cc9d90b13..d47b7fe373 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java @@ -76,7 +76,11 @@ @JsonSubTypes.Type(VerifyAuthentication.class), @JsonSubTypes.Type(NewClientCertificateProvider.class), @JsonSubTypes.Type(ClientCertificateProviderCompleted.class), - @JsonSubTypes.Type(ClientCertificateProviderClose.class) + @JsonSubTypes.Type(ClientCertificateProviderClose.class), + @JsonSubTypes.Type(CreateEncapsulatedKey.class), + @JsonSubTypes.Type(EncryptToBytes.class), + @JsonSubTypes.Type(Decrypt.class), + @JsonSubTypes.Type(ImportEncapsulatedKey.class) }) public interface TestkitRequest { TestkitResponse process(TestkitState testkitState); diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/HexByteArrayDeserializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/HexByteArrayDeserializer.java new file mode 100644 index 0000000000..eabc25f8fd --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/HexByteArrayDeserializer.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import java.io.IOException; + +public class HexByteArrayDeserializer extends JsonDeserializer { + + @Override + public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + + String value = p.getValueAsString().replaceAll("\\s+", ""); + + if ((value.length() & 1) != 0) { + throw new JsonMappingException(p, "Hex string must have an even length"); + } + + byte[] result = new byte[value.length() / 2]; + + for (int i = 0; i < result.length; i++) { + result[i] = (byte) Integer.parseInt(value.substring(i * 2, i * 2 + 2), 16); + } + + return result; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherValueDeserializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherValueDeserializer.java new file mode 100644 index 0000000000..c27ca0a38d --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherValueDeserializer.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests.deserializer; + +import static neo4j.org.testkit.backend.messages.responses.serializer.GenUtils.cypherTypeToJavaType; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import java.io.Serial; +import java.util.HexFormat; +import java.util.Map; +import neo4j.org.testkit.backend.messages.requests.deserializer.types.CypherType; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; + +public class TestkitCypherValueDeserializer extends StdDeserializer { + @Serial + private static final long serialVersionUID = -6981002212322046400L; + + public TestkitCypherValueDeserializer() { + super(Value.class); + } + + public TestkitCypherValueDeserializer(Class typeClass) { + super(typeClass); + } + + @Override + public Value deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (!p.isExpectedStartObjectToken()) { + ctxt.reportWrongTokenException(this, JsonToken.START_OBJECT, "Expected Cypher value object"); + } + String paramType = null; + JsonNode data = null; + while (p.nextToken() != JsonToken.END_OBJECT) { + var fieldName = p.currentName(); + p.nextToken(); + switch (fieldName) { + case "name" -> paramType = p.getValueAsString(); + case "data" -> data = p.readValueAsTree(); + default -> p.skipChildren(); + } + } + if (paramType == null) { + ctxt.reportInputMismatch(this, "Missing 'name' field"); + } + if (data == null) { + ctxt.reportInputMismatch(this, "Missing 'data' field"); + } + if ("CypherMap".equals(paramType)) { // Adapt this depending on the exact representation of CypherMap. + return deserialize(data.traverse(p.getCodec()), ctxt); + } + + var javaType = cypherTypeToJavaType(paramType); + if (javaType == null) { + return null; + } + + var valueNode = data.get("value"); + if (valueNode == null) { + ctxt.reportInputMismatch(this, "Missing 'value' inside 'data'"); + } + + Object obj; + if (javaType == byte[].class) { + var hex = valueNode.asText().replaceAll("\\s+", ""); + obj = HexFormat.of().parseHex(hex); + } else { + obj = ctxt.readTreeAsValue(valueNode, javaType); + } + + if (obj instanceof CypherType cypherType) { + return cypherType.asValue(); + } + return Values.value(obj); + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DecryptedValue.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DecryptedValue.java new file mode 100644 index 0000000000..4f0a268fa7 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DecryptedValue.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses; + +import lombok.Builder; +import lombok.Getter; +import org.neo4j.driver.Value; + +@Getter +@Builder +public class DecryptedValue implements TestkitResponse { + private EncryptedValueBody data; + + @Override + public String testkitName() { + return "DecryptedValue"; + } + + @Getter + @Builder + public static class EncryptedValueBody { + private Value decryptedValue; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/EncapsulatedKey.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/EncapsulatedKey.java new file mode 100644 index 0000000000..859cc80cc5 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/EncapsulatedKey.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class EncapsulatedKey implements TestkitResponse { + private EncapsulatedKeyBody data; + + @Override + public String testkitName() { + return "EncapsulatedKey"; + } + + @Getter + @Builder + public static class EncapsulatedKeyBody { + private String id; + private String alias; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/EncryptedValue.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/EncryptedValue.java new file mode 100644 index 0000000000..2bd9e71c05 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/EncryptedValue.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Builder; +import lombok.Getter; +import neo4j.org.testkit.backend.messages.responses.serializer.HexByteArraySerializer; + +@Getter +@Builder +public class EncryptedValue implements TestkitResponse { + private EncryptedValueBody data; + + @Override + public String testkitName() { + return "EncryptedValue"; + } + + @Getter + @Builder + public static class EncryptedValueBody { + @JsonSerialize(using = HexByteArraySerializer.class) + private byte[] encryptedBytes; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java index 34a984eb5a..09e9723a84 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java @@ -91,6 +91,7 @@ public static Class cypherTypeToJavaType(String typeString) { case "CypherDuration" -> IsoDuration.class; case "CypherVector" -> Vector.class; case "CypherUUID" -> UUID.class; + case "CypherBytes" -> byte[].class; default -> null; }; } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/HexByteArraySerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/HexByteArraySerializer.java new file mode 100644 index 0000000000..34da918c25 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/HexByteArraySerializer.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.responses.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class HexByteArraySerializer extends JsonSerializer { + + @Override + public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + + if (value == null) { + gen.writeNull(); + return; + } + + String hex = IntStream.range(0, value.length) + .mapToObj(i -> String.format("%02x", value[i] & 0xff)) + .collect(Collectors.joining(" ")); + + gen.writeString(hex); + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitValueSerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitValueSerializer.java index a4144eabd3..61893c403b 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitValueSerializer.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitValueSerializer.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer; import java.io.IOException; import java.io.Serial; +import java.util.HexFormat; import org.neo4j.driver.Value; import org.neo4j.driver.internal.types.InternalTypeSystem; @@ -48,6 +49,8 @@ public void serialize(Value value, JsonGenerator gen, SerializerProvider provide cypherObject(gen, "CypherString", value.asString()); } else if (InternalTypeSystem.TYPE_SYSTEM.UUID().isTypeOf(value)) { cypherObject(gen, "CypherUUID", value.asUUID()); + } else if (InternalTypeSystem.TYPE_SYSTEM.BYTES().isTypeOf(value)) { + cypherObject(gen, "CypherBytes", HexFormat.ofDelimiter(" ").formatHex(value.asByteArray())); } } } diff --git a/testkit-tests/pom.xml b/testkit-tests/pom.xml index 77aab01e2d..420247475f 100644 --- a/testkit-tests/pom.xml +++ b/testkit-tests/pom.xml @@ -6,7 +6,7 @@ org.neo4j.driver neo4j-java-driver-parent - 6.2-SNAPSHOT + 6.3-SNAPSHOT .. @@ -20,7 +20,7 @@ ${project.basedir}/.. https://github.com/neo4j-drivers/testkit.git - 5.0 + 6.x --tests TESTKIT_TESTS INTEGRATION_TESTS STUB_TESTS STRESS_TESTS TLS_TESTS 7200000 @@ -31,7 +31,7 @@ - 0.48.1 + 0.49.0 true