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