This repository was archived by the owner on Dec 12, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathTotp.cs
More file actions
198 lines (178 loc) · 9.09 KB
/
Totp.cs
File metadata and controls
198 lines (178 loc) · 9.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
using System;
using System.Globalization;
namespace OtpSharp
{
/// <summary>
/// Calculate Timed-One-Time-Passwords (TOTP) from a secret key
/// </summary>
/// <remarks>
/// The specifications for this are found in RFC 6238
/// http://tools.ietf.org/html/rfc6238
/// </remarks>
public class Totp : Otp
{
/// <summary>
/// The number of ticks as Measured at Midnight Jan 1st 1970;
/// </summary>
const long unixEpochTicks = 621355968000000000L;
/// <summary>
/// A divisor for converting ticks to seconds
/// </summary>
const long ticksToSeconds = 10000000L;
private readonly int step;
private readonly int totpSize;
private readonly TimeCorrection correctedTime;
/// <summary>
/// Create a TOTP instance
/// </summary>
/// <param name="secretKey">The secret key to use in TOTP calculations</param>
/// <param name="step">The time window step amount to use in calculating time windows. The default is 30 as recommended in the RFC</param>
/// <param name="mode">The hash mode to use</param>
/// <param name="totpSize">The number of digits that the returning TOTP should have. The default is 6.</param>
/// <param name="timeCorrection">If required, a time correction can be specified to compensate of an out of sync local clock</param>
public Totp(byte[] secretKey, int step = 30, OtpHashMode mode = OtpHashMode.Sha1, int totpSize = 6, TimeCorrection timeCorrection = null)
: base(secretKey, mode)
{
VerifyParameters(step, totpSize);
this.step = step;
this.totpSize = totpSize;
// we never null check the corrected time object. Since it's readonly, we'll ensure that it isn't null here and provide neatral functionality in this case.
this.correctedTime = timeCorrection ?? TimeCorrection.UncorrectedInstance;
}
/// <summary>
/// Create a TOTP instance
/// </summary>
/// <param name="secretKey">The secret key to use in TOTP calculations</param>
/// <param name="step">The time window step amount to use in calculating time windows. The default is 30 as recommended in the RFC</param>
/// <param name="mode">The hash mode to use</param>
/// <param name="totpSize">The number of digits that the returning TOTP should have. The default is 6.</param>
/// <param name="timeCorrection">If required, a time correction can be specified to compensate of an out of sync local clock</param>
public Totp(IKeyProvider secretKey, int step = 30, OtpHashMode mode = OtpHashMode.Sha1, int totpSize = 6, TimeCorrection timeCorrection = null)
: base(secretKey, mode)
{
VerifyParameters(step, totpSize);
this.step = step;
this.totpSize = totpSize;
// we never null check the corrected time object. Since it's readonly, we'll ensure that it isn't null here and provide neatral functionality in this case.
this.correctedTime = timeCorrection ?? TimeCorrection.UncorrectedInstance;
}
private static void VerifyParameters(int step, int totpSize)
{
if (!(step > 0))
throw new ArgumentOutOfRangeException("step");
if (!(totpSize > 0))
throw new ArgumentOutOfRangeException("totpSize");
if (!(totpSize <= 10))
throw new ArgumentOutOfRangeException("totpSize");
}
/// <summary>
/// Takes a timestamp and applies correction (if provided) and then computes a TOTP value
/// </summary>
/// <param name="timestamp">The timestamp to use for the TOTP calculation</param>
/// <returns>a TOTP value</returns>
public string ComputeTotp(DateTime timestamp)
{
return ComputeTotpFromSpecificTime(this.correctedTime.GetCorrectedTime(timestamp));
}
/// <summary>
/// Takes a timestamp and computes a TOTP value for corrected UTC now
/// </summary>
/// <remarks>
/// It will be corrected against a corrected UTC time using the provided time correction. If none was provided then simply the current UTC will be used.
/// </remarks>
/// <returns>a TOTP value</returns>
public string ComputeTotp()
{
return this.ComputeTotpFromSpecificTime(this.correctedTime.CorrectedUtcNow);
}
private string ComputeTotpFromSpecificTime(DateTime timestamp)
{
var window = CalculateTimeStepFromTimestamp(timestamp);
return this.Compute(window, this.hashMode);
}
/// <summary>
/// Verify a value that has been provided with the calculated value.
/// </summary>
/// <remarks>
/// It will be corrected against a corrected UTC time using the provided time correction. If none was provided then simply the current UTC will be used.
/// </remarks>
/// <param name="totp">the trial TOTP value</param>
/// <param name="timeStepMatched">
/// This is an output parameter that gives that time step that was used to find a match.
/// This is useful in cases where a TOTP value should only be used once. This value is a unique identifier of the
/// time step (not the value) that can be used to prevent the same step from being used multiple times
/// </param>
/// <param name="window">The window of steps to verify</param>
/// <returns>True if there is a match.</returns>
public bool VerifyTotp(string totp, out long timeStepMatched, VerificationWindow window = null)
{
return this.VerifyTotpForSpecificTime(this.correctedTime.CorrectedUtcNow, totp, window, out timeStepMatched);
}
/// <summary>
/// Verify a value that has been provided with the calculated value
/// </summary>
/// <param name="timestamp">The timestamp to use</param>
/// <param name="totp">the trial TOTP value</param>
/// <param name="timeStepMatched">
/// This is an output parameter that gives that time step that was used to find a match.
/// This is usefule in cases where a TOTP value should only be used once. This value is a unique identifier of the
/// time step (not the value) that can be used to prevent the same step from being used multiple times
/// </param>
/// <param name="window">The window of steps to verify</param>
/// <returns>True if there is a match.</returns>
public bool VerifyTotp(DateTime timestamp, string totp, out long timeStepMatched, VerificationWindow window = null)
{
return this.VerifyTotpForSpecificTime(this.correctedTime.GetCorrectedTime(timestamp), totp, window, out timeStepMatched);
}
private bool VerifyTotpForSpecificTime(DateTime timestamp, string totp, VerificationWindow window, out long timeStepMatched)
{
var initialStep = CalculateTimeStepFromTimestamp(timestamp);
return this.Verify(initialStep, totp, out timeStepMatched, window);
}
/// <summary>
/// Takes a timestamp and calculates a time step
/// </summary>
private long CalculateTimeStepFromTimestamp(DateTime timestamp)
{
var unixTimestamp = (timestamp.Ticks - unixEpochTicks) / ticksToSeconds;
var window = unixTimestamp / (long)this.step;
return window;
}
/// <summary>
/// Remaining seconds in current window based on UtcNow
/// </summary>
/// <remarks>
/// It will be corrected against a corrected UTC time using the provided time correction. If none was provided then simply the current UTC will be used.
/// </remarks>
/// <returns>Number of remaining seconds</returns>
public int RemainingSeconds()
{
return RemainingSecondsForSpecificTime(this.correctedTime.CorrectedUtcNow);
}
/// <summary>
/// Remaining seconds in current window
/// </summary>
/// <param name="timestamp">The timestamp</param>
/// <returns>Number of remaining seconds</returns>
public int RemainingSeconds(DateTime timestamp)
{
return RemainingSecondsForSpecificTime(this.correctedTime.GetCorrectedTime(timestamp));
}
private int RemainingSecondsForSpecificTime(DateTime timestamp)
{
return this.step - (int)(((timestamp.Ticks - unixEpochTicks) / ticksToSeconds) % this.step);
}
/// <summary>
/// Takes a time step and computes a TOTP code
/// </summary>
/// <param name="counter">time step</param>
/// <param name="mode">The hash mode to use</param>
/// <returns>TOTP calculated code</returns>
protected override string Compute(long counter, OtpHashMode mode)
{
var data = KeyUtilities.GetBigEndianBytes(counter);
var otp = this.CalculateOtp(data, mode);
return Digits(otp, this.totpSize);
}
}
}