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:
parent
6fdb9be69e
commit
2257b8bbed
|
@ -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
|
||||||
|
|
|
@ -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,))
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue