1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //-----------------------------------------------------------------------------
4 
5 namespace System.ServiceModel.Security
6 {
7     using System.Globalization;
8     using System.Runtime;
9     using System.Xml;
10 
11     sealed class SecurityTimestamp
12     {
13         const string DefaultFormat = "yyyy-MM-ddTHH:mm:ss.fffZ";
14         //                            012345678901234567890123
15 
16         internal static readonly TimeSpan defaultTimeToLive = SecurityProtocolFactory.defaultTimestampValidityDuration;
17         char[] computedCreationTimeUtc;
18         char[] computedExpiryTimeUtc;
19         DateTime creationTimeUtc;
20         DateTime expiryTimeUtc;
21         readonly string id;
22         readonly string digestAlgorithm;
23         readonly byte[] digest;
24 
SecurityTimestamp(DateTime creationTimeUtc, DateTime expiryTimeUtc, string id)25         public SecurityTimestamp(DateTime creationTimeUtc, DateTime expiryTimeUtc, string id)
26             : this(creationTimeUtc, expiryTimeUtc, id, null, null)
27         {
28         }
29 
SecurityTimestamp(DateTime creationTimeUtc, DateTime expiryTimeUtc, string id, string digestAlgorithm, byte[] digest)30         internal SecurityTimestamp(DateTime creationTimeUtc, DateTime expiryTimeUtc, string id, string digestAlgorithm, byte[] digest)
31         {
32             Fx.Assert(creationTimeUtc.Kind == DateTimeKind.Utc, "creation time must be in UTC");
33             Fx.Assert(expiryTimeUtc.Kind == DateTimeKind.Utc, "expiry time must be in UTC");
34 
35             if (creationTimeUtc > expiryTimeUtc)
36             {
37                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new ArgumentOutOfRangeException("recordedExpiryTime", SR.GetString(SR.CreationTimeUtcIsAfterExpiryTime)));
38             }
39 
40             this.creationTimeUtc = creationTimeUtc;
41             this.expiryTimeUtc = expiryTimeUtc;
42             this.id = id;
43 
44             this.digestAlgorithm = digestAlgorithm;
45             this.digest = digest;
46         }
47 
48         public DateTime CreationTimeUtc
49         {
50             get
51             {
52                 return this.creationTimeUtc;
53             }
54         }
55 
56         public DateTime ExpiryTimeUtc
57         {
58             get
59             {
60                 return this.expiryTimeUtc;
61             }
62         }
63 
64         public string Id
65         {
66             get
67             {
68                 return this.id;
69             }
70         }
71 
72         public string DigestAlgorithm
73         {
74             get
75             {
76                 return this.digestAlgorithm;
77             }
78         }
79 
GetDigest()80         internal byte[] GetDigest()
81         {
82             return this.digest;
83         }
84 
GetCreationTimeChars()85         internal char[] GetCreationTimeChars()
86         {
87             if (this.computedCreationTimeUtc == null)
88             {
89                 this.computedCreationTimeUtc = ToChars(ref this.creationTimeUtc);
90             }
91             return this.computedCreationTimeUtc;
92         }
93 
GetExpiryTimeChars()94         internal char[] GetExpiryTimeChars()
95         {
96             if (this.computedExpiryTimeUtc == null)
97             {
98                 this.computedExpiryTimeUtc = ToChars(ref this.expiryTimeUtc);
99             }
100             return this.computedExpiryTimeUtc;
101         }
102 
ToChars(ref DateTime utcTime)103         static char[] ToChars(ref DateTime utcTime)
104         {
105             char[] buffer = new char[DefaultFormat.Length];
106             int offset = 0;
107 
108             ToChars(utcTime.Year, buffer, ref offset, 4);
109             buffer[offset++] = '-';
110 
111             ToChars(utcTime.Month, buffer, ref offset, 2);
112             buffer[offset++] = '-';
113 
114             ToChars(utcTime.Day, buffer, ref offset, 2);
115             buffer[offset++] = 'T';
116 
117             ToChars(utcTime.Hour, buffer, ref offset, 2);
118             buffer[offset++] = ':';
119 
120             ToChars(utcTime.Minute, buffer, ref offset, 2);
121             buffer[offset++] = ':';
122 
123             ToChars(utcTime.Second, buffer, ref offset, 2);
124             buffer[offset++] = '.';
125 
126             ToChars(utcTime.Millisecond, buffer, ref offset, 3);
127             buffer[offset++] = 'Z';
128 
129             return buffer;
130         }
131 
ToChars(int n, char[] buffer, ref int offset, int count)132         static void ToChars(int n, char[] buffer, ref int offset, int count)
133         {
134             for (int i = offset + count - 1; i >= offset; i--)
135             {
136                 buffer[i] = (char)('0' + (n % 10));
137                 n /= 10;
138             }
139             Fx.Assert(n == 0, "Overflow in encoding timestamp field");
140             offset += count;
141         }
142 
ToString()143         public override string ToString()
144         {
145             return string.Format(
146                 CultureInfo.InvariantCulture,
147                 "SecurityTimestamp: Id={0}, CreationTimeUtc={1}, ExpirationTimeUtc={2}",
148                 this.Id,
149                 XmlConvert.ToString(this.CreationTimeUtc, XmlDateTimeSerializationMode.RoundtripKind),
150                 XmlConvert.ToString(this.ExpiryTimeUtc, XmlDateTimeSerializationMode.RoundtripKind));
151         }
152 
153         /// <summary>
154         /// Internal method that checks if the timestamp is fresh with respect to the
155         /// timeToLive and allowedClockSkew values passed in.
156         /// Throws if the timestamp is stale.
157         /// </summary>
158         /// <param name="timeToLive"></param>
159         /// <param name="allowedClockSkew"></param>
ValidateRangeAndFreshness(TimeSpan timeToLive, TimeSpan allowedClockSkew)160         internal void ValidateRangeAndFreshness(TimeSpan timeToLive, TimeSpan allowedClockSkew)
161         {
162             // Check that the creation time is less than expiry time
163             if (this.CreationTimeUtc >= this.ExpiryTimeUtc)
164             {
165                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.TimeStampHasCreationAheadOfExpiry, this.CreationTimeUtc.ToString(DefaultFormat, CultureInfo.CurrentCulture), this.ExpiryTimeUtc.ToString(DefaultFormat, CultureInfo.CurrentCulture))));
166             }
167 
168             ValidateFreshness(timeToLive, allowedClockSkew);
169         }
170 
ValidateFreshness(TimeSpan timeToLive, TimeSpan allowedClockSkew)171         internal void ValidateFreshness(TimeSpan timeToLive, TimeSpan allowedClockSkew)
172         {
173             DateTime now = DateTime.UtcNow;
174             // check that the message has not expired
175             if (this.ExpiryTimeUtc <= TimeoutHelper.Subtract(now, allowedClockSkew))
176             {
177                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.TimeStampHasExpiryTimeInPast, this.ExpiryTimeUtc.ToString(DefaultFormat, CultureInfo.CurrentCulture), now.ToString(DefaultFormat, CultureInfo.CurrentCulture), allowedClockSkew)));
178             }
179 
180             // check that creation time is not in the future (modulo clock skew)
181             if (this.CreationTimeUtc >= TimeoutHelper.Add(now, allowedClockSkew))
182             {
183                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.TimeStampHasCreationTimeInFuture, this.CreationTimeUtc.ToString(DefaultFormat, CultureInfo.CurrentCulture), now.ToString(DefaultFormat, CultureInfo.CurrentCulture), allowedClockSkew)));
184             }
185 
186             // check that the creation time is not more than timeToLive in the past
187             if (this.CreationTimeUtc <= TimeoutHelper.Subtract(now, TimeoutHelper.Add(timeToLive, allowedClockSkew)))
188             {
189                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.TimeStampWasCreatedTooLongAgo, this.CreationTimeUtc.ToString(DefaultFormat, CultureInfo.CurrentCulture), now.ToString(DefaultFormat, CultureInfo.CurrentCulture), timeToLive, allowedClockSkew)));
190             }
191 
192             // this is a fresh timestamp
193         }
194     }
195 }
196