saturnin.c 12.3 KB
Newer Older
Rhys Weatherley committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * Copyright (C) 2020 Southern Storm Software, Pty Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "saturnin.h"
24
#include "internal-saturnin.h"
Rhys Weatherley committed
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
#include <string.h>

aead_cipher_t const saturnin_cipher = {
    "SATURNIN-CTR-Cascade",
    SATURNIN_KEY_SIZE,
    SATURNIN_NONCE_SIZE,
    SATURNIN_TAG_SIZE,
    AEAD_FLAG_LITTLE_ENDIAN,
    saturnin_aead_encrypt,
    saturnin_aead_decrypt
};

aead_cipher_t const saturnin_short_cipher = {
    "SATURNIN-Short",
    SATURNIN_KEY_SIZE,
    SATURNIN_NONCE_SIZE,
    SATURNIN_TAG_SIZE,
    AEAD_FLAG_NONE,
    saturnin_short_aead_encrypt,
    saturnin_short_aead_decrypt
};

aead_hash_algorithm_t const saturnin_hash_algorithm = {
    "SATURNIN-Hash",
    sizeof(saturnin_hash_state_t),
    SATURNIN_HASH_SIZE,
    AEAD_FLAG_LITTLE_ENDIAN,
    saturnin_hash,
    (aead_hash_init_t)saturnin_hash_init,
    (aead_hash_update_t)saturnin_hash_update,
    (aead_hash_finalize_t)saturnin_hash_finalize,
    0, /* absorb */
    0  /* squeeze */
};

/**
 * \brief Encrypts a 256-bit block with the SATURNIN block cipher and
 * then XOR's itself to generate a new key.
 *
 * \param block Block to be encrypted and then XOR'ed with itself.
 * \param key Points to the 32 byte key for the block cipher.
66
 * \param domain Domain separator and round counter.
Rhys Weatherley committed
67
 */
68 69
static void saturnin_block_encrypt_xor
    (const unsigned char *block, unsigned char *key, unsigned domain)
Rhys Weatherley committed
70
{
71 72 73 74 75
    saturnin_key_schedule_t ks;
    unsigned char *temp = (unsigned char *)ks.k; /* Reuse some stack space */
    saturnin_setup_key(&ks, key);
    saturnin_encrypt_block(&ks, temp, block, domain);
    lw_xor_block_2_src(key, block, temp, SATURNIN_BLOCK_SIZE);
Rhys Weatherley committed
76 77 78 79 80 81 82 83
}

/**
 * \brief Encrypts (or decrypts) a data packet in CTR mode.
 *
 * \param c Output ciphertext buffer.
 * \param m Input plaintext buffer.
 * \param mlen Length of the plaintext in bytes.
84
 * \param ks Points to the key schedule.
Rhys Weatherley committed
85 86 87 88
 * \param block Points to the pre-formatted nonce block.
 */
static void saturnin_ctr_encrypt
    (unsigned char *c, const unsigned char *m, unsigned long long mlen,
89
     const saturnin_key_schedule_t *ks, unsigned char *block)
Rhys Weatherley committed
90 91 92 93
{
    /* Note: Specification requires a 95-bit counter but we only use 32-bit.
     * This limits the maximum packet size to 128Gb.  That should be OK */
    uint32_t counter = 1;
94
    unsigned char out[SATURNIN_BLOCK_SIZE];
Rhys Weatherley committed
95 96
    while (mlen >= 32) {
        be_store_word32(block + 28, counter);
97
        saturnin_encrypt_block(ks, out, block, SATURNIN_DOMAIN_10_1);
Rhys Weatherley committed
98 99 100 101 102 103 104 105
        lw_xor_block_2_src(c, out, m, 32);
        c += 32;
        m += 32;
        mlen -= 32;
        ++counter;
    }
    if (mlen > 0) {
        be_store_word32(block + 28, counter);
106
        saturnin_encrypt_block(ks, out, block, SATURNIN_DOMAIN_10_1);
Rhys Weatherley committed
107 108 109 110 111 112 113 114 115 116 117
        lw_xor_block_2_src(c, out, m, (unsigned)mlen);
    }
}

/**
 * \brief Pads an authenticates a message.
 *
 * \param tag Points to the authentication tag.
 * \param block Temporary block of 32 bytes from the caller.
 * \param m Points to the message to be authenticated.
 * \param mlen Length of the message to be authenticated in bytes.
118 119
 * \param domain1 Round count and domain separator for full blocks.
 * \param domain2 Round count and domain separator for the last block.
Rhys Weatherley committed
120 121 122 123
 */
static void saturnin_authenticate
    (unsigned char *tag, unsigned char *block,
     const unsigned char *m, unsigned long long mlen,
124
     unsigned domain1, unsigned domain2)
Rhys Weatherley committed
125 126 127
{
    unsigned temp;
    while (mlen >= 32) {
128
        saturnin_block_encrypt_xor(m, tag, domain1);
Rhys Weatherley committed
129 130 131 132 133 134 135
        m += 32;
        mlen -= 32;
    }
    temp = (unsigned)mlen;
    memcpy(block, m, temp);
    block[temp] = 0x80;
    memset(block + temp + 1, 0, 31 - temp);
136
    saturnin_block_encrypt_xor(block, tag, domain2);
Rhys Weatherley committed
137 138 139 140 141 142 143 144 145 146
}

int saturnin_aead_encrypt
    (unsigned char *c, unsigned long long *clen,
     const unsigned char *m, unsigned long long mlen,
     const unsigned char *ad, unsigned long long adlen,
     const unsigned char *nsec,
     const unsigned char *npub,
     const unsigned char *k)
{
147
    saturnin_key_schedule_t ks;
Rhys Weatherley committed
148 149 150 151 152 153 154 155 156 157 158 159 160
    unsigned char block[32];
    unsigned char *tag;
    (void)nsec;

    /* Set the length of the returned ciphertext */
    *clen = mlen + SATURNIN_TAG_SIZE;

    /* Format the input block from the padded nonce */
    memcpy(block, npub, 16);
    block[16] = 0x80;
    memset(block + 17, 0, 15);

    /* Encrypt the plaintext in counter mode to produce the ciphertext */
161 162
    saturnin_setup_key(&ks, k);
    saturnin_ctr_encrypt(c, m, mlen, &ks, block);
Rhys Weatherley committed
163 164 165 166 167

    /* Set the counter back to zero and then encrypt the nonce */
    tag = c + mlen;
    memcpy(tag, k, 32);
    memset(block + 17, 0, 15);
168
    saturnin_block_encrypt_xor(block, tag, SATURNIN_DOMAIN_10_2);
Rhys Weatherley committed
169 170

    /* Authenticate the associated data and the ciphertext */
171 172 173 174
    saturnin_authenticate
        (tag, block, ad, adlen, SATURNIN_DOMAIN_10_2, SATURNIN_DOMAIN_10_3);
    saturnin_authenticate
        (tag, block, c, mlen, SATURNIN_DOMAIN_10_4, SATURNIN_DOMAIN_10_5);
Rhys Weatherley committed
175 176 177 178 179 180 181 182 183 184 185
    return 0;
}

int saturnin_aead_decrypt
    (unsigned char *m, unsigned long long *mlen,
     unsigned char *nsec,
     const unsigned char *c, unsigned long long clen,
     const unsigned char *ad, unsigned long long adlen,
     const unsigned char *npub,
     const unsigned char *k)
{
186
    saturnin_key_schedule_t ks;
Rhys Weatherley committed
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
    unsigned char block[32];
    unsigned char tag[32];
    (void)nsec;

    /* Validate the ciphertext length and set the return "mlen" value */
    if (clen < SATURNIN_TAG_SIZE)
        return -1;
    *mlen = clen - SATURNIN_TAG_SIZE;

    /* Format the input block from the padded nonce */
    memcpy(block, npub, 16);
    block[16] = 0x80;
    memset(block + 17, 0, 15);

    /* Encrypt the nonce to initialize the authentication phase */
    memcpy(tag, k, 32);
203
    saturnin_block_encrypt_xor(block, tag, SATURNIN_DOMAIN_10_2);
Rhys Weatherley committed
204 205

    /* Authenticate the associated data and the ciphertext */
206 207 208 209
    saturnin_authenticate
        (tag, block, ad, adlen, SATURNIN_DOMAIN_10_2, SATURNIN_DOMAIN_10_3);
    saturnin_authenticate
        (tag, block, c, *mlen, SATURNIN_DOMAIN_10_4, SATURNIN_DOMAIN_10_5);
Rhys Weatherley committed
210 211 212 213 214

    /* Decrypt the ciphertext in counter mode to produce the plaintext */
    memcpy(block, npub, 16);
    block[16] = 0x80;
    memset(block + 17, 0, 15);
215 216
    saturnin_setup_key(&ks, k);
    saturnin_ctr_encrypt(m, c, *mlen, &ks, block);
Rhys Weatherley committed
217 218 219 220 221 222 223 224 225 226 227 228 229 230

    /* Check the authentication tag at the end of the message */
    return aead_check_tag
        (m, *mlen, tag, c + *mlen, SATURNIN_TAG_SIZE);
}

int saturnin_short_aead_encrypt
    (unsigned char *c, unsigned long long *clen,
     const unsigned char *m, unsigned long long mlen,
     const unsigned char *ad, unsigned long long adlen,
     const unsigned char *nsec,
     const unsigned char *npub,
     const unsigned char *k)
{
231
    saturnin_key_schedule_t ks;
Rhys Weatherley committed
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
    unsigned char block[32];
    unsigned temp;
    (void)nsec;
    (void)ad;

    /* Validate the parameters: no associated data allowed and m <= 15 bytes */
    if (adlen > 0 || mlen > 15)
        return -2;

    /* Format the input block from the nonce and plaintext */
    temp = (unsigned)mlen;
    memcpy(block, npub, 16);
    memcpy(block + 16, m, temp);
    block[16 + temp] = 0x80; /* Padding */
    memset(block + 17 + temp, 0, 15 - temp);

    /* Encrypt the input block to produce the output ciphertext */
249 250
    saturnin_setup_key(&ks, k);
    saturnin_encrypt_block(&ks, c, block, SATURNIN_DOMAIN_10_6);
Rhys Weatherley committed
251 252 253 254 255 256 257 258 259 260 261 262
    *clen = 32;
    return 0;
}

int saturnin_short_aead_decrypt
    (unsigned char *m, unsigned long long *mlen,
     unsigned char *nsec,
     const unsigned char *c, unsigned long long clen,
     const unsigned char *ad, unsigned long long adlen,
     const unsigned char *npub,
     const unsigned char *k)
{
263
    saturnin_key_schedule_t ks;
Rhys Weatherley committed
264 265 266 267 268 269 270 271 272 273 274 275 276
    unsigned char block[32];
    unsigned check1, check2, len;
    int index, result;
    (void)nsec;
    (void)ad;

    /* Validate the parameters: no associated data and c is always 32 bytes */
    if (adlen > 0)
        return -2;
    if (clen != 32)
        return -1;

    /* Decrypt the ciphertext block */
277 278
    saturnin_setup_key(&ks, k);
    saturnin_decrypt_block(&ks, block, c, SATURNIN_DOMAIN_10_6);
Rhys Weatherley committed
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318

    /* Verify that the output block starts with the nonce and that it is
     * padded correctly.  We need to do this very carefully to avoid leaking
     * any information that could be used in a padding oracle attack.  Use the
     * same algorithm as the reference implementation of SATURNIN-Short */
    check1 = 0;
    for (index = 0; index < 16; ++index)
        check1 |= npub[index] ^ block[index];
    check2 = 0xFF;
    len = 0;
    for (index = 15; index >= 0; --index) {
        unsigned temp = block[16 + index];
        unsigned temp2 = check2 & -(1 - (((temp ^ 0x80) + 0xFF) >> 8));
        len |= temp2 & (unsigned)index;
        check2 &= ~temp2;
        check1 |= check2 & ((temp + 0xFF) >> 8);
    }
    check1 |= check2;

    /* At this point, check1 is zero if the nonce and plaintext are good,
     * or non-zero if there was an error in the decrypted data */
    result = (((int)check1) - 1) >> 8;

    /* The "result" is -1 if the data is good or zero if the data is invalid.
     * Copy either the plaintext or zeroes to the output buffer.  We assume
     * that the output buffer has space for up to 15 bytes.  This may return
     * some of the padding to the caller but as long as they restrict
     * themselves to the first *mlen bytes then it shouldn't be a problem */
    for (index = 0; index < 15; ++index)
        m[index] = block[16 + index] & result;
    *mlen = len;
    return ~result;
}

int saturnin_hash
    (unsigned char *out, const unsigned char *in, unsigned long long inlen)
{
    unsigned char tag[32];
    unsigned char block[32];
    memset(tag, 0, sizeof(tag));
319 320
    saturnin_authenticate
        (tag, block, in, inlen, SATURNIN_DOMAIN_16_7, SATURNIN_DOMAIN_16_8);
Rhys Weatherley committed
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
    memcpy(out, tag, 32);
    return 0;
}

void saturnin_hash_init(saturnin_hash_state_t *state)
{
    memset(state, 0, sizeof(saturnin_hash_state_t));
}

void saturnin_hash_update
    (saturnin_hash_state_t *state, const unsigned char *in,
     unsigned long long inlen)
{
    unsigned temp;

    /* Handle the partial left-over block from last time */
    if (state->s.count) {
        temp = 32 - state->s.count;
        if (temp > inlen) {
            temp = (unsigned)inlen;
            memcpy(state->s.block + state->s.count, in, temp);
            state->s.count += temp;
            return;
        }
        memcpy(state->s.block + state->s.count, in, temp);
        state->s.count = 0;
        in += temp;
        inlen -= temp;
349 350
        saturnin_block_encrypt_xor
            (state->s.block, state->s.hash, SATURNIN_DOMAIN_16_7);
Rhys Weatherley committed
351 352 353 354
    }

    /* Process full blocks that are aligned at state->s.count == 0 */
    while (inlen >= 32) {
355 356
        saturnin_block_encrypt_xor
            (in, state->s.hash, SATURNIN_DOMAIN_16_7);
Rhys Weatherley committed
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
        in += 32;
        inlen -= 32;
    }

    /* Process the left-over block at the end of the input */
    temp = (unsigned)inlen;
    memcpy(state->s.block, in, temp);
    state->s.count = temp;
}

void saturnin_hash_finalize
    (saturnin_hash_state_t *state, unsigned char *out)
{
    /* Pad the final block */
    state->s.block[state->s.count] = 0x80;
    memset(state->s.block + state->s.count + 1, 0, 31 - state->s.count);

    /* Generate the final hash value */
375 376
    saturnin_block_encrypt_xor
        (state->s.block, state->s.hash, SATURNIN_DOMAIN_16_8);
Rhys Weatherley committed
377 378
    memcpy(out, state->s.hash, 32);
}