Advertisement
Guest User

Untitled

a guest
Sep 21st, 2017
111
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 64.59 KB | None | 0 0
  1. # Copyright (C) 2008, 2009, 2010 Red Hat, Inc. All rights reserved.
  2.  
  3. #
  4.  
  5. # This copyrighted material is made available to anyone wishing to use, modify,
  6.  
  7. # copy, or redistribute it subject to the terms and conditions of the GNU
  8.  
  9. # General Public License v.2. This program is distributed in the hope that it
  10.  
  11. # will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
  12.  
  13. # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  14.  
  15. # See the GNU General Public License for more details. You should have
  16.  
  17. # received a copy of the GNU General Public License along with this program; if
  18.  
  19. # not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
  20.  
  21. # Floor, Boston, MA 02110-1301, USA. Any Red Hat trademarks that are
  22.  
  23. # incorporated in the source code or documentation are not subject to the GNU
  24.  
  25. # General Public License and may only be used or replicated with the express
  26.  
  27. # permission of Red Hat, Inc.
  28.  
  29. #
  30.  
  31. # Red Hat Author: Miloslav Trmac <mitr@redhat.com>
  32.  
  33.  
  34.  
  35. import Queue
  36.  
  37. import binascii
  38.  
  39. import cStringIO
  40.  
  41. import crypt
  42.  
  43. import logging
  44.  
  45. import os
  46.  
  47. import shutil
  48.  
  49. import signal
  50.  
  51. import socket
  52.  
  53. import string
  54.  
  55. import struct
  56.  
  57. import subprocess
  58.  
  59. import sys
  60.  
  61. import tempfile
  62.  
  63. import time
  64.  
  65.  
  66.  
  67. import gpgme
  68.  
  69. import nss.error
  70.  
  71. import nss.nss
  72.  
  73. import pexpect
  74.  
  75.  
  76.  
  77. import double_tls
  78.  
  79. import errors
  80.  
  81. import server_common
  82.  
  83. import settings
  84.  
  85. import utils
  86.  
  87.  
  88.  
  89. # When trying to connect to the bridge, don't repeat the connections way too
  90.  
  91. # often. Try MAX_FAST_RECONNECTIONS attempts FAST_RECONNECTION_SECONDS apart,
  92.  
  93. # then wait SLOW_RECONNECTION_SECONDS. Then try again
  94.  
  95. MAX_FAST_RECONNECTIONS = 5
  96.  
  97. FAST_RECONNECTION_SECONDS = 5
  98.  
  99. SLOW_RECONNECTION_SECONDS = 60
  100.  
  101.  
  102.  
  103. # Infrastructure
  104.  
  105.  
  106.  
  107. Key = server_common.Key
  108.  
  109. KeyAccess = server_common.KeyAccess
  110.  
  111. User = server_common.User
  112.  
  113.  
  114.  
  115. class ServerConfiguration(server_common.GPGConfiguration,
  116.  
  117. server_common.ServerBaseConfiguration):
  118.  
  119.  
  120.  
  121. def _add_defaults(self, defaults):
  122.  
  123. super(ServerConfiguration, self)._add_defaults(defaults)
  124.  
  125. defaults.update({'bridge-port': 44333,
  126.  
  127. 'gnupg-key-type': 'DSA',
  128.  
  129. 'gnupg-key-length': 1024,
  130.  
  131. 'gnupg-subkey-type': 'ELG-E',
  132.  
  133. 'gnupg-subkey-length': 2048,
  134.  
  135. 'gnupg-key-usage': 'sign',
  136.  
  137. 'max-file-payload-size': 1024 * 1024 * 1024,
  138.  
  139. 'max-memory-payload-size': 1024 * 1024,
  140.  
  141. 'max-rpms-payloads-size': 10 * 1024 * 1024 * 1024,
  142.  
  143. 'passphrase-length': 64,
  144.  
  145. 'server-cert-nickname': 'sigul-server-cert',
  146.  
  147. 'signing-timeout': 60})
  148.  
  149.  
  150.  
  151. def _add_sections(self, sections):
  152.  
  153. super(ServerConfiguration, self)._add_sections(sections)
  154.  
  155. sections.update(('gnupg','server'))
  156.  
  157.  
  158.  
  159. def _read_configuration(self, parser):
  160.  
  161. super(ServerConfiguration, self)._read_configuration(parser)
  162.  
  163. self.gnupg_key_type = parser.get('gnupg', 'gnupg-key-type')
  164.  
  165. self.gnupg_key_length = parser.getint('gnupg', 'gnupg-key-length')
  166.  
  167. self.gnupg_subkey_type = parser.get('gnupg', 'gnupg-subkey-type')
  168.  
  169. if self.gnupg_subkey_type == '':
  170.  
  171. self.gnupg_subkey_type = None
  172.  
  173. else:
  174.  
  175. self.gnupg_subkey_length = parser.getint('gnupg',
  176.  
  177. 'gnupg-subkey-length')
  178.  
  179. self.gnupg_key_usage = parser.get('gnupg', 'gnupg-key-usage')
  180.  
  181. self.passphrase_length = parser.getint('gnupg', 'passphrase-length')
  182.  
  183. self.bridge_hostname = parser.get('server', 'bridge-hostname')
  184.  
  185. self.bridge_port = parser.getint('server', 'bridge-port')
  186.  
  187. self.max_file_payload_size = parser.getint('server',
  188.  
  189. 'max-file-payload-size')
  190.  
  191. self.max_memory_payload_size = parser.getint('server',
  192.  
  193. 'max-memory-payload-size')
  194.  
  195. self.max_rpms_payloads_size = parser.getint('server',
  196.  
  197. 'max-rpms-payloads-size')
  198.  
  199. self.server_cert_nickname = parser.get('server', 'server-cert-nickname')
  200.  
  201. self.signing_timeout = parser.getint('server', 'signing-timeout')
  202.  
  203.  
  204.  
  205. class RequestHandled(Exception):
  206.  
  207. '''Used to terminate further processing of the request.'''
  208.  
  209. pass
  210.  
  211.  
  212.  
  213. class InvalidRequestError(Exception):
  214.  
  215. pass
  216.  
  217.  
  218.  
  219. class RequestHandler(object):
  220.  
  221. '''Information about a request type and its handler.'''
  222.  
  223.  
  224.  
  225. PAYLOAD_NONE = 0
  226.  
  227. PAYLOAD_MEMORY = 1
  228.  
  229. PAYLOAD_FILE = 2
  230.  
  231.  
  232.  
  233. def __init__(self, handler, payload_storage=PAYLOAD_NONE,
  234.  
  235. payload_auth_optional=False):
  236.  
  237. self.handler = handler
  238.  
  239. self.payload_storage = payload_storage
  240.  
  241. self.payload_auth_optional = payload_auth_optional
  242.  
  243.  
  244.  
  245. # op value => (handler, expected payload type)
  246.  
  247. # Each handler raises RequestHandled, InvalidRequestError. op value None means
  248.  
  249. # the default handler
  250.  
  251. request_handlers = {}
  252.  
  253.  
  254.  
  255. def request_handler(**kwargs):
  256.  
  257. '''Register this function as a request handler, using kwargs.
  258.  
  259.  
  260.  
  261. Function name must be cmd_request_name_with_dash_replaced_by_underscore,
  262.  
  263. e.g. cmd_list_users for 'list-users'. This decorator must be used with
  264.  
  265. (possibly zero) parameters.
  266.  
  267.  
  268.  
  269. '''
  270.  
  271. def real_decorator(fn):
  272.  
  273. assert fn.__name__.startswith('cmd_')
  274.  
  275. request_handlers[fn.__name__[len('cmd_'):].replace('_', '-')] = \
  276.  
  277. RequestHandler(fn, **kwargs)
  278.  
  279. return fn
  280.  
  281. return real_decorator
  282.  
  283.  
  284.  
  285. class ServerProxy(object):
  286.  
  287. '''A proxy for double_tls.DoubleTLSClient that stores read outer data.'''
  288.  
  289.  
  290.  
  291. def __init__(self, server):
  292.  
  293. self.__server = server
  294.  
  295. self.__stored = ''
  296.  
  297.  
  298.  
  299. def stored_outer_read(self, bytes):
  300.  
  301. data = self.__server.outer_read(bytes)
  302.  
  303. self.__stored += data
  304.  
  305. return data
  306.  
  307.  
  308.  
  309. def stored_data(self):
  310.  
  311. '''Return the currently stored data.
  312.  
  313.  
  314.  
  315. Each piece of data is returned only once in repeated calls to this
  316.  
  317. method.
  318.  
  319.  
  320.  
  321. '''
  322.  
  323. res = self.__stored
  324.  
  325. self.__stored = ''
  326.  
  327. return res
  328.  
  329.  
  330.  
  331. class ServersConnection(object):
  332.  
  333. '''A connection to the bridge/client.'''
  334.  
  335.  
  336.  
  337. def __init__(self, config):
  338.  
  339. self.config = config
  340.  
  341. self.__client = double_tls.DoubleTLSClient(config,
  342.  
  343. config.bridge_hostname,
  344.  
  345. config.bridge_port,
  346.  
  347. config.server_cert_nickname)
  348.  
  349. self.payload_path = None
  350.  
  351. self.payload_file = None
  352.  
  353. utils.nss_init(config) # May raise utils.NSSInitError
  354.  
  355.  
  356.  
  357. def outer_field(self, key, required=False):
  358.  
  359. '''Return an outer field value, or None if not present.
  360.  
  361.  
  362.  
  363. Raise InvalidRequestError if field is not present and required == True.
  364.  
  365.  
  366.  
  367. '''
  368.  
  369. v = self.__outer_fields.get(key)
  370.  
  371. if required and v is None:
  372.  
  373. raise InvalidRequestError('Required outer field %s missing' % key)
  374.  
  375. return v
  376.  
  377.  
  378.  
  379. def safe_outer_field(self, key, **kwargs):
  380.  
  381. '''Return an outer field value, or None if not present.
  382.  
  383.  
  384.  
  385. Raise InvalidRequestError if field is not a safe string.
  386.  
  387.  
  388.  
  389. '''
  390.  
  391. v = self.outer_field(key, **kwargs)
  392.  
  393. if v is not None and not utils.string_is_safe(v):
  394.  
  395. raise InvalidRequestError('Field %s has unsafe value' % repr(key))
  396.  
  397. return v
  398.  
  399.  
  400.  
  401. def outer_field_bool(self, key):
  402.  
  403. '''Return outer field value as a bool or None if not present.
  404.  
  405.  
  406.  
  407. Raise InvalidRequestError.
  408.  
  409.  
  410.  
  411. '''
  412.  
  413. v = self.__outer_fields.get(key)
  414.  
  415. if v is not None:
  416.  
  417. try:
  418.  
  419. v = utils.u32_unpack(v)
  420.  
  421. except struct.error:
  422.  
  423. raise InvalidRequestError('Integer field has incorrect length')
  424.  
  425. try:
  426.  
  427. v = { 0: False, 1: True }[v]
  428.  
  429. except KeyError:
  430.  
  431. raise InvalidRequestError('Boolean field has invalid value')
  432.  
  433. return v
  434.  
  435.  
  436.  
  437. def inner_field(self, key, required=False):
  438.  
  439. '''Return an inner field value, or None if not present.
  440.  
  441.  
  442.  
  443. Raise InvalidRequestError if fiels is not present and required == True.
  444.  
  445.  
  446.  
  447. '''
  448.  
  449. v = self.__inner_fields.get(key)
  450.  
  451. if required and v is None:
  452.  
  453. raise InvalidRequestError('Required inner field %s missing.' % key)
  454.  
  455. return v
  456.  
  457.  
  458.  
  459. def read_request(self):
  460.  
  461. '''Read a request.
  462.  
  463.  
  464.  
  465. Return request handler. Raise RequestHandled, InvalidRequestError,
  466.  
  467. double_tls.InnerCertificateNotFound.
  468.  
  469.  
  470.  
  471. '''
  472.  
  473. proxy = ServerProxy(self.__client)
  474.  
  475. buf = proxy.stored_outer_read(utils.u32_size)
  476.  
  477. logging.debug('Started processing a request')
  478.  
  479. client_version = utils.u32_unpack(buf)
  480.  
  481. if client_version != utils.protocol_version:
  482.  
  483. logging.warning('Unknown protocol version %d in request',
  484.  
  485. client_version)
  486.  
  487. self.__client.inner_close()
  488.  
  489. self.__client.outer_write(utils.u32_pack(errors.UNKNOWN_VERSION))
  490.  
  491. raise RequestHandled()
  492.  
  493. try:
  494.  
  495. self.__outer_fields = utils.read_fields(proxy.stored_outer_read)
  496.  
  497. except utils.InvalidFieldsError, e:
  498.  
  499. raise InvalidRequestError(str(e))
  500.  
  501. logging.info('Request: %s', utils.readable_fields(self.__outer_fields))
  502.  
  503. header_data = proxy.stored_data()
  504.  
  505. buf = self.__client.outer_read(utils.u32_size)
  506.  
  507. payload_size = utils.u32_unpack(buf)
  508.  
  509.  
  510.  
  511. request_op = self.safe_outer_field('op', required=True)
  512.  
  513. if request_op not in request_handlers:
  514.  
  515. request_op = None
  516.  
  517. handler = request_handlers[request_op]
  518.  
  519.  
  520.  
  521. reader = utils.SHA512Reader(self.__client.outer_read)
  522.  
  523. if handler.payload_storage == RequestHandler.PAYLOAD_NONE:
  524.  
  525. if payload_size != 0:
  526.  
  527. raise InvalidRequestError('Unexpected payload')
  528.  
  529. elif handler.payload_storage == RequestHandler.PAYLOAD_MEMORY:
  530.  
  531. if payload_size > self.config.max_memory_payload_size:
  532.  
  533. raise InvalidRequestError('Payload too large')
  534.  
  535. f = cStringIO.StringIO()
  536.  
  537. utils.copy_data(f.write, reader.read, payload_size)
  538.  
  539. self.__payload = f.getvalue()
  540.  
  541. else:
  542.  
  543. assert handler.payload_storage == RequestHandler.PAYLOAD_FILE
  544.  
  545. if payload_size > self.config.max_file_payload_size:
  546.  
  547. raise InvalidRequestError('Payload too large')
  548.  
  549. (fd, self.payload_path) = tempfile.mkstemp(text=False)
  550.  
  551. f = os.fdopen(fd, 'w+b')
  552.  
  553. try:
  554.  
  555. utils.copy_data(f.write, reader.read, payload_size)
  556.  
  557. finally:
  558.  
  559. f.close()
  560.  
  561. self.payload_file = open(self.payload_path, 'rb')
  562.  
  563. self.payload_sha512_digest = reader.sha512()
  564.  
  565.  
  566.  
  567. # FIXME? authenticate using the client's certificate as well?
  568.  
  569. # May raise double_tls.InnerCertificateNotFound.
  570.  
  571. self.__client.inner_open_server(self.config.server_cert_nickname)
  572.  
  573. try:
  574.  
  575. try:
  576.  
  577. self.__inner_fields = utils.read_fields(self.__client.
  578.  
  579. inner_read)
  580.  
  581. except utils.InvalidFieldsError, e:
  582.  
  583. raise InvalidRequestError(str(e))
  584.  
  585. finally:
  586.  
  587. self.__client.inner_close()
  588.  
  589. # print repr(self.__inner_fields)
  590.  
  591. if (self.inner_field('header-auth-sha512', required=True) !=
  592.  
  593. nss.nss.sha512_digest(header_data)):
  594.  
  595. raise InvalidRequestError('Header authentication failed')
  596.  
  597. payload_auth = self.inner_field('payload-auth-sha512')
  598.  
  599. if payload_auth is None:
  600.  
  601. if not handler.payload_auth_optional:
  602.  
  603. raise InvalidRequestError('Authentication hash missing')
  604.  
  605. else:
  606.  
  607. if payload_auth != self.payload_sha512_digest:
  608.  
  609. raise InvalidRequestError('Payload authentication failed')
  610.  
  611. self.payload_authenticated = payload_auth is not None
  612.  
  613.  
  614.  
  615. mech = nss.nss.CKM_SHA512_HMAC
  616.  
  617. slot = nss.nss.get_best_slot(mech)
  618.  
  619. buf = self.inner_field('header-auth-key', required=True)
  620.  
  621. if len(buf) < 64:
  622.  
  623. raise InvalidRequestError('Header authentication key too small')
  624.  
  625. # "Unwrap" because the key was encrypted for transmission using TLS
  626.  
  627. nss_key = nss.nss.import_sym_key(slot, mech, nss.nss.PK11_OriginUnwrap,
  628.  
  629. nss.nss.CKA_SIGN, nss.nss.SecItem(buf))
  630.  
  631. self.__reply_header_writer = \
  632.  
  633. utils.SHA512HMACWriter(self.__client.outer_write, nss_key)
  634.  
  635. buf = self.inner_field('payload-auth-key', required=True)
  636.  
  637. if len(buf) < 64:
  638.  
  639. raise InvalidRequestError('Payload authentication key too small')
  640.  
  641. nss_key = nss.nss.import_sym_key(slot, mech, nss.nss.PK11_OriginUnwrap,
  642.  
  643. nss.nss.CKA_SIGN, nss.nss.SecItem(buf))
  644.  
  645. self.__reply_payload_writer = \
  646.  
  647. utils.SHA512HMACWriter(self.__client.outer_write, nss_key)
  648.  
  649. return handler
  650.  
  651.  
  652.  
  653. def send_reply_header(self, error_code, fields):
  654.  
  655. '''Send a reply header to the client.'''
  656.  
  657. self.__reply_header_writer.write(utils.u32_pack(error_code))
  658.  
  659. self.__reply_header_writer.write(utils.format_fields(fields))
  660.  
  661. self.__reply_header_writer.write_64B_hmac()
  662.  
  663.  
  664.  
  665. def __send_payload_size(self, payload_size):
  666.  
  667. '''Prepare for sending payload of payload_size to the client.
  668.  
  669.  
  670.  
  671. Valid both for the primary payload and for subreply payloads.
  672.  
  673.  
  674.  
  675. '''
  676.  
  677. self.__client.outer_write(utils.u32_pack(payload_size))
  678.  
  679.  
  680.  
  681. def __send_payload_from_file(self, writer, fd):
  682.  
  683. '''Send contents of fd to the client as payload, using writer.
  684.  
  685.  
  686.  
  687. Valid both for the primary payload and for subreply payloads.
  688.  
  689.  
  690.  
  691. '''
  692.  
  693. fd.seek(0)
  694.  
  695. file_size = os.fstat(fd.fileno()).st_size
  696.  
  697. self.__send_payload_size(file_size)
  698.  
  699.  
  700.  
  701. sent = 0
  702.  
  703. while True:
  704.  
  705. data = fd.read(4096)
  706.  
  707. if len(data) == 0:
  708.  
  709. break
  710.  
  711. writer.write(data)
  712.  
  713. sent += len(data)
  714.  
  715. if sent != file_size:
  716.  
  717. raise IOError('File size did not match size returned by fstat()')
  718.  
  719. writer.write_64B_hmac()
  720.  
  721.  
  722.  
  723. def send_reply_payload(self, payload):
  724.  
  725. '''Send payload to the client.'''
  726.  
  727. self.__send_payload_size(len(payload))
  728.  
  729. self.__reply_payload_writer.write(payload)
  730.  
  731. self.__reply_payload_writer.write_64B_hmac()
  732.  
  733.  
  734.  
  735. def send_reply_payload_from_file(self, fd):
  736.  
  737. '''Send contents of fd to the client as payload.'''
  738.  
  739. self.__send_payload_from_file(self.__reply_payload_writer, fd)
  740.  
  741.  
  742.  
  743. def send_reply_ok_only(self):
  744.  
  745. '''Send an erorrs.OK reply with no fields or payload.'''
  746.  
  747. self.send_reply_header(errors.OK, {})
  748.  
  749. self.send_reply_payload('')
  750.  
  751.  
  752.  
  753. def send_error(self, error_code, message=None, log_it=True):
  754.  
  755. '''Send an erorr response with code and message.
  756.  
  757.  
  758.  
  759. Raise RequestHandled at the end.
  760.  
  761.  
  762.  
  763. '''
  764.  
  765. if message is not None:
  766.  
  767. f = {'message': message}
  768.  
  769. if log_it:
  770.  
  771. logging.info('Request error: %s, %s',
  772.  
  773. errors.message(error_code), message)
  774.  
  775. else:
  776.  
  777. f = {}
  778.  
  779. if log_it:
  780.  
  781. logging.info('Request error: %s', errors.message(error_code))
  782.  
  783. self.send_reply_header(error_code, f)
  784.  
  785. self.send_reply_payload('')
  786.  
  787. raise RequestHandled()
  788.  
  789.  
  790.  
  791. def read_subheader(self, nss_key):
  792.  
  793. '''Read fields in a subrequest header authenticated using nss_key.
  794.  
  795.  
  796.  
  797. Return the header.
  798.  
  799.  
  800.  
  801. '''
  802.  
  803. reader = utils.SHA512HMACReader(self.__client.outer_read, nss_key)
  804.  
  805. try:
  806.  
  807. fields = utils.read_fields(reader.read)
  808.  
  809. except utils.InvalidFieldsError, e:
  810.  
  811. raise InvalidRequestError('Invalid response format: %s' % str(e))
  812.  
  813. if not reader.verify_64B_hmac_authenticator():
  814.  
  815. raise InvalidRequestError('Subrequest header authentication failed')
  816.  
  817. return fields
  818.  
  819.  
  820.  
  821. def read_subpayload_to_file(self, nss_key, max_size, tmp_dir):
  822.  
  823. '''Read a subpayload authenticated using nss_key.
  824.  
  825.  
  826.  
  827. Return (path, file, payload digest, payload authenticated). Limit file
  828.  
  829. size to max_size. Create the temporary file in tmp_dir.
  830.  
  831.  
  832.  
  833. '''
  834.  
  835. buf = self.__client.outer_read(utils.u32_size)
  836.  
  837. payload_size = utils.u32_unpack(buf)
  838.  
  839. if payload_size > self.config.max_file_payload_size:
  840.  
  841. raise InvalidRequestError('Payload too large')
  842.  
  843. if payload_size > max_size:
  844.  
  845. raise InvalidRequestError('Total payload size too large')
  846.  
  847.  
  848.  
  849. reader = utils.SHA512HashAndHMACReader(self.__client.outer_read,
  850.  
  851. nss_key)
  852.  
  853. (fd, payload_path) = tempfile.mkstemp(text=False, dir=tmp_dir)
  854.  
  855. f = os.fdopen(fd, 'w+b')
  856.  
  857. try:
  858.  
  859. utils.copy_data(f.write, reader.read, payload_size)
  860.  
  861. finally:
  862.  
  863. f.close()
  864.  
  865. payload_file = open(payload_path, 'rb')
  866.  
  867. payload_sha512_digest = reader.sha512()
  868.  
  869. auth = self.__client.outer_read(64)
  870.  
  871. return (payload_path, payload_file, payload_sha512_digest,
  872.  
  873. auth == reader.hmac())
  874.  
  875.  
  876.  
  877. def send_subheader(self, fields, nss_key):
  878.  
  879. '''Send fields in a subreply header authenticated using nss_key.'''
  880.  
  881. writer = utils.SHA512HMACWriter(self.__client.outer_write, nss_key)
  882.  
  883. writer.write(utils.format_fields(fields))
  884.  
  885. writer.write_64B_hmac()
  886.  
  887.  
  888.  
  889. def send_empty_subpayload(self, nss_key):
  890.  
  891. '''Send an empty subreply payload authenticated using nss_key.'''
  892.  
  893. self.__send_payload_size(0)
  894.  
  895. writer = utils.SHA512HMACWriter(self.__client.outer_write, nss_key)
  896.  
  897. writer.write_64B_hmac()
  898.  
  899.  
  900.  
  901. def send_subpayload_from_file(self, fd, nss_key):
  902.  
  903. '''Send a subreply payload from fd authenticated using nss_key.'''
  904.  
  905. writer = utils.SHA512HMACWriter(self.__client.outer_write, nss_key)
  906.  
  907. self.__send_payload_from_file(writer, fd)
  908.  
  909.  
  910.  
  911. def close(self):
  912.  
  913. '''Destroy non-garbage-collected state.
  914.  
  915.  
  916.  
  917. Raise double_tls.ChildConnectionRefusedError,
  918.  
  919. double_tls.ChildUnrecoverableError.
  920.  
  921.  
  922.  
  923. '''
  924.  
  925. if self.payload_file is not None:
  926.  
  927. self.payload_file.close()
  928.  
  929. if self.payload_path is not None:
  930.  
  931. os.remove(self.payload_path)
  932.  
  933. # May raise double_tls.ChildConnectionRefusedError,
  934.  
  935. # double_tls.ChildUnrecoverableError.
  936.  
  937. self.__client.outer_close()
  938.  
  939.  
  940.  
  941. def auth_fail(self, reason):
  942.  
  943. '''Report an authentication failure.
  944.  
  945.  
  946.  
  947. Raise RequestHandled.
  948.  
  949.  
  950.  
  951. '''
  952.  
  953. logging.warning('Request authentication failed: %s', reason)
  954.  
  955. self.send_error(errors.AUTHENTICATION_FAILED, log_it=False)
  956.  
  957.  
  958.  
  959. def authenticate_admin(self, db):
  960.  
  961. '''Check the request is a valid administration request.
  962.  
  963.  
  964.  
  965. Raise RequestHandled (on permission denied), InvalidRequestError.
  966.  
  967. '''
  968.  
  969.  
  970.  
  971.  
  972.  
  973. user = self.safe_outer_field('user')
  974.  
  975. if user is None:
  976.  
  977. self.auth_fail('user field missing')
  978.  
  979. password = self.inner_field('password')
  980.  
  981. if password is None:
  982.  
  983. self.auth_fail('password field missing')
  984.  
  985. user = db.query(User).filter_by(name=user).first()
  986.  
  987. if user is not None and user.sha512_password is not None:
  988.  
  989. crypted_pw = str(user.sha512_password)
  990.  
  991. else:
  992.  
  993. # Perform the encryption anyway to make timing attacks more
  994.  
  995. # difficult.
  996.  
  997. crypted_pw = 'x'
  998.  
  999. if crypt.crypt(password, crypted_pw) != crypted_pw:
  1000.  
  1001. self.auth_fail('password does not match')
  1002.  
  1003. if not user.admin:
  1004.  
  1005. self.auth_fail('user is not a server administrator')
  1006.  
  1007. # OK
  1008.  
  1009.  
  1010.  
  1011. def __authenticate_admin_or_user(self, db):
  1012.  
  1013. '''Check the request is a valid key access request.
  1014.  
  1015.  
  1016.  
  1017. Allow server administrators to authenticate without having a key
  1018.  
  1019. passphrase. Return (user, key, access), with access None if a server
  1020.  
  1021. administrator was authenticated. Raise RequestHandled (on permission
  1022.  
  1023. denied), InvalidRequestError.
  1024.  
  1025.  
  1026.  
  1027. '''
  1028.  
  1029. user = self.safe_outer_field('user')
  1030.  
  1031. if user is None:
  1032.  
  1033. self.auth_fail('user field missing')
  1034.  
  1035. key = self.safe_outer_field('key')
  1036.  
  1037. if key is None:
  1038.  
  1039. self.auth_fail('key field missing')
  1040.  
  1041. password = self.inner_field('password')
  1042.  
  1043. user_passphrase = self.inner_field('passphrase')
  1044.  
  1045. if password is None and user_passphrase is None:
  1046.  
  1047. self.auth_fail('both password and passphrase fields missing')
  1048.  
  1049. user = db.query(User).filter_by(name=user).first()
  1050.  
  1051. key = db.query(Key).filter_by(name=key).first()
  1052.  
  1053. access = None
  1054.  
  1055. if password is not None:
  1056.  
  1057. if user is not None and user.sha512_password is not None:
  1058.  
  1059. crypted_pw = str(user.sha512_password)
  1060.  
  1061. else:
  1062.  
  1063. # Perform the encryption anyway to make timing attacks more
  1064.  
  1065. # difficult.
  1066.  
  1067. crypted_pw = 'x'
  1068.  
  1069. if crypt.crypt(password, crypted_pw) != crypted_pw:
  1070.  
  1071. self.auth_fail('password does not match')
  1072.  
  1073. assert user is not None
  1074.  
  1075. if not user.admin or key is None:
  1076.  
  1077. self.auth_fail('user is not a server administrator')
  1078.  
  1079. else:
  1080.  
  1081. assert user_passphrase is not None
  1082.  
  1083. encrypted_passphrase = None
  1084.  
  1085. if user is not None and key is not None:
  1086.  
  1087. access = (db.query(KeyAccess).filter_by(user=user, key=key).
  1088.  
  1089. first())
  1090.  
  1091. if access is not None:
  1092.  
  1093. encrypted_passphrase = access.encrypted_passphrase
  1094.  
  1095. if encrypted_passphrase is None:
  1096.  
  1097. # Perform a decryption attempt anyway to make timing attacks
  1098.  
  1099. # more difficult. gpg will probably choke on the attempt
  1100.  
  1101. # quickly enough, too bad.
  1102.  
  1103. encrypted_passphrase = 'x'
  1104.  
  1105. try:
  1106.  
  1107. server_common.gpg_decrypt(self.config, encrypted_passphrase,
  1108.  
  1109. user_passphrase)
  1110.  
  1111. except gpgme.GpgmeError:
  1112.  
  1113. self.auth_fail('passphrase does not match')
  1114.  
  1115. assert user is not None and key is not None and access is not None
  1116.  
  1117. return (user, key, access) # OK
  1118.  
  1119.  
  1120.  
  1121. def authenticate_admin_or_user(self, db):
  1122.  
  1123. '''Check the request is a valid key access request.
  1124.  
  1125.  
  1126.  
  1127. Allow server administrators to authenticate without having a key
  1128.  
  1129. passphrase. Return (user, key). Raise RequestHandled (on permission
  1130.  
  1131. denied), InvalidRequestError.
  1132.  
  1133.  
  1134.  
  1135. '''
  1136.  
  1137. (user, key, _) = self.__authenticate_admin_or_user(db)
  1138.  
  1139. return (user, key)
  1140.  
  1141.  
  1142.  
  1143. def authenticate_admin_or_key_admin(self, db):
  1144.  
  1145. '''Check the request is a valid key administration request.
  1146.  
  1147.  
  1148.  
  1149. Allow server administrators to authenticate without having a key
  1150.  
  1151. passphrase. Return (user, key). Raise RequestHandled (on permission
  1152.  
  1153. denied), InvalidRequestError.
  1154.  
  1155.  
  1156.  
  1157. '''
  1158.  
  1159. (user, key, access) = self.__authenticate_admin_or_user(db)
  1160.  
  1161. if access is not None and not access.key_admin:
  1162.  
  1163. self.auth_fail('user is not a key administrator')
  1164.  
  1165. return (user, key)
  1166.  
  1167.  
  1168.  
  1169. def authenticate_user(self, db):
  1170.  
  1171. '''Check the request is a valid key access request.
  1172.  
  1173.  
  1174.  
  1175. Return a (access, key passphrase). Raise RequestHandled (on permission
  1176.  
  1177. denied), InvalidRequestError.
  1178.  
  1179.  
  1180.  
  1181. '''
  1182.  
  1183. user = self.safe_outer_field('user')
  1184.  
  1185. if user is None:
  1186.  
  1187. self.auth_fail('user field missing')
  1188.  
  1189. key = self.safe_outer_field('key')
  1190.  
  1191. if key is None:
  1192.  
  1193. self.auth_fail('key field missing')
  1194.  
  1195. user_passphrase = self.inner_field('passphrase')
  1196.  
  1197. if user_passphrase is None:
  1198.  
  1199. self.auth_fail('passphrase field missing')
  1200.  
  1201. user = db.query(User).filter_by(name=user).first()
  1202.  
  1203. key = db.query(Key).filter_by(name=key).first()
  1204.  
  1205. encrypted_passphrase = None
  1206.  
  1207. access = None
  1208.  
  1209. if user is not None and key is not None:
  1210.  
  1211. access = db.query(KeyAccess).filter_by(user=user, key=key).first()
  1212.  
  1213. if access is not None:
  1214.  
  1215. encrypted_passphrase = access.encrypted_passphrase
  1216.  
  1217. if encrypted_passphrase is None:
  1218.  
  1219. # Perform a decryption attempt anyway to make timing attacks more
  1220.  
  1221. # difficult. gpg will probably choke on the attempt quickly
  1222.  
  1223. # enough, too bad.
  1224.  
  1225. encrypted_passphrase = 'x'
  1226.  
  1227. try:
  1228.  
  1229. key_passphrase = server_common.gpg_decrypt(self.config,
  1230.  
  1231. encrypted_passphrase,
  1232.  
  1233. user_passphrase)
  1234.  
  1235. except gpgme.GpgmeError:
  1236.  
  1237. self.auth_fail('passphrase does not match')
  1238.  
  1239. assert user is not None and key is not None and access is not None
  1240.  
  1241. return (access, key_passphrase)
  1242.  
  1243.  
  1244.  
  1245. def authenticate_key_admin(self, db):
  1246.  
  1247. '''Check the request is a valid key administration request.
  1248.  
  1249.  
  1250.  
  1251. Return a KeyAccess. Raise RequestHandled (on permission denied),
  1252.  
  1253. InvalidRequestError.
  1254.  
  1255.  
  1256.  
  1257. '''
  1258.  
  1259. (access, key_passphrase) = self.authenticate_user(db)
  1260.  
  1261. if not access.key_admin:
  1262.  
  1263. self.auth_fail('user is not a key administrator')
  1264.  
  1265. return (access, key_passphrase)
  1266.  
  1267.  
  1268.  
  1269. def key_by_name(db, conn):
  1270.  
  1271. '''Return a key specified by conn.safe_outer_field('key').
  1272.  
  1273.  
  1274.  
  1275. Raise InvalidRequestError.
  1276.  
  1277.  
  1278.  
  1279. '''
  1280.  
  1281. name = conn.safe_outer_field('key', required=True)
  1282.  
  1283. key = db.query(Key).filter_by(name=name).first()
  1284.  
  1285. if key is None:
  1286.  
  1287. conn.send_error(errors.KEY_NOT_FOUND)
  1288.  
  1289. return key
  1290.  
  1291.  
  1292.  
  1293. def user_by_name(db, conn):
  1294.  
  1295. '''Return an user specified by conn.safe_outer_field('name').
  1296.  
  1297.  
  1298.  
  1299. Raise InvalidRequestError.
  1300.  
  1301.  
  1302.  
  1303. '''
  1304.  
  1305. name = conn.safe_outer_field('name', required=True)
  1306.  
  1307. user = db.query(User).filter_by(name=name).first()
  1308.  
  1309. if user is None:
  1310.  
  1311. conn.send_error(errors.USER_NOT_FOUND)
  1312.  
  1313. return user
  1314.  
  1315.  
  1316.  
  1317. def key_access_by_names(db, conn):
  1318.  
  1319. '''Return a key access specified by conn.safe_outer_field('name'),
  1320.  
  1321. conn.safe_outer_field('key').
  1322.  
  1323.  
  1324.  
  1325. Raise InvalidRequestError.
  1326.  
  1327.  
  1328.  
  1329. '''
  1330.  
  1331. # Load user and key to provide full diagnostics
  1332.  
  1333. user = user_by_name(db, conn)
  1334.  
  1335. key = key_by_name(db, conn)
  1336.  
  1337. access = db.query(KeyAccess).filter_by(user=user, key=key).first()
  1338.  
  1339. if access is None:
  1340.  
  1341. conn.send_error(errors.KEY_USER_NOT_FOUND)
  1342.  
  1343. return access
  1344.  
  1345.  
  1346.  
  1347. _passphrase_characters = string.ascii_letters + string.digits
  1348.  
  1349. def random_passphrase(conn):
  1350.  
  1351. '''Return a random passphrase.'''
  1352.  
  1353. random = nss.nss.generate_random(conn.config.passphrase_length)
  1354.  
  1355. return ''.join(_passphrase_characters[ord(c) % len(_passphrase_characters)]
  1356.  
  1357. for c in random)
  1358.  
  1359.  
  1360.  
  1361. class RPMFileError(Exception):
  1362.  
  1363. pass
  1364.  
  1365.  
  1366.  
  1367. class RPMFile(object):
  1368.  
  1369. '''A single RPM, to be signed.'''
  1370.  
  1371.  
  1372.  
  1373. def __init__(self, path, sha512_digest, request_id=None):
  1374.  
  1375. '''Initialize.
  1376.  
  1377.  
  1378.  
  1379. sha512_digest is a SHA-512 digest of path, in binary form.
  1380.  
  1381. self.status is set to None, to be updated by other operations with this
  1382.  
  1383. RPM.
  1384.  
  1385.  
  1386.  
  1387. '''
  1388.  
  1389. self.path = path
  1390.  
  1391. self.__sha512_digest = sha512_digest
  1392.  
  1393. self.request_id = request_id
  1394.  
  1395. self.status = None
  1396.  
  1397.  
  1398.  
  1399. def verify(self):
  1400.  
  1401. '''Verify validity of the file.
  1402.  
  1403.  
  1404.  
  1405. Raise RPMFileError (setting self.status).
  1406.  
  1407.  
  1408.  
  1409. '''
  1410.  
  1411. # Use an external process to verify the file, to prevent the attacker
  1412.  
  1413. # from taking control of a process with an open network socket and
  1414.  
  1415. # key_passphrase if a security bug in librpm* is exploitable.
  1416.  
  1417. res = subprocess.call(('rpm', '--quiet', '--nosignature', '-K',
  1418.  
  1419. self.path),
  1420.  
  1421. # PIPE is used only to avoid inheriting our file
  1422.  
  1423. # descriptors
  1424.  
  1425. stdin=subprocess.PIPE, stdout=subprocess.PIPE,
  1426.  
  1427. stderr=subprocess.STDOUT, close_fds=True)
  1428.  
  1429. if res != 0:
  1430.  
  1431. self.status = errors.CORRUPT_RPM
  1432.  
  1433. raise RPMFileError('Corrupt RPM')
  1434.  
  1435.  
  1436.  
  1437. def read_header(self, fd):
  1438.  
  1439. '''Read file header from fd, which corresponds to self.path.
  1440.  
  1441.  
  1442.  
  1443. Set self.rpm_id to a string identifying the RPM. Raise RPMFileError
  1444.  
  1445. (setting self.status).
  1446.  
  1447.  
  1448.  
  1449. '''
  1450.  
  1451. # Don't import rpm at the top of the file! The rpm Python module calls
  1452.  
  1453. # NSS_NoDB_Init() during its initialization, which breaks our attempts
  1454.  
  1455. # to initialize nss with our certificate database.
  1456.  
  1457. import rpm
  1458.  
  1459.  
  1460.  
  1461. ts = rpm.ts()
  1462.  
  1463. ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
  1464.  
  1465. try:
  1466.  
  1467. self.__header = ts.hdrFromFdno(fd.fileno())
  1468.  
  1469. except rpm.error, e:
  1470.  
  1471. self.status = errors.CORRUPT_RPM
  1472.  
  1473. raise RPMFileError('Error reading RPM header: %s' % str(e))
  1474.  
  1475.  
  1476.  
  1477. rpm_id = (self.__header[rpm.RPMTAG_NAME],
  1478.  
  1479. self.__header[rpm.RPMTAG_EPOCH],
  1480.  
  1481. self.__header[rpm.RPMTAG_VERSION],
  1482.  
  1483. self.__header[rpm.RPMTAG_RELEASE],
  1484.  
  1485. self.__header[rpm.RPMTAG_ARCH],
  1486.  
  1487. binascii.b2a_hex(self.__sha512_digest))
  1488.  
  1489. self.rpm_id = repr(rpm_id)
  1490.  
  1491.  
  1492.  
  1493. def authenticate(self, get_field, payload_authenticated):
  1494.  
  1495. '''Verify the file corresponds to the request fields.
  1496.  
  1497.  
  1498.  
  1499. Use get_field to read a request field (may return None if missing).
  1500.  
  1501. Raise RPMFileError on missing authentication (setting self.status),
  1502.  
  1503. InvalidRequestError on invalid authentication.
  1504.  
  1505.  
  1506.  
  1507. '''
  1508.  
  1509. # Don't import rpm at the top of the file! The rpm Python module calls
  1510.  
  1511. # NSS_NoDB_Init() during its initialization, which breaks our attempts
  1512.  
  1513. # to initialize nss with our certificate database.
  1514.  
  1515. import rpm
  1516.  
  1517.  
  1518.  
  1519. for (field, tag) in (('rpm-name', rpm.RPMTAG_NAME),
  1520.  
  1521. ('rpm-epoch', rpm.RPMTAG_EPOCH),
  1522.  
  1523. ('rpm-version', rpm.RPMTAG_VERSION),
  1524.  
  1525. ('rpm-release', rpm.RPMTAG_RELEASE),
  1526.  
  1527. ('rpm-arch', rpm.RPMTAG_ARCH)):
  1528.  
  1529. field_value = get_field(field)
  1530.  
  1531. if field_value is None:
  1532.  
  1533. continue
  1534.  
  1535. if not utils.string_is_safe(field_value):
  1536.  
  1537. raise InvalidRequestError('Field %s has unsafe value' %
  1538.  
  1539. repr(field))
  1540.  
  1541. if (tag == rpm.RPMTAG_ARCH and
  1542.  
  1543. self.__header[rpm.RPMTAG_SOURCEPACKAGE] == 1):
  1544.  
  1545. rpm_value = 'src'
  1546.  
  1547. else:
  1548.  
  1549. rpm_value = self.__header[tag]
  1550.  
  1551. if rpm_value is None:
  1552.  
  1553. rpm_value = ''
  1554.  
  1555. if field_value != str(rpm_value):
  1556.  
  1557. raise InvalidRequestError('RPM mismatch')
  1558.  
  1559.  
  1560.  
  1561. field_value = get_field('rpm-sigmd5')
  1562.  
  1563. if field_value is not None:
  1564.  
  1565. rpm_value = self.__header[rpm.RPMTAG_SIGMD5]
  1566.  
  1567. if rpm_value is None or field_value != rpm_value:
  1568.  
  1569. raise InvalidRequestError('RPM sigmd5 mismatch')
  1570.  
  1571. elif not payload_authenticated:
  1572.  
  1573. self.status = errors.UNAUTHENTICATED_RPM
  1574.  
  1575. raise RPMFileError('RPM not authenticated')
  1576.  
  1577.  
  1578.  
  1579. class SigningContext(object):
  1580.  
  1581. '''A tool for running rpm --addsign.'''
  1582.  
  1583.  
  1584.  
  1585. def __init__(self, conn, key, key_passphrase):
  1586.  
  1587. self.__key = key
  1588.  
  1589. self.__key_passphrase = key_passphrase
  1590.  
  1591. self.__argv = ['--define', '_signature gpg',
  1592.  
  1593. '--define', '_gpg_name %s' % key.fingerprint]
  1594.  
  1595. field_value = conn.outer_field_bool('v3-signature')
  1596.  
  1597. if field_value is not None and field_value:
  1598.  
  1599. # Add --force-v3-sigs to the value in redhat-rpm-config-9.0.3-3.fc10
  1600.  
  1601. self.__argv += ['--define',
  1602.  
  1603. '__gpg_sign_cmd %{__gpg} gpg --force-v3-sigs '
  1604.  
  1605. '--batch --no-verbose --no-armor --passphrase-fd 3 '
  1606.  
  1607. '--no-secmem-warning -u "%{_gpg_name}" -sbo '
  1608.  
  1609. '%{__signature_filename} %{__plaintext_filename}']
  1610.  
  1611. self.__env = dict(os.environ) # Shallow copy, uses our $GNUPGHOME
  1612.  
  1613. self.__env['LC_ALL'] = 'C'
  1614.  
  1615.  
  1616.  
  1617. def sign_rpm(self, config, rpm):
  1618.  
  1619. '''Sign rpm, using config.
  1620.  
  1621.  
  1622.  
  1623. Raise RPMFileError on error.
  1624.  
  1625.  
  1626.  
  1627. '''
  1628.  
  1629. try:
  1630.  
  1631. child = pexpect.spawn('rpm', self.__argv + ['--addsign', rpm.path],
  1632.  
  1633. env=self.__env,
  1634.  
  1635. timeout=config.signing_timeout)
  1636.  
  1637. child.expect('Enter pass phrase: ')
  1638.  
  1639. child.sendline(self.__key_passphrase)
  1640.  
  1641. answer = child.expect(['Pass phrase is good\.',
  1642.  
  1643. 'Pass phrase check failed'])
  1644.  
  1645. child.expect(pexpect.EOF)
  1646.  
  1647. child.close()
  1648.  
  1649. except pexpect.ExceptionPexpect, e:
  1650.  
  1651. msg = str(e).splitlines()[0] # We don't want all of the pexpect dump
  1652.  
  1653. rpm.status = errors.UNKNOWN_ERROR
  1654.  
  1655. raise RPMFileError('Error signing %s: %s' % (rpm.rpm_id, msg))
  1656.  
  1657. if (not os.WIFEXITED(child.status) or
  1658.  
  1659. os.WEXITSTATUS(child.status) != 0 or answer != 0):
  1660.  
  1661. rpm.status = errors.UNKNOWN_ERROR
  1662.  
  1663. raise RPMFileError('Error signing %s: status %d, output %s'
  1664.  
  1665. % (rpm.rpm_id, child.status, child.before))
  1666.  
  1667. logging.info('Signed RPM %s with key %s', rpm.rpm_id, self.__key.name)
  1668.  
  1669.  
  1670.  
  1671. # Request handlers
  1672.  
  1673.  
  1674.  
  1675. @request_handler()
  1676.  
  1677. def cmd_list_users(db, conn):
  1678.  
  1679. conn.authenticate_admin(db)
  1680.  
  1681. # Order by name to hide database structure
  1682.  
  1683. users = db.query(User).order_by(User.name).all()
  1684.  
  1685. conn.send_reply_header(errors.OK, {'num-users': len(users)})
  1686.  
  1687. payload = ''
  1688.  
  1689. for user in users:
  1690.  
  1691. payload += user.name + '\x00'
  1692.  
  1693. conn.send_reply_payload(payload)
  1694.  
  1695.  
  1696.  
  1697. @request_handler()
  1698.  
  1699. def cmd_new_user(db, conn):
  1700.  
  1701. conn.authenticate_admin(db)
  1702.  
  1703. name = conn.safe_outer_field('name', required=True)
  1704.  
  1705. # FIXME: is this check atomic?
  1706.  
  1707. if db.query(User).filter_by(name=name).first() is not None:
  1708.  
  1709. conn.send_error(errors.ALREADY_EXISTS)
  1710.  
  1711. new_password = conn.inner_field('new-password')
  1712.  
  1713. admin = conn.outer_field_bool('admin')
  1714.  
  1715. if admin is None:
  1716.  
  1717. admin = False
  1718.  
  1719. user = User(name, clear_password=new_password, admin=admin)
  1720.  
  1721. db.add(user)
  1722.  
  1723. db.commit()
  1724.  
  1725. conn.send_reply_ok_only()
  1726.  
  1727.  
  1728.  
  1729. @request_handler()
  1730.  
  1731. def cmd_delete_user(db, conn):
  1732.  
  1733. conn.authenticate_admin(db)
  1734.  
  1735. user = user_by_name(db, conn)
  1736.  
  1737. if len(user.key_accesses) > 0:
  1738.  
  1739. conn.send_error(errors.USER_HAS_KEY_ACCESSES)
  1740.  
  1741. db.delete(user)
  1742.  
  1743. db.commit()
  1744.  
  1745. conn.send_reply_ok_only()
  1746.  
  1747.  
  1748.  
  1749. @request_handler()
  1750.  
  1751. def cmd_user_info(db, conn):
  1752.  
  1753. conn.authenticate_admin(db)
  1754.  
  1755. user = user_by_name(db, conn)
  1756.  
  1757. conn.send_reply_header(errors.OK, {'admin': user.admin})
  1758.  
  1759. conn.send_reply_payload('')
  1760.  
  1761.  
  1762.  
  1763. @request_handler()
  1764.  
  1765. def cmd_modify_user(db, conn):
  1766.  
  1767. conn.authenticate_admin(db)
  1768.  
  1769. user = user_by_name(db, conn)
  1770.  
  1771. admin = conn.outer_field_bool('admin')
  1772.  
  1773. if admin is not None:
  1774.  
  1775. user.admin = admin
  1776.  
  1777. new_name = conn.safe_outer_field('new-name')
  1778.  
  1779. if new_name is not None:
  1780.  
  1781. # FIXME: is this check atomic?
  1782.  
  1783. if db.query(User).filter_by(name=new_name).first() is not None:
  1784.  
  1785. conn.send_error(errors.ALREADY_EXISTS)
  1786.  
  1787. user.name = new_name
  1788.  
  1789. new_password = conn.inner_field('new-password')
  1790.  
  1791. if new_password is not None:
  1792.  
  1793. user.clear_password = new_password
  1794.  
  1795. db.commit()
  1796.  
  1797. conn.send_reply_ok_only()
  1798.  
  1799.  
  1800.  
  1801. @request_handler()
  1802.  
  1803. def cmd_key_user_info(db, conn):
  1804.  
  1805. conn.authenticate_admin(db)
  1806.  
  1807. access = key_access_by_names(db, conn)
  1808.  
  1809. conn.send_reply_header(errors.OK, {'key-admin': access.key_admin})
  1810.  
  1811. conn.send_reply_payload('')
  1812.  
  1813.  
  1814.  
  1815. @request_handler()
  1816.  
  1817. def cmd_modify_key_user(db, conn):
  1818.  
  1819. conn.authenticate_admin(db)
  1820.  
  1821. access = key_access_by_names(db, conn)
  1822.  
  1823. key_admin = conn.outer_field_bool('key-admin')
  1824.  
  1825. if key_admin is not None:
  1826.  
  1827. access.key_admin = key_admin
  1828.  
  1829. db.commit()
  1830.  
  1831. conn.send_reply_ok_only()
  1832.  
  1833.  
  1834.  
  1835. @request_handler()
  1836.  
  1837. def cmd_list_keys(db, conn):
  1838.  
  1839. conn.authenticate_admin(db)
  1840.  
  1841. # Order by name to hide database structure
  1842.  
  1843. keys = db.query(Key).order_by(Key.name).all()
  1844.  
  1845. conn.send_reply_header(errors.OK, {'num-keys': len(keys)})
  1846.  
  1847. payload = ''
  1848.  
  1849. for user in keys:
  1850.  
  1851. payload += user.name + '\x00'
  1852.  
  1853. conn.send_reply_payload(payload)
  1854.  
  1855.  
  1856.  
  1857. @request_handler()
  1858.  
  1859. def cmd_new_key(db, conn):
  1860.  
  1861. conn.authenticate_admin(db)
  1862.  
  1863. key_name = conn.safe_outer_field('key', required=True)
  1864.  
  1865. # FIXME: is this check atomic?
  1866.  
  1867. if db.query(Key).filter_by(name=key_name).first() is not None:
  1868.  
  1869. conn.send_error(errors.ALREADY_EXISTS)
  1870.  
  1871. admin_name = conn.safe_outer_field('initial-key-admin')
  1872.  
  1873. if admin_name is None:
  1874.  
  1875. admin_name = conn.safe_outer_field('user', required=True)
  1876.  
  1877. admin = db.query(User).filter_by(name=admin_name).first()
  1878.  
  1879. if admin is None:
  1880.  
  1881. conn.send_error(errors.USER_NOT_FOUND)
  1882.  
  1883. key_attrs = ('Key-Type: %s\n' % conn.config.gnupg_key_type +
  1884.  
  1885. 'Key-Length: %d\n' % conn.config.gnupg_key_length +
  1886.  
  1887. 'Key-Usage: %s\n' % conn.config.gnupg_key_usage)
  1888.  
  1889. if conn.config.gnupg_subkey_type is not None:
  1890.  
  1891. key_attrs += ('Subkey-Type: %s\n' % conn.config.gnupg_subkey_type +
  1892.  
  1893. 'Subkey-Length: %d\n' % conn.config.gnupg_subkey_length)
  1894.  
  1895. key_passphrase = random_passphrase(conn)
  1896.  
  1897. key_attrs += 'Passphrase: %s\n' % key_passphrase
  1898.  
  1899. name = conn.safe_outer_field('name-real')
  1900.  
  1901. if name is None:
  1902.  
  1903. name = key_name
  1904.  
  1905. key_attrs += 'Name-Real: %s\n' % name
  1906.  
  1907. name = conn.safe_outer_field('name-comment')
  1908.  
  1909. if name is not None:
  1910.  
  1911. key_attrs += 'Name-Comment: %s\n' % name
  1912.  
  1913. name = conn.safe_outer_field('name-email')
  1914.  
  1915. if name is not None:
  1916.  
  1917. key_attrs += 'Name-Email: %s\n' % name
  1918.  
  1919. expire = conn.safe_outer_field('expire-date')
  1920.  
  1921. if expire is not None:
  1922.  
  1923. if not utils.yyyy_mm_dd_is_valid(expire):
  1924.  
  1925. raise InvalidRequestError('Invalid expiration date')
  1926.  
  1927. key_attrs += 'Expire-Date: %s\n' % expire
  1928.  
  1929. user_passphrase = conn.inner_field('passphrase', required=True)
  1930.  
  1931.  
  1932.  
  1933. env = dict(os.environ) # Shallow copy, uses our $GNUPGHOME
  1934.  
  1935. env['LC_ALL'] = 'C'
  1936.  
  1937. sub = subprocess.Popen((settings.gnupg_bin, '--gen-key', '--batch',
  1938.  
  1939. '--quiet', '--status-fd', '1'),
  1940.  
  1941. stdin=subprocess.PIPE, stdout=subprocess.PIPE,
  1942.  
  1943. stderr=subprocess.PIPE, close_fds=True, env=env)
  1944.  
  1945. (out, err) = sub.communicate(key_attrs)
  1946.  
  1947. for line in err.split('\n'):
  1948.  
  1949. if (line != '' and
  1950.  
  1951. not line.startswith('gpg: WARNING: unsafe permissions on homedir')
  1952.  
  1953. and not line.startswith('Not enough random bytes available.')
  1954.  
  1955. and not line.startswith('the OS a chance to collect more entropy!')
  1956.  
  1957. and not (line.startswith('gpg: key ') and
  1958.  
  1959. line.endswith('marked as ultimately trusted'))):
  1960.  
  1961. logging.error('Unrecognized GPG stderr: %s', repr(line))
  1962.  
  1963. conn.send_error(errors.UNKNOWN_ERROR)
  1964.  
  1965. fingerprint = None
  1966.  
  1967. for line in out.split('\n'):
  1968.  
  1969. if (line == '' or line == '[GNUPG:] GOOD_PASSPHRASE' or
  1970.  
  1971. line.startswith('[GNUPG:] PROGRESS')):
  1972.  
  1973. continue
  1974.  
  1975. if not line.startswith('[GNUPG:] KEY_CREATED'):
  1976.  
  1977. logging.error('Unrecognized GPG stdout: %s', repr(line))
  1978.  
  1979. conn.send_error(errors.UNKNOWN_ERROR)
  1980.  
  1981. fingerprint = line.split(' ')[-1]
  1982.  
  1983. if fingerprint is None:
  1984.  
  1985. logging.error('Can not find fingerprint of a new key in gpg output')
  1986.  
  1987. conn.send_error(errors.UNKNOWN_ERROR)
  1988.  
  1989.  
  1990.  
  1991. try:
  1992.  
  1993. key = Key(key_name, fingerprint)
  1994.  
  1995. db.add(key)
  1996.  
  1997. access = KeyAccess(key, admin, key_admin=True)
  1998.  
  1999. access.set_passphrase(conn.config, key_passphrase=key_passphrase,
  2000.  
  2001. user_passphrase=user_passphrase)
  2002.  
  2003. db.add(access)
  2004.  
  2005. db.commit()
  2006.  
  2007. except:
  2008.  
  2009. server_common.gpg_delete_key(conn.config, fingerprint)
  2010.  
  2011. raise
  2012.  
  2013. payload = server_common.gpg_public_key(conn.config, fingerprint)
  2014.  
  2015. conn.send_reply_header(errors.OK, {})
  2016.  
  2017. conn.send_reply_payload(payload)
  2018.  
  2019.  
  2020.  
  2021. @request_handler(payload_storage=RequestHandler.PAYLOAD_FILE)
  2022.  
  2023. def cmd_import_key(db, conn):
  2024.  
  2025. conn.authenticate_admin(db)
  2026.  
  2027. key_name = conn.safe_outer_field('key', required=True)
  2028.  
  2029. # FIXME: is this check atomic?
  2030.  
  2031. if db.query(Key).filter_by(name=key_name).first() is not None:
  2032.  
  2033. conn.send_error(errors.ALREADY_EXISTS)
  2034.  
  2035. admin_name = conn.safe_outer_field('initial-key-admin')
  2036.  
  2037. if admin_name is None:
  2038.  
  2039. admin_name = conn.safe_outer_field('user', required=True)
  2040.  
  2041. admin = db.query(User).filter_by(name=admin_name).first()
  2042.  
  2043. if admin is None:
  2044.  
  2045. conn.send_error(errors.USER_NOT_FOUND)
  2046.  
  2047. new_key_passphrase = random_passphrase(conn)
  2048.  
  2049. import_key_passphrase = conn.inner_field('passphrase', required=True)
  2050.  
  2051. user_passphrase = conn.inner_field('new-passphrase', required=True)
  2052.  
  2053.  
  2054.  
  2055. try:
  2056.  
  2057. fingerprint = server_common.gpg_import_key(conn.config, conn.payload_file)
  2058.  
  2059. except server_common.GPGError, e:
  2060.  
  2061. conn.send_error(errors.INVALID_IMPORT, message=str(e))
  2062.  
  2063.  
  2064.  
  2065. try:
  2066.  
  2067. try:
  2068.  
  2069. server_common.gpg_change_password(conn.config, fingerprint,
  2070.  
  2071. import_key_passphrase,
  2072.  
  2073. new_key_passphrase)
  2074.  
  2075. except server_common.GPGError, e:
  2076.  
  2077. conn.send_error(errors.IMPORT_PASSPHRASE_ERROR)
  2078.  
  2079.  
  2080.  
  2081. key = Key(key_name, fingerprint)
  2082.  
  2083. db.add(key)
  2084.  
  2085. access = KeyAccess(key, admin, key_admin=True)
  2086.  
  2087. access.set_passphrase(conn.config, key_passphrase=new_key_passphrase,
  2088.  
  2089. user_passphrase=user_passphrase)
  2090.  
  2091. db.add(access)
  2092.  
  2093. db.commit()
  2094.  
  2095. except:
  2096.  
  2097. server_common.gpg_delete_key(conn.config, fingerprint)
  2098.  
  2099. raise
  2100.  
  2101. conn.send_reply_ok_only()
  2102.  
  2103.  
  2104.  
  2105. @request_handler()
  2106.  
  2107. def cmd_delete_key(db, conn):
  2108.  
  2109. conn.authenticate_admin(db)
  2110.  
  2111. key = key_by_name(db, conn)
  2112.  
  2113. server_common.gpg_delete_key(conn.config, key.fingerprint)
  2114.  
  2115. for a in key.key_accesses:
  2116.  
  2117. db.delete(a)
  2118.  
  2119. db.delete(key)
  2120.  
  2121. db.commit()
  2122.  
  2123. conn.send_reply_ok_only()
  2124.  
  2125.  
  2126.  
  2127. @request_handler()
  2128.  
  2129. def cmd_modify_key(db, conn):
  2130.  
  2131. conn.authenticate_admin(db)
  2132.  
  2133. key = key_by_name(db, conn)
  2134.  
  2135. new_name = conn.safe_outer_field('new-name')
  2136.  
  2137. if new_name is not None:
  2138.  
  2139. # FIXME: is this check atomic?
  2140.  
  2141. if db.query(Key).filter_by(name=new_name).first() is not None:
  2142.  
  2143. conn.send_error(errors.ALREADY_EXISTS)
  2144.  
  2145. key.name = new_name
  2146.  
  2147. db.commit()
  2148.  
  2149. conn.send_reply_ok_only()
  2150.  
  2151.  
  2152.  
  2153. @request_handler()
  2154.  
  2155. def cmd_list_key_users(db, conn):
  2156.  
  2157. (user, key) = conn.authenticate_admin_or_key_admin(db)
  2158.  
  2159. # Order by name to hide database structure
  2160.  
  2161. names = sorted(access.user.name for access in key.key_accesses)
  2162.  
  2163. conn.send_reply_header(errors.OK, {'num-users': len(names)})
  2164.  
  2165. payload = ''
  2166.  
  2167. for name in names:
  2168.  
  2169. payload += name + '\x00'
  2170.  
  2171. conn.send_reply_payload(payload)
  2172.  
  2173.  
  2174.  
  2175. @request_handler()
  2176.  
  2177. def cmd_grant_key_access(db, conn):
  2178.  
  2179. (access, key_passphrase) = conn.authenticate_key_admin(db)
  2180.  
  2181. user = user_by_name(db, conn)
  2182.  
  2183. new_passphrase = conn.inner_field('new-passphrase', required=True)
  2184.  
  2185. # FIXME: is this check atomic?
  2186.  
  2187. if (db.query(KeyAccess).filter_by(user=user, key=access.key).first() is not
  2188.  
  2189. None):
  2190.  
  2191. conn.send_error(errors.ALREADY_EXISTS)
  2192.  
  2193. a2 = KeyAccess(access.key, user, key_admin=False)
  2194.  
  2195. a2.set_passphrase(conn.config, key_passphrase=key_passphrase,
  2196.  
  2197. user_passphrase=new_passphrase)
  2198.  
  2199. db.add(a2)
  2200.  
  2201. db.commit()
  2202.  
  2203. conn.send_reply_ok_only()
  2204.  
  2205.  
  2206.  
  2207. @request_handler()
  2208.  
  2209. def cmd_revoke_key_access(db, conn):
  2210.  
  2211. (_, key) = conn.authenticate_admin_or_key_admin(db)
  2212.  
  2213. user = user_by_name(db, conn)
  2214.  
  2215. access = db.query(KeyAccess).filter_by(user=user, key=key).first()
  2216.  
  2217. if access is None:
  2218.  
  2219. conn.send_error(errors.KEY_USER_NOT_FOUND)
  2220.  
  2221. if len(key.key_accesses) == 1:
  2222.  
  2223. conn.send_error(errors.ONLY_ONE_KEY_USER)
  2224.  
  2225. db.delete(access)
  2226.  
  2227. db.commit()
  2228.  
  2229. conn.send_reply_ok_only()
  2230.  
  2231.  
  2232.  
  2233. @request_handler()
  2234.  
  2235. def cmd_get_public_key(db, conn):
  2236.  
  2237. (_, key) = conn.authenticate_admin_or_user(db)
  2238.  
  2239. payload = server_common.gpg_public_key(conn.config, str(key.fingerprint))
  2240.  
  2241. conn.send_reply_header(errors.OK, {})
  2242.  
  2243. conn.send_reply_payload(payload)
  2244.  
  2245.  
  2246.  
  2247. @request_handler()
  2248.  
  2249. def cmd_change_passphrase(db, conn):
  2250.  
  2251. (access, key_passphrase) = conn.authenticate_user(db)
  2252.  
  2253. new_passphrase = conn.inner_field('new-passphrase', required=True)
  2254.  
  2255. access.set_passphrase(conn.config, key_passphrase=key_passphrase,
  2256.  
  2257. user_passphrase=new_passphrase)
  2258.  
  2259. db.commit()
  2260.  
  2261. conn.send_reply_ok_only()
  2262.  
  2263.  
  2264.  
  2265. @request_handler(payload_storage=RequestHandler.PAYLOAD_FILE)
  2266.  
  2267. def cmd_sign_text(db, conn):
  2268.  
  2269. (access, key_passphrase) = conn.authenticate_user(db)
  2270.  
  2271. signed_file = tempfile.TemporaryFile()
  2272.  
  2273. try:
  2274.  
  2275. server_common.gpg_clearsign(conn.config, signed_file, conn.payload_file,
  2276.  
  2277. access.key.fingerprint, key_passphrase)
  2278.  
  2279. logging.info('Signed text %s with key %s',
  2280.  
  2281. binascii.b2a_hex(conn.payload_sha512_digest),
  2282.  
  2283. access.key.name)
  2284.  
  2285. conn.send_reply_header(errors.OK, {})
  2286.  
  2287. conn.send_reply_payload_from_file(signed_file)
  2288.  
  2289. finally:
  2290.  
  2291. signed_file.close()
  2292.  
  2293.  
  2294.  
  2295. @request_handler(payload_storage=RequestHandler.PAYLOAD_FILE)
  2296.  
  2297. def cmd_sign_data(db, conn):
  2298.  
  2299. (access, key_passphrase) = conn.authenticate_user(db)
  2300.  
  2301. signature_file = tempfile.TemporaryFile()
  2302.  
  2303. try:
  2304.  
  2305. server_common.gpg_detached_signature(conn.config, signature_file,
  2306.  
  2307. conn.payload_file,
  2308.  
  2309. access.key.fingerprint,
  2310.  
  2311. key_passphrase)
  2312.  
  2313. logging.info('Signed data %s with key %s',
  2314.  
  2315. binascii.b2a_hex(conn.payload_sha512_digest),
  2316.  
  2317. access.key.name)
  2318.  
  2319. conn.send_reply_header(errors.OK, {})
  2320.  
  2321. conn.send_reply_payload_from_file(signature_file)
  2322.  
  2323. finally:
  2324.  
  2325. signature_file.close()
  2326.  
  2327.  
  2328.  
  2329. @request_handler(payload_storage=RequestHandler.PAYLOAD_FILE,
  2330.  
  2331. payload_auth_optional=True)
  2332.  
  2333. def cmd_sign_rpm(db, conn):
  2334.  
  2335. (access, key_passphrase) = conn.authenticate_user(db)
  2336.  
  2337.  
  2338.  
  2339. rpm = RPMFile(conn.payload_path, conn.payload_sha512_digest)
  2340.  
  2341. try:
  2342.  
  2343. rpm.verify()
  2344.  
  2345. rpm.read_header(conn.payload_file)
  2346.  
  2347. rpm.authenticate(conn.outer_field, conn.payload_authenticated)
  2348.  
  2349. except RPMFileError:
  2350.  
  2351. conn.send_error(rpm.status)
  2352.  
  2353.  
  2354.  
  2355. ctx = SigningContext(conn, access.key, key_passphrase)
  2356.  
  2357. try:
  2358.  
  2359. ctx.sign_rpm(conn.config, rpm)
  2360.  
  2361. except RPMFileError, e:
  2362.  
  2363. logging.error(str(e))
  2364.  
  2365. conn.send_error(rpm.status)
  2366.  
  2367.  
  2368.  
  2369. # Reopen to get the new file even if rpm doesn't overwrite the file in place
  2370.  
  2371. f = open(rpm.path, 'rb')
  2372.  
  2373. try:
  2374.  
  2375. conn.send_reply_header(errors.OK, {})
  2376.  
  2377. conn.send_reply_payload_from_file(f)
  2378.  
  2379. finally:
  2380.  
  2381. f.close()
  2382.  
  2383.  
  2384.  
  2385. class SignRPMsRequestThread(utils.WorkerThread):
  2386.  
  2387. '''A thread that handles sign-rpm requests.
  2388.  
  2389.  
  2390.  
  2391. The requests are put into dest_queue as RPMFile objects, with None marking
  2392.  
  2393. end of the requests.
  2394.  
  2395.  
  2396.  
  2397. '''
  2398.  
  2399.  
  2400.  
  2401. def __init__(self, conn, dest_queue, header_nss_key, payload_nss_key,
  2402.  
  2403. tmp_dir):
  2404.  
  2405. super(SignRPMsRequestThread, self). \
  2406.  
  2407. __init__('sign-rpms:requests', 'request thread',
  2408.  
  2409. output_queues=((dest_queue, None),))
  2410.  
  2411. self.__conn = conn
  2412.  
  2413. self.__dest = dest_queue
  2414.  
  2415. self.__header_nss_key = header_nss_key
  2416.  
  2417. self.__payload_nss_key = payload_nss_key
  2418.  
  2419. self.__tmp_dir = tmp_dir
  2420.  
  2421.  
  2422.  
  2423. def _real_run(self):
  2424.  
  2425. total_size = 0
  2426.  
  2427. server_idx = 0
  2428.  
  2429. while True:
  2430.  
  2431. (rpm, size) = self.__read_one_request \
  2432.  
  2433. (server_idx,
  2434.  
  2435. self.__conn.config.max_rpms_payloads_size - total_size)
  2436.  
  2437. if rpm is None:
  2438.  
  2439. break
  2440.  
  2441. server_idx += 1
  2442.  
  2443. total_size += size
  2444.  
  2445. self.__dest.put(rpm)
  2446.  
  2447.  
  2448.  
  2449. def __read_one_request(self, server_idx, remaining_size):
  2450.  
  2451. '''Read one request from self.__conn.
  2452.  
  2453.  
  2454.  
  2455. Return (RPMFile, file size), (None, None) on EOF. Raise
  2456.  
  2457. InvalidRequestError, others. Only allow remaining_size bytes for the
  2458.  
  2459. payload.
  2460.  
  2461.  
  2462.  
  2463. '''
  2464.  
  2465. try:
  2466.  
  2467. nss_key = utils.derived_key(self.__header_nss_key, server_idx)
  2468.  
  2469. fields = self.__conn.read_subheader(nss_key)
  2470.  
  2471. except EOFError:
  2472.  
  2473. return (None, None)
  2474.  
  2475. s = utils.readable_fields(fields)
  2476.  
  2477. logging.debug('%s: Started handling %s', self.name, s)
  2478.  
  2479. logging.info('Subrequest: %s', s)
  2480.  
  2481. if 'id' not in fields:
  2482.  
  2483. raise InvalidRequestError('Required subheader field id missing.')
  2484.  
  2485.  
  2486.  
  2487. nss_key = utils.derived_key(self.__payload_nss_key, server_idx)
  2488.  
  2489. (path, payload_file, sha512_digest, authenticated) \
  2490.  
  2491. = self.__conn.read_subpayload_to_file(nss_key, remaining_size,
  2492.  
  2493. self.__tmp_dir)
  2494.  
  2495. try:
  2496.  
  2497. # Count whole blocks to avoid millions of 1-byte files filling the
  2498.  
  2499. # hard drive due to internal fragmentation.
  2500.  
  2501. size = utils.file_size_in_blocks(payload_file)
  2502.  
  2503.  
  2504.  
  2505. rpm = RPMFile(path, sha512_digest, request_id=fields['id'])
  2506.  
  2507. try:
  2508.  
  2509. rpm.verify()
  2510.  
  2511. rpm.read_header(payload_file)
  2512.  
  2513. except RPMFileError:
  2514.  
  2515. return (rpm, size)
  2516.  
  2517. finally:
  2518.  
  2519. payload_file.close()
  2520.  
  2521.  
  2522.  
  2523. try:
  2524.  
  2525. rpm.authenticate(fields.get, authenticated)
  2526.  
  2527. except RPMFileError:
  2528.  
  2529. return (rpm, size)
  2530.  
  2531.  
  2532.  
  2533. return (rpm, size)
  2534.  
  2535.  
  2536.  
  2537. class SignRPMsSignerThread(utils.WorkerThread):
  2538.  
  2539. '''A thread that actually performs the signing.
  2540.  
  2541.  
  2542.  
  2543. The requests in dst_queue and src_queue are RPMFile objects, with None
  2544.  
  2545. marking end of the requests.
  2546.  
  2547.  
  2548.  
  2549. '''
  2550.  
  2551.  
  2552.  
  2553. def __init__(self, config, dst_queue, src_queue, ctx):
  2554.  
  2555. super(SignRPMsSignerThread, self).__init__ \
  2556.  
  2557. ('sign-rpms:signing', 'signer thread',
  2558.  
  2559. input_queues=((src_queue, None),),
  2560.  
  2561. output_queues=((dst_queue, None),))
  2562.  
  2563. self.__config = config
  2564.  
  2565. self.__dst = dst_queue
  2566.  
  2567. self.__src = src_queue
  2568.  
  2569. self.__ctx = ctx
  2570.  
  2571.  
  2572.  
  2573. def _real_run(self):
  2574.  
  2575. while True:
  2576.  
  2577. rpm = self.__src.get()
  2578.  
  2579. if rpm is None:
  2580.  
  2581. break
  2582.  
  2583. try:
  2584.  
  2585. try:
  2586.  
  2587. # FIXME: sign more at a time
  2588.  
  2589. self.__handle_one_rpm(rpm)
  2590.  
  2591. except:
  2592.  
  2593. if rpm.status is None:
  2594.  
  2595. rpm.status = errors.UNKNOWN_ERROR
  2596.  
  2597. raise
  2598.  
  2599. finally:
  2600.  
  2601. self.__dst.put(rpm)
  2602.  
  2603.  
  2604.  
  2605. def __handle_one_rpm(self, rpm):
  2606.  
  2607. '''Handle an incoming request.'''
  2608.  
  2609. logging.debug('%s: Started handling %s', self.name, rpm.rpm_id)
  2610.  
  2611. if rpm.status is not None:
  2612.  
  2613. return
  2614.  
  2615.  
  2616.  
  2617. try:
  2618.  
  2619. self.__ctx.sign_rpm(self.__config, rpm)
  2620.  
  2621. except RPMFileError, e:
  2622.  
  2623. logging.error(str(e))
  2624.  
  2625.  
  2626.  
  2627. class SignRPMsReplyThread(utils.WorkerThread):
  2628.  
  2629. '''A thread that sends subrequest replies.
  2630.  
  2631.  
  2632.  
  2633. The requests in src_queue are RPMFile objects, with None marking end of the
  2634.  
  2635. requests.
  2636.  
  2637.  
  2638.  
  2639. '''
  2640.  
  2641.  
  2642.  
  2643. def __init__(self, conn, src_queue, header_nss_key, payload_nss_key):
  2644.  
  2645. super(SignRPMsReplyThread, self). \
  2646.  
  2647. __init__('sign-rpms:replies', 'reply thread',
  2648.  
  2649. input_queues=((src_queue, None),))
  2650.  
  2651. self.__conn = conn
  2652.  
  2653. self.__src = src_queue
  2654.  
  2655. self.__header_nss_key = header_nss_key
  2656.  
  2657. self.__payload_nss_key = payload_nss_key
  2658.  
  2659.  
  2660.  
  2661. def _real_run(self):
  2662.  
  2663. '''Read all results and send subreplies.'''
  2664.  
  2665. server_idx = 0
  2666.  
  2667. while True:
  2668.  
  2669. rpm = self.__src.get()
  2670.  
  2671. if rpm is None:
  2672.  
  2673. break
  2674.  
  2675. self.__handle_one_rpm(rpm, server_idx)
  2676.  
  2677. server_idx += 1
  2678.  
  2679.  
  2680.  
  2681. def __handle_one_rpm(self, rpm, server_idx):
  2682.  
  2683. '''Send information based on rpm.'''
  2684.  
  2685. logging.debug('%s: Started handling %s', self.name, rpm.rpm_id)
  2686.  
  2687. f = {'id': rpm.request_id}
  2688.  
  2689. if rpm.status is not None:
  2690.  
  2691. f['status'] = rpm.status
  2692.  
  2693. logging.info('Subrequest %d error: %s', server_idx,
  2694.  
  2695. errors.message(rpm.status))
  2696.  
  2697. else:
  2698.  
  2699. f['status'] = errors.OK
  2700.  
  2701. nss_key = utils.derived_key(self.__header_nss_key, server_idx)
  2702.  
  2703. self.__conn.send_subheader(f, nss_key)
  2704.  
  2705.  
  2706.  
  2707. nss_key = utils.derived_key(self.__payload_nss_key, server_idx)
  2708.  
  2709. if rpm.status is None:
  2710.  
  2711. f = open(rpm.path, 'rb')
  2712.  
  2713. try:
  2714.  
  2715. self.__conn.send_subpayload_from_file(f, nss_key)
  2716.  
  2717. finally:
  2718.  
  2719. f.close()
  2720.  
  2721. else:
  2722.  
  2723. self.__conn.send_empty_subpayload(nss_key)
  2724.  
  2725.  
  2726.  
  2727.  
  2728.  
  2729. @request_handler()
  2730.  
  2731. def cmd_sign_rpms(db, conn):
  2732.  
  2733. (access, key_passphrase) = conn.authenticate_user(db)
  2734.  
  2735. mech = nss.nss.CKM_GENERIC_SECRET_KEY_GEN
  2736.  
  2737. slot = nss.nss.get_best_slot(mech)
  2738.  
  2739. buf = conn.inner_field('subrequest-header-auth-key', required=True)
  2740.  
  2741. if len(buf) < 64:
  2742.  
  2743. raise InvalidRequestError('Subrequest header authentication key too '
  2744.  
  2745. 'small')
  2746.  
  2747. # "Unwrap" because the key was encrypted for transmission using TLS
  2748.  
  2749. subrequest_header_nss_key = nss.nss.import_sym_key \
  2750.  
  2751. (slot, mech, nss.nss.PK11_OriginUnwrap, nss.nss.CKA_DERIVE,
  2752.  
  2753. nss.nss.SecItem(buf))
  2754.  
  2755. buf = conn.inner_field('subrequest-payload-auth-key', required=True)
  2756.  
  2757. if len(buf) < 64:
  2758.  
  2759. raise InvalidRequestError('Subrequest payload authentication key too '
  2760.  
  2761. 'small')
  2762.  
  2763. subrequest_payload_nss_key = nss.nss.import_sym_key \
  2764.  
  2765. (slot, mech, nss.nss.PK11_OriginUnwrap, nss.nss.CKA_DERIVE,
  2766.  
  2767. nss.nss.SecItem(buf))
  2768.  
  2769. buf = conn.inner_field('subreply-header-auth-key', required=True)
  2770.  
  2771. if len(buf) < 64:
  2772.  
  2773. raise InvalidRequestError('Subreply header authentication key too '
  2774.  
  2775. 'small')
  2776.  
  2777. subreply_header_nss_key = nss.nss.import_sym_key \
  2778.  
  2779. (slot, mech, nss.nss.PK11_OriginUnwrap, nss.nss.CKA_DERIVE,
  2780.  
  2781. nss.nss.SecItem(buf))
  2782.  
  2783. buf = conn.inner_field('subreply-payload-auth-key', required=True)
  2784.  
  2785. if len(buf) < 64:
  2786.  
  2787. raise InvalidRequestError('Subreply payload authentication key too '
  2788.  
  2789. 'small')
  2790.  
  2791. subreply_payload_nss_key = nss.nss.import_sym_key \
  2792.  
  2793. (slot, mech, nss.nss.PK11_OriginUnwrap, nss.nss.CKA_DERIVE,
  2794.  
  2795. nss.nss.SecItem(buf))
  2796.  
  2797. signing_ctx = SigningContext(conn, access.key, key_passphrase)
  2798.  
  2799. conn.send_reply_ok_only()
  2800.  
  2801.  
  2802.  
  2803. tmp_dir = tempfile.mkdtemp()
  2804.  
  2805. exception = None
  2806.  
  2807. try:
  2808.  
  2809. q1 = Queue.Queue(100)
  2810.  
  2811. q2 = Queue.Queue(100)
  2812.  
  2813. threads = []
  2814.  
  2815. threads.append(SignRPMsRequestThread(conn, q1,
  2816.  
  2817. subrequest_header_nss_key,
  2818.  
  2819. subrequest_payload_nss_key,
  2820.  
  2821. tmp_dir))
  2822.  
  2823. threads.append(SignRPMsSignerThread(conn.config, q2, q1, signing_ctx))
  2824.  
  2825. threads.append(SignRPMsReplyThread(conn, q2, subreply_header_nss_key,
  2826.  
  2827. subreply_payload_nss_key))
  2828.  
  2829.  
  2830.  
  2831. (_, exception) = utils.run_worker_threads(threads,
  2832.  
  2833. (InvalidRequestError,))
  2834.  
  2835. finally:
  2836.  
  2837. shutil.rmtree(tmp_dir)
  2838.  
  2839. if exception is not None:
  2840.  
  2841. raise exception[0], exception[1], exception[2]
  2842.  
  2843.  
  2844.  
  2845. def unknown_request_handler(unused_db, conn):
  2846.  
  2847. conn.send_reply_header(errors.UNKNOWN_OP, {})
  2848.  
  2849. conn.send_reply_payload('')
  2850.  
  2851. # Allow some payload in order to return errors.UNKNOWN_OP rather than fail with
  2852.  
  2853. # "payload too large"
  2854.  
  2855. request_handlers[None] = RequestHandler(unknown_request_handler,
  2856.  
  2857. RequestHandler.PAYLOAD_MEMORY)
  2858.  
  2859.  
  2860.  
  2861.  
  2862.  
  2863.  
  2864. _CHILD_OK = 0 # Handled a request
  2865.  
  2866. _CHILD_CONNECTION_REFUSED = 1 # Connection to the bridge was refused
  2867.  
  2868. _CHILD_BUG = 2 # A bug in the child
  2869.  
  2870. # Undefined values are treated as _CHILD_BUG:
  2871.  
  2872.  
  2873.  
  2874. def request_handling_child(config):
  2875.  
  2876. '''Handle a single request, runinng in a child process.
  2877.  
  2878.  
  2879.  
  2880. Return one of the _CHILD_* exit codes.
  2881.  
  2882.  
  2883.  
  2884. '''
  2885.  
  2886. try:
  2887.  
  2888. utils.set_regid(config)
  2889.  
  2890. utils.set_reuid(config)
  2891.  
  2892. utils.update_HOME_for_uid(config)
  2893.  
  2894. except:
  2895.  
  2896. # The failing function has already logged the exception
  2897.  
  2898. return _CHILD_BUG
  2899.  
  2900.  
  2901.  
  2902. child_exception = None
  2903.  
  2904. try:
  2905.  
  2906. db = server_common.db_open(config)
  2907.  
  2908. conn = ServersConnection(config)
  2909.  
  2910. try:
  2911.  
  2912. logging.debug('Waiting for a request')
  2913.  
  2914. handler = conn.read_request()
  2915.  
  2916. handler.handler(db, conn)
  2917.  
  2918. finally:
  2919.  
  2920. try:
  2921.  
  2922. conn.close()
  2923.  
  2924. except (double_tls.ChildConnectionRefusedError,
  2925.  
  2926. double_tls.ChildUnrecoverableError), e:
  2927.  
  2928. child_exception = e
  2929.  
  2930. except RequestHandled:
  2931.  
  2932. pass
  2933.  
  2934. except InvalidRequestError, e:
  2935.  
  2936. logging.warning('Invalid request: %s', str(e))
  2937.  
  2938. except (IOError, socket.error), e:
  2939.  
  2940. logging.info('I/O error: %s', repr(e))
  2941.  
  2942. except nss.error.NSPRError, e:
  2943.  
  2944. if e.errno == nss.error.PR_CONNECT_RESET_ERROR:
  2945.  
  2946. logging.debug('NSPR error: Connection reset')
  2947.  
  2948. else:
  2949.  
  2950. logging.warning('NSPR error: %s', str(e))
  2951.  
  2952. except EOFError, e:
  2953.  
  2954. if isinstance(child_exception, double_tls.ChildConnectionRefusedError):
  2955.  
  2956. logging.info('Connection to the bridge refused')
  2957.  
  2958. return _CHILD_CONNECTION_REFUSED
  2959.  
  2960. elif isinstance(child_exception, double_tls.ChildUnrecoverableError):
  2961.  
  2962. logging.debug('Unrecoverable error in child')
  2963.  
  2964. return _CHILD_BUG
  2965.  
  2966. else:
  2967.  
  2968. logging.info('Unexpected EOF')
  2969.  
  2970. except (KeyboardInterrupt, SystemExit):
  2971.  
  2972. pass # Don't consider this an unexpected exception
  2973.  
  2974. except (utils.NSSInitError, double_tls.InnerCertificateNotFound), e:
  2975.  
  2976. logging.error(str(e))
  2977.  
  2978. return _CHILD_BUG
  2979.  
  2980. except:
  2981.  
  2982. logging.error('Unexpected exception', exc_info=True)
  2983.  
  2984. return _CHILD_BUG
  2985.  
  2986. logging.debug('Request handling finished')
  2987.  
  2988. return _CHILD_OK
  2989.  
  2990.  
  2991.  
  2992. def main():
  2993.  
  2994. options = utils.get_daemon_options('A signing server',
  2995.  
  2996. '~/.sigul/server.conf')
  2997.  
  2998. logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s',
  2999.  
  3000. level=utils.logging_level_from_options(options),
  3001.  
  3002. filename=os.path.join(options.log_dir,
  3003.  
  3004. 'sigul_server.log'))
  3005.  
  3006. try:
  3007.  
  3008. config = ServerConfiguration(options.config_file)
  3009.  
  3010. except utils.ConfigurationError, e:
  3011.  
  3012. sys.exit(str(e))
  3013.  
  3014.  
  3015.  
  3016. server_common.gpg_modify_environ(config)
  3017.  
  3018.  
  3019.  
  3020. if options.daemonize:
  3021.  
  3022. utils.daemonize()
  3023.  
  3024.  
  3025.  
  3026. signal.signal(signal.SIGTERM, utils.sigterm_handler)
  3027.  
  3028. utils.create_pid_file(options, 'sigul_server')
  3029.  
  3030. try:
  3031.  
  3032. try:
  3033.  
  3034. fast_reconnections_done = 0
  3035.  
  3036. while True:
  3037. print("[parent] loop: forking child [start]...")
  3038.  
  3039. child_pid = os.fork()
  3040.  
  3041. if child_pid == 0:
  3042.  
  3043. try:
  3044. print("[child] request_handling_child [start]...")
  3045.  
  3046. status = request_handling_child(config)
  3047. print("[child] request_handling_child [finished]...")
  3048.  
  3049. logging.shutdown()
  3050.  
  3051. os._exit(status)
  3052.  
  3053. finally:
  3054.  
  3055. try:
  3056.  
  3057. logging.shutdown()
  3058.  
  3059. finally:
  3060.  
  3061. os._exit(_CHILD_BUG)
  3062.  
  3063. print("[parent] waiting for child [start]...")
  3064. (_, status) = os.waitpid(child_pid, 0)
  3065. print("[parent] waiting for child [finished]...")
  3066.  
  3067. if os.WIFEXITED(status) and os.WEXITSTATUS(status) == _CHILD_OK:
  3068.  
  3069. fast_reconnections_done = 0
  3070.  
  3071. elif (os.WIFEXITED(status) and
  3072.  
  3073. os.WEXITSTATUS(status) == _CHILD_CONNECTION_REFUSED):
  3074.  
  3075. if fast_reconnections_done < MAX_FAST_RECONNECTIONS:
  3076.  
  3077. print("[parent] sleep for [%d] seconds..." % (FAST_RECONNECTION_SECONDS))
  3078. time.sleep(FAST_RECONNECTION_SECONDS)
  3079.  
  3080. fast_reconnections_done += 1
  3081.  
  3082. else:
  3083. print("[parent] sleep for [%d] seconds..." % (SLOW_RECONNECTION_SECONDS))
  3084.  
  3085. time.sleep(SLOW_RECONNECTION_SECONDS)
  3086.  
  3087. fast_reconnections_done = 0
  3088.  
  3089. else: # _CHILD_BUG, unknown status code or WIFSIGNALED
  3090.  
  3091. logging.error('Child died with status %d', status)
  3092.  
  3093. break
  3094. print("[parent] loop: forking child [finished]...")
  3095.  
  3096. except (KeyboardInterrupt, SystemExit):
  3097.  
  3098. pass # Silence is golden
  3099.  
  3100. finally:
  3101.  
  3102. utils.delete_pid_file(options, 'sigul_server')
  3103.  
  3104.  
  3105.  
  3106. if __name__ == '__main__':
  3107.  
  3108. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement