From 159a0c84115c055487da633294c3261434731df5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Julius=20H=C3=A4rtl?= <jus@bitgrid.net>
Date: Thu, 9 Jun 2022 20:56:18 +0200
Subject: [PATCH] feat(s3): Add option to specify an SSE-C customer provided
 key
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Julius Härtl <jus@bitgrid.net>
---
 .../Files/ObjectStore/S3ConnectionTrait.php   | 30 +++++++++++++++++++
 .../Files/ObjectStore/S3ObjectTrait.php       | 13 ++++----
 2 files changed, 38 insertions(+), 5 deletions(-)

diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php
index 09fdffe01bd81..deb03571c7624 100644
--- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php
+++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php
@@ -231,4 +231,34 @@ protected function getCertificateBundlePath(): ?string {
 			return null;
 		}
 	}
+
+	protected function getSSECKey(): ?string {
+		if (isset($this->params['sse_c_key'])) {
+			return $this->params['sse_c_key'];
+		}
+
+		return null;
+	}
+
+	protected function getSSECParameters(bool $copy = false): array {
+		$key = $this->getSSECKey();
+
+		if ($key === null) {
+			return [];
+		}
+
+		$rawKey = base64_decode($key);
+		if ($copy) {
+			return [
+				'CopySourceSSECustomerAlgorithm' => 'AES256',
+				'CopySourceSSECustomerKey' => $rawKey,
+				'CopySourceSSECustomerKeyMD5' => md5($rawKey, true)
+			];
+		}
+		return [
+			'SSECustomerAlgorithm' => 'AES256',
+			'SSECustomerKey' => $rawKey,
+			'SSECustomerKeyMD5' => md5($rawKey, true)
+		];
+	}
 }
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php
index 33b9f6f7feddb..8fa6d67faa3cd 100644
--- a/lib/private/Files/ObjectStore/S3ObjectTrait.php
+++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php
@@ -44,6 +44,7 @@ trait S3ObjectTrait {
 	abstract protected function getConnection();
 
 	abstract protected function getCertificateBundlePath(): ?string;
+	abstract protected function getSSECParameters(bool $copy = false): array;
 
 	/**
 	 * @param string $urn the unified resource name used to identify the object
@@ -58,7 +59,7 @@ public function readObject($urn) {
 				'Bucket' => $this->bucket,
 				'Key' => $urn,
 				'Range' => 'bytes=' . $range,
-			]);
+			] + $this->getSSECParameters());
 			$request = \Aws\serialize($command);
 			$headers = [];
 			foreach ($request->getHeaders() as $key => $values) {
@@ -106,7 +107,7 @@ protected function writeSingle(string $urn, StreamInterface $stream, string $mim
 			'ACL' => 'private',
 			'ContentType' => $mimetype,
 			'StorageClass' => $this->storageClass,
-		]);
+		] + $this->getSSECParameters());
 	}
 
 
@@ -126,7 +127,7 @@ protected function writeMultiPart(string $urn, StreamInterface $stream, string $
 			'params' => [
 				'ContentType' => $mimetype,
 				'StorageClass' => $this->storageClass,
-			],
+			] + $this->getSSECParameters(),
 		]);
 
 		try {
@@ -181,10 +182,12 @@ public function deleteObject($urn) {
 	}
 
 	public function objectExists($urn) {
-		return $this->getConnection()->doesObjectExist($this->bucket, $urn);
+		return $this->getConnection()->doesObjectExist($this->bucket, $urn, $this->getSSECParameters());
 	}
 
 	public function copyObject($from, $to) {
-		$this->getConnection()->copy($this->getBucket(), $from, $this->getBucket(), $to);
+		$this->getConnection()->copy($this->getBucket(), $from, $this->getBucket(), $to, 'private', [
+			'params' => $this->getSSECParameters() + $this->getSSECParameters(true)
+		]);
 	}
 }