diff --git a/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml b/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml index d882cc8c7adf..d6efc1ba53d2 100644 --- a/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml +++ b/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml @@ -63,7 +63,7 @@ asp-fallback-src="~/lib/jquery/dist/jquery.min.js" asp-fallback-test="window.jQuery" crossorigin="anonymous" - integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk"> + integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT"> diff --git a/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml b/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml index 637c3fe8d6e0..7e68378c09b2 100644 --- a/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml +++ b/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml @@ -63,7 +63,7 @@ asp-fallback-src="~/lib/jquery/dist/jquery.min.js" asp-fallback-test="window.jQuery" crossorigin="anonymous" - integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk"> + integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT"> diff --git a/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml b/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml index 10d5288e4597..76cfabcb6c52 100644 --- a/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml +++ b/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml @@ -55,7 +55,7 @@ asp-fallback-src="~/lib/jquery/dist/jquery.min.js" asp-fallback-test="window.jQuery" crossorigin="anonymous" - integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk"> + integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT"> diff --git a/test/Templates.Test/CdnScriptTagTests.cs b/test/Templates.Test/CdnScriptTagTests.cs new file mode 100644 index 000000000000..63e18a29feec --- /dev/null +++ b/test/Templates.Test/CdnScriptTagTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Templates.Test +{ + public class CdnScriptTagTests + { + private readonly ITestOutputHelper _output; + + public CdnScriptTagTests(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public async Task CheckSubresourceIntegrity() + { + var dir = GetSolutionDir(); + var artifactsDir = Path.Combine(dir, "artifacts", "build"); + var packages = Directory.GetFiles(artifactsDir, "*.nupkg"); + + var scriptTags = new List(); + foreach (var packagePath in packages) + { + scriptTags.AddRange(GetScriptTags(packagePath)); + } + + Assert.NotEmpty(scriptTags); + var shasum = new Dictionary(); + + var client = new HttpClient(); + foreach (var script in scriptTags) + { + if (shasum.ContainsKey(script.Src)) + { + continue; + } + + using (var resp = await client.GetStreamAsync(script.Src)) + using (var alg = SHA384.Create()) + { + var hash = alg.ComputeHash(resp); + shasum.Add(script.Src, "sha384-" + Convert.ToBase64String(hash)); + } + } + + Assert.All(scriptTags, t => + { + Assert.True(shasum[t.Src] == t.Integrity, userMessage: $"Expected integrity on script tag to be {shasum[t.Src]} but it was {t.Integrity}. {t.FileName}:{t.Entry}"); + }); + } + + private struct ScriptTag + { + public string Src; + public string Integrity; + public string FileName; + public string Entry; + } + + private static readonly Regex _scriptRegex = new Regex(@"]*src=""(?'src'http[^""]+)""[^>]*integrity=""(?'integrity'[^""]+)""([^>]*)>", RegexOptions.Multiline); + + private IEnumerable GetScriptTags(string zipFile) + { + using (var zip = new ZipArchive(File.OpenRead(zipFile), ZipArchiveMode.Read, leaveOpen: false)) + { + foreach (var entry in zip.Entries) + { + if (string.Equals(".cshtml", Path.GetExtension(entry.Name), StringComparison.OrdinalIgnoreCase)) + { + string contents; + using (var reader = new StreamReader(entry.Open())) + { + contents = reader.ReadToEnd(); + } + + var match = _scriptRegex.Match(contents); + while (match != null && match != Match.Empty) + { + var tag = new ScriptTag + { + Src = match.Groups["src"].Value, + Integrity = match.Groups["integrity"].Value, + FileName = Path.GetFileName(zipFile), + Entry = entry.FullName, + }; + yield return tag; + _output.WriteLine($"Found script tag in {tag.FileName}:{tag.Entry}, src='{tag.Src}' integrity='{tag.Integrity}'"); + match = match.NextMatch(); + } + } + } + } + } + + private static string GetSolutionDir() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null) + { + if (File.Exists(Path.Combine(dir.FullName, "Templating.sln"))) + { + break; + } + dir = dir.Parent; + } + return dir.FullName; + } + } +}