Introduce fips_safe_md5, see issue #7611

This commit is contained in:
Lars Hupfeldt
2020-05-05 00:50:51 +02:00
parent 153682dd4c
commit 1b8415a1c1
6 changed files with 25 additions and 10 deletions

View File

@@ -85,6 +85,7 @@ Other contributors, listed alphabetically, are:
* Daniel Pizetta -- inheritance diagram improvements * Daniel Pizetta -- inheritance diagram improvements
* KINEBUCHI Tomohiko -- typing Sphinx as well as docutils * KINEBUCHI Tomohiko -- typing Sphinx as well as docutils
* Adrián Chaves (Gallaecio) -- coverage builder improvements * Adrián Chaves (Gallaecio) -- coverage builder improvements
* Lars Hupfeldt Nielsen - OpenSSL FIPS mode md5 bug fix
Many thanks for all contributions! Many thanks for all contributions!

View File

@@ -17,6 +17,7 @@ Bugs fixed
---------- ----------
* #7567: autodoc: parametrized types are shown twice for generic types * #7567: autodoc: parametrized types are shown twice for generic types
* #7611: md5 fails when OpenSSL FIPS is enabled
Testing Testing
-------- --------

View File

@@ -13,7 +13,6 @@ import posixpath
import re import re
import sys import sys
import warnings import warnings
from hashlib import md5
from os import path from os import path
from typing import Any, Dict, IO, Iterable, Iterator, List, Set, Tuple from typing import Any, Dict, IO, Iterable, Iterator, List, Set, Tuple
@@ -38,7 +37,7 @@ from sphinx.highlighting import PygmentsBridge
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.search import js_index from sphinx.search import js_index
from sphinx.theming import HTMLThemeFactory from sphinx.theming import HTMLThemeFactory
from sphinx.util import logging, progress_message, status_iterator from sphinx.util import logging, progress_message, status_iterator, fips_safe_md5
from sphinx.util.docutils import is_html5_writer_available, new_document from sphinx.util.docutils import is_html5_writer_available, new_document
from sphinx.util.fileutil import copy_asset from sphinx.util.fileutil import copy_asset
from sphinx.util.i18n import format_date from sphinx.util.i18n import format_date
@@ -77,7 +76,7 @@ def get_stable_hash(obj: Any) -> str:
return get_stable_hash(list(obj.items())) return get_stable_hash(list(obj.items()))
elif isinstance(obj, (list, tuple)): elif isinstance(obj, (list, tuple)):
obj = sorted(get_stable_hash(o) for o in obj) obj = sorted(get_stable_hash(o) for o in obj)
return md5(str(obj).encode()).hexdigest() return fips_safe_md5(str(obj).encode()).hexdigest()
class Stylesheet(str): class Stylesheet(str):

View File

@@ -38,7 +38,6 @@ r"""
import builtins import builtins
import inspect import inspect
import re import re
from hashlib import md5
from importlib import import_module from importlib import import_module
from typing import Any, Dict, Iterable, List, Tuple from typing import Any, Dict, Iterable, List, Tuple
from typing import cast from typing import cast
@@ -55,6 +54,7 @@ from sphinx.ext.graphviz import (
graphviz, figure_wrapper, graphviz, figure_wrapper,
render_dot_html, render_dot_latex, render_dot_texinfo render_dot_html, render_dot_latex, render_dot_texinfo
) )
from sphinx.util import fips_safe_md5
from sphinx.util.docutils import SphinxDirective from sphinx.util.docutils import SphinxDirective
from sphinx.writers.html import HTMLTranslator from sphinx.writers.html import HTMLTranslator
from sphinx.writers.latex import LaTeXTranslator from sphinx.writers.latex import LaTeXTranslator
@@ -387,7 +387,7 @@ class InheritanceDiagram(SphinxDirective):
def get_graph_hash(node: inheritance_diagram) -> str: def get_graph_hash(node: inheritance_diagram) -> str:
encoded = (node['content'] + str(node['parts'])).encode() encoded = (node['content'] + str(node['parts'])).encode()
return md5(encoded).hexdigest()[-10:] return fips_safe_md5(encoded).hexdigest()[-10:]
def html_visit_inheritance_diagram(self: HTMLTranslator, node: inheritance_diagram) -> None: def html_visit_inheritance_diagram(self: HTMLTranslator, node: inheritance_diagram) -> None:

View File

@@ -170,6 +170,21 @@ class FilenameUniqDict(dict):
self._existing = state self._existing = state
def fips_safe_md5(data=b'', **kwargs):
"""Wrapper around hashlib.md5
Attempt call with 'usedforsecurity=False' if we get a ValueError, which happens when OpenSSL FIPS mode is enabled:
ValueError: error:060800A3:digital envelope routines:EVP_DigestInit_ex:disabled for fips
See: https://github.com/sphinx-doc/sphinx/issues/7611
"""
try:
return md5(data, **kwargs)
except ValueError:
return md5(data, **kwargs, usedforsecurity=False)
class DownloadFiles(dict): class DownloadFiles(dict):
"""A special dictionary for download files. """A special dictionary for download files.
@@ -179,7 +194,7 @@ class DownloadFiles(dict):
def add_file(self, docname: str, filename: str) -> str: def add_file(self, docname: str, filename: str) -> str:
if filename not in self: if filename not in self:
digest = md5(filename.encode()).hexdigest() digest = fips_safe_md5(filename.encode()).hexdigest()
dest = '%s/%s' % (digest, os.path.basename(filename)) dest = '%s/%s' % (digest, os.path.basename(filename))
self[filename] = (set(), dest) self[filename] = (set(), dest)

View File

@@ -10,7 +10,6 @@
import os import os
import re import re
from hashlib import md5
from itertools import cycle, chain from itertools import cycle, chain
import pytest import pytest
@@ -19,7 +18,7 @@ from html5lib import HTMLParser
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
from sphinx.errors import ConfigError from sphinx.errors import ConfigError
from sphinx.testing.util import strip_escseq from sphinx.testing.util import strip_escseq
from sphinx.util import docutils from sphinx.util import docutils, fips_safe_md5
from sphinx.util.inventory import InventoryFile from sphinx.util.inventory import InventoryFile
@@ -450,9 +449,9 @@ def test_html_download(app):
@pytest.mark.sphinx('html', testroot='roles-download') @pytest.mark.sphinx('html', testroot='roles-download')
def test_html_download_role(app, status, warning): def test_html_download_role(app, status, warning):
app.build() app.build()
digest = md5(b'dummy.dat').hexdigest() digest = fips_safe_md5(b'dummy.dat').hexdigest()
assert (app.outdir / '_downloads' / digest / 'dummy.dat').exists() assert (app.outdir / '_downloads' / digest / 'dummy.dat').exists()
digest_another = md5(b'another/dummy.dat').hexdigest() digest_another = fips_safe_md5(b'another/dummy.dat').hexdigest()
assert (app.outdir / '_downloads' / digest_another / 'dummy.dat').exists() assert (app.outdir / '_downloads' / digest_another / 'dummy.dat').exists()
content = (app.outdir / 'index.html').read_text() content = (app.outdir / 'index.html').read_text()