• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender / 969241802

16 Aug 2023 08:29AM UTC coverage: 79.019% (+0.2%) from 78.825%
969241802

push

gitlab-ci

oleorhagen
chore(crypto): Make the PrivateKey constructor public

Signed-off-by: Ole Petter <ole.orhagen@northern.tech>

5314 of 6725 relevant lines covered (79.02%)

200.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.34
/api/auth/auth.cpp
1
// Copyright 2023 Northern.tech AS
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
#include <api/auth.hpp>
16

17
#include <mutex>
18
#include <string>
19
#include <vector>
20

21
#include <common/common.hpp>
22
#include <common/conf/paths.hpp>
23
#include <common/crypto.hpp>
24
#include <common/json.hpp>
25
#include <common/error.hpp>
26
#include <common/log.hpp>
27
#include <common/path.hpp>
28
#include <common/expected.hpp>
29
#include <common/identity_parser.hpp>
30
#include <common/optional.hpp>
31

32
namespace mender {
33
namespace api {
34
namespace auth {
35

36

37
using namespace std;
38
namespace error = mender::common::error;
39
namespace common = mender::common;
40
namespace conf = mender::common::conf;
41

42

43
namespace identity_parser = mender::common::identity_parser;
44
namespace key_value_parser = mender::common::key_value_parser;
45
namespace config_path = mender::common::conf::paths;
46
namespace path = mender::common::path;
47
namespace mlog = mender::common::log;
48
namespace expected = mender::common::expected;
49
namespace io = mender::common::io;
50
namespace json = mender::common::json;
51
namespace crypto = mender::common::crypto;
52
namespace optional = mender::common::optional;
53

54

55
const string request_uri = "/api/devices/v1/authentication/auth_requests";
56

57
const AuthClientErrorCategoryClass AuthClientErrorCategory;
58

59
const char *AuthClientErrorCategoryClass::name() const noexcept {
×
60
        return "AuthClientErrorCategory";
×
61
}
62

63
string AuthClientErrorCategoryClass::message(int code) const {
×
64
        switch (code) {
×
65
        case NoError:
×
66
                return "Success";
×
67
        case SetupError:
×
68
                return "Error during setup";
×
69
        case RequestError:
×
70
                return "HTTP client request error";
×
71
        case ResponseError:
×
72
                return "HTTP client response error";
×
73
        case APIError:
×
74
                return "API error";
×
75
        case UnauthorizedError:
×
76
                return "Unauthorized error";
×
77
        default:
×
78
                return "Unknown";
×
79
        }
80
}
81

82
error::Error MakeError(AuthClientErrorCode code, const string &msg) {
2✔
83
        return error::Error(error_condition(code, AuthClientErrorCategory), msg);
4✔
84
}
85

86
namespace http {
87

88
error::Error MakeHTTPResponseError(
×
89
        const AuthClientErrorCode code,
90
        const mender::http::ResponsePtr resp,
91
        const string &response_body,
92
        const string &msg) {
93
        return error::Error(
94
                error_condition(code, AuthClientErrorCategory),
×
95
                "Authentication error(" + resp->GetStatusMessage() + "): " + msg + "(" + response_body
×
96
                        + ")");
×
97
}
98

99
error::Error FetchJWTToken(
13✔
100
        mender::http::Client &client,
101
        const string &server_url,
102
        const string &private_key_path,
103
        const string &device_identity_script_path,
104
        APIResponseHandler api_handler,
105
        const string &tenant_token) {
106
        key_value_parser::ExpectedKeyValuesMap expected_identity_data =
107
                identity_parser::GetIdentityData(device_identity_script_path);
26✔
108
        if (!expected_identity_data) {
13✔
109
                return expected_identity_data.error();
×
110
        }
111
        expected::ExpectedString expected_identity_data_json =
112
                json::Dump(expected_identity_data.value());
26✔
113
        if (!expected_identity_data_json) {
13✔
114
                mlog::Error("Failed to dump the identity data to JSON");
×
115
                return expected_identity_data_json.error();
×
116
        }
117
        auto identity_data_json = expected_identity_data_json.value();
26✔
118
        mlog::Debug("Got identity data: " + identity_data_json);
13✔
119

120
        // Create the request body
121
        unordered_map<string, string> request_body_map {
122
                {"id_data", identity_data_json},
123
        };
52✔
124

125
        if (tenant_token.size() > 0) {
13✔
126
                request_body_map.insert({"tenant_token", tenant_token});
1✔
127
        }
128

129
        auto expected_public_key = crypto::ExtractPublicKey(private_key_path);
26✔
130
        if (!expected_public_key) {
13✔
131
                return expected_public_key.error();
×
132
        }
133
        request_body_map.insert({"pubkey", expected_public_key.value()});
13✔
134

135
        auto expected_request_body = json::Dump(request_body_map);
26✔
136
        if (!expected_request_body) {
13✔
137
                return expected_request_body.error();
×
138
        }
139
        auto request_body = expected_request_body.value();
26✔
140

141
        // Sign the body
142
        auto expected_signature =
143
                crypto::SignRawData(private_key_path, common::ByteVectorFromString(request_body));
26✔
144
        if (!expected_signature) {
13✔
145
                return expected_signature.error();
×
146
        }
147
        auto signature = expected_signature.value();
26✔
148

149
        auto whole_url = mender::http::JoinUrl(server_url, request_uri);
26✔
150

151
        auto req = make_shared<mender::http::OutgoingRequest>();
26✔
152
        req->SetMethod(mender::http::Method::POST);
13✔
153
        req->SetAddress(whole_url);
13✔
154
        req->SetHeader("Content-Type", "application/json");
13✔
155
        req->SetHeader("Content-Length", to_string(request_body.size()));
13✔
156
        req->SetHeader("Accept", "application/json");
13✔
157
        req->SetHeader("X-MEN-Signature", signature);
13✔
158
        req->SetHeader("Authorization", "API_KEY");
13✔
159

160
        req->SetBodyGenerator([request_body]() -> io::ExpectedReaderPtr {
26✔
161
                return make_shared<io::StringReader>(request_body);
26✔
162
        });
26✔
163

164
        auto received_body = make_shared<vector<uint8_t>>();
13✔
165

166
        return client.AsyncCall(
167
                req,
168
                [received_body, api_handler](mender::http::ExpectedIncomingResponsePtr exp_resp) {
13✔
169
                        if (!exp_resp) {
13✔
170
                                mlog::Error("Request failed: " + exp_resp.error().message);
×
171
                                api_handler(expected::unexpected(exp_resp.error()));
×
172
                                return;
×
173
                        }
174
                        auto resp = exp_resp.value();
26✔
175

176
                        auto body_writer = make_shared<io::ByteWriter>(received_body);
13✔
177
                        body_writer->SetUnlimited(true);
13✔
178
                        resp->SetBodyWriter(body_writer);
13✔
179

180
                        mlog::Debug("Received response header value:");
13✔
181
                        mlog::Debug("Status code:" + to_string(resp->GetStatusCode()));
13✔
182
                        mlog::Debug("Status message: " + resp->GetStatusMessage());
13✔
183
                },
184
                [received_body, api_handler](mender::http::ExpectedIncomingResponsePtr exp_resp) {
13✔
185
                        if (!exp_resp) {
13✔
186
                                mlog::Error("Request failed: " + exp_resp.error().message);
×
187
                                api_handler(expected::unexpected(exp_resp.error()));
×
188
                                return;
×
189
                        }
190
                        auto resp = exp_resp.value();
26✔
191

192
                        string response_body = common::StringFromByteVector(*received_body);
26✔
193

194
                        switch (resp->GetStatusCode()) {
13✔
195
                        case mender::http::StatusOK:
11✔
196
                                api_handler(response_body);
11✔
197
                                return;
11✔
198
                        case mender::http::StatusUnauthorized:
×
199
                                api_handler(expected::unexpected(MakeHTTPResponseError(
×
200
                                        UnauthorizedError,
201
                                        resp,
202
                                        response_body,
203
                                        "Failed to authorize with the server.")));
×
204
                                return;
×
205
                        case mender::http::StatusBadRequest:
×
206
                        case mender::http::StatusInternalServerError:
207
                                api_handler(expected::unexpected(MakeHTTPResponseError(
×
208
                                        APIError, resp, response_body, "Failed to authorize with the server.")));
×
209
                                return;
×
210
                        default:
2✔
211
                                mlog::Error("Unexpected error code " + resp->GetStatusMessage());
2✔
212
                                api_handler(expected::unexpected(MakeError(
4✔
213
                                        ResponseError, "Unexpected error code: " + resp->GetStatusMessage())));
6✔
214
                                return;
2✔
215
                        }
216
                });
13✔
217
}
218
} // namespace http
219

220
error::Error FetchJWTToken(
13✔
221
        mender::http::Client &client,
222
        const string &server_url,
223
        const string &private_key_path,
224
        const string &device_identity_script_path,
225
        APIResponseHandler api_handler,
226
        const string &tenant_token) {
227
        return http::FetchJWTToken(
228
                client,
229
                server_url,
230
                private_key_path,
231
                device_identity_script_path,
232
                api_handler,
233
                tenant_token);
13✔
234
}
235

236
void Authenticator::ExpireToken() {
13✔
237
        unique_lock<mutex> lock {auth_lock_};
26✔
238
        token_ = optional::nullopt;
13✔
239
}
13✔
240

241
void Authenticator::RunPendingActions(ExpectedToken ex_token) {
11✔
242
        unique_lock<mutex> lock {auth_lock_};
22✔
243
        for (auto action : pending_actions_) {
13✔
244
                loop_.Post([action, ex_token]() { action(ex_token); });
4✔
245
        }
246
        pending_actions_.clear();
11✔
247
}
11✔
248

249
error::Error Authenticator::WithToken(AuthenticatedAction action) {
15✔
250
        unique_lock<mutex> lock {auth_lock_};
30✔
251
        if (token_) {
15✔
252
                string token = *token_;
4✔
253
                lock.unlock();
2✔
254
                action(ExpectedToken(token));
2✔
255
                return error::NoError;
2✔
256
        }
257
        // else => no token
258
        if (auth_in_progress_) {
13✔
259
                pending_actions_.push_back(action);
2✔
260
                lock.unlock();
2✔
261
                return error::NoError;
2✔
262
        }
263
        // else => should fetch the token, cache it and call all pending actions
264
        auth_in_progress_ = true;
11✔
265
        lock.unlock();
11✔
266

267
        return FetchJWTToken(
268
                client_,
11✔
269
                server_url_,
11✔
270
                private_key_path_,
11✔
271
                device_identity_script_path_,
11✔
272
                [this, action](APIResponse resp) {
49✔
273
                        if (resp) {
11✔
274
                                unique_lock<mutex> lock {auth_lock_};
9✔
275
                                token_ = resp.value();
9✔
276
                                auth_in_progress_ = false;
9✔
277
                        }
278
                        action(resp);
11✔
279
                        RunPendingActions(resp);
11✔
280
                },
11✔
281
                tenant_token_);
11✔
282
}
283

284
} // namespace auth
285
} // namespace api
286
} // namespace mender
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc