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

mendersoftware / mender / 1046240096

23 Oct 2023 01:07PM UTC coverage: 80.229% (-0.1%) from 80.369%
1046240096

push

gitlab-ci

oleorhagen
fix(artifact): Add support for verifying our custom EC signature format

This adds support for verifying our custom EC signature marshalling from our
Mender-Artifact format/tool.

Somewhat surprisingly, it turns out that we do not encode the `EC` signature in
any known binary encoding format.

Instead we simply concatenate the integers (r & s) in this instance, into a
binary array, and write it to file.

This brings a host of issues obviously (where hard to understand is just one of
them). The other is that not encoding it in an architecture independent format
is that the signature will only be verified on the architecture it was made (it
needs the right byte-order), so Big- and Little-endian makes a difference here.

To accomodate this, the new client will try and decode both integers in both
Little- and Big-endian format, then encode it into `ASN.1` and `DER`, and then
pass it on to our regular verification code.

Ticket: MEN-6671
Changelog: None

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

48 of 48 new or added lines in 1 file covered. (100.0%)

6874 of 8568 relevant lines covered (80.23%)

9387.78 hits per line

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

85.14
/common/conf/conf.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 <common/conf.hpp>
16

17
#include <string>
18
#include <cstdlib>
19
#include <cerrno>
20

21
#include <mender-version.h>
22

23
#include <common/error.hpp>
24
#include <common/expected.hpp>
25
#include <common/log.hpp>
26
#include <common/json.hpp>
27

28
namespace mender {
29
namespace common {
30
namespace conf {
31

32
using namespace std;
33
namespace error = mender::common::error;
34
namespace expected = mender::common::expected;
35
namespace log = mender::common::log;
36
namespace json = mender::common::json;
37

38
const string kMenderVersion = MENDER_VERSION;
39

40
const ConfigErrorCategoryClass ConfigErrorCategory;
41

42
const char *ConfigErrorCategoryClass::name() const noexcept {
×
43
        return "ConfigErrorCategory";
×
44
}
45

46
string ConfigErrorCategoryClass::message(int code) const {
10✔
47
        switch (code) {
10✔
48
        case NoError:
49
                return "Success";
×
50
        case InvalidOptionsError:
51
                return "Invalid options given";
10✔
52
        }
53
        assert(false);
54
        return "Unknown";
×
55
}
56

57
error::Error MakeError(ConfigErrorCode code, const string &msg) {
5✔
58
        return error::Error(error_condition(code, ConfigErrorCategory), msg);
66✔
59
}
60

61

62
string GetEnv(const string &var_name, const string &default_value) {
1,873✔
63
        const char *value = getenv(var_name.c_str());
1,873✔
64
        if (value == nullptr) {
1,873✔
65
                return string(default_value);
1,872✔
66
        } else {
67
                return string(value);
1✔
68
        }
69
}
70

71
ExpectedOptionValue CmdlineOptionsIterator::Next() {
501✔
72
        string option = "";
501✔
73
        string value = "";
501✔
74

75
        if (start_ + pos_ >= end_) {
501✔
76
                return ExpectedOptionValue({"", ""});
160✔
77
        }
78

79
        if (past_double_dash_) {
341✔
80
                OptionValue opt_val {"", start_[pos_]};
6✔
81
                pos_++;
3✔
82
                return ExpectedOptionValue(opt_val);
3✔
83
        }
84

85
        if (start_[pos_] == "--") {
338✔
86
                past_double_dash_ = true;
1✔
87
                pos_++;
1✔
88
                return ExpectedOptionValue({"--", ""});
1✔
89
        }
90

91
        if (start_[pos_][0] == '-') {
337✔
92
                auto eq_idx = start_[pos_].find('=');
133✔
93
                if (eq_idx != string::npos) {
133✔
94
                        option = start_[pos_].substr(0, eq_idx);
3✔
95
                        value = start_[pos_].substr(eq_idx + 1, start_[pos_].size() - eq_idx - 1);
3✔
96
                        pos_++;
3✔
97
                } else {
98
                        option = start_[pos_];
130✔
99
                        pos_++;
130✔
100
                }
101

102
                if (opts_with_value_.count(option) != 0) {
133✔
103
                        // option with value
104
                        if ((value == "") && ((start_ + pos_ >= end_) || (start_[pos_][0] == '-'))) {
111✔
105
                                // the next item is not a value
106
                                error::Error err = MakeError(
107
                                        ConfigErrorCode::InvalidOptionsError, "Option " + option + " missing value");
4✔
108
                                return ExpectedOptionValue(expected::unexpected(err));
4✔
109
                        } else if (value == "") {
109✔
110
                                // only assign the next item as value if there was no value
111
                                // specified as '--opt=value' (parsed above)
112
                                value = start_[pos_];
107✔
113
                                pos_++;
107✔
114
                        }
115
                } else if (opts_wo_value_.count(option) == 0) {
22✔
116
                        // unknown option
117
                        error::Error err = MakeError(
118
                                ConfigErrorCode::InvalidOptionsError, "Unrecognized option '" + option + "'");
16✔
119
                        return ExpectedOptionValue(expected::unexpected(err));
16✔
120
                } else if (value != "") {
14✔
121
                        // option without a value, yet, there was a value specified as '--opt=value' (parsed
122
                        // above)
123
                        error::Error err = MakeError(
124
                                ConfigErrorCode::InvalidOptionsError,
125
                                "Option " + option + " doesn't expect a value");
2✔
126
                        return ExpectedOptionValue(expected::unexpected(err));
2✔
127
                }
128
        } else {
129
                switch (mode_) {
204✔
130
                case ArgumentsMode::AcceptBareArguments:
50✔
131
                        value = start_[pos_];
132
                        pos_++;
50✔
133
                        break;
50✔
134
                case ArgumentsMode::RejectBareArguments:
50✔
135
                        return expected::unexpected(MakeError(
50✔
136
                                ConfigErrorCode::InvalidOptionsError,
137
                                "Unexpected argument '" + start_[pos_] + "'"));
150✔
138
                case ArgumentsMode::StopAtBareArguments:
139
                        return ExpectedOptionValue({"", ""});
104✔
140
                }
141
        }
142

143
        return ExpectedOptionValue({std::move(option), std::move(value)});
172✔
144
}
145

146
expected::ExpectedSize MenderConfig::ProcessCmdlineArgs(
119✔
147
        vector<string>::const_iterator start, vector<string>::const_iterator end, const CliApp &app) {
148
        bool explicit_config_path = false;
149
        bool explicit_fallback_config_path = false;
150
        string log_file = "";
119✔
151
        string log_level;
152
        string trusted_cert;
153
        bool skip_verify_arg = false;
154
        bool version_arg = false;
155
        bool help_arg = false;
156

157
        CmdlineOptionsIterator opts_iter(
158
                start, end, GlobalOptsSetWithValue(), GlobalOptsSetWithoutValue());
357✔
159
        opts_iter.SetArgumentsMode(ArgumentsMode::StopAtBareArguments);
160
        auto ex_opt_val = opts_iter.Next();
119✔
161
        int arg_count = 0;
162
        while (ex_opt_val && ((ex_opt_val.value().option != "") || (ex_opt_val.value().value != ""))) {
225✔
163
                arg_count++;
110✔
164
                auto opt_val = ex_opt_val.value();
110✔
165
                if ((opt_val.option == "--config") || (opt_val.option == "-c")) {
110✔
166
                        paths.SetConfFile(opt_val.value);
167
                        explicit_config_path = true;
168
                } else if ((opt_val.option == "--fallback-config") || (opt_val.option == "-b")) {
107✔
169
                        paths.SetFallbackConfFile(opt_val.value);
170
                        explicit_fallback_config_path = true;
171
                } else if ((opt_val.option == "--data") || (opt_val.option == "-d")) {
107✔
172
                        paths.SetDataStore(opt_val.value);
97✔
173
                } else if ((opt_val.option == "--log-file") || (opt_val.option == "-L")) {
10✔
174
                        log_file = opt_val.value;
175
                } else if ((opt_val.option == "--log-level") || (opt_val.option == "-l")) {
10✔
176
                        log_level = opt_val.value;
177
                } else if ((opt_val.option == "--trusted-certs") || (opt_val.option == "-E")) {
8✔
178
                        trusted_cert = opt_val.value;
179
                } else if (opt_val.option == "--skipverify") {
8✔
180
                        skip_verify_arg = true;
181
                } else if ((opt_val.option == "--version") || (opt_val.option == "-v")) {
8✔
182
                        version_arg = true;
183
                } else if ((opt_val.option == "--help") || (opt_val.option == "-h")) {
4✔
184
                        help_arg = true;
185
                        break;
4✔
186
                } else {
187
                        assert(false);
188
                }
189
                ex_opt_val = opts_iter.Next();
212✔
190
        }
191
        if (!ex_opt_val) {
119✔
192
                return expected::unexpected(ex_opt_val.error());
×
193
        }
194

195
        if (version_arg) {
119✔
196
                if (arg_count > 1 || opts_iter.GetPos() < static_cast<size_t>(end - start)) {
4✔
197
                        return expected::unexpected(error::Error(
2✔
198
                                make_error_condition(errc::invalid_argument),
4✔
199
                                "--version can not be combined with other commands and arguments"));
6✔
200
                } else {
201
                        cout << kMenderVersion << endl;
2✔
202
                        return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
6✔
203
                }
204
        }
205

206
        if (help_arg) {
115✔
207
                PrintCliHelp(app);
4✔
208
                return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
12✔
209
        }
210

211
        if (log_file != "") {
111✔
212
                auto err = log::SetupFileLogging(log_file, true);
×
213
                if (error::NoError != err) {
×
214
                        return expected::unexpected(err);
×
215
                }
216
        }
217

218
        SetLevel(log::kDefaultLogLevel);
111✔
219

220
        if (log_level != "") {
111✔
221
                auto ex_log_level = log::StringToLogLevel(log_level);
2✔
222
                if (!ex_log_level) {
2✔
223
                        return expected::unexpected(ex_log_level.error());
×
224
                }
225
                SetLevel(ex_log_level.value());
2✔
226
        }
227

228
        auto err = LoadConfigFile_(paths.GetConfFile(), explicit_config_path);
111✔
229
        if (error::NoError != err) {
111✔
230
                this->Reset();
×
231
                return expected::unexpected(err);
×
232
        }
233

234
        err = LoadConfigFile_(paths.GetFallbackConfFile(), explicit_fallback_config_path);
222✔
235
        if (error::NoError != err) {
111✔
236
                this->Reset();
×
237
                return expected::unexpected(err);
×
238
        }
239

240
        if (this->update_log_path != "") {
111✔
241
                paths.SetUpdateLogPath(this->update_log_path);
242
        }
243

244
        if (log_level == "" && this->daemon_log_level != "") {
111✔
245
                auto ex_log_level = log::StringToLogLevel(this->daemon_log_level);
1✔
246
                if (!ex_log_level) {
1✔
247
                        return expected::unexpected(ex_log_level.error());
×
248
                }
249
                SetLevel(ex_log_level.value());
1✔
250
        }
251

252
        if (trusted_cert != "") {
111✔
253
                this->server_certificate = trusted_cert;
×
254
        }
255

256
        if (skip_verify_arg) {
111✔
257
                this->skip_verify = true;
×
258
        }
259

260
        http_client_config_.server_cert_path = server_certificate;
111✔
261
        http_client_config_.client_cert_path = https_client.certificate;
111✔
262
        http_client_config_.client_cert_key_path = https_client.key;
111✔
263
        http_client_config_.skip_verify = skip_verify;
111✔
264

265
        auto proxy = http::GetHttpProxyStringFromEnvironment();
111✔
266
        if (proxy) {
111✔
267
                http_client_config_.http_proxy = proxy.value();
110✔
268
        } else {
269
                return expected::unexpected(proxy.error());
2✔
270
        }
271

272
        proxy = http::GetHttpsProxyStringFromEnvironment();
220✔
273
        if (proxy) {
110✔
274
                http_client_config_.https_proxy = proxy.value();
109✔
275
        } else {
276
                return expected::unexpected(proxy.error());
2✔
277
        }
278

279
        proxy = http::GetNoProxyStringFromEnvironment();
218✔
280
        if (proxy) {
109✔
281
                http_client_config_.no_proxy = proxy.value();
108✔
282
        } else {
283
                return expected::unexpected(proxy.error());
2✔
284
        }
285

286
        return opts_iter.GetPos();
287
}
288

289
error::Error MenderConfig::LoadConfigFile_(const string &path, bool required) {
222✔
290
        auto ret = this->LoadFile(path);
222✔
291
        if (!ret) {
222✔
292
                if (required) {
219✔
293
                        // any failure when a file is required (e.g. path was given explicitly) means an error
294
                        log::Error("Failed to load config from '" + path + "': " + ret.error().message);
×
295
                        return ret.error();
×
296
                } else if (ret.error().IsErrno(ENOENT)) {
219✔
297
                        // File doesn't exist, OK for non-required
298
                        log::Debug("Failed to load config from '" + path + "': " + ret.error().message);
438✔
299
                        return error::NoError;
219✔
300
                } else {
301
                        // other errors (parsing errors,...) for default paths should produce warnings
302
                        log::Warning("Failed to load config from '" + path + "': " + ret.error().message);
×
303
                        return error::NoError;
×
304
                }
305
        }
306
        // else
307
        auto valid = this->ValidateConfig();
3✔
308
        if (!valid) {
3✔
309
                // validation error is always an error
310
                log::Error("Failed to validate config from '" + path + "': " + valid.error().message);
×
311
                return valid.error();
×
312
        }
313

314
        return error::NoError;
3✔
315
}
316

317
} // namespace conf
318
} // namespace common
319
} // 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