xref: /aosp_15_r20/external/tink/cc/integration/awskms/aws_kms_client.cc (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1 // Copyright 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ///////////////////////////////////////////////////////////////////////////////
16 #include "tink/integration/awskms/aws_kms_client.h"
17 
18 #include <fstream>
19 #include <iostream>
20 #include <sstream>
21 
22 #include "aws/core/Aws.h"
23 #include "aws/core/auth/AWSCredentialsProvider.h"
24 #include "aws/core/auth/AWSCredentialsProviderChain.h"
25 #include "aws/core/client/ClientConfiguration.h"
26 #include "aws/core/utils/crypto/Factories.h"
27 #include "aws/core/utils/memory/AWSMemory.h"
28 #include "aws/kms/KMSClient.h"
29 #include "absl/base/call_once.h"
30 #include "absl/status/status.h"
31 #include "absl/strings/ascii.h"
32 #include "absl/strings/escaping.h"
33 #include "absl/strings/match.h"
34 #include "absl/strings/str_split.h"
35 #include "absl/strings/string_view.h"
36 #include "absl/synchronization/mutex.h"
37 #include "tink/integration/awskms/aws_kms_aead.h"
38 #include "tink/kms_client.h"
39 #include "tink/util/status.h"
40 #include "tink/util/statusor.h"
41 
42 namespace crypto {
43 namespace tink {
44 namespace integration {
45 namespace awskms {
46 namespace {
47 
48 constexpr absl::string_view kKeyUriPrefix = "aws-kms://";
49 constexpr char kTinkAwsKmsAllocationTag[] = "tink::integration::awskms";
50 
51 // Returns AWS key ARN contained in `key_uri`. If `key_uri` does not refer to an
52 // AWS key, returns an error.
GetKeyArn(absl::string_view key_uri)53 util::StatusOr<std::string> GetKeyArn(absl::string_view key_uri) {
54   if (!absl::StartsWithIgnoreCase(key_uri, kKeyUriPrefix)) {
55     return util::Status(absl::StatusCode::kInvalidArgument,
56                         absl::StrCat("Invalid key URI ", key_uri));
57   }
58   return std::string(key_uri.substr(kKeyUriPrefix.length()));
59 }
60 
61 // Returns ClientConfiguration with region set to the value extracted from
62 // `key_arn`.
63 // An AWS key ARN is of the form
64 // arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab.
GetAwsClientConfig(absl::string_view key_arn)65 util::StatusOr<Aws::Client::ClientConfiguration> GetAwsClientConfig(
66     absl::string_view key_arn) {
67   std::vector<std::string> key_arn_parts = absl::StrSplit(key_arn, ':');
68   if (key_arn_parts.size() < 6) {
69     return util::Status(absl::StatusCode::kInvalidArgument,
70                         absl::StrCat("Invalid key ARN ", key_arn));
71   }
72   Aws::Client::ClientConfiguration config;
73   // 4th part of key arn.
74   config.region = key_arn_parts[3].c_str();
75   config.scheme = Aws::Http::Scheme::HTTPS;
76   config.connectTimeoutMs = 30000;
77   config.requestTimeoutMs = 60000;
78   return config;
79 }
80 
81 // Reads the specified file and returns the content as a string.
ReadFile(const std::string & filename)82 util::StatusOr<std::string> ReadFile(const std::string& filename) {
83   std::ifstream input_stream;
84   input_stream.open(filename, std::ifstream::in);
85   if (!input_stream.is_open()) {
86     return util::Status(absl::StatusCode::kInvalidArgument,
87                         absl::StrCat("Error opening file ", filename));
88   }
89   std::stringstream input;
90   input << input_stream.rdbuf();
91   input_stream.close();
92   return input.str();
93 }
94 
95 // Extracts a value of `name` from `line`, where `line` must be of the form:
96 // name = value
GetValue(absl::string_view name,absl::string_view line)97 util::StatusOr<std::string> GetValue(absl::string_view name,
98                                      absl::string_view line) {
99   std::vector<std::string> parts = absl::StrSplit(line, '=');
100   if (parts.size() != 2 || absl::StripAsciiWhitespace(parts[0]) != name) {
101     return util::Status(absl::StatusCode::kInvalidArgument,
102                         absl::StrCat("Expected line to have the format: ", name,
103                                      " = value. Found: ", line));
104   }
105   return std::string(absl::StripAsciiWhitespace(parts[1]));
106 }
107 
108 // Returns AWS credentials from the given `credential_path`.
109 //
110 // Credentials are retrieved as follows:
111 //
112 // If `credentials_path` is not empty the credentials in the given file are
113 // returned. The file should have the following format:
114 //
115 //   [default]
116 //   aws_access_key_id = your_access_key_id
117 //   aws_secret_access_key = your_secret_access_key
118 //
119 // If `credentials_path` is empty, the credentials are searched for in the
120 // following order:
121 //   1. In the file specified via environment variable
122 //   AWS_SHARED_CREDENTIALS_FILE
123 //   2. In the file specified via environment variable AWS_PROFILE
124 //   3. In the file ~/.aws/credentials
125 //   4. In the file ~/.aws/config
126 //   5. In values specified in environment variables AWS_ACCESS_KEY_ID,
127 //      AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN
128 //
129 // For more info on AWS credentials see:
130 // https://docs.aws.amazon.com/sdk-for-cpp/v1/developer-guide/credentials.html
131 // and documentation of Aws::Auth::EnvironmentAWSCredentialsProvider and
132 // Aws::Auth::ProfileConfigFileAWSCredentialsProvider.
GetAwsCredentials(absl::string_view credentials_path)133 util::StatusOr<Aws::Auth::AWSCredentials> GetAwsCredentials(
134     absl::string_view credentials_path) {
135   if (credentials_path.empty()) {
136     // Get default credentials.
137     Aws::Auth::DefaultAWSCredentialsProviderChain provider_chain;
138     return provider_chain.GetAWSCredentials();
139   }
140   // Read credentials from the given file.
141   util::StatusOr<std::string> creds_result =
142       ReadFile(std::string(credentials_path));
143   if (!creds_result.ok()) {
144     return creds_result.status();
145   }
146   std::vector<std::string> creds_lines =
147       absl::StrSplit(creds_result.value(), '\n');
148   if (creds_lines.size() < 3) {
149     return util::Status(absl::StatusCode::kInvalidArgument,
150                         absl::StrCat("Invalid format of credentials in file ",
151                                      credentials_path));
152   }
153   util::StatusOr<std::string> key_id_result =
154       GetValue("aws_access_key_id", creds_lines[1]);
155   if (!key_id_result.ok()) {
156     return util::Status(
157         absl::StatusCode::kInvalidArgument,
158         absl::StrCat("Invalid format of credentials in file ", credentials_path,
159                      " : ", key_id_result.status().message()));
160   }
161   util::StatusOr<std::string> secret_key_result =
162       GetValue("aws_secret_access_key", creds_lines[2]);
163   if (!secret_key_result.ok()) {
164     return util::Status(
165         absl::StatusCode::kInvalidArgument,
166         absl::StrCat("Invalid format of credentials in file ", credentials_path,
167                      " : ", secret_key_result.status().message()));
168   }
169   return Aws::Auth::AWSCredentials(key_id_result.value().c_str(),
170                                    secret_key_result.value().c_str());
171 }
172 
InitAwsApi()173 void InitAwsApi() {
174   Aws::SDKOptions options;
175   Aws::InitAPI(options);
176 }
177 
178 }  // namespace
179 
180 static absl::once_flag aws_initialization_once;
181 
New(absl::string_view key_uri,absl::string_view credentials_path)182 util::StatusOr<std::unique_ptr<AwsKmsClient>> AwsKmsClient::New(
183     absl::string_view key_uri, absl::string_view credentials_path) {
184   absl::call_once(aws_initialization_once, []() { InitAwsApi(); });
185   // Read credentials.
186   util::StatusOr<Aws::Auth::AWSCredentials> credentials =
187       GetAwsCredentials(credentials_path);
188   if (!credentials.ok()) {
189     return credentials.status();
190   }
191 
192   if (key_uri.empty()) {
193     return absl::WrapUnique(new AwsKmsClient(*credentials));
194   }
195 
196   // If a specific key is given, create an AWS KMSClient.
197   util::StatusOr<std::string> key_arn = GetKeyArn(key_uri);
198   if (!key_arn.ok()) {
199     return key_arn.status();
200   }
201   util::StatusOr<Aws::Client::ClientConfiguration> client_config =
202       GetAwsClientConfig(*key_arn);
203   if (!client_config.ok()) {
204     return client_config.status();
205   }
206   auto client = absl::WrapUnique(new AwsKmsClient(*key_arn, *credentials));
207   // Create AWS KMSClient.
208   client->aws_client_ = Aws::MakeShared<Aws::KMS::KMSClient>(
209       kTinkAwsKmsAllocationTag, client->credentials_, *client_config);
210   return std::move(client);
211 }
212 
DoesSupport(absl::string_view key_uri) const213 bool AwsKmsClient::DoesSupport(absl::string_view key_uri) const {
214   util::StatusOr<std::string> key_arn = GetKeyArn(key_uri);
215   if (!key_arn.ok()) {
216     return false;
217   }
218   // If this is bound to a specific key, make sure the key ARNs are equal.
219   return key_arn_.empty() ? true : key_arn_ == *key_arn;
220 }
221 
GetAead(absl::string_view key_uri) const222 util::StatusOr<std::unique_ptr<Aead>> AwsKmsClient::GetAead(
223     absl::string_view key_uri) const {
224   util::StatusOr<std::string> key_arn = GetKeyArn(key_uri);
225   if (!key_arn.ok()) {
226     return key_arn.status();
227   }
228   // This client is bound to a specific key.
229   if (!key_arn_.empty()) {
230     if (key_arn_ != *key_arn) {
231       return util::Status(absl::StatusCode::kInvalidArgument,
232                           absl::StrCat("This client is bound to ", key_arn_,
233                                        " and cannot use key ", key_uri));
234     }
235     return AwsKmsAead::New(key_arn_, aws_client_);
236   }
237 
238   util::StatusOr<Aws::Client::ClientConfiguration> client_config =
239       GetAwsClientConfig(*key_arn);
240   if (!client_config.ok()) {
241     return client_config.status();
242   }
243   auto aws_client = Aws::MakeShared<Aws::KMS::KMSClient>(
244       kTinkAwsKmsAllocationTag, credentials_, *client_config);
245   return AwsKmsAead::New(*key_arn, aws_client);
246 }
247 
RegisterNewClient(absl::string_view key_uri,absl::string_view credentials_path)248 util::Status AwsKmsClient::RegisterNewClient(
249     absl::string_view key_uri, absl::string_view credentials_path) {
250   util::StatusOr<std::unique_ptr<AwsKmsClient>> client_result =
251       AwsKmsClient::New(key_uri, credentials_path);
252   if (!client_result.ok()) {
253     return client_result.status();
254   }
255 
256   return KmsClients::Add(*std::move(client_result));
257 }
258 
259 }  // namespace awskms
260 }  // namespace integration
261 }  // namespace tink
262 }  // namespace crypto
263