// Copyright (c) 2012 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 "sync/util/nigori.h"
#include <sstream>
#include <vector>
#include "base/base64.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/sys_byteorder.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/random.h"
#include "crypto/symmetric_key.h"
using base::Base64Encode;
using base::Base64Decode;
using crypto::Encryptor;
using crypto::HMAC;
using crypto::SymmetricKey;
namespace syncer {
// NigoriStream simplifies the concatenation operation of the Nigori protocol.
class NigoriStream {
public:
// Append the big-endian representation of the length of |value| with 32 bits,
// followed by |value| itself to the stream.
NigoriStream& operator<<(const std::string& value) {
uint32 size = base::HostToNet32(value.size());
stream_.write((char *) &size, sizeof(uint32));
stream_ << value;
return *this;
}
// Append the big-endian representation of the length of |type| with 32 bits,
// followed by the big-endian representation of the value of |type|, with 32
// bits, to the stream.
NigoriStream& operator<<(const Nigori::Type type) {
uint32 size = base::HostToNet32(sizeof(uint32));
stream_.write((char *) &size, sizeof(uint32));
uint32 value = base::HostToNet32(type);
stream_.write((char *) &value, sizeof(uint32));
return *this;
}
std::string str() {
return stream_.str();
}
private:
std::ostringstream stream_;
};
// static
const char Nigori::kSaltSalt[] = "saltsalt";
Nigori::Nigori() {
}
Nigori::~Nigori() {
}
bool Nigori::InitByDerivation(const std::string& hostname,
const std::string& username,
const std::string& password) {
NigoriStream salt_password;
salt_password << username << hostname;
// Suser = PBKDF2(Username || Servername, "saltsalt", Nsalt, 8)
scoped_ptr<SymmetricKey> user_salt(SymmetricKey::DeriveKeyFromPassword(
SymmetricKey::HMAC_SHA1, salt_password.str(),
kSaltSalt,
kSaltIterations,
kSaltKeySizeInBits));
DCHECK(user_salt.get());
std::string raw_user_salt;
if (!user_salt->GetRawKey(&raw_user_salt))
return false;
// Kuser = PBKDF2(P, Suser, Nuser, 16)
user_key_.reset(SymmetricKey::DeriveKeyFromPassword(SymmetricKey::AES,
password, raw_user_salt, kUserIterations, kDerivedKeySizeInBits));
DCHECK(user_key_.get());
// Kenc = PBKDF2(P, Suser, Nenc, 16)
encryption_key_.reset(SymmetricKey::DeriveKeyFromPassword(SymmetricKey::AES,
password, raw_user_salt, kEncryptionIterations, kDerivedKeySizeInBits));
DCHECK(encryption_key_.get());
// Kmac = PBKDF2(P, Suser, Nmac, 16)
mac_key_.reset(SymmetricKey::DeriveKeyFromPassword(
SymmetricKey::HMAC_SHA1, password, raw_user_salt, kSigningIterations,
kDerivedKeySizeInBits));
DCHECK(mac_key_.get());
return user_key_.get() && encryption_key_.get() && mac_key_.get();
}
bool Nigori::InitByImport(const std::string& user_key,
const std::string& encryption_key,
const std::string& mac_key) {
user_key_.reset(SymmetricKey::Import(SymmetricKey::AES, user_key));
DCHECK(user_key_.get());
encryption_key_.reset(SymmetricKey::Import(SymmetricKey::AES,
encryption_key));
DCHECK(encryption_key_.get());
mac_key_.reset(SymmetricKey::Import(SymmetricKey::HMAC_SHA1, mac_key));
DCHECK(mac_key_.get());
return user_key_.get() && encryption_key_.get() && mac_key_.get();
}
// Permute[Kenc,Kmac](type || name)
bool Nigori::Permute(Type type, const std::string& name,
std::string* permuted) const {
DCHECK_LT(0U, name.size());
NigoriStream plaintext;
plaintext << type << name;
Encryptor encryptor;
if (!encryptor.Init(encryption_key_.get(), Encryptor::CBC,
std::string(kIvSize, 0)))
return false;
std::string ciphertext;
if (!encryptor.Encrypt(plaintext.str(), &ciphertext))
return false;
std::string raw_mac_key;
if (!mac_key_->GetRawKey(&raw_mac_key))
return false;
HMAC hmac(HMAC::SHA256);
if (!hmac.Init(raw_mac_key))
return false;
std::vector<unsigned char> hash(kHashSize);
if (!hmac.Sign(ciphertext, &hash[0], hash.size()))
return false;
std::string output;
output.assign(ciphertext);
output.append(hash.begin(), hash.end());
Base64Encode(output, permuted);
return true;
}
// Enc[Kenc,Kmac](value)
bool Nigori::Encrypt(const std::string& value, std::string* encrypted) const {
if (0U >= value.size())
return false;
std::string iv;
crypto::RandBytes(WriteInto(&iv, kIvSize + 1), kIvSize);
Encryptor encryptor;
if (!encryptor.Init(encryption_key_.get(), Encryptor::CBC, iv))
return false;
std::string ciphertext;
if (!encryptor.Encrypt(value, &ciphertext))
return false;
std::string raw_mac_key;
if (!mac_key_->GetRawKey(&raw_mac_key))
return false;
HMAC hmac(HMAC::SHA256);
if (!hmac.Init(raw_mac_key))
return false;
std::vector<unsigned char> hash(kHashSize);
if (!hmac.Sign(ciphertext, &hash[0], hash.size()))
return false;
std::string output;
output.assign(iv);
output.append(ciphertext);
output.append(hash.begin(), hash.end());
Base64Encode(output, encrypted);
return true;
}
bool Nigori::Decrypt(const std::string& encrypted, std::string* value) const {
std::string input;
if (!Base64Decode(encrypted, &input))
return false;
if (input.size() < kIvSize * 2 + kHashSize)
return false;
// The input is:
// * iv (16 bytes)
// * ciphertext (multiple of 16 bytes)
// * hash (32 bytes)
std::string iv(input.substr(0, kIvSize));
std::string ciphertext(input.substr(kIvSize,
input.size() - (kIvSize + kHashSize)));
std::string hash(input.substr(input.size() - kHashSize, kHashSize));
std::string raw_mac_key;
if (!mac_key_->GetRawKey(&raw_mac_key))
return false;
HMAC hmac(HMAC::SHA256);
if (!hmac.Init(raw_mac_key))
return false;
std::vector<unsigned char> expected(kHashSize);
if (!hmac.Sign(ciphertext, &expected[0], expected.size()))
return false;
if (hash.compare(0, hash.size(),
reinterpret_cast<char *>(&expected[0]),
expected.size()))
return false;
Encryptor encryptor;
if (!encryptor.Init(encryption_key_.get(), Encryptor::CBC, iv))
return false;
if (!encryptor.Decrypt(ciphertext, value))
return false;
return true;
}
bool Nigori::ExportKeys(std::string* user_key,
std::string* encryption_key,
std::string* mac_key) const {
DCHECK(user_key);
DCHECK(encryption_key);
DCHECK(mac_key);
return user_key_->GetRawKey(user_key) &&
encryption_key_->GetRawKey(encryption_key) &&
mac_key_->GetRawKey(mac_key);
}
} // namespace syncer