/* * 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 #include #include #include #include #include #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 { return FAILURE_V(unique_fd(), "open('%s', output=%d) failed with errno=%d", filename, output, errno); } } std::vector loadFile(const int fd) { constexpr size_t kChunkSize = 256; std::vector 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) { decltype(result) empty; return FAILURE_V(empty, "error reading from a file, errno=%d", errno); } 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) { return FAILURE_V(false, "error writing to a file, errno=%d", errno); } else { return FAILURE_V(false, "`write` returned zero, size=%zu, errno=%d", size, errno); } } return true; } template 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 std::vector& operator<<(std::vector& v, const T& x) { const uint8_t* x8 = reinterpret_cast(&x); v.insert(v.end(), x8, x8 + sizeof(x)); return v; } std::vector& operator<<(std::vector& 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 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 data = serialize(); saveFile(file.get(), data.data(), data.size()); } } std::vector Storage::serialize() const { std::vector 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 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& enrollmentIds) { for (const int enrollmentId : enrollmentIds) { mEnrollments.erase(enrollmentId); } save(); } std::tuple 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, static_cast(inMs), {}}; } break; case LockOut::State::PERMANENT: return {AuthResult::LOCKED_OUT_PERMANENT, 0, {}}; } if (mEnrollments.count(enrollmentId) > 0) { mLockOut.state = LockOut::State::NO; AuthToken tok; tok.userId = mSecureUserId; tok.authenticatorId = mAuthId; return {AuthResult::OK, 0, tok}; } 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