OLD | NEW |
| (Empty) |
1 // Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "pam_offline/authenticator.h" | |
6 | |
7 #include <openssl/sha.h> | |
8 #include <openssl/evp.h> | |
9 #include <openssl/err.h> | |
10 #include <stdlib.h> | |
11 #include <unistd.h> | |
12 | |
13 #include "chromeos/utility.h" | |
14 #include "base/logging.h" | |
15 | |
16 namespace pam_offline { | |
17 | |
18 using std::string; | |
19 | |
20 // system salt and user dirs start here... | |
21 const string kDefaultShadowRoot = "/home/.shadow/"; | |
22 | |
23 // String that appears at the start of OpenSSL cipher text with embedded salt | |
24 const string kOpenSSLMagic = "Salted__"; | |
25 | |
26 Authenticator::Authenticator(const string &shadow_root) | |
27 : shadow_root_(shadow_root) | |
28 {} | |
29 | |
30 Authenticator::Authenticator() : shadow_root_(kDefaultShadowRoot) {} | |
31 | |
32 Authenticator::~Authenticator() {} | |
33 | |
34 bool Authenticator::Init() { | |
35 return LoadFileBytes(PathAppend(shadow_root_, "salt"), &system_salt_); | |
36 } | |
37 | |
38 Blob Authenticator::GetSystemSalt() const { | |
39 return system_salt_; | |
40 } | |
41 | |
42 // This is the analog to cryptohome::password_to_wrapper from the | |
43 // cryptohome script. It computes a SHA1(salt + str) and returns an | |
44 // ASCII encoded version of the result as a string. The hashing step is | |
45 // repeated |iters| number of times. | |
46 // | |
47 string Authenticator::IteratedWrapHashedPassword( | |
48 const string &master_salt_file, const string &hashed_password, | |
49 const int iters) const { | |
50 | |
51 string master_salt; | |
52 if (!LoadFileString(master_salt_file, &master_salt)) { | |
53 return false; | |
54 } | |
55 | |
56 Blob blob(hashed_password.begin(), hashed_password.end()); | |
57 | |
58 for (int i = 0; i < iters; ++i) { | |
59 SHA_CTX ctx; | |
60 unsigned char md_value[SHA_DIGEST_LENGTH]; | |
61 | |
62 SHA1_Init(&ctx); | |
63 SHA1_Update(&ctx, master_salt.c_str(), master_salt.length()); | |
64 SHA1_Update(&ctx, &blob.front(), blob.size()); | |
65 SHA1_Final(md_value, &ctx); | |
66 | |
67 blob.assign(md_value, md_value + SHA_DIGEST_LENGTH); | |
68 } | |
69 | |
70 return AsciiEncode(blob); | |
71 } | |
72 | |
73 string Authenticator::WrapHashedPassword( | |
74 const string &master_salt_file, const string &hashed_password) const { | |
75 return IteratedWrapHashedPassword(master_salt_file, hashed_password, 1); | |
76 } | |
77 | |
78 bool Authenticator::TestDecrypt(const string passphrase, | |
79 const Blob salt, | |
80 const Blob cipher_text) const { | |
81 if (salt.size() < PKCS5_SALT_LEN) { | |
82 LOG(ERROR) << "Invalid salt"; | |
83 return false; | |
84 } | |
85 | |
86 unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH]; | |
87 | |
88 int rv = EVP_BytesToKey( | |
89 EVP_aes_256_cbc(), EVP_sha1(), &salt.front(), | |
90 reinterpret_cast<const unsigned char *>(passphrase.c_str()), | |
91 passphrase.size(), 1, key, iv); | |
92 | |
93 if (rv != EVP_MAX_KEY_LENGTH) { | |
94 LOG(ERROR) << "Key size is " << rv << " bytes, should be " | |
95 << EVP_MAX_KEY_LENGTH; | |
96 return false; | |
97 } | |
98 | |
99 int pt_size = cipher_text.size(); | |
100 | |
101 unsigned char *plain_text = new unsigned char[pt_size]; | |
102 int final_size = 0; | |
103 | |
104 EVP_CIPHER_CTX d_ctx; | |
105 EVP_CIPHER_CTX_init(&d_ctx); | |
106 EVP_DecryptInit_ex(&d_ctx, EVP_aes_256_ecb(), NULL, key, iv); | |
107 rv = EVP_DecryptUpdate(&d_ctx, plain_text, &pt_size, | |
108 &cipher_text.front(), | |
109 cipher_text.size()); | |
110 | |
111 rv = EVP_DecryptFinal_ex(&d_ctx, plain_text + pt_size, &final_size); | |
112 | |
113 pt_size += final_size; | |
114 | |
115 chromeos::SecureMemset(plain_text, sizeof(plain_text), 0); | |
116 delete plain_text; | |
117 | |
118 if (rv != 1) { | |
119 unsigned long err = ERR_get_error(); | |
120 ERR_load_ERR_strings(); | |
121 ERR_load_crypto_strings(); | |
122 | |
123 LOG(INFO) << "OpenSSL Error: " << err | |
124 << ": " << ERR_lib_error_string(err) | |
125 << ", " << ERR_func_error_string(err) | |
126 << ", " << ERR_reason_error_string(err); | |
127 | |
128 return false; | |
129 } | |
130 | |
131 return true; | |
132 } | |
133 | |
134 bool Authenticator::TestOneMasterKey(const string &master_key_file, | |
135 const string &hashed_password) const { | |
136 if (system_salt_.empty()) { | |
137 LOG(ERROR) << "System salt not loaded."; | |
138 return false; | |
139 } | |
140 | |
141 Blob cipher_text; | |
142 if (!LoadFileBytes(master_key_file, &cipher_text)) { | |
143 LOG(ERROR) << "Error loading master key from '" << master_key_file << "'"; | |
144 return false; | |
145 } | |
146 | |
147 unsigned int header_size = kOpenSSLMagic.length() + PKCS5_SALT_LEN; | |
148 if (cipher_text.size() <= header_size) { | |
149 LOG(ERROR) << "Master key file too short: '" << master_key_file << "'"; | |
150 return false; | |
151 } | |
152 | |
153 string magic(cipher_text.begin(), | |
154 cipher_text.begin() + kOpenSSLMagic.length()); | |
155 if (magic != kOpenSSLMagic) { | |
156 LOG(ERROR) << "Invalid magic in master key file: '" << master_key_file | |
157 << "'"; | |
158 return false; | |
159 } | |
160 | |
161 Blob salt(cipher_text.begin() + kOpenSSLMagic.length(), | |
162 cipher_text.begin() + header_size); | |
163 | |
164 string passphrase = WrapHashedPassword(master_key_file + ".salt", | |
165 hashed_password); | |
166 | |
167 cipher_text.erase(cipher_text.begin(), cipher_text.begin() + header_size); | |
168 return TestDecrypt(passphrase, salt, cipher_text); | |
169 } | |
170 | |
171 bool Authenticator::TestAllMasterKeys(const Credentials &credentials) const { | |
172 #ifdef CHROMEOS_PAM_LOCALACCOUNT | |
173 if (credentials.IsLocalAccount()) { | |
174 LOG(WARNING) << "Logging in with local account credentials."; | |
175 return true; | |
176 } | |
177 #endif | |
178 | |
179 if (system_salt_.empty()) { | |
180 LOG(ERROR) << "System salt not loaded."; | |
181 return false; | |
182 } | |
183 | |
184 string user_path(PathAppend(shadow_root_, | |
185 credentials.GetObfuscatedUsername(system_salt_))); | |
186 string weak_hash(credentials.GetPasswordWeakHash(system_salt_)); | |
187 char index_str[5]; | |
188 | |
189 // Test against all of the master keys (master.0, master.1, ...) | |
190 for (int i = 0; /* loop forever */ ; ++i) { | |
191 string master_key_file(PathAppend(user_path, "master.")); | |
192 if (0 == snprintf(index_str, sizeof(index_str), "%i", i)) | |
193 return false; | |
194 master_key_file.append(index_str); | |
195 | |
196 if (0 != access(master_key_file.c_str(), R_OK)) { | |
197 // master.N can't be read, assume we're done and have failed | |
198 break; | |
199 } | |
200 | |
201 if (TestOneMasterKey(master_key_file, weak_hash)) { | |
202 // decrypted ok, return success | |
203 return true; | |
204 } | |
205 } | |
206 | |
207 return false; | |
208 } | |
209 | |
210 } // namespace pam_offline | |
OLD | NEW |