-
Notifications
You must be signed in to change notification settings - Fork 607
/
XmlHelpers.cs
705 lines (618 loc) · 27 KB
/
XmlHelpers.cs
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Xml;
using System.Linq;
//using Sustainsys.Saml2.Exceptions;
using System.Collections.Generic;
using System.Reflection;
using System.Globalization;
using System.Diagnostics.CodeAnalysis;
using System.Xml.Linq;
using System.IO;
using Sustainsys.Saml2.Exceptions;
using Sustainsys.Saml2.Internal;
using Sustainsys.Saml2.Metadata.Exceptions;
//using Sustainsys.Saml2.Internal;
using Sustainsys.Saml2.Metadata.Tokens;
namespace Sustainsys.Saml2
{
public class SignedXmlWithIdFix : SignedXml
{
public SignedXmlWithIdFix(XmlElement element) :
base(element)
{
}
public SignedXmlWithIdFix(XmlDocument doc) :
base(doc)
{
}
public override XmlElement GetIdElement(XmlDocument document, string id)
{
var nodes = document.SelectNodes(
$"//*[name() != 'Reference' and (@id='{id}' or @iD='{id}' or @Id='{id}' or @ID='{id}')]");
if (nodes.Count == 0)
{
throw new CryptographicException($"The reference id '{id}' does not match any nodes");
}
if (nodes.Count > 1)
{
throw new CryptographicException($"The reference id '{id}' matches multiple nodes");
}
return (XmlElement)nodes[0];
}
}
/// <summary>
/// Extension methods and helpers for XmlDocument/XmlElement etc.
/// </summary>
public static class XmlHelpers
{
/// <summary>
/// Sign an xml document with the supplied cert.
/// </summary>
/// <param name="xmlDocument">XmlDocument to be signed. The signature is
/// added as a node in the document, right after the Issuer node.</param>
/// <param name="cert">Certificate to use when signing.</param>
public static void Sign(this XmlDocument xmlDocument, X509Certificate2 cert)
{
Sign(xmlDocument, cert, false);
}
/// <summary>
/// Creates an Xml document with secure settings and initialized it from
/// a string.
/// </summary>
/// <param name="source">Source string to load</param>
/// <returns>Xml document</returns>
public static XmlDocument XmlDocumentFromString(string source)
{
var xmlDoc = CreateSafeXmlDocument();
xmlDoc.LoadXml(source);
return xmlDoc;
}
/// <summary>
/// Create an Xml Document with secure settings, specifically
/// disabling xml external entities. Also set PreserveWhiteSpace = true
/// </summary>
/// <returns>Xml Document</returns>
public static XmlDocument CreateSafeXmlDocument()
{
return new XmlDocument()
{
// Null is the default on 4.6 and later, but not on 4.5.
XmlResolver = null,
PreserveWhitespace = true
};
}
/// <summary>
/// Remove the attribute with the given name from the collection.
/// </summary>
/// <param name="attributes">Attribute collection.</param>
/// <param name="attributeName">Name of attribute to remove.</param>
public static void Remove(this XmlAttributeCollection attributes, string attributeName)
{
if(attributes == null)
{
throw new ArgumentNullException(nameof(attributes));
}
if(attributeName == null)
{
throw new ArgumentNullException(nameof(attributeName));
}
var attribute = attributes[attributeName];
attributes.Remove(attribute);
}
/// <summary>
/// Remove the child xml element with the specified name.
/// </summary>
/// <param name="xmlElement">Parent</param>
/// <param name="name">Name of child</param>
/// <param name="ns">Namespace of child</param>
public static void RemoveChild(this XmlElement xmlElement, string name, string ns)
{
if(xmlElement == null)
{
throw new ArgumentNullException(nameof(xmlElement));
}
if(name == null)
{
throw new ArgumentNullException(nameof(name));
}
if(ns == null)
{
throw new ArgumentNullException(nameof(ns));
}
var toRemove = xmlElement[name, ns];
xmlElement.RemoveChild(toRemove);
}
/// <summary>
/// Sign an xml document with the supplied cert.
/// </summary>
/// <param name="xmlDocument">XmlDocument to be signed. The signature is
/// added as a node in the document, right after the Issuer node.</param>
/// <param name="cert">Certificate to use when signing.</param>
/// <param name="includeKeyInfo">Include public key in signed output.</param>
public static void Sign(this XmlDocument xmlDocument, X509Certificate2 cert, bool includeKeyInfo)
{
if(xmlDocument == null)
{
throw new ArgumentNullException(nameof(xmlDocument));
}
xmlDocument.DocumentElement.Sign(cert, includeKeyInfo);
}
/// <summary>
/// Sign an xml document with the supplied cert.
/// </summary>
/// <param name="xmlDocument">XmlDocument to be signed. The signature is
/// added as a node in the document, right after the Issuer node.</param>
/// <param name="cert">Certificate to use when signing.</param>
/// <param name="includeKeyInfo">Include public key in signed output.</param>
/// <param name="signingAlgorithm">Uri of signing algorithm to use.</param>
public static void Sign(
this XmlDocument xmlDocument,
X509Certificate2 cert,
bool includeKeyInfo,
string signingAlgorithm)
{
if (xmlDocument == null)
{
throw new ArgumentNullException(nameof(xmlDocument));
}
xmlDocument.DocumentElement.Sign(cert, includeKeyInfo, signingAlgorithm);
}
/// <summary>
/// Sign an xml element with the supplied cert.
/// </summary>
/// <param name="xmlElement">xmlElement to be signed. The signature is
/// added as a node in the document, right after the Issuer node.</param>
/// <param name="cert">Certificate to use when signing.</param>
/// <param name="includeKeyInfo">Include public key in signed output.</param>
public static void Sign(this XmlElement xmlElement, X509Certificate2 cert, bool includeKeyInfo)
{
xmlElement.Sign(cert, includeKeyInfo, GetDefaultSigningAlgorithmName());
}
/// <summary>
/// Sign an xml element with the supplied cert.
/// </summary>
/// <param name="xmlElement">xmlElement to be signed. The signature is
/// added as a node in the document, right after the Issuer node.</param>
/// <param name="cert">Certificate to use when signing.</param>
/// <param name="includeKeyInfo">Include public key in signed output.</param>
/// <param name="signingAlgorithm">The signing algorithm to use.</param>
public static void Sign(
this XmlElement xmlElement,
X509Certificate2 cert,
bool includeKeyInfo,
string signingAlgorithm)
{
if (xmlElement == null)
{
throw new ArgumentNullException(nameof(xmlElement));
}
if (cert == null)
{
throw new ArgumentNullException(nameof(cert));
}
var signedXml = new SignedXmlWithIdFix(xmlElement.OwnerDocument);
// The transform XmlDsigExcC14NTransform and canonicalization method XmlDsigExcC14NTransformUrl is important for partially signed XML files
// see: http://msdn.microsoft.com/en-us/library/system.security.cryptography.xml.signedxml.xmldsigexcc14ntransformurl(v=vs.110).aspx
// The reference URI has to be set correctly to avoid assertion injections
// For both, the ID/Reference and the Transform/Canonicalization see as well:
// https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf section 5.4.2 and 5.4.3
signedXml.SigningKey = EnvironmentHelpers.IsNetCore ? cert.PrivateKey :
((RSACryptoServiceProvider)cert.PrivateKey)
.GetSha256EnabledRSACryptoServiceProvider();
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
signedXml.SignedInfo.SignatureMethod = signingAlgorithm;
// We need a document unique ID on the element to sign it -- make one up if it's missing
string id = xmlElement.GetAttribute("ID");
if (String.IsNullOrEmpty(id))
{
id = "_" + Guid.NewGuid().ToString("N");
xmlElement.SetAttribute("ID", id);
}
var reference = new Reference
{
Uri = "#" + id,
DigestMethod = GetCorrespondingDigestAlgorithm(signingAlgorithm)
};
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
signedXml.AddReference(reference);
signedXml.ComputeSignature();
if (includeKeyInfo)
{
var keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo = keyInfo;
}
xmlElement.InsertAfter(
xmlElement.OwnerDocument.ImportNode(signedXml.GetXml(), true),
xmlElement["Issuer", Saml2Namespaces.Saml2Name]);
}
/// <summary>
/// Checks if an xml element is signed by the given certificate, through
/// a contained enveloped signature.
/// </summary>
/// <param name="xmlElement">Xml Element that should be signed</param>
/// <param name="signingKeys">Signing keys to test, one should validate.</param>
/// <param name="validateCertificate">Should the certificate be validated too?</param>
/// <param name="minimumSigningAlgorithm">The mininum signing algorithm
/// strength allowed.</param>
/// <returns>True on correct signature, false on missing signature</returns>
/// <exception cref="InvalidSignatureException">If the data has
/// been tampered with or is not valid according to the SAML spec.</exception>
public static bool IsSignedByAny(
this XmlElement xmlElement,
IEnumerable<SecurityKeyIdentifierClause> signingKeys,
bool validateCertificate,
string minimumSigningAlgorithm)
{
if (xmlElement == null)
{
throw new ArgumentNullException(nameof(xmlElement));
}
var signedXml = new SignedXmlWithIdFix(xmlElement);
var signatureElement = xmlElement["Signature", SignedXml.XmlDsigNamespaceUrl];
if (signatureElement == null)
{
return false;
}
signedXml.LoadXml(signatureElement);
ValidateSignedInfo(signedXml, xmlElement, minimumSigningAlgorithm);
VerifySignature(signingKeys, signedXml, signatureElement, validateCertificate);
return true;
}
private static void VerifySignature(
IEnumerable<SecurityKeyIdentifierClause> signingKeys,
SignedXml signedXml,
XmlElement signatureElement,
bool validateCertificate)
{
FixSignatureIndex(signedXml, signatureElement);
foreach (var keyIdentifier in signingKeys)
{
var key = ((AsymmetricSecurityKey)keyIdentifier.CreateKey())
.GetAsymmetricAlgorithm(signedXml.SignatureMethod, false);
if (signedXml.CheckSignature(key))
{
ValidateCertificate(validateCertificate, keyIdentifier);
return;
}
}
var containedKey = signedXml.Signature.KeyInfo.OfType<KeyInfoX509Data>()
.SingleOrDefault()?.Certificates.OfType<X509Certificate2>()
.SingleOrDefault();
if (containedKey != null && signedXml.CheckSignature(containedKey, true))
{
throw new InvalidSignatureException("The signature verified correctly with the key contained in the signature, but that key is not trusted.");
}
throw new InvalidSignatureException("Signature didn't verify. Have the contents been tampered with?");
}
// Splitting up in several methods to set ExcludeFromCodeCoverage on
// as small part of the code as possible. To actually have a test
// case that passes with a valid cert would require a signature
// that is created by a long lived official cert or the tests would
// mysteriously start to fail when the cert expired.
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ValidateCertificates")]
[ExcludeFromCodeCoverage]
private static void ValidateCertificate(bool validateCertificate, SecurityKeyIdentifierClause keyIdentifier)
{
if (validateCertificate)
{
var rawCert = keyIdentifier as X509RawDataKeyIdentifierClause;
if (rawCert == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"Certificate validation enabled, but the signing key identifier is of type {0} which cannot be validated as a certificate.",
keyIdentifier.GetType().Name));
}
if (!new X509Certificate2(rawCert.GetX509RawData()).Verify())
{
throw new InvalidSignatureException("The signature was valid, but the verification of the certificate failed. Is it expired or revoked? Are you sure you really want to enable ValidateCertificates (it's normally not needed)?");
}
}
}
static readonly PropertyInfo signaturePosition = typeof(XmlDsigEnvelopedSignatureTransform)
.GetProperty("SignaturePosition", BindingFlags.Instance | BindingFlags.NonPublic);
/// <summary>
/// Workaround for a bug in Reference.LoadXml incorrectly counting index
/// of signature from the start of the document, not from the start of
/// the element. Reported to Microsoft at
/// https://connect.microsoft.com/VisualStudio/feedback/details/2288620
/// </summary>
/// <param name="signedXml">SignedXml</param>
/// <param name="signatureElement">Signature element.</param>
private static void FixSignatureIndex(SignedXml signedXml, XmlElement signatureElement)
{
Transform transform = null;
foreach(var t in ((Reference)signedXml.SignedInfo.References[0]).TransformChain)
{
var envelopeTransform = t as XmlDsigEnvelopedSignatureTransform;
if(envelopeTransform != null)
{
transform = envelopeTransform;
}
}
if(signaturePosition != null)
{
var nsm = new XmlNamespaceManager(signatureElement.OwnerDocument.NameTable);
nsm.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);
var signaturesInParent = signatureElement.ParentNode.SelectNodes(".//ds:Signature", nsm);
int correctSignaturePosition = 0;
for (int i = 0; i < signaturesInParent.Count; i++)
{
if (signaturesInParent[i] == signatureElement)
{
correctSignaturePosition = i + 1;
}
}
signaturePosition.SetValue(transform, correctSignaturePosition);
}
}
private static readonly string[] allowedTransforms = new string[]
{
SignedXml.XmlDsigEnvelopedSignatureTransformUrl,
SignedXml.XmlDsigExcC14NTransformUrl,
SignedXml.XmlDsigExcC14NWithCommentsTransformUrl
};
private static void ValidateSignedInfo(
SignedXml signedXml,
XmlElement xmlElement,
string minIncomingSignatureAlgorithm)
{
var signatureMethod = signedXml.SignedInfo.SignatureMethod;
ValidateSignatureMethodStrength(minIncomingSignatureAlgorithm, signatureMethod);
ValidateReference(signedXml, xmlElement, GetCorrespondingDigestAlgorithm(minIncomingSignatureAlgorithm));
}
/// <summary>
/// Check if the signature method is at least as strong as the mininum one.
/// </summary>
/// <param name="minIncomingSignatureAlgorithm"></param>
/// <param name="signatureMethod"></param>
/// <exception cref="InvalidSignatureException">If the signaturemethod is too weak.</exception>
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "minIncomingSigningAlgorithm")]
public static void ValidateSignatureMethodStrength(
string minIncomingSignatureAlgorithm,
string signatureMethod)
{
if (!KnownSigningAlgorithms.SkipWhile(a => a != minIncomingSignatureAlgorithm)
.Contains(signatureMethod))
{
throw new InvalidSignatureException(
"The signing algorithm " + signatureMethod +
" is weaker than the minimum accepted " + minIncomingSignatureAlgorithm +
". If you want to allow this signing algorithm, use the minIncomingSigningAlgorithm configuration attribute.");
}
}
private static void ValidateReference(
SignedXml signedXml,
XmlElement xmlElement,
string mininumDigestAlgorithm)
{
if (signedXml.SignedInfo.References.Count == 0)
{
throw new InvalidSignatureException("No reference found in Xml signature, it doesn't validate the Xml data.");
}
if (signedXml.SignedInfo.References.Count != 1)
{
throw new InvalidSignatureException("Multiple references for Xml signatures are not allowed.");
}
var reference = (Reference)signedXml.SignedInfo.References[0];
if ( string.IsNullOrWhiteSpace( reference.Uri ) )
{
throw new InvalidSignatureException( "Empty reference for Xml signature is not allowed." );
}
var id = reference.Uri.Substring(1);
var idElement = signedXml.GetIdElement(xmlElement.OwnerDocument, id);
if (idElement != xmlElement)
{
throw new InvalidSignatureException("Incorrect reference on Xml signature. The reference must be to the root element of the element containing the signature.");
}
foreach (Transform transform in reference.TransformChain)
{
if (!allowedTransforms.Contains(transform.Algorithm))
{
throw new InvalidSignatureException(
"Transform \"" + transform.Algorithm + "\" found in Xml signature SHOULD NOT be used with SAML2.");
}
}
if(!DigestAlgorithms.SkipWhile(a => a != mininumDigestAlgorithm)
.Contains(reference.DigestMethod))
{
throw new InvalidSignatureException("The digest method " + reference.DigestMethod
+ " is weaker than the minimum accepted " + mininumDigestAlgorithm + ".");
}
}
internal static XElement AddAttributeIfNotNullOrEmpty(this XElement xElement, XName attribute, object value)
{
if (value != null && !string.IsNullOrEmpty(value.ToString()))
{
xElement.Add(new XAttribute(attribute, value));
}
return xElement;
}
internal static string GetValueIfNotNull(this XmlAttribute xmlAttribute)
{
if (xmlAttribute == null)
{
return null;
}
return xmlAttribute.Value;
}
internal static string GetTrimmedTextIfNotNull(this XmlElement xmlElement)
{
if (xmlElement == null)
{
return null;
}
return xmlElement.InnerText.Trim();
}
internal static string GetRequiredAttributeValue(this XmlElement node, string attributeName)
{
var foundAttribute = node.Attributes[attributeName];
if (string.IsNullOrWhiteSpace(foundAttribute?.Value))
{
throw new BadFormatSamlResponseException($"Attribute '{attributeName}' (case-sensitive) was not found or its value is empty");
}
return foundAttribute.Value;
}
internal static XmlElement GetRequiredElement(this XmlElement node, string name, string namespaceValue)
{
var foundElement = node[name, namespaceValue];
if (foundElement == null)
{
throw new BadFormatSamlResponseException($"Element '{name}' (case-sensitive, namespace '{namespaceValue}') was not found");
}
return foundElement;
}
internal static XmlElement StartElement(this XmlNode parent, string name, Uri namespaceUri)
{
var xmlElement = parent.GetOwnerDoc().CreateElement(name, namespaceUri.OriginalString);
parent.AppendChild(xmlElement);
return xmlElement;
}
private static XmlDocument GetOwnerDoc(this XmlNode node)
{
var doc = node as XmlDocument;
if(doc != null)
{
return doc;
}
return node.OwnerDocument;
}
internal static XmlElement AddAttribute(this XmlElement parent, string name, string value)
{
parent.SetAttribute(name, value);
return parent;
}
internal static XmlElement AddAttributeIfNotNull(this XmlElement parent, string name, object value)
{
if(value != null)
{
parent.SetAttribute(name, value.ToString());
}
return parent;
}
internal static XmlElement If(this XmlElement parent, bool condition, Action<XmlElement> action)
{
if (condition)
{
action(parent);
}
return parent;
}
internal static XmlElement AddElement(this XmlElement parent, string name, Uri namespaceUri, string content)
{
parent.StartElement(name, namespaceUri)
.SetInnerText(content);
return parent;
}
internal static XmlElement SetInnerText(this XmlElement parent, string content)
{
parent.InnerText = content;
return parent;
}
/// <summary>
/// Pretty an xml element.
/// </summary>
/// <param name="xml">Xml to pretty print.</param>
/// <returns>Nicely indented and readable data.</returns>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "I don't care, the StringWriter contains no references to unmanaged resources")]
public static string PrettyPrint(this XmlElement xml)
{
if(xml == null)
{
throw new ArgumentNullException(nameof(xml));
}
// Based on http://stackoverflow.com/a/1123731/280222
var strWriter = new StringWriter(CultureInfo.InvariantCulture);
using (var xmlWriter = new XmlTextWriter(strWriter))
{
xmlWriter.Formatting = Formatting.Indented;
xml.ParentNode.WriteContentTo(xmlWriter);
xmlWriter.Flush();
return strWriter.ToString();
}
}
/// <summary>
/// Store a list of signing algorithms that are available in SignedXml.
/// This needs to be done through reflection, to keep the library
/// targetting lowest supported .NET version, while still getting
/// access to new algorithms if the hosting application targets a
/// later version.
/// </summary>
private static string[] GetKnownSigningAlgorithms()
{
// The newer algorithms names are marked internal in .NET Core
// https://github.com/dotnet/corefx/issues/25123
if (EnvironmentHelpers.IsNetCore)
{
return new string[] {
"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
};
}
return typeof(SignedXml).GetFields()
.Where(f => f.Name.StartsWith("XmlDsigRSASHA", StringComparison.Ordinal))
.Select(f => (string)f.GetRawConstantValue())
.OrderBy(f => f)
.ToArray();
}
internal static readonly string[] KnownSigningAlgorithms =
GetKnownSigningAlgorithms();
internal static string GetFullSigningAlgorithmName(string shortName)
{
return string.IsNullOrEmpty(shortName) ?
GetDefaultSigningAlgorithmName()
: KnownSigningAlgorithms.Single(
a => a.EndsWith(shortName, StringComparison.OrdinalIgnoreCase));
}
// Can't test the fallback behaviour on a machine that has a modern
// framework installed.
[ExcludeFromCodeCoverage]
internal static string GetDefaultSigningAlgorithmName()
{
var rsaSha256Name = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
if (EnvironmentHelpers.IsNetCore ||
KnownSigningAlgorithms.Contains(rsaSha256Name))
{
return rsaSha256Name;
}
return SignedXml.XmlDsigRSASHA1Url;
}
private static string[] GetKnownDigestAlgorithms()
{
if (EnvironmentHelpers.IsNetCore)
{
return new string[] {
"http://www.w3.org/2000/09/xmldsig#sha1",
"http://www.w3.org/2001/04/xmlenc#sha256",
"http://www.w3.org/2001/04/xmldsig-more#sha384",
"http://www.w3.org/2001/04/xmlenc#sha512"
};
}
return typeof(SignedXml).GetFields()
.Where(f => f.Name.StartsWith("XmlDsigSHA", StringComparison.Ordinal))
.Select(f => (string)f.GetRawConstantValue())
.OrderBy(f => f)
.ToArray();
}
internal static readonly string[] DigestAlgorithms = GetKnownDigestAlgorithms();
internal static string GetCorrespondingDigestAlgorithm(string signingAlgorithm)
{
var matchPattern = signingAlgorithm.Substring(signingAlgorithm.LastIndexOf('-') + 1);
string match = DigestAlgorithms.FirstOrDefault(a => a.EndsWith(
matchPattern,
StringComparison.Ordinal));
if (match == null)
{
throw new InvalidOperationException(
$"Unable to find a digest algorithm for the signing algorithm {signingAlgorithm}");
}
return match;
}
}
}