From 8466b3fc7f259a110f0f300781237f355e24a424 Mon Sep 17 00:00:00 2001 From: "agl@chromium.org" Date: Fri, 1 Jul 2011 20:17:18 +0000 Subject: crypto: convert OpenPGP code to NSS BUG=none TEST=crypto_unittests Review URL: http://codereview.chromium.org/7273080 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@91350 0039d316-1c4b-4281-b951-d872f2087c98 --- crypto/crypto.gyp | 11 +- crypto/openpgp_symmetric_encryption_nss.cc | 790 +++++++++++++++++++++ .../openpgp_symmetric_encryption_nss_unittest.cc | 114 +++ crypto/openpgp_symmetric_encryption_openssl.cc | 707 ------------------ .../openpgp_symmetric_encryption_test_openssl.cc | 114 --- 5 files changed, 908 insertions(+), 828 deletions(-) create mode 100644 crypto/openpgp_symmetric_encryption_nss.cc create mode 100644 crypto/openpgp_symmetric_encryption_nss_unittest.cc delete mode 100644 crypto/openpgp_symmetric_encryption_openssl.cc delete mode 100644 crypto/openpgp_symmetric_encryption_test_openssl.cc (limited to 'crypto') diff --git a/crypto/crypto.gyp b/crypto/crypto.gyp index e3d6c84..9340ace 100644 --- a/crypto/crypto.gyp +++ b/crypto/crypto.gyp @@ -92,6 +92,7 @@ 'hmac_nss.cc', 'nss_util.cc', 'nss_util.h', + 'openpgp_symmetric_encryption_nss.cc', 'rsa_private_key_nss.cc', 'secure_hash_default.cc', 'signature_creator_nss.cc', @@ -106,7 +107,6 @@ 'sources!': [ 'encryptor_openssl.cc', 'hmac_openssl.cc', - 'openpgp_symmetric_encryption_openssl.cc', 'openssl_util.cc', 'openssl_util.h', 'rsa_private_key_openssl.cc', @@ -141,7 +141,7 @@ 'nss_util.cc', 'nss_util.h', 'nss_util_internal.h', - 'openpgp_symmetric_encryption_openssl.cc', + 'openpgp_symmetric_encryption_nss.cc', 'openpgp_symmetric_encryption.h', 'openssl_util.cc', 'openssl_util.h', @@ -196,7 +196,7 @@ 'signature_creator_unittest.cc', 'signature_verifier_unittest.cc', 'symmetric_key_unittest.cc', - 'openpgp_symmetric_encryption_test_openssl.cc', + 'openpgp_symmetric_encryption_nss_unittest.cc', ], 'dependencies': [ 'crypto', @@ -230,12 +230,9 @@ }], [ 'use_openssl==1', { 'sources!': [ + 'openpgp_symmetric_encryption_test_nss.cc', 'rsa_private_key_nss_unittest.cc', ], - }, { - 'sources!': [ - 'openpgp_symmetric_encryption_test_openssl.cc', - ], }], ], }, diff --git a/crypto/openpgp_symmetric_encryption_nss.cc b/crypto/openpgp_symmetric_encryption_nss.cc new file mode 100644 index 0000000..62223f9 --- /dev/null +++ b/crypto/openpgp_symmetric_encryption_nss.cc @@ -0,0 +1,790 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "crypto/openpgp_symmetric_encryption.h" + +#include + +#include +#include + +#include + +#include "base/logging.h" +#include "base/rand_util.h" +#include "crypto/scoped_nss_types.h" + +namespace crypto { + +namespace { + +// Reader wraps a StringPiece and provides methods to read several datatypes +// while advancing the StringPiece. +class Reader { + public: + Reader(base::StringPiece input) + : data_(input) { + } + + bool U8(uint8* out) { + if (data_.size() < 1) + return false; + *out = static_cast(data_[0]); + data_.remove_prefix(1); + return true; + } + + bool U32(uint32* out) { + if (data_.size() < 4) + return false; + *out = static_cast(data_[0]) << 24 | + static_cast(data_[1]) << 16 | + static_cast(data_[2]) << 8 | + static_cast(data_[3]); + data_.remove_prefix(4); + return true; + } + + // Prefix sets |*out| to the first |n| bytes of the StringPiece and advances + // the StringPiece by |n|. + bool Prefix(size_t n, base::StringPiece *out) { + if (data_.size() < n) + return false; + *out = base::StringPiece(data_.data(), n); + data_.remove_prefix(n); + return true; + } + + // Remainder returns the remainer of the StringPiece and advances it to the + // end. + base::StringPiece Remainder() { + base::StringPiece ret = data_; + data_ = base::StringPiece(); + return ret; + } + + typedef base::StringPiece Position; + + Position tell() const { + return data_; + } + + void Seek(Position p) { + data_ = p; + } + + bool Skip(size_t n) { + if (data_.size() < n) + return false; + data_.remove_prefix(n); + return true; + } + + bool empty() const { + return data_.empty(); + } + + size_t size() const { + return data_.size(); + } + + private: + base::StringPiece data_; +}; + +// SaltedIteratedS2K implements the salted and iterated string-to-key +// convertion. See RFC 4880, section 3.7.1.3. +void SaltedIteratedS2K(unsigned cipher_key_length, + HASH_HashType hash_function, + base::StringPiece passphrase, + base::StringPiece salt, + unsigned count, + uint8 *out_key) { + const std::string combined = salt.as_string() + passphrase.as_string(); + const size_t combined_len = combined.size(); + + unsigned done = 0; + uint8 zero[1] = {0}; + + HASHContext* hash_context = HASH_Create(hash_function); + + for (unsigned i = 0; done < cipher_key_length; i++) { + HASH_Begin(hash_context); + + for (unsigned j = 0; j < i; j++) + HASH_Update(hash_context, zero, sizeof(zero)); + + unsigned written = 0; + while (written < count) { + if (written + combined_len > count) { + unsigned todo = count - written; + HASH_Update(hash_context, + reinterpret_cast(combined.data()), + todo); + written = count; + } else { + HASH_Update(hash_context, + reinterpret_cast(combined.data()), + combined_len); + written += combined_len; + } + } + + unsigned num_hash_bytes; + uint8 digest[HASH_LENGTH_MAX]; + HASH_End(hash_context, digest, &num_hash_bytes, sizeof(digest)); + + unsigned todo = cipher_key_length - done; + if (todo > num_hash_bytes) + todo = num_hash_bytes; + memcpy(out_key + done, digest, todo); + done += todo; + } + + HASH_Destroy(hash_context); +} + +// CreateAESContext sets up |out_key| to be an AES context, with the given key, +// in ECB mode and with no IV. +bool CreateAESContext(const uint8* key, unsigned key_len, + ScopedPK11Context* out_decryption_context) { + ScopedPK11Slot slot(PK11_GetBestSlot(CKM_AES_ECB, NULL)); + if (!slot.get()) + return false; + SECItem key_item; + key_item.type = siBuffer; + key_item.data = const_cast(key); + key_item.len = key_len; + ScopedPK11SymKey pk11_key(PK11_ImportSymKey( + slot.get(), CKM_AES_ECB, PK11_OriginUnwrap, CKA_ENCRYPT, &key_item, + NULL)); + if (!pk11_key.get()) + return false; + ScopedSECItem iv_param(PK11_ParamFromIV(CKM_AES_ECB, NULL)); + out_decryption_context->reset( + PK11_CreateContextBySymKey(CKM_AES_ECB, CKA_ENCRYPT, pk11_key.get(), + iv_param.get())); + return out_decryption_context->get() != NULL; +} + + +// These constants are the tag numbers for the various packet types that we +// use. +static const unsigned kSymmetricKeyEncryptedTag = 3; +static const unsigned kSymmetricallyEncryptedTag = 18; +static const unsigned kCompressedTag = 8; +static const unsigned kLiteralDataTag = 11; + +class Decrypter { + public: + ~Decrypter() { + for (std::vector::iterator + i = arena_.begin(); i != arena_.end(); i++) { + free(*i); + } + arena_.clear(); + } + + OpenPGPSymmetricEncrytion::Result Decrypt(base::StringPiece in, + base::StringPiece passphrase, + base::StringPiece *out_contents) { + Reader reader(in); + unsigned tag; + base::StringPiece contents; + ScopedPK11Context decryption_context; + + if (!ParsePacket(&reader, &tag, &contents)) + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + if (tag != kSymmetricKeyEncryptedTag) + return OpenPGPSymmetricEncrytion::NOT_SYMMETRICALLY_ENCRYPTED; + Reader inner(contents); + OpenPGPSymmetricEncrytion::Result result = + ParseSymmetricKeyEncrypted(&inner, passphrase, &decryption_context); + if (result != OpenPGPSymmetricEncrytion::OK) + return result; + + if (!ParsePacket(&reader, &tag, &contents)) + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + if (tag != kSymmetricallyEncryptedTag) + return OpenPGPSymmetricEncrytion::NOT_SYMMETRICALLY_ENCRYPTED; + if (!reader.empty()) + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + inner = Reader(contents); + if (!ParseSymmetricallyEncrypted(&inner, &decryption_context, &contents)) + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + + reader = Reader(contents); + if (!ParsePacket(&reader, &tag, &contents)) + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + if (tag == kCompressedTag) + return OpenPGPSymmetricEncrytion::COMPRESSED; + if (tag != kLiteralDataTag) + return OpenPGPSymmetricEncrytion::NOT_SYMMETRICALLY_ENCRYPTED; + inner = Reader(contents); + if (!ParseLiteralData(&inner, out_contents)) + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + + return OpenPGPSymmetricEncrytion::OK; + } + + private: + // ParsePacket parses an OpenPGP packet from reader. See RFC 4880, section + // 4.2.2. + bool ParsePacket(Reader *reader, + unsigned *out_tag, + base::StringPiece *out_contents) { + uint8 header; + if (!reader->U8(&header)) + return false; + if ((header & 0x80) == 0) { + // Tag byte must have MSB set. + return false; + } + + if ((header & 0x40) == 0) { + // Old format packet. + *out_tag = (header & 0x3f) >> 2; + + uint8 length_type = header & 3; + if (length_type == 3) { + *out_contents = reader->Remainder(); + return true; + } + + const unsigned length_bytes = 1 << length_type; + size_t length = 0; + for (unsigned i = 0; i < length_bytes; i++) { + uint8 length_byte; + if (!reader->U8(&length_byte)) + return false; + length <<= 8; + length |= length_byte; + } + + return reader->Prefix(length, out_contents); + } + + // New format packet. + *out_tag = header & 0x3f; + size_t length; + bool is_partial; + if (!ParseLength(reader, &length, &is_partial)) + return false; + if (is_partial) + return ParseStreamContents(reader, length, out_contents); + return reader->Prefix(length, out_contents); + } + + // ParseStreamContents parses all the chunks of a partial length stream from + // reader. See http://tools.ietf.org/html/rfc4880#section-4.2.2.4 + bool ParseStreamContents(Reader *reader, + size_t length, + base::StringPiece *out_contents) { + const Reader::Position beginning_of_stream = reader->tell(); + const size_t first_chunk_length = length; + + // First we parse the stream to find its length. + if (!reader->Skip(length)) + return false; + + for (;;) { + size_t chunk_length; + bool is_partial; + + if (!ParseLength(reader, &chunk_length, &is_partial)) + return false; + if (length + chunk_length < length) + return false; + length += chunk_length; + if (!reader->Skip(chunk_length)) + return false; + if (!is_partial) + break; + } + + // Now we have the length of the whole stream in |length|. + char* buf = reinterpret_cast(malloc(length)); + arena_.push_back(buf); + size_t j = 0; + reader->Seek(beginning_of_stream); + + base::StringPiece first_chunk; + if (!reader->Prefix(first_chunk_length, &first_chunk)) + return false; + memcpy(buf + j, first_chunk.data(), first_chunk_length); + j += first_chunk_length; + + // Now we parse the stream again, this time copying into |buf| + for (;;) { + size_t chunk_length; + bool is_partial; + + if (!ParseLength(reader, &chunk_length, &is_partial)) + return false; + base::StringPiece chunk; + if (!reader->Prefix(chunk_length, &chunk)) + return false; + memcpy(buf + j, chunk.data(), chunk_length); + j += chunk_length; + if (!is_partial) + break; + } + + *out_contents = base::StringPiece(buf, length); + return true; + } + + // ParseLength parses an OpenPGP length from reader. See RFC 4880, section + // 4.2.2. + bool ParseLength(Reader *reader, size_t *out_length, bool *out_is_prefix) { + uint8 length_spec; + if (!reader->U8(&length_spec)) + return false; + + *out_is_prefix = false; + if (length_spec < 192) { + *out_length = length_spec; + return true; + } else if (length_spec < 224) { + uint8 next_byte; + if (!reader->U8(&next_byte)) + return false; + + *out_length = (length_spec - 192) << 8; + *out_length += next_byte; + return true; + } else if (length_spec < 255) { + *out_length = 1u << (length_spec & 0x1f); + *out_is_prefix = true; + return true; + } else { + uint32 length32; + if (!reader->U32(&length32)) + return false; + *out_length = length32; + return true; + } + } + + // ParseSymmetricKeyEncrypted parses a passphrase protected session key. See + // RFC 4880, section 5.3. + OpenPGPSymmetricEncrytion::Result ParseSymmetricKeyEncrypted( + Reader *reader, + base::StringPiece passphrase, + ScopedPK11Context *decryption_context) { + uint8 version, cipher, s2k_type, hash_func_id; + if (!reader->U8(&version) || version != 4) + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + + if (!reader->U8(&cipher) || + !reader->U8(&s2k_type) || + !reader->U8(&hash_func_id)) { + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + } + + uint8 cipher_key_length = OpenPGPCipherIdToKeyLength(cipher); + if (cipher_key_length == 0) + return OpenPGPSymmetricEncrytion::UNKNOWN_CIPHER; + + HASH_HashType hash_function; + switch (hash_func_id) { + case 2: // SHA-1 + hash_function = HASH_AlgSHA1; + break; + case 8: // SHA-256 + hash_function = HASH_AlgSHA256; + break; + default: + return OpenPGPSymmetricEncrytion::UNKNOWN_HASH; + } + + // This chunk of code parses the S2K specifier. See RFC 4880, section 3.7.1. + base::StringPiece salt; + uint8 key[32]; + uint8 count_spec; + switch (s2k_type) { + case 1: + if (!reader->Prefix(8, &salt)) + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + // Fall through. + case 0: + SaltedIteratedS2K(cipher_key_length, hash_function, passphrase, salt, + passphrase.size() + salt.size(), key); + break; + case 3: + if (!reader->Prefix(8, &salt) || + !reader->U8(&count_spec)) { + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + } + SaltedIteratedS2K( + cipher_key_length, hash_function, passphrase, salt, + static_cast( + 16 + (count_spec&15)) << ((count_spec >> 4) + 6), key); + break; + default: + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + } + + if (!CreateAESContext(key, cipher_key_length, decryption_context)) + return OpenPGPSymmetricEncrytion::INTERNAL_ERROR; + + if (reader->empty()) { + // The resulting key is used directly. + return OpenPGPSymmetricEncrytion::OK; + } + + // The S2K derived key encrypts another key that follows: + base::StringPiece encrypted_key = reader->Remainder(); + if (encrypted_key.size() < 1) + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + + uint8* plaintext_key = reinterpret_cast( + malloc(encrypted_key.size())); + arena_.push_back(plaintext_key); + + CFBDecrypt(encrypted_key, decryption_context, plaintext_key); + + cipher_key_length = OpenPGPCipherIdToKeyLength(plaintext_key[0]); + if (cipher_key_length == 0) + return OpenPGPSymmetricEncrytion::UNKNOWN_CIPHER; + if (encrypted_key.size() != 1u + cipher_key_length) + return OpenPGPSymmetricEncrytion::PARSE_ERROR; + if (!CreateAESContext(plaintext_key + 1, cipher_key_length, + decryption_context)) { + return OpenPGPSymmetricEncrytion::INTERNAL_ERROR; + } + return OpenPGPSymmetricEncrytion::OK; + } + + // CFBDecrypt decrypts the cipher-feedback encrypted data in |in| to |out| + // using |decryption_context| and assumes an IV of all zeros. + void CFBDecrypt(base::StringPiece in, ScopedPK11Context* decryption_context, + uint8* out) { + // We need this for PK11_CipherOp to write to, but we never check it as we + // work in ECB mode, one block at a time. + int out_len; + + uint8 mask[AES_BLOCK_SIZE]; + memset(mask, 0, sizeof(mask)); + + unsigned used = AES_BLOCK_SIZE; + + for (size_t i = 0; i < in.size(); i++) { + if (used == AES_BLOCK_SIZE) { + PK11_CipherOp(decryption_context->get(), mask, &out_len, sizeof(mask), + mask, AES_BLOCK_SIZE); + used = 0; + } + + uint8 t = in[i]; + out[i] = t ^ mask[used]; + mask[used] = t; + used++; + } + } + + // OpenPGPCipherIdToKeyLength converts an OpenPGP cipher id (see RFC 4880, + // section 9.2) to the key length of that cipher. It returns 0 on error. + unsigned OpenPGPCipherIdToKeyLength(uint8 cipher) { + switch (cipher) { + case 7: // AES-128 + return 16; + case 8: // AES-192 + return 24; + case 9: // AES-256 + return 32; + default: + return 0; + } + } + + // ParseSymmetricallyEncrypted parses a Symmetrically Encrypted packet. See + // RFC 4880, sections 5.7 and 5.13. + bool ParseSymmetricallyEncrypted(Reader *reader, + ScopedPK11Context *decryption_context, + base::StringPiece *out_plaintext) { + // We need this for PK11_CipherOp to write to, but we never check it as we + // work in ECB mode, one block at a time. + int out_len; + + uint8 version; + if (!reader->U8(&version) || version != 1) + return false; + + base::StringPiece prefix_sp; + if (!reader->Prefix(AES_BLOCK_SIZE + 2, &prefix_sp)) + return false; + uint8 prefix[AES_BLOCK_SIZE + 2]; + memcpy(prefix, prefix_sp.data(), sizeof(prefix)); + + uint8 prefix_copy[AES_BLOCK_SIZE + 2]; + uint8 fre[AES_BLOCK_SIZE]; + + memset(prefix_copy, 0, AES_BLOCK_SIZE); + PK11_CipherOp(decryption_context->get(), fre, &out_len, sizeof(fre), + prefix_copy, AES_BLOCK_SIZE); + for (unsigned i = 0; i < AES_BLOCK_SIZE; i++) + prefix_copy[i] = fre[i] ^ prefix[i]; + PK11_CipherOp(decryption_context->get(), fre, &out_len, sizeof(fre), prefix, + AES_BLOCK_SIZE); + prefix_copy[AES_BLOCK_SIZE] = prefix[AES_BLOCK_SIZE] ^ fre[0]; + prefix_copy[AES_BLOCK_SIZE + 1] = prefix[AES_BLOCK_SIZE + 1] ^ fre[1]; + + if (prefix_copy[AES_BLOCK_SIZE - 2] != prefix_copy[AES_BLOCK_SIZE] || + prefix_copy[AES_BLOCK_SIZE - 1] != prefix_copy[AES_BLOCK_SIZE + 1]) { + return false; + } + + fre[0] = prefix[AES_BLOCK_SIZE]; + fre[1] = prefix[AES_BLOCK_SIZE + 1]; + + unsigned out_used = 2; + + const size_t plaintext_size = reader->size(); + if (plaintext_size < SHA1_LENGTH + 2) { + // Too small to contain an MDC trailer. + return false; + } + + uint8* plaintext = reinterpret_cast(malloc(plaintext_size)); + arena_.push_back(plaintext); + + for (size_t i = 0; i < plaintext_size; i++) { + uint8 b; + if (!reader->U8(&b)) + return false; + if (out_used == AES_BLOCK_SIZE) { + PK11_CipherOp(decryption_context->get(), fre, &out_len, sizeof(fre), + fre, AES_BLOCK_SIZE); + out_used = 0; + } + + plaintext[i] = b ^ fre[out_used]; + fre[out_used++] = b; + } + + // The plaintext should be followed by a Modification Detection Code + // packet. This packet is specified such that the header is always + // serialized as exactly these two bytes: + if (plaintext[plaintext_size - SHA1_LENGTH - 2] != 0xd3 || + plaintext[plaintext_size - SHA1_LENGTH - 1] != 0x14) { + return false; + } + + HASHContext* hash_context = HASH_Create(HASH_AlgSHA1); + HASH_Begin(hash_context); + HASH_Update(hash_context, prefix_copy, sizeof(prefix_copy)); + HASH_Update(hash_context, plaintext, plaintext_size - SHA1_LENGTH); + uint8 digest[SHA1_LENGTH]; + unsigned num_hash_bytes; + HASH_End(hash_context, digest, &num_hash_bytes, sizeof(digest)); + HASH_Destroy(hash_context); + + if (memcmp(digest, &plaintext[plaintext_size - SHA1_LENGTH], + SHA1_LENGTH) != 0) { + return false; + } + + *out_plaintext = base::StringPiece(reinterpret_cast(plaintext), + plaintext_size - SHA1_LENGTH); + return true; + } + + // ParseLiteralData parses a Literal Data packet. See RFC 4880, section 5.9. + bool ParseLiteralData(Reader *reader, base::StringPiece *out_data) { + uint8 is_binary, filename_len; + if (!reader->U8(&is_binary) || + !reader->U8(&filename_len) || + !reader->Skip(filename_len) || + !reader->Skip(sizeof(uint32) /* mtime */)) { + return false; + } + + *out_data = reader->Remainder(); + return true; + } + + // arena_ contains malloced pointers that are used as temporary space during + // the decryption. + std::vector arena_; +}; + +class Encrypter { + public: + // ByteString is used throughout in order to avoid signedness issues with a + // std::string. + typedef std::basic_string ByteString; + + static ByteString Encrypt(base::StringPiece plaintext, + base::StringPiece passphrase) { + ByteString key; + ByteString ske = SerializeSymmetricKeyEncrypted(passphrase, &key); + + ByteString literal_data = SerializeLiteralData(plaintext); + ByteString se = SerializeSymmetricallyEncrypted(literal_data, key); + return ske + se; + } + + private: + // MakePacket returns an OpenPGP packet tagged as type |tag|. It always uses + // new-format headers. See RFC 4880, section 4.2. + static ByteString MakePacket(unsigned tag, const ByteString& contents) { + ByteString header; + header.push_back(0x80 | 0x40 | tag); + + if (contents.size() < 192) { + header.push_back(contents.size()); + } else if (contents.size() < 8384) { + size_t length = contents.size(); + length -= 192; + header.push_back(192 + (length >> 8)); + header.push_back(length & 0xff); + } else { + size_t length = contents.size(); + header.push_back(255); + header.push_back(length >> 24); + header.push_back(length >> 16); + header.push_back(length >> 8); + header.push_back(length); + } + + return header + contents; + } + + // SerializeLiteralData returns a Literal Data packet containing |contents| + // as binary data with no filename nor mtime specified. See RFC 4880, section + // 5.9. + static ByteString SerializeLiteralData(base::StringPiece contents) { + ByteString literal_data; + literal_data.push_back(0x74); // text mode + literal_data.push_back(0x00); // no filename + literal_data.push_back(0x00); // zero mtime + literal_data.push_back(0x00); + literal_data.push_back(0x00); + literal_data.push_back(0x00); + literal_data += ByteString(reinterpret_cast(contents.data()), + contents.size()); + return MakePacket(kLiteralDataTag, literal_data); + } + + // SerializeSymmetricKeyEncrypted generates a random AES-128 key from + // |passphrase|, sets |out_key| to it and returns a Symmetric Key Encrypted + // packet. See RFC 4880, section 5.3. + static ByteString SerializeSymmetricKeyEncrypted(base::StringPiece passphrase, + ByteString *out_key) { + ByteString ske; + ske.push_back(4); // version 4 + ske.push_back(7); // AES-128 + ske.push_back(3); // iterated and salted S2K + ske.push_back(2); // SHA-1 + + uint64 salt64 = base::RandUint64(); + ByteString salt(sizeof(salt64), 0); + + // It's a random value, so endianness doesn't matter. + ske += ByteString(reinterpret_cast(&salt64), sizeof(salt64)); + ske.push_back(96); // iteration count of 65536 + + uint8 key[16]; + SaltedIteratedS2K( + sizeof(key), HASH_AlgSHA1, passphrase, + base::StringPiece(reinterpret_cast(&salt64), sizeof(salt64)), + 65536, key); + *out_key = ByteString(key, sizeof(key)); + return MakePacket(kSymmetricKeyEncryptedTag, ske); + } + + // SerializeSymmetricallyEncrypted encrypts |plaintext| with |key| and + // returns a Symmetrically Encrypted packet containing the ciphertext. See + // RFC 4880, section 5.7. + static ByteString SerializeSymmetricallyEncrypted(ByteString plaintext, + const ByteString& key) { + // We need this for PK11_CipherOp to write to, but we never check it as we + // work in ECB mode, one block at a time. + int out_len; + + ByteString packet; + packet.push_back(1); // version 1 + static const unsigned kBlockSize = 16; // AES block size + + uint8 prefix[kBlockSize + 2], fre[kBlockSize], iv[kBlockSize]; + base::RandBytes(iv, kBlockSize); + memset(fre, 0, sizeof(fre)); + + ScopedPK11Context aes_context; + CHECK(CreateAESContext(key.data(), key.size(), &aes_context)); + + PK11_CipherOp(aes_context.get(), fre, &out_len, sizeof(fre), fre, + AES_BLOCK_SIZE); + for (unsigned i = 0; i < 16; i++) + prefix[i] = iv[i] ^ fre[i]; + PK11_CipherOp(aes_context.get(), fre, &out_len, sizeof(fre), prefix, + AES_BLOCK_SIZE); + prefix[kBlockSize] = iv[kBlockSize - 2] ^ fre[0]; + prefix[kBlockSize + 1] = iv[kBlockSize - 1] ^ fre[1]; + + packet += ByteString(prefix, sizeof(prefix)); + + ByteString plaintext_copy = plaintext; + plaintext_copy.push_back(0xd3); // MDC packet + plaintext_copy.push_back(20); // packet length (20 bytes) + + HASHContext* hash_context = HASH_Create(HASH_AlgSHA1); + HASH_Begin(hash_context); + HASH_Update(hash_context, iv, sizeof(iv)); + HASH_Update(hash_context, iv + kBlockSize - 2, 2); + HASH_Update(hash_context, plaintext_copy.data(), plaintext_copy.size()); + uint8 digest[SHA1_LENGTH]; + unsigned num_hash_bytes; + HASH_End(hash_context, digest, &num_hash_bytes, sizeof(digest)); + HASH_Destroy(hash_context); + + plaintext_copy += ByteString(digest, sizeof(digest)); + + fre[0] = prefix[kBlockSize]; + fre[1] = prefix[kBlockSize+1]; + unsigned out_used = 2; + + for (size_t i = 0; i < plaintext_copy.size(); i++) { + if (out_used == kBlockSize) { + PK11_CipherOp(aes_context.get(), fre, &out_len, sizeof(fre), fre, + AES_BLOCK_SIZE); + out_used = 0; + } + + uint8 c = plaintext_copy[i] ^ fre[out_used]; + fre[out_used++] = c; + packet.push_back(c); + } + + return MakePacket(kSymmetricallyEncryptedTag, packet); + } +}; + +} // anonymous namespace + +// static +OpenPGPSymmetricEncrytion::Result OpenPGPSymmetricEncrytion::Decrypt( + base::StringPiece encrypted, + base::StringPiece passphrase, + std::string *out) { + Decrypter decrypter; + + base::StringPiece result; + Result reader = decrypter.Decrypt(encrypted, passphrase, &result); + if (reader == OK) + *out = result.as_string(); + return reader; +} + +// static +std::string OpenPGPSymmetricEncrytion::Encrypt( + base::StringPiece plaintext, + base::StringPiece passphrase) { + Encrypter::ByteString b = + Encrypter::Encrypt(plaintext, passphrase); + return std::string(reinterpret_cast(b.data()), b.size()); +} + +} // namespace crypto diff --git a/crypto/openpgp_symmetric_encryption_nss_unittest.cc b/crypto/openpgp_symmetric_encryption_nss_unittest.cc new file mode 100644 index 0000000..6185719 --- /dev/null +++ b/crypto/openpgp_symmetric_encryption_nss_unittest.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "crypto/openpgp_symmetric_encryption.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace crypto { + +// These test vectors were created by hand using either GPG or the Go OpenPGP +// library. + +// AES-128, GPG +static const uint8 kTestMessage1[] = { + 0x8c, 0x0d, 0x04, 0x07, 0x03, 0x02, 0x69, 0x24, 0xaf, 0xbf, 0x0b, 0x31, 0x98, + 0x6d, 0x60, 0xd2, 0x3d, 0x01, 0xc4, 0x29, 0xab, 0xec, 0x1b, 0xdf, 0xfa, 0x90, + 0x86, 0x92, 0x94, 0xc7, 0xa5, 0xe7, 0xd8, 0x80, 0x0a, 0x55, 0x3e, 0xbd, 0x10, + 0xef, 0x40, 0xfe, 0xb7, 0x39, 0x83, 0x4e, 0x5e, 0x77, 0x9d, 0x57, 0x94, 0xb6, + 0xe4, 0x59, 0xa7, 0x32, 0x76, 0x22, 0x48, 0xed, 0x37, 0xe8, 0x6f, 0xf9, 0x80, + 0x04, 0xa1, 0xe4, 0xbf, 0x40, 0xa6, 0x9b, 0xd1, 0x3e, 0xba, 0xaa, 0x52, 0xd0, +}; + +// AES-256, GPG +static const uint8 kTestMessage2[] = { + 0x8c, 0x0d, 0x04, 0x09, 0x03, 0x02, 0xd6, 0xb0, 0x34, 0xa0, 0xb8, 0x6c, 0x15, + 0xb8, 0x60, 0xd2, 0x3d, 0x01, 0x97, 0xe4, 0x46, 0x56, 0xc5, 0xc9, 0xc7, 0x81, + 0xd1, 0x09, 0xf3, 0xa0, 0x5d, 0x3b, 0xa7, 0xe3, 0x68, 0xb4, 0x19, 0xd2, 0x76, + 0x83, 0x38, 0x13, 0x98, 0xb8, 0xaf, 0x54, 0x51, 0x52, 0xbb, 0xc5, 0x7d, 0x8a, + 0x70, 0x66, 0x40, 0x0b, 0xb5, 0x92, 0xc3, 0xd3, 0x51, 0x63, 0x5d, 0x99, 0x9b, + 0x96, 0x82, 0xe1, 0xfe, 0xac, 0xa8, 0xa5, 0x87, 0x8b, 0x3f, 0xd1, 0x90, 0x70, +}; + +// AES-128, Go +static const uint8 kTestMessage3[] = { + 0xc3, 0x1e, 0x04, 0x07, 0x03, 0x02, 0x74, 0x1e, 0x2d, 0x7d, 0x2e, 0xdf, 0x20, + 0xdb, 0x60, 0xb1, 0x22, 0xca, 0x39, 0x74, 0x2f, 0xe8, 0x2f, 0x09, 0xf8, 0xa4, + 0x13, 0x76, 0x14, 0x65, 0x6e, 0xb9, 0xd2, 0xe0, 0x01, 0xe4, 0x40, 0x83, 0x90, + 0x3c, 0x76, 0xac, 0x23, 0x3e, 0xd2, 0xb0, 0xb2, 0x1c, 0x9d, 0x24, 0xcb, 0x7b, + 0xe1, 0x0c, 0x6b, 0xe0, 0x86, 0xe0, 0x22, 0xe1, 0xf2, 0xcb, 0xe0, 0xf3, 0xe2, + 0xbf, 0xdd, 0x89, 0xda, 0xe0, 0x83, 0xe3, 0xd9, 0x34, 0xf9, 0xaa, 0x17, 0x96, + 0x6a, 0x28, 0xe0, 0xca, 0xe2, 0x78, 0xca, 0x0e, 0x7d, 0xe0, 0xeb, 0xe4, 0xca, + 0xe6, 0xf0, 0x90, 0x4f, 0x22, 0x91, 0xf9, 0xb2, 0xbb, 0x19, 0x0b, 0x45, 0xc0, + 0x16, 0x9e, 0xe2, 0xdb, 0x30, 0xad, 0xbb, 0xe1, 0xb8, 0x12, 0x00, +}; + +// AES-128, salted S2K, GPG +static const uint8 kTestMessage4[] = { + 0x8c, 0x0c, 0x04, 0x07, 0x01, 0x02, 0xf8, 0x42, 0x78, 0x07, 0x04, 0xaa, 0x54, + 0xcc, 0xd2, 0x3d, 0x01, 0xcf, 0xb7, 0x30, 0xe1, 0xed, 0xb2, 0x53, 0x6e, 0x4a, + 0xbc, 0x49, 0x27, 0x45, 0xde, 0x1d, 0x5b, 0xe2, 0x17, 0x43, 0x39, 0x79, 0xdc, + 0xa5, 0xb7, 0x1a, 0x1b, 0xb7, 0x29, 0x9c, 0xb5, 0x69, 0x2f, 0x42, 0xc5, 0xe5, + 0x0c, 0x78, 0x57, 0x16, 0xa6, 0x46, 0x22, 0x18, 0x0c, 0xa2, 0xb3, 0x8c, 0xee, + 0xa1, 0xde, 0x38, 0xf1, 0xca, 0x73, 0xd3, 0xd6, 0xa3, 0x61, 0x47, 0xe2, +}; + +TEST(OpenPGPSymmetricEncrytionTest, AES128GPG) { + base::StringPiece encrypted(reinterpret_cast(kTestMessage1), + sizeof(kTestMessage1)); + std::string out; + + OpenPGPSymmetricEncrytion::Result r = + OpenPGPSymmetricEncrytion::Decrypt(encrypted, "testing", &out); + EXPECT_EQ(OpenPGPSymmetricEncrytion::OK, r); + EXPECT_EQ("Hello world\n", out); +} + +TEST(OpenPGPSymmetricEncrytionTest, AES256GPG) { + base::StringPiece encrypted(reinterpret_cast(kTestMessage2), + sizeof(kTestMessage2)); + std::string out; + + OpenPGPSymmetricEncrytion::Result r = + OpenPGPSymmetricEncrytion::Decrypt(encrypted, "testing", &out); + EXPECT_EQ(OpenPGPSymmetricEncrytion::OK, r); + EXPECT_EQ("Hello world\n", out); +} + +TEST(OpenPGPSymmetricEncrytionTest, AES128Go) { + base::StringPiece encrypted(reinterpret_cast(kTestMessage3), + sizeof(kTestMessage3)); + std::string out; + + OpenPGPSymmetricEncrytion::Result r = + OpenPGPSymmetricEncrytion::Decrypt(encrypted, "testing", &out); + EXPECT_EQ(OpenPGPSymmetricEncrytion::OK, r); + EXPECT_EQ("Hello world\n", out); +} + +TEST(OpenPGPSymmetricEncrytionTest, SaltedS2K) { + base::StringPiece encrypted(reinterpret_cast(kTestMessage4), + sizeof(kTestMessage4)); + std::string out; + + OpenPGPSymmetricEncrytion::Result r = + OpenPGPSymmetricEncrytion::Decrypt(encrypted, "testing", &out); + EXPECT_EQ(OpenPGPSymmetricEncrytion::OK, r); + EXPECT_EQ("Hello world\n", out); +} + +TEST(OpenPGPSymmetricEncrytionTest, Encrypt) { + for (unsigned i = 0; i < 16; i++) { + std::string encrypted = + OpenPGPSymmetricEncrytion::Encrypt("Hello world\n", "testing"); + + std::string out; + OpenPGPSymmetricEncrytion::Result r = + OpenPGPSymmetricEncrytion::Decrypt(encrypted, "testing", &out); + EXPECT_EQ(OpenPGPSymmetricEncrytion::OK, r); + EXPECT_EQ("Hello world\n", out); + } +} + +} // namespace crypto diff --git a/crypto/openpgp_symmetric_encryption_openssl.cc b/crypto/openpgp_symmetric_encryption_openssl.cc deleted file mode 100644 index bebf095..0000000 --- a/crypto/openpgp_symmetric_encryption_openssl.cc +++ /dev/null @@ -1,707 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "crypto/openpgp_symmetric_encryption.h" - -#include -#include - -#include -#include -#include - -#include "base/rand_util.h" -#include "base/logging.h" - -namespace crypto { - -namespace { - -// Reader wraps a StringPiece and provides methods to read several datatypes -// while advancing the StringPiece. -class Reader { - public: - Reader(base::StringPiece input) - : data_(input) { - } - - bool U8(uint8* out) { - if (data_.size() < 1) - return false; - *out = static_cast(data_[0]); - data_.remove_prefix(1); - return true; - } - - bool U32(uint32* out) { - if (data_.size() < 4) - return false; - *out = static_cast(data_[0]) << 24 | - static_cast(data_[1]) << 16 | - static_cast(data_[2]) << 8 | - static_cast(data_[3]); - data_.remove_prefix(4); - return true; - } - - // Prefix sets |*out| to the first |n| bytes of the StringPiece and advances - // the StringPiece by |n|. - bool Prefix(uint32 n, base::StringPiece *out) { - if (data_.size() < n) - return false; - *out = base::StringPiece(data_.data(), n); - data_.remove_prefix(n); - return true; - } - - // Remainder returns the remainer of the StringPiece and advances it to the - // end. - base::StringPiece Remainder() { - base::StringPiece ret = data_; - data_ = base::StringPiece(); - return ret; - } - - typedef base::StringPiece Position; - - Position tell() const { - return data_; - } - - void Seek(Position p) { - data_ = p; - } - - bool Skip(uint32 n) { - if (data_.size() < n) - return false; - data_.remove_prefix(n); - return true; - } - - bool empty() const { - return data_.empty(); - } - - size_t size() const { - return data_.size(); - } - - private: - base::StringPiece data_; -}; - -// SaltedIteratedS2K implements the salted and iterated string-to-key -// convertion. See RFC 4880, section 3.7.1.3. -void SaltedIteratedS2K(uint32 cipher_key_length, - const EVP_MD *hash_function, - base::StringPiece passphrase, - base::StringPiece salt, - uint32 count, - uint8 *out_key) { - const std::string combined = salt.as_string() + passphrase.as_string(); - const size_t combined_len = combined.size(); - - uint32 done = 0; - uint8 zero[1] = {0}; - - EVP_MD_CTX ctx; - EVP_MD_CTX_init(&context); - - for (uint32 i = 0; done < cipher_key_length; i++) { - CHECK_EQ(EVP_DigestInit_ex(&ctx, hash_function, NULL), 1); - - for (uint32 j = 0; j < i; j++) - EVP_DigestUpdate(&ctx, zero, sizeof(zero)); - - uint32 written = 0; - while (written < count) { - if (written + combined_len > count) { - uint32 todo = count - written; - EVP_DigestUpdate(&ctx, combined.data(), todo); - written = count; - } else { - EVP_DigestUpdate(&ctx, combined.data(), combined_len); - written += combined_len; - } - } - - uint32 num_hash_bytes; - uint8 hash[EVP_MAX_MD_SIZE]; - CHECK_EQ(EVP_DigestFinal_ex(&ctx, hash, &num_hash_bytes), 1); - - uint32 todo = cipher_key_length - done; - if (todo > num_hash_bytes) - todo = num_hash_bytes; - memcpy(out_key + done, hash, todo); - done += todo; - } - - EVP_MD_CTX_cleanup(&context); -} - -// These constants are the tag numbers for the various packet types that we -// use. -static const uint32 kSymmetricKeyEncryptedTag = 3; -static const uint32 kSymmetricallyEncryptedTag = 18; -static const uint32 kCompressedTag = 8; -static const uint32 kLiteralDataTag = 11; - -class Decrypter { - public: - ~Decrypter() { - for (std::vector::iterator - i = arena_.begin(); i != arena_.end(); i++) { - free(*i); - } - arena_.clear(); - } - - OpenPGPSymmetricEncrytion::Result Decrypt(base::StringPiece in, - base::StringPiece passphrase, - base::StringPiece *out_contents) { - Reader reader(in); - uint32 tag; - base::StringPiece contents; - AES_KEY key; - - if (!ParsePacket(&reader, &tag, &contents)) - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - if (tag != kSymmetricKeyEncryptedTag) - return OpenPGPSymmetricEncrytion::NOT_SYMMETRICALLY_ENCRYPTED; - Reader inner(contents); - OpenPGPSymmetricEncrytion::Result result = - ParseSymmetricKeyEncrypted(&inner, passphrase, &key); - if (result != OpenPGPSymmetricEncrytion::OK) - return result; - - if (!ParsePacket(&reader, &tag, &contents)) - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - if (tag != kSymmetricallyEncryptedTag) - return OpenPGPSymmetricEncrytion::NOT_SYMMETRICALLY_ENCRYPTED; - if (!reader.empty()) - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - inner = Reader(contents); - if (!ParseSymmetricallyEncrypted(&inner, &key, &contents)) - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - - reader = Reader(contents); - if (!ParsePacket(&reader, &tag, &contents)) - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - if (tag == kCompressedTag) - return OpenPGPSymmetricEncrytion::COMPRESSED; - if (tag != kLiteralDataTag) - return OpenPGPSymmetricEncrytion::NOT_SYMMETRICALLY_ENCRYPTED; - inner = Reader(contents); - if (!ParseLiteralData(&inner, out_contents)) - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - - return OpenPGPSymmetricEncrytion::OK; - } - - private: - // ParsePacket parses an OpenPGP packet from reader. See RFC 4880, section - // 4.2.2. - bool ParsePacket(Reader *reader, - uint32 *out_tag, - base::StringPiece *out_contents) { - uint8 header; - if (!reader->U8(&header)) - return false; - if ((header & 0x80) == 0) { - // Tag byte must have MSB set. - return false; - } - - if ((header & 0x40) == 0) { - // Old format packet. - *out_tag = (header & 0x3f) >> 2; - - uint8 length_type = header & 3; - if (length_type == 3) { - *out_contents = reader->Remainder(); - return true; - } - - const uint32 length_bytes = 1 << length_type; - uint32 length = 0; - for (uint32 i = 0; i < length_bytes; i++) { - uint8 length_byte; - if (!reader->U8(&length_byte)) - return false; - length <<= 8; - length |= length_byte; - } - - return reader->Prefix(length, out_contents); - } - - // New format packet. - *out_tag = header & 0x3f; - uint32 length; - bool is_partial; - if (!ParseLength(reader, &length, &is_partial)) - return false; - if (is_partial) - return ParseStreamContents(reader, length, out_contents); - return reader->Prefix(length, out_contents); - } - - // ParseStreamContents parses all the chunks of a partial length stream from - // reader. See http://tools.ietf.org/html/rfc4880#section-4.2.2.4 - bool ParseStreamContents(Reader *reader, - uint32 length, - base::StringPiece *out_contents) { - const Reader::Position beginning_of_stream = reader->tell(); - const uint32 first_chunk_length = length; - - // First we parse the stream to find its length. - if (!reader->Skip(length)) - return false; - - for (;;) { - uint32 chunk_length; - bool is_partial; - - if (!ParseLength(reader, &chunk_length, &is_partial)) - return false; - if (length + chunk_length < length) - return false; - length += chunk_length; - if (!reader->Skip(chunk_length)) - return false; - if (!is_partial) - break; - } - - // Now we have the length of the whole stream in |length|. - char* buf = reinterpret_cast(malloc(length)); - arena_.push_back(buf); - uint32 j = 0; - reader->Seek(beginning_of_stream); - - base::StringPiece first_chunk; - if (!reader->Prefix(first_chunk_length, &first_chunk)) - return false; - memcpy(buf + j, first_chunk.data(), first_chunk_length); - j += first_chunk_length; - - // Now we parse the stream again, this time copying into |buf| - for (;;) { - uint32 chunk_length; - bool is_partial; - - if (!ParseLength(reader, &chunk_length, &is_partial)) - return false; - base::StringPiece chunk; - if (!reader->Prefix(chunk_length, &chunk)) - return false; - memcpy(buf + j, chunk.data(), chunk_length); - j += chunk_length; - if (!is_partial) - break; - } - - *out_contents = base::StringPiece(buf, length); - return true; - } - - // ParseLength parses an OpenPGP length from reader. See RFC 4880, section - // 4.2.2. - bool ParseLength(Reader *reader, uint32 *out_length, bool *out_is_prefix) { - uint8 length_spec; - if (!reader->U8(&length_spec)) - return false; - - *out_is_prefix = false; - if (length_spec < 192) { - *out_length = length_spec; - return true; - } else if (length_spec < 224) { - uint8 next_byte; - if (!reader->U8(&next_byte)) - return false; - - *out_length = (length_spec - 192) << 8; - *out_length += next_byte; - return true; - } else if (length_spec < 255) { - *out_length = 1u << (length_spec & 0x1f); - *out_is_prefix = true; - return true; - } else { - return reader->U32(out_length); - } - } - - // ParseSymmetricKeyEncrypted parses a passphrase protected session key. See - // RFC 4880, section 5.3. - OpenPGPSymmetricEncrytion::Result ParseSymmetricKeyEncrypted( - Reader *reader, - base::StringPiece passphrase, - AES_KEY *out_key) { - uint8 version, cipher, s2k_type, hash_func_id; - if (!reader->U8(&version) || version != 4) - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - - if (!reader->U8(&cipher) || - !reader->U8(&s2k_type) || - !reader->U8(&hash_func_id)) { - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - } - - uint8 cipher_key_length = OpenPGPCipherIdToKeyLength(cipher); - if (cipher_key_length == 0) - return OpenPGPSymmetricEncrytion::UNKNOWN_CIPHER; - - const EVP_MD *hash_function; - switch (hash_func_id) { - case 2: // SHA-1 - hash_function = EVP_sha1(); - break; - case 8: // SHA-256 - hash_function = EVP_sha256(); - break; - default: - return OpenPGPSymmetricEncrytion::UNKNOWN_HASH; - } - - base::StringPiece salt; - uint8 key[32]; - uint8 count_spec; - switch (s2k_type) { - case 1: - if (!reader->Prefix(8, &salt)) - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - case 0: - SaltedIteratedS2K(cipher_key_length, hash_function, passphrase, salt, - passphrase.size() + salt.size(), key); - break; - case 3: - if (!reader->Prefix(8, &salt) || - !reader->U8(&count_spec)) { - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - } - SaltedIteratedS2K( - cipher_key_length, hash_function, passphrase, salt, - static_cast( - 16 + (count_spec&15)) << ((count_spec >> 4) + 6), key); - break; - default: - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - } - - if (AES_set_encrypt_key(key, 8 * cipher_key_length, out_key)) - return OpenPGPSymmetricEncrytion::INTERNAL_ERROR; - - if (reader->empty()) { - // The resulting key is used directly. - return OpenPGPSymmetricEncrytion::OK; - } - - // The S2K derived key encrypts another key that follows: - base::StringPiece encrypted_key = reader->Remainder(); - if (encrypted_key.size() < 1) - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - - uint8* plaintext_key = reinterpret_cast( - malloc(encrypted_key.size())); - arena_.push_back(plaintext_key); - - int num = 0; - uint8 iv[16] = {0}; - - AES_cfb128_encrypt(reinterpret_cast(encrypted_key.data()), - plaintext_key, - encrypted_key.size(), - out_key, - iv, - &num, - AES_DECRYPT); - - cipher_key_length = OpenPGPCipherIdToKeyLength(plaintext_key[0]); - if (cipher_key_length == 0) - return OpenPGPSymmetricEncrytion::UNKNOWN_CIPHER; - if (encrypted_key.size() != 1u + cipher_key_length) - return OpenPGPSymmetricEncrytion::PARSE_ERROR; - if (AES_set_encrypt_key(plaintext_key + 1, 8 * cipher_key_length, - out_key)) { - return OpenPGPSymmetricEncrytion::INTERNAL_ERROR; - } - return OpenPGPSymmetricEncrytion::OK; - } - - uint32 OpenPGPCipherIdToKeyLength(uint8 cipher) { - switch (cipher) { - case 7: // AES-128 - return 16; - case 8: // AES-192 - return 24; - case 9: // AES-256 - return 32; - default: - return 0; - } - } - - // ParseSymmetricallyEncrypted parses a Symmetrically Encrypted packet. See - // RFC 4880, sections 5.7 and 5.13. - bool ParseSymmetricallyEncrypted(Reader *reader, - AES_KEY *key, - base::StringPiece *out_plaintext) { - uint8 version; - if (!reader->U8(&version) || version != 1) - return false; - - base::StringPiece prefix_sp; - if (!reader->Prefix(AES_BLOCK_SIZE + 2, &prefix_sp)) - return false; - uint8 prefix[AES_BLOCK_SIZE + 2]; - memcpy(prefix, prefix_sp.data(), sizeof(prefix)); - - uint8 prefix_copy[AES_BLOCK_SIZE + 2]; - uint8 fre[AES_BLOCK_SIZE]; - - memset(prefix_copy, 0, AES_BLOCK_SIZE); - AES_ecb_encrypt(prefix_copy, fre, key, AES_ENCRYPT); - for (uint32 i = 0; i < AES_BLOCK_SIZE; i++) - prefix_copy[i] = fre[i] ^ prefix[i]; - AES_ecb_encrypt(prefix, fre, key, AES_ENCRYPT); - prefix_copy[AES_BLOCK_SIZE] = prefix[AES_BLOCK_SIZE] ^ fre[0]; - prefix_copy[AES_BLOCK_SIZE + 1] = prefix[AES_BLOCK_SIZE + 1] ^ fre[1]; - - if (prefix_copy[AES_BLOCK_SIZE - 2] != prefix_copy[AES_BLOCK_SIZE] || - prefix_copy[AES_BLOCK_SIZE - 1] != prefix_copy[AES_BLOCK_SIZE + 1]) { - return false; - } - - fre[0] = prefix[AES_BLOCK_SIZE]; - fre[1] = prefix[AES_BLOCK_SIZE + 1]; - - uint32 out_used = 2; - - const uint32 plaintext_size = reader->size(); - if (plaintext_size < SHA_DIGEST_LENGTH + 2) { - // Too small to contain an MDC trailer. - return false; - } - - uint8* plaintext = reinterpret_cast(malloc(plaintext_size)); - arena_.push_back(plaintext); - - for (uint32 i = 0; i < plaintext_size; i++) { - uint8 b; - if (!reader->U8(&b)) - return false; - if (out_used == AES_BLOCK_SIZE) { - AES_ecb_encrypt(fre, fre, key, AES_ENCRYPT); - out_used = 0; - } - - plaintext[i] = b ^ fre[out_used]; - fre[out_used++] = b; - } - - // The plaintext should be followed by a Modification Detection Code - // packet. This packet is specified such that the header is always - // serialized as exactly these two bytes: - if (plaintext[plaintext_size - SHA_DIGEST_LENGTH - 2] != 0xd3 || - plaintext[plaintext_size - SHA_DIGEST_LENGTH - 1] != 0x14) { - return false; - } - - SHA_CTX sha1; - SHA1_Init(&sha1); - SHA1_Update(&sha1, prefix_copy, sizeof(prefix_copy)); - SHA1_Update(&sha1, plaintext, plaintext_size - SHA_DIGEST_LENGTH); - uint8 digest[SHA_DIGEST_LENGTH]; - SHA1_Final(digest, &sha1); - - if (memcmp(digest, &plaintext[plaintext_size - SHA_DIGEST_LENGTH], - SHA_DIGEST_LENGTH) != 0) { - return false; - } - - *out_plaintext = base::StringPiece(reinterpret_cast(plaintext), - plaintext_size - SHA_DIGEST_LENGTH); - return true; - } - - // ParseLiteralData parses a Literal Data packet. See RFC 4880, section 5.9. - bool ParseLiteralData(Reader *reader, base::StringPiece *out_data) { - uint8 is_binary, filename_len; - if (!reader->U8(&is_binary) || - !reader->U8(&filename_len) || - !reader->Skip(filename_len) || - !reader->Skip(sizeof(uint32) /* mtime */)) { - return false; - } - - *out_data = reader->Remainder(); - return true; - } - - // arena_ contains malloced pointers that are used as temporary space during - // the decryption. - std::vector arena_; -}; - -class Encrypter { - public: - // ByteString is used throughout in order to avoid signedness issues with a - // std::string. - typedef std::basic_string ByteString; - - static ByteString Encrypt(base::StringPiece plaintext, - base::StringPiece passphrase) { - ByteString key; - ByteString ske = SerializeSymmetricKeyEncrypted(passphrase, &key); - - ByteString literal_data = SerializeLiteralData(plaintext); - ByteString se = SerializeSymmetricallyEncrypted(literal_data, key); - return ske + se; - } - - private: - static ByteString MakePacket(uint32 tag, const ByteString& contents) { - ByteString header; - header.push_back(0x80 | 0x40 | tag); - - if (contents.size() < 192) { - header.push_back(contents.size()); - } else if (contents.size() < 8384) { - size_t length = contents.size(); - length -= 192; - header.push_back(192 + (length >> 8)); - header.push_back(length & 0xff); - } else { - size_t length = contents.size(); - header.push_back(255); - header.push_back(length >> 24); - header.push_back(length >> 16); - header.push_back(length >> 8); - header.push_back(length); - } - - return header + contents; - } - - static ByteString SerializeLiteralData(base::StringPiece contents) { - ByteString literal_data; - literal_data.push_back(0x74); // text mode - literal_data.push_back(0x00); // no filename - literal_data.push_back(0x00); // zero mtime - literal_data.push_back(0x00); - literal_data.push_back(0x00); - literal_data.push_back(0x00); - literal_data += ByteString(reinterpret_cast(contents.data()), - contents.size()); - return MakePacket(kLiteralDataTag, literal_data); - } - - static ByteString SerializeSymmetricKeyEncrypted(base::StringPiece passphrase, - ByteString *out_key) { - ByteString ske; - ske.push_back(4); // version 4 - ske.push_back(7); // AES-128 - ske.push_back(3); // iterated and salted S2K - ske.push_back(2); // SHA-1 - - uint64 salt64 = base::RandUint64(); - ByteString salt(sizeof(salt64), 0); - - // It's a random value, so endianness doesn't matter. - ske += ByteString(reinterpret_cast(&salt64), sizeof(salt64)); - ske.push_back(96); // iteration count of 65536 - - uint8 key[16]; - SaltedIteratedS2K( - sizeof(key), EVP_sha1(), passphrase, - base::StringPiece(reinterpret_cast(&salt64), sizeof(salt64)), - 65536, key); - *out_key = ByteString(key, sizeof(key)); - return MakePacket(kSymmetricKeyEncryptedTag, ske); - } - - static ByteString SerializeSymmetricallyEncrypted(ByteString plaintext, - const ByteString& key) { - ByteString packet; - packet.push_back(1); // version 1 - static const uint32 kBlockSize = 16; // AES block size - - uint8 prefix[kBlockSize + 2], fre[kBlockSize], iv[kBlockSize]; - base::RandBytes(iv, kBlockSize); - memset(fre, 0, sizeof(fre)); - - AES_KEY aes_key; - AES_set_encrypt_key(key.data(), 8 * key.size(), &aes_key); - - AES_ecb_encrypt(fre, fre, &aes_key, AES_ENCRYPT); - for (uint32 i = 0; i < 16; i++) - prefix[i] = iv[i] ^ fre[i]; - AES_ecb_encrypt(prefix, fre, &aes_key, AES_ENCRYPT); - prefix[kBlockSize] = iv[kBlockSize - 2] ^ fre[0]; - prefix[kBlockSize + 1] = iv[kBlockSize - 1] ^ fre[1]; - - packet += ByteString(prefix, sizeof(prefix)); - - ByteString plaintext_copy = plaintext; - plaintext_copy.push_back(0xd3); // MDC packet - plaintext_copy.push_back(20); // packet length (20 bytes) - - SHA_CTX sha1; - SHA1_Init(&sha1); - SHA1_Update(&sha1, iv, sizeof(iv)); - SHA1_Update(&sha1, iv + kBlockSize - 2, 2); - SHA1_Update(&sha1, plaintext_copy.data(), plaintext_copy.size()); - uint8 digest[SHA_DIGEST_LENGTH]; - SHA1_Final(digest, &sha1); - - plaintext_copy += ByteString(digest, sizeof(digest)); - - fre[0] = prefix[kBlockSize]; - fre[1] = prefix[kBlockSize+1]; - uint32 out_used = 2; - - for (size_t i = 0; i < plaintext_copy.size(); i++) { - if (out_used == kBlockSize) { - AES_ecb_encrypt(fre, fre, &aes_key, AES_ENCRYPT); - out_used = 0; - } - - uint8 c = plaintext_copy[i] ^ fre[out_used]; - fre[out_used++] = c; - packet.push_back(c); - } - - return MakePacket(kSymmetricallyEncryptedTag, packet); - } -}; - -} // anonymous namespace - -// static -OpenPGPSymmetricEncrytion::Result OpenPGPSymmetricEncrytion::Decrypt( - base::StringPiece encrypted, - base::StringPiece passphrase, - std::string *out) { - Decrypter decrypter; - - base::StringPiece result; - Result reader = decrypter.Decrypt(encrypted, passphrase, &result); - if (reader == OK) - *out = result.as_string(); - return reader; -} - -// static -std::string OpenPGPSymmetricEncrytion::Encrypt( - base::StringPiece plaintext, - base::StringPiece passphrase) { - Encrypter::ByteString b = - Encrypter::Encrypt(plaintext, passphrase); - return std::string(reinterpret_cast(b.data()), b.size()); -} - -} // namespace crypto diff --git a/crypto/openpgp_symmetric_encryption_test_openssl.cc b/crypto/openpgp_symmetric_encryption_test_openssl.cc deleted file mode 100644 index 6185719..0000000 --- a/crypto/openpgp_symmetric_encryption_test_openssl.cc +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "crypto/openpgp_symmetric_encryption.h" - -#include "testing/gtest/include/gtest/gtest.h" - -namespace crypto { - -// These test vectors were created by hand using either GPG or the Go OpenPGP -// library. - -// AES-128, GPG -static const uint8 kTestMessage1[] = { - 0x8c, 0x0d, 0x04, 0x07, 0x03, 0x02, 0x69, 0x24, 0xaf, 0xbf, 0x0b, 0x31, 0x98, - 0x6d, 0x60, 0xd2, 0x3d, 0x01, 0xc4, 0x29, 0xab, 0xec, 0x1b, 0xdf, 0xfa, 0x90, - 0x86, 0x92, 0x94, 0xc7, 0xa5, 0xe7, 0xd8, 0x80, 0x0a, 0x55, 0x3e, 0xbd, 0x10, - 0xef, 0x40, 0xfe, 0xb7, 0x39, 0x83, 0x4e, 0x5e, 0x77, 0x9d, 0x57, 0x94, 0xb6, - 0xe4, 0x59, 0xa7, 0x32, 0x76, 0x22, 0x48, 0xed, 0x37, 0xe8, 0x6f, 0xf9, 0x80, - 0x04, 0xa1, 0xe4, 0xbf, 0x40, 0xa6, 0x9b, 0xd1, 0x3e, 0xba, 0xaa, 0x52, 0xd0, -}; - -// AES-256, GPG -static const uint8 kTestMessage2[] = { - 0x8c, 0x0d, 0x04, 0x09, 0x03, 0x02, 0xd6, 0xb0, 0x34, 0xa0, 0xb8, 0x6c, 0x15, - 0xb8, 0x60, 0xd2, 0x3d, 0x01, 0x97, 0xe4, 0x46, 0x56, 0xc5, 0xc9, 0xc7, 0x81, - 0xd1, 0x09, 0xf3, 0xa0, 0x5d, 0x3b, 0xa7, 0xe3, 0x68, 0xb4, 0x19, 0xd2, 0x76, - 0x83, 0x38, 0x13, 0x98, 0xb8, 0xaf, 0x54, 0x51, 0x52, 0xbb, 0xc5, 0x7d, 0x8a, - 0x70, 0x66, 0x40, 0x0b, 0xb5, 0x92, 0xc3, 0xd3, 0x51, 0x63, 0x5d, 0x99, 0x9b, - 0x96, 0x82, 0xe1, 0xfe, 0xac, 0xa8, 0xa5, 0x87, 0x8b, 0x3f, 0xd1, 0x90, 0x70, -}; - -// AES-128, Go -static const uint8 kTestMessage3[] = { - 0xc3, 0x1e, 0x04, 0x07, 0x03, 0x02, 0x74, 0x1e, 0x2d, 0x7d, 0x2e, 0xdf, 0x20, - 0xdb, 0x60, 0xb1, 0x22, 0xca, 0x39, 0x74, 0x2f, 0xe8, 0x2f, 0x09, 0xf8, 0xa4, - 0x13, 0x76, 0x14, 0x65, 0x6e, 0xb9, 0xd2, 0xe0, 0x01, 0xe4, 0x40, 0x83, 0x90, - 0x3c, 0x76, 0xac, 0x23, 0x3e, 0xd2, 0xb0, 0xb2, 0x1c, 0x9d, 0x24, 0xcb, 0x7b, - 0xe1, 0x0c, 0x6b, 0xe0, 0x86, 0xe0, 0x22, 0xe1, 0xf2, 0xcb, 0xe0, 0xf3, 0xe2, - 0xbf, 0xdd, 0x89, 0xda, 0xe0, 0x83, 0xe3, 0xd9, 0x34, 0xf9, 0xaa, 0x17, 0x96, - 0x6a, 0x28, 0xe0, 0xca, 0xe2, 0x78, 0xca, 0x0e, 0x7d, 0xe0, 0xeb, 0xe4, 0xca, - 0xe6, 0xf0, 0x90, 0x4f, 0x22, 0x91, 0xf9, 0xb2, 0xbb, 0x19, 0x0b, 0x45, 0xc0, - 0x16, 0x9e, 0xe2, 0xdb, 0x30, 0xad, 0xbb, 0xe1, 0xb8, 0x12, 0x00, -}; - -// AES-128, salted S2K, GPG -static const uint8 kTestMessage4[] = { - 0x8c, 0x0c, 0x04, 0x07, 0x01, 0x02, 0xf8, 0x42, 0x78, 0x07, 0x04, 0xaa, 0x54, - 0xcc, 0xd2, 0x3d, 0x01, 0xcf, 0xb7, 0x30, 0xe1, 0xed, 0xb2, 0x53, 0x6e, 0x4a, - 0xbc, 0x49, 0x27, 0x45, 0xde, 0x1d, 0x5b, 0xe2, 0x17, 0x43, 0x39, 0x79, 0xdc, - 0xa5, 0xb7, 0x1a, 0x1b, 0xb7, 0x29, 0x9c, 0xb5, 0x69, 0x2f, 0x42, 0xc5, 0xe5, - 0x0c, 0x78, 0x57, 0x16, 0xa6, 0x46, 0x22, 0x18, 0x0c, 0xa2, 0xb3, 0x8c, 0xee, - 0xa1, 0xde, 0x38, 0xf1, 0xca, 0x73, 0xd3, 0xd6, 0xa3, 0x61, 0x47, 0xe2, -}; - -TEST(OpenPGPSymmetricEncrytionTest, AES128GPG) { - base::StringPiece encrypted(reinterpret_cast(kTestMessage1), - sizeof(kTestMessage1)); - std::string out; - - OpenPGPSymmetricEncrytion::Result r = - OpenPGPSymmetricEncrytion::Decrypt(encrypted, "testing", &out); - EXPECT_EQ(OpenPGPSymmetricEncrytion::OK, r); - EXPECT_EQ("Hello world\n", out); -} - -TEST(OpenPGPSymmetricEncrytionTest, AES256GPG) { - base::StringPiece encrypted(reinterpret_cast(kTestMessage2), - sizeof(kTestMessage2)); - std::string out; - - OpenPGPSymmetricEncrytion::Result r = - OpenPGPSymmetricEncrytion::Decrypt(encrypted, "testing", &out); - EXPECT_EQ(OpenPGPSymmetricEncrytion::OK, r); - EXPECT_EQ("Hello world\n", out); -} - -TEST(OpenPGPSymmetricEncrytionTest, AES128Go) { - base::StringPiece encrypted(reinterpret_cast(kTestMessage3), - sizeof(kTestMessage3)); - std::string out; - - OpenPGPSymmetricEncrytion::Result r = - OpenPGPSymmetricEncrytion::Decrypt(encrypted, "testing", &out); - EXPECT_EQ(OpenPGPSymmetricEncrytion::OK, r); - EXPECT_EQ("Hello world\n", out); -} - -TEST(OpenPGPSymmetricEncrytionTest, SaltedS2K) { - base::StringPiece encrypted(reinterpret_cast(kTestMessage4), - sizeof(kTestMessage4)); - std::string out; - - OpenPGPSymmetricEncrytion::Result r = - OpenPGPSymmetricEncrytion::Decrypt(encrypted, "testing", &out); - EXPECT_EQ(OpenPGPSymmetricEncrytion::OK, r); - EXPECT_EQ("Hello world\n", out); -} - -TEST(OpenPGPSymmetricEncrytionTest, Encrypt) { - for (unsigned i = 0; i < 16; i++) { - std::string encrypted = - OpenPGPSymmetricEncrytion::Encrypt("Hello world\n", "testing"); - - std::string out; - OpenPGPSymmetricEncrytion::Result r = - OpenPGPSymmetricEncrytion::Decrypt(encrypted, "testing", &out); - EXPECT_EQ(OpenPGPSymmetricEncrytion::OK, r); - EXPECT_EQ("Hello world\n", out); - } -} - -} // namespace crypto -- cgit v1.1