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