blob: d6765a3d3bf9f867f368020566cc4c604165a372 [file] [log] [blame]
Zhiyi Zhangdefa9592017-02-21 10:56:22 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
tylerliu1f2c3cf2021-11-15 15:43:02 -08002/*
Davide Pesavento9510c912024-02-25 17:50:05 -05003 * Copyright (c) 2017-2024, Regents of the University of California.
Zhiyi Zhangdefa9592017-02-21 10:56:22 -08004 *
5 * This file is part of ndncert, a certificate management system based on NDN.
6 *
7 * ndncert is free software: you can redistribute it and/or modify it under the terms
8 * of the GNU General Public License as published by the Free Software Foundation, either
9 * version 3 of the License, or (at your option) any later version.
10 *
11 * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 *
15 * You should have received copies of the GNU General Public License along with
16 * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * See AUTHORS.md for complete list of ndncert authors and contributors.
19 */
20
21#include "challenge-email.hpp"
Davide Pesavento9510c912024-02-25 17:50:05 -050022
23#include <ndn-cxx/util/logger.hpp>
24
Zhiyi Zhang523f0c22020-09-29 14:19:20 -070025#include <regex>
tylerliu1f480be2020-11-10 13:02:53 -080026#include <boost/process.hpp>
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080027
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080028namespace ndncert {
29
Zhiyi Zhangd61b4a82020-10-10 15:18:43 -070030NDN_LOG_INIT(ndncert.challenge.email);
Zhiyi Zhang36706832019-07-04 21:33:03 -070031NDNCERT_REGISTER_CHALLENGE(ChallengeEmail, "email");
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080032
33const std::string ChallengeEmail::NEED_CODE = "need-code";
34const std::string ChallengeEmail::WRONG_CODE = "wrong-code";
Zhiyi Zhang46049832020-09-28 17:08:12 -070035const std::string ChallengeEmail::PARAMETER_KEY_EMAIL = "email";
36const std::string ChallengeEmail::PARAMETER_KEY_CODE = "code";
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080037
38ChallengeEmail::ChallengeEmail(const std::string& scriptPath,
39 const size_t& maxAttemptTimes,
40 const time::seconds secretLifetime)
Davide Pesavento0dc02012021-11-23 22:55:03 -050041 : ChallengeModule("email", maxAttemptTimes, secretLifetime)
42 , m_sendEmailScript(scriptPath)
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080043{
44}
45
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070046// For CA
Zhiyi Zhangaafc55e2020-09-28 17:54:48 -070047std::tuple<ErrorCode, std::string>
Zhiyi Zhang32d4b4e2020-10-28 22:10:49 -070048ChallengeEmail::handleChallengeRequest(const Block& params, ca::RequestState& request)
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080049{
Suyong Won44d0cce2020-05-10 04:07:43 -070050 params.parse();
Zhiyi Zhang8da54d62019-11-21 00:03:05 -080051 auto currentTime = time::system_clock::now();
Davide Pesavento6866b902024-12-22 23:11:26 -050052
tylerliu7b9185c2020-11-24 12:15:18 -080053 if (request.status == Status::BEFORE_CHALLENGE) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070054 // for the first time, init the challenge
tylerliu50d679e2020-10-14 14:08:39 -070055 std::string emailAddress = readString(params.get(tlv::ParameterValue));
tylerliu7b9185c2020-11-24 12:15:18 -080056 auto lastComponentRequested = readString(request.cert.getIdentity().get(-1));
Zhiyi Zhang46049832020-09-28 17:08:12 -070057 if (lastComponentRequested != emailAddress) {
Davide Pesavento6866b902024-12-22 23:11:26 -050058 NDN_LOG_TRACE("Email and requested name do not match: email=" << emailAddress
59 << " requested=" << lastComponentRequested);
Zhiyi Zhang5f749a22019-06-12 17:02:33 -070060 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070061 std::string emailCode = generateSecretCode();
62 JsonSection secretJson;
Zhiyi Zhang46049832020-09-28 17:08:12 -070063 secretJson.add(PARAMETER_KEY_CODE, emailCode);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070064 // send out the email
65 sendEmail(emailAddress, emailCode, request);
Davide Pesavento6866b902024-12-22 23:11:26 -050066 NDN_LOG_TRACE("Secret for request " << ndn::toHex(request.requestId) << " is " << emailCode);
Davide Pesavento0dc02012021-11-23 22:55:03 -050067 return returnWithNewChallengeStatus(request, NEED_CODE, std::move(secretJson),
68 m_maxAttemptTimes, m_secretLifetime);
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080069 }
Davide Pesavento6866b902024-12-22 23:11:26 -050070
tylerliu7b9185c2020-11-24 12:15:18 -080071 if (request.challengeState) {
72 if (request.challengeState->challengeStatus == NEED_CODE ||
73 request.challengeState->challengeStatus == WRONG_CODE) {
Davide Pesavento6866b902024-12-22 23:11:26 -050074 NDN_LOG_TRACE("Challenge status: " << request.challengeState->challengeStatus);
Davide Pesavento397faa92025-01-04 20:56:56 -050075 // the incoming interest should bring the verification code
tylerliu50d679e2020-10-14 14:08:39 -070076 std::string givenCode = readString(params.get(tlv::ParameterValue));
tylerliu7b9185c2020-11-24 12:15:18 -080077 auto secret = request.challengeState->secrets;
Zhiyi Zhanga749f442020-09-29 17:19:51 -070078 // check if run out of time
tylerliu7b9185c2020-11-24 12:15:18 -080079 if (currentTime - request.challengeState->timestamp >= m_secretLifetime) {
Davide Pesavento6866b902024-12-22 23:11:26 -050080 NDN_LOG_TRACE("Secret expired");
Zhiyi Zhanga749f442020-09-29 17:19:51 -070081 return returnWithError(request, ErrorCode::OUT_OF_TIME, "Secret expired.");
82 }
83 // check if provided secret is correct
84 if (givenCode == secret.get<std::string>(PARAMETER_KEY_CODE)) {
85 // the code is correct
Davide Pesavento6866b902024-12-22 23:11:26 -050086 NDN_LOG_TRACE("Secret is correct, challenge succeeded");
Zhiyi Zhanga749f442020-09-29 17:19:51 -070087 return returnWithSuccess(request);
88 }
89 // otherwise, check remaining attempt times
tylerliu7b9185c2020-11-24 12:15:18 -080090 if (request.challengeState->remainingTries > 1) {
91 auto remainTime = m_secretLifetime - (currentTime - request.challengeState->timestamp);
Davide Pesavento6866b902024-12-22 23:11:26 -050092 NDN_LOG_TRACE("Wrong secret, remaining tries = " << request.challengeState->remainingTries - 1);
Zhiyi Zhanga749f442020-09-29 17:19:51 -070093 return returnWithNewChallengeStatus(request, WRONG_CODE, std::move(secret),
tylerliu7b9185c2020-11-24 12:15:18 -080094 request.challengeState->remainingTries - 1,
Zhiyi Zhanga749f442020-09-29 17:19:51 -070095 time::duration_cast<time::seconds>(remainTime));
96 }
97 else {
Davide Pesavento6866b902024-12-22 23:11:26 -050098 NDN_LOG_TRACE("Wrong secret, no tries remaining");
Tianyuan Yu13aac732022-03-03 20:59:54 -080099 return returnWithError(request, ErrorCode::OUT_OF_TRIES, "Ran out of tries.");
Zhiyi Zhanga749f442020-09-29 17:19:51 -0700100 }
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800101 }
102 }
Davide Pesavento6866b902024-12-22 23:11:26 -0500103
104 return returnWithError(request, ErrorCode::INVALID_PARAMETER, "Unexpected challenge status.");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700105}
106
107// For Client
tylerliu40226332020-11-11 15:37:16 -0800108std::multimap<std::string, std::string>
Zhiyi Zhang46049832020-09-28 17:08:12 -0700109ChallengeEmail::getRequestedParameterList(Status status, const std::string& challengeStatus)
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700110{
tylerliu40226332020-11-11 15:37:16 -0800111 std::multimap<std::string, std::string> result;
Davide Pesavento6f1a2ab2022-03-17 03:57:21 -0400112 if (status == Status::BEFORE_CHALLENGE && challengeStatus.empty()) {
tylerliu40226332020-11-11 15:37:16 -0800113 result.emplace(PARAMETER_KEY_EMAIL, "Please input your email address");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700114 }
Zhiyi Zhang48f23782020-09-28 12:11:24 -0700115 else if (status == Status::CHALLENGE && challengeStatus == NEED_CODE) {
tylerliu40226332020-11-11 15:37:16 -0800116 result.emplace(PARAMETER_KEY_CODE, "Please input your verification code");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700117 }
Zhiyi Zhang48f23782020-09-28 12:11:24 -0700118 else if (status == Status::CHALLENGE && challengeStatus == WRONG_CODE) {
tylerliu40226332020-11-11 15:37:16 -0800119 result.emplace(PARAMETER_KEY_CODE, "Incorrect code, please try again");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700120 }
121 else {
Davide Pesavento6866b902024-12-22 23:11:26 -0500122 NDN_THROW(std::runtime_error("Unexpected challenge status"));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700123 }
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800124 return result;
125}
126
Suyong Won19fba4d2020-05-09 13:39:46 -0700127Block
Zhiyi Zhangba8e45e2020-11-03 08:55:32 -0800128ChallengeEmail::genChallengeRequestTLV(Status status, const std::string& challengeStatus,
tylerliuf2e6bb52020-12-13 13:23:05 -0800129 const std::multimap<std::string, std::string>& params)
Suyong Won19fba4d2020-05-09 13:39:46 -0700130{
tylerliu1f480be2020-11-10 13:02:53 -0800131 Block request(tlv::EncryptedPayload);
Zhiyi Zhang46049832020-09-28 17:08:12 -0700132 if (status == Status::BEFORE_CHALLENGE) {
tylerliu40226332020-11-11 15:37:16 -0800133 if (params.size() != 1 || params.find(PARAMETER_KEY_EMAIL) == params.end()) {
Davide Pesavento6866b902024-12-22 23:11:26 -0500134 NDN_THROW(std::runtime_error("Wrong parameter provided"));
Zhiyi Zhang46049832020-09-28 17:08:12 -0700135 }
Davide Pesavento0dc02012021-11-23 22:55:03 -0500136 request.push_back(ndn::makeStringBlock(tlv::SelectedChallenge, CHALLENGE_TYPE));
137 request.push_back(ndn::makeStringBlock(tlv::ParameterKey, PARAMETER_KEY_EMAIL));
138 request.push_back(ndn::makeStringBlock(tlv::ParameterValue, params.find(PARAMETER_KEY_EMAIL)->second));
Suyong Won19fba4d2020-05-09 13:39:46 -0700139 }
Zhiyi Zhang46049832020-09-28 17:08:12 -0700140 else if (status == Status::CHALLENGE && (challengeStatus == NEED_CODE || challengeStatus == WRONG_CODE)) {
tylerliu40226332020-11-11 15:37:16 -0800141 if (params.size() != 1 || params.find(PARAMETER_KEY_CODE) == params.end()) {
Davide Pesavento6866b902024-12-22 23:11:26 -0500142 NDN_THROW(std::runtime_error("Wrong parameter provided"));
Zhiyi Zhang46049832020-09-28 17:08:12 -0700143 }
Davide Pesavento0dc02012021-11-23 22:55:03 -0500144 request.push_back(ndn::makeStringBlock(tlv::SelectedChallenge, CHALLENGE_TYPE));
145 request.push_back(ndn::makeStringBlock(tlv::ParameterKey, PARAMETER_KEY_CODE));
146 request.push_back(ndn::makeStringBlock(tlv::ParameterValue, params.find(PARAMETER_KEY_CODE)->second));
Suyong Won19fba4d2020-05-09 13:39:46 -0700147 }
148 else {
Davide Pesavento6866b902024-12-22 23:11:26 -0500149 NDN_THROW(std::runtime_error("Unexpected challenge status"));
Suyong Won19fba4d2020-05-09 13:39:46 -0700150 }
Suyong Won44d0cce2020-05-10 04:07:43 -0700151 request.encode();
Suyong Won19fba4d2020-05-09 13:39:46 -0700152 return request;
153}
154
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800155bool
156ChallengeEmail::isValidEmailAddress(const std::string& emailAddress)
157{
Zhiyi Zhang8ce677b2018-07-13 14:44:06 -0700158 const std::string pattern = R"_REGEX_((^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9\-\.]+$))_REGEX_";
159 static const std::regex emailPattern(pattern);
160 return std::regex_match(emailAddress, emailPattern);
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800161}
162
163void
Zhiyi Zhang576aad12017-10-03 15:41:53 -0700164ChallengeEmail::sendEmail(const std::string& emailAddress, const std::string& secret,
Zhiyi Zhang32d4b4e2020-10-28 22:10:49 -0700165 const ca::RequestState& request) const
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800166{
Zhiyi Zhang576aad12017-10-03 15:41:53 -0700167 std::string command = m_sendEmailScript;
Zhiyi Zhangba8e45e2020-11-03 08:55:32 -0800168 command += " \"" + emailAddress + "\" \"" + secret + "\" \"" +
tylerliu7b9185c2020-11-24 12:15:18 -0800169 request.caPrefix.toUri() + "\" \"" +
170 request.cert.getName().toUri() + "\"";
tylerliu1f480be2020-11-10 13:02:53 -0800171 boost::process::child child(command);
172 child.wait();
173 if (child.exit_code() != 0) {
Davide Pesavento6866b902024-12-22 23:11:26 -0500174 NDN_LOG_ERROR("Email sending script " + m_sendEmailScript + " failed");
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800175 }
Tianyuan Yu13aac732022-03-03 20:59:54 -0800176 else {
Davide Pesavento6866b902024-12-22 23:11:26 -0500177 NDN_LOG_TRACE("Email sending script " + m_sendEmailScript + " succeeded");
Tianyuan Yu13aac732022-03-03 20:59:54 -0800178 }
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800179}
180
Zhiyi Zhange4891b72020-10-10 15:11:57 -0700181} // namespace ndncert