1 // SPDX-License-Identifier: BSD-2-Clause
2 /* LibTomCrypt, modular cryptographic library -- Tom St Denis
3  *
4  * LibTomCrypt is a library that provides various cryptographic
5  * algorithms in a highly modular and flexible manner.
6  *
7  * The library is free for all purposes without any express
8  * guarantee it works.
9  */
10 
11  /* the idea of re-keying loosely follows the approach used in:
12   * http://bxr.su/OpenBSD/lib/libc/crypt/arc4random.c
13   */
14 
15 #include "tomcrypt_private.h"
16 
17 #ifdef LTC_CHACHA20_PRNG
18 
19 const struct ltc_prng_descriptor chacha20_prng_desc =
20 {
21    "chacha20",
22    40,
23    &chacha20_prng_start,
24    &chacha20_prng_add_entropy,
25    &chacha20_prng_ready,
26    &chacha20_prng_read,
27    &chacha20_prng_done,
28    &chacha20_prng_export,
29    &chacha20_prng_import,
30    &chacha20_prng_test
31 };
32 
33 /**
34   Start the PRNG
35   @param prng The PRNG state to initialize
36   @return CRYPT_OK if successful
37 */
chacha20_prng_start(prng_state * prng)38 int chacha20_prng_start(prng_state *prng)
39 {
40    LTC_ARGCHK(prng != NULL);
41    prng->ready = 0;
42    XMEMSET(&prng->u.chacha.ent, 0, sizeof(prng->u.chacha.ent));
43    prng->u.chacha.idx = 0;
44    LTC_MUTEX_INIT(&prng->lock)
45    return CRYPT_OK;
46 }
47 
48 /**
49   Add entropy to the PRNG state
50   @param in       The data to add
51   @param inlen    Length of the data to add
52   @param prng     PRNG state to update
53   @return CRYPT_OK if successful
54 */
chacha20_prng_add_entropy(const unsigned char * in,unsigned long inlen,prng_state * prng)55 int chacha20_prng_add_entropy(const unsigned char *in, unsigned long inlen, prng_state *prng)
56 {
57    unsigned char buf[40];
58    unsigned long i;
59    int err;
60 
61    LTC_ARGCHK(prng != NULL);
62    LTC_ARGCHK(in != NULL);
63    LTC_ARGCHK(inlen > 0);
64 
65    LTC_MUTEX_LOCK(&prng->lock);
66    if (prng->ready) {
67       /* chacha20_prng_ready() was already called, do "rekey" operation */
68       if ((err = chacha_keystream(&prng->u.chacha.s, buf, sizeof(buf))) != CRYPT_OK) goto LBL_UNLOCK;
69       for(i = 0; i < inlen; i++) buf[i % sizeof(buf)] ^= in[i];
70       /* key 32 bytes, 20 rounds */
71       if ((err = chacha_setup(&prng->u.chacha.s, buf, 32, 20)) != CRYPT_OK)      goto LBL_UNLOCK;
72       /* iv 8 bytes */
73       if ((err = chacha_ivctr64(&prng->u.chacha.s, buf + 32, 8, 0)) != CRYPT_OK) goto LBL_UNLOCK;
74       /* clear KEY + IV */
75       zeromem(buf, sizeof(buf));
76    }
77    else {
78       /* chacha20_prng_ready() was not called yet, add entropy to ent buffer */
79       while (inlen--) prng->u.chacha.ent[prng->u.chacha.idx++ % sizeof(prng->u.chacha.ent)] ^= *in++;
80    }
81    err = CRYPT_OK;
82 LBL_UNLOCK:
83    LTC_MUTEX_UNLOCK(&prng->lock);
84    return err;
85 }
86 
87 /**
88   Make the PRNG ready to read from
89   @param prng   The PRNG to make active
90   @return CRYPT_OK if successful
91 */
chacha20_prng_ready(prng_state * prng)92 int chacha20_prng_ready(prng_state *prng)
93 {
94    int err;
95 
96    LTC_ARGCHK(prng != NULL);
97 
98    LTC_MUTEX_LOCK(&prng->lock);
99    if (prng->ready)                                                    { err = CRYPT_OK; goto LBL_UNLOCK; }
100    /* key 32 bytes, 20 rounds */
101    if ((err = chacha_setup(&prng->u.chacha.s, prng->u.chacha.ent, 32, 20)) != CRYPT_OK)      goto LBL_UNLOCK;
102    /* iv 8 bytes */
103    if ((err = chacha_ivctr64(&prng->u.chacha.s, prng->u.chacha.ent + 32, 8, 0)) != CRYPT_OK) goto LBL_UNLOCK;
104    XMEMSET(&prng->u.chacha.ent, 0, sizeof(prng->u.chacha.ent));
105    prng->u.chacha.idx = 0;
106    prng->ready = 1;
107 LBL_UNLOCK:
108    LTC_MUTEX_UNLOCK(&prng->lock);
109    return err;
110 }
111 
112 /**
113   Read from the PRNG
114   @param out      Destination
115   @param outlen   Length of output
116   @param prng     The active PRNG to read from
117   @return Number of octets read
118 */
chacha20_prng_read(unsigned char * out,unsigned long outlen,prng_state * prng)119 unsigned long chacha20_prng_read(unsigned char *out, unsigned long outlen, prng_state *prng)
120 {
121    if (outlen == 0 || prng == NULL || out == NULL) return 0;
122    LTC_MUTEX_LOCK(&prng->lock);
123    if (!prng->ready) { outlen = 0; goto LBL_UNLOCK; }
124    if (chacha_keystream(&prng->u.chacha.s, out, outlen) != CRYPT_OK) outlen = 0;
125 LBL_UNLOCK:
126    LTC_MUTEX_UNLOCK(&prng->lock);
127    return outlen;
128 }
129 
130 /**
131   Terminate the PRNG
132   @param prng   The PRNG to terminate
133   @return CRYPT_OK if successful
134 */
chacha20_prng_done(prng_state * prng)135 int chacha20_prng_done(prng_state *prng)
136 {
137    int err;
138    LTC_ARGCHK(prng != NULL);
139    LTC_MUTEX_LOCK(&prng->lock);
140    prng->ready = 0;
141    err = chacha_done(&prng->u.chacha.s);
142    LTC_MUTEX_UNLOCK(&prng->lock);
143    LTC_MUTEX_DESTROY(&prng->lock);
144    return err;
145 }
146 
147 /**
148   Export the PRNG state
149   @param out       [out] Destination
150   @param outlen    [in/out] Max size and resulting size of the state
151   @param prng      The PRNG to export
152   @return CRYPT_OK if successful
153 */
_LTC_PRNG_EXPORT(chacha20_prng)154 _LTC_PRNG_EXPORT(chacha20_prng)
155 
156 /**
157   Import a PRNG state
158   @param in       The PRNG state
159   @param inlen    Size of the state
160   @param prng     The PRNG to import
161   @return CRYPT_OK if successful
162 */
163 int chacha20_prng_import(const unsigned char *in, unsigned long inlen, prng_state *prng)
164 {
165    int err;
166 
167    LTC_ARGCHK(prng != NULL);
168    LTC_ARGCHK(in   != NULL);
169    if (inlen < (unsigned long)chacha20_prng_desc.export_size) return CRYPT_INVALID_ARG;
170 
171    if ((err = chacha20_prng_start(prng)) != CRYPT_OK)                  return err;
172    if ((err = chacha20_prng_add_entropy(in, inlen, prng)) != CRYPT_OK) return err;
173    return CRYPT_OK;
174 }
175 
176 /**
177   PRNG self-test
178   @return CRYPT_OK if successful, CRYPT_NOP if self-testing has been disabled
179 */
chacha20_prng_test(void)180 int chacha20_prng_test(void)
181 {
182 #ifndef LTC_TEST
183    return CRYPT_NOP;
184 #else
185    prng_state st;
186    unsigned char en[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
187                           0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
188                           0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
189                           0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
190                           0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32 };
191    unsigned char dmp[300];
192    unsigned long dmplen = sizeof(dmp);
193    unsigned char out[500];
194    unsigned char t1[] = { 0x59, 0xB2, 0x26, 0x95, 0x2B, 0x01, 0x8F, 0x05, 0xBE, 0xD8 };
195    unsigned char t2[] = { 0x47, 0xC9, 0x0D, 0x03, 0xE4, 0x75, 0x34, 0x27, 0xBD, 0xDE };
196    unsigned char t3[] = { 0xBC, 0xFA, 0xEF, 0x59, 0x37, 0x7F, 0x1A, 0x91, 0x1A, 0xA6 };
197    int err;
198 
199    if ((err = chacha20_prng_start(&st)) != CRYPT_OK)                       return err;
200    /* add entropy to uninitialized prng */
201    if ((err = chacha20_prng_add_entropy(en, sizeof(en), &st)) != CRYPT_OK) return err;
202    if ((err = chacha20_prng_ready(&st)) != CRYPT_OK)                       return err;
203    if (chacha20_prng_read(out, 10, &st) != 10)                             return CRYPT_ERROR_READPRNG; /* 10 bytes for testing */
204    if (compare_testvector(out, 10, t1, sizeof(t1), "CHACHA-PRNG", 1))      return CRYPT_FAIL_TESTVECTOR;
205    if (chacha20_prng_read(out, 500, &st) != 500)                           return CRYPT_ERROR_READPRNG; /* skip 500 bytes */
206    /* add entropy to already initialized prng */
207    if ((err = chacha20_prng_add_entropy(en, sizeof(en), &st)) != CRYPT_OK) return err;
208    if (chacha20_prng_read(out, 500, &st) != 500)                           return CRYPT_ERROR_READPRNG; /* skip 500 bytes */
209    if ((err = chacha20_prng_export(dmp, &dmplen, &st)) != CRYPT_OK)        return err;
210    if (chacha20_prng_read(out, 500, &st) != 500)                           return CRYPT_ERROR_READPRNG; /* skip 500 bytes */
211    if (chacha20_prng_read(out, 10, &st) != 10)                             return CRYPT_ERROR_READPRNG; /* 10 bytes for testing */
212    if (compare_testvector(out, 10, t2, sizeof(t2), "CHACHA-PRNG", 2))      return CRYPT_FAIL_TESTVECTOR;
213    if ((err = chacha20_prng_done(&st)) != CRYPT_OK)                        return err;
214    if ((err = chacha20_prng_import(dmp, dmplen, &st)) != CRYPT_OK)         return err;
215    if ((err = chacha20_prng_ready(&st)) != CRYPT_OK)                       return err;
216    if (chacha20_prng_read(out, 500, &st) != 500)                           return CRYPT_ERROR_READPRNG; /* skip 500 bytes */
217    if (chacha20_prng_read(out, 10, &st) != 10)                             return CRYPT_ERROR_READPRNG; /* 10 bytes for testing */
218    if (compare_testvector(out, 10, t3, sizeof(t3), "CHACHA-PRNG", 3))      return CRYPT_FAIL_TESTVECTOR;
219    if ((err = chacha20_prng_done(&st)) != CRYPT_OK)                        return err;
220 
221    return CRYPT_OK;
222 #endif
223 }
224 
225 #endif
226 
227 /* ref:         $Format:%D$ */
228 /* git commit:  $Format:%H$ */
229 /* commit time: $Format:%ai$ */
230