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