Generate an Amazon S3 presigned URL with SSE using the AWS SDK for C++

Amazon Simple Storage Service (Amazon S3) presigned URLs give you or your customers an option to access an Amazon S3 object identified in the URL, without having AWS credentials and permissions.

With server-side encryption (SSE) specified, Amazon S3 will encrypt the data when the object is written to disks, and decrypt the data when the object is accessed from disks. Amazon S3 presigned URLs and SSE are two different functionalities, and have already been supported separately by the AWS SDK for C++. Now customers using the AWS SDK for C++ can use them together.

In this blog series, we’ve already explained different types of SSE and how we can generate and use Amazon S3 presigned URLs with SSE by using the AWS SDK for Java. In this blog post, we’ll give examples for AWS SDK for C++ customers. But, unlike the AWS SDK for Java, AWS Signature Version 2 (SigV2) is no longer supported in the AWS SDK for C++. The underlying signer is AWS Signature Version 4 (SigV4).

Using functions defined in S3Client.h to generate an S3 presigned URL with SSE is pretty straight forward.
Let’s look at PutObject as an example.

Generate a presigned URL with server-side encryption (SSE) and S3 managed keys (SSE-S3)

Aws::S3::S3Client s3Client;
Aws::String presignedPutUrl = s3Client->GeneratePresignedUrlWithSSES3(“BUCKET_NAME”, “KEY_NAME”, HttpMethod::HTTP_PUT);

Generate a presigned URL with server-side encryption (SSE) and KMS managed keys (SSE-KMS)

Aws::S3::S3Client s3Client;
// If KMS_MASTER_KEY_ID is empty, we will use KMS managed default key: “aws/s3” for s3.
Aws::String presignedPutUrl = s3Client->GeneratePresignedUrlWithSSEKMS(“BUCKET_NAME”, “KEY_NAME”, HttpMethod::HTTP_PUT, “KMS_MASTER_KEY_ID”);

Generate a presigned URL with server-side encryption (SSE) and customer-supplied key (SSE-C)

Aws::S3::S3Client s3Client;
Aws::String presignedPutUrl = s3Client->GeneratePresignedUrlWithSSEC(“BUCKET_NAME”, “KEY_NAME”, HttpMethod::HTTP_PUT, “BASE64_ENCODED_AES256_KEY”);

 

To actually use generated S3 presigned URLs with SSE programmatically in your project requires a little bit more work. First, you have to create an HttpRequest. Then, based on different SSE types, you need to add some HTTP headers. Last, unlike common Amazon S3 operations, you have to explicitly send out the request with the content body (the actual object you want to upload) that’s been set.

Code snippet to create HttpRequest

std::shared_ptr putRequest = CreateHttpRequest(presignedUrlPut, HttpMethod::HTTP_PUT, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);

Add required headers if using SSE-S3

putRequest->SetHeaderValue(Aws::S3::SSEHeaders::SERVER_SIDE_ENCRYPTION, Aws::S3::Model::ServerSideEncryptionMapper::GetNameForServerSideEncryption(Aws::S3::Model::ServerSideEncryption::AES256));

Add required headers if using SSE-KMS

putRequest->SetHeaderValue(Aws::S3::SSEHeaders::SERVER_SIDE_ENCRYPTION, Aws::S3::Model::ServerSideEncryptionMapper::GetNameForServerSideEncryption(Aws::S3::Model::ServerSideEncryption::aws_kms));

Add required headers if using SSE-C

// Suppose customer supplied key is stored in a ByteBuffer called sseKey
putRequest->SetHeaderValue(Aws::S3::SSEHeaders::SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM, Aws::S3::Model::ServerSideEncryptionMapper::GetNameForServerSideEncryption(Aws::S3::Model::ServerSideEncryption::AES256));
putRequest->SetHeaderValue(Aws::S3::SSEHeaders::SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY, HashingUtils::Base64Encode(sseKey));
Aws::String strBuffer(reinterpret_cast<char*>(sseKey.GetUnderlyingData()), sseKey.GetLength());
putRequest->SetHeaderValue(Aws::S3::SSEHeaders::SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5, HashingUtils::Base64Encode(HashingUtils::CalculateMD5(strBuffer)));

Add content body (object’s data) to the request and send it out

std::shared_ptr objectStream = Aws::MakeShared("Test");
*objectStream << "Test Object";
objectStream->flush();
putRequest->AddContentBody(objectStream);
Aws::StringStream intConverter;
intConverter << objectStream->tellp();
putRequest->SetContentLength(intConverter.str());
putRequest->SetContentType("text/plain");
Aws::Http::HttpClient httpClient = Aws::Http::CreateHttpClient(Aws::Client::ClientConfiguration());
std::shared_ptr putResponse = httpClient->MakeRequest(putRequest);

 

As we said at the beginning, you can also use a presigned URL to access objects (GetObject). For SSE-S3 and SSE-KMS, SSE-related headers are not required. That means, to access objects, there’s no difference between using a presigned URL and presigned URL with SSE. However, for SSE-C, related headers are still required. You can use the same GeneratePresignedUrlWithSSEC function to generate a presigned URL with SSE-C to access objects.

Get an object uploaded with an SSE-S3 or SSE-KMS presigned URL

// It’s the same to use GeneratePresignedUrlWithSSES3 or GeneratePresignedUrlWithSSEKMS
Aws::String presignedUrlGet = s3Client->GeneratePresignedUrl("BUCKET_NAME", “KEY_NAME”, HttpMethod::HTTP_GET);
std::shared_ptr getRequest = CreateHttpRequest(presignedUrlGet, HttpMethod::HTTP_GET, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
std::shared_ptr getResponse = httpClient->MakeRequest(getRequest);

Get an object uploaded with an SSE-C presigned URL

Aws::String presignedUrlGet = s3Client->GeneratePresignedUrlWithSSEC(“BUCKET_NAME”, “KEY_NAME”, HttpMethod::HTTP_GET, HashingUtils::Base64Encode(sseKey));
std::shared_ptr getRequest = CreateHttpRequest(presignedUrlGet, HttpMethod::HTTP_GET, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
getRequest->SetHeaderValue(Aws::S3::SSEHeaders::SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM, Aws::S3::Model::ServerSideEncryptionMapper::GetNameForServerSideEncryption(Aws::S3::Model::ServerSideEncryption::AES256));
getRequest->SetHeaderValue(Aws::S3::SSEHeaders::SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY, HashingUtils::Base64Encode(sseKey));
getRequest->SetHeaderValue(Aws::S3::SSEHeaders::SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5, HashingUtils::Base64Encode(HashingUtils::CalculateMD5(strBuffer)));
std::shared_ptr getResponse = httpClient->MakeRequest(getRequest);
std::cout << getResponse->GetResponseBody().rdbuf(); // Should output “Test Object”

There is also another set of functions that enable customers to add customized headers when generating an Amazon S3 presigned URL with SSE. For more details on using all of these functions, check out these test cases.

Please reach out to us with questions and improvements. As always, pull requests are welcome!