Welcome to little lamb

Code » limb » commit 77074df

Add shldata.h & related functions

author Olivier Brunel
2023-04-30 20:21:14 UTC
committer Olivier Brunel
2023-05-20 18:06:39 UTC
parent a7fa2570a72d7591e2e0cc5ebe48ede7611d8329

Add shldata.h & related functions

src/doc/hasher.h.0.md +23 -0
src/doc/hasher.h/hasher_hash.3.md +23 -8
src/doc/patrim.5.md +5 -0
src/doc/patrim.h.0.md +3 -0
src/doc/patrim.h/patrim_put.3.md +9 -0
src/doc/shldata.5.md +49 -0
src/doc/shldata.h.0.md +52 -0
src/doc/shldata.h/shldata_initr.3.md +88 -0
src/doc/shldata.h/shldata_initw.3.md +107 -0
src/include/shldata.h +18 -0
src/liblimb/hasher.h/hashers.c +19 -0
src/liblimb/include/limb/hasher.h +18 -0
src/liblimb/include/limb/patrim.h +1 -0
src/liblimb/include/limb/shldata.h +30 -0
src/liblimb/shldata.h/shldata_decrypt.c +16 -0
src/liblimb/shldata.h/shldata_encrypt.c +17 -0
src/liblimb/shldata.h/shldata_finalr.c +31 -0
src/liblimb/shldata.h/shldata_finalw.c +26 -0
src/liblimb/shldata.h/shldata_initr.c +75 -0
src/liblimb/shldata.h/shldata_initw.c +61 -0
src/liblimb/shldata.h/shldata_predata.c +23 -0

diff --git a/src/doc/hasher.h.0.md b/src/doc/hasher.h.0.md
index f5482c0..ebc1159 100644
--- a/src/doc/hasher.h.0.md
+++ b/src/doc/hasher.h.0.md
@@ -27,6 +27,18 @@ The following types are defined :
 : *hasher_final*
 :: Generic pointer function for computing/obtaining the message digest
 
+## Constants
+
+The following constants are defined :
+
+: *ALGO_SHA1*, *ALGO_SHA256*, *ALGO_SHA512*, *ALGO_SHA3_224*, *ALGO_SHA3_256*,
+: *ALGO_SHA3_384*, *ALGO_SHA3_512*, *ALGO_BLAKE3*
+:: Indices to identify each of the algorithms for which a hasher is available
+:: in the arrays *algos* and *hashers*.
+
+: *NB_AGLOS*
+:: Number of elements/algorithms.
+
 ## Structures
 
 The following structures are defined :
@@ -35,6 +47,17 @@ The following structures are defined :
 :: A structure representing a hasher, allowing to compute a message digest
 :: through the algorithm of said hasher.
 
+## Pointers
+
+The following pointers are defined :
+
+: *algos*
+:: Pointer to an NULL-terminated array of *NB_ALGOS* strings naming the
+:: algorithms.
+
+: *hashers*
+:: Pointer to an array of *NB_ALGOS* pointers to the available *hasher*s.
+
 ## Functions
 
 The following functions/macros are defined :
diff --git a/src/doc/hasher.h/hasher_hash.3.md b/src/doc/hasher.h/hasher_hash.3.md
index 598c7e6..d2fcd24 100644
--- a/src/doc/hasher.h/hasher_hash.3.md
+++ b/src/doc/hasher.h/hasher_hash.3.md
@@ -10,6 +10,10 @@ hasher\_hash, hinit, hupdate, hfinal - compute a message digest using a given ha
     #include <limb/hasher.h>
 
 ```pre hl
+const char * const algos[NB_ALGOS + 1]
+hasher * const hashers[NB_ALGOS]
+
+
 void hinit (hasher *<em>hasher</em>)
 void hupdate (const void *<em>msg</em>, size_t <em>mlen</em>, hasher *<em>hasher</em>)
 void hfinal (void *<em>digest</em>, hasher *<em>hasher</em>)
@@ -80,31 +84,42 @@ A few hashers are available, each define in their own header. In order to use a
 hasher, all you need is to include its header, which includes [hasher.h](0), and
 you can any of the functions described above.
 
+A constant *NB_ALGOS* is defined with the number of available hashers. Two
+pointers are defined : 
+
+- `algos` which points to a NULL-terminated array of strings, naming each of the
+  available hashers/algorithms.
+- `hashers` which points to an array of pointers to the available hashers.
+
+Obviously, indices in each array are referring to the same hasher/algorithm. In
+addition, constants are defined for each of the indices.
+
+
 The following hashers are available :
 
 : *blake3*
-:: Requires [hasher_blake3.h](0).
+:: Index *ALGO_BLAKE3*. Requires [hasher_blake3.h](0).
 
 : *sha1*
-:: Requires [hasher_sha1.h](0).
+:: Index *ALGO_SHA1*. Requires [hasher_sha1.h](0).
 
 : *sha256*
-:: Requires [hasher_sha256.h](0).
+:: Index *ALGO_SHA256*. Requires [hasher_sha256.h](0).
 
 : *sha512*
-:: Requires [hasher_sha512.h](0).
+:: Index *ALGO_SHA512*. Requires [hasher_sha512.h](0).
 
 : *sha3_224*
-:: Requires [hasher_sha3_224.h](0).
+:: Index *ALGO_SHA3_224*. Requires [hasher_sha3_224.h](0).
 
 : *sha3_256*
-:: Requires [hasher_sha3_256.h](0).
+:: Index *ALGO_SHA3_256*. Requires [hasher_sha3_256.h](0).
 
 : *sha3_384*
-:: Requires [hasher_sha3_384.h](0).
+:: Index *ALGO_SHA3_384*. Requires [hasher_sha3_384.h](0).
 
 : *sha3_512*
-:: Requires [hasher_sha3_512.h](0).
+:: Index *ALGO_SHA3_512*. Requires [hasher_sha3_512.h](0).
 
 ! NOTE:
 ! The hashers for SHA1, SHA256 and SHA512 are wrappers around functions from
diff --git a/src/doc/patrim.5.md b/src/doc/patrim.5.md
index 066c33c..e730398 100644
--- a/src/doc/patrim.5.md
+++ b/src/doc/patrim.5.md
@@ -42,6 +42,11 @@ header as such :
 - A big-endian encoded 32bit magic number, to identify the specific file format.
   The magic number should have certain bits set to indicate it is PATRIM
   encoded, specifically : `(magic & 0xf0f0f000) = 0xa0e0f000`
+- Additionally, bit 8 is used as a flag to indicate whether or not the file
+  contains shielded data, as described in [shldata](5).
+  When set, and shielded data are present, for every ID in the file its bit 7 is
+  a flag which, when set, indicates the data are part of the shielded data
+  protocol itself.
 - A version number, pack-trimmed.
 
 Helpers functions [buffer_puthdr](3) and [buffer_gethdr](3) are available to
diff --git a/src/doc/patrim.h.0.md b/src/doc/patrim.h.0.md
index 0e7bce8..fe9c272 100644
--- a/src/doc/patrim.h.0.md
+++ b/src/doc/patrim.h.0.md
@@ -23,6 +23,9 @@ The following functions/macros are defined :
 : [patrim_isint](3)
 :: Returns whether the given ID is that of an integer or not.
 
+: [patrim_isshldata](3)
+:: Returns whether the given ID belongs to the shielded data protocol or not.
+
 : [patrim_put](3)
 :: To encode data in [patrim](5) format.
 
diff --git a/src/doc/patrim.h/patrim_put.3.md b/src/doc/patrim.h/patrim_put.3.md
index 964a262..1c897a4 100644
--- a/src/doc/patrim.h/patrim_put.3.md
+++ b/src/doc/patrim.h/patrim_put.3.md
@@ -16,6 +16,7 @@ int patrim_get(u64 *<em>id</em>, u64 *<em>u</em>, const char *<em>data</em>, siz
 
 int patrim_isblob(u64 <em>id</em>)
 int patrim_isint(u64 <em>id</em>)
+int patrim_isshldata(u64 <em>id</em>)
 ```
 
 # DESCRIPTION
@@ -40,6 +41,10 @@ otherwise.
 The `patrim_isint`() macro returns 1 if the `id` if that of an integer, zero
 otherwise.
 
+The `patrim_isshldata`() macro returns 1 if the `id` is one that belongs to the
+"shielded data" protocol - as described in [shldata](5). Note that this /only
+applies/ when e.g. reading a shielded data file.
+
 # RETURN VALUE
 
 On success the `patrim_put`() and `patrim_get`() functions return the number of
@@ -48,3 +53,7 @@ not enough bytes could be written or read - they return -1.
 
 The `patrim_isblob`() and `patrim_isint`() macros return 1 id `id` represents a
 blob or an integer, respectively, otherwise they return 0.
+
+The `patrim_isshldata`() macro returns 1 if `id` belongs to the shielded data
+protocol, 0 otherwise. Note that this is only applicable when e.g. reading a
+shielded data file.
diff --git a/src/doc/shldata.5.md b/src/doc/shldata.5.md
new file mode 100644
index 0000000..c7fb35f
--- /dev/null
+++ b/src/doc/shldata.5.md
@@ -0,0 +1,49 @@
+% limb manual
+% shldata(5)
+
+# NAME
+
+shielded data - encryption/decryption protocol
+
+# DESCRIPTION
+
+The "shielded data" protocol is a way to protect data using a user-provided
+password. Using specified parameters (cryptographic hash algorithm, iteration
+numbers, etc) a secret key is derived from the password and used to encrypt data
+via the ChaCha20-Poly1305 algorithm.
+
+Similarly, provided with the original password, the same key derivation function
+is applied - using parameters taken from the input data - to obtain the secret
+key, used via ChaCha20-Poly1305 to both decrypt & authenticate the data, adding
+the derivation parameters to the authentication process.
+
+The actual encryption/decryption is performed using the ChaCha20-Poly1305
+algorithm, as described in [RFC 8439][rfc8439].
+
+[rfc8439] (https://datatracker.ietf.org/doc/html/rfc8439)
+
+The derivation parameters as well as the encrypted data and the message
+authentication code are encoded in [patrim](5) format :
+
+- The file header should use a PATRIM compatible magic constant, with bit 8 of
+  said number being set to 1 to indicate it contains shielded data.
+
+- For every ID in the file, bit 7 is a flag with special meaning : If set, it
+  indicates that the associated data are part of the shielded data protocol.
+  Else, the ID is a custom ID defined/used per the application itself.
+
+- When shielded data are added into a file, first all the IDs referring to key
+  derivation must be included, then the `ID_DATA` containing the encrypted data
+  followed by the `ID_MAC` containing the authentication code.
+
+- It is possible to have more than one such shielded data in a single file,
+  though each will have its own settings & require its own password.
+
+- Application-custom IDs can be used before, after, or in-between such shielded
+  data sections, but must not be mixed within groups of shielded data IDs.
+
+Refer to [shldata.h](0) for an interface to the encryption/decryption of
+shielded data.
+
+Helpers functions for general purpose PATRIM encoding are also available via
+[patrim.h](0).
diff --git a/src/doc/shldata.h.0.md b/src/doc/shldata.h.0.md
new file mode 100644
index 0000000..f23575c
--- /dev/null
+++ b/src/doc/shldata.h.0.md
@@ -0,0 +1,52 @@
+% limb manual
+% shldata.h(0)
+
+# NAME
+
+shldata.h - encrypt/decrypt data using the shielded data protocol
+
+# SYNOPSIS
+
+    #include <limb/shldata.h>
+
+# DESCRIPTION
+
+The header defines functions used to encrypt/decrypt data using the "shielded
+data" protocol. Refer to [shldata](5) for more.
+
+## Types
+
+The following types are defined :
+
+: *shldata_ctx*
+:: An opaque structure used to perform encryption or decryption through the
+:: shield data protocol.
+
+## Functions
+
+The following functions/macros are defined :
+
+: [shldata_initw](3)
+:: To initialize encryption of data through the shielded data protocol.
+
+: [shldata_encrypt](3)
+:: To encrypt data through the shielded data protocol.
+
+: [shldata_predata](3)
+:: Part of the shielded data encryption protocol.
+
+: [shldata_finalw](3)
+:: To finalize the encryption of data through the shielded data protocol.
+
+: [shldata_initr](3)
+:: To initialize decryption through the shielded data protocol.
+
+: [shldata_datasize](3)
+:: Returns the length of encrypted data to be decrypted through the shielded data
+:: protocol.
+
+: [shldata_decrypt](3)
+:: To decrypt data through the shielded data protocol.
+
+: [shldata_finalr](3)
+:: To finalize the decryption of data through the shielded data protocol.
diff --git a/src/doc/shldata.h/shldata_initr.3.md b/src/doc/shldata.h/shldata_initr.3.md
new file mode 100644
index 0000000..98a03cb
--- /dev/null
+++ b/src/doc/shldata.h/shldata_initr.3.md
@@ -0,0 +1,88 @@
+% limb manual
+% shldata_initr(3)
+
+# NAME
+
+shldata\_initr, shldata\_datasize, shldata\_decrypt, shldata\_finalr - decrypt
+data using the shielded data protocol
+
+# SYNOPSIS
+
+    #include <limb/shldata.h>
+
+```pre hl
+ssize_t shldata_initr(const char *<em>sce</em>, size_t <em>slen</em>, const char *<em>pwd</em>, size_t <em>plen</em>, shldata_ctx *<em>ctx</em>)
+size_t shldata_datasize(shldata_ctx *<em>ctx</em>)
+int shldata_decrypt(char *<em>dst</em>, const char *<em>sce</em>, size_t <em>len</em>, shldata_ctx *<em>ctx</em>)
+ssize_t shldata_finalr(const char *<em>sce</em>, size_t <em>slen</em>, shldata_ctx *<em>ctx</em>)
+```
+
+# DESCRIPTION
+
+All those functions are used to read & decrypt data protected with a
+user-supplied password using the shielded data protocol, as described in
+[shldata](5).
+
+First, the `shldata_initr`() function will read the memory pointed by `sce` (up
+to a maximum of `slen` bytes) for derivation parameters, then it will derive a
+secret key from the password pointed by `pwd` of length `plen` and initialize
+the opaque structure pointed by `ctx`.
+
+The `shldata_datasize`() macro returns the length of the encrypted data, and
+therefore decrypted data.
+
+The `shldata_decrypt`() function can then be used to decrypt data pointed by
+`sce` of length `slen` into the memory pointed by `dst` (which must be able to
+store at least `slen` bytes), using the specified `ctx`.
+
+As with [ccpl_decrupt](3) it is possible to use the same memory area for both
+`sce` and `dst`, and have it processed in-place.
+
+It is possible to call this function as many times as needed.
+
+Lastly, the `shldata_finalr`() function will read the memory pointed by `sce`
+(up to a maximum of `sleb` bytes) to authenticate both the derivation parameters
+used during `shldata_initr`() and the encrypted data.
+
+Once done, it is possible to re-use an opaque structure for a new processing of
+data. It is also allowed to to so more than once per shielded data file, as per
+[shldata](5).
+
+# RETURN VALUE
+
+The `shldata_initr`(), and `shldata_finalr`() functions return the number of
+bytes read and processed from `sce` on success. Otherwise they return -1 and set
+`errno` to indicate the error.
+
+For `shldata_initr`() this obviously means the offset at which encrypted data
+begins in `sce`.
+
+The `shldata_datasize`() macro returns the length of the data to be decrypted.
+
+The `shldata_decrypt`() function returns 1 on success, 0 otherwise. It should
+never fail, as the only possible reason for failure is that the amount of data
+given to be decrypted is larger than the amount of encrypted data, which should
+never happen.
+
+# ERRORS
+
+The `shldata_initr`() and `shldata_finalr`() functions may fail if :
+
+: *EINVAL*
+:: The memory pointed by `sce` contains invalid data.
+
+: *ENODATA*
+:: There is not enough data available in `sce`.
+
+The `shldata_finalr`() function may fail if :
+
+: *EINVAL*
+:: The amount of data decrypted is different from the amount of encrypted data.
+
+: *EBADMSG*
+:: Authentication of the message (derivation parameters and encrypted data)
+:: failed. (This could be caused by a wrong password being used.)
+
+# SEE ALSO
+
+[shldata_initw](3)
diff --git a/src/doc/shldata.h/shldata_initw.3.md b/src/doc/shldata.h/shldata_initw.3.md
new file mode 100644
index 0000000..8e9fbfb
--- /dev/null
+++ b/src/doc/shldata.h/shldata_initw.3.md
@@ -0,0 +1,107 @@
+% limb manual
+% shldata_initw(3)
+
+# NAME
+
+shldata\_initw, shldata\_encrypt, shldata\_predata, shldata\_finalw - encrypt
+data using the shielded data protocol
+
+# SYNOPSIS
+
+    #include <limb/shldata.h>
+
+```pre hl
+ssize_t shldata_initw(char *<em>dst</em>, size_t <em>dlen</em>, const char *<em>pwd</em>, size_t <em>plen</em>,
+                      unsigned <em>algo</em>, unsigned <em>iter</em>, size_t <em>len</em>, shldata_ctx *<em>ctx</em>)
+int shldata_encrypt(char *<em>dst</em>, const char *<em>data</em>, size_t <em>dlen</em>, shldata_ctx *<em>ctx</em>)
+ssize_t shldata_predata(char *<em>dst</em>, size_t <em>dlen</em>, shldata_ctx *<em>ctx</em>)
+ssize_t shldata_finalw(char *<em>dst</em>, size_t <em>dlen</em>, shldata_ctx *<em>ctx</em>)
+```
+
+# DESCRIPTION
+
+All those functions are used to protect data with a user-supplied password using
+the shielded data protocol, as described in [shldata](5).
+
+First, the `shldata_initw`() function must be called to both write into the
+memory pointed by `dst` of length `dlen` bytes the necessary parameters that
+will be needed for decryption, and to initialize the opaque structure pointed by
+`ctx` in order to perform data encryption.
+
+Key derivation will be performed using PBKDF2 from the password pointed by `pwd`
+of length `plen`, with the HMAC based on the algorithm indicated in `algo` -
+which must be a valid index/constant as defined in [hasher.h](0) - and the
+number of iterations indicated in `iter`.
+
+It is recommended, if possible, to indicate the exact length of data to be
+encrypted in `len`. It is however possible to use zero if not yet known.
+
+The `shldata_encrypt`() function can then be used to encrypt data pointed by
+`data` of length `dlen` into the memory pointed by `dst` (which must be able to
+store at least `dlen` bytes), using the specified `ctx`.
+
+As with [ccpl_encrupt](3) it is possible to use the same memory area for both
+`data` and `dst`, and have it processed in-place.
+
+It is possible to call this function as many times as needed.
+
+! If the length of data to be encrypted was known and given to `shldata_initw`()
+! it is correct to write encrypted data right after the parameters written by
+! said function, and no call to `shldata_predata`() is required.
+!
+! If, however, the length was /not/ given (i.e. `len` was zero), one should
+! *not* write encrypted data yet, but hold them in memory somewhere. All data
+! to be encrypted must be encrypted through `shldata_encrypt`() (in as many
+! calls as needed), and /only then/ a call to `shldata_predata`() must be done.
+
+The `shldata_predata`() function will write into the memory pointed by `dst` of
+length `dlen` additional data that must be written out after the output written
+by `shldata_initw`() but /before/ the encrypted data written by
+`shldata_encrypt`().
+This is because the length of encrypted data must be specified /before/ the
+actual data. This is also why specifying said length to `shldata_initw`() means
+it will already have been written out then, and no call to `shldata_predata`()
+is then required (though it is valid to call it anyways, but it will never write
+anything).
+
+Lastly, the `shldata_finalw`() function must be called, and will write into the
+memory pointed by `dst` of length `dlen` the final additional data (i.e. the
+message authentication code).
+
+Once done, it is possible to re-use an opaque structure for a new processing of
+data. It is also allowed to to so more than once per shielded data file, as per
+[shldata](5).
+
+# RETURN VALUE
+
+The `shldata_initw`(), `shldata_predata`() and `shldata_finalw`() functions
+return the number of bytes written into `dst` on success. Otherwise they return
+-1 and set `errno` to indicate the error.
+
+The `shldata_encrypt`() function returns 1 on success, 0 otherwise. It should
+never fail, as the only possible reason for failure is that the amount of data
+given to be encrypted is larger than that specified to `shldata_initw`(), which
+should never happen.
+
+# ERRORS
+
+The `shldata_initw`(), `shldata_predata`() and `shldata_finalw`() functions may
+fail if :
+
+: *ENOBUFS*
+:: There is not enough space in `dst`.
+
+The `shldata_initw`() may fail if :
+
+: *EINVAL*
+:: The value of `algo` or `iter` is invalid.
+
+The `shldata_predata`() and `shldata_finalw`() function may fail if :
+
+: *EINVAL*
+:: The length specified to `shldata_initw`() and the length of encrypted data
+:: are different (which should never happen).
+
+# SEE ALSO
+
+[shldata_initr](3)
diff --git a/src/include/shldata.h b/src/include/shldata.h
new file mode 100644
index 0000000..8f394fc
--- /dev/null
+++ b/src/include/shldata.h
@@ -0,0 +1,18 @@
+/* This file is part of limb                           https://lila.oss/limb
+ * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LIMB_LIMB_SHLDATA_H
+#define LIMB_LIMB_SHLDATA_H
+
+#define KEY_LEN         32
+#define SALT_LEN        32
+#define NONCE_LEN       12
+
+#define ID_ALGO         64
+#define ID_ITER         66
+
+#define ID_SALT         65
+#define ID_DATA         67
+#define ID_MAC          69
+
+#endif /* LIMB_LIMB_SHLDATA_H */
diff --git a/src/liblimb/hasher.h/hashers.c b/src/liblimb/hasher.h/hashers.c
new file mode 100644
index 0000000..a81b87c
--- /dev/null
+++ b/src/liblimb/hasher.h/hashers.c
@@ -0,0 +1,19 @@
+/* This file is part of limb                           https://lila.oss/limb
+ * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <limb/hasher.h>
+#include <limb/hasher_sha1.h>
+#include <limb/hasher_sha256.h>
+#include <limb/hasher_sha512.h>
+#include <limb/hasher_sha3_224.h>
+#include <limb/hasher_sha3_256.h>
+#include <limb/hasher_sha3_384.h>
+#include <limb/hasher_sha3_512.h>
+#include <limb/hasher_blake3.h>
+
+/* XXX make sure to preserve order as ALGO_* in hasher.h */
+const char * const algos[NB_ALGOS + 1] = { "sha1", "sha256", "sha512", "sha3-224",
+    "sha3-256", "sha3-384", "sha3-512", "blake3", 0 };
+
+hasher * const hashers[NB_ALGOS] = { sha1, sha256, sha512, sha3_224,
+    sha3_256, sha3_384, sha3_512, blake3 };
diff --git a/src/liblimb/include/limb/hasher.h b/src/liblimb/include/limb/hasher.h
index a21f906..f39cea9 100644
--- a/src/liblimb/include/limb/hasher.h
+++ b/src/liblimb/include/limb/hasher.h
@@ -28,4 +28,22 @@ struct hasher {
 
 extern void hasher_hash(void *digest, const void *msg, size_t mlen, hasher *hasher);
 
+/* supported algorithms/hashers */
+/* XXX make sure to preserve order in *algos[] in hashers.c */
+enum {
+    ALGO_SHA1 = 0,
+    ALGO_SHA256,
+    ALGO_SHA512,
+    ALGO_SHA3_224,
+    ALGO_SHA3_256,
+    ALGO_SHA3_384,
+    ALGO_SHA3_512,
+    ALGO_BLAKE3,
+    NB_ALGOS
+};
+
+/* names of said algos, in the same order */
+extern const char * const algos[NB_ALGOS + 1];
+extern hasher * const hashers[NB_ALGOS];
+
 #endif /* LIMB_HASHER_H */
diff --git a/src/liblimb/include/limb/patrim.h b/src/liblimb/include/limb/patrim.h
index c13cc2a..8646b2f 100644
--- a/src/liblimb/include/limb/patrim.h
+++ b/src/liblimb/include/limb/patrim.h
@@ -9,6 +9,7 @@
 
 #define patrim_isblob(id)       (id & 1)
 #define patrim_isint(id)        !patrim_isblob(id)
+#define patrim_isshldata(id)    (id & (1 << 6))
 
 extern int patrim_put(char *dst, size_t dlen, size_t offset, u64 id, u64 u);
 extern int patrim_get(u64 *id, u64 *u, const char *data, size_t dlen, size_t offset);
diff --git a/src/liblimb/include/limb/shldata.h b/src/liblimb/include/limb/shldata.h
new file mode 100644
index 0000000..9081fce
--- /dev/null
+++ b/src/liblimb/include/limb/shldata.h
@@ -0,0 +1,30 @@
+/* This file is part of limb                           https://lila.oss/limb
+ * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LIMB_SHLDATA_H
+#define LIMB_SHLDATA_H
+
+#include <sys/types.h>
+#include <limb/ccpl.h>
+
+struct shldata_ctx {
+    ccpl_ctx ccpl;
+    size_t len;
+    size_t done;
+};
+
+typedef struct shldata_ctx shldata_ctx;
+
+extern ssize_t shldata_initw(char *dst, size_t dlen, const char *pwd, size_t plen,
+                             unsigned algo, unsigned iter, size_t len, shldata_ctx *ctx);
+extern int shldata_encrypt(char *dst, const char *data, size_t dlen, shldata_ctx *ctx);
+extern ssize_t shldata_predata(char *dst, size_t dlen, shldata_ctx *ctx);
+extern ssize_t shldata_finalw(char *dst, size_t dlen, shldata_ctx *ctx);
+
+
+extern ssize_t shldata_initr(const char *sce, size_t slen, const char *pwd, size_t plen, shldata_ctx *ctx);
+#define shldata_datasize(ctx)       (ctx)->len
+extern int shldata_decrypt(char *dst, const char *sce, size_t len, shldata_ctx *ctx);
+extern ssize_t shldata_finalr(const char *sce, size_t slen, shldata_ctx *ctx);
+
+#endif /* LIMB_SHLDATA_H */
diff --git a/src/liblimb/shldata.h/shldata_decrypt.c b/src/liblimb/shldata.h/shldata_decrypt.c
new file mode 100644
index 0000000..9ca4e35
--- /dev/null
+++ b/src/liblimb/shldata.h/shldata_decrypt.c
@@ -0,0 +1,16 @@
+/* This file is part of limb                           https://lila.oss/limb
+ * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <limb/ccpl.h>
+#include <limb/shldata.h>
+
+int
+shldata_decrypt(char *dst, const char *sce, size_t len, shldata_ctx *ctx)
+{
+    if (ctx->done + len > ctx->len)
+        return 0;
+
+    ccpl_decrypt(dst, sce, len, &ctx->ccpl);
+    ctx->done += len;
+    return 1;
+}
diff --git a/src/liblimb/shldata.h/shldata_encrypt.c b/src/liblimb/shldata.h/shldata_encrypt.c
new file mode 100644
index 0000000..045e97b
--- /dev/null
+++ b/src/liblimb/shldata.h/shldata_encrypt.c
@@ -0,0 +1,17 @@
+/* This file is part of limb                           https://lila.oss/limb
+ * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <limb/ccpl.h>
+#include <limb/shldata.h>
+#include "shldata.h"
+
+int
+shldata_encrypt(char *dst, const char *data, size_t dlen, shldata_ctx *ctx)
+{
+    if (ctx->len && ctx->done + dlen > ctx->len)
+        return 0;
+
+    ccpl_encrypt(dst, data, dlen, &ctx->ccpl);
+    ctx->done += dlen;
+    return 1;
+}
diff --git a/src/liblimb/shldata.h/shldata_finalr.c b/src/liblimb/shldata.h/shldata_finalr.c
new file mode 100644
index 0000000..b74721a
--- /dev/null
+++ b/src/liblimb/shldata.h/shldata_finalr.c
@@ -0,0 +1,31 @@
+/* This file is part of limb                           https://lila.oss/limb
+ * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <errno.h>
+#include <string.h>
+#include <limb/patrim.h>
+#include <limb/shldata.h>
+#include "shldata.h"
+
+ssize_t
+shldata_finalr(const char *sce, size_t slen, shldata_ctx *ctx)
+{
+    char mac[16];
+
+    if (ctx->done != ctx->len)
+        return (errno = EINVAL, -1);
+
+    u64 id, u;
+    int r;
+    r = patrim_get(&id, &u, sce, slen, 0);
+    if (r < 0) return (errno = ENODATA, -1);
+
+    if (id != ID_MAC || u != sizeof(mac))
+        return (errno = EINVAL, -1);
+
+    ccpl_final(mac, &ctx->ccpl);
+    if (memcmp(sce + r, mac, sizeof(mac)))
+        return (errno = EBADMSG, -1);
+
+    return r + sizeof(mac);
+}
diff --git a/src/liblimb/shldata.h/shldata_finalw.c b/src/liblimb/shldata.h/shldata_finalw.c
new file mode 100644
index 0000000..432a4ff
--- /dev/null
+++ b/src/liblimb/shldata.h/shldata_finalw.c
@@ -0,0 +1,26 @@
+/* This file is part of limb                           https://lila.oss/limb
+ * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <errno.h>
+#include <string.h>
+#include <limb/patrim.h>
+#include <limb/shldata.h>
+#include "shldata.h"
+
+ssize_t
+shldata_finalw(char *dst, size_t dlen, shldata_ctx *ctx)
+{
+    if (!ctx->len || ctx->len != ctx->done)
+        return (errno = EINVAL, -1);
+
+    char mac[16];
+    ccpl_final(mac, &ctx->ccpl);
+
+    int r;
+    r = patrim_put(dst, dlen, 0, ID_MAC, sizeof(mac));
+    if (r < 0 || dlen - r < sizeof(mac))
+        return (errno = ENOBUFS, -1);
+    memcpy(dst + r, mac, sizeof(mac));
+
+    return r + sizeof(mac);
+}
diff --git a/src/liblimb/shldata.h/shldata_initr.c b/src/liblimb/shldata.h/shldata_initr.c
new file mode 100644
index 0000000..1e3e922
--- /dev/null
+++ b/src/liblimb/shldata.h/shldata_initr.c
@@ -0,0 +1,75 @@
+/* This file is part of limb                           https://lila.oss/limb
+ * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <errno.h>
+#include <limb/hasher.h>
+#include <limb/patrim.h>
+#include <limb/pbkdf2.h>
+#include <limb/shldata.h>
+#include "shldata.h"
+
+ssize_t
+shldata_initr(const char *sce, size_t slen, const char *pwd, size_t plen, shldata_ctx *ctx)
+{
+    unsigned algo, iter, o;
+    const char *salt;
+    size_t sltlen;
+    ssize_t aadoff;
+    int r;
+
+    /* silence warnings */
+    salt = NULL;
+    sltlen = 0;
+
+    aadoff = -1;
+    o = algo = iter = 0;
+    for (;;) {
+        u64 id, u;
+
+        r = patrim_get(&id, &u, sce, slen, o);
+        if (r < 0) return (errno = ENODATA, -1);
+        if (!patrim_isshldata(id)) {
+            /* skip any ID that isn't ours */
+            o += r;
+            if (patrim_isblob(id))
+                o += u;
+            continue;
+        }
+        if (aadoff < 0)
+            aadoff = o;
+        switch (id) {
+            case ID_ALGO:
+                algo = u + 1;
+                break;
+            case ID_ITER:
+                iter = u;
+                break;
+            case ID_SALT:
+                salt = sce + o + r;
+                sltlen = u;
+                o += sltlen;
+                break;
+            case ID_DATA:
+                ctx->len = u;
+                ctx->done = 0;
+                break;
+            default:
+                return (errno = EINVAL, -1);
+        }
+        if (id == ID_DATA)
+            break;
+        o += r;
+    }
+    if (!algo || !iter || !sltlen)
+        return (errno = EINVAL, -1);
+    --algo;
+
+    char key[KEY_LEN];
+    char nonce[NONCE_LEN] = { 0 };
+    pbkdf2(key, sizeof(key), hashers[algo], pwd, plen, salt, sltlen, iter);
+    ccpl_init(key, nonce, &ctx->ccpl);
+    ccpl_aad(sce + aadoff, o - aadoff, &ctx->ccpl);
+
+    /* return offset of begining of encrypted data */
+    return o + r;
+}
diff --git a/src/liblimb/shldata.h/shldata_initw.c b/src/liblimb/shldata.h/shldata_initw.c
new file mode 100644
index 0000000..163e4e1
--- /dev/null
+++ b/src/liblimb/shldata.h/shldata_initw.c
@@ -0,0 +1,61 @@
+/* This file is part of limb                           https://lila.oss/limb
+ * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <errno.h>
+#include <string.h>
+#include <skalibs/random.h>
+#include <limb/bytestr.h>
+#include <limb/hasher.h>
+#include <limb/patrim.h>
+#include <limb/pbkdf2.h>
+#include <limb/shldata.h>
+#include "shldata.h"
+
+ssize_t
+shldata_initw(char *dst, size_t dlen, const char *pwd, size_t plen,
+              unsigned algo, unsigned iter, size_t len, shldata_ctx *ctx)
+{
+    if (algo >= NB_ALGOS || !iter)
+        return (errno = EINVAL, -1);
+
+    char salt[SALT_LEN];
+    random_buf(salt, sizeof(salt));
+
+    size_t l = 0;
+    int r;
+
+    r = patrim_put(dst, dlen, l, ID_ALGO, algo);
+    if (r < 0) return (errno = ENOBUFS, -1);
+    l += r;
+    r = patrim_put(dst, dlen, l, ID_ITER, iter);
+    if (r < 0) return (errno = ENOBUFS, -1);
+    l += r;
+    r = patrim_put(dst, dlen, l, ID_SALT, sizeof(salt));
+    if (r < 0) return (errno = ENOBUFS, -1);
+    l += r;
+    if (dlen - l < sizeof(salt))
+        return (errno = ENOBUFS, -1);
+    memcpy(dst + l, salt, sizeof(salt));
+    l += sizeof(salt);
+
+    ctx->len = len;
+    ctx->done = 0;
+
+    if (len) {
+        r = patrim_put(dst, dlen, l, ID_DATA, len);
+        if (r < 0) return (errno = ENOBUFS, -1);
+        l += r;
+    } else {
+        r = 0;
+    }
+
+    char key[KEY_LEN];
+    char nonce[NONCE_LEN] = { 0 };
+
+    pbkdf2(key, sizeof(key), hashers[algo], pwd, plen, salt, sizeof(salt), iter);
+    ccpl_init(key, nonce, &ctx->ccpl);
+    ccpl_aad(dst, l - r, &ctx->ccpl);
+    byte_zero(key, sizeof(key));
+
+    return l;
+}
diff --git a/src/liblimb/shldata.h/shldata_predata.c b/src/liblimb/shldata.h/shldata_predata.c
new file mode 100644
index 0000000..a4c1afb
--- /dev/null
+++ b/src/liblimb/shldata.h/shldata_predata.c
@@ -0,0 +1,23 @@
+/* This file is part of limb                           https://lila.oss/limb
+ * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <errno.h>
+#include <limb/patrim.h>
+#include <limb/shldata.h>
+#include "shldata.h"
+
+ssize_t
+shldata_predata(char *dst, size_t dlen, shldata_ctx *ctx)
+{
+    if (ctx->len) {
+        if (ctx->len != ctx->done)
+            return (errno = EINVAL, -1);
+        return 0;
+    }
+
+    int r = patrim_put(dst, dlen, 0, ID_DATA, ctx->done);
+    if (r < 0) return (errno = ENOBUFS, -1);
+
+    ctx->len = ctx->done;
+    return r;
+}