diff --git a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/failing/ipv6-1143.conf b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/failing/ipv6-1143.conf
deleted file mode 100644
index ab4ed412e..000000000
--- a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/failing/ipv6-1143.conf
+++ /dev/null
@@ -1,9 +0,0 @@
-
-DocumentRoot /xxxx/
-ServerName noodles.net.nz
-ServerAlias www.noodles.net.nz
-CustomLog ${APACHE_LOG_DIR}/domlogs/noodles.log combined
-
- AllowOverride All
-
-
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/failing/ipv6-1143b.conf b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/failing/ipv6-1143b.conf
deleted file mode 100644
index 25655a07c..000000000
--- a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/failing/ipv6-1143b.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-DocumentRoot /xxxx/
-ServerName noodles.net.nz
-ServerAlias www.noodles.net.nz
-CustomLog ${APACHE_LOG_DIR}/domlogs/noodles.log combined
-
- AllowOverride All
-
-
- SSLEngine on
-
- SSLHonorCipherOrder On
- SSLProtocol all -SSLv2 -SSLv3
- SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH +aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"
-
- SSLCertificateFile /xxxx/noodles.net.nz.crt
- SSLCertificateKeyFile /xxxx/noodles.net.nz.key
-
- Header set Strict-Transport-Security "max-age=31536000; preload"
-
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143.conf b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143.conf
new file mode 100644
index 000000000..ad988dc05
--- /dev/null
+++ b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143.conf
@@ -0,0 +1,9 @@
+
+DocumentRoot /tmp
+ServerName example.com
+ServerAlias www.example.com
+CustomLog ${APACHE_LOG_DIR}/example.log combined
+
+ AllowOverride All
+
+
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143b.conf b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143b.conf
new file mode 100644
index 000000000..e2b4fd3da
--- /dev/null
+++ b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143b.conf
@@ -0,0 +1,18 @@
+
+DocumentRoot /tmp
+ServerName example.com
+ServerAlias www.example.com
+CustomLog ${APACHE_LOG_DIR}/example.log combined
+
+ AllowOverride All
+
+
+ SSLEngine on
+
+ SSLHonorCipherOrder On
+ SSLProtocol all -SSLv2 -SSLv3
+ SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH +aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"
+
+ SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
+ SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
+
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143c.conf b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143c.conf
new file mode 100644
index 000000000..f2d2ecbea
--- /dev/null
+++ b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143c.conf
@@ -0,0 +1,9 @@
+
+DocumentRoot /tmp
+ServerName example.com
+ServerAlias www.example.com
+CustomLog ${APACHE_LOG_DIR}/example.log combined
+
+ AllowOverride All
+
+
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143d.conf b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143d.conf
new file mode 100644
index 000000000..f5b7a2b45
--- /dev/null
+++ b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/passing/ipv6-1143d.conf
@@ -0,0 +1,18 @@
+
+DocumentRoot /tmp
+ServerName example.com
+ServerAlias www.example.com
+CustomLog ${APACHE_LOG_DIR}/example.log combined
+
+ AllowOverride All
+
+
+ SSLEngine on
+
+ SSLHonorCipherOrder On
+ SSLProtocol all -SSLv2 -SSLv3
+ SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH +aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"
+
+ SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
+ SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
+
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf b/letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf
index c759768c5..2bd4e1fe9 100644
--- a/letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf
+++ b/letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf
@@ -1,4 +1,4 @@
-
+
ServerName ip-172-30-0-17
ServerAdmin webmaster@localhost
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/util.py b/letsencrypt-apache/letsencrypt_apache/tests/util.py
index 928084e3c..2fbfd70c6 100644
--- a/letsencrypt-apache/letsencrypt_apache/tests/util.py
+++ b/letsencrypt-apache/letsencrypt_apache/tests/util.py
@@ -133,8 +133,9 @@ def get_vh_truth(temp_dir, config_name):
obj.VirtualHost(
os.path.join(prefix, "000-default.conf"),
os.path.join(aug_pre, "000-default.conf/VirtualHost"),
- set([obj.Addr.fromstring("*:80")]), False, True,
- "ip-172-30-0-17"),
+ set([obj.Addr.fromstring("*:80"),
+ obj.Addr.fromstring("[::]:80")]),
+ False, True, "ip-172-30-0-17"),
obj.VirtualHost(
os.path.join(prefix, "letsencrypt.conf"),
os.path.join(aug_pre, "letsencrypt.conf/VirtualHost"),
diff --git a/letsencrypt/plugins/common.py b/letsencrypt/plugins/common.py
index f6a2c3d76..a9410d514 100644
--- a/letsencrypt/plugins/common.py
+++ b/letsencrypt/plugins/common.py
@@ -104,14 +104,24 @@ class Addr(object):
:param str port: port number or \*, or ""
"""
- def __init__(self, tup):
+ def __init__(self, tup, ipv6=False):
self.tup = tup
+ self.ipv6 = ipv6
@classmethod
def fromstring(cls, str_addr):
"""Initialize Addr from string."""
- tup = str_addr.partition(':')
- return cls((tup[0], tup[2]))
+ if str_addr.startswith('['):
+ # ipv6 addresses starts with [
+ endIndex = str_addr.rfind(']')
+ host = str_addr[:endIndex + 1]
+ port = ''
+ if len(str_addr) > endIndex + 2 and str_addr[endIndex + 1] == ':':
+ port = str_addr[endIndex + 2:]
+ return cls((host, port), ipv6=True)
+ else:
+ tup = str_addr.partition(':')
+ return cls((tup[0], tup[2]))
def __str__(self):
if self.tup[1]:
@@ -120,7 +130,16 @@ class Addr(object):
def __eq__(self, other):
if isinstance(other, self.__class__):
- return self.tup == other.tup
+ if self.ipv6:
+ # compare normalized to take different
+ # styles of representation into account
+ return (other.ipv6 and
+ self._normalize_ipv6(self.tup[0]) ==
+ self._normalize_ipv6(other.tup[0]) and
+ self.tup[1] == other.tup[1])
+ else:
+ return self.tup == other.tup
+
return False
def __hash__(self):
@@ -136,7 +155,44 @@ class Addr(object):
def get_addr_obj(self, port):
"""Return new address object with same addr and new port."""
- return self.__class__((self.tup[0], port))
+ return self.__class__((self.tup[0], port), self.ipv6)
+
+ def _normalize_ipv6(self, addr):
+ """Return IPv6 address in normalized form, helper function"""
+ addr = addr.lstrip("[")
+ addr = addr.rstrip("]")
+ return self._explode_ipv6(addr)
+
+ def get_ipv6_exploded(self):
+ """Return IPv6 in normalized form"""
+ if self.ipv6:
+ return ":".join(self._normalize_ipv6(self.tup[0]))
+ return ""
+
+ def _explode_ipv6(self, addr):
+ """Explode IPv6 address for comparison"""
+ result = ['0', '0', '0', '0', '0', '0', '0', '0']
+ addr_list = addr.split(":")
+ if len(addr_list) > len(result):
+ # too long, truncate
+ addr_list = addr_list[0:len(result)]
+ append_to_end = False
+ for i in range(0, len(addr_list)):
+ block = addr_list[i]
+ if len(block) == 0:
+ # encountered ::, so rest of the blocks should be
+ # appended to the end
+ append_to_end = True
+ continue
+ elif len(block) > 1:
+ # remove leading zeros
+ block = block.lstrip("0")
+ if not append_to_end:
+ result[i] = str(block)
+ else:
+ # count the location from the end using negative indices
+ result[i-len(addr_list)] = str(block)
+ return result
class TLSSNI01(object):
diff --git a/letsencrypt/plugins/common_test.py b/letsencrypt/plugins/common_test.py
index 55319f0a0..a4292151e 100644
--- a/letsencrypt/plugins/common_test.py
+++ b/letsencrypt/plugins/common_test.py
@@ -81,6 +81,11 @@ class AddrTest(unittest.TestCase):
self.addr1 = Addr.fromstring("192.168.1.1")
self.addr2 = Addr.fromstring("192.168.1.1:*")
self.addr3 = Addr.fromstring("192.168.1.1:80")
+ self.addr4 = Addr.fromstring("[fe00::1]")
+ self.addr5 = Addr.fromstring("[fe00::1]:*")
+ self.addr6 = Addr.fromstring("[fe00::1]:80")
+ self.addr7 = Addr.fromstring("[fe00::1]:5")
+ self.addr8 = Addr.fromstring("[fe00:1:2:3:4:5:6:7:8:9]:8080")
def test_fromstring(self):
self.assertEqual(self.addr1.get_addr(), "192.168.1.1")
@@ -89,22 +94,49 @@ class AddrTest(unittest.TestCase):
self.assertEqual(self.addr2.get_port(), "*")
self.assertEqual(self.addr3.get_addr(), "192.168.1.1")
self.assertEqual(self.addr3.get_port(), "80")
+ self.assertEqual(self.addr4.get_addr(), "[fe00::1]")
+ self.assertEqual(self.addr4.get_port(), "")
+ self.assertEqual(self.addr5.get_addr(), "[fe00::1]")
+ self.assertEqual(self.addr5.get_port(), "*")
+ self.assertEqual(self.addr6.get_addr(), "[fe00::1]")
+ self.assertEqual(self.addr6.get_port(), "80")
+ self.assertEqual(self.addr6.get_ipv6_exploded(),
+ "fe00:0:0:0:0:0:0:1")
+ self.assertEqual(self.addr1.get_ipv6_exploded(),
+ "")
+ self.assertEqual(self.addr7.get_port(), "5")
+ self.assertEqual(self.addr8.get_ipv6_exploded(),
+ "fe00:1:2:3:4:5:6:7")
def test_str(self):
self.assertEqual(str(self.addr1), "192.168.1.1")
self.assertEqual(str(self.addr2), "192.168.1.1:*")
self.assertEqual(str(self.addr3), "192.168.1.1:80")
+ self.assertEqual(str(self.addr4), "[fe00::1]")
+ self.assertEqual(str(self.addr5), "[fe00::1]:*")
+ self.assertEqual(str(self.addr6), "[fe00::1]:80")
def test_get_addr_obj(self):
self.assertEqual(str(self.addr1.get_addr_obj("443")), "192.168.1.1:443")
self.assertEqual(str(self.addr2.get_addr_obj("")), "192.168.1.1")
self.assertEqual(str(self.addr1.get_addr_obj("*")), "192.168.1.1:*")
+ self.assertEqual(str(self.addr4.get_addr_obj("443")), "[fe00::1]:443")
+ self.assertEqual(str(self.addr5.get_addr_obj("")), "[fe00::1]")
+ self.assertEqual(str(self.addr4.get_addr_obj("*")), "[fe00::1]:*")
def test_eq(self):
self.assertEqual(self.addr1, self.addr2.get_addr_obj(""))
self.assertNotEqual(self.addr1, self.addr2)
self.assertFalse(self.addr1 == 3333)
+ self.assertEqual(self.addr4, self.addr4.get_addr_obj(""))
+ self.assertNotEqual(self.addr4, self.addr5)
+ self.assertFalse(self.addr4 == 3333)
+ from letsencrypt.plugins.common import Addr
+ self.assertEqual(self.addr4, Addr.fromstring("[fe00:0:0::1]"))
+ self.assertEqual(self.addr4, Addr.fromstring("[fe00:0::0:0:1]"))
+
+
def test_set_inclusion(self):
from letsencrypt.plugins.common import Addr
set_a = set([self.addr1, self.addr2])
@@ -114,6 +146,13 @@ class AddrTest(unittest.TestCase):
self.assertEqual(set_a, set_b)
+ set_c = set([self.addr4, self.addr5])
+ addr4b = Addr.fromstring("[fe00::1]")
+ addr5b = Addr.fromstring("[fe00::1]:*")
+ set_d = set([addr4b, addr5b])
+
+ self.assertEqual(set_c, set_d)
+
class TLSSNI01Test(unittest.TestCase):
"""Tests for letsencrypt.plugins.common.TLSSNI01."""