-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into stages/rc-2019-01-31
- Loading branch information
Showing
14 changed files
with
470 additions
and
94 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
class FinitePolicyMappingDepth | ||
attr_reader :value | ||
|
||
# :reek:FeatureEnvy | ||
def initialize(value) | ||
@value = value.to_i | ||
end | ||
|
||
def negative? | ||
@value.negative? | ||
end | ||
|
||
def any? | ||
false | ||
end | ||
|
||
# :reek:FeatureEnvy | ||
def <=>(other) | ||
if other.any? | ||
-1 | ||
else | ||
value <=> other.value | ||
end | ||
end | ||
|
||
def -(other) | ||
FinitePolicyMappingDepth.new(value - other) | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
class InfinitePolicyMappingDepth | ||
def negative? | ||
false | ||
end | ||
|
||
def any? | ||
true | ||
end | ||
|
||
# :reek:UtilityFunction | ||
def <=>(other) | ||
if other.any? | ||
0 | ||
else | ||
1 | ||
end | ||
end | ||
|
||
def -(_other) | ||
self | ||
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
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,90 @@ | ||
class CertificatePolicies | ||
KNOWN_POLICIES = [ | ||
# Policies we've seen marked critical | ||
'basicConstraints', | ||
'inhibitAnyPolicy', | ||
'keyUsage', | ||
'policyConstraints', | ||
# Policies we use that could be marked critical | ||
'authorityInfoAccess', | ||
'authorityKeyIdentifier', | ||
'certificatePolicies', | ||
'crlDistributionPoints', | ||
'policyMappings', | ||
'subjectKeyIdentifier', | ||
# Policies we don't use | ||
'nameConstraints', | ||
'subjectAltName', | ||
'subjectInfoAccess', | ||
].freeze | ||
|
||
def initialize(cert) | ||
@certificate = cert | ||
end | ||
|
||
def allowed_by_policy? | ||
# if at least one policy in the cert matches one of the "required policies", then we're good | ||
# otherwise, we want to allow it for now, but log the cert so we can see what policies are | ||
# coming up | ||
# This policy check is only on the leaf certificate - not used by CAs | ||
mapping = PolicyMappingService.new(@certificate).call | ||
expected_policies = required_policies | ||
cert_policies = policies.map { |policy| mapping[policy] } | ||
(cert_policies & expected_policies).any? | ||
end | ||
|
||
def policies | ||
(get_extension('certificatePolicies') || '').split(/\n/).map do |line| | ||
line.sub(/^Policy:\s+/, '') | ||
end | ||
end | ||
|
||
def critical_policies_recognized? | ||
(certificate.x509_cert.extensions.select(&:critical?).map(&:oid) - KNOWN_POLICIES).empty? | ||
end | ||
|
||
# provides a mapping of policy OIDs seen in child certificates to policy OIDs expected by the | ||
# issuing certificate | ||
def policy_mappings | ||
get_extension('policyMappings')&. | ||
split(/\s*,\s*/)&. | ||
map { |mapping| mapping.split(/:/).reverse }. | ||
to_h | ||
end | ||
|
||
ANY_POLICY_MAPPING_DEPTH = InfinitePolicyMappingDepth.new | ||
|
||
def policy_mappings_allowed(previous_allowed = ANY_POLICY_MAPPING_DEPTH) | ||
[inhibit_policy_mapping, previous_allowed - 1].min | ||
end | ||
|
||
def policy_constraints | ||
get_extension('policyConstraints')&. | ||
split(/\s*,\s*/)&. | ||
map { |mapping| mapping.split(/:/) }. | ||
to_h | ||
end | ||
|
||
def inhibit_policy_mapping | ||
value = policy_constraints.fetch('Inhibit Policy Mapping') { :any } | ||
if value == :any | ||
ANY_POLICY_MAPPING_DEPTH | ||
else | ||
FinitePolicyMappingDepth.new(value) | ||
end | ||
end | ||
|
||
private | ||
|
||
attr_reader :certificate | ||
|
||
# :reek:UtilityFunction | ||
def get_extension(oid) | ||
certificate.x509_cert.extensions.detect { |record| record.oid == oid }&.value | ||
end | ||
|
||
# :reek:UtilityFunction | ||
def required_policies | ||
JSON.parse(Figaro.env.required_policies || '[]') | ||
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
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,57 @@ | ||
class PolicyMappingService | ||
def initialize(certificate) | ||
@certificate = certificate | ||
end | ||
|
||
def call | ||
policy_mapping | ||
end | ||
|
||
private | ||
|
||
attr_reader :certificate | ||
|
||
def chain(set = []) | ||
# walk from the cert to a root - we can do this safely because we've already | ||
# constructed a path from the leaf cert to a trusted root elsewhere | ||
store = CertificateStore.instance | ||
@chain ||= begin | ||
signer = store[certificate.signing_key_id] | ||
while signer | ||
set << signer | ||
signer = !signer.self_signed? && store[signer.signing_key_id] | ||
end | ||
set.reverse | ||
end | ||
end | ||
|
||
# :reek:UtilityFunction | ||
def new_mapping | ||
Hash.new { |_, key| key } | ||
end | ||
|
||
# ultimately maps OIDs seen in child certs to OIDs we expect at the top level | ||
def policy_mapping | ||
return new_mapping if chain.empty? | ||
allowed_depth = CertificatePolicies.new(chain.first).policy_mappings_allowed | ||
|
||
chain.each_with_object(new_mapping) do |cert, mapping| | ||
next if allowed_depth != :any && allowed_depth.negative? | ||
|
||
allowed_depth = import_mapping(mapping, cert, allowed_depth) | ||
end | ||
end | ||
|
||
# :reek:UtilityFunction | ||
def import_mapping(mapping, cert, allowed_depth) | ||
policy = CertificatePolicies.new(cert) | ||
|
||
policy.policy_mappings.each do |(key, value)| | ||
# RFC 5280, section 4.2.1.5 requires that no mapping can be to or from | ||
# the value anyPolicy. | ||
next if ([key, value] & ['X509v3 Any Policy', Certificate::ANY_POLICY]).any? | ||
mapping[key] = mapping[value] | ||
end | ||
policy.policy_mappings_allowed(allowed_depth) | ||
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
Oops, something went wrong.