1 /*
2 * Copyright (C) 2020-2021 Bareos GmbH & Co. KG
3 * Copyright (C) 2010 SCALITY SA. All rights reserved.
4 * http://www.scality.com
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 * Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 *
13 * Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY SCALITY SA ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL SCALITY SA OR CONTRIBUTORS BE LIABLE FOR
21 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * The views and conclusions contained in the software and documentation
30 * are those of the authors and should not be interpreted as representing
31 * official policies, either expressed or implied, of SCALITY SA.
32 *
33 * https://github.com/scality/Droplet
34 */
35 #include "dropletp.h"
36 #include <droplet/uks/uks.h>
37 #include <sys/param.h>
38
39 /**
40 * @defgroup scality Scality specific functions
41 * @addtogroup scality
42 * @{
43 * Functions specific to Scality backends
44 *
45 * This module contains utility functions for dealing with the Scality
46 * backend cloud provider.
47 *
48 * Several of these functions deal with Scality's UKS (Universal Key Scheme)
49 * which is the binary object ID format used in Scality's RING software.
50 * UKS keys are 160 bits long and are divided into several fixed-length
51 * fields which encode specific information. Fields include:
52 *
53 * @arg @b hashing/dispersion (24b) used to help distribute objects
54 * around multiple servers in a ring, normally an MD5 hash of the
55 * payload field.
56 *
57 * @arg @b payload (128b) Further information, broken out below.
58 *
59 * @arg @b class (4b). Classes from 0-5 specify the number of *additional*
60 * copies to be stored, i.e. class 2 means 3 copies will be stored.
61 * Some of the class numbers 6-15 are used for other purposes.
62 *
63 * @arg @b replica (4b). The replica number for classes 0-5, i.e. 0 for
64 * the first copy, 1 for the second copy.
65 *
66 * You can store anything you like in the @b payload field, but a
67 * recommended format is to use the following bit fields.
68 *
69 * @arg @b Object @b ID (64b). Identifies an object, e.g. an inode number.
70 *
71 * @arg @b Volume @b ID (32b). Identifies a virtual volume, e.g. a
72 * filesystem.
73 *
74 * @arg @b Application @b ID (8b). Also known as @b Service @b ID. A fixed
75 * field identifying your application, to allow multiple applications to use
76 * the same RING storage. To avoid clashing with future Scality software,
77 * use a number greater than 0xc0.
78 *
79 * @arg @b Application @b Specific (24b), e.g. block offset within
80 * the file.
81 */
82
83
84 //#define DPRINTF(fmt,...) fprintf(stderr, fmt, ##__VA_ARGS__)
85 #define DPRINTF(fmt, ...)
86
87 #define BIT_SET(bit) (entropy[(bit) / NBBY] |= (1 << ((bit) % NBBY)))
88 #define BIT_CLEAR(bit) (entropy[(bit) / NBBY] &= ~(1 << ((bit) % NBBY)))
89
90 /**
91 * Generate a UKS key from raw data
92 *
93 * Generates a binary UKS key in @a id using raw data for each of the
94 * bitfields in the generic format. Note the class and replica fields
95 * should be set separately using `dpl_uks_set_class()` and
96 * `dpl_uks_set_replica()`. The binary key @a id is stored in a
97 * `BIGNUM` structure, which you should create with `BN_new()` and
98 * free with `BN_free()`.
99 *
100 * Only use this function if you know what you are doing. Note
101 * particularly that this function requires you to calculate and set the
102 * hashing/dispersion field yourself. For most applications you should
103 * use either `dpl_uks_gen_key_ext()` or `dpl_uks_gen_key()` which will
104 * calculate the hashing/dispersion field for you.
105 *
106 * @param id the binary UKS key will be generated here
107 * @param hash will be used as the hashing/dispersion field
108 * @param oid will be used as the Object ID field
109 * @param volid will be used as the Volume ID field
110 * @param serviceid will be used as the Service ID field
111 * @param specific will be used as the Application Specific field
112 * @retval DPL_SUCCESS this function currently cannot fail
113 */
dpl_uks_gen_key_raw(BIGNUM * id,uint32_t hash,uint64_t oid,uint32_t volid,uint8_t serviceid,uint32_t specific)114 dpl_status_t dpl_uks_gen_key_raw(BIGNUM* id,
115 uint32_t hash,
116 uint64_t oid,
117 uint32_t volid,
118 uint8_t serviceid,
119 uint32_t specific)
120 {
121 int off, i;
122
123 BN_zero(id);
124
125 off = DPL_UKS_REPLICA_NBITS + DPL_UKS_CLASS_NBITS;
126
127 for (i = 0; i < DPL_UKS_SPECIFIC_NBITS; i++) {
128 if (specific & 1 << i) {
129 BN_set_bit(id, off + i);
130 } else {
131 BN_clear_bit(id, off + i);
132 }
133 }
134
135 off += DPL_UKS_SPECIFIC_NBITS;
136
137 for (i = 0; i < DPL_UKS_SERVICEID_NBITS; i++) {
138 if (serviceid & 1 << i) {
139 BN_set_bit(id, off + i);
140 } else {
141 BN_clear_bit(id, off + i);
142 }
143 }
144
145 off += DPL_UKS_SERVICEID_NBITS;
146
147 for (i = 0; i < DPL_UKS_VOLID_NBITS; i++) {
148 if (volid & 1 << i) {
149 BN_set_bit(id, off + i);
150 } else {
151 BN_clear_bit(id, off + i);
152 }
153 }
154
155 off += DPL_UKS_VOLID_NBITS;
156
157 for (i = 0; i < DPL_UKS_OID_NBITS; i++) {
158 if (oid & (1ULL << i)) {
159 BN_set_bit(id, off + i);
160 } else {
161 BN_clear_bit(id, off + i);
162 }
163 }
164
165 off += DPL_UKS_OID_NBITS;
166
167 for (i = 0; i < DPL_UKS_HASH_NBITS; i++) {
168 if (hash & (1ULL << i)) {
169 BN_set_bit(id, off + i);
170 } else {
171 BN_clear_bit(id, off + i);
172 }
173 }
174
175 return DPL_SUCCESS;
176 }
177
178 /**
179 * Set some fields in a binary UKS key.
180 *
181 * Sets some fields in a binary UKS key, according to a mask of which
182 * fields to set. Fields not specified in the mask are preserved.
183 * Automatically recalculates the hashing/dispersion field. You should
184 * also call `dpl_uks_set_class()` to set the class field. The binary
185 * key @a id is stored in a `BIGNUM` structure, which you should create
186 * with `BN_new()` and free with `BN_free()`.
187 *
188 * @param id the binary UKS key
189 * @param mask a bitmask of the following values indicating which fields to set
190 * @arg `DPL_UKS_MASK_OID` use the @a oid parameter as the Object ID field
191 * @arg `DPL_UKS_MASK_VOLID` use the @a volid parameter as the Volume ID field
192 * @arg `DPL_UKS_MASK_SERVICEID` use the @serviceid parameter as the
193 * Service ID field
194 * @arg `DPL_UKS_MASK_SPECIFIC` use the @specific parameter as the
195 * Application Specific field.
196 * @param oid will be used as the Object ID field if `DPL_UKS_MASK_OID`
197 * is present in @a mask
198 * @param volid will be used as the Volume ID field if
199 * `DPL_UKS_MASK_VOLID` is present in @a mask
200 * @param serviceid will be used as the Service ID field if
201 * `DPL_UKS_MASK_SERVICEID` is present in @a mask
202 * @param specific will be used as the Application Specific field if
203 * `DPL_UKS_MASK_SPECIFIC` is present in @a mask
204 * @retval DPL_SUCCESS on success, or
205 * @retval DPL_* a Droplet error code on failure
206 */
dpl_uks_gen_key_ext(BIGNUM * id,dpl_uks_mask_t mask,uint64_t oid,uint32_t volid,uint8_t serviceid,uint32_t specific)207 dpl_status_t dpl_uks_gen_key_ext(BIGNUM* id,
208 dpl_uks_mask_t mask,
209 uint64_t oid,
210 uint32_t volid,
211 uint8_t serviceid,
212 uint32_t specific)
213 {
214 int off, i;
215 MD5_CTX ctx;
216 char entropy[DPL_UKS_PAYLOAD_NBITS / NBBY];
217 u_char hash[MD5_DIGEST_LENGTH];
218
219 off = DPL_UKS_REPLICA_NBITS + DPL_UKS_CLASS_NBITS;
220
221 memset(entropy, 0, sizeof(entropy));
222 if (!(mask & DPL_UKS_MASK_SPECIFIC)) {
223 specific = 0;
224 for (i = 0; i < DPL_UKS_SPECIFIC_NBITS; i++) {
225 if (BN_is_bit_set(id, off + i)) { specific |= (1 << i); }
226 }
227 }
228
229 for (i = 0; i < DPL_UKS_SPECIFIC_NBITS; i++) {
230 if (specific & 1 << i) {
231 BN_set_bit(id, off + i);
232 BIT_SET(off - DPL_UKS_EXTRA_NBITS + i);
233 } else {
234 BN_clear_bit(id, off + i);
235 BIT_CLEAR(off - DPL_UKS_EXTRA_NBITS + i);
236 }
237 }
238
239 off += DPL_UKS_SPECIFIC_NBITS;
240
241 if (!(mask & DPL_UKS_MASK_SERVICEID)) {
242 serviceid = 0;
243 for (i = 0; i < DPL_UKS_SERVICEID_NBITS; i++) {
244 if (BN_is_bit_set(id, off + i)) { serviceid |= (1 << i); }
245 }
246 }
247
248 for (i = 0; i < DPL_UKS_SERVICEID_NBITS; i++) {
249 if (serviceid & 1 << i) {
250 BN_set_bit(id, off + i);
251 BIT_SET(off - DPL_UKS_EXTRA_NBITS + i);
252 } else {
253 BN_clear_bit(id, off + i);
254 BIT_CLEAR(off - DPL_UKS_EXTRA_NBITS + i);
255 }
256 }
257
258 off += DPL_UKS_SERVICEID_NBITS;
259
260 if (!(mask & DPL_UKS_MASK_VOLID)) {
261 volid = 0;
262 for (i = 0; i < DPL_UKS_VOLID_NBITS; i++) {
263 if (BN_is_bit_set(id, off + i)) { volid |= (1 << i); }
264 }
265 }
266
267 for (i = 0; i < DPL_UKS_VOLID_NBITS; i++) {
268 if (volid & 1 << i) {
269 BN_set_bit(id, off + i);
270 BIT_SET(off - DPL_UKS_EXTRA_NBITS + i);
271 } else {
272 BN_clear_bit(id, off + i);
273 BIT_CLEAR(off - DPL_UKS_EXTRA_NBITS + i);
274 }
275 }
276
277 off += DPL_UKS_VOLID_NBITS;
278
279 if (!(mask & DPL_UKS_MASK_OID)) {
280 oid = 0;
281 for (i = 0; i < DPL_UKS_OID_NBITS; i++) {
282 if (BN_is_bit_set(id, off + i)) { oid |= (1 << i); }
283 }
284 }
285
286 for (i = 0; i < DPL_UKS_OID_NBITS; i++) {
287 if (oid & (1ULL << i)) {
288 BN_set_bit(id, off + i);
289 BIT_SET(off - DPL_UKS_EXTRA_NBITS + i);
290 } else {
291 BN_clear_bit(id, off + i);
292 BIT_CLEAR(off - DPL_UKS_EXTRA_NBITS + i);
293 }
294 }
295
296 off += DPL_UKS_OID_NBITS;
297
298 MD5_Init(&ctx);
299 MD5_Update(&ctx, entropy, sizeof(entropy));
300 MD5_Final(hash, &ctx);
301
302 for (i = 0; i < DPL_UKS_HASH_NBITS; i++) {
303 if (hash[i / 8] & 1 << (i % 8))
304 BN_set_bit(id, off + i);
305 else
306 BN_clear_bit(id, off + i);
307 }
308
309 return DPL_SUCCESS;
310 }
311
312 /**
313 * Generate a binary UKS key.
314 *
315 * Sets all the fields in a binary UKS key and automatically calculates
316 * the hashing/dispersion field. You should also call `dpl_uks_set_class()`
317 * to set the class field. The binary key @a id is stored in a `BIGNUM`
318 * structure, which you should create with `BN_new()` and free with `BN_free()`.
319 *
320 * @param id the binary UKS key
321 * @param oid will be used as the Object ID field
322 * @param volid will be used as the Volume ID field
323 * @param serviceid will be used as the Service ID field
324 * @param specific will be used as the Application Specific field
325 * @retval DPL_SUCCESS on success, or
326 * @retval DPL_* a Droplet error code on failure
327 */
dpl_uks_gen_key(BIGNUM * id,uint64_t oid,uint32_t volid,uint8_t serviceid,uint32_t specific)328 dpl_status_t dpl_uks_gen_key(BIGNUM* id,
329 uint64_t oid,
330 uint32_t volid,
331 uint8_t serviceid,
332 uint32_t specific)
333 {
334 return dpl_uks_gen_key_ext(id, ~0, oid, volid, serviceid, specific);
335 }
336
337 /**
338 * Get the hash field from a UKS key.
339 *
340 * Get the hash field from a UKS key. The binary key @a id is stored in a
341 * `BIGNUM` structure, which you should create with `BN_new()` and free with
342 * `BN_free()`.
343 *
344 * @param k the binary UKS key
345 * @retval the value of the hash
346 */
dpl_uks_hash_get(BIGNUM * k)347 uint32_t dpl_uks_hash_get(BIGNUM* k)
348 {
349 int i;
350 int hash = 0;
351
352 for (i = 0; i < DPL_UKS_HASH_NBITS; i++) {
353 if (BN_is_bit_set(k, DPL_UKS_PAYLOAD_NBITS + i)) hash |= 1 << i;
354 }
355
356 return hash;
357 }
358
359
360 /**
361 * Set the hash field in a UKS key.
362 *
363 * Set the hash field in a UKS key. The binary key @a id is stored in a
364 * `BIGNUM` structure, which you should create with `BN_new()` and free with
365 * `BN_free()`.
366 *
367 * @param k the binary UKS key
368 * @param hash will be used as the class field
369 * @retval DPL_SUCCESS on success, or
370 * @retval DPL_* a Droplet error code on failure
371 */
dpl_uks_hash_set(BIGNUM * k,uint32_t hash)372 dpl_status_t dpl_uks_hash_set(BIGNUM* k, uint32_t hash)
373 {
374 int i;
375
376 if (hash < 0 || hash >= (1 << DPL_UKS_HASH_NBITS)) return DPL_FAILURE;
377
378 for (i = 0; i < DPL_UKS_HASH_NBITS; i++) {
379 if (hash & 1 << i)
380 BN_set_bit(k, DPL_UKS_PAYLOAD_NBITS + i);
381 else
382 BN_clear_bit(k, DPL_UKS_PAYLOAD_NBITS + i);
383 }
384
385 return DPL_SUCCESS;
386 }
387
388 /**
389 * Set the class field in a UKS key.
390 *
391 * Set the class field in a UKS key. The binary key @a id is stored in a
392 * `BIGNUM` structure, which you should create with `BN_new()` and free
393 * with `BN_free()`.
394 *
395 * @param k the binary UKS key
396 * @param cl will be used as the class field
397 * @retval DPL_SUCCESS on success, or
398 * @retval DPL_* a Droplet error code on failure
399 */
dpl_uks_set_class(BIGNUM * k,int cl)400 dpl_status_t dpl_uks_set_class(BIGNUM* k, int cl)
401 {
402 int i;
403
404 if (cl < 0 || cl >= 1 << DPL_UKS_CLASS_NBITS) return DPL_FAILURE;
405
406 for (i = 0; i < DPL_UKS_CLASS_NBITS; i++)
407 if (cl & 1 << i)
408 BN_set_bit(k, DPL_UKS_REPLICA_NBITS + i);
409 else
410 BN_clear_bit(k, DPL_UKS_REPLICA_NBITS + i);
411
412 return DPL_SUCCESS;
413 }
414
415 /**
416 * Set the replica field in a UKS key.
417 *
418 * Set the replica field in a UKS key. The binary key @a id is stored in a
419 * `BIGNUM` structure, which you should create with `BN_new()` and free
420 * with `BN_free()`.
421 *
422 * @param k the binary UKS key
423 * @param replica will be used as the replica field
424 * @retval DPL_SUCCESS on success, or
425 * @retval DPL_* a Droplet error code on failure
426 */
dpl_uks_set_replica(BIGNUM * k,int replica)427 dpl_status_t dpl_uks_set_replica(BIGNUM* k, int replica)
428 {
429 int i;
430
431 if (replica < 0 || replica >= 6) return DPL_FAILURE;
432
433 for (i = 0; i < DPL_UKS_REPLICA_NBITS; i++) {
434 if (replica & 1 << i)
435 BN_set_bit(k, i);
436 else
437 BN_clear_bit(k, i);
438 }
439
440 return DPL_SUCCESS;
441 }
442
dpl_uks_gen_random_key(dpl_ctx_t * ctx,dpl_storage_class_t storage_class,char * custom,char * id_buf,int max_len)443 dpl_status_t dpl_uks_gen_random_key(dpl_ctx_t* ctx,
444 dpl_storage_class_t storage_class,
445 char* custom,
446 char* id_buf,
447 int max_len)
448 {
449 BIGNUM* bn = NULL;
450 char* id_str = NULL;
451 dpl_status_t ret, ret2;
452 int len, padding;
453 int class = 0;
454
455 bn = BN_new();
456 if (NULL == bn) {
457 ret = DPL_ENOMEM;
458 goto end;
459 }
460
461 ret2 = dpl_uks_gen_key(bn, dpl_rand_u64(), dpl_rand_u32(), 0, dpl_rand_u32());
462 if (DPL_SUCCESS != ret2) {
463 ret = ret2;
464 goto end;
465 }
466
467 switch (storage_class) {
468 case DPL_STORAGE_CLASS_UNDEF:
469 case DPL_STORAGE_CLASS_STANDARD:
470 case DPL_STORAGE_CLASS_STANDARD_IA:
471 class = 2;
472 break;
473 case DPL_STORAGE_CLASS_REDUCED_REDUNDANCY:
474 class = 1;
475 break;
476 case DPL_STORAGE_CLASS_CUSTOM:
477
478 if (NULL == custom) {
479 ret = DPL_EINVAL;
480 goto end;
481 }
482
483 class = atoi(custom);
484
485 if (class < 0 || class > 15) {
486 ret = DPL_EINVAL;
487 goto end;
488 }
489 break;
490 }
491
492 dpl_uks_set_class(bn, class);
493
494 id_str = BN_bn2hex(bn);
495 if (NULL == id_str) {
496 ret = DPL_ENOMEM;
497 goto end;
498 }
499
500 len = snprintf(id_buf, max_len, "%s", id_str);
501 if (len >= max_len) {
502 ret = DPL_ENAMETOOLONG;
503 goto end;
504 }
505
506 padding = DPL_UKS_BCH_LEN - strlen(id_buf);
507 if (padding > 0) {
508 int i;
509
510 memmove(id_buf + padding, id_buf, strlen(id_buf));
511 for (i = 0; i < padding; i++) id_buf[i] = '0';
512 }
513
514 ret = DPL_SUCCESS;
515
516 end:
517
518 free(id_str);
519 BN_free(bn);
520
521 return ret;
522 }
523
524 /**
525 * Convert a binary UKS key to string form.
526 *
527 * Converts the binary UKS key in @a id to a string form in
528 * @a id_str. The string form is suitable for use as the @a id
529 * parameter of the RESTful functions such as `dpl_put_id()`.
530 * The binary key @a id is stored in a `BIGNUM` structure, which
531 * you should create with `BN_new()` and free with `BN_free()`.
532 *
533 * @param id the binary UKS key
534 * @param[out] id_str on success the string form is written here, must
535 * be at least `DPL_UKS_BCH_LEN+1` bytes long
536 * @retval DPL_SUCCESS on success, or
537 * @retval DPL_* a Droplet error code on failure
538 */
dpl_uks_bn2hex(const BIGNUM * id,char * id_str)539 dpl_status_t dpl_uks_bn2hex(const BIGNUM* id, char* id_str)
540 {
541 int ret;
542 int tmp_str_len = 0;
543 char* tmp_str = BN_bn2hex(id);
544
545 if (!tmp_str) {
546 ret = DPL_ENOMEM;
547 goto end;
548 }
549
550 tmp_str_len = strlen(tmp_str);
551 memset(id_str, '0', DPL_UKS_BCH_LEN);
552 memcpy(id_str + DPL_UKS_BCH_LEN - tmp_str_len, tmp_str, tmp_str_len);
553 id_str[DPL_UKS_BCH_LEN] = 0;
554
555 ret = DPL_SUCCESS;
556
557 end:
558
559 if (tmp_str) free(tmp_str);
560
561 return ret;
562 }
563
564 dpl_id_scheme_t dpl_id_scheme_uks = {
565 .name = "uks",
566 .gen_random_key = dpl_uks_gen_random_key,
567 };
568
569 /** @} */
570