blob: b65d331a9f45aacfdac18c958db48c91c4f09946 [file] [log] [blame]
Zhiyi Zhangdefa9592017-02-21 10:56:22 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Zhiyi Zhang8ce677b2018-07-13 14:44:06 -07003 * Copyright (c) 2017-2018, 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"
22#include "../logging.hpp"
Zhiyi Zhang8ce677b2018-07-13 14:44:06 -070023#include <regex>
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080024
25namespace ndn {
26namespace ndncert {
27
28_LOG_INIT(ndncert.ChallengeEmail);
29
30NDNCERT_REGISTER_CHALLENGE(ChallengeEmail, "Email");
31
32const std::string ChallengeEmail::NEED_CODE = "need-code";
33const std::string ChallengeEmail::WRONG_CODE = "wrong-code";
Zhiyi Zhanga9bda732017-05-20 22:58:55 -070034
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080035const std::string ChallengeEmail::FAILURE_INVALID_EMAIL = "failure-invalid-email";
Zhiyi Zhanga9bda732017-05-20 22:58:55 -070036const std::string ChallengeEmail::FAILURE_TIMEOUT = "timeout";
37const std::string ChallengeEmail::FAILURE_MAXRETRY = "max-retry";
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080038
39const std::string ChallengeEmail::JSON_EMAIL = "email";
40const std::string ChallengeEmail::JSON_CODE_TP = "code-timepoint";
41const std::string ChallengeEmail::JSON_CODE = "code";
42const std::string ChallengeEmail::JSON_ATTEMPT_TIMES = "attempt-times";
43
44ChallengeEmail::ChallengeEmail(const std::string& scriptPath,
45 const size_t& maxAttemptTimes,
46 const time::seconds secretLifetime)
Zhiyi Zhanga9bda732017-05-20 22:58:55 -070047 : ChallengeModule("Email")
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080048 , m_sendEmailScript(scriptPath)
49 , m_maxAttemptTimes(maxAttemptTimes)
50 , m_secretLifetime(secretLifetime)
51{
52}
53
54JsonSection
55ChallengeEmail::processSelectInterest(const Interest& interest, CertificateRequest& request)
56{
57 // interest format: /caName/CA/_SELECT/{"request-id":"id"}/EMAIL/{"Email":"email"}/<signature>
58 JsonSection emailJson = getJsonFromNameComponent(interest.getName(),
59 request.getCaName().size() + 4);
60 std::string emailAddress = emailJson.get<std::string>(JSON_EMAIL);
61 if (!isValidEmailAddress(emailAddress)) {
62 request.setStatus(FAILURE_INVALID_EMAIL);
63 request.setChallengeType(CHALLENGE_TYPE);
Zhiyi Zhanga9bda732017-05-20 22:58:55 -070064 return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_INVALID_EMAIL);
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080065 }
66
67 std::string emailCode = generateSecretCode();
Zhiyi Zhang576aad12017-10-03 15:41:53 -070068 sendEmail(emailAddress, emailCode, request.getCaName().toUri());
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080069
70 request.setStatus(NEED_CODE);
71 request.setChallengeType(CHALLENGE_TYPE);
72 request.setChallengeSecrets(generateStoredSecrets(time::system_clock::now(),
Zhiyi Zhanga9bda732017-05-20 22:58:55 -070073 emailCode, m_maxAttemptTimes));
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080074 return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, NEED_CODE);
75}
76
77JsonSection
78ChallengeEmail::processValidateInterest(const Interest& interest, CertificateRequest& request)
79{
80 // interest format: /caName/CA/_VALIDATION/{"request-id":"id"}/EMAIL/{"code":"code"}/<signature>
81 JsonSection infoJson = getJsonFromNameComponent(interest.getName(), request.getCaName().size() + 4);
82 std::string givenCode = infoJson.get<std::string>(JSON_CODE);
83
84 const auto parsedSecret = parseStoredSecrets(request.getChallengeSecrets());
85 if (time::system_clock::now() - std::get<0>(parsedSecret) >= m_secretLifetime) {
86 // secret expires
87 request.setStatus(FAILURE_TIMEOUT);
88 request.setChallengeSecrets(JsonSection());
Zhiyi Zhanga9bda732017-05-20 22:58:55 -070089 return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_TIMEOUT);
Zhiyi Zhangdefa9592017-02-21 10:56:22 -080090 }
91 else if (givenCode == std::get<1>(parsedSecret)) {
92 request.setStatus(SUCCESS);
93 request.setChallengeSecrets(JsonSection());
94 Name downloadName = genDownloadName(request.getCaName(), request.getRequestId());
95 return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, SUCCESS, downloadName);
96 }
97 else {
98 // check rest attempt times
99 if (std::get<2>(parsedSecret) > 1) {
100 int restAttemptTimes = std::get<2>(parsedSecret) - 1;
101 request.setStatus(WRONG_CODE);
102 request.setChallengeSecrets(generateStoredSecrets(std::get<0>(parsedSecret),
103 std::get<1>(parsedSecret),
104 restAttemptTimes));
105 return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, WRONG_CODE);
106 }
107 else {
108 // run out times
109 request.setStatus(FAILURE_MAXRETRY);
110 request.setChallengeSecrets(JsonSection());
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700111 return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_MAXRETRY);
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800112 }
113 }
114}
115
116std::list<std::string>
117ChallengeEmail::getSelectRequirements()
118{
119 std::list<std::string> result;
120 result.push_back("Please input your email address:");
121 return result;
122}
123
124std::list<std::string>
125ChallengeEmail::getValidateRequirements(const std::string& status)
126{
127 std::list<std::string> result;
128 if (status == NEED_CODE) {
129 result.push_back("Please input your verification code:");
130 }
131 else if (status == WRONG_CODE) {
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700132 result.push_back("Incorrect PIN code, please try again and input your verification code:");
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800133 }
134 return result;
135}
136
137JsonSection
138ChallengeEmail::doGenSelectParamsJson(const std::string& status,
139 const std::list<std::string>& paramList)
140{
141 JsonSection result;
Zhiyi Zhang0a89b722017-04-28 17:56:01 -0700142 BOOST_ASSERT(status == WAIT_SELECTION);
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800143 BOOST_ASSERT(paramList.size() == 1);
144 result.put(JSON_EMAIL, paramList.front());
145 return result;
146}
147
148JsonSection
149ChallengeEmail::doGenValidateParamsJson(const std::string& status,
150 const std::list<std::string>& paramList)
151{
152 JsonSection result;
153 BOOST_ASSERT(paramList.size() == 1);
154 result.put(JSON_CODE, paramList.front());
155 return result;
156}
157
158std::tuple<time::system_clock::TimePoint, std::string, int>
159ChallengeEmail::parseStoredSecrets(const JsonSection& storedSecrets)
160{
161 auto tp = time::fromIsoString(storedSecrets.get<std::string>(JSON_CODE_TP));
162 std::string rightCode= storedSecrets.get<std::string>(JSON_CODE);
163 int attemptTimes = std::stoi(storedSecrets.get<std::string>(JSON_ATTEMPT_TIMES));
164
165 return std::make_tuple(tp, rightCode, attemptTimes);
166}
167
168JsonSection
169ChallengeEmail::generateStoredSecrets(const time::system_clock::TimePoint& tp,
170 const std::string& secretCode, int attempTimes)
171{
172 JsonSection json;
173 json.put(JSON_CODE_TP, time::toIsoString(tp));
174 json.put(JSON_CODE, secretCode);
175 json.put(JSON_ATTEMPT_TIMES, std::to_string(attempTimes));
176 return json;
177}
178
179bool
180ChallengeEmail::isValidEmailAddress(const std::string& emailAddress)
181{
Zhiyi Zhang8ce677b2018-07-13 14:44:06 -0700182 const std::string pattern = R"_REGEX_((^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9\-\.]+$))_REGEX_";
183 static const std::regex emailPattern(pattern);
184 return std::regex_match(emailAddress, emailPattern);
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800185}
186
187void
Zhiyi Zhang576aad12017-10-03 15:41:53 -0700188ChallengeEmail::sendEmail(const std::string& emailAddress, const std::string& secret,
189 const std::string& caName) const
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800190{
Zhiyi Zhang576aad12017-10-03 15:41:53 -0700191 std::string command = m_sendEmailScript;
192 command += " \"" + emailAddress + "\" \"" + secret + "\" \"" + caName + "\"";
193 int result = system(command.c_str());
194 if (result == -1) {
195 _LOG_TRACE("EmailSending Script " + m_sendEmailScript + " fails.");
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800196 }
Zhiyi Zhang576aad12017-10-03 15:41:53 -0700197 _LOG_TRACE("EmailSending Script " + m_sendEmailScript +
198 " was executed successfully with return value" + std::to_string(result) + ".");
Zhiyi Zhangdefa9592017-02-21 10:56:22 -0800199 return;
200}
201
202} // namespace ndncert
203} // namespace ndn