Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/service_identity/pyopenssl.py: 48%

44 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-07 06:38 +0000

1""" 

2`pyOpenSSL <https://github.com/pyca/pyopenssl>`_-specific code. 

3""" 

4 

5from __future__ import annotations 

6 

7import contextlib 

8import warnings 

9 

10from typing import Sequence 

11 

12from pyasn1.codec.der.decoder import decode 

13from pyasn1.type.char import IA5String 

14from pyasn1.type.univ import ObjectIdentifier 

15from pyasn1_modules.rfc2459 import GeneralNames 

16 

17from .exceptions import CertificateError 

18from .hazmat import ( 

19 DNS_ID, 

20 CertificatePattern, 

21 DNSPattern, 

22 IPAddress_ID, 

23 IPAddressPattern, 

24 SRVPattern, 

25 URIPattern, 

26 verify_service_identity, 

27) 

28 

29 

30with contextlib.suppress(ImportError): 

31 # We only use it for docstrings -- `if TYPE_CHECKING`` does not work. 

32 from OpenSSL.crypto import X509 

33 from OpenSSL.SSL import Connection 

34 

35 

36__all__ = ["verify_hostname"] 

37 

38 

39def verify_hostname(connection: Connection, hostname: str) -> None: 

40 r""" 

41 Verify whether the certificate of *connection* is valid for *hostname*. 

42 

43 Args: 

44 connection: A pyOpenSSL connection object. 

45 

46 hostname: The hostname that *connection* should be connected to. 

47 

48 Raises: 

49 service_identity.VerificationError: 

50 If *connection* does not provide a certificate that is valid for 

51 *hostname*. 

52 

53 service_identity.CertificateError: 

54 If certificate provided by *connection* contains invalid / 

55 unexpected data. This includes the case where the certificate 

56 contains no ``subjectAltName``\ s. 

57 

58 .. versionchanged:: 24.1.0 

59 :exc:`~service_identity.CertificateError` is raised if the certificate 

60 contains no ``subjectAltName``\ s instead of 

61 :exc:`~service_identity.VerificationError`. 

62 """ 

63 verify_service_identity( 

64 cert_patterns=extract_patterns( 

65 connection.get_peer_certificate() # type:ignore[arg-type] 

66 ), 

67 obligatory_ids=[DNS_ID(hostname)], 

68 optional_ids=[], 

69 ) 

70 

71 

72def verify_ip_address(connection: Connection, ip_address: str) -> None: 

73 r""" 

74 Verify whether the certificate of *connection* is valid for *ip_address*. 

75 

76 Args: 

77 connection: A pyOpenSSL connection object. 

78 

79 ip_address: 

80 The IP address that *connection* should be connected to. Can be an 

81 IPv4 or IPv6 address. 

82 

83 Raises: 

84 service_identity.VerificationError: 

85 If *connection* does not provide a certificate that is valid for 

86 *ip_address*. 

87 

88 service_identity.CertificateError: 

89 If the certificate chain of *connection* contains a certificate 

90 that contains invalid/unexpected data. 

91 

92 .. versionadded:: 18.1.0 

93 

94 .. versionchanged:: 24.1.0 

95 :exc:`~service_identity.CertificateError` is raised if the certificate 

96 contains no ``subjectAltName``\ s instead of 

97 :exc:`~service_identity.VerificationError`. 

98 """ 

99 verify_service_identity( 

100 cert_patterns=extract_patterns( 

101 connection.get_peer_certificate() # type:ignore[arg-type] 

102 ), 

103 obligatory_ids=[IPAddress_ID(ip_address)], 

104 optional_ids=[], 

105 ) 

106 

107 

108ID_ON_DNS_SRV = ObjectIdentifier("1.3.6.1.5.5.7.8.7") # id_on_dnsSRV 

109 

110 

111def extract_patterns(cert: X509) -> Sequence[CertificatePattern]: 

112 """ 

113 Extract all valid ID patterns from a certificate for service verification. 

114 

115 Args: 

116 cert: The certificate to be dissected. 

117 

118 Returns: 

119 List of IDs. 

120 

121 .. versionchanged:: 23.1.0 

122 ``commonName`` is not used as a fallback anymore. 

123 """ 

124 ids: list[CertificatePattern] = [] 

125 for i in range(cert.get_extension_count()): 

126 ext = cert.get_extension(i) 

127 if ext.get_short_name() == b"subjectAltName": 

128 names, _ = decode(ext.get_data(), asn1Spec=GeneralNames()) 

129 for n in names: 

130 name_string = n.getName() 

131 if name_string == "dNSName": 

132 ids.append( 

133 DNSPattern.from_bytes(n.getComponent().asOctets()) 

134 ) 

135 elif name_string == "iPAddress": 

136 ids.append( 

137 IPAddressPattern.from_bytes( 

138 n.getComponent().asOctets() 

139 ) 

140 ) 

141 elif name_string == "uniformResourceIdentifier": 

142 ids.append( 

143 URIPattern.from_bytes(n.getComponent().asOctets()) 

144 ) 

145 elif name_string == "otherName": 

146 comp = n.getComponent() 

147 oid = comp.getComponentByPosition(0) 

148 if oid == ID_ON_DNS_SRV: 

149 srv, _ = decode(comp.getComponentByPosition(1)) 

150 if isinstance(srv, IA5String): 

151 ids.append(SRVPattern.from_bytes(srv.asOctets())) 

152 else: # pragma: no cover 

153 raise CertificateError( 

154 "Unexpected certificate content." 

155 ) 

156 else: # pragma: no cover 

157 pass 

158 else: # pragma: no cover 

159 pass 

160 

161 return ids 

162 

163 

164def extract_ids(cert: X509) -> Sequence[CertificatePattern]: 

165 """ 

166 Deprecated and never public API. Use :func:`extract_patterns` instead. 

167 

168 .. deprecated:: 23.1.0 

169 """ 

170 warnings.warn( 

171 category=DeprecationWarning, 

172 message="`extract_ids()` is deprecated, please use `extract_patterns()`.", 

173 stacklevel=2, 

174 ) 

175 return extract_patterns(cert)