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