Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

"""Tools for checking certificate revocation.""" 

import logging 

import re 

 

from subprocess import Popen, PIPE 

 

from certbot import errors 

from certbot import util 

 

logger = logging.getLogger(__name__) 

 

class RevocationChecker(object): 

"This class figures out OCSP checking on this system, and performs it." 

 

def __init__(self): 

self.broken = False 

 

if not util.exe_exists("openssl"): 

logger.info("openssl not installed, can't check revocation") 

self.broken = True 

return 

 

# New versions of openssl want -header var=val, old ones want -header var val 

test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], 

stdout=PIPE, stderr=PIPE, universal_newlines=True) 

_out, err = test_host_format.communicate() 

if "Missing =" in err: 

self.host_args = lambda host: ["Host=" + host] 

else: 

self.host_args = lambda host: ["Host", host] 

 

 

def ocsp_revoked(self, cert_path, chain_path): 

"""Get revoked status for a particular cert version. 

 

.. todo:: Make this a non-blocking call 

 

:param str cert_path: Path to certificate 

:param str chain_path: Path to intermediate cert 

:rtype bool or None: 

:returns: True if revoked; False if valid or the check failed 

 

""" 

if self.broken: 

return False 

 

 

url, host = self.determine_ocsp_server(cert_path) 

if not host: 

return False 

# jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this! 

cmd = ["openssl", "ocsp", 

"-no_nonce", 

"-issuer", chain_path, 

"-cert", cert_path, 

"-url", url, 

"-CAfile", chain_path, 

"-verify_other", chain_path, 

"-trust_other", 

"-header"] + self.host_args(host) 

logger.debug("Querying OCSP for %s", cert_path) 

logger.debug(" ".join(cmd)) 

try: 

output, err = util.run_script(cmd, log=logger.debug) 

except errors.SubprocessError: 

logger.info("OCSP check failed for %s (are we offline?)", cert_path) 

return False 

 

return _translate_ocsp_query(cert_path, output, err) 

 

 

def determine_ocsp_server(self, cert_path): 

"""Extract the OCSP server host from a certificate. 

 

:param str cert_path: Path to the cert we're checking OCSP for 

:rtype tuple: 

:returns: (OCSP server URL or None, OCSP server host or None) 

 

""" 

try: 

url, _err = util.run_script( 

["openssl", "x509", "-in", cert_path, "-noout", "-ocsp_uri"], 

log=logger.debug) 

except errors.SubprocessError: 

logger.info("Cannot extract OCSP URI from %s", cert_path) 

return None, None 

 

url = url.rstrip() 

host = url.partition("://")[2].rstrip("/") 

if host: 

return url, host 

else: 

logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path) 

return None, None 

 

def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): 

"""Parse openssl's weird output to work out what it means.""" 

 

states = ("good", "revoked", "unknown") 

patterns = [r"{0}: (WARNING.*)?{1}".format(cert_path, s) for s in states] 

good, revoked, unknown = (re.search(p, ocsp_output, flags=re.DOTALL) for p in patterns) 

 

warning = good.group(1) if good else None 

 

if (not "Response verify OK" in ocsp_errors) or (good and warning) or unknown: 

logger.info("Revocation status for %s is unknown", cert_path) 

logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors) 

return False 

elif good and not warning: 

return False 

elif revoked: 

warning = revoked.group(1) 

if warning: 

logger.info("OCSP revocation warning: %s", warning) 

return True 

else: 

logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", 

ocsp_output, ocsp_errors) 

return False