Add scrypt-based password stretching.

Bug: 27056334
Change-Id: Ifa7f776c21c439f89dad7836175fbd045e1c603e
This commit is contained in:
Paul Crowley 2016-02-10 14:02:47 +00:00
parent 58e9c2a4aa
commit 63c18d3ba9
7 changed files with 180 additions and 57 deletions

View file

@ -29,6 +29,7 @@ common_src_files := \
TrimTask.cpp \
Keymaster.cpp \
KeyStorage.cpp \
ScryptParameters.cpp \
secontext.cpp \
common_c_includes := \

View file

@ -42,13 +42,9 @@
#include "cryptfs.h"
#include "ext4_crypt.h"
#define LOG_TAG "Ext4Crypt"
#define EMULATED_USES_SELINUX 0
#include <cutils/fs.h>
#include <cutils/log.h>
#include <cutils/klog.h>
#include <android-base/file.h>
#include <android-base/logging.h>

View file

@ -17,6 +17,7 @@
#include "KeyStorage.h"
#include "Keymaster.h"
#include "ScryptParameters.h"
#include "Utils.h"
#include <vector>
@ -32,8 +33,16 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <cutils/properties.h>
#include <keymaster/authorization_set.h>
extern "C" {
#include "crypto_scrypt.h"
}
namespace android {
namespace vold {
@ -42,14 +51,19 @@ const KeyAuthentication kEmptyAuthentication { "", "" };
static constexpr size_t AES_KEY_BYTES = 32;
static constexpr size_t GCM_NONCE_BYTES = 12;
static constexpr size_t GCM_MAC_BYTES = 16;
// FIXME: better name than "secdiscardable" sought!
static constexpr size_t SALT_BYTES = 1<<4;
static constexpr size_t SECDISCARDABLE_BYTES = 1<<14;
static constexpr size_t STRETCHED_BYTES = 1<<6;
static const char* kCurrentVersion = "1";
static const char* kRmPath = "/system/bin/rm";
static const char* kSecdiscardPath = "/system/bin/secdiscard";
static const char* kStretch_none = "none";
static const char* kStretch_nopassword = "nopassword";
static const std::string kStretchPrefix_scrypt = "scrypt ";
static const char* kFn_encrypted_key = "encrypted_key";
static const char* kFn_keymaster_key_blob = "keymaster_key_blob";
static const char* kFn_salt = "salt";
static const char* kFn_secdiscardable = "secdiscardable";
static const char* kFn_stretching = "stretching";
static const char* kFn_version = "version";
@ -165,15 +179,64 @@ static bool writeStringToFile(const std::string &payload, const std::string &fil
return true;
}
static keymaster::AuthorizationSet buildParams(
const KeyAuthentication &auth, const std::string &secdiscardable) {
static std::string getStretching() {
char paramstr[PROPERTY_VALUE_MAX];
property_get(SCRYPT_PROP, paramstr, SCRYPT_DEFAULTS);
return std::string() + kStretchPrefix_scrypt + paramstr;
}
static bool stretchingNeedsSalt(const std::string &stretching) {
return stretching != kStretch_nopassword && stretching != kStretch_none;
}
static bool stretchSecret(const std::string &stretching, const std::string &secret,
const std::string &salt, std::string &stretched) {
if (stretching == kStretch_nopassword) {
if (!secret.empty()) {
LOG(ERROR) << "Password present but stretching is nopasswd";
// Continue anyway
}
stretched.clear();
} else if (stretching == kStretch_none) {
stretched = secret;
} else if (std::equal(kStretchPrefix_scrypt.begin(),
kStretchPrefix_scrypt.end(), stretching.begin())) {
int Nf, rf, pf;
if (!parse_scrypt_parameters(
stretching.substr(kStretchPrefix_scrypt.size()).c_str(), &Nf, &rf, &pf)) {
LOG(ERROR) << "Unable to parse scrypt params in stretching: " << stretching;
return false;
}
stretched.assign(STRETCHED_BYTES, '\0');
if (crypto_scrypt(
reinterpret_cast<const uint8_t *>(secret.data()), secret.size(),
reinterpret_cast<const uint8_t *>(salt.data()), salt.size(),
1 << Nf, 1 << rf, 1 << pf,
reinterpret_cast<uint8_t *>(&stretched[0]), stretched.size()) != 0) {
LOG(ERROR) << "scrypt failed with params: " << stretching;
return false;
}
} else {
LOG(ERROR) << "Unknown stretching type: " << stretching;
return false;
}
return true;
}
static bool buildParams(const KeyAuthentication &auth, const std::string &stretching,
const std::string &salt, const std::string &secdiscardable,
keymaster::AuthorizationSet &result) {
std::string stretched;
if (!stretchSecret(stretching, auth.secret, salt, stretched)) return false;
auto appId = hashSecdiscardable(secdiscardable) + stretched;
keymaster::AuthorizationSetBuilder paramBuilder;
auto appId = hashSecdiscardable(secdiscardable) + auth.secret;
addStringParam(paramBuilder, keymaster::TAG_APPLICATION_ID, appId);
if (!auth.token.empty()) {
addStringParam(paramBuilder, keymaster::TAG_AUTH_TOKEN, auth.token);
}
return paramBuilder.build();
result = paramBuilder.build();
return true;
}
bool storeKey(const std::string &dir, const KeyAuthentication &auth, const std::string &key) {
@ -189,17 +252,24 @@ bool storeKey(const std::string &dir, const KeyAuthentication &auth, const std::
return false;
}
if (!writeStringToFile(secdiscardable, dir + "/" + kFn_secdiscardable)) return false;
// Future proofing for when we add key stretching per b/27056334
auto stretching = auth.secret.empty() ? "nopassword" : "none";
std::string stretching = auth.secret.empty() ? kStretch_nopassword : getStretching();
if (!writeStringToFile(stretching, dir + "/" + kFn_stretching)) return false;
auto extraParams = buildParams(auth, secdiscardable);
std::string salt;
if (stretchingNeedsSalt(stretching)) {
if (ReadRandomBytes(SALT_BYTES, salt) != OK) {
LOG(ERROR) << "Random read failed";
return false;
}
if (!writeStringToFile(salt, dir + "/" + kFn_salt)) return false;
}
keymaster::AuthorizationSet extraParams;
if (!buildParams(auth, stretching, salt, secdiscardable, extraParams)) return false;
Keymaster keymaster;
if (!keymaster) return false;
std::string kmKey;
if (!generateKeymasterKey(keymaster, extraParams, kmKey)) return false;
std::string encryptedKey;
if (!encryptWithKeymasterKey(
keymaster, kmKey, extraParams, key, encryptedKey)) return false;
if (!encryptWithKeymasterKey(keymaster, kmKey, extraParams, key, encryptedKey)) return false;
if (!writeStringToFile(kmKey, dir + "/" + kFn_keymaster_key_blob)) return false;
if (!writeStringToFile(encryptedKey, dir + "/" + kFn_encrypted_key)) return false;
return true;
@ -214,7 +284,14 @@ bool retrieveKey(const std::string &dir, const KeyAuthentication &auth, std::str
}
std::string secdiscardable;
if (!readFileToString(dir + "/" + kFn_secdiscardable, secdiscardable)) return false;
auto extraParams = buildParams(auth, secdiscardable);
std::string stretching;
if (!readFileToString(dir + "/" + kFn_stretching, stretching)) return false;
std::string salt;
if (stretchingNeedsSalt(stretching)) {
if (!readFileToString(dir + "/" + kFn_salt, salt)) return false;
}
keymaster::AuthorizationSet extraParams;
if (!buildParams(auth, stretching, salt, secdiscardable, extraParams)) return false;
std::string kmKey;
if (!readFileToString(dir + "/" + kFn_keymaster_key_blob, kmKey)) return false;
std::string encryptedMessage;

50
ScryptParameters.cpp Normal file
View file

@ -0,0 +1,50 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ScryptParameters.h"
#include <stdlib.h>
#include <string.h>
bool parse_scrypt_parameters(const char* paramstr, int *Nf, int *rf, int *pf) {
int params[3];
char *token;
char *saveptr;
int i;
/*
* The token we're looking for should be three integers separated by
* colons (e.g., "12:8:1"). Scan the property to make sure it matches.
*/
for (i = 0, token = strtok_r(const_cast<char *>(paramstr), ":", &saveptr);
token != nullptr && i < 3;
i++, token = strtok_r(nullptr, ":", &saveptr)) {
char *endptr;
params[i] = strtol(token, &endptr, 10);
/*
* Check that there was a valid number and it's 8-bit.
*/
if ((*token == '\0') || (*endptr != '\0') || params[i] < 0 || params[i] > 255) {
return false;
}
}
if (token != nullptr) {
return false;
}
*Nf = params[0]; *rf = params[1]; *pf = params[2];
return true;
}

32
ScryptParameters.h Normal file
View file

@ -0,0 +1,32 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_VOLD_SCRYPT_PARAMETERS_H
#define ANDROID_VOLD_SCRYPT_PARAMETERS_H
#include <stdbool.h>
#include <sys/cdefs.h>
#define SCRYPT_PROP "ro.crypto.scrypt_params"
#define SCRYPT_DEFAULTS "15:3:1"
__BEGIN_DECLS
bool parse_scrypt_parameters(const char* paramstr, int *Nf, int *rf, int *pf);
__END_DECLS
#endif

View file

@ -52,6 +52,7 @@
#include "cutils/android_reboot.h"
#include "hardware_legacy/power.h"
#include <logwrap/logwrap.h>
#include "ScryptParameters.h"
#include "VolumeManager.h"
#include "VoldUtil.h"
#include "crypto_scrypt.h"
@ -475,48 +476,17 @@ static void ioctl_init(struct dm_ioctl *io, size_t dataSize, const char *name, u
* given device.
*/
static void get_device_scrypt_params(struct crypt_mnt_ftr *ftr) {
const int default_params[] = SCRYPT_DEFAULTS;
int params[] = SCRYPT_DEFAULTS;
char paramstr[PROPERTY_VALUE_MAX];
char *token;
char *saveptr;
int i;
int Nf, rf, pf;
property_get(SCRYPT_PROP, paramstr, "");
if (paramstr[0] != '\0') {
/*
* The token we're looking for should be three integers separated by
* colons (e.g., "12:8:1"). Scan the property to make sure it matches.
*/
for (i = 0, token = strtok_r(paramstr, ":", &saveptr);
token != NULL && i < 3;
i++, token = strtok_r(NULL, ":", &saveptr)) {
char *endptr;
params[i] = strtol(token, &endptr, 10);
/*
* Check that there was a valid number and it's 8-bit. If not,
* break out and the end check will take the default values.
*/
if ((*token == '\0') || (*endptr != '\0') || params[i] < 0 || params[i] > 255) {
break;
}
}
/*
* If there were not enough tokens or a token was malformed (not an
* integer), it will end up here and the default parameters can be
* taken.
*/
if ((i != 3) || (token != NULL)) {
SLOGW("bad scrypt parameters '%s' should be like '12:8:1'; using defaults", paramstr);
memcpy(params, default_params, sizeof(params));
}
property_get(SCRYPT_PROP, paramstr, SCRYPT_DEFAULTS);
if (!parse_scrypt_parameters(paramstr, &Nf, &rf, &pf)) {
SLOGW("bad scrypt parameters '%s' should be like '12:8:1'; using defaults", paramstr);
parse_scrypt_parameters(SCRYPT_DEFAULTS, &Nf, &rf, &pf);
}
ftr->N_factor = params[0];
ftr->r_factor = params[1];
ftr->p_factor = params[2];
ftr->N_factor = Nf;
ftr->r_factor = rf;
ftr->p_factor = pf;
}
static unsigned int get_fs_size(char *dev)

View file

@ -76,9 +76,6 @@
#define CRYPT_MNT_MAGIC 0xD0B5B1C4
#define PERSIST_DATA_MAGIC 0xE950CD44
#define SCRYPT_PROP "ro.crypto.scrypt_params"
#define SCRYPT_DEFAULTS { 15, 3, 1 }
/* Key Derivation Function algorithms */
#define KDF_PBKDF2 1
#define KDF_SCRYPT 2