committed by
Kévin Chalet
110 changed files with 5039 additions and 25 deletions
@ -0,0 +1,625 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
|
||||
|
#pragma warning disable 1591
|
||||
|
// ReSharper disable UnusedMember.Global
|
||||
|
// ReSharper disable UnusedParameter.Local
|
||||
|
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
|
// ReSharper disable IntroduceOptionalParameters.Global
|
||||
|
// ReSharper disable MemberCanBeProtected.Global
|
||||
|
// ReSharper disable InconsistentNaming
|
||||
|
|
||||
|
namespace NWebsec.Annotations |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Indicates that the value of the marked element could be <c>null</c> sometimes,
|
||||
|
/// so the check for <c>null</c> is necessary before its usage
|
||||
|
/// </summary>
|
||||
|
/// <example><code>
|
||||
|
/// [CanBeNull] public object Test() { return null; }
|
||||
|
/// public void UseTest() {
|
||||
|
/// var p = Test();
|
||||
|
/// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException'
|
||||
|
/// }
|
||||
|
/// </code></example>
|
||||
|
[Conditional("DEBUG"), AttributeUsage( |
||||
|
AttributeTargets.Method | AttributeTargets.Parameter | |
||||
|
AttributeTargets.Property | AttributeTargets.Delegate | |
||||
|
AttributeTargets.Field, AllowMultiple = false, Inherited = true)] |
||||
|
internal sealed class CanBeNullAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates that the value of the marked element could never be <c>null</c>
|
||||
|
/// </summary>
|
||||
|
/// <example><code>
|
||||
|
/// [NotNull] public object Foo() {
|
||||
|
/// return null; // Warning: Possible 'null' assignment
|
||||
|
/// }
|
||||
|
/// </code></example>
|
||||
|
[Conditional("DEBUG"), AttributeUsage( |
||||
|
AttributeTargets.Method | AttributeTargets.Parameter | |
||||
|
AttributeTargets.Property | AttributeTargets.Delegate | |
||||
|
AttributeTargets.Field, AllowMultiple = false, Inherited = true)] |
||||
|
internal sealed class NotNullAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates that the marked method builds string by format pattern and (optional) arguments.
|
||||
|
/// Parameter, which contains format string, should be given in constructor. The format string
|
||||
|
/// should be in <see cref="string.Format(IFormatProvider,string,object[])"/>-like form
|
||||
|
/// </summary>
|
||||
|
/// <example><code>
|
||||
|
/// [StringFormatMethod("message")]
|
||||
|
/// public void ShowError(string message, params object[] args) { /* do something */ }
|
||||
|
/// public void Foo() {
|
||||
|
/// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string
|
||||
|
/// }
|
||||
|
/// </code></example>
|
||||
|
[Conditional("DEBUG"), AttributeUsage( |
||||
|
AttributeTargets.Constructor | AttributeTargets.Method, |
||||
|
AllowMultiple = false, Inherited = true)] |
||||
|
internal sealed class StringFormatMethodAttribute : Attribute |
||||
|
{ |
||||
|
/// <param name="formatParameterName">
|
||||
|
/// Specifies which parameter of an annotated method should be treated as format-string
|
||||
|
/// </param>
|
||||
|
public StringFormatMethodAttribute(string formatParameterName) |
||||
|
{ |
||||
|
FormatParameterName = formatParameterName; |
||||
|
} |
||||
|
|
||||
|
public string FormatParameterName { get; private set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates that the function argument should be string literal and match one
|
||||
|
/// of the parameters of the caller function. For example, ReSharper annotates
|
||||
|
/// the parameter of <see cref="System.ArgumentNullException"/>
|
||||
|
/// </summary>
|
||||
|
/// <example><code>
|
||||
|
/// public void Foo(string param) {
|
||||
|
/// if (param == null)
|
||||
|
/// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol
|
||||
|
/// }
|
||||
|
/// </code></example>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] |
||||
|
internal sealed class InvokerParameterNameAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates that the method is contained in a type that implements
|
||||
|
/// <see cref="System.ComponentModel.INotifyPropertyChanged"/> interface
|
||||
|
/// and this method is used to notify that some property value changed
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The method should be non-static and conform to one of the supported signatures:
|
||||
|
/// <list>
|
||||
|
/// <item><c>NotifyChanged(string)</c></item>
|
||||
|
/// <item><c>NotifyChanged(params string[])</c></item>
|
||||
|
/// <item><c>NotifyChanged{T}(Expression{Func{T}})</c></item>
|
||||
|
/// <item><c>NotifyChanged{T,U}(Expression{Func{T,U}})</c></item>
|
||||
|
/// <item><c>SetProperty{T}(ref T, T, string)</c></item>
|
||||
|
/// </list>
|
||||
|
/// </remarks>
|
||||
|
/// <example><code>
|
||||
|
/// public class Foo : INotifyPropertyChanged {
|
||||
|
/// public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
/// [NotifyPropertyChangedInvocator]
|
||||
|
/// protected virtual void NotifyChanged(string propertyName) { ... }
|
||||
|
///
|
||||
|
/// private string _name;
|
||||
|
/// public string Name {
|
||||
|
/// get { return _name; }
|
||||
|
/// set { _name = value; NotifyChanged("LastName"); /* Warning */ }
|
||||
|
/// }
|
||||
|
/// }
|
||||
|
/// </code>
|
||||
|
/// Examples of generated notifications:
|
||||
|
/// <list>
|
||||
|
/// <item><c>NotifyChanged("Property")</c></item>
|
||||
|
/// <item><c>NotifyChanged(() => Property)</c></item>
|
||||
|
/// <item><c>NotifyChanged((VM x) => x.Property)</c></item>
|
||||
|
/// <item><c>SetProperty(ref myField, value, "Property")</c></item>
|
||||
|
/// </list>
|
||||
|
/// </example>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] |
||||
|
internal sealed class NotifyPropertyChangedInvocatorAttribute : Attribute |
||||
|
{ |
||||
|
public NotifyPropertyChangedInvocatorAttribute() { } |
||||
|
public NotifyPropertyChangedInvocatorAttribute(string parameterName) |
||||
|
{ |
||||
|
ParameterName = parameterName; |
||||
|
} |
||||
|
|
||||
|
public string ParameterName { get; private set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Describes dependency between method input and output
|
||||
|
/// </summary>
|
||||
|
/// <syntax>
|
||||
|
/// <p>Function Definition Table syntax:</p>
|
||||
|
/// <list>
|
||||
|
/// <item>FDT ::= FDTRow [;FDTRow]*</item>
|
||||
|
/// <item>FDTRow ::= Input => Output | Output <= Input</item>
|
||||
|
/// <item>Input ::= ParameterName: Value [, Input]*</item>
|
||||
|
/// <item>Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value}</item>
|
||||
|
/// <item>Value ::= true | false | null | notnull | canbenull</item>
|
||||
|
/// </list>
|
||||
|
/// If method has single input parameter, it's name could be omitted.<br/>
|
||||
|
/// Using <c>halt</c> (or <c>void</c>/<c>nothing</c>, which is the same)
|
||||
|
/// for method output means that the methos doesn't return normally.<br/>
|
||||
|
/// <c>canbenull</c> annotation is only applicable for output parameters.<br/>
|
||||
|
/// You can use multiple <c>[ContractAnnotation]</c> for each FDT row,
|
||||
|
/// or use single attribute with rows separated by semicolon.<br/>
|
||||
|
/// </syntax>
|
||||
|
/// <examples><list>
|
||||
|
/// <item><code>
|
||||
|
/// [ContractAnnotation("=> halt")]
|
||||
|
/// public void TerminationMethod()
|
||||
|
/// </code></item>
|
||||
|
/// <item><code>
|
||||
|
/// [ContractAnnotation("halt <= condition: false")]
|
||||
|
/// public void Assert(bool condition, string text) // regular assertion method
|
||||
|
/// </code></item>
|
||||
|
/// <item><code>
|
||||
|
/// [ContractAnnotation("s:null => true")]
|
||||
|
/// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty()
|
||||
|
/// </code></item>
|
||||
|
/// <item><code>
|
||||
|
/// // A method that returns null if the parameter is null, and not null if the parameter is not null
|
||||
|
/// [ContractAnnotation("null => null; notnull => notnull")]
|
||||
|
/// public object Transform(object data)
|
||||
|
/// </code></item>
|
||||
|
/// <item><code>
|
||||
|
/// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")]
|
||||
|
/// public bool TryParse(string s, out Person result)
|
||||
|
/// </code></item>
|
||||
|
/// </list></examples>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] |
||||
|
internal sealed class ContractAnnotationAttribute : Attribute |
||||
|
{ |
||||
|
public ContractAnnotationAttribute([NotNull] string contract) |
||||
|
: this(contract, false) { } |
||||
|
|
||||
|
public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) |
||||
|
{ |
||||
|
Contract = contract; |
||||
|
ForceFullStates = forceFullStates; |
||||
|
} |
||||
|
|
||||
|
public string Contract { get; private set; } |
||||
|
public bool ForceFullStates { get; private set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates that marked element should be localized or not
|
||||
|
/// </summary>
|
||||
|
/// <example><code>
|
||||
|
/// [LocalizationRequiredAttribute(true)]
|
||||
|
/// public class Foo {
|
||||
|
/// private string str = "my string"; // Warning: Localizable string
|
||||
|
/// }
|
||||
|
/// </code></example>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] |
||||
|
internal sealed class LocalizationRequiredAttribute : Attribute |
||||
|
{ |
||||
|
public LocalizationRequiredAttribute() : this(true) { } |
||||
|
public LocalizationRequiredAttribute(bool required) |
||||
|
{ |
||||
|
Required = required; |
||||
|
} |
||||
|
|
||||
|
public bool Required { get; private set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates that the value of the marked type (or its derivatives)
|
||||
|
/// cannot be compared using '==' or '!=' operators and <c>Equals()</c>
|
||||
|
/// should be used instead. However, using '==' or '!=' for comparison
|
||||
|
/// with <c>null</c> is always permitted.
|
||||
|
/// </summary>
|
||||
|
/// <example><code>
|
||||
|
/// [CannotApplyEqualityOperator]
|
||||
|
/// class NoEquality { }
|
||||
|
/// class UsesNoEquality {
|
||||
|
/// public void Test() {
|
||||
|
/// var ca1 = new NoEquality();
|
||||
|
/// var ca2 = new NoEquality();
|
||||
|
/// if (ca1 != null) { // OK
|
||||
|
/// bool condition = ca1 == ca2; // Warning
|
||||
|
/// }
|
||||
|
/// }
|
||||
|
/// }
|
||||
|
/// </code></example>
|
||||
|
[Conditional("DEBUG"), AttributeUsage( |
||||
|
AttributeTargets.Interface | AttributeTargets.Class | |
||||
|
AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] |
||||
|
internal sealed class CannotApplyEqualityOperatorAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// When applied to a target attribute, specifies a requirement for any type marked
|
||||
|
/// with the target attribute to implement or inherit specific type or types.
|
||||
|
/// </summary>
|
||||
|
/// <example><code>
|
||||
|
/// [BaseTypeRequired(typeof(IComponent)] // Specify requirement
|
||||
|
/// public class ComponentAttribute : Attribute { }
|
||||
|
/// [Component] // ComponentAttribute requires implementing IComponent interface
|
||||
|
/// public class MyComponent : IComponent { }
|
||||
|
/// </code></example>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] |
||||
|
[BaseTypeRequired(typeof(Attribute))] |
||||
|
internal sealed class BaseTypeRequiredAttribute : Attribute |
||||
|
{ |
||||
|
public BaseTypeRequiredAttribute([NotNull] Type baseType) |
||||
|
{ |
||||
|
BaseType = baseType; |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public Type BaseType { get; private set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates that the marked symbol is used implicitly
|
||||
|
/// (e.g. via reflection, in external library), so this symbol
|
||||
|
/// will not be marked as unused (as well as by other usage inspections)
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] |
||||
|
internal sealed class UsedImplicitlyAttribute : Attribute |
||||
|
{ |
||||
|
public UsedImplicitlyAttribute() |
||||
|
: this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } |
||||
|
|
||||
|
public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) |
||||
|
: this(useKindFlags, ImplicitUseTargetFlags.Default) { } |
||||
|
|
||||
|
public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) |
||||
|
: this(ImplicitUseKindFlags.Default, targetFlags) { } |
||||
|
|
||||
|
public UsedImplicitlyAttribute( |
||||
|
ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) |
||||
|
{ |
||||
|
UseKindFlags = useKindFlags; |
||||
|
TargetFlags = targetFlags; |
||||
|
} |
||||
|
|
||||
|
public ImplicitUseKindFlags UseKindFlags { get; private set; } |
||||
|
public ImplicitUseTargetFlags TargetFlags { get; private set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Should be used on attributes and causes ReSharper
|
||||
|
/// to not mark symbols marked with such attributes as unused
|
||||
|
/// (as well as by other usage inspections)
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] |
||||
|
internal sealed class MeansImplicitUseAttribute : Attribute |
||||
|
{ |
||||
|
public MeansImplicitUseAttribute() |
||||
|
: this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } |
||||
|
|
||||
|
public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) |
||||
|
: this(useKindFlags, ImplicitUseTargetFlags.Default) { } |
||||
|
|
||||
|
public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) |
||||
|
: this(ImplicitUseKindFlags.Default, targetFlags) { } |
||||
|
|
||||
|
public MeansImplicitUseAttribute( |
||||
|
ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) |
||||
|
{ |
||||
|
UseKindFlags = useKindFlags; |
||||
|
TargetFlags = targetFlags; |
||||
|
} |
||||
|
|
||||
|
[UsedImplicitly] |
||||
|
public ImplicitUseKindFlags UseKindFlags { get; private set; } |
||||
|
[UsedImplicitly] |
||||
|
public ImplicitUseTargetFlags TargetFlags { get; private set; } |
||||
|
} |
||||
|
|
||||
|
[Flags] |
||||
|
internal enum ImplicitUseKindFlags |
||||
|
{ |
||||
|
Default = Access | Assign | InstantiatedWithFixedConstructorSignature, |
||||
|
/// <summary>Only entity marked with attribute considered used</summary>
|
||||
|
Access = 1, |
||||
|
/// <summary>Indicates implicit assignment to a member</summary>
|
||||
|
Assign = 2, |
||||
|
/// <summary>
|
||||
|
/// Indicates implicit instantiation of a type with fixed constructor signature.
|
||||
|
/// That means any unused constructor parameters won't be reported as such.
|
||||
|
/// </summary>
|
||||
|
InstantiatedWithFixedConstructorSignature = 4, |
||||
|
/// <summary>Indicates implicit instantiation of a type</summary>
|
||||
|
InstantiatedNoFixedConstructorSignature = 8, |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specify what is considered used implicitly
|
||||
|
/// when marked with <see cref="MeansImplicitUseAttribute"/>
|
||||
|
/// or <see cref="UsedImplicitlyAttribute"/>
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
internal enum ImplicitUseTargetFlags |
||||
|
{ |
||||
|
Default = Itself, |
||||
|
Itself = 1, |
||||
|
/// <summary>Members of entity marked with attribute are considered used</summary>
|
||||
|
Members = 2, |
||||
|
/// <summary>Entity marked with attribute and all its members considered used</summary>
|
||||
|
WithMembers = Itself | Members |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This attribute is intended to mark publicly available API
|
||||
|
/// which should not be removed and so is treated as used
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), MeansImplicitUse] |
||||
|
internal sealed class PublicAPIAttribute : Attribute |
||||
|
{ |
||||
|
public PublicAPIAttribute() { } |
||||
|
public PublicAPIAttribute([NotNull] string comment) |
||||
|
{ |
||||
|
Comment = comment; |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public string Comment { get; private set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tells code analysis engine if the parameter is completely handled
|
||||
|
/// when the invoked method is on stack. If the parameter is a delegate,
|
||||
|
/// indicates that delegate is executed while the method is executed.
|
||||
|
/// If the parameter is an enumerable, indicates that it is enumerated
|
||||
|
/// while the method is executed
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter, Inherited = true)] |
||||
|
internal sealed class InstantHandleAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates that a method does not make any observable state changes.
|
||||
|
/// The same as <c>System.Diagnostics.Contracts.PureAttribute</c>
|
||||
|
/// </summary>
|
||||
|
/// <example><code>
|
||||
|
/// [Pure] private int Multiply(int x, int y) { return x * y; }
|
||||
|
/// public void Foo() {
|
||||
|
/// const int a = 2, b = 2;
|
||||
|
/// Multiply(a, b); // Waring: Return value of pure method is not used
|
||||
|
/// }
|
||||
|
/// </code></example>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Method, Inherited = true)] |
||||
|
internal sealed class PureAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Indicates that a parameter is a path to a file or a folder
|
||||
|
/// within a web project. Path can be relative or absolute,
|
||||
|
/// starting from web root (~)
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter)] |
||||
|
internal class PathReferenceAttribute : Attribute |
||||
|
{ |
||||
|
public PathReferenceAttribute() { } |
||||
|
public PathReferenceAttribute([PathReference] string basePath) |
||||
|
{ |
||||
|
BasePath = basePath; |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public string BasePath { get; private set; } |
||||
|
} |
||||
|
|
||||
|
// ASP.NET MVC attributes
|
||||
|
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] |
||||
|
internal sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute |
||||
|
{ |
||||
|
public AspMvcAreaMasterLocationFormatAttribute(string format) { } |
||||
|
} |
||||
|
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] |
||||
|
internal sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute |
||||
|
{ |
||||
|
public AspMvcAreaPartialViewLocationFormatAttribute(string format) { } |
||||
|
} |
||||
|
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] |
||||
|
internal sealed class AspMvcAreaViewLocationFormatAttribute : Attribute |
||||
|
{ |
||||
|
public AspMvcAreaViewLocationFormatAttribute(string format) { } |
||||
|
} |
||||
|
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] |
||||
|
internal sealed class AspMvcMasterLocationFormatAttribute : Attribute |
||||
|
{ |
||||
|
public AspMvcMasterLocationFormatAttribute(string format) { } |
||||
|
} |
||||
|
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] |
||||
|
internal sealed class AspMvcPartialViewLocationFormatAttribute : Attribute |
||||
|
{ |
||||
|
public AspMvcPartialViewLocationFormatAttribute(string format) { } |
||||
|
} |
||||
|
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] |
||||
|
internal sealed class AspMvcViewLocationFormatAttribute : Attribute |
||||
|
{ |
||||
|
public AspMvcViewLocationFormatAttribute(string format) { } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
|
||||
|
/// is an MVC action. If applied to a method, the MVC action name is calculated
|
||||
|
/// implicitly from the context. Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] |
||||
|
internal sealed class AspMvcActionAttribute : Attribute |
||||
|
{ |
||||
|
public AspMvcActionAttribute() { } |
||||
|
public AspMvcActionAttribute([NotNull] string anonymousProperty) |
||||
|
{ |
||||
|
AnonymousProperty = anonymousProperty; |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public string AnonymousProperty { get; private set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC area.
|
||||
|
/// Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter)] |
||||
|
internal sealed class AspMvcAreaAttribute : PathReferenceAttribute |
||||
|
{ |
||||
|
public AspMvcAreaAttribute() { } |
||||
|
public AspMvcAreaAttribute([NotNull] string anonymousProperty) |
||||
|
{ |
||||
|
AnonymousProperty = anonymousProperty; |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public string AnonymousProperty { get; private set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. If applied to a parameter, indicates that
|
||||
|
/// the parameter is an MVC controller. If applied to a method,
|
||||
|
/// the MVC controller name is calculated implicitly from the context.
|
||||
|
/// Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] |
||||
|
internal sealed class AspMvcControllerAttribute : Attribute |
||||
|
{ |
||||
|
public AspMvcControllerAttribute() { } |
||||
|
public AspMvcControllerAttribute([NotNull] string anonymousProperty) |
||||
|
{ |
||||
|
AnonymousProperty = anonymousProperty; |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public string AnonymousProperty { get; private set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master.
|
||||
|
/// Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.Web.Mvc.Controller.View(String, String)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter)] |
||||
|
internal sealed class AspMvcMasterAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type.
|
||||
|
/// Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.Web.Mvc.Controller.View(String, Object)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter)] |
||||
|
internal sealed class AspMvcModelTypeAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. If applied to a parameter, indicates that
|
||||
|
/// the parameter is an MVC partial view. If applied to a method,
|
||||
|
/// the MVC partial view name is calculated implicitly from the context.
|
||||
|
/// Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] |
||||
|
internal sealed class AspMvcPartialViewAttribute : PathReferenceAttribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. Allows disabling all inspections
|
||||
|
/// for MVC views within a class or a method.
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] |
||||
|
internal sealed class AspMvcSupressViewErrorAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template.
|
||||
|
/// Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter)] |
||||
|
internal sealed class AspMvcDisplayTemplateAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template.
|
||||
|
/// Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter)] |
||||
|
internal sealed class AspMvcEditorTemplateAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC template.
|
||||
|
/// Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.ComponentModel.DataAnnotations.UIHintAttribute(System.String)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter)] |
||||
|
internal sealed class AspMvcTemplateAttribute : Attribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
|
||||
|
/// is an MVC view. If applied to a method, the MVC view name is calculated implicitly
|
||||
|
/// from the context. Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.Web.Mvc.Controller.View(Object)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] |
||||
|
internal sealed class AspMvcViewAttribute : PathReferenceAttribute { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ASP.NET MVC attribute. When applied to a parameter of an attribute,
|
||||
|
/// indicates that this parameter is an MVC action name
|
||||
|
/// </summary>
|
||||
|
/// <example><code>
|
||||
|
/// [ActionName("Foo")]
|
||||
|
/// public ActionResult Login(string returnUrl) {
|
||||
|
/// ViewBag.ReturnUrl = Url.Action("Foo"); // OK
|
||||
|
/// return RedirectToAction("Bar"); // Error: Cannot resolve action
|
||||
|
/// }
|
||||
|
/// </code></example>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] |
||||
|
internal sealed class AspMvcActionSelectorAttribute : Attribute { } |
||||
|
|
||||
|
[Conditional("DEBUG"), AttributeUsage( |
||||
|
AttributeTargets.Parameter | AttributeTargets.Property | |
||||
|
AttributeTargets.Field, Inherited = true)] |
||||
|
internal sealed class HtmlElementAttributesAttribute : Attribute |
||||
|
{ |
||||
|
public HtmlElementAttributesAttribute() { } |
||||
|
public HtmlElementAttributesAttribute([NotNull] string name) |
||||
|
{ |
||||
|
Name = name; |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public string Name { get; private set; } |
||||
|
} |
||||
|
|
||||
|
[Conditional("DEBUG"), AttributeUsage( |
||||
|
AttributeTargets.Parameter | AttributeTargets.Field | |
||||
|
AttributeTargets.Property, Inherited = true)] |
||||
|
internal sealed class HtmlAttributeValueAttribute : Attribute |
||||
|
{ |
||||
|
public HtmlAttributeValueAttribute([NotNull] string name) |
||||
|
{ |
||||
|
Name = name; |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public string Name { get; private set; } |
||||
|
} |
||||
|
|
||||
|
// Razor attributes
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Razor attribute. Indicates that a parameter or a method is a Razor section.
|
||||
|
/// Use this attribute for custom wrappers similar to
|
||||
|
/// <c>System.Web.WebPages.WebPageBase.RenderSection(String)</c>
|
||||
|
/// </summary>
|
||||
|
[Conditional("DEBUG"), AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, Inherited = true)] |
||||
|
internal sealed class RazorSectionAttribute : Attribute { } |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<PropertyGroup> |
||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> |
||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> |
||||
|
<PropertyGroup Label="Globals"> |
||||
|
<ProjectGuid>144226e7-3eb3-459d-89c8-4d8720e1ef1e</ProjectGuid> |
||||
|
<RootNamespace>NWebsec.Annotations</RootNamespace> |
||||
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> |
||||
|
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<SchemaVersion>2.0</SchemaVersion> |
||||
|
</PropertyGroup> |
||||
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" /> |
||||
|
</Project> |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System.Reflection; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
// General Information about an assembly is controlled through the following
|
||||
|
// set of attributes. Change these attribute values to modify the information
|
||||
|
// associated with an assembly.
|
||||
|
[assembly: AssemblyTitle("NWebsec.Annotations")] |
||||
|
[assembly: AssemblyDescription("")] |
||||
|
[assembly: AssemblyConfiguration("")] |
||||
|
[assembly: AssemblyCompany("")] |
||||
|
[assembly: AssemblyProduct("NWebsec.Annotations")] |
||||
|
[assembly: AssemblyCopyright("Copyright © 2015")] |
||||
|
[assembly: AssemblyTrademark("")] |
||||
|
[assembly: AssemblyCulture("")] |
||||
|
|
||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
|
// to COM components. If you need to access a type in this assembly from
|
||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||
|
[assembly: ComVisible(false)] |
||||
|
|
||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
|
[assembly: Guid("144226e7-3eb3-459d-89c8-4d8720e1ef1e")] |
||||
|
[assembly: InternalsVisibleTo("NWebsec.Core")] |
||||
|
[assembly: InternalsVisibleTo("NWebsec.Middleware")] |
||||
|
[assembly: InternalsVisibleTo("NWebsec.Mvc")] |
||||
@ -0,0 +1,18 @@ |
|||||
|
{ |
||||
|
"version": "1.0.0-*", |
||||
|
"description": "NWebsec.Annotations Class Library", |
||||
|
"authors": [ "klings" ], |
||||
|
"tags": [ "" ], |
||||
|
"projectUrl": "", |
||||
|
"licenseUrl": "", |
||||
|
|
||||
|
"frameworks": { |
||||
|
"dnx451": { }, |
||||
|
"net451": { }, |
||||
|
"dotnet5.4": { |
||||
|
"dependencies": { |
||||
|
"Microsoft.CSharp": "4.0.1-beta-23516" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace NWebsec.Core.Exceptions |
||||
|
{ |
||||
|
public class RedirectValidationException : Exception |
||||
|
{ |
||||
|
public RedirectValidationException(string message) : base(message) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Annotations; |
||||
|
|
||||
|
namespace NWebsec.Core.Extensions |
||||
|
{ |
||||
|
public static class HttpContextExtensions |
||||
|
{ |
||||
|
[NotNull] |
||||
|
public static NWebsecContext GetNWebsecContext(this HttpContext context) |
||||
|
{ |
||||
|
if (!context.Items.ContainsKey(NWebsecContext.ContextKey)) |
||||
|
{ |
||||
|
context.Items[NWebsecContext.ContextKey] = new NWebsecContext(); |
||||
|
} |
||||
|
|
||||
|
return context.Items[NWebsecContext.ContextKey] as NWebsecContext; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
//This code was lended from http://bit.ly/ifluentinterface, and is not covered by the regular NWebsec lisence.
|
||||
|
|
||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
|
||||
|
namespace NWebsec.Core.Fluent |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Interface that is used to build fluent interfaces and hides methods declared by <see cref="object" /> from
|
||||
|
/// IntelliSense.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Code that consumes implementations of this interface should expect one of two things:
|
||||
|
/// <list type="number">
|
||||
|
/// <item>
|
||||
|
/// When referencing the interface from within the same solution (project reference), you will still see the
|
||||
|
/// methods this interface is meant to hide.
|
||||
|
/// </item>
|
||||
|
/// <item>
|
||||
|
/// When referencing the interface through the compiled output assembly (external reference), the standard
|
||||
|
/// Object methods will be hidden as intended.
|
||||
|
/// </item>
|
||||
|
/// </list>
|
||||
|
/// See http://bit.ly/ifluentinterface for more information.
|
||||
|
/// </remarks>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public interface IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Redeclaration that hides the <see cref="object.GetType()" /> method from IntelliSense.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
Type GetType(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Redeclaration that hides the <see cref="object.GetHashCode()" /> method from IntelliSense.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
int GetHashCode(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Redeclaration that hides the <see cref="object.ToString()" /> method from IntelliSense.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
string ToString(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Redeclaration that hides the <see cref="object.Equals(object)" /> method from IntelliSense.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
bool Equals(object obj); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core.HttpHeaders; |
||||
|
|
||||
|
namespace NWebsec.Core.Helpers |
||||
|
{ |
||||
|
public class HeaderResultHandler : IHeaderResultHandler |
||||
|
{ |
||||
|
public void HandleHeaderResult(HttpResponse response, HeaderResult result) |
||||
|
{ |
||||
|
if (result == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
switch (result.Action) |
||||
|
{ |
||||
|
case HeaderResult.ResponseAction.Set: |
||||
|
response.Headers[result.Name] = result.Value; |
||||
|
return; |
||||
|
case HeaderResult.ResponseAction.Remove: |
||||
|
response.Headers.Remove(result.Name); |
||||
|
return; |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core.HttpHeaders; |
||||
|
|
||||
|
namespace NWebsec.Core.Helpers |
||||
|
{ |
||||
|
public interface IHeaderResultHandler |
||||
|
{ |
||||
|
void HandleHeaderResult(HttpResponse response, HeaderResult result); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.Helpers.X509 |
||||
|
{ |
||||
|
internal class TlvTripletHeader |
||||
|
{ |
||||
|
public byte Tag { get; set; } |
||||
|
public int Length { get; set; } |
||||
|
public byte[] RawData { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,252 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Security.Cryptography; |
||||
|
using System.Security.Cryptography.X509Certificates; |
||||
|
|
||||
|
namespace NWebsec.Core.Helpers.X509 |
||||
|
{ |
||||
|
public class X509Helper |
||||
|
{ |
||||
|
private const byte AsnInteger = 0x02; |
||||
|
private const byte AsnBitString = 0x03; |
||||
|
private const byte AsnSequence = 0x30; |
||||
|
private const byte AsnOptional = 0xA0; |
||||
|
|
||||
|
private static readonly byte[] AsnTags = { AsnInteger, AsnBitString, AsnSequence, AsnOptional }; |
||||
|
|
||||
|
//TODO cleanup. Perhaps a test or two.
|
||||
|
//[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands"), SecuritySafeCritical]
|
||||
|
public X509Certificate2 GetCertByThumbprint(string thumbprint, StoreLocation storeLocation, StoreName storeName) |
||||
|
{ |
||||
|
X509Store certStore = null; |
||||
|
X509Certificate2Collection certs = null; |
||||
|
try |
||||
|
{ |
||||
|
certStore = new X509Store(storeName, storeLocation); |
||||
|
certStore.Open(OpenFlags.ReadOnly); |
||||
|
certs = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); |
||||
|
|
||||
|
if (certs.Count > 1) |
||||
|
{ |
||||
|
var message = string.Format("Something went horribly wrong, found more than one cert with thumbprint {0} in store location {1}, storename {2}", thumbprint, storeLocation, storeName); |
||||
|
throw new Exception(message); |
||||
|
} |
||||
|
|
||||
|
if (certs.Count == 0) |
||||
|
{ |
||||
|
var message = string.Format("No certificate with thumbprint {0} in store location {1}, storename {2}", thumbprint, storeLocation, storeName); |
||||
|
throw new ArgumentException(message); |
||||
|
} |
||||
|
|
||||
|
//Returns new cert, all existing certs will be cleaned up
|
||||
|
return certs[0]; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
if (certs != null) |
||||
|
{ |
||||
|
foreach (var cert in certs) |
||||
|
{ |
||||
|
CleanupCert(cert); |
||||
|
} |
||||
|
} |
||||
|
if (certStore != null) |
||||
|
{ |
||||
|
foreach (var cert in certStore.Certificates) |
||||
|
{ |
||||
|
CleanupCert(cert); |
||||
|
} |
||||
|
#if DNX451
|
||||
|
certStore.Close(); |
||||
|
#elif NET451
|
||||
|
certStore.Close(); |
||||
|
#else
|
||||
|
certStore.Dispose(); |
||||
|
#endif
|
||||
|
} |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a string suitable for inclusion in an HPKP header, including hash algoritm.
|
||||
|
/// </summary>
|
||||
|
/// <param name="cert"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public string GetSubjectPublicKeyInfoPinValue(X509Certificate2 cert) |
||||
|
{ |
||||
|
var spki = GetRawSubjectPublicKeyInfo(cert); |
||||
|
using (var sha256 = SHA256.Create()) |
||||
|
{ |
||||
|
var hash = Convert.ToBase64String(sha256.ComputeHash(spki)); |
||||
|
return string.Format("sha256=\"" + hash + "\""); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static byte[] GetRawSubjectPublicKeyInfo(X509Certificate2 cert) |
||||
|
{ |
||||
|
if (cert.Version != 3) |
||||
|
{ |
||||
|
throw new ArgumentException("Only X.509 certificate version 3 is supported. This cert was version " + cert.Version); |
||||
|
} |
||||
|
|
||||
|
var rawCert = cert.RawData; |
||||
|
|
||||
|
using (var ms = new MemoryStream(rawCert)) |
||||
|
{ |
||||
|
//Get outer cert sequence header
|
||||
|
var tlv = ReadTlvTripletHeader(ms); |
||||
|
if (tlv.Tag != AsnSequence) throw new Exception("Expected ASN sequence, for start of certificate."); |
||||
|
//Console.WriteLine("Got certficate sequence, parsed length: " + tlv.Length + " " + BitConverter.ToString(tlv.RawData));
|
||||
|
|
||||
|
//Get tbs cert sequence header
|
||||
|
tlv = ReadTlvTripletHeader(ms); |
||||
|
if (tlv.Tag != AsnSequence) throw new Exception("Expected ASN sequence, for start of tbc cert."); |
||||
|
//Console.WriteLine("Got tbs certficate sequence, parsed length: " + tlv.Length + " " + BitConverter.ToString(tlv.RawData));
|
||||
|
|
||||
|
//Get cert version integer header
|
||||
|
tlv = ReadTlvTripletHeader(ms); |
||||
|
|
||||
|
while (tlv.Tag == AsnOptional) |
||||
|
{ |
||||
|
//Console.WriteLine("Got optional TLV, parsed length: " + tlv.Length + " " + BitConverter.ToString(tlv.RawData));
|
||||
|
tlv = ReadTlvTripletHeader(ms); |
||||
|
} |
||||
|
|
||||
|
if (tlv.Tag != AsnInteger) throw new Exception("Expected ASN integer cert version."); |
||||
|
//Console.WriteLine("Got the certficate version, parsed length: " + tlv.Length + " " + BitConverter.ToString(tlv.RawData));
|
||||
|
|
||||
|
|
||||
|
var version = ms.ReadByte(); |
||||
|
if (version == -1) throw new Exception("Could not read version byte"); |
||||
|
//Console.WriteLine("Cert version: " + version);
|
||||
|
|
||||
|
//Get serial number
|
||||
|
tlv = ReadTlvTripletHeader(ms); |
||||
|
if (tlv.Tag != AsnInteger) throw new Exception("Expected ASN integer serial number."); |
||||
|
//Console.WriteLine("Got the cert serial number, parsed length: " + tlv.Length + " " + BitConverter.ToString(tlv.RawData));
|
||||
|
|
||||
|
var serialNumber = new byte[tlv.Length]; |
||||
|
|
||||
|
var read = ms.Read(serialNumber, 0, serialNumber.Length); |
||||
|
|
||||
|
if (read < serialNumber.Length) throw new Exception("Expected reading " + tlv.Length + " serial number bytes, got " + read); |
||||
|
|
||||
|
//Skip signature sequence
|
||||
|
tlv = ReadTlvTripletHeader(ms); |
||||
|
if (tlv.Tag != AsnSequence) throw new Exception("Expected ASN sequence signature."); |
||||
|
//Console.WriteLine("Got the cert signature sequence, parsed length: " + tlv.Length + " " + BitConverter.ToString(tlv.RawData));
|
||||
|
ms.Seek(tlv.Length, SeekOrigin.Current); |
||||
|
//Console.WriteLine("Skipped ahead " + tlv.Length + " bytes.");
|
||||
|
|
||||
|
//Skip issuer sequence
|
||||
|
tlv = ReadTlvTripletHeader(ms); |
||||
|
if (tlv.Tag != AsnSequence) throw new Exception("Expected ASN sequence issuer."); |
||||
|
//Console.WriteLine("Got the cert issuer sequence, parsed length: " + tlv.Length + " " + BitConverter.ToString(tlv.RawData));
|
||||
|
ms.Seek(tlv.Length, SeekOrigin.Current); |
||||
|
//Console.WriteLine("Skipped ahead " + tlv.Length + " bytes.");
|
||||
|
|
||||
|
//Skip validity sequence
|
||||
|
tlv = ReadTlvTripletHeader(ms); |
||||
|
if (tlv.Tag != AsnSequence) throw new Exception("Expected ASN sequence validity."); |
||||
|
//Console.WriteLine("Got the cert validity sequence, parsed length: " + tlv.Length + " " + BitConverter.ToString(tlv.RawData));
|
||||
|
ms.Seek(tlv.Length, SeekOrigin.Current); |
||||
|
//Console.WriteLine("Skipped ahead " + tlv.Length + " bytes.");
|
||||
|
|
||||
|
//Skip subject sequence
|
||||
|
tlv = ReadTlvTripletHeader(ms); |
||||
|
if (tlv.Tag != AsnSequence) throw new Exception("Expected ASN sequence subject."); |
||||
|
//Console.WriteLine("Got the cert subject sequence, parsed length: " + tlv.Length + " " + BitConverter.ToString(tlv.RawData));
|
||||
|
ms.Seek(tlv.Length, SeekOrigin.Current); |
||||
|
//Console.WriteLine("Skipped ahead " + tlv.Length + " bytes.");
|
||||
|
|
||||
|
//Skip subject sequence
|
||||
|
tlv = ReadTlvTripletHeader(ms); |
||||
|
if (tlv.Tag != AsnSequence) throw new Exception("Expected ASN sequence SPKI."); |
||||
|
//Console.WriteLine("Got the cert SPKI sequence, parsed length: " + tlv.Length + " " + BitConverter.ToString(tlv.RawData));
|
||||
|
|
||||
|
//New array for both tlv bits and content bits.
|
||||
|
var spkiChunk = new byte[tlv.RawData.Length + tlv.Length]; |
||||
|
|
||||
|
Array.Copy(tlv.RawData, spkiChunk, tlv.RawData.Length); |
||||
|
|
||||
|
read = ms.Read(spkiChunk, tlv.RawData.Length, tlv.Length); |
||||
|
|
||||
|
if (read > tlv.Length) throw new Exception("Got " + read + " SPKI bytes, expected " + spkiChunk.Length); |
||||
|
|
||||
|
return spkiChunk; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static TlvTripletHeader ReadTlvTripletHeader(MemoryStream ms) |
||||
|
{ |
||||
|
var firstBytes = new byte[2]; |
||||
|
var read = ms.Read(firstBytes, 0, firstBytes.Length); |
||||
|
|
||||
|
if (read < 1) |
||||
|
{ |
||||
|
throw new Exception("No data read!"); |
||||
|
} |
||||
|
|
||||
|
if (!AsnTags.Any(t => t == firstBytes[0])) throw new Exception("Unexptected ASN.1 tag byte: " + BitConverter.ToString(firstBytes, 0, 1)); |
||||
|
|
||||
|
if (read < 2) |
||||
|
{ |
||||
|
throw new Exception("No length byte read!"); |
||||
|
} |
||||
|
|
||||
|
if (firstBytes[1] < 0x80) |
||||
|
{ |
||||
|
return new TlvTripletHeader() { Tag = firstBytes[0], Length = firstBytes[1], RawData = firstBytes }; |
||||
|
} |
||||
|
|
||||
|
//Handle multi-byte length.
|
||||
|
var numberOfLengthBytes = firstBytes[1] - 0x80; |
||||
|
|
||||
|
if (numberOfLengthBytes < 1) throw new Exception("Invalid length byte. Indicated multibyte length, with length 0."); |
||||
|
if (numberOfLengthBytes > 4) throw new NotSupportedException("Leading length byte indicates more than 4 length bytes, which is not supported. Indicated length bytes: " + numberOfLengthBytes); |
||||
|
|
||||
|
//Get the bytes
|
||||
|
var lengthBytes = new byte[numberOfLengthBytes]; |
||||
|
var bytesRead = ms.Read(lengthBytes, 0, lengthBytes.Length); |
||||
|
|
||||
|
if (bytesRead != lengthBytes.Length) throw new Exception(string.Format("Expected {0} length bytes, got {1}", lengthBytes.Length, bytesRead)); |
||||
|
|
||||
|
//Got the bytes, make an int.
|
||||
|
var length = 0; |
||||
|
//Console.WriteLine("Adding length bytes: " + BitConverter.ToString(lengthBytes));
|
||||
|
foreach (var lengthByte in lengthBytes) |
||||
|
{ |
||||
|
|
||||
|
//Shift existing bytes so they become more significant. Avoid platform dependent bit fiddling.
|
||||
|
//Console.WriteLine("Length tweak starting: " + BitConverter.ToString(BitConverter.GetBytes(length)));
|
||||
|
|
||||
|
length = length * 256; |
||||
|
//Console.WriteLine("Shifted length to make room for next byte: " + BitConverter.ToString(BitConverter.GetBytes(length)));
|
||||
|
|
||||
|
length += lengthByte; |
||||
|
//Console.WriteLine("Added next byte: " + BitConverter.ToString(BitConverter.GetBytes(length)));
|
||||
|
} |
||||
|
|
||||
|
var rawbytes = new byte[firstBytes.Length + lengthBytes.Length]; |
||||
|
Array.Copy(firstBytes, rawbytes, firstBytes.Length); |
||||
|
Array.Copy(lengthBytes, 0, rawbytes, firstBytes.Length, lengthBytes.Length); |
||||
|
|
||||
|
return new TlvTripletHeader { Tag = firstBytes[0], Length = length, RawData = rawbytes }; |
||||
|
} |
||||
|
|
||||
|
private void CleanupCert(X509Certificate2 cert) |
||||
|
{ |
||||
|
#if NET451
|
||||
|
cert.Reset(); |
||||
|
#elif DNX451
|
||||
|
cert.Reset(); |
||||
|
#else
|
||||
|
cert.Dispose(); |
||||
|
#endif
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class CspConfiguration : ICspConfiguration |
||||
|
{ |
||||
|
public CspConfiguration(bool initializeDirectives=true) |
||||
|
{ |
||||
|
if (!initializeDirectives) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
DefaultSrcDirective = new CspDirectiveConfiguration(); |
||||
|
ScriptSrcDirective = new CspDirectiveConfiguration(); |
||||
|
ObjectSrcDirective = new CspDirectiveConfiguration(); |
||||
|
StyleSrcDirective = new CspDirectiveConfiguration(); |
||||
|
ImgSrcDirective = new CspDirectiveConfiguration(); |
||||
|
MediaSrcDirective = new CspDirectiveConfiguration(); |
||||
|
FrameSrcDirective = new CspDirectiveConfiguration(); |
||||
|
FontSrcDirective = new CspDirectiveConfiguration(); |
||||
|
ConnectSrcDirective = new CspDirectiveConfiguration(); |
||||
|
BaseUriDirective = new CspDirectiveConfiguration(); |
||||
|
ChildSrcDirective = new CspDirectiveConfiguration(); |
||||
|
FormActionDirective = new CspDirectiveConfiguration(); |
||||
|
FrameAncestorsDirective = new CspDirectiveConfiguration(); |
||||
|
PluginTypesDirective = new CspPluginTypesDirectiveConfiguration(); |
||||
|
SandboxDirective = new CspSandboxDirectiveConfiguration(); |
||||
|
UpgradeInsecureRequestsDirective = new CspUpgradeDirectiveConfiguration(); |
||||
|
ReportUriDirective = new CspReportUriDirectiveConfiguration(); |
||||
|
} |
||||
|
|
||||
|
public bool Enabled { get; set; } |
||||
|
public ICspDirectiveConfiguration DefaultSrcDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration ScriptSrcDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration ObjectSrcDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration StyleSrcDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration ImgSrcDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration MediaSrcDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration FrameSrcDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration FontSrcDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration ConnectSrcDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration BaseUriDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration ChildSrcDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration FormActionDirective { get; set; } |
||||
|
public ICspDirectiveConfiguration FrameAncestorsDirective { get; set; } |
||||
|
public ICspPluginTypesDirectiveConfiguration PluginTypesDirective { get; set; } |
||||
|
public ICspSandboxDirectiveConfiguration SandboxDirective { get; set; } |
||||
|
public ICspUpgradeDirectiveConfiguration UpgradeInsecureRequestsDirective { get; set; } |
||||
|
public ICspReportUriDirectiveConfiguration ReportUriDirective { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class CspDirectiveConfiguration : ICspDirectiveConfiguration |
||||
|
{ |
||||
|
private static readonly string[] EmptySources = new string[0]; |
||||
|
|
||||
|
public CspDirectiveConfiguration() |
||||
|
{ |
||||
|
Enabled = true; |
||||
|
CustomSources = EmptySources; |
||||
|
} |
||||
|
|
||||
|
public bool Enabled { get; set; } |
||||
|
public bool NoneSrc { get; set; } |
||||
|
public bool SelfSrc { get; set; } |
||||
|
public bool UnsafeInlineSrc { get; set; } |
||||
|
public bool UnsafeEvalSrc { get; set; } |
||||
|
public IEnumerable<string> CustomSources { get; set; } |
||||
|
public string Nonce { get; set; } |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class CspHeaderConfiguration : ICspHeaderConfiguration |
||||
|
{ |
||||
|
public bool Enabled { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class CspPluginTypesDirectiveConfiguration : ICspPluginTypesDirectiveConfiguration |
||||
|
{ |
||||
|
private static readonly string[] EmptySources = new string[0]; |
||||
|
|
||||
|
public bool Enabled { get; set; } |
||||
|
public IEnumerable<string> MediaTypes { get; set; } |
||||
|
|
||||
|
public CspPluginTypesDirectiveConfiguration() |
||||
|
{ |
||||
|
Enabled = true; |
||||
|
MediaTypes = EmptySources; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class CspReportUriDirectiveConfiguration : ICspReportUriDirectiveConfiguration |
||||
|
{ |
||||
|
public CspReportUriDirectiveConfiguration() |
||||
|
{ |
||||
|
ReportUris = new string[0]; |
||||
|
} |
||||
|
|
||||
|
public bool Enabled { get; set; } |
||||
|
//TODO figure out what to do with this property
|
||||
|
public bool EnableBuiltinHandler { get; set; } |
||||
|
public IEnumerable<string> ReportUris { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class CspSandboxDirectiveConfiguration : ICspSandboxDirectiveConfiguration |
||||
|
{ |
||||
|
public bool Enabled { get; set; } |
||||
|
public bool AllowForms { get; set; } |
||||
|
public bool AllowPointerLock { get; set; } |
||||
|
public bool AllowPopups { get; set; } |
||||
|
public bool AllowSameOrigin { get; set; } |
||||
|
public bool AllowScripts { get; set; } |
||||
|
public bool AllowTopNavigation { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class CspUpgradeDirectiveConfiguration : ICspUpgradeDirectiveConfiguration |
||||
|
{ |
||||
|
|
||||
|
public bool Enabled { get; set; } |
||||
|
public int HttpsPort { get; set; } = 443; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class HpkpConfiguration : IHpkpConfiguration |
||||
|
{ |
||||
|
public IEnumerable<string> Pins { get; set; } |
||||
|
public TimeSpan MaxAge { get; set; } |
||||
|
public bool IncludeSubdomains { get; set; } |
||||
|
public string ReportUri { get; set; } |
||||
|
public bool HttpsOnly { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class HstsConfiguration : IHstsConfiguration |
||||
|
{ |
||||
|
public TimeSpan MaxAge { get; set; } |
||||
|
public bool IncludeSubdomains { get; set; } |
||||
|
public bool Preload { get; set; } |
||||
|
public bool HttpsOnly { get; set; } |
||||
|
public bool UpgradeInsecureRequests { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface ICspConfiguration |
||||
|
{ |
||||
|
bool Enabled { get; set; } |
||||
|
ICspDirectiveConfiguration DefaultSrcDirective { get; set; } |
||||
|
ICspDirectiveConfiguration ScriptSrcDirective { get; set; } |
||||
|
ICspDirectiveConfiguration ObjectSrcDirective { get; set; } |
||||
|
ICspDirectiveConfiguration StyleSrcDirective { get; set; } |
||||
|
ICspDirectiveConfiguration ImgSrcDirective { get; set; } |
||||
|
ICspDirectiveConfiguration MediaSrcDirective { get; set; } |
||||
|
ICspDirectiveConfiguration FrameSrcDirective { get; set; } |
||||
|
ICspDirectiveConfiguration FontSrcDirective { get; set; } |
||||
|
ICspDirectiveConfiguration ConnectSrcDirective { get; set; } |
||||
|
ICspReportUriDirectiveConfiguration ReportUriDirective { get; set; } |
||||
|
|
||||
|
//CSP 2
|
||||
|
ICspDirectiveConfiguration BaseUriDirective { get; set; } |
||||
|
ICspDirectiveConfiguration ChildSrcDirective { get; set; } |
||||
|
ICspDirectiveConfiguration FormActionDirective { get; set; } |
||||
|
ICspDirectiveConfiguration FrameAncestorsDirective { get; set; } |
||||
|
ICspPluginTypesDirectiveConfiguration PluginTypesDirective { get; set; } |
||||
|
ICspSandboxDirectiveConfiguration SandboxDirective { get; set; } |
||||
|
|
||||
|
//Upgrade insecure requests
|
||||
|
ICspUpgradeDirectiveConfiguration UpgradeInsecureRequestsDirective { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
using NWebsec.Core.Fluent; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the properties required for CSP directive configuration.
|
||||
|
/// </summary>
|
||||
|
public interface ICspDirectiveBasicConfiguration : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
bool Enabled { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
bool NoneSrc { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
bool SelfSrc { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
IEnumerable<string> CustomSources { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.ComponentModel; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the properties required for CSP directive configuration.
|
||||
|
/// </summary>
|
||||
|
public interface ICspDirectiveConfiguration : ICspDirectiveUnsafeInlineConfiguration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
bool UnsafeEvalSrc { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.ComponentModel; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the properties required for CSP directive configuration.
|
||||
|
/// </summary>
|
||||
|
public interface ICspDirectiveUnsafeInlineConfiguration : ICspDirectiveBasicConfiguration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
bool UnsafeInlineSrc { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
string Nonce { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface ICspHeaderConfiguration |
||||
|
{ |
||||
|
bool Enabled { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the properties required for CSP sandbox directive configuration.
|
||||
|
/// </summary>
|
||||
|
public interface ICspPluginTypesDirectiveConfiguration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
bool Enabled { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
IEnumerable<string> MediaTypes { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface ICspReportUriDirectiveConfiguration |
||||
|
{ |
||||
|
bool Enabled { get; set; } |
||||
|
|
||||
|
bool EnableBuiltinHandler { get; set; } |
||||
|
|
||||
|
IEnumerable<string> ReportUris { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the properties required for CSP sandbox directive configuration.
|
||||
|
/// </summary>
|
||||
|
public interface ICspSandboxDirectiveConfiguration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
bool Enabled { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
bool AllowForms { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
bool AllowPointerLock { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
bool AllowPopups { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
bool AllowSameOrigin { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
bool AllowScripts { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Infrastructure. Not intended to be used by your code directly. An attempt to hide this from Intellisense has been
|
||||
|
/// made.
|
||||
|
/// </summary>
|
||||
|
bool AllowTopNavigation { get; set; } |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the properties required for CSP sandbox directive configuration.
|
||||
|
/// </summary>
|
||||
|
public interface ICspUpgradeDirectiveConfiguration |
||||
|
{ |
||||
|
bool Enabled { get; set; } |
||||
|
|
||||
|
int HttpsPort { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Security.Cryptography.X509Certificates; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface IHpkpCertConfiguration |
||||
|
{ |
||||
|
string ThumbPrint { get; set; } |
||||
|
StoreLocation StoreLocation { get; set; } |
||||
|
StoreName Storename { get; set; } |
||||
|
string SpkiPinValue { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface IHpkpConfiguration |
||||
|
{ |
||||
|
IEnumerable<string> Pins { get; set; } |
||||
|
TimeSpan MaxAge { get; set; } |
||||
|
bool IncludeSubdomains { get; set; } |
||||
|
string ReportUri { get; set; } |
||||
|
bool HttpsOnly { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface IHpkpPinConfiguration |
||||
|
{ |
||||
|
string Pin { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface IHstsConfiguration |
||||
|
{ |
||||
|
TimeSpan MaxAge { get; set; } |
||||
|
|
||||
|
bool IncludeSubdomains { get; set; } |
||||
|
|
||||
|
bool Preload { get; set; } |
||||
|
|
||||
|
bool HttpsOnly { get; set; } |
||||
|
|
||||
|
bool UpgradeInsecureRequests { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface IRedirectValidationConfiguration |
||||
|
{ |
||||
|
bool Enabled { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// URIs allowed for redirect. Strings in this list should be created with Uri.AbsoluteUri to assure consistency.
|
||||
|
/// </summary>
|
||||
|
IEnumerable<string> AllowedUris { get; set; } |
||||
|
|
||||
|
ISameHostHttpsRedirectConfiguration SameHostRedirectConfiguration { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface ISameHostHttpsRedirectConfiguration |
||||
|
{ |
||||
|
bool Enabled { get; set; } |
||||
|
int[] Ports { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface ISimpleBooleanConfiguration |
||||
|
{ |
||||
|
bool Enabled { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface IXFrameOptionsConfiguration |
||||
|
{ |
||||
|
XfoPolicy Policy { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface IXRobotsTagConfiguration |
||||
|
{ |
||||
|
bool Enabled { get; set; } |
||||
|
|
||||
|
bool NoIndex { get; set; } |
||||
|
|
||||
|
bool NoFollow { get; set; } |
||||
|
|
||||
|
bool NoSnippet { get; set; } |
||||
|
|
||||
|
bool NoArchive { get; set; } |
||||
|
|
||||
|
bool NoOdp { get; set; } |
||||
|
|
||||
|
bool NoTranslate { get; set; } |
||||
|
|
||||
|
bool NoImageIndex { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public interface IXXssProtectionConfiguration |
||||
|
{ |
||||
|
XXssPolicy Policy { get; set; } |
||||
|
|
||||
|
bool BlockMode { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
internal class RedirectValidationConfiguration : IRedirectValidationConfiguration |
||||
|
{ |
||||
|
public RedirectValidationConfiguration() |
||||
|
{ |
||||
|
AllowedUris = new string[0]; |
||||
|
SameHostRedirectConfiguration = new SameHostHttpsRedirectConfiguration(); |
||||
|
} |
||||
|
|
||||
|
public bool Enabled { get; set; } |
||||
|
public IEnumerable<string> AllowedUris { get; set; } |
||||
|
public ISameHostHttpsRedirectConfiguration SameHostRedirectConfiguration { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class SameHostHttpsRedirectConfiguration : ISameHostHttpsRedirectConfiguration |
||||
|
{ |
||||
|
public SameHostHttpsRedirectConfiguration() |
||||
|
{ |
||||
|
Ports = new int[0]; |
||||
|
} |
||||
|
|
||||
|
public bool Enabled { get; set; } |
||||
|
public int[] Ports { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class SimpleBooleanConfiguration : ISimpleBooleanConfiguration |
||||
|
{ |
||||
|
public bool Enabled { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Text.RegularExpressions; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration.Validation |
||||
|
{ |
||||
|
public class HpkpConfigurationValidator |
||||
|
{ |
||||
|
private static readonly string[] ValidSchemes = { "http", "https" }; |
||||
|
|
||||
|
public void ValidateNumberOfPins(IHpkpConfiguration hpkpConfig) |
||||
|
{ |
||||
|
if (hpkpConfig.MaxAge > TimeSpan.Zero && hpkpConfig.Pins.Count() < 2) |
||||
|
{ |
||||
|
throw new Exception("You must supply two or more HPKP pins. One should represent a certificate currently in use, you should also include a backup pin for a cert/key not (yet) in use."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void ValidateRawPin(string pin) |
||||
|
{ |
||||
|
var bytes = Convert.FromBase64String(pin); |
||||
|
|
||||
|
if (bytes.Length != 32) |
||||
|
{ |
||||
|
throw new Exception("Expected a 256 bit pin value, it was " + bytes.Length * 8 + " bits: " + pin); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void ValidateThumbprint(string thumbPrint) |
||||
|
{ |
||||
|
if (Regex.IsMatch(thumbPrint, "^([a-fA-F0-9]{2} ?){19}[a-fA-F0-9]{2}$")) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
throw new Exception("Malformed thumbprint, expected 20 HEX octets without any leading or trailing whitespace, was: " + thumbPrint); |
||||
|
} |
||||
|
|
||||
|
public void ValidateReportUri(string reportUri) |
||||
|
{ |
||||
|
Uri result; |
||||
|
if (!Uri.TryCreate(reportUri, UriKind.Absolute, out result)) |
||||
|
{ |
||||
|
throw new Exception("Report URIs must be absolute URIs. This is not: " + reportUri); |
||||
|
} |
||||
|
|
||||
|
if (!ValidSchemes.Any(s => s.Equals(result.Scheme))) |
||||
|
{ |
||||
|
throw new Exception("Report URIs must have the http or https scheme. Got: " + reportUri); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration.Validation |
||||
|
{ |
||||
|
public class HstsConfigurationValidator |
||||
|
{ |
||||
|
public void Validate(IHstsConfiguration hstsConfig) |
||||
|
{ |
||||
|
if (!hstsConfig.Preload) return; |
||||
|
|
||||
|
if (hstsConfig.UpgradeInsecureRequests) |
||||
|
{ |
||||
|
throw new Exception("The Preload setting cannot be combined with the UpgradeInsecureRequests setting. Use one or the other."); |
||||
|
} |
||||
|
|
||||
|
if (hstsConfig.MaxAge.TotalSeconds < 10886400 || !hstsConfig.IncludeSubdomains) |
||||
|
{ |
||||
|
throw new Exception("HSTS max age must be at least 18 weeks and includesubdomains must be enabled to use the preload directive."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Text.RegularExpressions; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration.Validation |
||||
|
{ |
||||
|
public class Rfc2045MediaTypeValidator |
||||
|
{ |
||||
|
private static readonly string[] ValidTypes = { "application", "audio", "image","model", "text", "video" }; |
||||
|
|
||||
|
public void Validate(string mediaType) |
||||
|
{ |
||||
|
if (String.IsNullOrEmpty(mediaType)) throw new ArgumentException("String was null or empty", "mediaType"); |
||||
|
|
||||
|
var components = mediaType.Split(new[] { '/' }, 2); |
||||
|
var type = components[0]; |
||||
|
|
||||
|
if (!ValidTypes.Any(t => t.Equals(type, StringComparison.OrdinalIgnoreCase))) |
||||
|
{ |
||||
|
var message = String.Format("Media type \"{0}\" did not match any of the expected types: {1}", mediaType, String.Join(", ", ValidTypes)); |
||||
|
throw new Exception(message); |
||||
|
} |
||||
|
|
||||
|
if (components.Length != 2) |
||||
|
{ |
||||
|
throw new Exception("Invalid format for media type. Expected \"type/subtype\" but was: " + mediaType); |
||||
|
} |
||||
|
|
||||
|
var subType = components[1]; |
||||
|
|
||||
|
if (!Regex.IsMatch(subType, @"^[\x00-\x7F]*$")) |
||||
|
{ |
||||
|
throw new Exception("Subtype contained characters from outside the US-ASCII range, was: " + subType); |
||||
|
} |
||||
|
|
||||
|
if (Regex.IsMatch(subType, @"[\x00-\x20\x7F]+")) |
||||
|
{ |
||||
|
throw new Exception("Subtype contained the space character, or an ASCII control character."); |
||||
|
} |
||||
|
|
||||
|
var escapedTspecials = @"[()<>@,;:""\\/[\]?=]+"; |
||||
|
|
||||
|
if (Regex.IsMatch(subType, escapedTspecials)) |
||||
|
{ |
||||
|
throw new Exception("Subtype contained one of the forbidden tspecial characters: " + Regex.Unescape(escapedTspecials)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration.Validation |
||||
|
{ |
||||
|
public class XRobotsTagConfigurationValidator |
||||
|
{ |
||||
|
public void Validate(IXRobotsTagConfiguration xRobotsConfig) |
||||
|
{ |
||||
|
if (!xRobotsConfig.Enabled) return; |
||||
|
|
||||
|
if (xRobotsConfig.NoArchive || |
||||
|
xRobotsConfig.NoFollow || |
||||
|
xRobotsConfig.NoImageIndex || |
||||
|
xRobotsConfig.NoIndex || |
||||
|
xRobotsConfig.NoOdp || |
||||
|
xRobotsConfig.NoSnippet || |
||||
|
xRobotsConfig.NoTranslate) return; |
||||
|
throw new Exception( |
||||
|
"One or more directives must be enabled when header is enabled. Enable directives or disable header."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class XFrameOptionsConfiguration : IXFrameOptionsConfiguration |
||||
|
{ |
||||
|
public XfoPolicy Policy { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class XRobotsTagConfiguration : IXRobotsTagConfiguration |
||||
|
{ |
||||
|
public bool Enabled { get; set; } |
||||
|
public bool NoIndex { get; set; } |
||||
|
public bool NoFollow { get; set; } |
||||
|
public bool NoSnippet { get; set; } |
||||
|
public bool NoArchive { get; set; } |
||||
|
public bool NoOdp { get; set; } |
||||
|
public bool NoTranslate { get; set; } |
||||
|
public bool NoImageIndex { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Configuration |
||||
|
{ |
||||
|
public class XXssProtectionConfiguration : IXXssProtectionConfiguration |
||||
|
{ |
||||
|
public XXssPolicy Policy { get; set; } |
||||
|
public bool BlockMode { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Csp |
||||
|
{ |
||||
|
internal class CspSourceParseResult |
||||
|
{ |
||||
|
public string Scheme { get; set; } |
||||
|
public string Host { get; set; } |
||||
|
public string Port { get; set; } |
||||
|
public string PathAndQuery { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,172 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Text.RegularExpressions; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Csp |
||||
|
{ |
||||
|
public class CspUriSource |
||||
|
{ |
||||
|
private const string HostRegex = @"^(\*\.)?([\p{Ll}\p{Lu}0-9\-]+)(\.[\p{Ll}\p{Lu}0-9\-]+)*$"; |
||||
|
private static readonly string SchemeOnlyRegex = "^[a-zA-Z]*[a-zA-Z0-9" + Regex.Escape("+.-") + "]:$"; |
||||
|
private static readonly string[] KnownSchemes = { "http", "https", "data", "ws", "wss" }; |
||||
|
private readonly string _source; |
||||
|
|
||||
|
private CspUriSource(string source) |
||||
|
{ |
||||
|
_source = source; |
||||
|
} |
||||
|
|
||||
|
// Returns the source as a string encoded according to the CSP spec.
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return _source; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public static string EncodeUri(Uri uri) |
||||
|
{ |
||||
|
|
||||
|
if (!uri.IsAbsoluteUri) |
||||
|
{ |
||||
|
var uriString = uri.IsWellFormedOriginalString() ? uri.ToString() : Uri.EscapeUriString(uri.ToString()); |
||||
|
return EscapeReservedCspChars(uriString); |
||||
|
} |
||||
|
|
||||
|
var host = uri.Host; |
||||
|
var encodedHost = EncodeHostname(host); |
||||
|
|
||||
|
var needsReplacement = !host.Equals(encodedHost); |
||||
|
|
||||
|
var authority = uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped); |
||||
|
|
||||
|
if (needsReplacement) |
||||
|
{ |
||||
|
authority = authority.Replace(host, encodedHost); |
||||
|
} |
||||
|
|
||||
|
if (uri.PathAndQuery.Equals("/")) |
||||
|
{ |
||||
|
return authority; |
||||
|
} |
||||
|
|
||||
|
return authority + EscapeReservedCspChars(uri.PathAndQuery); |
||||
|
} |
||||
|
|
||||
|
public static CspUriSource Parse(string source) |
||||
|
{ |
||||
|
if (String.IsNullOrEmpty(source)) throw new ArgumentException("Value was null or empty", "source"); |
||||
|
|
||||
|
if (source.Equals("*")) return new CspUriSource(source); |
||||
|
|
||||
|
Uri uriResult; //TODO figure out what happened to known schemes.
|
||||
|
if (Uri.TryCreate(source, UriKind.Absolute, out uriResult) && KnownSchemes.Contains(uriResult.Scheme)) |
||||
|
{ |
||||
|
return new CspUriSource(EncodeUri(uriResult)); |
||||
|
} |
||||
|
|
||||
|
//Scheme only source
|
||||
|
if (Regex.IsMatch(source, SchemeOnlyRegex)) return new CspUriSource(source.ToLower()); |
||||
|
|
||||
|
var parseResult = ParseSourceComponents(source); |
||||
|
var sb = new StringBuilder(); |
||||
|
|
||||
|
if (!String.IsNullOrEmpty(parseResult.Scheme)) |
||||
|
{ |
||||
|
if (!Regex.IsMatch(parseResult.Scheme, SchemeOnlyRegex)) |
||||
|
{ |
||||
|
throw new InvalidCspSourceException("Invalid scheme in CSP source: " + source); |
||||
|
} |
||||
|
sb.Append(parseResult.Scheme.ToLower()).Append("//"); |
||||
|
} |
||||
|
|
||||
|
if (String.IsNullOrEmpty(parseResult.Host)) |
||||
|
{ |
||||
|
throw new InvalidCspSourceException("Could not parse host in CSP source: " + source); |
||||
|
} |
||||
|
|
||||
|
if (!Regex.IsMatch(parseResult.Host, HostRegex)) |
||||
|
{ |
||||
|
throw new InvalidCspSourceException("Invalid host in CSP source: " + source); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
sb.Append(EncodeHostname(parseResult.Host.ToLower())); |
||||
|
|
||||
|
if (!String.IsNullOrEmpty(parseResult.Port)) |
||||
|
{ |
||||
|
if (!ValidatePort(parseResult.Port)) |
||||
|
{ |
||||
|
throw new InvalidCspSourceException("Invalid port in CSP source: " + source); |
||||
|
} |
||||
|
sb.Append(":").Append(parseResult.Port); |
||||
|
} |
||||
|
|
||||
|
if (!String.IsNullOrEmpty(parseResult.PathAndQuery)) |
||||
|
{ |
||||
|
sb.Append(EscapeReservedCspChars(Uri.EscapeUriString(parseResult.PathAndQuery))); |
||||
|
} |
||||
|
|
||||
|
return new CspUriSource(sb.ToString()); |
||||
|
} |
||||
|
|
||||
|
private static CspSourceParseResult ParseSourceComponents(string uri) |
||||
|
{ |
||||
|
const string regex = @"^((?<scheme>.*?:)\/\/)?" + // match anything up to ://
|
||||
|
@"(?<host>.*?[^:\/])" + //then match anything up to a : or /
|
||||
|
@"(:(?<port>(.*?[^\/])))?" + //then match port if exists up to a /
|
||||
|
@"(?<pathAndQuery>\/.*)?$"; //grab the rest
|
||||
|
|
||||
|
var re = new Regex(regex, RegexOptions.ExplicitCapture); |
||||
|
var result = re.Match(uri); |
||||
|
|
||||
|
if (!result.Success) |
||||
|
{ |
||||
|
throw new InvalidCspSourceException("Malformed CSP source: " + uri); |
||||
|
} |
||||
|
|
||||
|
return new CspSourceParseResult |
||||
|
{ |
||||
|
Scheme = result.Groups["scheme"].Value, |
||||
|
Host = result.Groups["host"].Value, |
||||
|
Port = result.Groups["port"].Value, |
||||
|
PathAndQuery = result.Groups["pathAndQuery"].Value |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private static string EncodeHostname(string hostname) |
||||
|
{ |
||||
|
var idn = new IdnMapping(); |
||||
|
|
||||
|
return idn.GetAscii(hostname); |
||||
|
} |
||||
|
|
||||
|
private static string EscapeReservedCspChars(string pathAndQuery) |
||||
|
{ |
||||
|
char[] encodeChars = { ';', ',' }; |
||||
|
|
||||
|
if (pathAndQuery.IndexOfAny(encodeChars) == -1) |
||||
|
{ |
||||
|
return pathAndQuery; |
||||
|
} |
||||
|
|
||||
|
var sb = new StringBuilder(pathAndQuery); |
||||
|
sb.Replace(";", "%3B"); |
||||
|
sb.Replace(",", "%2C"); |
||||
|
|
||||
|
return sb.ToString(); |
||||
|
} |
||||
|
|
||||
|
private static bool ValidatePort(string port) |
||||
|
{ |
||||
|
if (port.Equals("*")) return true; |
||||
|
|
||||
|
int portNumber; |
||||
|
var isInt = Int32.TryParse(port, out portNumber); |
||||
|
return isInt && portNumber > 0 && portNumber <= 65535; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders.Csp |
||||
|
{ |
||||
|
//[Serializable]
|
||||
|
public class InvalidCspSourceException : Exception |
||||
|
{ |
||||
|
public InvalidCspSourceException(string s) |
||||
|
: base(s) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders |
||||
|
{ |
||||
|
public class HeaderConstants |
||||
|
{ |
||||
|
public static readonly string XFrameOptionsHeader = "X-Frame-Options"; |
||||
|
public static readonly string XRobotsTagHeader = "X-Robots-Tag"; |
||||
|
public static readonly string StrictTransportSecurityHeader = "Strict-Transport-Security"; |
||||
|
public static readonly string XContentTypeOptionsHeader = "X-Content-Type-Options"; |
||||
|
public static readonly string XDownloadOptionsHeader = "X-Download-Options"; |
||||
|
public static readonly string XXssProtectionHeader = "X-XSS-Protection"; |
||||
|
public static readonly string ContentSecurityPolicyHeader = "Content-Security-Policy"; |
||||
|
public static readonly string ContentSecurityPolicyReportOnlyHeader = "Content-Security-Policy-Report-Only"; |
||||
|
public static readonly string HpkpHeader = "Public-Key-Pins"; |
||||
|
public static readonly string HpkpReportOnlyHeader = "Public-Key-Pins-Report-Only"; |
||||
|
|
||||
|
public static readonly string[] CspSourceList = |
||||
|
{ |
||||
|
"'none'", |
||||
|
"'self'", |
||||
|
"'unsafe-inline'", |
||||
|
"'unsafe-eval'" |
||||
|
}; |
||||
|
|
||||
|
public static readonly string[] CspDirectives = |
||||
|
{ |
||||
|
"default-src", |
||||
|
"script-src", |
||||
|
"object-src", |
||||
|
"style-src", |
||||
|
"img-src", |
||||
|
"media-src", |
||||
|
"frame-src", |
||||
|
"font-src", |
||||
|
"connect-src", |
||||
|
"report-uri" |
||||
|
}; |
||||
|
|
||||
|
public static readonly string[] CspSchemes = |
||||
|
{ |
||||
|
"data:", |
||||
|
"https:", |
||||
|
"http:" |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,383 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using NWebsec.Annotations; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders |
||||
|
{ |
||||
|
public class HeaderGenerator : IHeaderGenerator |
||||
|
{ |
||||
|
[CanBeNull] |
||||
|
public HeaderResult CreateXRobotsTagResult(IXRobotsTagConfiguration xRobotsTagConfig, |
||||
|
IXRobotsTagConfiguration oldXRobotsTagConfig = null) |
||||
|
{ |
||||
|
if (oldXRobotsTagConfig != null && oldXRobotsTagConfig.Enabled && xRobotsTagConfig.Enabled == false) |
||||
|
{ |
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Remove, HeaderConstants.XRobotsTagHeader); |
||||
|
} |
||||
|
|
||||
|
if (xRobotsTagConfig.Enabled == false) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var sb = new StringBuilder(); |
||||
|
sb.Append(xRobotsTagConfig.NoIndex ? "noindex, " : String.Empty); |
||||
|
sb.Append(xRobotsTagConfig.NoFollow ? "nofollow, " : String.Empty); |
||||
|
sb.Append(xRobotsTagConfig.NoSnippet && !xRobotsTagConfig.NoIndex ? "nosnippet, " : String.Empty); |
||||
|
sb.Append(xRobotsTagConfig.NoArchive && !xRobotsTagConfig.NoIndex ? "noarchive, " : String.Empty); |
||||
|
sb.Append(xRobotsTagConfig.NoOdp && !xRobotsTagConfig.NoIndex ? "noodp, " : String.Empty); |
||||
|
sb.Append(xRobotsTagConfig.NoTranslate && !xRobotsTagConfig.NoIndex ? "notranslate, " : String.Empty); |
||||
|
sb.Append(xRobotsTagConfig.NoImageIndex ? "noimageindex" : String.Empty); |
||||
|
var value = sb.ToString().TrimEnd(' ', ','); |
||||
|
|
||||
|
if (value.Length == 0) return null; |
||||
|
|
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Set, HeaderConstants.XRobotsTagHeader, value); |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public HeaderResult CreateHstsResult(IHstsConfiguration hstsConfig) |
||||
|
{ |
||||
|
if (hstsConfig.MaxAge < TimeSpan.Zero) return null; |
||||
|
|
||||
|
if (hstsConfig.Preload && (hstsConfig.MaxAge.TotalSeconds < 10886400 || !hstsConfig.IncludeSubdomains)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var seconds = (int)hstsConfig.MaxAge.TotalSeconds; |
||||
|
|
||||
|
var includeSubdomains = (hstsConfig.IncludeSubdomains ? "; includeSubdomains" : ""); |
||||
|
var preload = (hstsConfig.Preload ? "; preload" : ""); |
||||
|
var value = string.Format("max-age={0}{1}{2}", seconds, includeSubdomains, preload); |
||||
|
|
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Set, HeaderConstants.StrictTransportSecurityHeader, |
||||
|
value); |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public HeaderResult CreateXContentTypeOptionsResult(ISimpleBooleanConfiguration xContentTypeOptionsConfig, |
||||
|
ISimpleBooleanConfiguration oldXContentTypeOptionsConfig = null) |
||||
|
{ |
||||
|
if (oldXContentTypeOptionsConfig != null && oldXContentTypeOptionsConfig.Enabled && |
||||
|
!xContentTypeOptionsConfig.Enabled) |
||||
|
{ |
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Remove, HeaderConstants.XContentTypeOptionsHeader); |
||||
|
} |
||||
|
|
||||
|
return xContentTypeOptionsConfig.Enabled |
||||
|
? new HeaderResult(HeaderResult.ResponseAction.Set, HeaderConstants.XContentTypeOptionsHeader, "nosniff") |
||||
|
: null; |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public HeaderResult CreateXDownloadOptionsResult(ISimpleBooleanConfiguration xDownloadOptionsConfig, |
||||
|
ISimpleBooleanConfiguration oldXDownloadOptionsConfig = null) |
||||
|
{ |
||||
|
if (oldXDownloadOptionsConfig != null && oldXDownloadOptionsConfig.Enabled && |
||||
|
!xDownloadOptionsConfig.Enabled) |
||||
|
{ |
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Remove, HeaderConstants.XDownloadOptionsHeader); |
||||
|
} |
||||
|
return xDownloadOptionsConfig.Enabled |
||||
|
? new HeaderResult(HeaderResult.ResponseAction.Set, HeaderConstants.XDownloadOptionsHeader, "noopen") |
||||
|
: null; |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public HeaderResult CreateXXssProtectionResult(IXXssProtectionConfiguration xXssProtectionConfig, |
||||
|
IXXssProtectionConfiguration oldXXssProtectionConfig = null) |
||||
|
{ |
||||
|
if (oldXXssProtectionConfig != null && oldXXssProtectionConfig.Policy != XXssPolicy.Disabled && |
||||
|
xXssProtectionConfig.Policy == XXssPolicy.Disabled) |
||||
|
{ |
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Remove, HeaderConstants.XXssProtectionHeader); |
||||
|
} |
||||
|
|
||||
|
string value; |
||||
|
switch (xXssProtectionConfig.Policy) |
||||
|
{ |
||||
|
case XXssPolicy.Disabled: |
||||
|
return null; |
||||
|
|
||||
|
case XXssPolicy.FilterDisabled: |
||||
|
value = "0"; |
||||
|
break; |
||||
|
|
||||
|
case XXssPolicy.FilterEnabled: |
||||
|
value = (xXssProtectionConfig.BlockMode ? "1; mode=block" : "1"); |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
throw new NotImplementedException("Somebody apparently forgot to implement support for: " + |
||||
|
xXssProtectionConfig.Policy); |
||||
|
} |
||||
|
|
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Set, HeaderConstants.XXssProtectionHeader, value); |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public HeaderResult CreateXfoResult(IXFrameOptionsConfiguration xfoConfig, |
||||
|
IXFrameOptionsConfiguration oldXfoConfig = null) |
||||
|
{ |
||||
|
if (oldXfoConfig != null && oldXfoConfig.Policy != XfoPolicy.Disabled && |
||||
|
xfoConfig.Policy == XfoPolicy.Disabled) |
||||
|
{ |
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Remove, HeaderConstants.XFrameOptionsHeader); |
||||
|
} |
||||
|
|
||||
|
switch (xfoConfig.Policy) |
||||
|
{ |
||||
|
case XfoPolicy.Disabled: |
||||
|
return null; |
||||
|
|
||||
|
case XfoPolicy.Deny: |
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Set, HeaderConstants.XFrameOptionsHeader, "Deny"); |
||||
|
|
||||
|
case XfoPolicy.SameOrigin: |
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Set, HeaderConstants.XFrameOptionsHeader, |
||||
|
"SameOrigin"); |
||||
|
|
||||
|
default: |
||||
|
throw new NotImplementedException("Apparently someone forgot to implement support for: " + |
||||
|
xfoConfig.Policy); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public HeaderResult CreateHpkpResult(IHpkpConfiguration hpkpConfig, bool reportOnly) |
||||
|
{ |
||||
|
if (hpkpConfig.MaxAge < TimeSpan.Zero || hpkpConfig.Pins == null || !hpkpConfig.Pins.Any()) return null; |
||||
|
|
||||
|
var headerName = reportOnly ? HeaderConstants.HpkpReportOnlyHeader : HeaderConstants.HpkpHeader; |
||||
|
|
||||
|
var seconds = (int)hpkpConfig.MaxAge.TotalSeconds; |
||||
|
//Unpinning. Save a few bytes by ignoring other directives.
|
||||
|
if (seconds == 0) |
||||
|
{ |
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Set, headerName, "max-age=" + seconds); |
||||
|
} |
||||
|
|
||||
|
var sb = new StringBuilder(); |
||||
|
sb.Append("max-age=").Append(seconds).Append(";"); |
||||
|
|
||||
|
if (hpkpConfig.IncludeSubdomains) |
||||
|
{ |
||||
|
sb.Append("includeSubdomains;"); |
||||
|
} |
||||
|
|
||||
|
foreach (var pin in hpkpConfig.Pins) |
||||
|
{ |
||||
|
sb.Append("pin-").Append(pin).Append(";"); |
||||
|
} |
||||
|
|
||||
|
if (string.IsNullOrEmpty(hpkpConfig.ReportUri)) |
||||
|
{ |
||||
|
sb.Remove(sb.Length - 1, 1); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
sb.Append("report-uri=\"").Append(hpkpConfig.ReportUri).Append("\""); |
||||
|
} |
||||
|
|
||||
|
var value = sb.ToString(); |
||||
|
|
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Set, headerName, value); |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public HeaderResult CreateCspResult(ICspConfiguration cspConfig, bool reportOnly, |
||||
|
string builtinReportHandlerUri = null, ICspConfiguration oldCspConfig = null) |
||||
|
{ |
||||
|
var headerValue = cspConfig.Enabled ? CreateCspHeaderValue(cspConfig, builtinReportHandlerUri) : null; |
||||
|
|
||||
|
if (oldCspConfig != null && oldCspConfig.Enabled) |
||||
|
{ |
||||
|
if (!cspConfig.Enabled || headerValue == null) |
||||
|
{ |
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Remove, |
||||
|
(reportOnly ? HeaderConstants.ContentSecurityPolicyReportOnlyHeader : HeaderConstants.ContentSecurityPolicyHeader)); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!cspConfig.Enabled || headerValue == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return new HeaderResult(HeaderResult.ResponseAction.Set, |
||||
|
(reportOnly ? HeaderConstants.ContentSecurityPolicyReportOnlyHeader : HeaderConstants.ContentSecurityPolicyHeader), headerValue); |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
private string CreateCspHeaderValue(ICspConfiguration config, string builtinReportHandlerUri = null) |
||||
|
{ |
||||
|
var sb = new StringBuilder(); |
||||
|
|
||||
|
AppendDirective(sb, "default-src", GetDirectiveList(config.DefaultSrcDirective)); |
||||
|
AppendDirective(sb, "script-src", GetDirectiveList(config.ScriptSrcDirective)); |
||||
|
AppendDirective(sb, "object-src", GetDirectiveList(config.ObjectSrcDirective)); |
||||
|
AppendDirective(sb, "style-src", GetDirectiveList(config.StyleSrcDirective)); |
||||
|
AppendDirective(sb, "img-src", GetDirectiveList(config.ImgSrcDirective)); |
||||
|
AppendDirective(sb, "media-src", GetDirectiveList(config.MediaSrcDirective)); |
||||
|
AppendDirective(sb, "frame-src", GetDirectiveList(config.FrameSrcDirective)); |
||||
|
AppendDirective(sb, "font-src", GetDirectiveList(config.FontSrcDirective)); |
||||
|
AppendDirective(sb, "connect-src", GetDirectiveList(config.ConnectSrcDirective)); |
||||
|
AppendDirective(sb, "base-uri", GetDirectiveList(config.BaseUriDirective)); |
||||
|
AppendDirective(sb, "child-src", GetDirectiveList(config.ChildSrcDirective)); |
||||
|
AppendDirective(sb, "form-action", GetDirectiveList(config.FormActionDirective)); |
||||
|
AppendDirective(sb, "frame-ancestors", GetDirectiveList(config.FrameAncestorsDirective)); |
||||
|
AppendDirective(sb, "plugin-types", GetPluginTypesDirectiveList(config.PluginTypesDirective)); |
||||
|
AppendDirective(sb, "sandbox", GetSandboxDirectiveList(config.SandboxDirective)); |
||||
|
AppendUpgradeDirective(sb, "upgrade-insecure-requests", config.UpgradeInsecureRequestsDirective); |
||||
|
|
||||
|
if (sb.Length == 0) return null; |
||||
|
|
||||
|
AppendDirective(sb, "report-uri", |
||||
|
GetReportUriList(config.ReportUriDirective, builtinReportHandlerUri)); |
||||
|
|
||||
|
//Get rid of trailing ;
|
||||
|
sb.Length--; |
||||
|
return sb.ToString(); |
||||
|
} |
||||
|
|
||||
|
private void AppendDirective(StringBuilder sb, string directiveName, List<string> sources) |
||||
|
{ |
||||
|
if (sources == null) return; |
||||
|
|
||||
|
sb.Append(directiveName); |
||||
|
|
||||
|
foreach (var source in sources) |
||||
|
{ |
||||
|
sb.Append(' ').Append(source); |
||||
|
} |
||||
|
|
||||
|
sb.Append(';'); |
||||
|
} |
||||
|
|
||||
|
private void AppendUpgradeDirective(StringBuilder sb, string directiveName, ICspUpgradeDirectiveConfiguration config) |
||||
|
{ |
||||
|
if (!config.Enabled) return; |
||||
|
|
||||
|
sb.Append(directiveName); |
||||
|
sb.Append(';'); |
||||
|
} |
||||
|
|
||||
|
private List<string> GetDirectiveList(ICspDirectiveConfiguration directive) |
||||
|
{ |
||||
|
if (directive == null || !directive.Enabled) |
||||
|
return null; |
||||
|
|
||||
|
var sources = new List<string>(); |
||||
|
|
||||
|
if (directive.NoneSrc) |
||||
|
{ |
||||
|
sources.Add("'none'"); |
||||
|
} |
||||
|
|
||||
|
if (directive.SelfSrc) |
||||
|
{ |
||||
|
sources.Add("'self'"); |
||||
|
} |
||||
|
|
||||
|
if (directive.UnsafeInlineSrc) |
||||
|
{ |
||||
|
sources.Add("'unsafe-inline'"); |
||||
|
} |
||||
|
|
||||
|
if (!String.IsNullOrEmpty(directive.Nonce)) |
||||
|
{ |
||||
|
var nonce = $"'nonce-{directive.Nonce}'"; |
||||
|
sources.Add(nonce); |
||||
|
} |
||||
|
|
||||
|
if (directive.UnsafeEvalSrc) |
||||
|
{ |
||||
|
sources.Add("'unsafe-eval'"); |
||||
|
} |
||||
|
|
||||
|
if (directive.CustomSources != null) |
||||
|
{ |
||||
|
sources.AddRange(directive.CustomSources); |
||||
|
} |
||||
|
|
||||
|
return sources.Count > 0 ? sources : null; |
||||
|
} |
||||
|
|
||||
|
private List<string> GetPluginTypesDirectiveList(ICspPluginTypesDirectiveConfiguration directive) |
||||
|
{ |
||||
|
if (directive == null || !directive.Enabled || !directive.MediaTypes.Any()) |
||||
|
return null; |
||||
|
|
||||
|
//We know there are MediaTypes, so not null.
|
||||
|
return new List<string>(directive.MediaTypes); |
||||
|
} |
||||
|
|
||||
|
private List<string> GetSandboxDirectiveList(ICspSandboxDirectiveConfiguration directive) |
||||
|
{ |
||||
|
if (directive == null || !directive.Enabled) |
||||
|
return null; |
||||
|
|
||||
|
var sources = new List<string>(); |
||||
|
|
||||
|
if (directive.AllowForms) |
||||
|
{ |
||||
|
sources.Add("allow-forms"); |
||||
|
} |
||||
|
|
||||
|
if (directive.AllowPointerLock) |
||||
|
{ |
||||
|
sources.Add("allow-pointer-lock"); |
||||
|
} |
||||
|
|
||||
|
if (directive.AllowPopups) |
||||
|
{ |
||||
|
sources.Add("allow-popups"); |
||||
|
} |
||||
|
|
||||
|
if (directive.AllowSameOrigin) |
||||
|
{ |
||||
|
sources.Add("allow-same-origin"); |
||||
|
} |
||||
|
|
||||
|
if (directive.AllowScripts) |
||||
|
{ |
||||
|
sources.Add("allow-scripts"); |
||||
|
} |
||||
|
|
||||
|
if (directive.AllowTopNavigation) |
||||
|
{ |
||||
|
sources.Add("allow-top-navigation"); |
||||
|
} |
||||
|
|
||||
|
return sources; //We want to return empty list and not null
|
||||
|
} |
||||
|
|
||||
|
private List<string> GetReportUriList(ICspReportUriDirectiveConfiguration directive, |
||||
|
string builtinReportHandlerUri = null) |
||||
|
{ |
||||
|
if (directive == null || !directive.Enabled) |
||||
|
return null; |
||||
|
|
||||
|
var reportUris = new List<string>(); |
||||
|
|
||||
|
if (directive.EnableBuiltinHandler) |
||||
|
{ |
||||
|
reportUris.Add(builtinReportHandlerUri); |
||||
|
} |
||||
|
|
||||
|
if (directive.ReportUris != null) |
||||
|
{ |
||||
|
reportUris.AddRange(directive.ReportUris); |
||||
|
} |
||||
|
|
||||
|
return reportUris.Count > 0 ? reportUris : null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders |
||||
|
{ |
||||
|
public class HeaderResult |
||||
|
{ |
||||
|
public enum ResponseAction |
||||
|
{ |
||||
|
Set = 0, |
||||
|
Remove = 1 |
||||
|
} |
||||
|
|
||||
|
public HeaderResult(ResponseAction action, string name, string value = null) |
||||
|
{ |
||||
|
Action = action; |
||||
|
Name = name; |
||||
|
Value = value; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public ResponseAction Action { get; set; } |
||||
|
public string Name { get; set; } |
||||
|
public string Value { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders |
||||
|
{ |
||||
|
public interface IHeaderGenerator |
||||
|
{ |
||||
|
HeaderResult CreateXRobotsTagResult(IXRobotsTagConfiguration xRobotsTagConfig, |
||||
|
IXRobotsTagConfiguration oldXRobotsTagConfig = null); |
||||
|
|
||||
|
HeaderResult CreateHstsResult(IHstsConfiguration hstsConfig); |
||||
|
|
||||
|
HeaderResult CreateXContentTypeOptionsResult(ISimpleBooleanConfiguration xContentTypeOptionsConfig, |
||||
|
ISimpleBooleanConfiguration oldXContentTypeOptionsConfig = null); |
||||
|
|
||||
|
HeaderResult CreateXDownloadOptionsResult(ISimpleBooleanConfiguration xDownloadOptionsConfig, |
||||
|
ISimpleBooleanConfiguration oldXDownloadOptionsConfig = null); |
||||
|
|
||||
|
HeaderResult CreateXXssProtectionResult(IXXssProtectionConfiguration xXssProtectionConfig, |
||||
|
IXXssProtectionConfiguration oldXXssProtectionConfig = null); |
||||
|
|
||||
|
HeaderResult CreateXfoResult(IXFrameOptionsConfiguration xfoConfig, |
||||
|
IXFrameOptionsConfiguration oldXfoConfig = null); |
||||
|
|
||||
|
HeaderResult CreateCspResult(ICspConfiguration cspConfig, bool reportOnly, |
||||
|
string builtinReportHandlerUri = null, ICspConfiguration oldCspConfig = null); |
||||
|
|
||||
|
HeaderResult CreateHpkpResult(IHpkpConfiguration hpkpConfig, bool reportOnly); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders |
||||
|
{ |
||||
|
public enum XXssPolicy |
||||
|
{ |
||||
|
/// <summary>Specifies that the X-Xss-Protection header should not be set in the HTTP response.</summary>
|
||||
|
Disabled, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies that the X-Xss-Protection header should be set in the HTTP response, explicitly disabling the IE XSS
|
||||
|
/// filter.
|
||||
|
/// </summary>
|
||||
|
FilterDisabled, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies that the X-Xss-Protection header should be set in the HTTP response, explicitly enabling the IE XSS
|
||||
|
/// filter.
|
||||
|
/// </summary>
|
||||
|
FilterEnabled |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Core.HttpHeaders |
||||
|
{ |
||||
|
public enum XfoPolicy |
||||
|
{ |
||||
|
/// <summary>Specifies that the X-Frame-Options header should not be set in the HTTP response.</summary>
|
||||
|
Disabled, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies that the X-Frame-Options header should be set in the HTTP response, instructing the browser to not
|
||||
|
/// display the page when it is loaded in an iframe.
|
||||
|
/// </summary>
|
||||
|
Deny, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies that the X-Frame-Options header should be set in the HTTP response, instructing the browser to
|
||||
|
/// display the page when it is loaded in an iframe - but only if the iframe is from the same origin as the page.
|
||||
|
/// </summary>
|
||||
|
SameOrigin |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<PropertyGroup> |
||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> |
||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> |
||||
|
</PropertyGroup> |
||||
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> |
||||
|
<PropertyGroup Label="Globals"> |
||||
|
<ProjectGuid>e9575361-5c4b-425a-bca5-0e8249c6c5fb</ProjectGuid> |
||||
|
<RootNamespace>NWebsec.Core</RootNamespace> |
||||
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> |
||||
|
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup> |
||||
|
<SchemaVersion>2.0</SchemaVersion> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
||||
|
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild> |
||||
|
</PropertyGroup> |
||||
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" /> |
||||
|
</Project> |
||||
@ -0,0 +1,28 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Core |
||||
|
{ |
||||
|
public class NWebsecContext |
||||
|
{ |
||||
|
public static readonly string ContextKey = "nwebsec.Context"; |
||||
|
|
||||
|
public ISimpleBooleanConfiguration NoCacheHeaders { get; set; } |
||||
|
public ISimpleBooleanConfiguration XContentTypeOptions { get; set; } |
||||
|
public ISimpleBooleanConfiguration XDownloadOptions { get; set; } |
||||
|
public IXFrameOptionsConfiguration XFrameOptions { get; set; } |
||||
|
public IXRobotsTagConfiguration XRobotsTag { get; set; } |
||||
|
public IXXssProtectionConfiguration XXssProtection { get; set; } |
||||
|
public ICspConfiguration Csp { get; set; } |
||||
|
public ICspConfiguration CspReportOnly { get; set; } |
||||
|
|
||||
|
public ConfigurationOverrides ConfigOverrides { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class ConfigurationOverrides |
||||
|
{ |
||||
|
public ICspConfiguration CspOverride { get; set; } |
||||
|
public ICspConfiguration CspReportOnlyOverride { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System.Reflection; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
// General Information about an assembly is controlled through the following
|
||||
|
// set of attributes. Change these attribute values to modify the information
|
||||
|
// associated with an assembly.
|
||||
|
[assembly: AssemblyTitle("NWebsec.Core")] |
||||
|
[assembly: AssemblyDescription("Core features for the NWebsec security libraries.")] |
||||
|
[assembly: AssemblyConfiguration("")] |
||||
|
[assembly: AssemblyCompany("Andre N. Klingsheim")] |
||||
|
[assembly: AssemblyProduct("NWebsec.Core")] |
||||
|
[assembly: AssemblyCopyright("Copyright © 2013 - 2015")] |
||||
|
[assembly: AssemblyTrademark("")] |
||||
|
[assembly: AssemblyCulture("")] |
||||
|
|
||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
|
// to COM components. If you need to access a type in this assembly from
|
||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||
|
[assembly: ComVisible(false)] |
||||
|
|
||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
|
[assembly: Guid("e9575361-5c4b-425a-bca5-0e8249c6c5fb")] |
||||
|
|
||||
|
[assembly:InternalsVisibleTo("NWebsec.Core.Tests")] |
||||
@ -0,0 +1,83 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using NWebsec.Core.Exceptions; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Core |
||||
|
{ |
||||
|
public class RedirectValidator |
||||
|
{ |
||||
|
public void ValidateRedirect(int statusCode, string locationHeader, Uri requestAuthority, |
||||
|
IRedirectValidationConfiguration config) |
||||
|
{ |
||||
|
if (!config.Enabled) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
//Not a redirect
|
||||
|
if (!IsRedirectStatusCode(statusCode)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
//No location header
|
||||
|
if (String.IsNullOrEmpty(locationHeader)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Uri locationUri; |
||||
|
if (!Uri.TryCreate(locationHeader, UriKind.RelativeOrAbsolute, out locationUri)) |
||||
|
{ |
||||
|
throw new Exception("Unable to parse location header value as URI. Value was: " + locationHeader); |
||||
|
} |
||||
|
|
||||
|
//Relative Uri
|
||||
|
if (!locationUri.IsAbsoluteUri) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Same origin TODO look into URL encoding
|
||||
|
if (locationUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped).Equals(requestAuthority.GetComponents(UriComponents.SchemeAndServer,UriFormat.SafeUnescaped))) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
//Same host https
|
||||
|
if (config.SameHostRedirectConfiguration.Enabled && locationUri.Scheme.Equals("https") && requestAuthority.Host.Equals(locationUri.Host)) |
||||
|
{ |
||||
|
var sameHostConfig = config.SameHostRedirectConfiguration; |
||||
|
|
||||
|
if (sameHostConfig.Ports.Length == 0 && locationUri.IsDefaultPort) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (sameHostConfig.Ports.Contains(locationUri.Port)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
throw new RedirectValidationException("A potentially dangerous redirect was detected. Allow same host redirects to this port number in configuration if the redirect was intended. Offending redirect: " + locationHeader); |
||||
|
} |
||||
|
|
||||
|
// Allowed Uri
|
||||
|
if (config.AllowedUris.Any(locationUri.AbsoluteUri.StartsWith)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
throw new RedirectValidationException( |
||||
|
"A potentially dangerous redirect was detected. Add the destination to the whitelist in configuration if the redirect was intended. Offending redirect: " + |
||||
|
locationHeader); |
||||
|
} |
||||
|
|
||||
|
public bool IsRedirectStatusCode(int statusCode) |
||||
|
{ |
||||
|
return statusCode >= 300 && statusCode < 400 && statusCode != 304; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
{ |
||||
|
|
||||
|
"version": "2.0.0-gamma-custom-*", |
||||
|
"title": "NWebsec.Core (ASP.NET 5)", |
||||
|
"description": "This library provides core functionality for the NWebsec security libraries.", |
||||
|
"releaseNotes": "This release includes core functions for the NWebsec security libaries.", |
||||
|
"authors": [ "André N. Klingsheim" ], |
||||
|
"owners": [ "André N. Klingsheim" ], |
||||
|
"copyright": "Copyright © 2014 - 2015", |
||||
|
"tags": [ "NWebsec Security AspNet AppSec" ], |
||||
|
"projectUrl": "https://docs.nwebsec.com/en/latest/", |
||||
|
"licenseUrl": "https://github.com/NWebsec/NWebsec/blob/master/LICENSE", |
||||
|
"iconUrl": "https://klings.blob.core.windows.net/nwebsecicon/nwebsec_nuget.png", |
||||
|
"frameworks": { |
||||
|
"dnx451": { }, |
||||
|
"net451": { }, |
||||
|
"dotnet5.4": { |
||||
|
"dependencies": { |
||||
|
"Microsoft.CSharp": "4.0.1-beta-23516", |
||||
|
"System.Runtime": "4.0.21-beta-23516", |
||||
|
"System.Security.Cryptography.Algorithms": "4.0.0-beta-23516", |
||||
|
"System.Security.Cryptography.X509Certificates": "4.0.0-beta-23516", |
||||
|
"System.Text.RegularExpressions": "4.0.11-beta-23516" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"Microsoft.AspNet.Http.Abstractions": "1.0.0-rc2-*", |
||||
|
"NWebsec.Annotations": { |
||||
|
"version": "1.0.0-*", |
||||
|
"type": "build" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,197 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration.Validation; |
||||
|
using NWebsec.Middleware; |
||||
|
using NWebsec.Middleware.Middleware; |
||||
|
|
||||
|
// ReSharper disable once CheckNamespace
|
||||
|
namespace Microsoft.AspNet.Builder |
||||
|
{ |
||||
|
public static class ApplicationBuilderExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the pipeline that validates redirects.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseRedirectValidation(this IApplicationBuilder app) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
|
||||
|
var options = new RedirectValidationOptions(); |
||||
|
return app.UseMiddleware<RedirectValidationMiddleware>(options); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that validates redirects.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <param name="configurer">An <see cref="Action" /> that configures the options for the middleware.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseRedirectValidation(this IApplicationBuilder app, Action<IFluentRedirectValidationOptions> configurer) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
if (configurer == null) throw new ArgumentNullException(nameof(configurer)); |
||||
|
|
||||
|
var options = new RedirectValidationOptions(); |
||||
|
configurer(options); |
||||
|
return app.UseMiddleware<RedirectValidationMiddleware>(options); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that sets the Strict-Transport-Security header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <param name="configurer">An <see cref="Action" /> that configures the options for the middleware.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseHsts(this IApplicationBuilder app, Action<IFluentHstsOptions> configurer) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
if (configurer == null) throw new ArgumentNullException(nameof(configurer)); |
||||
|
|
||||
|
var options = new HstsOptions(); |
||||
|
configurer(options); |
||||
|
new HstsConfigurationValidator().Validate(options); |
||||
|
return app.UseMiddleware<HstsMiddleware>(options); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that sets the Public-Key-Pins header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <param name="configurer">An <see cref="Action" /> that configures the options for the middleware.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseHpkp(this IApplicationBuilder app, Action<IFluentHpkpOptions> configurer) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
if (configurer == null) throw new ArgumentNullException(nameof(configurer)); |
||||
|
|
||||
|
var options = new HpkpOptions(); |
||||
|
configurer(options); |
||||
|
new HpkpConfigurationValidator().ValidateNumberOfPins(options.Config); |
||||
|
return app.UseMiddleware<HpkpMiddleware>(options, false); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that sets the Public-Key-Pins-Report-Only header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <param name="configurer">An <see cref="Action" /> that configures the options for the middleware.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseHpkpReportOnly(this IApplicationBuilder app, Action<IFluentHpkpOptions> configurer) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
if (configurer == null) throw new ArgumentNullException(nameof(configurer)); |
||||
|
|
||||
|
var options = new HpkpOptions(); |
||||
|
configurer(options); |
||||
|
new HpkpConfigurationValidator().ValidateNumberOfPins(options.Config); |
||||
|
return app.UseMiddleware<HpkpMiddleware>(options, true); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that sets the X-Content-Type-Options header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseXContentTypeOptions(this IApplicationBuilder app) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
|
||||
|
return app.UseMiddleware<XContentTypeOptionsMiddleware>(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that sets the X-Download-Options header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseXDownloadOptions(this IApplicationBuilder app) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
|
||||
|
return app.UseMiddleware<XDownloadOptionsMiddleware>(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that sets the X-Frame-Options header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <param name="configurer">An <see cref="Action" /> that configures the options for the middleware.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseXfo(this IApplicationBuilder app, Action<IFluentXFrameOptions> configurer) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
if (configurer == null) throw new ArgumentNullException(nameof(configurer)); |
||||
|
|
||||
|
var options = new XFrameOptions(); |
||||
|
configurer(options); |
||||
|
return app.UseMiddleware<XfoMiddleware>(options); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that sets the X-Robots-Tag header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <param name="configurer">An <see cref="Action" /> that configures the options for the middleware.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseXRobotsTag(this IApplicationBuilder app, Action<IFluentXRobotsTagOptions> configurer) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
if (configurer == null) throw new ArgumentNullException(nameof(configurer)); |
||||
|
|
||||
|
var options = new XRobotsTagOptions(); |
||||
|
configurer(options); |
||||
|
return app.UseMiddleware<XRobotsTagMiddleware>(options); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that sets the X-Xss-Protection header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <param name="configurer">An <see cref="Action" /> that configures the options for the middleware.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseXXssProtection(this IApplicationBuilder app, Action<IFluentXXssProtectionOptions> configurer) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
if (configurer == null) throw new ArgumentNullException(nameof(configurer)); |
||||
|
|
||||
|
var options = new XXssProtectionOptions(); |
||||
|
configurer(options); |
||||
|
return app.UseMiddleware<XXssMiddleware>(options); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that sets the Content-Security-Policy header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <param name="configurer">An <see cref="Action" /> that configures the options for the middleware.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseCsp(this IApplicationBuilder app, Action<IFluentCspOptions> configurer) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
if (configurer == null) throw new ArgumentNullException(nameof(configurer)); |
||||
|
|
||||
|
var options = new CspOptions(); |
||||
|
configurer(options); |
||||
|
return app.UseMiddleware<CspMiddleware>(options, false); //Last param indicates it's not reportOnly.
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds a middleware to the ASP.NET pipeline that sets the Content-Security-Policy-Report-Only header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The <see cref="IApplicationBuilder" /> to which the middleware is added.</param>
|
||||
|
/// <param name="configurer">An <see cref="Action" /> that configures the options for the middleware.</param>
|
||||
|
/// <returns>The <see cref="IApplicationBuilder" /> supplied in the app parameter.</returns>
|
||||
|
public static IApplicationBuilder UseCspReportOnly(this IApplicationBuilder app, Action<IFluentCspOptions> configurer) |
||||
|
{ |
||||
|
if (app == null) throw new ArgumentNullException(nameof(app)); |
||||
|
if (configurer == null) throw new ArgumentNullException(nameof(configurer)); |
||||
|
|
||||
|
var options = new CspOptions(); |
||||
|
configurer(options); |
||||
|
return app.UseMiddleware<CspMiddleware>(options, true); //Last param indicates it's reportOnly.
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using NWebsec.Core; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Core |
||||
|
{ |
||||
|
//TODO Get rid of these?
|
||||
|
internal class OwinEnvironment |
||||
|
{ |
||||
|
private readonly IDictionary<string, object> _environment; |
||||
|
|
||||
|
internal OwinEnvironment(IDictionary<string, object> env) |
||||
|
{ |
||||
|
_environment = env; |
||||
|
RequestHeaders = new RequestHeaders((IDictionary<string, string[]>)_environment[OwinKeys.RequestHeaders]); |
||||
|
ResponseHeaders = new ResponseHeaders((IDictionary<string, string[]>)_environment[OwinKeys.ResponseHeaders]); |
||||
|
} |
||||
|
|
||||
|
internal string RequestScheme => (string)_environment[OwinKeys.RequestScheme]; |
||||
|
|
||||
|
internal string RequestPathBase => (string)_environment[OwinKeys.RequestPathBase]; |
||||
|
|
||||
|
internal string RequestPath => (string)_environment[OwinKeys.RequestPath]; |
||||
|
|
||||
|
internal int ResponseStatusCode |
||||
|
{ |
||||
|
get { return (int)_environment[OwinKeys.ResponseStatusCode]; } |
||||
|
set { _environment[OwinKeys.ResponseStatusCode] = value; } |
||||
|
} |
||||
|
|
||||
|
internal RequestHeaders RequestHeaders { get; private set; } |
||||
|
|
||||
|
internal ResponseHeaders ResponseHeaders { get; private set; } |
||||
|
|
||||
|
internal NWebsecContext NWebsecContext |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (!_environment.ContainsKey(NWebsecContext.ContextKey)) |
||||
|
{ |
||||
|
_environment[NWebsecContext.ContextKey] = new NWebsecContext(); |
||||
|
} |
||||
|
|
||||
|
return _environment[NWebsecContext.ContextKey] as NWebsecContext; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
namespace NWebsec.Middleware.Core |
||||
|
{ |
||||
|
internal static class OwinKeys |
||||
|
{ |
||||
|
//Request
|
||||
|
internal static string RequestBody = "owin.RequestBody"; |
||||
|
internal static string RequestHeaders = "owin.RequestHeaders"; |
||||
|
internal static string RequestMethod = "owin.RequestMethod"; |
||||
|
internal static string RequestPath = "owin.RequestPath"; |
||||
|
internal static string RequestPathBase = "owin.RequestPathBase"; |
||||
|
internal static string RequestProtocol = "owin.RequestProtocol"; |
||||
|
internal static string RequestQueryString = "owin.RequestQueryString"; |
||||
|
internal static string RequestScheme = "owin.RequestScheme"; |
||||
|
|
||||
|
//Response
|
||||
|
internal static string ResponseBody = "owin.ResponseBody"; |
||||
|
internal static string ResponseHeaders = "owin.ResponseHeaders"; |
||||
|
internal static string ResponseStatusCode = "owin.ResponseStatusCode"; |
||||
|
internal static string ResponseReasonPhrase = "owin.ResponseReasonPhrase"; |
||||
|
internal static string ResponseProtocol = "owin.ResponseProtocol"; |
||||
|
|
||||
|
//Other
|
||||
|
internal static string CallCancelled = "owin.CallCancelled"; |
||||
|
internal static string Version = "owin.Version"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Core |
||||
|
{ |
||||
|
internal class RequestHeaders |
||||
|
{ |
||||
|
private readonly IDictionary<string, string[]> _headers; |
||||
|
|
||||
|
internal RequestHeaders(IDictionary<string, string[]> headers) |
||||
|
{ |
||||
|
_headers = headers; |
||||
|
} |
||||
|
|
||||
|
public string Host |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return _headers.ContainsKey("Host") ? _headers["Host"].Single() : null; |
||||
|
} |
||||
|
catch (Exception) |
||||
|
{ |
||||
|
throw new Exception("Multiple Host headers detected: " + String.Join(" ", _headers["Host"])); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the value of a header
|
||||
|
/// </summary>
|
||||
|
/// <param name="headername"></param>
|
||||
|
/// <returns>The header's values as a comma separated list, null if the header is not set.</returns>
|
||||
|
public string GetHeaderValue(string headername) |
||||
|
{ |
||||
|
string[] values; |
||||
|
return _headers.TryGetValue(headername, out values) ? String.Join(",", values) : null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Core |
||||
|
{ |
||||
|
internal class ResponseHeaders |
||||
|
{ |
||||
|
private readonly IDictionary<string, string[]> _headers; |
||||
|
|
||||
|
internal ResponseHeaders(IDictionary<string, string[]> headers) |
||||
|
{ |
||||
|
_headers = headers; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the value of the Location header if present. Otherwise returns null.
|
||||
|
/// </summary>
|
||||
|
public string Location |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return _headers.ContainsKey("Location") ? _headers["Location"].Single() : null; |
||||
|
} |
||||
|
catch (Exception) |
||||
|
{ |
||||
|
throw new Exception("Multiple Location headers detected: " + String.Join(" ", _headers["Location"])); |
||||
|
} |
||||
|
} |
||||
|
set { _headers["Location"] = new[] { value }; } |
||||
|
} |
||||
|
|
||||
|
internal void SetHeader(string name, string value) |
||||
|
{ |
||||
|
_headers[name] = new[] { value }; |
||||
|
} |
||||
|
|
||||
|
internal void RemoveHeader(string name) |
||||
|
{ |
||||
|
_headers.Remove(name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public class CspDirective : ICspDirectiveConfiguration |
||||
|
{ |
||||
|
public CspDirective() |
||||
|
{ |
||||
|
Enabled = true; |
||||
|
} |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool Enabled { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool NoneSrc { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool SelfSrc { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool UnsafeInlineSrc { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool UnsafeEvalSrc { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public string Nonce { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public IEnumerable<string> CustomSources { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,114 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
using NWebsec.Core.HttpHeaders.Csp; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public static class CspDirectiveExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Sets the "none" source for the CSP directive. This source cannot be combined with other sources on a CSP directive.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The type of the CSP directive configuration object.</typeparam>
|
||||
|
/// <param name="directive">The CSP directive configuration object.</param>
|
||||
|
/// <exception cref="InvalidOperationException">Thrown when sources have already been configured for the directive.</exception>
|
||||
|
public static void None<T>(this T directive) where T : class, ICspDirectiveBasicConfiguration |
||||
|
{ |
||||
|
if (directive == null) throw new ArgumentNullException("directive"); |
||||
|
|
||||
|
ValidateBeforeSettingNoneSource(directive); |
||||
|
directive.NoneSrc = true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the "self" source for the CSP directive.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The type of the CSP directive configuration object.</typeparam>
|
||||
|
/// <param name="directive">The CSP directive configuration object.</param>
|
||||
|
/// <returns>The CSP directive configuration object.</returns>
|
||||
|
public static T Self<T>(this T directive) where T : class, ICspDirectiveBasicConfiguration |
||||
|
{ |
||||
|
if (directive == null) throw new ArgumentNullException("directive"); |
||||
|
|
||||
|
directive.SelfSrc = true; |
||||
|
return directive; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets custom sources for the CSP directive.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The type of the CSP directive configuration object.</typeparam>
|
||||
|
/// <param name="directive">The CSP directive configuration object.</param>
|
||||
|
/// <param name="sources">One or more custom sources.</param>
|
||||
|
/// <returns>The CSP directive configuration object.</returns>
|
||||
|
public static T CustomSources<T>(this T directive, params string[] sources) where T : class, ICspDirectiveBasicConfiguration |
||||
|
{ |
||||
|
if (directive == null) throw new ArgumentNullException("directive"); |
||||
|
if (sources.Length == 0) throw new ArgumentException("You must supply at least one source.", "sources"); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
directive.CustomSources = sources.Select(s => CspUriSource.Parse(s).ToString()).ToArray(); |
||||
|
} |
||||
|
catch (InvalidCspSourceException e) |
||||
|
{ |
||||
|
throw new ArgumentException("Invalid source. Details: " + e.Message, "sources", e); |
||||
|
} |
||||
|
|
||||
|
return directive; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the "unsafe-inline" source for the CSP directive.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The type of the CSP directive configuration object.</typeparam>
|
||||
|
/// <param name="directive">The CSP directive configuration object.</param>
|
||||
|
/// <returns>The CSP directive configuration object.</returns>
|
||||
|
public static T UnsafeInline<T>(this T directive) where T : class, ICspDirectiveUnsafeInlineConfiguration |
||||
|
{ |
||||
|
if (directive == null) throw new ArgumentNullException("directive"); |
||||
|
|
||||
|
directive.UnsafeInlineSrc = true; |
||||
|
return directive; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the "unsafe-eval" source for the CSP directive.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The type of the CSP directive configuration object.</typeparam>
|
||||
|
/// <param name="directive">The CSP directive configuration object.</param>
|
||||
|
/// <returns>The CSP directive configuration object.</returns>
|
||||
|
public static T UnsafeEval<T>(this T directive) where T : class, ICspDirectiveConfiguration |
||||
|
{ |
||||
|
if (directive == null) throw new ArgumentNullException("directive"); |
||||
|
|
||||
|
directive.UnsafeEvalSrc = true; |
||||
|
return directive; |
||||
|
} |
||||
|
|
||||
|
private static void ValidateBeforeSettingNoneSource(ICspDirectiveBasicConfiguration directive) |
||||
|
{ |
||||
|
if (directive.SelfSrc || (directive.CustomSources != null && directive.CustomSources.Any())) |
||||
|
{ |
||||
|
throw new InvalidOperationException("It is a logical error to combine the \"None\" source with other sources."); |
||||
|
} |
||||
|
|
||||
|
var unsafeInline = directive as ICspDirectiveUnsafeInlineConfiguration; |
||||
|
|
||||
|
if (unsafeInline != null && unsafeInline.UnsafeInlineSrc) |
||||
|
{ |
||||
|
throw new InvalidOperationException("It is a logical error to combine the \"None\" source with other sources."); |
||||
|
} |
||||
|
|
||||
|
var unsafeEval = directive as ICspDirectiveConfiguration; |
||||
|
|
||||
|
if (unsafeEval != null && unsafeEval.UnsafeEvalSrc) |
||||
|
{ |
||||
|
throw new InvalidOperationException("It is a logical error to combine the \"None\" source with other sources."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,161 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public class CspOptions : ICspConfiguration, IFluentCspOptions |
||||
|
{ |
||||
|
public bool Enabled { get; set; } = true; |
||||
|
|
||||
|
public ICspDirectiveConfiguration DefaultSrcDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration ScriptSrcDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration ObjectSrcDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration StyleSrcDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration ImgSrcDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration MediaSrcDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration FrameSrcDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration FontSrcDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration ConnectSrcDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration BaseUriDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration ChildSrcDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration FormActionDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspDirectiveConfiguration FrameAncestorsDirective { get; set; } = new CspDirective(); |
||||
|
|
||||
|
public ICspPluginTypesDirectiveConfiguration PluginTypesDirective { get; set; } = new FluentCspPluginTypesDirective(); |
||||
|
|
||||
|
public ICspSandboxDirectiveConfiguration SandboxDirective { get; set; } = new FluentCspSandboxDirective(); |
||||
|
|
||||
|
public ICspUpgradeDirectiveConfiguration UpgradeInsecureRequestsDirective { get; set; } = new CspUpgradeDirectiveConfiguration(); |
||||
|
|
||||
|
public ICspReportUriDirectiveConfiguration ReportUriDirective { get; set; } = new CspReportUriDirective(); |
||||
|
|
||||
|
public IFluentCspOptions DefaultSources(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(DefaultSrcDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions ScriptSources(Action<ICspDirectiveConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(ScriptSrcDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions ObjectSources(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(ObjectSrcDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions StyleSources(Action<ICspDirectiveUnsafeInlineConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(StyleSrcDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions ImageSources(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(ImgSrcDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions MediaSources(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(MediaSrcDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions FrameSources(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(FrameSrcDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions FontSources(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(FontSrcDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions ConnectSources(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(ConnectSrcDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions BaseUris(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(BaseUriDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions ChildSources(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(ChildSrcDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions FormActions(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(FormActionDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions FrameAncestors(Action<ICspDirectiveBasicConfiguration> configurer) |
||||
|
{ |
||||
|
configurer(FrameAncestorsDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions PluginTypes(Action<IFluentCspPluginTypesDirective> configurer) |
||||
|
{ |
||||
|
configurer((IFluentCspPluginTypesDirective)PluginTypesDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions Sandbox() |
||||
|
{ |
||||
|
SandboxDirective.Enabled = true; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions Sandbox(Action<IFluentCspSandboxDirective> configurer) |
||||
|
{ |
||||
|
SandboxDirective.Enabled = true; |
||||
|
configurer((IFluentCspSandboxDirective)SandboxDirective); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions UpgradeInsecureRequests(int httpsPort = 443) |
||||
|
{ |
||||
|
if (httpsPort < 1 || httpsPort > 65535) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException(nameof(httpsPort),"The port number must be in the range 1-65535."); |
||||
|
} |
||||
|
|
||||
|
UpgradeInsecureRequestsDirective.Enabled = true; |
||||
|
UpgradeInsecureRequestsDirective.HttpsPort = httpsPort; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentCspOptions ReportUris(Action<IFluentCspReportUriDirective> configurer) |
||||
|
{ |
||||
|
configurer((IFluentCspReportUriDirective)ReportUriDirective); |
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
using NWebsec.Core.HttpHeaders.Csp; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public class CspReportUriDirective : ICspReportUriDirectiveConfiguration, IFluentCspReportUriDirective |
||||
|
{ |
||||
|
internal CspReportUriDirective() |
||||
|
{ |
||||
|
Enabled = true; |
||||
|
} |
||||
|
|
||||
|
public bool Enabled { get; set; } |
||||
|
public bool EnableBuiltinHandler { get; set; } |
||||
|
public IEnumerable<string> ReportUris { get; set; } |
||||
|
|
||||
|
public void Uris(params string[] reportUris) |
||||
|
{ |
||||
|
if (reportUris.Length == 0) throw new ArgumentException("You must supply at least one report URI.", "reportUris"); |
||||
|
|
||||
|
var reportUriList = new List<string>(); |
||||
|
|
||||
|
foreach (var reportUri in reportUris) |
||||
|
{ |
||||
|
Uri uri; |
||||
|
if (!Uri.TryCreate(reportUri, UriKind.RelativeOrAbsolute, out uri)) |
||||
|
{ |
||||
|
throw new ArgumentException("Could not parse reportUri: " + reportUri); |
||||
|
} |
||||
|
|
||||
|
reportUriList.Add(CspUriSource.EncodeUri(uri)); |
||||
|
} |
||||
|
ReportUris = reportUriList.ToArray(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration.Validation; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public class FluentCspPluginTypesDirective : CspPluginTypesDirectiveConfiguration, IFluentCspPluginTypesDirective |
||||
|
{ |
||||
|
public FluentCspPluginTypesDirective() |
||||
|
{ |
||||
|
Enabled = true; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
|
||||
|
public void MediaTypes(params string[] mediaTypes) |
||||
|
#pragma warning restore CS0108 // Member hides inherited member; missing new keyword
|
||||
|
{ |
||||
|
if (mediaTypes == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(mediaTypes)); |
||||
|
} |
||||
|
|
||||
|
if (mediaTypes.Length == 0) |
||||
|
{ |
||||
|
throw new ArgumentException("One or more parameter values expected.", nameof(mediaTypes)); |
||||
|
} |
||||
|
var validator = new Rfc2045MediaTypeValidator(); |
||||
|
var types = mediaTypes.Distinct().ToArray(); |
||||
|
|
||||
|
foreach (var mediaType in types) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
validator.Validate(mediaType); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
throw new ArgumentException("Invalid argument. Details: " + e.Message, e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
base.MediaTypes = types; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
class FluentCspSandboxDirective : CspSandboxDirectiveConfiguration, IFluentCspSandboxDirective |
||||
|
{ |
||||
|
public new IFluentCspSandboxDirective AllowForms() |
||||
|
{ |
||||
|
base.AllowForms = true; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public new IFluentCspSandboxDirective AllowPointerLock() |
||||
|
{ |
||||
|
base.AllowPointerLock = true; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public new IFluentCspSandboxDirective AllowPopups() |
||||
|
{ |
||||
|
base.AllowPopups = true; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public new IFluentCspSandboxDirective AllowSameOrigin() |
||||
|
{ |
||||
|
base.AllowSameOrigin = true; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public new IFluentCspSandboxDirective AllowScripts() |
||||
|
{ |
||||
|
base.AllowScripts = true; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public new IFluentCspSandboxDirective AllowTopNavigation() |
||||
|
{ |
||||
|
base.AllowTopNavigation = true; |
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Helpers |
||||
|
{ |
||||
|
//Tested indirectly by CSP Middleware
|
||||
|
internal class CspUpgradeHelper |
||||
|
{ |
||||
|
internal static bool UaSupportsUpgradeInsecureRequests(HttpContext env) |
||||
|
{ |
||||
|
var upgradeHeader = env.Request.Headers["Upgrade-Insecure-Requests"]; |
||||
|
|
||||
|
return upgradeHeader.Any(h => h.Equals("1", StringComparison.Ordinal)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,124 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Security.Cryptography.X509Certificates; |
||||
|
using NWebsec.Core.Helpers.X509; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration.Validation; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public class HpkpOptions : IFluentHpkpOptions |
||||
|
{ |
||||
|
private readonly List<string> _pins; |
||||
|
private readonly HpkpConfigurationValidator _validator; |
||||
|
|
||||
|
internal HpkpOptionsConfiguration Config { get; set; } |
||||
|
|
||||
|
public HpkpOptions() |
||||
|
{ |
||||
|
_pins = new List<string>(); |
||||
|
Config = new HpkpOptionsConfiguration { Pins = _pins }; |
||||
|
_validator = new HpkpConfigurationValidator(); |
||||
|
} |
||||
|
|
||||
|
// ReSharper disable once CSharpWarnings::CS0109
|
||||
|
public IFluentHpkpOptions MaxAge(int days = 0, int hours = 0, int minutes = 0, int seconds = 0) |
||||
|
{ |
||||
|
if (days < 0) throw new ArgumentOutOfRangeException("days", "Value must be equal to or larger than 0."); |
||||
|
if (hours < 0) throw new ArgumentOutOfRangeException("hours", "Value must be equal to or larger than 0."); |
||||
|
if (minutes < 0) throw new ArgumentOutOfRangeException("minutes", "Value must be equal to or larger than 0."); |
||||
|
if (seconds < 0) throw new ArgumentOutOfRangeException("seconds", "Value must be equal to or larger than 0."); |
||||
|
|
||||
|
Config.MaxAge = new TimeSpan(days, hours, minutes, seconds); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentHpkpOptions IncludeSubdomains() |
||||
|
{ |
||||
|
Config.IncludeSubdomains = true; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentHpkpOptions ReportUri(string reportUri) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
_validator.ValidateReportUri(reportUri); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
throw new ArgumentException(e.Message, "reportUri"); |
||||
|
} |
||||
|
|
||||
|
Config.ReportUri = reportUri; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentHpkpOptions AllResponses() |
||||
|
{ |
||||
|
Config.HttpsOnly = false; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentHpkpOptions Sha256Pins(params string[] pins) |
||||
|
{ |
||||
|
foreach (var pin in pins) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
_validator.ValidateRawPin(pin); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
throw new ArgumentException(e.Message, "pins"); |
||||
|
} |
||||
|
|
||||
|
var formattedPin = "sha256=\"" + pin + "\""; |
||||
|
if (!_pins.Contains(formattedPin)) |
||||
|
{ |
||||
|
_pins.Add(formattedPin); |
||||
|
} |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentHpkpOptions PinCertificate(string thumbprint, StoreLocation storeLocation = StoreLocation.LocalMachine, |
||||
|
StoreName storeName = StoreName.My) |
||||
|
{ |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
_validator.ValidateThumbprint(thumbprint); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
throw new ArgumentException(e.Message, thumbprint); |
||||
|
} |
||||
|
|
||||
|
var helper = new X509Helper(); |
||||
|
var cert = helper.GetCertByThumbprint(thumbprint, storeLocation, storeName); |
||||
|
var pin = helper.GetSubjectPublicKeyInfoPinValue(cert); |
||||
|
|
||||
|
#if DNX451
|
||||
|
cert.Reset(); |
||||
|
#elif NET451
|
||||
|
cert.Reset(); |
||||
|
#else
|
||||
|
cert.Dispose(); |
||||
|
#endif
|
||||
|
if (!_pins.Contains(pin)) |
||||
|
{ |
||||
|
_pins.Add(pin); |
||||
|
} |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentHpkpOptions HttpsOnly() |
||||
|
{ |
||||
|
Config.HttpsOnly = true; |
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public class HpkpOptionsConfiguration : IHpkpConfiguration |
||||
|
{ |
||||
|
internal HpkpOptionsConfiguration() |
||||
|
{ |
||||
|
MaxAge = TimeSpan.Zero; |
||||
|
HttpsOnly = true; |
||||
|
Pins = new string[0]; |
||||
|
} |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public IEnumerable<string> Pins { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public TimeSpan MaxAge { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool IncludeSubdomains { get; set; } |
||||
|
|
||||
|
public string ReportUri { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool HttpsOnly { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public class HstsOptions : HstsOptionsConfiguration, IFluentHstsOptions |
||||
|
{ |
||||
|
|
||||
|
// ReSharper disable once CSharpWarnings::CS0109
|
||||
|
public new IFluentHstsOptions MaxAge(int days = 0, int hours = 0, int minutes = 0, int seconds = 0) |
||||
|
{ |
||||
|
if (days < 0) throw new ArgumentOutOfRangeException(nameof(days), "Value must be equal to or larger than 0."); |
||||
|
if (hours < 0) throw new ArgumentOutOfRangeException(nameof(hours), "Value must be equal to or larger than 0."); |
||||
|
if (minutes < 0) throw new ArgumentOutOfRangeException(nameof(minutes), "Value must be equal to or larger than 0."); |
||||
|
if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds), "Value must be equal to or larger than 0."); |
||||
|
|
||||
|
base.MaxAge = new TimeSpan(days, hours, minutes, seconds); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public new IFluentHstsOptions IncludeSubdomains() |
||||
|
{ |
||||
|
base.IncludeSubdomains = true; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public new IFluentHstsOptions Preload() |
||||
|
{ |
||||
|
base.Preload = true; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public new IFluentHstsOptions UpgradeInsecureRequests() |
||||
|
{ |
||||
|
base.UpgradeInsecureRequests = true; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public IFluentHstsOptions AllResponses() |
||||
|
{ |
||||
|
base.HttpsOnly = false; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public new IFluentHstsOptions HttpsOnly() |
||||
|
{ |
||||
|
base.HttpsOnly = true; |
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public class HstsOptionsConfiguration : IHstsConfiguration |
||||
|
{ |
||||
|
internal HstsOptionsConfiguration() |
||||
|
{ |
||||
|
MaxAge = TimeSpan.Zero; |
||||
|
HttpsOnly = true; |
||||
|
} |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public TimeSpan MaxAge { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool IncludeSubdomains { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool Preload { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool HttpsOnly { get; set; } |
||||
|
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public bool UpgradeInsecureRequests { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,142 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using NWebsec.Core.Fluent; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Fluent interface to configure options for Content-Security-Options.
|
||||
|
/// </summary>
|
||||
|
public interface IFluentCspOptions : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Configures the default-src directive (CSP 1.0).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions DefaultSources(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the script-src directive (CSP 1.0).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions ScriptSources(Action<ICspDirectiveConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the object-src directive (CSP 1.0).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions ObjectSources(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the style-src directive (CSP 1.0).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions StyleSources(Action<ICspDirectiveUnsafeInlineConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the image-src directive (CSP 1.0).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions ImageSources(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the media-src directive (CSP 1.0).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions MediaSources(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the frame-src directive (CSP 1.0).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions FrameSources(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the font-src directive (CSP 1.0).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions FontSources(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the connect-src directive (CSP 1.0).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions ConnectSources(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the base-uri directive (CSP 2).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions BaseUris(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the child-src directive (CSP 2).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions ChildSources(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the form-action directive (CSP 2).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions FormActions(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the fram-ancestors directive (CSP 2).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions FrameAncestors(Action<ICspDirectiveBasicConfiguration> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the plugin-types directive (CSP 2).
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the media types for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions PluginTypes(Action<IFluentCspPluginTypesDirective> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the sandbox directive (CSP 2) without further ado.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>Support for this directive was optional in CSP 1.0, but is mandatory as of CSP 2.</remarks>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions Sandbox(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the sandbox directive (CSP 2) with one or more sources.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>Support for this directive was optional in CSP 1.0, but is mandatory as of CSP 2.</remarks>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the sources for the directive.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions Sandbox(Action<IFluentCspSandboxDirective> configurer); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the upgrade-insecure-requests directive and redirects conformant UAs to HTTPS.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>This directive is not part of CSP 1.0 or CSP 2, but is described in a separate specification.</remarks>
|
||||
|
/// <param name="httpsPort">The HTTPS port. Defaults to 443.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions UpgradeInsecureRequests(int httpsPort = 443); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the report-uri directive (CSP 1.0). Support for absolute URIs was introduced in CSP 2.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configurer">An <see cref="Action"/> that configures the report URIs.</param>
|
||||
|
/// <returns>The current <see cref="CspOptions" /> instance.</returns>
|
||||
|
IFluentCspOptions ReportUris(Action<IFluentCspReportUriDirective> configurer); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using NWebsec.Core.Fluent; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public interface IFluentCspPluginTypesDirective : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Sets the media types for the CSP plugin-types directive.
|
||||
|
/// </summary>
|
||||
|
void MediaTypes(params string[] pluginType); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using NWebsec.Core.Fluent; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Fluent interface to configure report URIs.
|
||||
|
/// </summary>
|
||||
|
public interface IFluentCspReportUriDirective : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Sets report URIs for the CSP directive.
|
||||
|
/// </summary>
|
||||
|
/// <param name="reportUris">One or more report URIs.</param>
|
||||
|
void Uris(params string[] reportUris); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using NWebsec.Core.Fluent; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
public interface IFluentCspSandboxDirective : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Sets the 'allow-forms' source for the CSP sandbox directive.
|
||||
|
/// </summary>
|
||||
|
IFluentCspSandboxDirective AllowForms(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the 'allow-pointer-lock' source for the CSP sandbox directive.
|
||||
|
/// </summary>
|
||||
|
IFluentCspSandboxDirective AllowPointerLock(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the 'allow-popups' source for the CSP sandbox directive.
|
||||
|
/// </summary>
|
||||
|
IFluentCspSandboxDirective AllowPopups(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the 'allow-same-origin' source for the CSP sandbox directive.
|
||||
|
/// </summary>
|
||||
|
IFluentCspSandboxDirective AllowSameOrigin(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the 'allow-scripts' source for the CSP sandbox directive.
|
||||
|
/// </summary>
|
||||
|
IFluentCspSandboxDirective AllowScripts(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the 'allow-top-navigation' source for the CSP sandbox directive.
|
||||
|
/// </summary>
|
||||
|
IFluentCspSandboxDirective AllowTopNavigation(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Security.Cryptography.X509Certificates; |
||||
|
using NWebsec.Core.Fluent; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Fluent interface to configure options for Http Strict Transport Security.
|
||||
|
/// </summary>
|
||||
|
public interface IFluentHpkpOptions : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Specifies the max age for the HPKP header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="days">The number of days added to max age.</param>
|
||||
|
/// <param name="hours">The number of hours added to max age.</param>
|
||||
|
/// <param name="minutes">The number of minutes added to max age.</param>
|
||||
|
/// <param name="seconds">The number of seconds added to max age.</param>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown if a negative value was supplied in any of the parameters.</exception>
|
||||
|
IFluentHpkpOptions MaxAge(int days = 0, int hours = 0, int minutes = 0, int seconds = 0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the IncludeSubdomains directive in the HPKP header.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentHpkpOptions IncludeSubdomains(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies a report URI where the browser can send HPKP violations.
|
||||
|
/// </summary>
|
||||
|
/// <param name="reportUri">The report URI, which is an absolute URI with scheme http or https.</param>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentHpkpOptions ReportUri(string reportUri); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies that the HPKP header should also be set for HTTP responses. The header is always set for HTTPS responses.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>The HPKP standard specifies that the header should only be set over secure connections, which is the default behavior.
|
||||
|
/// This configuration option exists to accomodate websites running behind an SSL terminator.</remarks>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentHpkpOptions AllResponses(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies one or more certificate pins to include in the HPKP header. A certificate pin is the Base64 encoded SHA-256 hash value of a certficate's SPKI.
|
||||
|
/// </summary>
|
||||
|
/// <param name="pins">One or more certficate pin values.</param>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentHpkpOptions Sha256Pins(params string[] pins); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies a certificate that should be pinned in the HPKP header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="thumbprint">The certificate thumbprint.</param>
|
||||
|
/// <param name="storeLocation">The <see cref="StoreLocation"/> for the certificate. The default is <see cref="StoreLocation.LocalMachine"/>.</param>
|
||||
|
/// <param name="storeName">The <see cref="StoreName"/> for the certificate. The default is <see cref="StoreName.My"/>.</param>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentHpkpOptions PinCertificate(string thumbprint, StoreLocation storeLocation = StoreLocation.LocalMachine, StoreName storeName = StoreName.My); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using NWebsec.Core.Fluent; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Fluent interface to configure options for Http Strict Transport Security.
|
||||
|
/// </summary>
|
||||
|
public interface IFluentHstsOptions : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Specifies the max age for the HSTS header.
|
||||
|
/// </summary>
|
||||
|
/// <param name="days">The number of days added to max age.</param>
|
||||
|
/// <param name="hours">The number of hours added to max age.</param>
|
||||
|
/// <param name="minutes">The number of minutes added to max age.</param>
|
||||
|
/// <param name="seconds">The number of seconds added to max age.</param>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown if a negative value was supplied in any of the parameters.</exception>
|
||||
|
IFluentHstsOptions MaxAge(int days = 0, int hours = 0, int minutes = 0, int seconds = 0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the IncludeSubdomains directive in the Hsts header.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentHstsOptions IncludeSubdomains(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the Preload directive in the HSTS header. MaxAge must be at least 18 weeks, and IncludeSubdomains must be enabled.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>Read more about preloaded HSTS sites at <a href="https://www.chromium.org/hsts">www.chromium.org/sts</a></remarks>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentHstsOptions Preload(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the HSTS header only when the user agent signals that it supports the upgrade-insecure-requests CSP directive.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>This setting is intended to be used in combination with the upgrade-insecure-requests CSP directive.</remarks>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentHstsOptions UpgradeInsecureRequests(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies that the HSTS header should also be set for HTTP responses. The header is always set for HTTPS responses.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>The HSTS standard specifies that the header should only be set over secure connections, which is the default behavior.
|
||||
|
/// This configuration option exists to accomodate websites running behind an SSL terminator.</remarks>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentHstsOptions AllResponses(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies that the HSTS header should be set for HTTPS responses only.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
[Obsolete("This method is deprecated as the default has been changed to HTTPS only.", false)] |
||||
|
IFluentHstsOptions HttpsOnly(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using NWebsec.Core.Fluent; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Fluent interface to configure options for redirect validation.
|
||||
|
/// </summary>
|
||||
|
public interface IFluentRedirectValidationOptions : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Configures the allowed redirect destinations. These must be well formed absolute URIs.
|
||||
|
/// </summary>
|
||||
|
/// <param name="uris">Allowed redirect destinations.</param>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentRedirectValidationOptions AllowedDestinations(params string[] uris); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Allows same host redirects to HTTPS.
|
||||
|
/// </summary>
|
||||
|
/// <param name="httpsPorts">Allowed destination port(s) for redirects to HTTPS. The default HTTPS port (443) is assumed if no values are configured.</param>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentRedirectValidationOptions AllowSameHostRedirectsToHttps(params int[] httpsPorts); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using NWebsec.Core.Fluent; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Fluent interface to configure options for X-Frame-Options.
|
||||
|
/// </summary>
|
||||
|
public interface IFluentXFrameOptions : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enables the Deny directive.
|
||||
|
/// </summary>
|
||||
|
void Deny(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the SameOrigin directive.
|
||||
|
/// </summary>
|
||||
|
void SameOrigin(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using NWebsec.Core.Fluent; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Fluent interface to configure options for X-Robots-Tag.
|
||||
|
/// </summary>
|
||||
|
public interface IFluentXRobotsTagOptions : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enables the noindex directive.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentXRobotsTagOptions NoIndex(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the nofollow directive.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentXRobotsTagOptions NoFollow(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the nosnippet directive.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentXRobotsTagOptions NoSnippet(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the noarchive directive.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentXRobotsTagOptions NoArchive(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the noodp directive.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentXRobotsTagOptions NoOdp(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the notranslate directive.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentXRobotsTagOptions NoTranslate(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables the noimageindex directive.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The current instance.</returns>
|
||||
|
IFluentXRobotsTagOptions NoImageIndex(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using NWebsec.Core.Fluent; |
||||
|
|
||||
|
namespace NWebsec.Middleware |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Fluent interface to configure options for X-Xss-Protection.
|
||||
|
/// </summary>
|
||||
|
public interface IFluentXXssProtectionOptions : IFluentInterface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Configures the header to explicitly disable protection.
|
||||
|
/// </summary>
|
||||
|
void Disabled(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the header to explicitly enable protection.
|
||||
|
/// </summary>
|
||||
|
void Enabled(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures the header to explicitly enable protection with block mode.
|
||||
|
/// </summary>
|
||||
|
void EnabledWithBlockMode(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNet.Builder; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core.Extensions; |
||||
|
using NWebsec.Core.HttpHeaders; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
using NWebsec.Middleware.Helpers; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Middleware |
||||
|
{ |
||||
|
public class CspMiddleware |
||||
|
{ |
||||
|
private readonly ICspConfiguration _config; |
||||
|
private readonly HeaderResult _headerResult; |
||||
|
private readonly bool _reportOnly; |
||||
|
private readonly RequestDelegate _next; |
||||
|
|
||||
|
public CspMiddleware(RequestDelegate next, ICspConfiguration options, bool reportOnly) |
||||
|
{ |
||||
|
_next = next; |
||||
|
_config = options; |
||||
|
_reportOnly = reportOnly; |
||||
|
|
||||
|
var headerGenerator = new HeaderGenerator(); |
||||
|
_headerResult = headerGenerator.CreateCspResult(_config, reportOnly); |
||||
|
} |
||||
|
|
||||
|
public async Task Invoke(HttpContext context) |
||||
|
{ |
||||
|
|
||||
|
if (HandleUpgradeInsecureRequest(context)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
SetCspHeaders(context); |
||||
|
|
||||
|
if (_next != null) |
||||
|
{ |
||||
|
await _next(context); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
internal bool HandleUpgradeInsecureRequest(HttpContext context) |
||||
|
{ |
||||
|
//Already on https.
|
||||
|
if (context.Request.IsHttps) return false; |
||||
|
|
||||
|
//CSP upgrade-insecure-requests is disabled
|
||||
|
if (!_config.Enabled || !_config.UpgradeInsecureRequestsDirective.Enabled) return false; |
||||
|
|
||||
|
if (!CspUpgradeHelper.UaSupportsUpgradeInsecureRequests(context)) return false; |
||||
|
|
||||
|
var upgradeUri = new UriBuilder($"https://{context.Request.Host}") |
||||
|
{ |
||||
|
Port = _config.UpgradeInsecureRequestsDirective.HttpsPort, |
||||
|
Path = context.Request.PathBase + context.Request.Path |
||||
|
}; |
||||
|
|
||||
|
//Redirect
|
||||
|
context.Response.Headers["Vary"] = "Upgrade-Insecure-Requests"; |
||||
|
context.Response.Headers["Location"] = upgradeUri.Uri.AbsoluteUri; |
||||
|
context.Response.StatusCode = 307; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
internal void SetCspHeaders(HttpContext context) |
||||
|
{ |
||||
|
if (_reportOnly) |
||||
|
{ |
||||
|
context.GetNWebsecContext().CspReportOnly = _config; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
context.GetNWebsecContext().Csp = _config; |
||||
|
} |
||||
|
|
||||
|
if (_headerResult.Action == HeaderResult.ResponseAction.Set) |
||||
|
{ |
||||
|
context.Response.Headers[_headerResult.Name] = _headerResult.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using Microsoft.AspNet.Builder; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core.HttpHeaders; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Middleware |
||||
|
{ |
||||
|
|
||||
|
public class HpkpMiddleware : MiddlewareBase |
||||
|
{ |
||||
|
private readonly IHpkpConfiguration _config; |
||||
|
private readonly HeaderResult _headerResult; |
||||
|
|
||||
|
public HpkpMiddleware(RequestDelegate next, HpkpOptions options, bool reportOnly) |
||||
|
: base(next) |
||||
|
{ |
||||
|
_config = options.Config; |
||||
|
|
||||
|
var headerGenerator = new HeaderGenerator(); |
||||
|
_headerResult = headerGenerator.CreateHpkpResult(_config, reportOnly); |
||||
|
} |
||||
|
|
||||
|
internal override void PreInvokeNext(HttpContext context) |
||||
|
{ |
||||
|
|
||||
|
if (_config.HttpsOnly && !context.Request.IsHttps) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (_headerResult.Action == HeaderResult.ResponseAction.Set) |
||||
|
{ |
||||
|
context.Response.Headers[_headerResult.Name] = _headerResult.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using Microsoft.AspNet.Builder; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core.HttpHeaders; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
using NWebsec.Middleware.Helpers; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Middleware |
||||
|
{ |
||||
|
|
||||
|
public class HstsMiddleware : MiddlewareBase |
||||
|
{ |
||||
|
private readonly IHstsConfiguration _config; |
||||
|
private readonly HeaderResult _headerResult; |
||||
|
private const string Https = "https"; |
||||
|
|
||||
|
public HstsMiddleware(RequestDelegate next, HstsOptions options) |
||||
|
: base(next) |
||||
|
{ |
||||
|
_config = options; |
||||
|
|
||||
|
var headerGenerator = new HeaderGenerator(); |
||||
|
_headerResult = headerGenerator.CreateHstsResult(_config); |
||||
|
} |
||||
|
|
||||
|
internal override void PreInvokeNext(HttpContext context) |
||||
|
{ |
||||
|
|
||||
|
if (_config.HttpsOnly && !context.Request.IsHttps) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (_config.UpgradeInsecureRequests && !CspUpgradeHelper.UaSupportsUpgradeInsecureRequests(context)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (_headerResult.Action == HeaderResult.ResponseAction.Set) |
||||
|
{ |
||||
|
context.Response.Headers[_headerResult.Name] = _headerResult.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNet.Builder; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Middleware |
||||
|
{ |
||||
|
public class MiddlewareBase |
||||
|
{ |
||||
|
private readonly RequestDelegate _next; |
||||
|
|
||||
|
public MiddlewareBase(RequestDelegate next) |
||||
|
{ |
||||
|
_next = next; |
||||
|
} |
||||
|
|
||||
|
public async Task Invoke(HttpContext context) |
||||
|
{ |
||||
|
|
||||
|
PreInvokeNext(context); |
||||
|
|
||||
|
if (_next != null) |
||||
|
{ |
||||
|
await _next(context); |
||||
|
} |
||||
|
|
||||
|
PostInvokeNext(context); |
||||
|
} |
||||
|
|
||||
|
internal virtual void PreInvokeNext(HttpContext context) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
internal virtual void PostInvokeNext(HttpContext context) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using System; |
||||
|
using Microsoft.AspNet.Builder; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Middleware |
||||
|
{ |
||||
|
|
||||
|
public class RedirectValidationMiddleware : MiddlewareBase |
||||
|
{ |
||||
|
private readonly RedirectValidationOptions _config; |
||||
|
private readonly RedirectValidator _redirectValidator; |
||||
|
|
||||
|
public RedirectValidationMiddleware(RequestDelegate next, RedirectValidationOptions options) |
||||
|
: base(next) |
||||
|
{ |
||||
|
_config = options; |
||||
|
_redirectValidator = new RedirectValidator(); |
||||
|
} |
||||
|
|
||||
|
internal override void PostInvokeNext(HttpContext context) |
||||
|
{ |
||||
|
var statusCode = context.Response.StatusCode; |
||||
|
|
||||
|
if (!_redirectValidator.IsRedirectStatusCode(statusCode)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var scheme = context.Request.Scheme; |
||||
|
var hostandport = context.Request.Host; |
||||
|
var requestUri = new Uri(scheme + "://" + hostandport); |
||||
|
|
||||
|
_redirectValidator.ValidateRedirect(statusCode, context.Response.Headers["Location"], requestUri, _config); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using Microsoft.AspNet.Builder; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core.Extensions; |
||||
|
using NWebsec.Core.HttpHeaders; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Middleware |
||||
|
{ |
||||
|
public class XContentTypeOptionsMiddleware : MiddlewareBase |
||||
|
{ |
||||
|
private readonly ISimpleBooleanConfiguration _config; |
||||
|
private readonly HeaderResult _headerResult; |
||||
|
|
||||
|
public XContentTypeOptionsMiddleware(RequestDelegate next) |
||||
|
: base(next) |
||||
|
{ |
||||
|
_config = new SimpleBooleanConfiguration { Enabled = true }; |
||||
|
var headerGenerator = new HeaderGenerator(); |
||||
|
_headerResult = headerGenerator.CreateXContentTypeOptionsResult(_config); |
||||
|
} |
||||
|
|
||||
|
internal override void PreInvokeNext(HttpContext owinEnvironment) |
||||
|
{ |
||||
|
owinEnvironment.GetNWebsecContext().XContentTypeOptions = _config; |
||||
|
|
||||
|
if (_headerResult.Action == HeaderResult.ResponseAction.Set) |
||||
|
{ |
||||
|
owinEnvironment.Response.Headers[_headerResult.Name] = _headerResult.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using Microsoft.AspNet.Builder; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core.Extensions; |
||||
|
using NWebsec.Core.HttpHeaders; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Middleware |
||||
|
{ |
||||
|
public class XDownloadOptionsMiddleware : MiddlewareBase |
||||
|
{ |
||||
|
private readonly ISimpleBooleanConfiguration _config; |
||||
|
private readonly HeaderResult _headerResult; |
||||
|
|
||||
|
public XDownloadOptionsMiddleware(RequestDelegate next) |
||||
|
: base(next) |
||||
|
{ |
||||
|
_config = new SimpleBooleanConfiguration { Enabled = true }; |
||||
|
var headerGenerator = new HeaderGenerator(); |
||||
|
_headerResult = headerGenerator.CreateXDownloadOptionsResult(_config); |
||||
|
} |
||||
|
|
||||
|
internal override void PreInvokeNext(HttpContext owinEnvironment) |
||||
|
{ |
||||
|
owinEnvironment.GetNWebsecContext().XDownloadOptions = _config; |
||||
|
|
||||
|
if (_headerResult.Action == HeaderResult.ResponseAction.Set) |
||||
|
{ |
||||
|
owinEnvironment.Response.Headers[_headerResult.Name]= _headerResult.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using Microsoft.AspNet.Builder; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core.Extensions; |
||||
|
using NWebsec.Core.HttpHeaders; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Middleware |
||||
|
{ |
||||
|
|
||||
|
public class XRobotsTagMiddleware : MiddlewareBase |
||||
|
{ |
||||
|
private readonly IXRobotsTagConfiguration _config; |
||||
|
private readonly HeaderResult _headerResult; |
||||
|
|
||||
|
public XRobotsTagMiddleware(RequestDelegate next, XRobotsTagOptions options) |
||||
|
: base(next) |
||||
|
{ |
||||
|
_config = options.Config; |
||||
|
|
||||
|
var headerGenerator = new HeaderGenerator(); |
||||
|
_headerResult = headerGenerator.CreateXRobotsTagResult(_config); |
||||
|
} |
||||
|
|
||||
|
internal override void PreInvokeNext(HttpContext owinEnvironment) |
||||
|
{ |
||||
|
owinEnvironment.GetNWebsecContext().XRobotsTag = _config; |
||||
|
|
||||
|
if (_headerResult.Action == HeaderResult.ResponseAction.Set) |
||||
|
{ |
||||
|
owinEnvironment.Response.Headers[_headerResult.Name] = _headerResult.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using Microsoft.AspNet.Builder; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core.Extensions; |
||||
|
using NWebsec.Core.HttpHeaders; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Middleware |
||||
|
{ |
||||
|
public class XXssMiddleware : MiddlewareBase |
||||
|
{ |
||||
|
private readonly IXXssProtectionConfiguration _config; |
||||
|
private readonly HeaderResult _headerResult; |
||||
|
|
||||
|
public XXssMiddleware(RequestDelegate next, XXssProtectionOptions options) |
||||
|
: base(next) |
||||
|
{ |
||||
|
_config = options; |
||||
|
var headerGenerator = new HeaderGenerator(); |
||||
|
_headerResult = headerGenerator.CreateXXssProtectionResult(_config); |
||||
|
} |
||||
|
|
||||
|
internal override void PreInvokeNext(HttpContext owinEnvironment) |
||||
|
{ |
||||
|
owinEnvironment.GetNWebsecContext().XXssProtection = _config; |
||||
|
|
||||
|
if (_headerResult.Action == HeaderResult.ResponseAction.Set) |
||||
|
{ |
||||
|
owinEnvironment.Response.Headers[_headerResult.Name] = _headerResult.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
// Copyright (c) André N. Klingsheim. See License.txt in the project root for license information.
|
||||
|
|
||||
|
using Microsoft.AspNet.Builder; |
||||
|
using Microsoft.AspNet.Http; |
||||
|
using NWebsec.Core.Extensions; |
||||
|
using NWebsec.Core.HttpHeaders; |
||||
|
using NWebsec.Core.HttpHeaders.Configuration; |
||||
|
|
||||
|
namespace NWebsec.Middleware.Middleware |
||||
|
{ |
||||
|
public class XfoMiddleware : MiddlewareBase |
||||
|
{ |
||||
|
private readonly IXFrameOptionsConfiguration _config; |
||||
|
private readonly HeaderResult _headerResult; |
||||
|
|
||||
|
public XfoMiddleware(RequestDelegate next, XFrameOptions options) |
||||
|
: base(next) |
||||
|
{ |
||||
|
_config = options; |
||||
|
var headerGenerator = new HeaderGenerator(); |
||||
|
_headerResult = headerGenerator.CreateXfoResult(_config); |
||||
|
} |
||||
|
|
||||
|
internal override void PreInvokeNext(HttpContext owinEnvironment) |
||||
|
{ |
||||
|
owinEnvironment.GetNWebsecContext().XFrameOptions = _config; |
||||
|
if (_headerResult.Action == HeaderResult.ResponseAction.Set) |
||||
|
{ |
||||
|
owinEnvironment.Response.Headers[_headerResult.Name] = _headerResult.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue