Allow custom domain labels

Original validator checked for domain labels as defined by RFC1034, however real
internet deals with other domains as well - starting with digits or symbols.
This change allows modifying the pattern to allow custom / relaxed rules.

Validation has been removed from adding a domain to a new extension, since it's
only used in fixups and the domain should be already validated. (or not, if not
configured)

Closes-bug: 1592489

Change-Id: Ib453054ba5f554bab28cff392c539e713fa28918
This commit is contained in:
Stanisław Pitucha 2016-06-16 16:15:59 +10:00
parent 6fdb9be69e
commit 2257b8bbed
7 changed files with 30 additions and 24 deletions

View File

@ -24,7 +24,6 @@ from pyasn1.type import tag as asn1_tag
from pyasn1.type import univ as asn1_univ from pyasn1.type import univ as asn1_univ
from anchor.asn1 import rfc5280 from anchor.asn1 import rfc5280
from anchor import util as a_utils
from anchor.X509 import errors from anchor.X509 import errors
from anchor.X509 import utils from anchor.X509 import utils
@ -331,11 +330,6 @@ class X509ExtensionSubjectAltName(X509Extension):
@modifies_ext_value @modifies_ext_value
def add_dns_id(self, dns_id, validate=True, ext_value=None): def add_dns_id(self, dns_id, validate=True, ext_value=None):
if validate:
try:
a_utils.verify_domain(dns_id, allow_wildcards=True)
except ValueError as e:
raise errors.X509Error("invalid domain provided: %s" % str(e))
new_pos = len(ext_value) new_pos = len(ext_value)
ext_value[new_pos] = None ext_value[new_pos] = None
ext_value[new_pos]['dNSName'] = dns_id ext_value[new_pos]['dNSName'] = dns_id

View File

@ -15,18 +15,12 @@ from __future__ import absolute_import
import base64 import base64
import os import os
import re
import stat import stat
from anchor import errors from anchor import errors
# RFC1034 allows a simple " " too, but it's not allowed in certificates, so it def verify_domain(domain, label_re_comp, allow_wildcards=False):
# will not match
RE_DOMAIN_LABEL = re.compile("^[a-z](?:[-a-z0-9]*[a-z0-9])?$", re.IGNORECASE)
def verify_domain(domain, allow_wildcards=False):
labels = domain.split('.') labels = domain.split('.')
if labels[-1] == "": if labels[-1] == "":
# single trailing . is ok, ignore # single trailing . is ok, ignore
@ -44,7 +38,7 @@ def verify_domain(domain, allow_wildcards=False):
"domain <%s> has wildcard that's not in the " "domain <%s> has wildcard that's not in the "
"left-most label (RFC6125/6.4.3)" % (domain,)) "left-most label (RFC6125/6.4.3)" % (domain,))
else: else:
if RE_DOMAIN_LABEL.match(label) is None: if label_re_comp.match(label) is None:
raise ValueError( raise ValueError(
"domain <%s> contains invalid characters " "domain <%s> contains invalid characters "
"(RFC1034/3.5)" % (domain,)) "(RFC1034/3.5)" % (domain,))

View File

@ -28,12 +28,22 @@ from anchor.validators import errors
from anchor.X509 import errors as x509_errors from anchor.X509 import errors as x509_errors
from anchor.X509 import extension from anchor.X509 import extension
import re
def standards_compliance(csr=None, **kwargs):
# RFC1034 allows a simple " " too, but it's not allowed in certificates, so it
# will not match
#
# This pattern is RFC1034 compatible otherwise, but since newer RFCs actually
# allow any binary value as the domain label, some operators may want to relax
# the pattern in the configuration, for example to allow leading digits or
# hyphens.
def standards_compliance(csr=None, label_re="^[a-z](?:[-a-z0-9]*[a-z0-9])?$",
**kwargs):
"""Collection of separate cases of standards validation.""" """Collection of separate cases of standards validation."""
_no_extension_duplicates(csr) _no_extension_duplicates(csr)
_critical_flags(csr) _critical_flags(csr)
_valid_domains(csr) _valid_domains(csr, label_re)
_csr_signature(csr) _csr_signature(csr)
# TODO(stan): validate srv/uri, distinct DNs, email format, identity keys # TODO(stan): validate srv/uri, distinct DNs, email format, identity keys
@ -67,7 +77,7 @@ def _critical_flags(csr):
"(RFC5280/4.1.2.9)") "(RFC5280/4.1.2.9)")
def _valid_domains(csr): def _valid_domains(csr, label_re="^[a-z](?:[-a-z0-9]*[a-z0-9])?$"):
"""Format of the domin names """Format of the domin names
See RFC5280 section 4.2.1.6 / RFC6125 / RFC1034 See RFC5280 section 4.2.1.6 / RFC6125 / RFC1034
@ -76,10 +86,12 @@ def _valid_domains(csr):
if not sans: if not sans:
return return
label_re_comp = re.compile(label_re, re.IGNORECASE)
ext = sans[0] ext = sans[0]
for domain in ext.get_dns_ids(): for domain in ext.get_dns_ids():
try: try:
util.verify_domain(domain, allow_wildcards=True) util.verify_domain(domain, label_re_comp, allow_wildcards=True)
except ValueError as e: except ValueError as e:
raise errors.ValidationError(str(e)) raise errors.ValidationError(str(e))

View File

@ -21,7 +21,9 @@
"authentication": "method_1", "authentication": "method_1",
"signing_ca": "local", "signing_ca": "local",
"validators": { "validators": {
"standards_compliance": {}, "standards_compliance": {
"label_re": "^[a-z](?:[-a-z0-9]*[a-z0-9])?$"
},
"source_cidrs": { "source_cidrs": {
"cidrs": ["127.0.0.0/8"] "cidrs": ["127.0.0.0/8"]
} }

View File

@ -21,6 +21,10 @@ The following validators are implemented at the moment:
Any requests produced using standard tooling that fail this check should be Any requests produced using standard tooling that fail this check should be
reported as Anchor issues. reported as Anchor issues.
Parameters:
- ``label_re``: pattern for acceptable domain label format
``whitelist_names`` ``whitelist_names``
Verifies: CSR. Parameters: Verifies: CSR. Parameters:

View File

@ -146,11 +146,6 @@ class TestSubjectAltName(unittest.TestCase):
self.ext.add_ip(self.ip) self.ext.add_ip(self.ip)
self.assertEqual([self.domain], self.ext.get_dns_ids()) self.assertEqual([self.domain], self.ext.get_dns_ids())
def test_add_dns_id_validation(self):
self.ext.add_dns_id("good.exapmle.com")
with self.assertRaises(errors.X509Error):
self.ext.add_dns_id("-blah")
def test_ips(self): def test_ips(self):
self.ext.add_dns_id(self.domain) self.ext.add_dns_id(self.domain)
self.ext.add_ip(self.ip) self.ext.add_ip(self.ip)

View File

@ -163,6 +163,11 @@ class TestValidDomains(unittest.TestCase):
with self.assertRaises(errors.ValidationError): with self.assertRaises(errors.ValidationError):
standards._valid_domains(csr) standards._valid_domains(csr)
def test_custom_re(self):
csr = self._create_csr_with_domain_san('123.example.com.')
with self.assertRaises(errors.ValidationError):
standards._valid_domains(csr, "^\s\+$")
class TestCsrSignature(tests.DefaultRequestMixin, unittest.TestCase): class TestCsrSignature(tests.DefaultRequestMixin, unittest.TestCase):
def test_csr_signature(self): def test_csr_signature(self):