Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/acme2certifier/acme_srv/helpers/validation.py: 17%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

148 statements  

1# -*- coding: utf-8 -*- 

2"""Validation utilities for acme2certifier""" 

3 

4import re 

5import logging 

6import ipaddress 

7from email.utils import parseaddr 

8from typing import List, Dict, Tuple, Optional 

9import idna 

10 

11 

12def dkeys_lower(tree: Dict[str, str]) -> Dict[str, str]: 

13 """lower characters in payload string""" 

14 if isinstance(tree, dict): 

15 result = {k.lower(): dkeys_lower(v) for k, v in tree.items()} 

16 elif isinstance(tree, list): 

17 result = [dkeys_lower(ele) for ele in tree] 

18 else: 

19 result = tree 

20 return result 

21 

22 

23def fqdn_in_san_check(logger: logging.Logger, san_list: List[str], fqdn: str) -> bool: 

24 """check if fqdn is in a list of sans""" 

25 logger.debug("Helper.fqdn_in_san_check([%s], %s)", san_list, fqdn) 

26 

27 result = False 

28 if fqdn and san_list: 

29 for san in san_list: 

30 try: 

31 _type, value = san.lower().split(":", 1) 

32 if fqdn == value: 

33 result = True 

34 break 

35 except Exception: 

36 logger.error("Error during SAN check. SAN split failed: %s", san) 

37 

38 logger.debug("Helper.fqdn_in_san_check() ended with: %s", result) 

39 return result 

40 

41 

42def validate_csr(logger: logging.Logger, order_dic: Dict[str, str], _csr) -> bool: 

43 """validate certificate signing request against order""" 

44 logger.debug("Helper.validate_csr(%s)", order_dic) 

45 return True 

46 

47 

48def normalize_email_address(logger: logging.Logger, raw: str) -> Optional[str]: 

49 """Parse and normalize an email address for identity comparison. 

50 

51 Returns a canonical form with a lowercased local part and an A-label domain, 

52 or None if the address cannot be parsed or the domain is not valid IDNA. 

53 """ 

54 logger.debug("Helper.normalize_email_address()") 

55 if not raw or not isinstance(raw, str): 

56 return None 

57 

58 _display, addr = parseaddr(raw.strip()) 

59 if not addr or "@" not in addr: 

60 logger.debug("Helper.normalize_email_address(): unparseable From header") 

61 return None 

62 

63 local, _, domain = addr.rpartition("@") 

64 local = local.strip() 

65 domain = domain.strip() 

66 if not local or not domain: 

67 return None 

68 

69 try: 

70 domain_ascii = idna.encode(domain).decode("ascii").lower() 

71 except idna.IDNAError as err: 

72 logger.debug( 

73 "Helper.normalize_email_address(): invalid domain %s: %s", domain, err 

74 ) 

75 return None 

76 

77 result = f"{local.lower()}@{domain_ascii}" 

78 logger.debug("Helper.normalize_email_address() ended with: %s", result) 

79 return result 

80 

81 

82def validate_email(logger: logging.Logger, contact_list: List[str]) -> bool: 

83 """validate contact against RFC608""" 

84 logger.debug("Helper.validate_email()") 

85 result = True 

86 pattern = ( 

87 r"^[A-Za-z0-9.+_-]+@[A-Za-z][A-Za-z0-9._-]+[A-Za-z0-9]" 

88 r"\.[A-Za-z]+(?:\.[A-Za-z]+)*$" 

89 ) 

90 # check if we got a list or single address 

91 if isinstance(contact_list, list): 

92 for contact in contact_list: 

93 contact = contact.replace("mailto:", "") 

94 contact = contact.lstrip() 

95 tmp_result = bool(re.search(pattern, contact)) 

96 logger.debug("# validate: %s result: %s", contact, tmp_result) 

97 if not tmp_result: 

98 result = tmp_result 

99 else: 

100 logger.debug("Helper.validate_email(): %s is not a list", contact_list) 

101 contact_list = contact_list.replace("mailto:", "") 

102 contact_list = contact_list.lstrip() 

103 result = bool(re.search(pattern, contact_list)) 

104 logger.debug( 

105 "Helper.validate_email() of: %s ended with result: %s", contact_list, result 

106 ) 

107 return result 

108 

109 

110def validate_identifier( 

111 logger: logging.Logger, 

112 id_type: str, 

113 identifier: str, 

114 tnauthlist_support: bool = False, 

115) -> bool: 

116 """validate identifier format""" 

117 logger.debug("Helper.validate_identifier()") 

118 

119 result = False 

120 if identifier: 

121 if id_type == "dns": 

122 result = validate_fqdn(logger, identifier) 

123 elif id_type == "ip": 

124 result = validate_ip(logger, identifier) 

125 elif id_type == "email": 

126 result = validate_email(logger, [identifier]) 

127 elif id_type == "tnauthlist" and tnauthlist_support: 

128 result = True 

129 

130 logger.debug("Helper.validate_identifier() ended with: %s", result) 

131 return result 

132 

133 

134def validate_ip(logger: logging.Logger, ip: str, raw=False) -> ipaddress._BaseAddress: 

135 """validate ip address""" 

136 logger.debug("Helper.validate_ip()") 

137 if raw: 

138 return ipaddress.ip_address(ip) 

139 result = False 

140 try: 

141 ipaddress.ip_address(ip) 

142 result = True 

143 except ValueError: 

144 result = False 

145 logger.debug("Helper.validate_ip() ended with: %s", result) 

146 return result 

147 

148 

149def validate_fqdn(logger: logging.Logger, fqdn: str) -> bool: 

150 """validate fqdn""" 

151 logger.debug("Helper.validate_fqdn()") 

152 

153 result = False 

154 regex = r"^(([a-z0-9]\-*[a-z0-9]*){1,63}\.?){1,255}$" 

155 p = re.compile(regex) 

156 if re.search(p, fqdn): 

157 result = True 

158 

159 if not result: 

160 logger.debug( 

161 "Helper.validate_fqdn(): invalid fqdn. Check for wildcard : %s", fqdn 

162 ) 

163 regex = r"^\*\.[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$" 

164 p = re.compile(regex) 

165 if re.search(p, fqdn): 

166 result = True 

167 

168 logger.debug("Helper.validate_fqdn() ended with: %s", result) 

169 return result 

170 

171 

172def validate_network(logger: logging.Logger, ip_network: str) -> ipaddress._BaseNetwork: 

173 """check if an ip address is in a list of ip networks""" 

174 logger.debug("Helper.validate_network(%s)", ip_network) 

175 return ipaddress.ip_network(ip_network, strict=False) 

176 

177 

178def ip_validate(logger: logging.Logger, ip_addr: str) -> Tuple[str, bool]: 

179 """validate ip address""" 

180 logger.debug("Helper.ip_validate(%s)", ip_addr) 

181 

182 try: 

183 reverse_pointer = ipaddress.ip_address(ip_addr).reverse_pointer 

184 invalid = False 

185 except ValueError: 

186 reverse_pointer = None 

187 invalid = True 

188 logger.debug("Helper.ip_validate() ended with: %s:%s", reverse_pointer, invalid) 

189 return (reverse_pointer, invalid) 

190 

191 

192def ipv6_chk(logger: logging.Logger, address: str) -> bool: 

193 """check if an address is ipv6""" 

194 logger.debug("Helper.ipv6_chk(%s)", address) 

195 

196 try: 

197 # we need to set a host header and braces for ipv6 headers and 

198 if isinstance(ipaddress.ip_address(address), ipaddress.IPv6Address): 

199 logger.debug("Helper.v6_adjust(}): ipv6 address detected") 

200 result = True 

201 else: 

202 result = False 

203 except Exception: 

204 result = False 

205 

206 logger.debug("Helper.ipv6_chk() ended with %s", result) 

207 return result 

208 

209 

210def cn_validate(logger: logging.Logger, cn: str) -> bool: 

211 """validate common name""" 

212 logger.debug("Helper.cn_validate(%s)", cn) 

213 

214 error = False 

215 if cn: 

216 # check if CN is a valid IP address 

217 result = validate_ip(logger, cn) 

218 if not result: 

219 # check if CN is a valid fqdn 

220 result = validate_fqdn(logger, cn) 

221 if not result: 

222 error = "Profile subject check failed: CN validation failed" 

223 else: 

224 error = "Profile subject check failed: commonName missing" 

225 

226 logger.debug("Helper.cn_validate() ended with: %s", error) 

227 return error