From 0f1aaaaf76f737fd0b421af00c49b82fd3d17d5b Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Mon, 14 Aug 2017 15:32:55 -0400 Subject: Support larger block sizes in OCB This doesn't match the draft-3 test vectors and may be bogus. [ci skip] --- src/lib/modes/aead/ocb/ocb.cpp | 182 +++++++++++++++++++++++---------- src/lib/modes/aead/ocb/ocb.h | 11 +- src/tests/data/aead/ocb.vec | 8 ++ src/tests/data/ocb_wide.vec | 31 ++++++ src/tests/data/ocb_wide_long.vec | 12 +++ src/tests/test_ocb.cpp | 216 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 401 insertions(+), 59 deletions(-) create mode 100644 src/tests/data/ocb_wide.vec create mode 100644 src/tests/data/ocb_wide_long.vec diff --git a/src/lib/modes/aead/ocb/ocb.cpp b/src/lib/modes/aead/ocb/ocb.cpp index aa8532526..7ebb0a5f0 100644 --- a/src/lib/modes/aead/ocb/ocb.cpp +++ b/src/lib/modes/aead/ocb/ocb.cpp @@ -1,6 +1,6 @@ /* * OCB Mode -* (C) 2013 Jack Lloyd +* (C) 2013,2017 Jack Lloyd * (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) @@ -30,16 +30,18 @@ class L_computer const secure_vector& operator()(size_t i) const { return get(i); } - const secure_vector& compute_offsets(secure_vector& offset, - size_t block_index, - size_t blocks) const + const secure_vector& + compute_offsets(secure_vector& offset, + size_t block_index, + size_t blocks, + size_t BS) const { - m_offset_buf.resize(blocks * 16); + m_offset_buf.resize(blocks * BS); for(size_t i = 0; i != blocks; ++i) { // could be done in parallel offset ^= get(ctz(block_index + 1 + i)); - copy_mem(&m_offset_buf[16*i], offset.data(), 16); + copy_mem(&m_offset_buf[BS*i], offset.data(), BS); } return m_offset_buf; @@ -72,16 +74,17 @@ namespace { * OCB's HASH */ secure_vector ocb_hash(const L_computer& L, - const BlockCipher& cipher, - const uint8_t ad[], size_t ad_len) + const BlockCipher& cipher, + const uint8_t ad[], size_t ad_len) { - secure_vector sum(16); - secure_vector offset(16); + const size_t BS = cipher.block_size(); + secure_vector sum(BS); + secure_vector offset(BS); - secure_vector buf(16); + secure_vector buf(BS); - const size_t ad_blocks = (ad_len / 16); - const size_t ad_remainder = (ad_len % 16); + const size_t ad_blocks = (ad_len / BS); + const size_t ad_remainder = (ad_len % BS); for(size_t i = 0; i != ad_blocks; ++i) { @@ -89,7 +92,7 @@ secure_vector ocb_hash(const L_computer& L, offset ^= L(ctz(i+1)); buf = offset; - xor_buf(buf.data(), &ad[16*i], 16); + xor_buf(buf.data(), &ad[BS*i], BS); cipher.encrypt(buf); @@ -101,8 +104,8 @@ secure_vector ocb_hash(const L_computer& L, offset ^= L.star(); buf = offset; - xor_buf(buf.data(), &ad[16*ad_blocks], ad_remainder); - buf[ad_len % 16] ^= 0x80; + xor_buf(buf.data(), &ad[BS*ad_blocks], ad_remainder); + buf[ad_len % BS] ^= 0x80; cipher.encrypt(buf); @@ -117,14 +120,21 @@ secure_vector ocb_hash(const L_computer& L, OCB_Mode::OCB_Mode(BlockCipher* cipher, size_t tag_size) : m_cipher(cipher), m_checksum(m_cipher->parallel_bytes()), - m_offset(16), - m_ad_hash(16), + m_offset(m_cipher->block_size()), + m_ad_hash(m_cipher->block_size()), m_tag_size(tag_size) { - if(m_cipher->block_size() != 16) - throw Invalid_Argument("OCB requires 128 bit cipher"); + const size_t BS = m_cipher->block_size(); - if(m_tag_size % 4 != 0 || m_tag_size < 8 || m_tag_size > 16) + /* + * draft-krovetz-ocb-wide-d1 specifies OCB for several other block + * sizes but only 128, 192, 256 and 512 bit are currently supported + * by this implementation. + */ + if(BS != 16 && BS != 24 && BS != 32 && BS != 64) + throw Invalid_Argument("OCB does not support cipher " + m_cipher->name()); + + if(m_tag_size % 4 != 0 || m_tag_size < 8 || m_tag_size > BS || m_tag_size > 32) throw Invalid_Argument("Invalid OCB tag length"); } @@ -149,12 +159,17 @@ void OCB_Mode::reset() bool OCB_Mode::valid_nonce_length(size_t length) const { - return (length > 0 && length < m_cipher->block_size()); + if(length == 0) + return false; + if(m_cipher->block_size() == 16) + return length < 16; + else + return length < (m_cipher->block_size() - 1); } std::string OCB_Mode::name() const { - return m_cipher->name() + "/OCB"; // include tag size + return m_cipher->name() + "/OCB"; // include tag size? } size_t OCB_Mode::update_granularity() const @@ -182,16 +197,24 @@ void OCB_Mode::set_associated_data(const uint8_t ad[], size_t ad_len) secure_vector OCB_Mode::update_nonce(const uint8_t nonce[], size_t nonce_len) { - BOTAN_ASSERT(nonce_len < 16, "OCB nonce is less than cipher block size"); + const size_t BS = m_cipher->block_size(); + + BOTAN_ASSERT(BS == 16 || BS == 24 || BS == 32 || BS == 64, + "OCB block size is supported"); + + const size_t MASKLEN = (BS == 16 ? 6 : ((BS == 24) ? 7 : 8)); + + const uint8_t BOTTOM_MASK = + static_cast((static_cast(1) << MASKLEN) - 1); - secure_vector nonce_buf(16); + secure_vector nonce_buf(BS); - copy_mem(&nonce_buf[16 - nonce_len], nonce, nonce_len); - nonce_buf[0] = ((tag_size() * 8) % 128) << 1; - nonce_buf[16 - nonce_len - 1] = 1; + copy_mem(&nonce_buf[BS - nonce_len], nonce, nonce_len); + nonce_buf[0] = ((tag_size()*8) % (BS*8)) << (BS <= 16 ? 1 : 0); + nonce_buf[BS - nonce_len - 1] ^= 1; - const uint8_t bottom = nonce_buf[16-1] & 0x3F; - nonce_buf[16-1] &= 0xC0; + const uint8_t bottom = nonce_buf[BS-1] & BOTTOM_MASK; + nonce_buf[BS-1] &= ~BOTTOM_MASK; const bool need_new_stretch = (m_last_nonce != nonce_buf); @@ -201,8 +224,47 @@ OCB_Mode::update_nonce(const uint8_t nonce[], size_t nonce_len) m_cipher->encrypt(nonce_buf); - for(size_t i = 0; i != 16 / 2; ++i) - nonce_buf.push_back(nonce_buf[i] ^ nonce_buf[i+1]); + /* + The loop bounds (BS vs BS/2) are derived from the relation + between the block size and the MASKLEN. Using the terminology + of draft-krovetz-ocb-wide, we have to derive enough bits in + ShiftedKtop to read up to BLOCKLEN+bottom bits from Stretch. + + +----------+---------+-------+---------+ + | BLOCKLEN | RESIDUE | SHIFT | MASKLEN | + +----------+---------+-------+---------+ + | 32 | 141 | 17 | 4 | + | 64 | 27 | 25 | 5 | + | 96 | 1601 | 33 | 6 | + | 128 | 135 | 8 | 6 | + | 192 | 135 | 40 | 7 | + | 256 | 1061 | 1 | 8 | + | 384 | 4109 | 80 | 8 | + | 512 | 293 | 176 | 8 | + | 1024 | 524355 | 352 | 9 | + +----------+---------+-------+---------+ + */ + + if(BS == 16) + { + for(size_t i = 0; i != BS / 2; ++i) + nonce_buf.push_back(nonce_buf[i] ^ nonce_buf[i+1]); + } + else if(BS == 24) + { + for(size_t i = 0; i != 16; ++i) + nonce_buf.push_back(nonce_buf[i] ^ nonce_buf[i+5]); + } + else if(BS == 32) + { + for(size_t i = 0; i != BS; ++i) + nonce_buf.push_back(nonce_buf[i] ^ (nonce_buf[i] << 1) ^ (nonce_buf[i+1] >> 7)); + } + else if(BS == 64) + { + for(size_t i = 0; i != BS / 2; ++i) + nonce_buf.push_back(nonce_buf[i] ^ nonce_buf[i+22]); + } m_stretch = nonce_buf; } @@ -212,8 +274,10 @@ OCB_Mode::update_nonce(const uint8_t nonce[], size_t nonce_len) const size_t shift_bytes = bottom / 8; const size_t shift_bits = bottom % 8; - secure_vector offset(16); - for(size_t i = 0; i != 16; ++i) + BOTAN_ASSERT(m_stretch.size() >= BS + shift_bytes + 1, "Size ok"); + + secure_vector offset(BS); + for(size_t i = 0; i != BS; ++i) { offset[i] = (m_stretch[i+shift_bytes] << shift_bits); offset[i] |= (m_stretch[i+shift_bytes+1] >> (8-shift_bits)); @@ -236,16 +300,17 @@ void OCB_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) void OCB_Encryption::encrypt(uint8_t buffer[], size_t blocks) { - const size_t par_blocks = m_checksum.size() / 16; + const size_t BS = m_cipher->block_size(); + const size_t par_blocks = m_checksum.size() / BS; while(blocks) { const size_t proc_blocks = std::min(blocks, par_blocks); - const size_t proc_bytes = proc_blocks * 16; + const size_t proc_bytes = proc_blocks * BS; BOTAN_ASSERT(m_L, "A key was set"); - const auto& offsets = m_L->compute_offsets(m_offset, m_block_index, proc_blocks); + const auto& offsets = m_L->compute_offsets(m_offset, m_block_index, proc_blocks, BS); xor_buf(m_checksum.data(), buffer, proc_bytes); @@ -261,27 +326,30 @@ void OCB_Encryption::encrypt(uint8_t buffer[], size_t blocks) size_t OCB_Encryption::process(uint8_t buf[], size_t sz) { - BOTAN_ASSERT(sz % 16 == 0, "Invalid OCB input size"); - encrypt(buf, sz / 16); + const size_t BS = m_cipher->block_size(); + BOTAN_ASSERT(sz % BS == 0, "Invalid OCB input size"); + encrypt(buf, sz / BS); return sz; } void OCB_Encryption::finish(secure_vector& buffer, size_t offset) { + const size_t BS = m_cipher->block_size(); + BOTAN_ASSERT(buffer.size() >= offset, "Offset is sane"); const size_t sz = buffer.size() - offset; uint8_t* buf = buffer.data() + offset; if(sz) { - const size_t final_full_blocks = sz / 16; - const size_t remainder_bytes = sz - (final_full_blocks * 16); + const size_t final_full_blocks = sz / BS; + const size_t remainder_bytes = sz - (final_full_blocks * BS); encrypt(buf, final_full_blocks); if(remainder_bytes) { - BOTAN_ASSERT(remainder_bytes < 16, "Only a partial block left"); + BOTAN_ASSERT(remainder_bytes < BS, "Only a partial block left"); uint8_t* remainder = &buf[sz - remainder_bytes]; xor_buf(m_checksum.data(), remainder, remainder_bytes); @@ -289,13 +357,13 @@ void OCB_Encryption::finish(secure_vector& buffer, size_t offset) m_offset ^= m_L->star(); // Offset_* - secure_vector zeros(16); + secure_vector zeros(BS); m_cipher->encrypt(m_offset, zeros); xor_buf(remainder, zeros.data(), remainder_bytes); } } - secure_vector checksum(16); + secure_vector checksum(BS); // fold checksum for(size_t i = 0; i != m_checksum.size(); ++i) @@ -319,18 +387,19 @@ void OCB_Encryption::finish(secure_vector& buffer, size_t offset) void OCB_Decryption::decrypt(uint8_t buffer[], size_t blocks) { + const size_t BS = m_cipher->block_size(); const size_t par_bytes = m_cipher->parallel_bytes(); - BOTAN_ASSERT(par_bytes % 16 == 0, "Cipher is parallel in full blocks"); + BOTAN_ASSERT(par_bytes % BS == 0, "Cipher is parallel in full blocks"); - const size_t par_blocks = par_bytes / 16; + const size_t par_blocks = par_bytes / BS; while(blocks) { const size_t proc_blocks = std::min(blocks, par_blocks); - const size_t proc_bytes = proc_blocks * 16; + const size_t proc_bytes = proc_blocks * BS; - const auto& offsets = m_L->compute_offsets(m_offset, m_block_index, proc_blocks); + const auto& offsets = m_L->compute_offsets(m_offset, m_block_index, proc_blocks, BS); xor_buf(buffer, offsets.data(), proc_bytes); m_cipher->decrypt_n(buffer, buffer, proc_blocks); @@ -346,13 +415,16 @@ void OCB_Decryption::decrypt(uint8_t buffer[], size_t blocks) size_t OCB_Decryption::process(uint8_t buf[], size_t sz) { - BOTAN_ASSERT(sz % 16 == 0, "Invalid OCB input size"); - decrypt(buf, sz / 16); + const size_t BS = m_cipher->block_size(); + BOTAN_ASSERT(sz % BS == 0, "Invalid OCB input size"); + decrypt(buf, sz / BS); return sz; } void OCB_Decryption::finish(secure_vector& buffer, size_t offset) { + const size_t BS = m_cipher->block_size(); + BOTAN_ASSERT(buffer.size() >= offset, "Offset is sane"); const size_t sz = buffer.size() - offset; uint8_t* buf = buffer.data() + offset; @@ -363,20 +435,20 @@ void OCB_Decryption::finish(secure_vector& buffer, size_t offset) if(remaining) { - const size_t final_full_blocks = remaining / 16; - const size_t final_bytes = remaining - (final_full_blocks * 16); + const size_t final_full_blocks = remaining / BS; + const size_t final_bytes = remaining - (final_full_blocks * BS); decrypt(buf, final_full_blocks); if(final_bytes) { - BOTAN_ASSERT(final_bytes < 16, "Only a partial block left"); + BOTAN_ASSERT(final_bytes < BS, "Only a partial block left"); uint8_t* remainder = &buf[remaining - final_bytes]; m_offset ^= m_L->star(); // Offset_* - secure_vector pad(16); + secure_vector pad(BS); m_cipher->encrypt(m_offset, pad); // P_* xor_buf(remainder, pad.data(), final_bytes); @@ -386,7 +458,7 @@ void OCB_Decryption::finish(secure_vector& buffer, size_t offset) } } - secure_vector checksum(16); + secure_vector checksum(BS); // fold checksum for(size_t i = 0; i != m_checksum.size(); ++i) diff --git a/src/lib/modes/aead/ocb/ocb.h b/src/lib/modes/aead/ocb/ocb.h index 174812ee3..69f5fde60 100644 --- a/src/lib/modes/aead/ocb/ocb.h +++ b/src/lib/modes/aead/ocb/ocb.h @@ -22,6 +22,9 @@ class L_computer; * * @see "The OCB Authenticated-Encryption Algorithm" RFC 7253 * https://tools.ietf.org/html/rfc7253 +* @see "OCB For Block Ciphers Without 128-Bit Blocks" +* (draft-krovetz-ocb-wide-d3) for the extension of OCB to +* block ciphers with larger block sizes. * @see Free Licenses http://www.cs.ucdavis.edu/~rogaway/ocb/license.htm * @see OCB home page http://www.cs.ucdavis.edu/~rogaway/ocb */ @@ -47,7 +50,7 @@ class BOTAN_DLL OCB_Mode : public AEAD_Mode ~OCB_Mode(); protected: /** - * @param cipher the 128-bit block cipher to use + * @param cipher the block cipher to use * @param tag_size is how big the auth tag will be */ OCB_Mode(BlockCipher* cipher, size_t tag_size); @@ -68,7 +71,7 @@ class BOTAN_DLL OCB_Mode : public AEAD_Mode secure_vector update_nonce(const uint8_t nonce[], size_t nonce_len); - size_t m_tag_size = 0; + const size_t m_tag_size = 0; secure_vector m_last_nonce; secure_vector m_stretch; }; @@ -77,7 +80,7 @@ class BOTAN_DLL OCB_Encryption final : public OCB_Mode { public: /** - * @param cipher the 128-bit block cipher to use + * @param cipher the block cipher to use * @param tag_size is how big the auth tag will be */ OCB_Encryption(BlockCipher* cipher, size_t tag_size = 16) : @@ -99,7 +102,7 @@ class BOTAN_DLL OCB_Decryption final : public OCB_Mode { public: /** - * @param cipher the 128-bit block cipher to use + * @param cipher the block cipher to use * @param tag_size is how big the auth tag will be */ OCB_Decryption(BlockCipher* cipher, size_t tag_size = 16) : diff --git a/src/tests/data/aead/ocb.vec b/src/tests/data/aead/ocb.vec index f0fbb3646..176dcb2b8 100644 --- a/src/tests/data/aead/ocb.vec +++ b/src/tests/data/aead/ocb.vec @@ -248,3 +248,11 @@ Nonce = BBAA9988776655443322110D AD = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 In = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627 Out = 1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6AD0C515F4D1CDD4FDAC4F02AA + +# Generated by Botan, unconfirmed result +[Threefish-512/OCB(32)] +Key = 1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE8841792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884 +Nonce = D5CA91748410C1751FF8A2F61825 +AD = C5CD9D1850C141E358649994EE701B68 +In = 2942BFC773BDA23CABC6ACFD9BFD5835BD300F0973 +Out = 45EEFFF01CDA61695EA24B036074491FE61B96C94337F0F947FB4E10E679A9F2A825DF8CEA530A2784E5640A768DE536C76A79157E diff --git a/src/tests/data/ocb_wide.vec b/src/tests/data/ocb_wide.vec new file mode 100644 index 000000000..d8cede352 --- /dev/null +++ b/src/tests/data/ocb_wide.vec @@ -0,0 +1,31 @@ + + +Key = 8182838485868788898A8B8C8D8E8F909192939495969798 +Nonce = F0F1F2F3F4F5F6F7F8F9FAFB +AD = +In = +Out = F00F1A7125DACF8B57D0F50E6B44615C9996D209B50A1ED7 + +Key = 9192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8 +Nonce = F1F2F3F4F5F6F7F8F9FAFBFC +AD = 05060708090A0B0C0D0E0F10 +In = 0102030405060708090A0B0C +Out = 9AFC5E331177D5A4534506C8670BAFC0E4882C6F9E82C72BD79BDF9E5AD6D4C83955F021 + +Key = A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8 +Nonce = F2F3F4F5F6F7F8F9FAFBFCFD +AD = 060708090A0B0C0D0E0F101112131415161718191A1B1C1D +In = 02030405060708090A0B0C0D0E0F10111213141516171819 +Out = 92A7C0C02A1F6E154762A3C3885DAFF1FAED6ACB59EC9E625995C61B5E92C5254F63D449CD41F4F2F6F9EAF61CD08670 + +Key = B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8 +Nonce = F3F4F5F6F7F8F9FAFBFCFDFE +AD = 0708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A +In = 030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223242526 +Out = 2070BEDB155997BFA6DE55F27CD45AA8223B16312965A814D347CC7EF551DA09E7BCB1806D9418BB37C64AB851272D0D193F32BCB7B081A149C84723 + +Key = C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8 +Nonce = F4F5F6F7F8F9FAFBFCFDFEFF +AD = 08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F3031323334353637 +In = 0405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233 +Out = 70E46BCD56C24DCBFC2A280C3C2A26A9AAB9A097AA3EB37352EE799774C85F517E809AF6FEC0D5524972730C09B52555889E94B2D0C6CCF2079291B49B8E86F9D4C7D6EE081E165A diff --git a/src/tests/data/ocb_wide_long.vec b/src/tests/data/ocb_wide_long.vec new file mode 100644 index 000000000..7c17f00bf --- /dev/null +++ b/src/tests/data/ocb_wide_long.vec @@ -0,0 +1,12 @@ + +Blocklen = 128 +Output = 0D099181BE37171BF94582877D6D4693 + +Blocklen = 192 +Output = C6B3449A7A5C174253720B65198779C0E1758794C023F567 + +Blocklen = 256 +Output = 87F321F24B0554565BEB6C994AD04F8F95F1A808E67EAFBD60E0E86152AFB37C + +Blocklen = 512 +Output = 6748655A0A83543D8AA6287AE9FFC37C9A433332DDFD4E8B42F94D741944D440 diff --git a/src/tests/test_ocb.cpp b/src/tests/test_ocb.cpp index ede15fb82..cacf9a0d4 100644 --- a/src/tests/test_ocb.cpp +++ b/src/tests/test_ocb.cpp @@ -9,6 +9,7 @@ #if defined(BOTAN_HAS_AEAD_OCB) #include #include + #include #endif namespace Botan_Tests { @@ -17,6 +18,221 @@ namespace { #if defined(BOTAN_HAS_AEAD_OCB) +// Toy cipher used for wide block tests + +class OCB_Wide_Test_Block_Cipher : public Botan::BlockCipher + { + public: + OCB_Wide_Test_Block_Cipher(size_t bs) : m_bs(bs) {} + + std::string name() const override { return "OCB_ToyCipher"; } + size_t block_size() const override { return m_bs; } + void clear() override { m_key.clear(); } + + Botan::BlockCipher* clone() const { return new OCB_Wide_Test_Block_Cipher(m_bs); } + + void key_schedule(const uint8_t key[], size_t length) override + { + m_key.assign(key, key + length); + } + + Botan::Key_Length_Specification key_spec() const override + { + return Botan::Key_Length_Specification(m_bs); + } + + void encrypt_n(const uint8_t in[], uint8_t out[], + size_t blocks) const override + { + while(blocks) + { + Botan::copy_mem(out, in, m_bs); + Botan::poly_double_n(out, m_bs); + + for(size_t i = 0; i != m_bs; ++i) + out[i] ^= m_key[i]; + + blocks--; + in += block_size(); + out += block_size(); + } + } + + void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override + { + while(blocks) + { + for(size_t i = 0; i != m_bs; ++i) + out[i] = in[i] ^ m_key[i]; + + const uint8_t bottom_carry = in[m_bs-1] & 0x01; + + if(bottom_carry) + { + if(m_bs == 16 || m_bs == 24) + { + out[m_bs-1] ^= 0x87; + } + else if(m_bs == 32) + { + out[m_bs-2] ^= 0x4; + out[m_bs-1] ^= 0x25; + } + else if(m_bs == 64) + { + out[m_bs-2] ^= 0x1; + out[m_bs-1] ^= 0x25; + } + else + throw Test_Error("Bad OCB test block size"); + } + + uint8_t carry = bottom_carry << 7; + + for(size_t i = 0; i != m_bs; ++i) + { + uint8_t temp = out[i]; + out[i] = (temp >> 1) | carry; + carry = (temp & 0x1) << 7; + } + + blocks--; + in += block_size(); + out += block_size(); + } + } + private: + size_t m_bs; + std::vector m_key; + }; + +class OCB_Wide_KAT_Tests : public Text_Based_Test + { + public: + OCB_Wide_KAT_Tests() + : Text_Based_Test("ocb_wide.vec", "Key,Nonce,AD,In,Out") {} + + Test::Result run_one_test(const std::string&, const VarMap& vars) override + { + Test::Result result("OCB wide block KAT"); + + const std::vector key = get_req_bin(vars, "Key"); + const std::vector nonce = get_req_bin(vars, "Nonce"); + const std::vector ad = get_req_bin(vars, "AD"); + const std::vector input = get_req_bin(vars, "In"); + const std::vector expected = get_req_bin(vars, "Out"); + + const size_t bs = key.size(); + Botan::secure_vector buf(input.begin(), input.end()); + + Botan::OCB_Encryption enc(new OCB_Wide_Test_Block_Cipher(bs), std::min(bs, 32)); + enc.set_key(key); + enc.set_ad(ad); + enc.start(nonce); + enc.finish(buf); + result.test_eq("Ciphertext matches", buf, expected); + + Botan::OCB_Decryption dec(new OCB_Wide_Test_Block_Cipher(bs), std::min(bs, 32)); + dec.set_key(key); + dec.set_ad(ad); + dec.start(nonce); + dec.finish(buf); + result.test_eq("Decryption correct", buf, input); + + return result; + } + }; + +BOTAN_REGISTER_TEST("ocb_wide", OCB_Wide_KAT_Tests); + +class OCB_Wide_Long_KAT_Tests : public Text_Based_Test + { + public: + OCB_Wide_Long_KAT_Tests() + : Text_Based_Test("ocb_wide_long.vec", "Blocklen,Output") {} + + Test::Result run_one_test(const std::string&, const VarMap& vars) override + { + Test::Result result("OCB wide block long test"); + + const size_t bs = get_req_sz(vars, "Blocklen") / 8; + const std::vector expected = get_req_bin(vars, "Output"); + + if(bs != 16 && bs != 24 && bs != 32 && bs != 64) + throw Test_Error("Unsupported Blocklen in OCB wide block test"); + + Botan::OCB_Encryption enc(new OCB_Wide_Test_Block_Cipher(bs), std::min(bs, 32)); + + /* + Y, string of length min(B, 256) bits + + Y is defined as follows. + + K = (0xA0 || 0xA1 || 0xA2 || ...)[1..B] + C = + for i = 0 to 127 do + S = (0x50 || 0x51 || 0x52 || ...)[1..8i] + N = num2str(3i+1,16) + C = C || OCB-ENCRYPT(K,N,S,S) + N = num2str(3i+2,16) + C = C || OCB-ENCRYPT(K,N,,S) + N = num2str(3i+3,16) + C = C || OCB-ENCRYPT(K,N,S,) + end for + N = num2str(385,16) + Y = OCB-ENCRYPT(K,N,C,) + */ + + std::vector key(bs); + for(size_t i = 0; i != bs; ++i) + key[i] = 0xA0 + i; + + enc.set_key(key); + + const std::vector empty; + std::vector N(2); + std::vector C; + + for(size_t i = 0; i != 128; ++i) + { + const std::vector S(i); + + Botan::store_be(static_cast(3 * i + 1), &N[8]); + + ocb_encrypt(result, C, enc, N, S, S); + Botan::store_be(static_cast(3 * i + 2), &N[8]); + ocb_encrypt(result, C, enc, N, S, empty); + Botan::store_be(static_cast(3 * i + 3), &N[8]); + ocb_encrypt(result, C, enc, N, empty, S); + } + + Botan::store_be(static_cast(385), &N[8]); + std::vector final_result; + ocb_encrypt(result, final_result, enc, N, empty, C); + + result.test_eq("correct value", final_result, expected); + + return result; + } + + private: + void ocb_encrypt(Test::Result& result, + std::vector& output_to, + Botan::OCB_Encryption& enc, + const std::vector& nonce, + const std::vector& pt, + const std::vector& ad) + { + enc.set_associated_data(ad.data(), ad.size()); + enc.start(nonce.data(), nonce.size()); + Botan::secure_vector buf(pt.begin(), pt.end()); + enc.finish(buf, 0); + output_to.insert(output_to.end(), buf.begin(), buf.end()); + } + }; + +BOTAN_REGISTER_TEST("ocb_long_wide", OCB_Wide_Long_KAT_Tests); + class OCB_Long_KAT_Tests : public Text_Based_Test { public: -- cgit v1.2.3 From 18a6033af50c1c5a37dc4cb48b5e1b313e2773bf Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Mon, 11 Sep 2017 13:08:54 -0400 Subject: Fix bugs in OCB long test --- src/tests/test_ocb.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/tests/test_ocb.cpp b/src/tests/test_ocb.cpp index cacf9a0d4..aa9343e7f 100644 --- a/src/tests/test_ocb.cpp +++ b/src/tests/test_ocb.cpp @@ -195,18 +195,20 @@ class OCB_Wide_Long_KAT_Tests : public Text_Based_Test for(size_t i = 0; i != 128; ++i) { - const std::vector S(i); + std::vector S(i); + for(size_t j = 0; j != S.size(); ++j) + S[j] = 0x50 + j; - Botan::store_be(static_cast(3 * i + 1), &N[8]); + Botan::store_be(static_cast(3 * i + 1), &N[0]); ocb_encrypt(result, C, enc, N, S, S); - Botan::store_be(static_cast(3 * i + 2), &N[8]); + Botan::store_be(static_cast(3 * i + 2), &N[0]); ocb_encrypt(result, C, enc, N, S, empty); - Botan::store_be(static_cast(3 * i + 3), &N[8]); + Botan::store_be(static_cast(3 * i + 3), &N[0]); ocb_encrypt(result, C, enc, N, empty, S); } - Botan::store_be(static_cast(385), &N[8]); + Botan::store_be(static_cast(385), &N[0]); std::vector final_result; ocb_encrypt(result, final_result, enc, N, empty, C); -- cgit v1.2.3 From 3ad91d3c8c06cf77e69b9a1c80fce236f660956b Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Mon, 11 Sep 2017 13:12:13 -0400 Subject: Add alternate form for matching OCB ref code --- src/lib/modes/aead/ocb/ocb.cpp | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/lib/modes/aead/ocb/ocb.cpp b/src/lib/modes/aead/ocb/ocb.cpp index 7ebb0a5f0..4eb8089b5 100644 --- a/src/lib/modes/aead/ocb/ocb.cpp +++ b/src/lib/modes/aead/ocb/ocb.cpp @@ -210,7 +210,12 @@ OCB_Mode::update_nonce(const uint8_t nonce[], size_t nonce_len) secure_vector nonce_buf(BS); copy_mem(&nonce_buf[BS - nonce_len], nonce, nonce_len); + #if 0 nonce_buf[0] = ((tag_size()*8) % (BS*8)) << (BS <= 16 ? 1 : 0); + #else + nonce_buf[0] = (tag_size()*8) << (BS <= 16 ? 1 : 0); + #endif + nonce_buf[BS - nonce_len - 1] ^= 1; const uint8_t bottom = nonce_buf[BS-1] & BOTTOM_MASK; @@ -244,7 +249,7 @@ OCB_Mode::update_nonce(const uint8_t nonce[], size_t nonce_len) | 1024 | 524355 | 352 | 9 | +----------+---------+-------+---------+ */ - +#if 0 if(BS == 16) { for(size_t i = 0; i != BS / 2; ++i) @@ -265,12 +270,35 @@ OCB_Mode::update_nonce(const uint8_t nonce[], size_t nonce_len) for(size_t i = 0; i != BS / 2; ++i) nonce_buf.push_back(nonce_buf[i] ^ nonce_buf[i+22]); } +#else + nonce_buf.insert(nonce_buf.end(), nonce_buf.begin(), nonce_buf.end()); + + if(BS == 16) + { + for(size_t i = BS; i != BS + (BS / 2); ++i) + nonce_buf[i] ^= nonce_buf[i+1]; + } + else if(BS == 24) + { + for(size_t i = BS; i != BS + (BS / 2); ++i) + nonce_buf[i] ^= nonce_buf[i+5]; + } + else if(BS == 32) + { + for(size_t i = BS; i != BS + (BS / 2); ++i) + nonce_buf[i] ^= (nonce_buf[i] << 1) ^ (nonce_buf[i+1] >> 7); + } + else if(BS == 64) + { + for(size_t i = BS; i != BS + (BS / 2); ++i) + nonce_buf[i] ^= nonce_buf[i+22]; + } +#endif m_stretch = nonce_buf; } // now set the offset from stretch and bottom - const size_t shift_bytes = bottom / 8; const size_t shift_bits = bottom % 8; @@ -373,9 +401,7 @@ void OCB_Encryption::finish(secure_vector& buffer, size_t offset) secure_vector mac = m_offset; mac ^= checksum; mac ^= m_L->dollar(); - m_cipher->encrypt(mac); - mac ^= m_ad_hash; buffer += std::make_pair(mac.data(), tag_size()); -- cgit v1.2.3 From 92245ad040b8f0e08a4a57137be5739e5c7bfbdc Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Fri, 15 Sep 2017 12:41:59 -0400 Subject: Change wide block OCB Ted Krovetz confirmed there were bugs in the reference code for blocks > 128 bits so these values should be the correct ones. --- src/lib/modes/aead/ocb/ocb.cpp | 29 ----------------------------- src/tests/data/aead/ocb.vec | 21 +++++++++++++++++++++ src/tests/data/ocb_wide.vec | 39 ++++++++++++++++++++++++++++++++++----- src/tests/data/ocb_wide_long.vec | 15 +++++++++------ src/tests/test_ocb.cpp | 33 ++++++++++++++++++++++++++------- src/tests/tests.cpp | 5 ++++- 6 files changed, 94 insertions(+), 48 deletions(-) diff --git a/src/lib/modes/aead/ocb/ocb.cpp b/src/lib/modes/aead/ocb/ocb.cpp index 4eb8089b5..e580c95d9 100644 --- a/src/lib/modes/aead/ocb/ocb.cpp +++ b/src/lib/modes/aead/ocb/ocb.cpp @@ -210,11 +210,7 @@ OCB_Mode::update_nonce(const uint8_t nonce[], size_t nonce_len) secure_vector nonce_buf(BS); copy_mem(&nonce_buf[BS - nonce_len], nonce, nonce_len); - #if 0 nonce_buf[0] = ((tag_size()*8) % (BS*8)) << (BS <= 16 ? 1 : 0); - #else - nonce_buf[0] = (tag_size()*8) << (BS <= 16 ? 1 : 0); - #endif nonce_buf[BS - nonce_len - 1] ^= 1; @@ -249,7 +245,6 @@ OCB_Mode::update_nonce(const uint8_t nonce[], size_t nonce_len) | 1024 | 524355 | 352 | 9 | +----------+---------+-------+---------+ */ -#if 0 if(BS == 16) { for(size_t i = 0; i != BS / 2; ++i) @@ -270,30 +265,6 @@ OCB_Mode::update_nonce(const uint8_t nonce[], size_t nonce_len) for(size_t i = 0; i != BS / 2; ++i) nonce_buf.push_back(nonce_buf[i] ^ nonce_buf[i+22]); } -#else - nonce_buf.insert(nonce_buf.end(), nonce_buf.begin(), nonce_buf.end()); - - if(BS == 16) - { - for(size_t i = BS; i != BS + (BS / 2); ++i) - nonce_buf[i] ^= nonce_buf[i+1]; - } - else if(BS == 24) - { - for(size_t i = BS; i != BS + (BS / 2); ++i) - nonce_buf[i] ^= nonce_buf[i+5]; - } - else if(BS == 32) - { - for(size_t i = BS; i != BS + (BS / 2); ++i) - nonce_buf[i] ^= (nonce_buf[i] << 1) ^ (nonce_buf[i+1] >> 7); - } - else if(BS == 64) - { - for(size_t i = BS; i != BS + (BS / 2); ++i) - nonce_buf[i] ^= nonce_buf[i+22]; - } -#endif m_stretch = nonce_buf; } diff --git a/src/tests/data/aead/ocb.vec b/src/tests/data/aead/ocb.vec index 176dcb2b8..b2c4e4744 100644 --- a/src/tests/data/aead/ocb.vec +++ b/src/tests/data/aead/ocb.vec @@ -256,3 +256,24 @@ Nonce = D5CA91748410C1751FF8A2F61825 AD = C5CD9D1850C141E358649994EE701B68 In = 2942BFC773BDA23CABC6ACFD9BFD5835BD300F0973 Out = 45EEFFF01CDA61695EA24B036074491FE61B96C94337F0F947FB4E10E679A9F2A825DF8CEA530A2784E5640A768DE536C76A79157E + +[SHACAL2/OCB(32)] +# Generated by Botan, unconfirmed result +Key = 4412923493C57D5DE0D700F753CCE0D1D2D95060122E9F15A5DDBFC5787E50B5 +Nonce = BBAA9988776655443322110D +AD = C5CD9D1850C141E358649994EE701B68 +In = FE80690BEE8A485D11F32965BC9D2A328CF761B6902EF764462AD86498CA6B97 +Out = 0407C5404170DB1A74B7AB712A8FC7D459B3E4412C7ADF632545C05E50FB0C2FE97A92A81371E7F7C04AFA10C68375A31923EDAB327DB776DBBB99ED3318424E + +# Generated by OCB reference code calling OpenSSL SHA256_Transform +Key = 6F98263502C983D78BC3F7B5208D488DC036F7BC1438AB55620CF8FB98767D070FA43C116DBFE9F883E0ADA36DF5302E2C27EA405F9595C1A18DBC3A043A4113 +Nonce = BE3FA1AB2F040615988F275402796F0A614BB4D6E4974FB7BCDB685F8B64 +AD = 1B08E7DCA97599E379D3258CB1A3B0FCE0 +In = DE7E244B3D0D43C0EDF0635BE6948912BB7352 +Out = 2FACBA6F0A62331845ACAB0F60EBA59BD2E4F4BC83A79FC73D2A0B5191C7492798BBDE2476B9D249247D1BD4B8F167333852B3 + +Key = 8182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0 +Nonce = F0F1 +AD = +In = +Out = 13EAF2583F2E24339182D3423D56759F0E05ABDD4682DBF9B1901CCCEC4FD639 diff --git a/src/tests/data/ocb_wide.vec b/src/tests/data/ocb_wide.vec index d8cede352..0bed695ef 100644 --- a/src/tests/data/ocb_wide.vec +++ b/src/tests/data/ocb_wide.vec @@ -1,31 +1,60 @@ +Key = 8182838485868788898A8B8C8D8E8F909192939495969798 +Nonce = F0F1 +AD = +In = +Out = B9CACBDCDDCECFFF75C0B9AA97808DE6FBCCC1D2AFB8B482 + +Key = 9192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8 +Nonce = F1F2 +AD = 05060708090A0B0C0D0E0F10 +In = 0102030405060708090A0B0C +Out = 4EB0BAACA698928B86E59F81B3474B5F53676B404B225E622E1206EADEA2B6BB4F736646 + +Key = A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8 +Nonce = F2F3 +AD = 060708090A0B0C0D0E0F101112131415161718191A1B1C1D +In = 02030405060708090A0B0C0D0E0F10111213141516171819 +Out = A8B5AEA7B4E9E2A2E803081982C7C46D36B380908BCEEE0775898DA1B5C9CDFF8522E6DA3E52461A7E92A69B7F132636 + +Key = B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8 +Nonce = F3F4 +AD = 0708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A +In = 030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223242526 +Out = F8F1CEBB841D227D911D024669E08D903E0768250AA3CFF7BC466C3E14D6FC8A3D3ED4A70295B8FBD6416C3B8BA5482AFF18576CA0672A468B4C021C + +Key = C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8 +Nonce = F4F5 +AD = 08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F3031323334353637 +In = 0405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233 +Out = CEFB901163563D53D5AC8764164388D091C40FEAB8ED24B7ECF1824B415C2FC95706359ED4A97B2B720FDC711B66B2915C8BC62509DE93E236965BFB57C04D57F96EE347EB7CF322 Key = 8182838485868788898A8B8C8D8E8F909192939495969798 Nonce = F0F1F2F3F4F5F6F7F8F9FAFB AD = In = -Out = F00F1A7125DACF8B57D0F50E6B44615C9996D209B50A1ED7 +Out = F00F1A7125DACF832FD0F50E6B44615C9996D209B29DF961 Key = 9192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8 Nonce = F1F2F3F4F5F6F7F8F9FAFBFC AD = 05060708090A0B0C0D0E0F10 In = 0102030405060708090A0B0C -Out = 9AFC5E331177D5A4534506C8670BAFC0E4882C6F9E82C72BD79BDF9E5AD6D4C83955F021 +Out = 9AFC5E331177D5B4A34506C8670BAFC0E4882C7F6E82C72BD79BDF9E5AD6D4C830BC7A8B Key = A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8 Nonce = F2F3F4F5F6F7F8F9FAFBFCFD AD = 060708090A0B0C0D0E0F101112131415161718191A1B1C1D In = 02030405060708090A0B0C0D0E0F10111213141516171819 -Out = 92A7C0C02A1F6E154762A3C3885DAFF1FAED6ACB59EC9E625995C61B5E92C5254F63D449CD41F4F2F6F9EAF61CD08670 +Out = 92A7C0C02A1F6E245762A3C3885DAFF1FAED6ACB4514475A5995C61B5E92C504AF63D449CD41F4F2F6F9EAF60B8017A0 Key = B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8 Nonce = F3F4F5F6F7F8F9FAFBFCFDFE AD = 0708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A In = 030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223242526 -Out = 2070BEDB155997BFA6DE55F27CD45AA8223B16312965A814D347CC7EF551DA09E7BCB1806D9418BB37C64AB851272D0D193F32BCB7B081A149C84723 +Out = 2070BEDB155997DD86DE55F27CD45AA8223B16313C31BCC0D347CC7EF551DA4A27BCB1806D9418BB37C64AFB91272D0D193F32BCB7B081A150505FBB Key = C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8 Nonce = F4F5F6F7F8F9FAFBFCFDFEFF AD = 08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F3031323334353637 In = 0405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233 -Out = 70E46BCD56C24DCBFC2A280C3C2A26A9AAB9A097AA3EB37352EE799774C85F517E809AF6FEC0D5524972730C09B52555889E94B2D0C6CCF2079291B49B8E86F9D4C7D6EE081E165A +Out = 70E46BCD56C24D0FBC2A280C3C2A26A9AAB9A0979B8B81C952EE799774C85F953E809AF6FEC0D5524972730C380017EF889E94B2D0C6CC75879291B49B8E86F9D4C7D6EE29383576 diff --git a/src/tests/data/ocb_wide_long.vec b/src/tests/data/ocb_wide_long.vec index 7c17f00bf..9fdb0b8a2 100644 --- a/src/tests/data/ocb_wide_long.vec +++ b/src/tests/data/ocb_wide_long.vec @@ -1,12 +1,15 @@ -Blocklen = 128 +[Toy128] Output = 0D099181BE37171BF94582877D6D4693 -Blocklen = 192 -Output = C6B3449A7A5C174253720B65198779C0E1758794C023F567 +[Toy192] +Output = BFC53A29EABF2774369F3611DED760AE33338B607E5A8E40 -Blocklen = 256 -Output = 87F321F24B0554565BEB6C994AD04F8F95F1A808E67EAFBD60E0E86152AFB37C +[Toy256] +Output = 623C27E137975E25BEF2F8441CB5BDEAE8E0F1E158515193900BBD20D1A7AFF7 -Blocklen = 512 +[Toy512] Output = 6748655A0A83543D8AA6287AE9FFC37C9A433332DDFD4E8B42F94D741944D440 + +[SHACAL2] +Output = DC4AA181A65BD11EAA23D0881A20740B7DBA53C9DE2474DB3C3EF04770DFAD99 diff --git a/src/tests/test_ocb.cpp b/src/tests/test_ocb.cpp index aa9343e7f..b9af9ba9c 100644 --- a/src/tests/test_ocb.cpp +++ b/src/tests/test_ocb.cpp @@ -149,19 +149,38 @@ class OCB_Wide_Long_KAT_Tests : public Text_Based_Test { public: OCB_Wide_Long_KAT_Tests() - : Text_Based_Test("ocb_wide_long.vec", "Blocklen,Output") {} + : Text_Based_Test("ocb_wide_long.vec", "Output") {} - Test::Result run_one_test(const std::string&, const VarMap& vars) override + Test::Result run_one_test(const std::string& algo, const VarMap& vars) override { Test::Result result("OCB wide block long test"); - const size_t bs = get_req_sz(vars, "Blocklen") / 8; const std::vector expected = get_req_bin(vars, "Output"); - if(bs != 16 && bs != 24 && bs != 32 && bs != 64) - throw Test_Error("Unsupported Blocklen in OCB wide block test"); + std::unique_ptr cipher; + size_t bs = 0; - Botan::OCB_Encryption enc(new OCB_Wide_Test_Block_Cipher(bs), std::min(bs, 32)); + if(algo == "SHACAL2") + { + cipher = Botan::BlockCipher::create_or_throw("SHACAL2"); + bs = 32; + } + else + { + if(algo == "Toy128") + bs = 16; + else if(algo == "Toy192") + bs = 24; + else if(algo == "Toy256") + bs = 32; + else if(algo == "Toy512") + bs = 64; + else + throw Test_Error("Unknown cipher for OCB wide block long test"); + cipher.reset(new OCB_Wide_Test_Block_Cipher(bs)); + } + + Botan::OCB_Encryption enc(cipher.release(), std::min(bs, 32)); /* Y, string of length min(B, 256) bits @@ -218,7 +237,7 @@ class OCB_Wide_Long_KAT_Tests : public Text_Based_Test } private: - void ocb_encrypt(Test::Result& result, + void ocb_encrypt(Test::Result& /*result*/, std::vector& output_to, Botan::OCB_Encryption& enc, const std::vector& nonce, diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp index 8f8aeec82..d7917bcc6 100644 --- a/src/tests/tests.cpp +++ b/src/tests/tests.cpp @@ -1032,7 +1032,10 @@ std::vector Text_Based_Test::run() if(result.tests_failed()) { - result.test_note("Test #" + std::to_string(test_cnt) + " failed"); + if(header.empty()) + result.test_note("Test #" + std::to_string(test_cnt) + " failed"); + else + result.test_note("Test #" + std::to_string(test_cnt) + " " + header + " failed"); } results.push_back(result); } -- cgit v1.2.3