|
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
3 | 3 |
|
4 | 4 | using System.Diagnostics;
|
| 5 | +using System.Formats.Asn1; |
| 6 | +using System.Security.Cryptography.Asn1.Pkcs7; |
5 | 7 | using System.Security.Cryptography.Pkcs;
|
| 8 | +using Internal.Cryptography; |
| 9 | + |
| 10 | +#if BUILDING_PKCS |
| 11 | +using Helpers = Internal.Cryptography.PkcsHelpers; |
| 12 | +#endif |
6 | 13 |
|
7 | 14 | namespace System.Security.Cryptography.Asn1.Pkcs12
|
8 | 15 | {
|
9 | 16 | internal partial struct PfxAsn
|
10 | 17 | {
|
| 18 | + private const int MaxIterationWork = 300_000; |
| 19 | + private static ReadOnlySpan<char> EmptyPassword => ""; // don't use ReadOnlySpan<byte>.Empty because it will get confused with default. |
| 20 | + private static ReadOnlySpan<char> NullPassword => default; |
| 21 | + |
11 | 22 | internal bool VerifyMac(
|
12 | 23 | ReadOnlySpan<char> macPassword,
|
13 | 24 | ReadOnlySpan<byte> authSafeContents)
|
@@ -84,5 +95,245 @@ internal bool VerifyMac(
|
84 | 95 | MacData.Value.Mac.Digest.Span);
|
85 | 96 | }
|
86 | 97 | }
|
| 98 | + |
| 99 | + internal ulong CountTotalIterations() |
| 100 | + { |
| 101 | + checked |
| 102 | + { |
| 103 | + ulong count = 0; |
| 104 | + |
| 105 | + // RFC 7292 section 4.1: |
| 106 | + // the contentType field of authSafe shall be of type data |
| 107 | + // or signedData. The content field of the authSafe shall, either |
| 108 | + // directly (data case) or indirectly (signedData case), contain a BER- |
| 109 | + // encoded value of type AuthenticatedSafe. |
| 110 | + // We don't support authSafe that is signedData, so enforce that it's just data. |
| 111 | + if (AuthSafe.ContentType != Oids.Pkcs7Data) |
| 112 | + { |
| 113 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 114 | + } |
| 115 | + |
| 116 | + ReadOnlyMemory<byte> authSafeContents = Helpers.DecodeOctetStringAsMemory(AuthSafe.Content); |
| 117 | + AsnValueReader outerAuthSafe = new AsnValueReader(authSafeContents.Span, AsnEncodingRules.BER); // RFC 7292 PDU says BER |
| 118 | + AsnValueReader authSafeReader = outerAuthSafe.ReadSequence(); |
| 119 | + outerAuthSafe.ThrowIfNotEmpty(); |
| 120 | + |
| 121 | + bool hasSeenEncryptedInfo = false; |
| 122 | + |
| 123 | + while (authSafeReader.HasData) |
| 124 | + { |
| 125 | + ContentInfoAsn.Decode(ref authSafeReader, authSafeContents, out ContentInfoAsn contentInfo); |
| 126 | + |
| 127 | + ReadOnlyMemory<byte> contentData; |
| 128 | + ArraySegment<byte>? rentedData = null; |
| 129 | + |
| 130 | + try |
| 131 | + { |
| 132 | + if (contentInfo.ContentType != Oids.Pkcs7Data) |
| 133 | + { |
| 134 | + if (contentInfo.ContentType == Oids.Pkcs7Encrypted) |
| 135 | + { |
| 136 | + if (hasSeenEncryptedInfo) |
| 137 | + { |
| 138 | + // We will process at most one encryptedData ContentInfo. This is the most typical scenario where |
| 139 | + // certificates are stored in an encryptedData ContentInfo, and keys are shrouded in a data ContentInfo. |
| 140 | + throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword); |
| 141 | + } |
| 142 | + |
| 143 | + ArraySegment<byte> content = DecryptContentInfo(contentInfo, out uint iterations); |
| 144 | + contentData = content; |
| 145 | + rentedData = content; |
| 146 | + hasSeenEncryptedInfo = true; |
| 147 | + count += iterations; |
| 148 | + } |
| 149 | + else |
| 150 | + { |
| 151 | + // Not a common scenario. It's not data or encryptedData, so they need to go through the |
| 152 | + // regular PKCS12 loader. |
| 153 | + throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword); |
| 154 | + } |
| 155 | + } |
| 156 | + else |
| 157 | + { |
| 158 | + contentData = Helpers.DecodeOctetStringAsMemory(contentInfo.Content); |
| 159 | + } |
| 160 | + |
| 161 | + AsnValueReader outerSafeBag = new AsnValueReader(contentData.Span, AsnEncodingRules.BER); |
| 162 | + AsnValueReader safeBagReader = outerSafeBag.ReadSequence(); |
| 163 | + outerSafeBag.ThrowIfNotEmpty(); |
| 164 | + |
| 165 | + while (safeBagReader.HasData) |
| 166 | + { |
| 167 | + SafeBagAsn.Decode(ref safeBagReader, contentData, out SafeBagAsn bag); |
| 168 | + |
| 169 | + // We only need to count iterations on PKCS8ShroudedKeyBag. |
| 170 | + // * KeyBag is PKCS#8 PrivateKeyInfo and doesn't do iterations. |
| 171 | + // * CertBag, either for x509Certificate or sdsiCertificate don't do iterations. |
| 172 | + // * CRLBag doesn't do iterations. |
| 173 | + // * SecretBag doesn't do iteations. |
| 174 | + // * Nested SafeContents _can_ do iterations, but Windows ignores it. So we will ignore it too. |
| 175 | + if (bag.BagId == Oids.Pkcs12ShroudedKeyBag) |
| 176 | + { |
| 177 | + AsnValueReader pkcs8ShroudedKeyReader = new AsnValueReader(bag.BagValue.Span, AsnEncodingRules.BER); |
| 178 | + EncryptedPrivateKeyInfoAsn.Decode( |
| 179 | + ref pkcs8ShroudedKeyReader, |
| 180 | + bag.BagValue, |
| 181 | + out EncryptedPrivateKeyInfoAsn epki); |
| 182 | + |
| 183 | + count += IterationsFromParameters(epki.EncryptionAlgorithm); |
| 184 | + } |
| 185 | + } |
| 186 | + } |
| 187 | + finally |
| 188 | + { |
| 189 | + if (rentedData.HasValue) |
| 190 | + { |
| 191 | + CryptoPool.Return(rentedData.Value); |
| 192 | + } |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + if (MacData.HasValue) |
| 197 | + { |
| 198 | + if (MacData.Value.IterationCount < 0) |
| 199 | + { |
| 200 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 201 | + } |
| 202 | + |
| 203 | + count += (uint)MacData.Value.IterationCount; |
| 204 | + } |
| 205 | + |
| 206 | + return count; |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + private static ArraySegment<byte> DecryptContentInfo(ContentInfoAsn contentInfo, out uint iterations) |
| 211 | + { |
| 212 | + EncryptedDataAsn encryptedData = EncryptedDataAsn.Decode(contentInfo.Content, AsnEncodingRules.BER); |
| 213 | + |
| 214 | + if (encryptedData.Version != 0 && encryptedData.Version != 2) |
| 215 | + { |
| 216 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 217 | + } |
| 218 | + |
| 219 | + // The encrypted contentInfo can only wrap a PKCS7 data. |
| 220 | + if (encryptedData.EncryptedContentInfo.ContentType != Oids.Pkcs7Data) |
| 221 | + { |
| 222 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 223 | + } |
| 224 | + |
| 225 | + if (!encryptedData.EncryptedContentInfo.EncryptedContent.HasValue) |
| 226 | + { |
| 227 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 228 | + } |
| 229 | + |
| 230 | + iterations = IterationsFromParameters(encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm); |
| 231 | + |
| 232 | + // This encryptData is encrypted with more rounds than we are willing to process. Bail out of the whole thing. |
| 233 | + if (iterations > MaxIterationWork) |
| 234 | + { |
| 235 | + throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword); |
| 236 | + } |
| 237 | + |
| 238 | + int encryptedValueLength = encryptedData.EncryptedContentInfo.EncryptedContent.Value.Length; |
| 239 | + byte[] destination = CryptoPool.Rent(encryptedValueLength); |
| 240 | + int written = 0; |
| 241 | + |
| 242 | + try |
| 243 | + { |
| 244 | + try |
| 245 | + { |
| 246 | + written = PasswordBasedEncryption.Decrypt( |
| 247 | + in encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm, |
| 248 | + EmptyPassword, |
| 249 | + default, |
| 250 | + encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span, |
| 251 | + destination); |
| 252 | + } |
| 253 | + catch |
| 254 | + { |
| 255 | + // If empty password didn't work, try null password. |
| 256 | + written = PasswordBasedEncryption.Decrypt( |
| 257 | + in encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm, |
| 258 | + NullPassword, |
| 259 | + default, |
| 260 | + encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span, |
| 261 | + destination); |
| 262 | + } |
| 263 | + } |
| 264 | + finally |
| 265 | + { |
| 266 | + if (written == 0) |
| 267 | + { |
| 268 | + // This means the decryption operation failed and destination could contain |
| 269 | + // partial data. Clear it to be hygienic. |
| 270 | + CryptographicOperations.ZeroMemory(destination); |
| 271 | + } |
| 272 | + } |
| 273 | + |
| 274 | + return new ArraySegment<byte>(destination, 0, written); |
| 275 | + } |
| 276 | + |
| 277 | + private static uint IterationsFromParameters(in AlgorithmIdentifierAsn algorithmIdentifier) |
| 278 | + { |
| 279 | + switch (algorithmIdentifier.Algorithm) |
| 280 | + { |
| 281 | + case Oids.PasswordBasedEncryptionScheme2: |
| 282 | + if (!algorithmIdentifier.Parameters.HasValue) |
| 283 | + { |
| 284 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 285 | + } |
| 286 | + |
| 287 | + PBES2Params pbes2Params = PBES2Params.Decode(algorithmIdentifier.Parameters.Value, AsnEncodingRules.BER); |
| 288 | + |
| 289 | + // PBES2 only defines PKBDF2 for now. See RFC 8018 A.4 |
| 290 | + if (pbes2Params.KeyDerivationFunc.Algorithm != Oids.Pbkdf2) |
| 291 | + { |
| 292 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 293 | + } |
| 294 | + |
| 295 | + if (!pbes2Params.KeyDerivationFunc.Parameters.HasValue) |
| 296 | + { |
| 297 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 298 | + } |
| 299 | + |
| 300 | + Pbkdf2Params pbkdf2Params = Pbkdf2Params.Decode(pbes2Params.KeyDerivationFunc.Parameters.Value, AsnEncodingRules.BER); |
| 301 | + |
| 302 | + if (pbkdf2Params.IterationCount < 0) |
| 303 | + { |
| 304 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 305 | + } |
| 306 | + |
| 307 | + return (uint)pbkdf2Params.IterationCount; |
| 308 | + |
| 309 | + // PBES1 |
| 310 | + case Oids.PbeWithMD5AndDESCBC: |
| 311 | + case Oids.PbeWithMD5AndRC2CBC: |
| 312 | + case Oids.PbeWithSha1AndDESCBC: |
| 313 | + case Oids.PbeWithSha1AndRC2CBC: |
| 314 | + case Oids.Pkcs12PbeWithShaAnd3Key3Des: |
| 315 | + case Oids.Pkcs12PbeWithShaAnd2Key3Des: |
| 316 | + case Oids.Pkcs12PbeWithShaAnd128BitRC2: |
| 317 | + case Oids.Pkcs12PbeWithShaAnd40BitRC2: |
| 318 | + if (!algorithmIdentifier.Parameters.HasValue) |
| 319 | + { |
| 320 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 321 | + } |
| 322 | + |
| 323 | + PBEParameter pbeParameters = PBEParameter.Decode( |
| 324 | + algorithmIdentifier.Parameters.Value, |
| 325 | + AsnEncodingRules.BER); |
| 326 | + |
| 327 | + if (pbeParameters.IterationCount < 0) |
| 328 | + { |
| 329 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 330 | + } |
| 331 | + |
| 332 | + return (uint)pbeParameters.IterationCount; |
| 333 | + |
| 334 | + default: |
| 335 | + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); |
| 336 | + } |
| 337 | + } |
87 | 338 | }
|
88 | 339 | }
|
0 commit comments