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
199
200
201
202
203
204
205
206
207
208
209
210
|
//-----------------------------------------------------------------------
// <copyright file="TokenDecryptor.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <license>
// Microsoft Public License (Ms-PL).
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL
// </license>
// <author>This file was subsequently modified by Andrew Arnott.</author>
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.InfoCard {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel.Security;
using System.Xml;
using DotNetOpenAuth.Messaging;
/// <summary>
/// A utility class for decrypting InfoCard tokens.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Decryptor", Justification = "By design")]
internal class TokenDecryptor {
/// <summary>
/// Backing field for the <see cref="Tokens"/> property.
/// </summary>
private List<SecurityToken> tokens;
/// <summary>
/// Initializes a new instance of the <see cref="TokenDecryptor"/> class.
/// </summary>
internal TokenDecryptor() {
this.tokens = new List<SecurityToken>();
StoreName storeName = StoreName.My;
StoreLocation storeLocation = StoreLocation.LocalMachine;
this.AddDecryptionCertificates(storeName, storeLocation);
}
/// <summary>
/// Gets a list of possible decryption certificates, from the store/location set
/// </summary>
/// <remarks>
/// Defaults to localmachine:my (same place SSL certs are)
/// </remarks>
internal List<SecurityToken> Tokens {
get { return this.tokens; }
}
/// <summary>
/// Adds a certificate to the list of certificates to decrypt with.
/// </summary>
/// <param name="certificate">The x509 cert to use for decryption</param>
internal void AddDecryptionCertificate(X509Certificate2 certificate) {
this.Tokens.Add(new X509SecurityToken(certificate));
}
/// <summary>
/// Adds a certificate to the list of certificates to decrypt with.
/// </summary>
/// <param name="storeName">store name of the certificate</param>
/// <param name="storeLocation">store location</param>
/// <param name="thumbprint">thumbprint of the cert to use</param>
internal void AddDecryptionCertificate(StoreName storeName, StoreLocation storeLocation, string thumbprint) {
this.AddDecryptionCertificates(
storeName,
storeLocation,
store => store.Find(X509FindType.FindByThumbprint, thumbprint, true));
}
/// <summary>
/// Adds a store of certificates to the list of certificates to decrypt with.
/// </summary>
/// <param name="storeName">store name of the certificates</param>
/// <param name="storeLocation">store location</param>
internal void AddDecryptionCertificates(StoreName storeName, StoreLocation storeLocation) {
this.AddDecryptionCertificates(storeName, storeLocation, store => store);
}
/// <summary>
/// Decrpyts a security token from an XML EncryptedData
/// </summary>
/// <param name="reader">The encrypted token XML reader.</param>
/// <returns>A byte array of the contents of the encrypted token</returns>
internal byte[] DecryptToken(XmlReader reader) {
Requires.NotNull(reader, "reader");
Contract.Ensures(Contract.Result<byte[]>() != null);
byte[] securityTokenData;
string encryptionAlgorithm;
SecurityKeyIdentifier keyIdentifier;
bool isEmptyElement;
ErrorUtilities.VerifyInternal(reader.IsStartElement(XmlEncryptionStrings.EncryptedData, XmlEncryptionStrings.Namespace), "Expected encrypted token starting XML element was not found.");
reader.Read(); // get started
// if it's not an encryption method, something is dreadfully wrong.
InfoCardErrorUtilities.VerifyInfoCard(reader.IsStartElement(XmlEncryptionStrings.EncryptionMethod, XmlEncryptionStrings.Namespace), InfoCardStrings.EncryptionAlgorithmNotFound);
// Looks good, let's grab the alg.
isEmptyElement = reader.IsEmptyElement;
encryptionAlgorithm = reader.GetAttribute(XmlEncryptionStrings.Algorithm);
reader.Read();
if (!isEmptyElement) {
while (reader.IsStartElement()) {
reader.Skip();
}
reader.ReadEndElement();
}
// get the key identifier
keyIdentifier = WSSecurityTokenSerializer.DefaultInstance.ReadKeyIdentifier(reader);
// resolve the symmetric key
SymmetricSecurityKey decryptingKey = (SymmetricSecurityKey)SecurityTokenResolver.CreateDefaultSecurityTokenResolver(this.tokens.AsReadOnly(), false).ResolveSecurityKey(keyIdentifier[0]);
SymmetricAlgorithm algorithm = decryptingKey.GetSymmetricAlgorithm(encryptionAlgorithm);
// dig for the security token data itself.
reader.ReadStartElement(XmlEncryptionStrings.CipherData, XmlEncryptionStrings.Namespace);
reader.ReadStartElement(XmlEncryptionStrings.CipherValue, XmlEncryptionStrings.Namespace);
securityTokenData = Convert.FromBase64String(reader.ReadString());
reader.ReadEndElement(); // CipherValue
reader.ReadEndElement(); // CipherData
reader.ReadEndElement(); // EncryptedData
// decrypto-magic!
int blockSizeBytes = algorithm.BlockSize / 8;
byte[] iv = new byte[blockSizeBytes];
Buffer.BlockCopy(securityTokenData, 0, iv, 0, iv.Length);
algorithm.Padding = PaddingMode.ISO10126;
algorithm.Mode = CipherMode.CBC;
ICryptoTransform decrTransform = algorithm.CreateDecryptor(algorithm.Key, iv);
byte[] plainText = decrTransform.TransformFinalBlock(securityTokenData, iv.Length, securityTokenData.Length - iv.Length);
decrTransform.Dispose();
return plainText;
}
#if CONTRACTS_FULL
/// <summary>
/// Verifies conditions that should be true for any valid state of this object.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
[ContractInvariantMethod]
private void ObjectInvariant() {
Contract.Invariant(this.Tokens != null);
}
#endif
/// <summary>
/// Adds a store of certificates to the list of certificates to decrypt with.
/// </summary>
/// <param name="storeName">store name of the certificates</param>
/// <param name="storeLocation">store location</param>
/// <param name="filter">A filter to on the certificates to add.</param>
private void AddDecryptionCertificates(StoreName storeName, StoreLocation storeLocation, Func<X509Certificate2Collection, X509Certificate2Collection> filter) {
X509Store store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadOnly);
this.tokens.AddRange((from cert in filter(store.Certificates).Cast<X509Certificate2>()
where cert.HasPrivateKey
select new X509SecurityToken(cert)).Cast<SecurityToken>());
store.Close();
}
/// <summary>
/// A set of strings used in parsing the XML token.
/// </summary>
internal static class XmlEncryptionStrings {
/// <summary>
/// The "http://www.w3.org/2001/04/xmlenc#" value.
/// </summary>
internal const string Namespace = "http://www.w3.org/2001/04/xmlenc#";
/// <summary>
/// The "EncryptionMethod" value.
/// </summary>
internal const string EncryptionMethod = "EncryptionMethod";
/// <summary>
/// The "CipherValue" value.
/// </summary>
internal const string CipherValue = "CipherValue";
/// <summary>
/// The "Algorithm" value.
/// </summary>
internal const string Algorithm = "Algorithm";
/// <summary>
/// The "EncryptedData" value.
/// </summary>
internal const string EncryptedData = "EncryptedData";
/// <summary>
/// The "CipherData" value.
/// </summary>
internal const string CipherData = "CipherData";
}
}
}
|