Skip to content

Commit f3e285e

Browse files
committed
WL#16173: Update allowed cipher and cipher-suite lists
The Oracle Cryptography Review Board has introduced an updated list (categories) of TLS Ciphers and Versions. This work log removes TLS Ciphers and Versions included in the "Unacceptable" category, and depracates those listed in the "Deprecated" category. These changes apply for both the Classic API and XDevAPI. Change-Id: I357331297aaad091dd8d16b3e1e72f0ff4eb2e7c
1 parent f71acca commit f3e285e

File tree

17 files changed

+1413
-193
lines changed

17 files changed

+1413
-193
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ v8.4.0
1212
======
1313

1414
- WL#16203: GPL License Exception Update
15+
- WL#16173: Update allowed cipher and cipher-suite lists
1516
- WL#16053: Support GSSAPI/Kerberos authentication on Windows using authentication_ldap_sasl_client plug-in for C-extension
1617

1718
v8.3.0
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Copyright (c) 2024, Oracle and/or its affiliates.
2+
#
3+
# This program is free software; you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License, version 2.0, as
5+
# published by the Free Software Foundation.
6+
#
7+
# This program is designed to work with certain software (including
8+
# but not limited to OpenSSL) that is licensed under separate terms,
9+
# as designated in a particular file or component or in included license
10+
# documentation. The authors of MySQL hereby grant you an
11+
# additional permission to link the program and your derivative works
12+
# with the separately licensed software that they have either included with
13+
# the program or referenced in the documentation.
14+
#
15+
# Without limiting anything contained in the foregoing, this file,
16+
# which is part of MySQL Connector/Python, is also subject to the
17+
# Universal FOSS Exception, version 1.0, a copy of which can be found at
18+
# http://oss.oracle.com/licenses/universal-foss-exception.
19+
#
20+
# This program is distributed in the hope that it will be useful, but
21+
# WITHOUT ANY WARRANTY; without even the implied warranty of
22+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23+
# See the GNU General Public License, version 2.0, for more details.
24+
#
25+
# You should have received a copy of the GNU General Public License
26+
# along with this program; if not, write to the Free Software Foundation, Inc.,
27+
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
28+
29+
"""
30+
Script to generate `tls_ciphers.py` given the OSSA CRB TLS Ciphersuites JSON file.
31+
32+
This script produces `tls_ciphers.py` given the OSSA CRB TLS Ciphersuites
33+
JSON file. It's up to the developer to decide where or when this program
34+
will be executed.
35+
36+
The program expects you to provide two command-line arguments:
37+
38+
--src: Absolute path to the OSSA CRB TLS Ciphersuites JSON file.
39+
--dst: Absolute path to the folder location where `tls_ciphers.py` must be placed.
40+
41+
You cannot change the name of the produced file, however, you can control the
42+
location where it should be placed.
43+
44+
Use case example: Near the push freeze date of each release, the developer
45+
produces an updated `tls_ciphers.py` based on the latest version of the
46+
OSSA CRB TLS Ciphersuites file.
47+
"""
48+
49+
import json
50+
import os
51+
import pathlib
52+
53+
from argparse import ArgumentParser, Namespace
54+
from typing import Any, Dict
55+
56+
HEADER = """{0}
57+
58+
# Generated from the OSSA cipher list
59+
# version: {1}
60+
# date: {2}\n
61+
from typing import Dict, List\n\n
62+
APPROVED_TLS_VERSIONS: List[str] = ["TLSv1.2", "TLSv1.3"]
63+
{3}
64+
65+
DEPRECATED_TLS_VERSIONS: List[str] = []
66+
{4}
67+
68+
UNACCEPTABLE_TLS_VERSIONS: List[str] = ["TLSv1", "TLSv1.0", "TLSv1.1"]
69+
{5}
70+
71+
"""
72+
73+
LINE_NEW_CATEGORY = """{0}: Dict[str, Dict[str, str]] = """
74+
75+
76+
# Helper to setup command-line argument parser
77+
def setup_cmd_parser() -> Namespace:
78+
parser = ArgumentParser(
79+
description="Script to autogenerate tls_ciphers.py. "
80+
"given the OSSA CRB TLS Ciphersuites json file."
81+
)
82+
parser.add_argument(
83+
"--src",
84+
nargs="?",
85+
help="Absolute path to the OSSA CRB TLS Ciphersuites json file.",
86+
required=True,
87+
metavar="ossa_cipher_json_file_src",
88+
)
89+
parser.add_argument(
90+
"--dst",
91+
nargs="?",
92+
help="Absolute path to the folder location where tls_ciphers.py must be dumped.",
93+
required=True,
94+
metavar="tls_ciphers_python_file_dst",
95+
)
96+
return parser.parse_args()
97+
98+
99+
def get_category_by_tls_version(
100+
ossa_cipher_data: Dict[str, Any], category: str, tls_version: str
101+
) -> Dict[str, str]:
102+
return {
103+
category_data["iana_cipher_name"]: category_data["openssl_cipher_name"]
104+
for category_data in ossa_cipher_data[category]
105+
if tls_version in category_data["tls_protocol"]
106+
}
107+
108+
109+
def main(src_file_location: pathlib.Path, dst_ciphers_file: pathlib.Path) -> None:
110+
ossa_cipher = json.load(open(src_file_location))
111+
112+
categories = {
113+
cat: {}
114+
for cat in [
115+
"mandatory_tls_ciphersuites",
116+
"approved_tls_ciphersuites",
117+
"deprecated_tls_ciphersuites",
118+
"unacceptable_tls_ciphersuites",
119+
]
120+
}
121+
tls_versions = ["TLSv1.2", "TLSv1.3"]
122+
123+
for cat in categories:
124+
for tls_version in tls_versions:
125+
categories[cat][tls_version] = get_category_by_tls_version(
126+
ossa_cipher_data=ossa_cipher, category=cat, tls_version=tls_version
127+
)
128+
129+
with open(dst_ciphers_file, mode="w") as f:
130+
f.write(
131+
HEADER.format(
132+
'"""TLS ciphersuites and versions."""',
133+
ossa_cipher["metadata"][0]["version"],
134+
ossa_cipher["metadata"][0]["date"],
135+
'"""Approved TLS versions."""',
136+
'"""Deprecated TLS versions."""',
137+
'"""Unacceptable TLS versions."""',
138+
)
139+
)
140+
for i, cat in enumerate(categories):
141+
f.write(LINE_NEW_CATEGORY.format(cat.upper()))
142+
json.dump(categories[cat], f, indent=4)
143+
f.write(
144+
'\n"""Access dictionary by TLS version that translates from cipher '
145+
'suites IANI (key)\n to OpenSSL name (value)."""'
146+
)
147+
f.write("\n" if i + 1 == len(categories) else "\n\n")
148+
149+
150+
if __name__ == "__main__":
151+
ossa_cipher_file_src = vars(setup_cmd_parser()).get("src")
152+
tls_ciphers_file_dst = vars(setup_cmd_parser()).get("dst")
153+
154+
if not ossa_cipher_file_src:
155+
raise RuntimeError(
156+
"No src file was provided. Please provide the absolute path to "
157+
"the OSSA CRB TLS Ciphersuites file. "
158+
"Use --help option for argument details."
159+
)
160+
if not tls_ciphers_file_dst:
161+
raise RuntimeError(
162+
"No dst file was provided. Please provide the absolute path to "
163+
"the folder location where tls_ciphers.py must be dumped. "
164+
"Use --help option for argument details."
165+
)
166+
167+
src_file_location = pathlib.Path(ossa_cipher_file_src)
168+
dst_ciphers_file = pathlib.Path(tls_ciphers_file_dst)
169+
170+
for p in [src_file_location, dst_ciphers_file]:
171+
if not os.path.exists(p):
172+
raise FileNotFoundError(f"File {p} does not exists.")
173+
174+
if src_file_location.suffix != ".json":
175+
raise RuntimeError(f"File {src_file_location} must be a json file.")
176+
177+
if not os.path.isdir(dst_ciphers_file):
178+
raise RuntimeError(f"File {dst_ciphers_file} must be a folder location.")
179+
180+
main(src_file_location, pathlib.Path(dst_ciphers_file, "tls_ciphers.py"))

mysql-connector-python/lib/mysql/connector/abstracts.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
from .constants import (
7474
CONN_ATTRS_DN,
7575
DEFAULT_CONFIGURATION,
76-
DEPRECATED_TLS_VERSIONS,
7776
OPENSSL_CS_NAMES,
7877
TLS_CIPHER_SUITES,
7978
TLS_VERSIONS,
@@ -105,6 +104,7 @@
105104
)
106105

107106
from .optionfiles import read_option_files
107+
from .tls_ciphers import UNACCEPTABLE_TLS_CIPHERSUITES, UNACCEPTABLE_TLS_VERSIONS
108108
from .types import (
109109
BinaryProtocolType,
110110
DescriptionType,
@@ -129,7 +129,7 @@
129129
"TLS protocol version (should be one of {})."
130130
)
131131

132-
TLS_VERSION_DEPRECATED_ERROR = (
132+
TLS_VERSION_UNACCEPTABLE_ERROR = (
133133
"The given tls_version: '{}' are no longer allowed (should be one of {})."
134134
)
135135

@@ -340,7 +340,7 @@ def _validate_tls_ciphersuites(self) -> None:
340340
# an older version.
341341
tls_versions.sort(reverse=True) # type: ignore[union-attr]
342342
newer_tls_ver = tls_versions[0]
343-
# translated_names[0] belongs to TLSv1, TLSv1.1 and TLSv1.2
343+
# translated_names[0] are TLSv1.2 only
344344
# translated_names[1] are TLSv1.3 only
345345
translated_names: List[List[str]] = [[], []]
346346
iani_cipher_suites_names = {}
@@ -380,6 +380,18 @@ def _validate_tls_ciphersuites(self) -> None:
380380
"No valid cipher suite found in the 'tls_ciphersuites' list"
381381
)
382382

383+
# raise an error when using an unacceptable cipher
384+
for cipher_as_ossl in translated_names[0]:
385+
if cipher_as_ossl in UNACCEPTABLE_TLS_CIPHERSUITES["TLSv1.2"].values():
386+
raise NotSupportedError(
387+
f"Cipher {cipher_as_ossl} when used with TLSv1.2 is unacceptable."
388+
)
389+
for cipher_as_ossl in translated_names[1]:
390+
if cipher_as_ossl in UNACCEPTABLE_TLS_CIPHERSUITES["TLSv1.3"].values():
391+
raise NotSupportedError(
392+
f"Cipher {cipher_as_ossl} when used with TLSv1.3 is unacceptable."
393+
)
394+
383395
self._ssl["tls_ciphersuites"] = [
384396
":".join(translated_names[0]),
385397
":".join(translated_names[1]),
@@ -441,13 +453,13 @@ def _validate_tls_versions(self) -> None:
441453
)
442454

443455
use_tls_versions = []
444-
deprecated_tls_versions = []
456+
unacceptable_tls_versions = []
445457
invalid_tls_versions = []
446458
for tls_ver in tls_versions:
447459
if tls_ver in TLS_VERSIONS:
448460
use_tls_versions.append(tls_ver)
449-
if tls_ver in DEPRECATED_TLS_VERSIONS:
450-
deprecated_tls_versions.append(tls_ver)
461+
if tls_ver in UNACCEPTABLE_TLS_VERSIONS:
462+
unacceptable_tls_versions.append(tls_ver)
451463
else:
452464
invalid_tls_versions.append(tls_ver)
453465

@@ -456,12 +468,11 @@ def _validate_tls_versions(self) -> None:
456468
raise NotSupportedError(
457469
TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS)
458470
)
459-
use_tls_versions.sort()
460471
self._ssl["tls_versions"] = use_tls_versions
461-
elif deprecated_tls_versions:
472+
elif unacceptable_tls_versions:
462473
raise NotSupportedError(
463-
TLS_VERSION_DEPRECATED_ERROR.format(
464-
deprecated_tls_versions, TLS_VERSIONS
474+
TLS_VERSION_UNACCEPTABLE_ERROR.format(
475+
unacceptable_tls_versions, TLS_VERSIONS
465476
)
466477
)
467478
elif invalid_tls_versions:
@@ -731,13 +742,10 @@ def config(self, **kwargs: Any) -> None:
731742
raise AttributeError(
732743
"ssl_key and ssl_cert need to be both set, or neither"
733744
)
734-
if "tls_versions" in self._ssl and self._ssl["tls_versions"] is not None:
745+
if self._ssl.get("tls_versions") is not None:
735746
self._validate_tls_versions()
736747

737-
if (
738-
"tls_ciphersuites" in self._ssl
739-
and self._ssl["tls_ciphersuites"] is not None
740-
):
748+
if self._ssl.get("tls_ciphersuites") is not None:
741749
self._validate_tls_ciphersuites()
742750

743751
if self._conn_attrs is None:

0 commit comments

Comments
 (0)