-
Notifications
You must be signed in to change notification settings - Fork 251
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for
Expect-CT
HTTP header (#322)
* Update README with example usage for `Expect-CT` * Add some tests for the expected API * Add classes and loading to main entrypoints * Add `ExpectCt` class Adds the `ExpectCt` class and it's associated validation of the header syntax * Use full name of specification instead * Use consistent double quotes * Update README example to use correct naming for Expect-CT * Set defaults for Expect-CT via helper * Add missing comma in README example * Add frozen string literal
- Loading branch information
1 parent
f06b926
commit 0038e25
Showing
6 changed files
with
124 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
lib/secure_headers/headers/expect_certificate_transparency.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# frozen_string_literal: true | ||
module SecureHeaders | ||
class ExpectCertificateTransparencyConfigError < StandardError; end | ||
|
||
class ExpectCertificateTransparency | ||
HEADER_NAME = "Expect-CT".freeze | ||
CONFIG_KEY = :expect_certificate_transparency | ||
INVALID_CONFIGURATION_ERROR = "config must be a hash.".freeze | ||
INVALID_ENFORCE_VALUE_ERROR = "enforce must be a boolean".freeze | ||
REQUIRED_MAX_AGE_ERROR = "max-age is a required directive.".freeze | ||
INVALID_MAX_AGE_ERROR = "max-age must be a number.".freeze | ||
|
||
class << self | ||
# Public: Generate a Expect-CT header. | ||
# | ||
# Returns nil if not configured, returns header name and value if | ||
# configured. | ||
def make_header(config) | ||
return if config.nil? | ||
|
||
header = new(config) | ||
[HEADER_NAME, header.value] | ||
end | ||
|
||
def validate_config!(config) | ||
return if config.nil? || config == OPT_OUT | ||
raise ExpectCertificateTransparencyConfigError.new(INVALID_CONFIGURATION_ERROR) unless config.is_a? Hash | ||
|
||
unless [true, false, nil].include?(config[:enforce]) | ||
raise ExpectCertificateTransparencyConfigError.new(INVALID_ENFORCE_VALUE_ERROR) | ||
end | ||
|
||
if !config[:max_age] | ||
raise ExpectCertificateTransparencyConfigError.new(REQUIRED_MAX_AGE_ERROR) | ||
elsif config[:max_age].to_s !~ /\A\d+\z/ | ||
raise ExpectCertificateTransparencyConfigError.new(INVALID_MAX_AGE_ERROR) | ||
end | ||
end | ||
end | ||
|
||
def initialize(config) | ||
@enforced = config.fetch(:enforce, nil) | ||
@max_age = config.fetch(:max_age, nil) | ||
@report_uri = config.fetch(:report_uri, nil) | ||
end | ||
|
||
def value | ||
header_value = [ | ||
enforced_directive, | ||
max_age_directive, | ||
report_uri_directive | ||
].compact.join("; ").strip | ||
end | ||
|
||
def enforced_directive | ||
# Unfortunately `if @enforced` isn't enough here in case someone | ||
# passes in a random string so let's be specific with it to prevent | ||
# accidental enforcement. | ||
"enforce" if @enforced == true | ||
end | ||
|
||
def max_age_directive | ||
"max-age=#{@max_age}" if @max_age | ||
end | ||
|
||
def report_uri_directive | ||
"report-uri=\"#{@report_uri}\"" if @report_uri | ||
end | ||
end | ||
end |
42 changes: 42 additions & 0 deletions
42
spec/lib/secure_headers/headers/expect_certificate_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# frozen_string_literal: true | ||
require "spec_helper" | ||
|
||
module SecureHeaders | ||
describe ExpectCertificateTransparency do | ||
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce; max-age=1234") } | ||
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: false).value).to eq("max-age=1234") } | ||
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: "yolocopter").value).to eq("max-age=1234") } | ||
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, report_uri: "https://report-uri.io/expect-ct").value).to eq("max-age=1234; report-uri=\"https://report-uri.io/expect-ct\"") } | ||
specify do | ||
config = { enforce: true, max_age: 1234, report_uri: "https://report-uri.io/expect-ct" } | ||
header_value = "enforce; max-age=1234; report-uri=\"https://report-uri.io/expect-ct\"" | ||
expect(ExpectCertificateTransparency.new(config).value).to eq(header_value) | ||
end | ||
|
||
context "with an invalid configuration" do | ||
it "raises an exception when configuration isn't a hash" do | ||
expect do | ||
ExpectCertificateTransparency.validate_config!(%w(a)) | ||
end.to raise_error(ExpectCertificateTransparencyConfigError) | ||
end | ||
|
||
it "raises an exception when max-age is not provided" do | ||
expect do | ||
ExpectCertificateTransparency.validate_config!(foo: "bar") | ||
end.to raise_error(ExpectCertificateTransparencyConfigError) | ||
end | ||
|
||
it "raises an exception with an invalid max-age" do | ||
expect do | ||
ExpectCertificateTransparency.validate_config!(max_age: "abc123") | ||
end.to raise_error(ExpectCertificateTransparencyConfigError) | ||
end | ||
|
||
it "raises an exception with an invalid enforce value" do | ||
expect do | ||
ExpectCertificateTransparency.validate_config!(enforce: "brokenstring") | ||
end.to raise_error(ExpectCertificateTransparencyConfigError) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters