1 | """NDG Security Attribute Authority .tac file |
---|
2 | |
---|
3 | This file enables the Attribute Authority web service to be |
---|
4 | called under the Twisted framework |
---|
5 | |
---|
6 | NERC Data Grid Project |
---|
7 | """ |
---|
8 | __author__ = "P J Kershaw" |
---|
9 | __date__ = "17/11/06" |
---|
10 | __copyright__ = "(C) 2007 STFC & NERC" |
---|
11 | __license__ = \ |
---|
12 | """This software may be distributed under the terms of the Q Public |
---|
13 | License, version 1.0 or later.""" |
---|
14 | __contact__ = "P.J.Kershaw@rl.ac.uk" |
---|
15 | __revision__ = '$Id$' |
---|
16 | |
---|
17 | import os, base64 |
---|
18 | from logging.config import fileConfig |
---|
19 | try: |
---|
20 | _logConfig = os.path.join(os.environ["NDGSEC_DIR"], |
---|
21 | 'conf', |
---|
22 | 'attAuthorityLog.cfg') |
---|
23 | fileConfig(_logConfig) |
---|
24 | except KeyError: |
---|
25 | from warnings import warn |
---|
26 | warn(\ |
---|
27 | '"NDGSEC_DIR" environment variable must be set to enable logging config', |
---|
28 | RuntimeWarning) |
---|
29 | |
---|
30 | import logging |
---|
31 | log = logging.getLogger(__name__) |
---|
32 | |
---|
33 | from ZSI.twisted.WSresource import WSResource |
---|
34 | from twisted.application import service, internet |
---|
35 | from twisted.web.server import Site |
---|
36 | from twisted.web.resource import Resource |
---|
37 | |
---|
38 | from \ |
---|
39 | ndg.security.server.zsi.twisted.attributeauthority.AttAuthority_services_server\ |
---|
40 | import AttAuthorityService |
---|
41 | |
---|
42 | from ndg.security.server.attributeauthority import AttributeAuthority, \ |
---|
43 | AttributeAuthorityAccessDenied |
---|
44 | |
---|
45 | from ndg.security.common.wssecurity.dom import SignatureHandler |
---|
46 | from ndg.security.server.zsi.twisted import WSSecurityHandlerChainFactory, \ |
---|
47 | WSSecurityHandler |
---|
48 | |
---|
49 | from ndg.security.common.X509 import X509Cert, X509CertRead |
---|
50 | |
---|
51 | |
---|
52 | class AttributeAuthorityServiceSub(AttributeAuthorityService, WSResource): |
---|
53 | |
---|
54 | # Add WS-Security handlers |
---|
55 | factory = WSSecurityHandlerChainFactory |
---|
56 | |
---|
57 | def __init__(self): |
---|
58 | |
---|
59 | # Stop in debugger at beginning of SOAP stub if environment variable |
---|
60 | # is set |
---|
61 | self.__debug = bool(os.environ.get('NDGSEC_INT_DEBUG')) |
---|
62 | if self.__debug: |
---|
63 | import pdb |
---|
64 | pdb.set_trace() |
---|
65 | |
---|
66 | WSResource.__init__(self) |
---|
67 | |
---|
68 | # Initialize Attribute Authority class - property file will be |
---|
69 | # picked up from default location under $NDG_DIR directory |
---|
70 | self.aa = AttributeAuthority() |
---|
71 | |
---|
72 | |
---|
73 | def soap_getAttCert(self, ps, **kw): |
---|
74 | '''Retrieve an Attribute Certificate |
---|
75 | |
---|
76 | @type ps: ZSI ParsedSoap |
---|
77 | @param ps: client SOAP message |
---|
78 | @rtype: tuple |
---|
79 | @return: request and response objects''' |
---|
80 | if self.__debug: |
---|
81 | import pdb |
---|
82 | pdb.set_trace() |
---|
83 | |
---|
84 | request, response = AttAuthorityService.soap_getAttCert(self, ps) |
---|
85 | |
---|
86 | # Derive designated holder cert differently according to whether |
---|
87 | # a signed message is expected from the client - NB, this is dependent |
---|
88 | # on WS-Security properties having been set |
---|
89 | if srv.aa.has_key('WS-Security'): |
---|
90 | # Get certificate corresponding to private key that signed the |
---|
91 | # message - i.e. the user's proxy |
---|
92 | holderCert = WSSecurityHandler.signatureHandler.verifyingCert |
---|
93 | else: |
---|
94 | # No signature from client - they must instead provide the |
---|
95 | # designated holder cert via the UserCert input |
---|
96 | holderCert = request.UserCert |
---|
97 | |
---|
98 | try: |
---|
99 | attCert = self.aa.getAttCert(userId=request.UserId, |
---|
100 | holderCert=holderCert, |
---|
101 | userAttCert=request.UserAttCert) |
---|
102 | response.AttCert = attCert.toString() |
---|
103 | |
---|
104 | except AttributeAuthorityAccessDenied, e: |
---|
105 | response.Msg = str(e) |
---|
106 | |
---|
107 | return request, response |
---|
108 | |
---|
109 | |
---|
110 | def soap_getHostInfo(self, ps, **kw): |
---|
111 | '''Get information about this host |
---|
112 | |
---|
113 | @type ps: ZSI ParsedSoap |
---|
114 | @param ps: client SOAP message |
---|
115 | @rtype: tuple |
---|
116 | @return: request and response objects''' |
---|
117 | if self.__debug: |
---|
118 | import pdb |
---|
119 | pdb.set_trace() |
---|
120 | |
---|
121 | request, response = AttAuthorityService.soap_getHostInfo(self, ps) |
---|
122 | |
---|
123 | response.Hostname = srv.aa.hostInfo.keys()[0] |
---|
124 | response.AaURI = srv.aa.hostInfo[response.Hostname]['aaURI'] |
---|
125 | response.AaDN = srv.aa.hostInfo[response.Hostname]['aaDN'] |
---|
126 | response.LoginURI = srv.aa.hostInfo[response.Hostname]['loginURI'] |
---|
127 | response.LoginServerDN = \ |
---|
128 | srv.aa.hostInfo[response.Hostname]['loginServerDN'] |
---|
129 | response.LoginRequestServerDN = \ |
---|
130 | srv.aa.hostInfo[response.Hostname]['loginRequestServerDN'] |
---|
131 | |
---|
132 | return request, response |
---|
133 | |
---|
134 | |
---|
135 | def soap_getAllHostsInfo(self, ps, **kw): |
---|
136 | '''Get information about all hosts |
---|
137 | |
---|
138 | @type ps: ZSI ParsedSoap |
---|
139 | @param ps: client SOAP message |
---|
140 | @rtype: tuple |
---|
141 | @return: request and response objects''' |
---|
142 | if self.__debug: |
---|
143 | import pdb |
---|
144 | pdb.set_trace() |
---|
145 | |
---|
146 | request, response = AttAuthorityService.soap_getAllHostsInfo(self, ps) |
---|
147 | |
---|
148 | |
---|
149 | trustedHostInfo = srv.aa.getTrustedHostInfo() |
---|
150 | |
---|
151 | # Convert ready for serialization |
---|
152 | |
---|
153 | # First get info for THIS Attribute Authority ... |
---|
154 | # Nb. No role lsit applies here |
---|
155 | hosts = [response.new_hosts()] |
---|
156 | |
---|
157 | hosts[0].Hostname = srv.aa.hostInfo.keys()[0] |
---|
158 | |
---|
159 | hosts[0].AaURI = \ |
---|
160 | srv.aa.hostInfo[hosts[0].Hostname]['aaURI'] |
---|
161 | hosts[0].AaDN = \ |
---|
162 | srv.aa.hostInfo[hosts[0].Hostname]['aaDN'] |
---|
163 | |
---|
164 | hosts[0].LoginURI = srv.aa.hostInfo[hosts[0].Hostname]['loginURI'] |
---|
165 | hosts[0].LoginServerDN = \ |
---|
166 | srv.aa.hostInfo[hosts[0].Hostname]['loginServerDN'] |
---|
167 | hosts[0].LoginRequestServerDN = \ |
---|
168 | srv.aa.hostInfo[hosts[0].Hostname]['loginRequestServerDN'] |
---|
169 | |
---|
170 | # ... then append info for other trusted attribute authorities... |
---|
171 | for hostname, hostInfo in trustedHostInfo.items(): |
---|
172 | host = response.new_hosts() |
---|
173 | |
---|
174 | host.Hostname = hostname |
---|
175 | host.AaURI = hostInfo['aaURI'] |
---|
176 | host.AaDN = hostInfo['aaDN'] |
---|
177 | host.LoginURI = hostInfo['loginURI'] |
---|
178 | host.LoginServerDN = hostInfo['loginServerDN'] |
---|
179 | host.LoginRequestServerDN=hostInfo['loginRequestServerDN'] |
---|
180 | host.RoleList = hostInfo['role'] |
---|
181 | |
---|
182 | hosts.append(host) |
---|
183 | |
---|
184 | response.Hosts = hosts |
---|
185 | |
---|
186 | return request, response |
---|
187 | |
---|
188 | |
---|
189 | def soap_getTrustedHostInfo(self, ps, **kw): |
---|
190 | '''Get information about other trusted hosts |
---|
191 | |
---|
192 | @type ps: ZSI ParsedSoap |
---|
193 | @param ps: client SOAP message |
---|
194 | @rtype: tuple |
---|
195 | @return: request and response objects''' |
---|
196 | if self.__debug: |
---|
197 | import pdb |
---|
198 | pdb.set_trace() |
---|
199 | |
---|
200 | request, response = \ |
---|
201 | AttAuthorityService.soap_getTrustedHostInfo(self, ps) |
---|
202 | |
---|
203 | trustedHostInfo = srv.aa.getTrustedHostInfo(role=request.Role) |
---|
204 | |
---|
205 | # Convert ready for serialization |
---|
206 | trustedHosts = [] |
---|
207 | for hostname, hostInfo in trustedHostInfo.items(): |
---|
208 | trustedHost = response.new_trustedHosts() |
---|
209 | |
---|
210 | trustedHost.Hostname = hostname |
---|
211 | trustedHost.AaURI = hostInfo['aaURI'] |
---|
212 | trustedHost.AaDN = hostInfo['aaDN'] |
---|
213 | trustedHost.LoginURI = hostInfo['loginURI'] |
---|
214 | trustedHost.LoginServerDN = hostInfo['loginServerDN'] |
---|
215 | trustedHost.LoginRequestServerDN=hostInfo['loginRequestServerDN'] |
---|
216 | trustedHost.RoleList = hostInfo['role'] |
---|
217 | |
---|
218 | trustedHosts.append(trustedHost) |
---|
219 | |
---|
220 | response.TrustedHosts = trustedHosts |
---|
221 | |
---|
222 | return request, response |
---|
223 | |
---|
224 | |
---|
225 | def soap_getX509Cert(self, ps, **kw): |
---|
226 | '''Retrieve Attribute Authority's X.509 certificate |
---|
227 | |
---|
228 | @type ps: ZSI ParsedSoap |
---|
229 | @param ps: client SOAP message |
---|
230 | @rtype: tuple |
---|
231 | @return: request and response objects''' |
---|
232 | if self.__debug: |
---|
233 | import pdb |
---|
234 | pdb.set_trace() |
---|
235 | |
---|
236 | request, response = AttAuthorityService.soap_getX509Cert(self, ps) |
---|
237 | |
---|
238 | x509Cert = X509CertRead(srv.aa['signingCertFilePath']) |
---|
239 | response.X509Cert = base64.encodestring(x509Cert.asDER()) |
---|
240 | return request, response |
---|
241 | |
---|
242 | |
---|
243 | root = Resource() |
---|
244 | |
---|
245 | # Create Service |
---|
246 | srv = AttributeAuthorityServiceSub() |
---|
247 | if srv.aa.has_key('WS-Security'): |
---|
248 | # Initialise WS-Security signature handler passing Attribute Authority |
---|
249 | # public and private keys |
---|
250 | |
---|
251 | # Inclusive namespaces for Exclusive C14N |
---|
252 | refC14nInclNS = srv.aa['refC14nInclNS'] |
---|
253 | signedInfoC14nInclNS = srv.aa['signedInfoC14nInclNS'] |
---|
254 | |
---|
255 | WSSecurityHandler.signatureHandler = SignatureHandler(\ |
---|
256 | verifyingCertFilePath=srv.aa['verifyingCertFilePath'], |
---|
257 | signingCertFilePath=srv.aa['signingCertFilePath'], |
---|
258 | signingPriKeyFilePath=srv.aa['signingPriKeyFilePath'], |
---|
259 | signingPriKeyPwd=srv.aa['signingPriKeyPwd'], |
---|
260 | caCertFilePathList=srv.aa.get('caCertFilePathList'), |
---|
261 | refC14nInclNS=refC14nInclNS, |
---|
262 | signedInfoC14nInclNS=signedInfoC14nInclNS, |
---|
263 | reqBinSecTokValType=srv.aa.get('reqBinSecTokValType'), |
---|
264 | applySignatureConfirmation=srv.aa.get('applySignatureConfirmation')) |
---|
265 | |
---|
266 | # Add Service to Attribute Authority branch |
---|
267 | root.putChild('AttributeAuthority', srv) |
---|
268 | siteFactory = Site(root) |
---|
269 | if srv.aa['useSSL']: |
---|
270 | log.info("Running over https ...") |
---|
271 | |
---|
272 | os.putenv("OPENSSL_ALLOW_PROXY_CERTS", "1") |
---|
273 | |
---|
274 | import twisted.protocols.policies as policies |
---|
275 | |
---|
276 | # Using M2Crypto |
---|
277 | from M2Crypto import SSL |
---|
278 | from M2Crypto.SSL import TwistedProtocolWrapper |
---|
279 | from M2Crypto.SSL.TwistedProtocolWrapper import TLSProtocolWrapper |
---|
280 | |
---|
281 | siteFactory.startTLS = True |
---|
282 | siteFactory.sslChecker = SSL.Checker.Checker() |
---|
283 | |
---|
284 | # TODO: Python ssl client seems to require SSL vers 2 is this a security |
---|
285 | # risk? |
---|
286 | ctx = SSL.Context(protocol='sslv23') |
---|
287 | ctx.set_cipher_list("NULL-MD5:ALL:!ADH:!EXP:@STRENGTH") |
---|
288 | ctx.load_cert(srv.aa['sslCertFile'], |
---|
289 | srv.aa['sslKeyFile'], |
---|
290 | callback=lambda *args, **kw: srv.aa['sslKeyPwd']) |
---|
291 | |
---|
292 | ctx.set_allow_unknown_ca(False) |
---|
293 | |
---|
294 | # TODO: resolve check - verify_peer setting fails with |
---|
295 | # BIOError: 'no certificate returned' error 18 |
---|
296 | # ctx.set_verify(SSL.verify_peer, 10) |
---|
297 | ctx.set_verify(SSL.verify_client_once, 1) |
---|
298 | |
---|
299 | ctx.load_verify_locations(capath=srv.aa['sslCACertDir']) |
---|
300 | |
---|
301 | class ContextFactory: |
---|
302 | def getContext(self): |
---|
303 | return ctx |
---|
304 | |
---|
305 | factory = policies.WrappingFactory(siteFactory) |
---|
306 | factory.protocol.TLS = True |
---|
307 | factory.protocol = lambda factory, wrappedProtocol: \ |
---|
308 | TLSProtocolWrapper(factory, |
---|
309 | wrappedProtocol, |
---|
310 | startPassThrough=0, |
---|
311 | client=0, |
---|
312 | contextFactory=ContextFactory(), |
---|
313 | postConnectionCheck=None) |
---|
314 | |
---|
315 | siteFactory = factory |
---|
316 | |
---|
317 | port = internet.TCPServer(srv.aa['portNum'], siteFactory) |
---|
318 | port.CERTFILE = srv.aa['sslCertFile'] |
---|
319 | port.KEYFILE = srv.aa['sslKeyFile'] |
---|
320 | root.__class__.server = port |
---|
321 | else: |
---|
322 | # Non-SSL |
---|
323 | log.info("Running over http ...") |
---|
324 | port = internet.TCPServer(srv.aa['portNum'], siteFactory) |
---|
325 | |
---|
326 | application = service.Application("AttributeAuthorityContainer") |
---|
327 | port.setServiceParent(application) |
---|
328 | |
---|