1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.Diagnostics; 7 using System.Linq; 8 using System.Security.Cryptography.Asn1; 9 using System.Security.Cryptography.Pkcs.Asn1; 10 using System.Security.Cryptography.X509Certificates; 11 using Internal.Cryptography; 12 13 namespace System.Security.Cryptography.Pkcs 14 { 15 public sealed partial class SignedCms 16 { 17 private SignedDataAsn _signedData; 18 private bool _hasData; 19 20 // A defensive copy of the relevant portions of the data to Decode 21 private Memory<byte> _heldData; 22 23 // Due to the way the underlying Windows CMS API behaves a copy of the content 24 // bytes will be held separate once the content is "bound" (first signature or decode) 25 private ReadOnlyMemory<byte>? _heldContent; 26 27 // Similar to _heldContent, the Windows CMS API held this separate internally, 28 // and thus we need to be reslilient against modification. 29 private string _contentType; 30 31 public int Version { get; private set; } 32 public ContentInfo ContentInfo { get; private set; } 33 public bool Detached { get; private set; } 34 SignedCms(SubjectIdentifierType signerIdentifierType, ContentInfo contentInfo, bool detached)35 public SignedCms(SubjectIdentifierType signerIdentifierType, ContentInfo contentInfo, bool detached) 36 { 37 if (contentInfo == null) 38 throw new ArgumentNullException(nameof(contentInfo)); 39 if (contentInfo.Content == null) 40 throw new ArgumentNullException("contentInfo.Content"); 41 42 // signerIdentifierType is ignored. 43 // In .NET Framework it is used for the signer type of a prompt-for-certificate signer. 44 // In .NET Core we don't support prompting. 45 // 46 // .NET Framework turned any unknown value into IssuerAndSerialNumber, so no exceptions 47 // are required, either. 48 49 ContentInfo = contentInfo; 50 Detached = detached; 51 Version = 0; 52 } 53 54 public X509Certificate2Collection Certificates 55 { 56 get 57 { 58 var coll = new X509Certificate2Collection(); 59 60 if (!_hasData) 61 { 62 return coll; 63 } 64 65 CertificateChoiceAsn[] certChoices = _signedData.CertificateSet; 66 67 if (certChoices == null) 68 { 69 return coll; 70 } 71 72 foreach (CertificateChoiceAsn choice in certChoices) 73 { 74 coll.Add(new X509Certificate2(choice.Certificate.Value.ToArray())); 75 } 76 77 return coll; 78 } 79 } 80 81 public SignerInfoCollection SignerInfos 82 { 83 get 84 { 85 if (!_hasData) 86 { 87 return new SignerInfoCollection(); 88 } 89 90 return new SignerInfoCollection(_signedData.SignerInfos, this); 91 } 92 } 93 Encode()94 public byte[] Encode() 95 { 96 if (!_hasData) 97 { 98 throw new InvalidOperationException(SR.Cryptography_Cms_MessageNotSigned); 99 } 100 101 // Write as DER, so everyone can read it. 102 AsnWriter writer = AsnSerializer.Serialize(_signedData, AsnEncodingRules.DER); 103 byte[] signedData = writer.Encode(); 104 105 ContentInfoAsn contentInfo = new ContentInfoAsn 106 { 107 Content = signedData, 108 ContentType = Oids.Pkcs7Signed, 109 }; 110 111 // Write as DER, so everyone can read it. 112 writer = AsnSerializer.Serialize(contentInfo, AsnEncodingRules.DER); 113 return writer.Encode(); 114 } 115 Decode(byte[] encodedMessage)116 public void Decode(byte[] encodedMessage) 117 { 118 if (encodedMessage == null) 119 throw new ArgumentNullException(nameof(encodedMessage)); 120 121 // Windows (and thus NetFx) reads the leading data and ignores extra. 122 // The deserializer will complain if too much data is given, so use the reader 123 // to ask how much we want to deserialize. 124 AsnReader reader = new AsnReader(encodedMessage, AsnEncodingRules.BER); 125 ReadOnlyMemory<byte> cmsSegment = reader.GetEncodedValue(); 126 127 ContentInfoAsn contentInfo = AsnSerializer.Deserialize<ContentInfoAsn>(cmsSegment, AsnEncodingRules.BER); 128 129 if (contentInfo.ContentType != Oids.Pkcs7Signed) 130 { 131 throw new CryptographicException(SR.Cryptography_Cms_InvalidMessageType); 132 } 133 134 // Hold a copy of the SignedData memory so we are protected against memory reuse by the caller. 135 _heldData = contentInfo.Content.ToArray(); 136 _signedData = AsnSerializer.Deserialize<SignedDataAsn>(_heldData, AsnEncodingRules.BER); 137 _contentType = _signedData.EncapContentInfo.ContentType; 138 139 if (!Detached) 140 { 141 ReadOnlyMemory<byte>? content = _signedData.EncapContentInfo.Content; 142 143 // This is in _heldData, so we don't need a defensive copy. 144 _heldContent = content ?? ReadOnlyMemory<byte>.Empty; 145 146 // The ContentInfo object/property DOES need a defensive copy, because 147 // a) it is mutable by the user, and 148 // b) it is no longer authoritative 149 // 150 // (and c: it takes a byte[] and we have a ReadOnlyMemory<byte>) 151 ContentInfo = new ContentInfo(new Oid(_contentType), _heldContent.Value.ToArray()); 152 } 153 else 154 { 155 // Hold a defensive copy of the content bytes, (Windows/NetFx compat) 156 _heldContent = ContentInfo.Content.CloneByteArray(); 157 } 158 159 Version = _signedData.Version; 160 _hasData = true; 161 } 162 ComputeSignature()163 public void ComputeSignature() 164 { 165 throw new PlatformNotSupportedException(SR.Cryptography_Cms_NoSignerCert); 166 } 167 168 public void ComputeSignature(CmsSigner signer) => ComputeSignature(signer, true); 169 ComputeSignature(CmsSigner signer, bool silent)170 public void ComputeSignature(CmsSigner signer, bool silent) 171 { 172 if (signer == null) 173 { 174 throw new ArgumentNullException(nameof(signer)); 175 } 176 177 // While it shouldn't be possible to change the length of ContentInfo.Content 178 // after it's built, use the property at this stage, then use the saved value 179 // (if applicable) after this point. 180 if (ContentInfo.Content.Length == 0) 181 { 182 throw new CryptographicException(SR.Cryptography_Cms_Sign_Empty_Content); 183 } 184 185 // If we had content already, use that now. 186 // (The second signer doesn't inherit edits to signedCms.ContentInfo.Content) 187 ReadOnlyMemory<byte> content = _heldContent ?? ContentInfo.Content; 188 string contentType = _contentType ?? ContentInfo.ContentType.Value; 189 190 X509Certificate2Collection chainCerts; 191 SignerInfoAsn newSigner = signer.Sign(content, contentType, silent, out chainCerts); 192 bool firstSigner = false; 193 194 if (!_hasData) 195 { 196 firstSigner = true; 197 198 _signedData = new SignedDataAsn 199 { 200 DigestAlgorithms = Array.Empty<AlgorithmIdentifierAsn>(), 201 SignerInfos = Array.Empty<SignerInfoAsn>(), 202 EncapContentInfo = new EncapsulatedContentInfoAsn { ContentType = contentType }, 203 }; 204 205 // Since we're going to call Decode before this method exits we don't need to save 206 // the copy of _heldContent or _contentType here if we're attached. 207 if (!Detached) 208 { 209 _signedData.EncapContentInfo.Content = content; 210 } 211 212 _hasData = true; 213 } 214 215 int newIdx = _signedData.SignerInfos.Length; 216 Array.Resize(ref _signedData.SignerInfos, newIdx + 1); 217 _signedData.SignerInfos[newIdx] = newSigner; 218 UpdateCertificatesFromAddition(chainCerts); 219 ConsiderDigestAddition(newSigner.DigestAlgorithm); 220 UpdateMetadata(); 221 222 if (firstSigner) 223 { 224 Reencode(); 225 226 Debug.Assert(_heldContent != null); 227 Debug.Assert(_contentType == contentType); 228 } 229 } 230 RemoveSignature(int index)231 public void RemoveSignature(int index) 232 { 233 if (!_hasData) 234 { 235 throw new InvalidOperationException(SR.Cryptography_Cms_MessageNotSigned); 236 } 237 238 if (index < 0 || index >= _signedData.SignerInfos.Length) 239 { 240 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); 241 } 242 243 AlgorithmIdentifierAsn signerAlgorithm = _signedData.SignerInfos[index].DigestAlgorithm; 244 Helpers.RemoveAt(ref _signedData.SignerInfos, index); 245 246 ConsiderDigestRemoval(signerAlgorithm); 247 UpdateMetadata(); 248 } 249 RemoveSignature(SignerInfo signerInfo)250 public void RemoveSignature(SignerInfo signerInfo) 251 { 252 if (signerInfo == null) 253 throw new ArgumentNullException(nameof(signerInfo)); 254 255 int idx = SignerInfos.FindIndexForSigner(signerInfo); 256 257 if (idx < 0) 258 { 259 throw new CryptographicException(SR.Cryptography_Cms_SignerNotFound); 260 } 261 262 RemoveSignature(idx); 263 } 264 GetContentSpan()265 internal ReadOnlySpan<byte> GetContentSpan() => _heldContent.Value.Span; 266 Reencode()267 internal void Reencode() 268 { 269 // When NetFx re-encodes it just resets the CMS handle, the ContentInfo property 270 // does not get changed. 271 // See ReopenToDecode 272 ContentInfo save = ContentInfo; 273 274 try 275 { 276 byte[] encoded = Encode(); 277 278 if (Detached) 279 { 280 // At this point the _heldContent becomes whatever ContentInfo says it should be. 281 _heldContent = null; 282 } 283 284 Decode(encoded); 285 Debug.Assert(_heldContent != null); 286 } 287 finally 288 { 289 ContentInfo = save; 290 } 291 } 292 UpdateMetadata()293 private void UpdateMetadata() 294 { 295 // Version 5: any certificate of type Other or CRL of type Other. We don't support this. 296 // Version 4: any certificates are V2 attribute certificates. We don't support this. 297 // Version 3a: any certificates are V1 attribute certificates. We don't support this. 298 // Version 3b: any signerInfos are v3 299 // Version 3c: eContentType != data 300 // Version 2: does not exist for signed-data 301 // Version 1: default 302 303 // The versions 3 are OR conditions, so we need to check the content type and the signerinfos. 304 int version = 1; 305 306 if ((_contentType ?? ContentInfo.ContentType.Value) != Oids.Pkcs7Data) 307 { 308 version = 3; 309 } 310 else if (_signedData.SignerInfos.Any(si => si.Version == 3)) 311 { 312 version = 3; 313 } 314 315 Version = version; 316 _signedData.Version = version; 317 } 318 ConsiderDigestAddition(AlgorithmIdentifierAsn candidate)319 private void ConsiderDigestAddition(AlgorithmIdentifierAsn candidate) 320 { 321 int curLength = _signedData.DigestAlgorithms.Length; 322 323 for (int i = 0; i < curLength; i++) 324 { 325 ref AlgorithmIdentifierAsn alg = ref _signedData.DigestAlgorithms[i]; 326 327 if (candidate.Equals(ref alg)) 328 { 329 return; 330 } 331 } 332 333 Array.Resize(ref _signedData.DigestAlgorithms, curLength + 1); 334 _signedData.DigestAlgorithms[curLength] = candidate; 335 } 336 ConsiderDigestRemoval(AlgorithmIdentifierAsn candidate)337 private void ConsiderDigestRemoval(AlgorithmIdentifierAsn candidate) 338 { 339 bool remove = true; 340 341 for (int i = 0; i < _signedData.SignerInfos.Length; i++) 342 { 343 ref AlgorithmIdentifierAsn signerAlg = ref _signedData.SignerInfos[i].DigestAlgorithm; 344 345 if (candidate.Equals(ref signerAlg)) 346 { 347 remove = false; 348 break; 349 } 350 } 351 352 if (!remove) 353 { 354 return; 355 } 356 357 for (int i = 0; i < _signedData.DigestAlgorithms.Length; i++) 358 { 359 ref AlgorithmIdentifierAsn alg = ref _signedData.DigestAlgorithms[i]; 360 361 if (candidate.Equals(ref alg)) 362 { 363 Helpers.RemoveAt(ref _signedData.DigestAlgorithms, i); 364 break; 365 } 366 } 367 } 368 UpdateCertificatesFromAddition(X509Certificate2Collection newCerts)369 internal void UpdateCertificatesFromAddition(X509Certificate2Collection newCerts) 370 { 371 if (newCerts.Count == 0) 372 { 373 return; 374 } 375 376 int existingLength = _signedData.CertificateSet?.Length ?? 0; 377 378 if (existingLength > 0 || newCerts.Count > 1) 379 { 380 var certs = new HashSet<X509Certificate2>(Certificates.OfType<X509Certificate2>()); 381 382 for (int i = 0; i < newCerts.Count; i++) 383 { 384 X509Certificate2 candidate = newCerts[i]; 385 386 if (!certs.Add(candidate)) 387 { 388 newCerts.RemoveAt(i); 389 i--; 390 } 391 } 392 } 393 394 if (newCerts.Count == 0) 395 { 396 return; 397 } 398 399 if (_signedData.CertificateSet == null) 400 { 401 _signedData.CertificateSet = new CertificateChoiceAsn[newCerts.Count]; 402 } 403 else 404 { 405 Array.Resize(ref _signedData.CertificateSet, existingLength + newCerts.Count); 406 } 407 408 for (int i = existingLength; i < _signedData.CertificateSet.Length; i++) 409 { 410 _signedData.CertificateSet[i] = new CertificateChoiceAsn 411 { 412 Certificate = newCerts[i - existingLength].RawData 413 }; 414 } 415 } 416 417 public void CheckSignature(bool verifySignatureOnly) => 418 CheckSignature(new X509Certificate2Collection(), verifySignatureOnly); 419 CheckSignature(X509Certificate2Collection extraStore, bool verifySignatureOnly)420 public void CheckSignature(X509Certificate2Collection extraStore, bool verifySignatureOnly) 421 { 422 if (!_hasData) 423 throw new InvalidOperationException(SR.Cryptography_Cms_MessageNotSigned); 424 if (extraStore == null) 425 throw new ArgumentNullException(nameof(extraStore)); 426 427 CheckSignatures(SignerInfos, extraStore, verifySignatureOnly); 428 } 429 CheckSignatures( SignerInfoCollection signers, X509Certificate2Collection extraStore, bool verifySignatureOnly)430 private static void CheckSignatures( 431 SignerInfoCollection signers, 432 X509Certificate2Collection extraStore, 433 bool verifySignatureOnly) 434 { 435 Debug.Assert(signers != null); 436 437 if (signers.Count < 1) 438 { 439 throw new CryptographicException(SR.Cryptography_Cms_NoSignerAtIndex); 440 } 441 442 foreach (SignerInfo signer in signers) 443 { 444 signer.CheckSignature(extraStore, verifySignatureOnly); 445 446 SignerInfoCollection counterSigners = signer.CounterSignerInfos; 447 448 if (counterSigners.Count > 0) 449 { 450 CheckSignatures(counterSigners, extraStore, verifySignatureOnly); 451 } 452 } 453 } 454 CheckHash()455 public void CheckHash() 456 { 457 if (!_hasData) 458 throw new InvalidOperationException(SR.Cryptography_Cms_MessageNotSigned); 459 460 SignerInfoCollection signers = SignerInfos; 461 Debug.Assert(signers != null); 462 463 if (signers.Count < 1) 464 { 465 throw new CryptographicException(SR.Cryptography_Cms_NoSignerAtIndex); 466 } 467 468 foreach (SignerInfo signer in signers) 469 { 470 if (signer.SignerIdentifier.Type == SubjectIdentifierType.NoSignature) 471 { 472 signer.CheckHash(); 473 } 474 } 475 } 476 GetRawData()477 internal ref SignedDataAsn GetRawData() 478 { 479 return ref _signedData; 480 } 481 } 482 } 483