Add scrypt-based password stretching.

Bug: 27056334
Change-Id: Ifa7f776c21c439f89dad7836175fbd045e1c603e
diff --git a/KeyStorage.cpp b/KeyStorage.cpp
index def1a32..2338f23 100644
--- a/KeyStorage.cpp
+++ b/KeyStorage.cpp
@@ -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 @@
 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 @@
     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 @@
         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 @@
     }
     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;