| /* |
| * Copyright (C) 2022 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 <fcntl.h> |
| #include <inttypes.h> |
| #include <unistd.h> |
| #include <cstdio> |
| #include <android-base/unique_fd.h> |
| #include <log/log.h> |
| #include "storage.h" |
| |
| namespace aidl::android::hardware::biometrics::fingerprint { |
| |
| namespace { |
| using ::android::base::unique_fd; |
| |
| constexpr uint32_t kFileSignature = 0x46507261; |
| |
| unique_fd openFile(const int32_t sensorId, const int32_t userId, const bool output) { |
| char filename[64]; |
| ::snprintf(filename, sizeof(filename), "/data/vendor_de/%d/fpdata/sensor%d.bin", |
| userId, sensorId); |
| |
| int fd; |
| if (output) { |
| fd = ::open(filename, O_CLOEXEC | O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); |
| } else { |
| fd = ::open(filename, O_CLOEXEC | O_RDONLY); |
| } |
| |
| if (fd >= 0) { |
| return unique_fd(fd); |
| } else { |
| ALOGE("%s:%d open('%s', output=%d) failed with errno=%d", |
| __func__, __LINE__, filename, output, errno); |
| return {}; |
| } |
| } |
| |
| std::vector<uint8_t> loadFile(const int fd) { |
| constexpr size_t kChunkSize = 256; |
| std::vector<uint8_t> result; |
| size_t size = 0; |
| |
| while (true) { |
| result.resize(size + kChunkSize); |
| const int n = TEMP_FAILURE_RETRY(::read(fd, &result[size], kChunkSize)); |
| if (n > 0) { |
| size += n; |
| } else if (n < 0) { |
| ALOGE("%s:%d error reading from a file, errno=%d", |
| __func__, __LINE__, errno); |
| return {}; |
| } else { |
| result.resize(size); |
| return result; |
| } |
| } |
| } |
| |
| bool saveFile(const int fd, const uint8_t* i, size_t size) { |
| while (size > 0) { |
| const int n = TEMP_FAILURE_RETRY(::write(fd, i, size)); |
| if (n > 0) { |
| i += n; |
| size -= n; |
| } else if (n < 0) { |
| ALOGE("%s:%d error writing to a file, errno=%d", |
| __func__, __LINE__, errno); |
| return false; |
| } else { |
| ALOGE("%s:%d `write` returned zero, size=%zu, errno=%d", |
| __func__, __LINE__, size, errno); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| template <class T> bool loadT(const uint8_t** i, const uint8_t* end, T* x) { |
| const uint8_t* p = *i; |
| if ((p + sizeof(*x)) <= end) { |
| memcpy(x, p, sizeof(*x)); |
| *i = p + sizeof(*x); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| template <class T> std::vector<uint8_t>& operator<<(std::vector<uint8_t>& v, const T& x) { |
| const uint8_t* x8 = reinterpret_cast<const uint8_t*>(&x); |
| v.insert(v.end(), x8, x8 + sizeof(x)); |
| return v; |
| } |
| |
| std::vector<uint8_t>& operator<<(std::vector<uint8_t>& v, const uint8_t x) { |
| v.push_back(x); |
| return v; |
| } |
| |
| } // namespace |
| |
| Storage::Storage(const int32_t sensorId, const int32_t userId) |
| : mSensorId(sensorId), mUserId(userId) { |
| unique_fd file(openFile(sensorId, mUserId, false)); |
| if (!file.ok()) { |
| return; |
| } |
| |
| const std::vector<uint8_t> data = loadFile(file.get()); |
| const uint8_t* i = data.data(); |
| const uint8_t* const end = i + data.size(); |
| |
| uint32_t signature; |
| if (!loadT(&i, end, &signature)) { |
| ALOGE("%s:%d", __func__, __LINE__); |
| return; |
| } |
| if (signature != kFileSignature) { |
| ALOGE("%s:%d", __func__, __LINE__); |
| return; |
| } |
| if (!loadT(&i, end, &mAuthId)) { |
| ALOGE("%s:%d", __func__, __LINE__); |
| return; |
| } |
| if (!loadT(&i, end, &mSecureUserId)) { |
| ALOGE("%s:%d", __func__, __LINE__); |
| return; |
| } |
| uint8_t nEnrollments; |
| if (!loadT(&i, end, &nEnrollments)) { |
| ALOGE("%s:%d", __func__, __LINE__); |
| return; |
| } |
| for (; nEnrollments > 0; --nEnrollments) { |
| int32_t enrollmentId; |
| if (loadT(&i, end, &enrollmentId)) { |
| mEnrollments.insert(enrollmentId); |
| } else { |
| ALOGE("%s:%d", __func__, __LINE__); |
| return; |
| } |
| } |
| } |
| |
| void Storage::save() const { |
| unique_fd file(openFile(mSensorId, mUserId, true)); |
| if (file.ok()) { |
| const std::vector<uint8_t> data = serialize(); |
| saveFile(file.get(), data.data(), data.size()); |
| } |
| } |
| |
| std::vector<uint8_t> Storage::serialize() const { |
| std::vector<uint8_t> result; |
| |
| result << kFileSignature << mAuthId << mSecureUserId << uint8_t(mEnrollments.size()); |
| for (const int32_t enrollmentId : mEnrollments) { |
| result << enrollmentId; |
| } |
| |
| return result; |
| } |
| |
| int64_t Storage::invalidateAuthenticatorId(const int64_t newAuthId) { |
| mAuthId = newAuthId; |
| save(); |
| return newAuthId; |
| } |
| |
| std::vector<int32_t> Storage::enumerateEnrollments() const { |
| return {mEnrollments.begin(), mEnrollments.end()}; |
| } |
| |
| bool Storage::enroll(const int enrollmentId, |
| const int64_t secureUserId, |
| const int64_t newAuthId) { |
| if (mEnrollments.insert(enrollmentId).second) { |
| mSecureUserId = secureUserId; |
| mAuthId = newAuthId; |
| save(); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| void Storage::removeEnrollments(const std::vector<int32_t>& enrollmentIds) { |
| for (const int enrollmentId : enrollmentIds) { |
| mEnrollments.erase(enrollmentId); |
| } |
| save(); |
| } |
| |
| std::pair<Storage::AuthResult, int64_t> Storage::authenticate(const int32_t enrollmentId) { |
| const auto now = std::chrono::steady_clock::now(); |
| |
| switch (mLockOut.state) { |
| default: |
| case LockOut::State::NO: |
| break; |
| |
| case LockOut::State::TIMED: |
| case LockOut::State::TIMED_LOCKED: |
| if (mLockOut.nextAttempt > now) { |
| mLockOut.state = LockOut::State::TIMED_LOCKED; |
| const int64_t inMs = |
| std::chrono::duration_cast< |
| std::chrono::milliseconds>(mLockOut.nextAttempt - now).count(); |
| return {AuthResult::LOCKED_OUT_TIMED, inMs}; |
| } |
| break; |
| |
| case LockOut::State::PERMANENT: |
| return {AuthResult::LOCKED_OUT_PERMANENT, 0}; |
| } |
| |
| if (mEnrollments.count(enrollmentId) > 0) { |
| mLockOut.state = LockOut::State::NO; |
| return {AuthResult::OK, mSecureUserId}; |
| } else { |
| const int failedAttempts = |
| (mLockOut.state == LockOut::State::NO) |
| ? 1 : ++mLockOut.failedAttempts; |
| |
| if (failedAttempts >= 10) { |
| mLockOut.state = LockOut::State::PERMANENT; |
| return {AuthResult::LOCKED_OUT_PERMANENT, 0}; |
| } |
| |
| mLockOut.state = LockOut::State::TIMED; |
| if (failedAttempts >= 5) { |
| mLockOut.nextAttempt = now + std::chrono::seconds(10); |
| mLockOut.expiration = now + std::chrono::minutes(10); |
| } else if (failedAttempts >= 3) { |
| mLockOut.nextAttempt = now + std::chrono::seconds(3); |
| mLockOut.expiration = now + std::chrono::minutes(1); |
| } else { |
| mLockOut.nextAttempt = now + std::chrono::milliseconds(500); |
| mLockOut.expiration = now + std::chrono::seconds(10); |
| } |
| |
| return {AuthResult::FAILED, 0}; |
| } |
| } |
| |
| void Storage::resetLockout() { |
| mLockOut.state = LockOut::State::NO; |
| } |
| |
| bool Storage::checkIfLockoutCleared() { |
| if (mLockOut.state != LockOut::State::TIMED_LOCKED) { |
| return false; |
| } |
| |
| const auto now = std::chrono::steady_clock::now(); |
| if (now > mLockOut.expiration) { |
| mLockOut.state = LockOut::State::NO; |
| return true; |
| } else if (now > mLockOut.nextAttempt) { |
| mLockOut.state = LockOut::State::TIMED; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| } // namespace aidl::android::hardware::biometrics::fingerprint |