12c593315Sopenharmony_ci#!/usr/bin/env python 22c593315Sopenharmony_ci# -*- coding: utf-8 -*- 32c593315Sopenharmony_ci 42c593315Sopenharmony_ci# nghttp2 - HTTP/2 C Library 52c593315Sopenharmony_ci 62c593315Sopenharmony_ci# Copyright (c) 2015 Tatsuhiro Tsujikawa 72c593315Sopenharmony_ci 82c593315Sopenharmony_ci# Permission is hereby granted, free of charge, to any person obtaining 92c593315Sopenharmony_ci# a copy of this software and associated documentation files (the 102c593315Sopenharmony_ci# "Software"), to deal in the Software without restriction, including 112c593315Sopenharmony_ci# without limitation the rights to use, copy, modify, merge, publish, 122c593315Sopenharmony_ci# distribute, sublicense, and/or sell copies of the Software, and to 132c593315Sopenharmony_ci# permit persons to whom the Software is furnished to do so, subject to 142c593315Sopenharmony_ci# the following conditions: 152c593315Sopenharmony_ci 162c593315Sopenharmony_ci# The above copyright notice and this permission notice shall be 172c593315Sopenharmony_ci# included in all copies or substantial portions of the Software. 182c593315Sopenharmony_ci 192c593315Sopenharmony_ci# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 202c593315Sopenharmony_ci# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 212c593315Sopenharmony_ci# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 222c593315Sopenharmony_ci# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 232c593315Sopenharmony_ci# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 242c593315Sopenharmony_ci# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 252c593315Sopenharmony_ci# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 262c593315Sopenharmony_ci 272c593315Sopenharmony_ci# This program was translated from the program originally developed by 282c593315Sopenharmony_ci# h2o project (https://github.com/h2o/h2o), written in Perl. It had 292c593315Sopenharmony_ci# the following copyright notice: 302c593315Sopenharmony_ci 312c593315Sopenharmony_ci# Copyright (c) 2015 DeNA Co., Ltd. 322c593315Sopenharmony_ci# 332c593315Sopenharmony_ci# Permission is hereby granted, free of charge, to any person obtaining a copy 342c593315Sopenharmony_ci# of this software and associated documentation files (the "Software"), to 352c593315Sopenharmony_ci# deal in the Software without restriction, including without limitation the 362c593315Sopenharmony_ci# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 372c593315Sopenharmony_ci# sell copies of the Software, and to permit persons to whom the Software is 382c593315Sopenharmony_ci# furnished to do so, subject to the following conditions: 392c593315Sopenharmony_ci# 402c593315Sopenharmony_ci# The above copyright notice and this permission notice shall be included in 412c593315Sopenharmony_ci# all copies or substantial portions of the Software. 422c593315Sopenharmony_ci# 432c593315Sopenharmony_ci# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 442c593315Sopenharmony_ci# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 452c593315Sopenharmony_ci# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 462c593315Sopenharmony_ci# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 472c593315Sopenharmony_ci# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 482c593315Sopenharmony_ci# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 492c593315Sopenharmony_ci# IN THE SOFTWARE. 502c593315Sopenharmony_ci 512c593315Sopenharmony_cifrom __future__ import unicode_literals 522c593315Sopenharmony_ciimport argparse 532c593315Sopenharmony_ciimport io 542c593315Sopenharmony_ciimport os 552c593315Sopenharmony_ciimport os.path 562c593315Sopenharmony_ciimport re 572c593315Sopenharmony_ciimport shutil 582c593315Sopenharmony_ciimport subprocess 592c593315Sopenharmony_ciimport sys 602c593315Sopenharmony_ciimport tempfile 612c593315Sopenharmony_ci 622c593315Sopenharmony_ci# make this program work for both Python 3 and Python 2. 632c593315Sopenharmony_citry: 642c593315Sopenharmony_ci from urllib.parse import urlparse 652c593315Sopenharmony_ci stdout_bwrite = sys.stdout.buffer.write 662c593315Sopenharmony_ciexcept ImportError: 672c593315Sopenharmony_ci from urlparse import urlparse 682c593315Sopenharmony_ci stdout_bwrite = sys.stdout.write 692c593315Sopenharmony_ci 702c593315Sopenharmony_ci 712c593315Sopenharmony_cidef die(msg): 722c593315Sopenharmony_ci sys.stderr.write(msg) 732c593315Sopenharmony_ci sys.stderr.write('\n') 742c593315Sopenharmony_ci sys.exit(255) 752c593315Sopenharmony_ci 762c593315Sopenharmony_ci 772c593315Sopenharmony_cidef tempfail(msg): 782c593315Sopenharmony_ci sys.stderr.write(msg) 792c593315Sopenharmony_ci sys.stderr.write('\n') 802c593315Sopenharmony_ci sys.exit(os.EX_TEMPFAIL) 812c593315Sopenharmony_ci 822c593315Sopenharmony_ci 832c593315Sopenharmony_cidef run_openssl(args, allow_tempfail=False): 842c593315Sopenharmony_ci buf = io.BytesIO() 852c593315Sopenharmony_ci try: 862c593315Sopenharmony_ci p = subprocess.Popen(args, stdout=subprocess.PIPE) 872c593315Sopenharmony_ci except Exception as e: 882c593315Sopenharmony_ci die('failed to invoke {}:{}'.format(args, e)) 892c593315Sopenharmony_ci try: 902c593315Sopenharmony_ci while True: 912c593315Sopenharmony_ci data = p.stdout.read() 922c593315Sopenharmony_ci if len(data) == 0: 932c593315Sopenharmony_ci break 942c593315Sopenharmony_ci buf.write(data) 952c593315Sopenharmony_ci if p.wait() != 0: 962c593315Sopenharmony_ci raise Exception('nonzero return code {}'.format(p.returncode)) 972c593315Sopenharmony_ci return buf.getvalue() 982c593315Sopenharmony_ci except Exception as e: 992c593315Sopenharmony_ci msg = 'OpenSSL exitted abnormally: {}:{}'.format(args, e) 1002c593315Sopenharmony_ci tempfail(msg) if allow_tempfail else die(msg) 1012c593315Sopenharmony_ci 1022c593315Sopenharmony_ci 1032c593315Sopenharmony_cidef read_file(path): 1042c593315Sopenharmony_ci with open(path, 'rb') as f: 1052c593315Sopenharmony_ci return f.read() 1062c593315Sopenharmony_ci 1072c593315Sopenharmony_ci 1082c593315Sopenharmony_cidef write_file(path, data): 1092c593315Sopenharmony_ci with open(path, 'wb') as f: 1102c593315Sopenharmony_ci f.write(data) 1112c593315Sopenharmony_ci 1122c593315Sopenharmony_ci 1132c593315Sopenharmony_cidef detect_openssl_version(cmd): 1142c593315Sopenharmony_ci return run_openssl([cmd, 'version']).decode('utf-8').strip() 1152c593315Sopenharmony_ci 1162c593315Sopenharmony_ci 1172c593315Sopenharmony_cidef extract_ocsp_uri(cmd, cert_fn): 1182c593315Sopenharmony_ci # obtain ocsp uri 1192c593315Sopenharmony_ci ocsp_uri = run_openssl( 1202c593315Sopenharmony_ci [cmd, 'x509', '-in', cert_fn, '-noout', 1212c593315Sopenharmony_ci '-ocsp_uri']).decode('utf-8').strip() 1222c593315Sopenharmony_ci 1232c593315Sopenharmony_ci if not re.match(r'^https?://', ocsp_uri): 1242c593315Sopenharmony_ci die('failed to extract ocsp URI from {}'.format(cert_fn)) 1252c593315Sopenharmony_ci 1262c593315Sopenharmony_ci return ocsp_uri 1272c593315Sopenharmony_ci 1282c593315Sopenharmony_ci 1292c593315Sopenharmony_cidef save_issuer_certificate(issuer_fn, cert_fn): 1302c593315Sopenharmony_ci # save issuer certificate 1312c593315Sopenharmony_ci chain = read_file(cert_fn).decode('utf-8') 1322c593315Sopenharmony_ci m = re.match( 1332c593315Sopenharmony_ci r'.*?-----END CERTIFICATE-----.*?(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----)', 1342c593315Sopenharmony_ci chain, re.DOTALL) 1352c593315Sopenharmony_ci if not m: 1362c593315Sopenharmony_ci die('--issuer option was not used, and failed to extract issuer certificate from the certificate') 1372c593315Sopenharmony_ci write_file(issuer_fn, (m.group(1) + '\n').encode('utf-8')) 1382c593315Sopenharmony_ci 1392c593315Sopenharmony_ci 1402c593315Sopenharmony_cidef send_and_receive_ocsp(respder_fn, cmd, cert_fn, issuer_fn, ocsp_uri, 1412c593315Sopenharmony_ci ocsp_host, openssl_version): 1422c593315Sopenharmony_ci # obtain response (without verification) 1432c593315Sopenharmony_ci sys.stderr.write('sending OCSP request to {}\n'.format(ocsp_uri)) 1442c593315Sopenharmony_ci args = [ 1452c593315Sopenharmony_ci cmd, 'ocsp', '-issuer', issuer_fn, '-cert', cert_fn, '-url', ocsp_uri, 1462c593315Sopenharmony_ci '-noverify', '-respout', respder_fn 1472c593315Sopenharmony_ci ] 1482c593315Sopenharmony_ci ver = openssl_version.lower() 1492c593315Sopenharmony_ci if ver.startswith('openssl 1.0.') or ver.startswith('libressl '): 1502c593315Sopenharmony_ci args.extend(['-header', 'Host', ocsp_host]) 1512c593315Sopenharmony_ci resp = run_openssl(args, allow_tempfail=True) 1522c593315Sopenharmony_ci return resp.decode('utf-8') 1532c593315Sopenharmony_ci 1542c593315Sopenharmony_ci 1552c593315Sopenharmony_cidef verify_response(cmd, tempdir, issuer_fn, respder_fn): 1562c593315Sopenharmony_ci # verify the response 1572c593315Sopenharmony_ci sys.stderr.write('verifying the response signature\n') 1582c593315Sopenharmony_ci 1592c593315Sopenharmony_ci verify_fn = os.path.join(tempdir, 'verify.out') 1602c593315Sopenharmony_ci 1612c593315Sopenharmony_ci # try from exotic options 1622c593315Sopenharmony_ci allextra = [ 1632c593315Sopenharmony_ci # for comodo 1642c593315Sopenharmony_ci ['-VAfile', issuer_fn], 1652c593315Sopenharmony_ci # these options are only available in OpenSSL >= 1.0.2 1662c593315Sopenharmony_ci ['-partial_chain', '-trusted_first', '-CAfile', issuer_fn], 1672c593315Sopenharmony_ci # for OpenSSL <= 1.0.1 1682c593315Sopenharmony_ci ['-CAfile', issuer_fn], 1692c593315Sopenharmony_ci ] 1702c593315Sopenharmony_ci 1712c593315Sopenharmony_ci for extra in allextra: 1722c593315Sopenharmony_ci with open(verify_fn, 'w+b') as f: 1732c593315Sopenharmony_ci args = [cmd, 'ocsp', '-respin', respder_fn] 1742c593315Sopenharmony_ci args.extend(extra) 1752c593315Sopenharmony_ci p = subprocess.Popen(args, stdout=f, stderr=f) 1762c593315Sopenharmony_ci if p.wait() == 0: 1772c593315Sopenharmony_ci # OpenSSL <= 1.0.1, openssl ocsp still returns exit 1782c593315Sopenharmony_ci # code 0 even if verification was failed. So check 1792c593315Sopenharmony_ci # the error message in stderr output. 1802c593315Sopenharmony_ci f.seek(0) 1812c593315Sopenharmony_ci if f.read().decode('utf-8').find( 1822c593315Sopenharmony_ci 'Response Verify Failure') != -1: 1832c593315Sopenharmony_ci continue 1842c593315Sopenharmony_ci sys.stderr.write('verify OK (used: {})\n'.format(extra)) 1852c593315Sopenharmony_ci return True 1862c593315Sopenharmony_ci 1872c593315Sopenharmony_ci sys.stderr.write(read_file(verify_fn).decode('utf-8')) 1882c593315Sopenharmony_ci return False 1892c593315Sopenharmony_ci 1902c593315Sopenharmony_ci 1912c593315Sopenharmony_cidef fetch_ocsp_response(cmd, cert_fn, tempdir, issuer_fn=None): 1922c593315Sopenharmony_ci openssl_version = detect_openssl_version(cmd) 1932c593315Sopenharmony_ci 1942c593315Sopenharmony_ci sys.stderr.write( 1952c593315Sopenharmony_ci 'fetch-ocsp-response (using {})\n'.format(openssl_version)) 1962c593315Sopenharmony_ci 1972c593315Sopenharmony_ci ocsp_uri = extract_ocsp_uri(cmd, cert_fn) 1982c593315Sopenharmony_ci ocsp_host = urlparse(ocsp_uri).netloc 1992c593315Sopenharmony_ci 2002c593315Sopenharmony_ci if not issuer_fn: 2012c593315Sopenharmony_ci issuer_fn = os.path.join(tempdir, 'issuer.crt') 2022c593315Sopenharmony_ci save_issuer_certificate(issuer_fn, cert_fn) 2032c593315Sopenharmony_ci 2042c593315Sopenharmony_ci respder_fn = os.path.join(tempdir, 'resp.der') 2052c593315Sopenharmony_ci resp = send_and_receive_ocsp( 2062c593315Sopenharmony_ci respder_fn, cmd, cert_fn, issuer_fn, ocsp_uri, ocsp_host, 2072c593315Sopenharmony_ci openssl_version) 2082c593315Sopenharmony_ci 2092c593315Sopenharmony_ci sys.stderr.write('{}\n'.format(resp)) 2102c593315Sopenharmony_ci 2112c593315Sopenharmony_ci # OpenSSL 1.0.2 still returns exit code 0 even if ocsp responder 2122c593315Sopenharmony_ci # returned error status (e.g., trylater(3)) 2132c593315Sopenharmony_ci if resp.find('Responder Error:') != -1: 2142c593315Sopenharmony_ci raise Exception('responder returned error') 2152c593315Sopenharmony_ci 2162c593315Sopenharmony_ci if not verify_response(cmd, tempdir, issuer_fn, respder_fn): 2172c593315Sopenharmony_ci tempfail('failed to verify the response') 2182c593315Sopenharmony_ci 2192c593315Sopenharmony_ci # success 2202c593315Sopenharmony_ci res = read_file(respder_fn) 2212c593315Sopenharmony_ci stdout_bwrite(res) 2222c593315Sopenharmony_ci 2232c593315Sopenharmony_ci 2242c593315Sopenharmony_ciif __name__ == '__main__': 2252c593315Sopenharmony_ci parser = argparse.ArgumentParser( 2262c593315Sopenharmony_ci description= 2272c593315Sopenharmony_ci '''The command issues an OCSP request for given server certificate, verifies the response and prints the resulting DER.''', 2282c593315Sopenharmony_ci epilog= 2292c593315Sopenharmony_ci '''The command exits 0 if successful, or 75 (EX_TEMPFAIL) on temporary error. Other exit codes may be returned in case of hard errors.''') 2302c593315Sopenharmony_ci parser.add_argument( 2312c593315Sopenharmony_ci '--issuer', 2322c593315Sopenharmony_ci metavar='FILE', 2332c593315Sopenharmony_ci help= 2342c593315Sopenharmony_ci 'issuer certificate (if omitted, is extracted from the certificate chain)') 2352c593315Sopenharmony_ci parser.add_argument('--openssl', 2362c593315Sopenharmony_ci metavar='CMD', 2372c593315Sopenharmony_ci help='openssl command to use (default: "openssl")', 2382c593315Sopenharmony_ci default='openssl') 2392c593315Sopenharmony_ci parser.add_argument('certificate', 2402c593315Sopenharmony_ci help='path to certificate file to validate') 2412c593315Sopenharmony_ci args = parser.parse_args() 2422c593315Sopenharmony_ci 2432c593315Sopenharmony_ci tempdir = None 2442c593315Sopenharmony_ci try: 2452c593315Sopenharmony_ci # Python3.2 has tempfile.TemporaryDirectory, which has nice 2462c593315Sopenharmony_ci # feature to delete its tree by cleanup() function. We have 2472c593315Sopenharmony_ci # to support Python2.7, so we have to do this manually. 2482c593315Sopenharmony_ci tempdir = tempfile.mkdtemp() 2492c593315Sopenharmony_ci fetch_ocsp_response(args.openssl, args.certificate, tempdir, 2502c593315Sopenharmony_ci args.issuer) 2512c593315Sopenharmony_ci finally: 2522c593315Sopenharmony_ci if tempdir: 2532c593315Sopenharmony_ci shutil.rmtree(tempdir) 254