app.py 68 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461
  1. """
  2. StoneVPN - Easy OpenVPN certificate and configuration management
  3. (C) 2009-2012 by L.S. Keijser, <keijser@stone-it.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15. """
  16. import commands
  17. import cStringIO
  18. import fileinput
  19. import getpass
  20. import glob
  21. import operator
  22. import os
  23. import random
  24. import re
  25. import shutil
  26. import smtplib
  27. import string
  28. import sys
  29. import time
  30. import zipfile
  31. from OpenSSL import SSL, crypto
  32. from optparse import OptionParser, OptionGroup
  33. from configobj import ConfigObj
  34. from time import strftime
  35. from datetime import date, datetime, timedelta
  36. from IPy import IP
  37. from string import atoi
  38. from datetime import datetime
  39. from email.MIMEMultipart import MIMEMultipart
  40. from email.MIMEBase import MIMEBase
  41. from email.MIMEText import MIMEText
  42. from email.Utils import formatdate
  43. from email import Encoders
  44. from socket import gethostname
  45. from StoneVPN import STONEVPN_VERSION
  46. def main():
  47. stonevpnconf = '/etc/stonevpn.conf'
  48. stonevpnver = STONEVPN_VERSION
  49. # Read main configuration from stonevpn.conf
  50. if os.path.exists(stonevpnconf):
  51. config = ConfigObj(stonevpnconf)
  52. sectionname = 'stonevpn conf'
  53. section=config[sectionname]
  54. crlfile = section['crlfile']
  55. prefix = section['prefix']
  56. pushrouter = section['pushrouter']
  57. cacertfile = section['cacertfile']
  58. cakeyfile = section['cakeyfile']
  59. openvpnconf = section['openvpnconf']
  60. ccddir = section['ccddir']
  61. working = section['working']
  62. opensslconf = section['opensslconf']
  63. ciphermethod = section['cipher']
  64. mail_server = section['mail_server']
  65. mail_cc = section['mail_cc']
  66. mail_msg = section['mail_msg']
  67. mail_from = section['mail_from']
  68. try:
  69. mail_passtxt = section['mail_passtxt']
  70. except:
  71. print "Missing variable 'mail_passtxt' in %s! Please update your configuration.\nHint: look at the example configuration file." % stonevpnconf
  72. sys.exit()
  73. else:
  74. print "File " + stonevpnconf + " does not exist!"
  75. sys.exit()
  76. # retrieve default expiration date from openssl.cnf, needed for optionparse
  77. if os.path.exists(opensslconf):
  78. config = ConfigObj(opensslconf)
  79. sectionname = 'CA_default'
  80. section=config[sectionname]
  81. defaultDays = section['default_days']
  82. else:
  83. print "Error: OpenSSL configuration file not found at %s" % opensslconf
  84. sys.exit()
  85. # define some crypto stuff
  86. TYPE_RSA = crypto.TYPE_RSA
  87. TYPE_DSA = crypto.TYPE_DSA
  88. FILETYPE = crypto.FILETYPE_PEM
  89. # command line options
  90. parser = OptionParser(usage="%prog -f <filename> -n <commonname> [ OPTIONS ]",version="%prog " + stonevpnver)
  91. # define groups
  92. group_crl = OptionGroup(parser, "Certificate revocation options")
  93. group_general = OptionGroup(parser, "General options",
  94. "All general options are mandatory")
  95. group_extra = OptionGroup(parser, "Extra options",
  96. "To be used in conjunction with the general options.")
  97. group_info = OptionGroup(parser, "Information/printing options")
  98. group_ca = OptionGroup(parser, "CA certificate options")
  99. group_test = OptionGroup(parser, "Test/experimental options",
  100. "Caution: use these options with care.")
  101. # define special case for action with optional argument
  102. def optional_arg(arg_default):
  103. def check_value(option,opt_str,value,parser):
  104. # check for remaining args. these shouldn't start with a '-'
  105. if parser.rargs and not parser.rargs[0].startswith('-'):
  106. val=parser.rargs[0]
  107. parser.rargs.pop(0)
  108. else:
  109. # return the default value for the argument
  110. val=arg_default
  111. # remove the argument from the list and return the remaining args back to parser
  112. setattr(parser.values,option.dest,val)
  113. return check_value
  114. # populate groups
  115. parser.add_option("-D", "--debug",
  116. action="count",
  117. dest="debug",
  118. help="enable debugging output")
  119. group_general.add_option("-n", "--name",
  120. action="store",
  121. type="string",
  122. dest="cname",
  123. help="Common Name, use quotes eg.: \"CNAME\" and only alphanumeric characters")
  124. group_general.add_option("-f", "--file",
  125. dest="fname",
  126. help="write to file FNAME (no extension!)")
  127. group_general.add_option("-o", "--config",
  128. action="store",
  129. dest="confs",
  130. default="windows",
  131. help="create config files for [windows|unix|mac|android|all]")
  132. group_extra.add_option("-e", "--prefix",
  133. action="store",
  134. dest="fprefix",
  135. default=prefix,
  136. help="prefix (almost all) generated files. Default = " + str(prefix))
  137. group_extra.add_option("-z", "--zip",
  138. action="store_true",
  139. dest="zip",
  140. help="add all generated files to a ZIP-file")
  141. group_extra.add_option("-m", "--mail",
  142. action="store",
  143. type="string",
  144. dest="emailaddress",
  145. help="send all generated files to EMAILADDRESS")
  146. group_extra.add_option("-i", "--free-ip",
  147. action="store_true",
  148. dest="freeip",
  149. help="locate and assign free ip")
  150. group_extra.add_option("-E", "--extrafile",
  151. action="append",
  152. dest="extrafile",
  153. help="include extra file(s) like documentation. Can be used multiple times")
  154. group_extra.add_option("-p", "--passphrase",
  155. action="callback",
  156. callback=optional_arg('please_prompt_me'),
  157. dest="passphrase",
  158. help="prompt for a passphrase when generating private key, or supply one on the commandline")
  159. group_extra.add_option("-M", "--mailpass",
  160. action="store_true",
  161. dest="mailpass",
  162. help="include passphrase in e-mail body (only useful with the '-m' option)")
  163. group_extra.add_option("-R", "--randpass",
  164. action="store",
  165. type="string",
  166. dest="randpass",
  167. help="generate a random password of RANDPASS characters (eg.: -R 8)")
  168. group_extra.add_option("-S", "--serverip",
  169. action="store",
  170. type="string",
  171. dest="server_ip",
  172. help="use this IP address for the server when generating the configuration file, overriding the one specified in stonevpn.conf")
  173. group_crl.add_option("-r", "--revoke",
  174. action="store",
  175. dest="serial",
  176. help="revoke certificate with serial SERIAL")
  177. group_extra.add_option("-u", "--route",
  178. action="append",
  179. dest="route",
  180. help="push extra route(s) to client. Specify multiple routes as: -u 192.168.1.1/32 -u 10.1.4.0/24")
  181. group_crl.add_option("-l", "--listrevoked",
  182. action="store_true",
  183. dest="listrevoked",
  184. help="list revoked certificates")
  185. group_crl.add_option("-C", "--crl",
  186. action="store_true",
  187. dest="displaycrl",
  188. help="display CRL file contents")
  189. group_info.add_option("-a", "--listall",
  190. action="store_true",
  191. dest="listall",
  192. help="list all certificates")
  193. group_info.add_option("--listcsv",
  194. action="store_true",
  195. dest="listallcsv",
  196. help="list all certificates (output as CSV)")
  197. group_info.add_option("-s", "--showserial",
  198. action="store_true",
  199. dest="showserial",
  200. help="display current SSL serial number")
  201. group_info.add_option("-c", "--printcert",
  202. action="store",
  203. dest="printcert",
  204. help="prints information about a certficiate file")
  205. group_info.add_option("-d", "--printindex",
  206. action="store_true",
  207. dest="printindex",
  208. help="prints index file")
  209. group_extra.add_option("-x", "--expire",
  210. action="store",
  211. dest="expiredate",
  212. help="certificate expires in EXPIREDATE h(ours), d(ays) or y(ears). The default is " + str(defaultDays) + " days. Example usage: -x 2h")
  213. group_crl.add_option("-N", "--newcrl",
  214. action="store_true",
  215. dest="emptycrl",
  216. help="create an empty CRL file (or overwrite an existing one)")
  217. group_ca.add_option("-A", "--newca",
  218. action="store",
  219. dest="newca",
  220. help="create a new self-signed CA certificate that's valid for NEWCA years")
  221. group_test.add_option("-t", "--test",
  222. action="store_true",
  223. dest="test",
  224. help="Danger, Will Robinson, Danger! test parameter - can do anything! Review source before executing!")
  225. # add optiongroups
  226. parser.add_option_group(group_general)
  227. parser.add_option_group(group_extra)
  228. parser.add_option_group(group_info)
  229. parser.add_option_group(group_crl)
  230. parser.add_option_group(group_ca)
  231. parser.add_option_group(group_test)
  232. # parse cmd line options
  233. (options, args) = parser.parse_args()
  234. s = StoneVPN()
  235. # values we got from optparse:
  236. s.debug = options.debug
  237. s.cname = options.cname
  238. s.fname = options.fname
  239. s.confs = options.confs
  240. s.fprefix = options.fprefix
  241. s.zip = options.zip
  242. s.emailaddress = options.emailaddress
  243. s.freeip = options.freeip
  244. s.passphrase = options.passphrase
  245. s.mailpass = options.mailpass
  246. s.randpass = options.randpass
  247. s.extrafile = options.extrafile
  248. s.server_ip = options.server_ip
  249. s.serial = options.serial
  250. s.route = options.route
  251. s.listrevoked = options.listrevoked
  252. s.displaycrl = options.displaycrl
  253. s.listall = options.listall
  254. s.listallcsv = options.listallcsv
  255. s.showserial = options.showserial
  256. s.printcert = options.printcert
  257. s.printindex = options.printindex
  258. s.expiredate = options.expiredate
  259. s.emptycrl = options.emptycrl
  260. s.newca = options.newca
  261. s.test = options.test
  262. # values we got from parsing the configuration file:
  263. s.cacertfile = cacertfile
  264. s.cakeyfile = cakeyfile
  265. s.openvpnconf = openvpnconf
  266. s.ccddir = ccddir
  267. s.working = working
  268. s.opensslconf = opensslconf
  269. s.pushrouter = pushrouter
  270. s.ciphermethod = ciphermethod
  271. s.prefix = prefix
  272. s.crlfile = crlfile
  273. s.mail_server = mail_server
  274. s.mail_cc = mail_cc
  275. s.mail_msg = mail_msg
  276. s.mail_from = mail_from
  277. s.mail_passtxt = mail_passtxt
  278. s.stonevpnconf = stonevpnconf
  279. # and all other variables
  280. s.TYPE_RSA = TYPE_RSA
  281. s.TYPE_DSA = TYPE_DSA
  282. s.FILETYPE = FILETYPE
  283. s.stonevpnver = stonevpnver
  284. # check for all args
  285. if len(sys.argv[1:]) == 0:
  286. parser.print_help()
  287. # check for valid args
  288. if options.fname is None and options.serial is not None and options.listrevoked is not None and options.listall is not None and options.listallcsv is not None and options.showserial is not None and options.printcert is not None and options.printindex is not None and options.emptycrl is not None and options.test is not None and options.newca is not None:
  289. parser.error("Error: you have to specify a filename (FNAME)")
  290. else:
  291. # must..have..root..
  292. myId = commands.getstatusoutput('id -u')[1]
  293. if not myId == '0':
  294. print "Sorry, root privileges required for this action."
  295. sys.exit(0)
  296. else:
  297. s.run()
  298. class StoneVPN:
  299. def __init__(self):
  300. """
  301. Constructor. Arguments will be filled in by optparse..
  302. """
  303. self.cname = None
  304. self.fname = None
  305. self.confs = None
  306. self.fprefix = None
  307. self.zip = None
  308. self.emailaddress = None
  309. self.freeip = None
  310. self.passphrase = None
  311. self.mailpass = None
  312. self.randpass = None
  313. self.extrafile = None
  314. self.server_ip = None
  315. self.serial = None
  316. self.route = None
  317. self.listrevoked = None
  318. self.displaycrl = None
  319. self.listall = None
  320. self.listallcsv = None
  321. self.showserial = None
  322. self.printcert = None
  323. self.printindex = None
  324. self.expiredate = None
  325. self.emptycrl = None
  326. self.newca = None
  327. self.test = None
  328. # Read certain vars from OpenSSL config file
  329. def readOpenSSLConf(self):
  330. if self.debug: print "DEBUG: parsing OpenSSL configuration file %s" % self.opensslconf
  331. config = ConfigObj(self.opensslconf)
  332. sectionname = 'req_distinguished_name'
  333. section=config[sectionname]
  334. # make these variables also global
  335. global countryName, stateOrProvinceName, localityName, organizationName, organizationalUnitName, defaultDays, prefixdir, indexdb, serialfile, default_bits, default_md
  336. # Check if certain sections in OpenSSL configfile are present, report if they're not
  337. try:
  338. countryName = section['countryName_default']
  339. if len(countryName) is 0:
  340. print "Error: countryName_default is empty. Please edit %s first." % self.opensslconf
  341. sys.exit()
  342. except KeyError:
  343. print "Error: missing section 'countryName_default' in " + self.opensslconf
  344. sys.exit()
  345. try:
  346. stateOrProvinceName = section['stateOrProvinceName_default']
  347. if len(stateOrProvinceName) is 0:
  348. print "Error: stateOrProvinceName_default is empty. Please edit %s first." % self.opensslconf
  349. sys.exit()
  350. except KeyError:
  351. print "Error: missing section 'stateOrProvinceName_default' in " + self.opensslconf
  352. sys.exit()
  353. try:
  354. localityName = section['localityName_default']
  355. if len(localityName) is 0:
  356. print "Error: localityName_default is empty. Please edit %s first." % self.opensslconf
  357. sys.exit()
  358. except KeyError:
  359. print "Error: missing section 'localityName_default' in " + self.opensslconf
  360. sys.exit()
  361. try:
  362. organizationName = section['0.organizationName_default']
  363. if len(organizationName) is 0:
  364. print "Error: 0.organizationName_default is empty. Please edit %s first." % self.opensslconf
  365. sys.exit()
  366. except KeyError:
  367. print "Error: missing section '0.organizationName_default' in " + self.opensslconf
  368. sys.exit()
  369. try:
  370. organizationalUnitName = section['organizationalUnitName_default']
  371. if len(organizationalUnitName) is 0:
  372. print "Error: organizationalUnitName_default is empty. Please edit %s first." % self.opensslconf
  373. sys.exit()
  374. except KeyError:
  375. print "Error: missing section 'organizationalUnitName_default' in " + self.opensslconf
  376. sys.exit()
  377. sectionname = 'CA_default'
  378. section=config[sectionname]
  379. defaultDays = section['default_days']
  380. prefixdir = section['dir']
  381. indexdb = section['database'].replace('$dir', prefixdir)
  382. serialfile = section['serial'].replace('$dir', prefixdir)
  383. sectionname = 'req'
  384. section=config[sectionname]
  385. default_bits = section['default_bits']
  386. try:
  387. default_md = section['default_md']
  388. except KeyError:
  389. print "Warning: your OpenSSL configuration file misses key 'default_md'. Please add it. For now we'll assume SHA1'"
  390. default_md = 'sha1'
  391. def run(self):
  392. """
  393. StoneVPN's main function
  394. """
  395. if os.path.exists(self.opensslconf):
  396. self.readOpenSSLConf()
  397. else:
  398. print "File " + self.opensslconf + " does not exist!"
  399. sys.exit()
  400. # Check for presence of OpenSSL index file
  401. if not os.path.exists(indexdb):
  402. print "Error: indexfile not found at: " + indexdb + " or insufficient rights."
  403. sys.exit()
  404. # Check for presence of OpenSSL serial file
  405. if not os.path.exists(serialfile):
  406. print "Error: serialfile not found at: " + serialfile + " or insufficient rights."
  407. sys.exit()
  408. # Make sure FPREFIX ends with a dash
  409. if not self.fprefix == '':
  410. if not self.fprefix[-1] == '-':
  411. self.fprefix = str(self.fprefix) + '-'
  412. # check if working dir exists, create it if it doesn't
  413. if not os.path.exists(self.working):
  414. print "Working dir didn't exist, making ..."
  415. os.mkdir(self.working)
  416. # Make certificates
  417. if self.cname:
  418. if self.fname is None:
  419. print "Error: required option -f/--file is missing."
  420. sys.exit()
  421. print "Creating " + self.fname + ".key and " + self.fname + ".crt for " + self.cname
  422. self.makeCert( self.fname, self.cname )
  423. # check for extra files to be included
  424. if self.extrafile:
  425. if self.fname is None or self.cname is None:
  426. print "Error: required option -f/--file and/or -n/--name is missing."
  427. sys.exit()
  428. for efile in self.extrafile:
  429. if os.path.exists(efile):
  430. # copy them to a temp subdir within the working dir to avoid duplicates
  431. try:
  432. os.mkdir(self.working + '/' + self.fname + '-extrafiles')
  433. except:
  434. pass
  435. print "Adding extra file %s" % efile
  436. shutil.copy(efile, self.working + '/' + self.fname + '-extrafiles/')
  437. else:
  438. # exit if the file wasn't found
  439. print "Error: file %s was not found."
  440. sys.exit()
  441. # Make nice zipfile from all the generated files
  442. # :: called only when option '-z' is used ::
  443. if self.zip:
  444. if self.fname is None or self.cname is None:
  445. print "Error: required option -f/--file and/or -n/--name is missing."
  446. sys.exit()
  447. print "Adding all files to " + self.working + "/" + self.fprefix + self.fname + ".zip"
  448. z = zipfile.ZipFile(self.working + "/" + self.fprefix + self.fname + ".zip", "w")
  449. for name in glob.glob(self.working + "/" + self.fprefix + self.fname + ".*"):
  450. # only add the files that begin with the name specified with the -f option, don't add the zipfile itself (duh)
  451. if not name == self.working + "/" + self.fprefix + self.fname + ".zip":
  452. if self.debug: print "DEBUG: adding %s to %s" % (name.split('/')[-1],self.fprefix + self.fname + '.zip')
  453. z.write(name, os.path.basename(name), zipfile.ZIP_DEFLATED)
  454. # and add the CA certificate file
  455. z.write(self.cacertfile, os.path.basename(self.cacertfile), zipfile.ZIP_DEFLATED)
  456. z.write("/etc/openvpn/keys/ta.key", "ta.key", zipfile.ZIP_DEFLATED)
  457. # check if extra files should be included as well
  458. if self.extrafile:
  459. for efile in self.extrafile:
  460. z.write(efile, os.path.basename(efile), zipfile.ZIP_DEFLATED)
  461. # we can safely remove all files in the temp dir now since it was only used when not creating a zip file
  462. for file in glob.glob(self.working + "/" + self.fname + "-extrafiles/*"):
  463. os.remove(file)
  464. # finally remove the temp dir itself
  465. os.rmdir(self.working + "/" + self.fname + "-extrafiles")
  466. z.close()
  467. # delete all the files generated, except the ZIP-file
  468. for file in glob.glob(self.working + "/" + self.fprefix + self.fname + ".*"):
  469. if not file == self.working + "/" + self.fprefix + self.fname + ".zip": os.remove(file)
  470. # Find free IP-address by parsing config files (usually in /etc/openvpn/ccd/*)
  471. # :: called only when option '-i' is used ::
  472. if self.freeip:
  473. if self.fname is None:
  474. print "Error: required option -f/--file is missing."
  475. sys.exit()
  476. print "Searching for free IP-address:"
  477. # see if vpn server conf file exists
  478. if not os.path.exists(self.openvpnconf):
  479. print "Error: OpenVPN server configuration file was not found at %s" % self.openvpnconf
  480. sys.exit()
  481. # parse config file in search for ifconfig-pool
  482. for line in fileinput.input(self.openvpnconf):
  483. if len(line.split()) > 0 and line.split()[0] == 'ifconfig-pool':
  484. pool_from = line.split()[1]
  485. pool_to = line.split()[2]
  486. print "Pool runs from " + pool_from + " to " + pool_to
  487. # from here on we have to do some magic to get a list of
  488. # valid IP's in the specified pool
  489. # we first check if the first 3 octets in both 'from' and 'to'
  490. # are the same.
  491. r = re.compile('(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})')
  492. mFrom = r.match(pool_from)
  493. mTo = r.match(pool_to)
  494. if self.debug:
  495. print "DEBUG: first 3 octets of ip_from are %s.%s.%s" % (mFrom.group(1),mFrom.group(2),mFrom.group(3))
  496. print "DEBUG: first 3 octets of ip_to are %s.%s.%s" % (mTo.group(1),mTo.group(2),mTo.group(3))
  497. from_3octs = str(mFrom.group(1)) + '.' + str(mFrom.group(2)) + '.' + str(mFrom.group(3))
  498. to_3octs = str(mTo.group(1)) + '.' + str(mTo.group(2)) + '.' + str(mTo.group(3))
  499. if from_3octs == to_3octs:
  500. # ip's in pool are in a /24 (or higher, thus less addresses) subnet
  501. # create a range of valid ip's addresses and put them in a list
  502. if self.debug: print "DEBUG: both ip_from and ip_to are in the same subnet\nDEBUG: calculating range the 'easy' way.."
  503. range_4oct = range(int(mFrom.group(4)),int(mTo.group(4)))
  504. # fill range with ip's
  505. rangeIP = []
  506. for octet in range_4oct:
  507. rangeIP.append(from_3octs + "." + str(octet))
  508. else:
  509. rangeIP = []
  510. # ip's in pool are not in a /24 subnet
  511. # we'll have to manually (well, kind of) calculate the range.
  512. range_3oct = range(int(mFrom.group(3)),int(mTo.group(3)))
  513. # append last octet since range() doesn't do that
  514. range_3oct.append(int(mTo.group(3)))
  515. if self.debug: print "DEBUG: range_3oct is %s" % range_3oct
  516. # the first element in the range is the starting octet and thus
  517. # we should look at the 4th octet now to determine the starting point
  518. for octet in range(int(mFrom.group(4)),255):
  519. # fill rangeIP with valid ip's
  520. rangeIP.append(from_3octs + "." + str(octet))
  521. # now remove the first octet from the range
  522. if self.debug: print "DEBUG: remove %s from %s" % (mFrom.group(3),range_3oct)
  523. range_3oct.remove(int(mFrom.group(3)))
  524. if self.debug: print "DEBUG: range_3oct is now %s" % range_3oct
  525. # now iterate over the rest, until we get to the last (3rd) octet
  526. for octet_3 in range_3oct:
  527. if int(octet_3) != int(mTo.group(3)):
  528. for octet in range(1,255):
  529. rangeIP.append(mFrom.group(1) + '.' + mFrom.group(2) + '.' + str(octet_3) + '.' + str(octet))
  530. else:
  531. # this is the last (3rd) octet so only fill the list until the 4th octet of pool_to
  532. for octet in range(1,int(mTo.group(4))):
  533. rangeIP.append(mFrom.group(1) + '.' + mFrom.group(2) + '.' + str(mTo.group(3)) + '.' + str(octet))
  534. if self.debug: print "DEBUG: rangeIP is %s" % rangeIP
  535. # define list of IP-addresses
  536. ipList = []
  537. for x in rangeIP:
  538. ipList.append(x)
  539. # go through the individual config files to find IP-addresses
  540. for file in glob.glob(self.ccddir+"/*"):
  541. if self.debug: print "DEBUG: parsing file: " + file
  542. for line in fileinput.input(file):
  543. # search for line that starts with 'ifconfig-push'
  544. if line.split()[0] == 'ifconfig-push':
  545. # the client IP is the 2nd argument ([2] is 0,1,2nd object on the line)
  546. clientip = line.split()[2]
  547. # remove IP from range if it exists in the list
  548. if clientip in ipList:
  549. ipList.remove(clientip)
  550. # the server IP is the 1st argument
  551. servip = line.split()[1]
  552. # remove IP from range if it exists in the list
  553. if servip in ipList:
  554. ipList.remove(servip)
  555. # sort list
  556. ipList.sort()
  557. # we now have a list of usable IP addresses :)
  558. # find 2 free IP-addresses:
  559. try:
  560. firstFree = ipList[0]
  561. except IndexError:
  562. print "Error: no free IP address left in pool!"
  563. sys.exit()
  564. try:
  565. secondFree = ipList[1]
  566. except IndexError:
  567. print "Error: no free IP address left in pool!"
  568. sys.exit()
  569. print "First free address: %s (local)" % firstFree
  570. print "Second free address: %s (peer)" % secondFree
  571. # check if ccd dir exists:
  572. if not os.path.exists(self.ccddir):
  573. print "Client configuration directory didn't exist, making ..."
  574. os.mkdir(self.ccddir)
  575. # And create the configuration file for these addresses
  576. nospaces_cname = self.cname.replace(' ', '_')
  577. f=open(self.ccddir + '/' + nospaces_cname, 'w')
  578. f.write('ifconfig-push ' + str(secondFree) + ' ' + str(firstFree) + '\n')
  579. f.write('push "route ' + self.pushrouter + ' 255.255.255.255"\n')
  580. f.close()
  581. print "CCD file written to: %s\nPlease review or make additional changes." % (self.ccddir + '/' + nospaces_cname)
  582. if self.listall:
  583. self.listAllCerts()
  584. if self.listallcsv:
  585. self.listAllCertsCSV()
  586. if self.displaycrl:
  587. self.displayCRL()
  588. if self.listrevoked:
  589. self.listRevokedCerts()
  590. if self.serial:
  591. self.revokeCert(str(self.serial))
  592. if self.showserial:
  593. print "Current SSL serial number (in hex): " + self.readSerial()
  594. if self.printindex:
  595. print "Current index file (" + indexdb + "):"
  596. self.printIndexDB()
  597. if self.printcert:
  598. self.print_cert ( self.printcert )
  599. if self.emailaddress:
  600. if self.fname is None or self.cname is None:
  601. print "Error: required option -f/--file and/or -n/--name is missing."
  602. sys.exit()
  603. mail_attachment = []
  604. mail_to = self.emailaddress
  605. # First check if we've generated a ZIP file (include just one attachment) or not (include all generated files)
  606. if self.zip:
  607. mail_attachment.append(self.working + '/' + self.fprefix + self.fname + '.zip')
  608. self.send_mail(self.mail_from, mail_to, 'StoneVPN: generated files for ' + str(self.cname), self.mail_msg, mail_attachment)
  609. else:
  610. # Generate a list of filenames to include as attachments
  611. for name in glob.glob(self.working + "/" + self.fprefix + self.fname + ".*"):
  612. mail_attachment.append(name)
  613. # Also include the CA certificate
  614. mail_attachment.append(self.cacertfile)
  615. # And check for extra files to be included
  616. if self.extrafile:
  617. for efile in self.extrafile:
  618. mail_attachment.append(efile)
  619. # Finally, send the mail
  620. self.send_mail(self.mail_from, mail_to, 'StoneVPN: generated files for ' + str(self.cname), self.mail_msg, mail_attachment)
  621. if self.route:
  622. if self.cname is None:
  623. print "Error: required option -n/--name is missing."
  624. sys.exit()
  625. IP.check_addr_prefixlen = False
  626. nospaces_cname = self.cname.replace(' ', '_')
  627. clientfile = self.ccddir + "/" + nospaces_cname
  628. # nowwhat : 0=continue normally (append), 1=don't write clientfile, 2=overwrite)
  629. # setting to 'append' by default
  630. nowwhat=0
  631. if not self.freeip:
  632. if os.path.exists(clientfile):
  633. overwrite=raw_input("Existing client configuration file was found. Do you want to (o)verwrite, (A)ppend or (s)kip): ")
  634. if overwrite in ('o', 'O'):
  635. os.remove(clientfile)
  636. nowwhat=2
  637. elif overwrite in ('s', 'S'):
  638. nowwhat=1
  639. if self.debug: print "DEBUG: adding %s routes" % len(self.route)
  640. for newroute in self.route:
  641. try:
  642. ip=IP(newroute)
  643. except ValueError:
  644. print "Error: invalid prefix length given."
  645. sys.exit()
  646. ip.NoPrefixForSingleIp = None
  647. ip.WantPrefixLen = 2
  648. if self.debug: print "DEBUG: ip: %s" % ip
  649. # check if supplied argument is an IPv4 address
  650. if IP(ip).version() != 4:
  651. print "Error: only IPv4 addresses are supported."
  652. sys.exit()
  653. route = str(ip).split('/')
  654. if self.debug:
  655. if len(route) == 1:
  656. print "DEBUG: only IP given, assume /32 netmask"
  657. # check if ccd dir exists:
  658. if not os.path.exists(self.ccddir):
  659. print "Client configuration directory didn't exist, making ..."
  660. os.mkdir(self.ccddir)
  661. f=open(self.ccddir + '/' + nospaces_cname, 'a')
  662. if self.debug: print "DEBUG: route: %s" % route
  663. if nowwhat == 1:
  664. if self.debug: print "DEBUG: not writing route to client configfile!"
  665. # only write routes if we didn't skip overwriting/appending earlier
  666. if nowwhat != 1:
  667. print "Adding route %s / %s" % (route[0],route[1])
  668. f.write("push \"route " + route[0] + " " + route[1] + "\"\n")
  669. f.close()
  670. if nowwhat != 1:
  671. print "Wrote extra route(s) to " + self.ccddir + "/" + nospaces_cname
  672. if self.emptycrl:
  673. try:
  674. crl = crypto.CRL()
  675. except:
  676. print "\nError: CRL support is not available in your version of"
  677. print "pyOpenSSL. Please check the README file that came with"
  678. print "StoneVPN to see what you can do about this. For now, "
  679. print "you will have to revoke certificates manually.\n"
  680. sys.exit()
  681. if os.path.exists(self.crlfile):
  682. overwrite=raw_input("Existing crlfile was found. Do you want to overwrite (y/N): ")
  683. if overwrite not in ('y', 'Y'):
  684. print "Doing nothing.."
  685. sys.exit()
  686. print "Creating empty CRL file at %s" % self.crlfile
  687. cacert = self.load_cert(self.cacertfile)
  688. cakey = self.load_key(self.cakeyfile)
  689. newCRL = crl.export(cacert, cakey, days=3650)
  690. f=open(self.crlfile, 'w')
  691. f.write(newCRL)
  692. f.close()
  693. if self.newca:
  694. self.createSelfSignedCertificate(self.newca)
  695. if self.test:
  696. print "Testing 1, 2, 5 ... three Sir!"
  697. sys.exit()
  698. # Create key
  699. def createKeyPair(self, type, bits):
  700. pkey = crypto.PKey()
  701. pkey.generate_key(type, bits)
  702. return pkey
  703. # Create request
  704. def createCertRequest(self, pkey, digest='sha256', **name):
  705. req = crypto.X509Req()
  706. subj = req.get_subject()
  707. for (key,value) in name.items():
  708. setattr(subj, key, value)
  709. req.set_pubkey(pkey)
  710. req.sign(pkey, default_md)
  711. return req
  712. # decimal 2 hexidecimal and vice versa
  713. def dec2hex(self, n):
  714. return "%X" % n
  715. def hex2dec(self, s):
  716. return int(s, 16)
  717. def printIndexDB(self):
  718. f=open(indexdb, 'r')
  719. for line in f:
  720. print line
  721. f.close()
  722. def readSerial(self):
  723. f=open(serialfile, 'r')
  724. serial = f.readline()
  725. f.close()
  726. # see if we got more than just a newline. If not, return 0
  727. if not serial.strip():
  728. serial = "0"
  729. return serial
  730. def writeSerial(self, serial):
  731. f=open(serialfile, 'w')
  732. f.write(serial)
  733. f.close()
  734. def writeIndex(self, index):
  735. f=open(indexdb, 'a')
  736. f.write(index)
  737. f.close()
  738. # Create certificate
  739. def createCertificate(self, req, (issuerCert, issuerKey), serial, (notBefore, notAfter), digest='sha256'):
  740. extensions = []
  741. # Create the X509 Extensions
  742. extensions.append(crypto.X509Extension('basicConstraints',1, 'CA:FALSE'))
  743. try:
  744. extensions.append(crypto.X509Extension('nsComment',0, 'Created with stonevpn ' + str(self.stonevpnver)))
  745. except ValueError:
  746. print "\n=================================================================="
  747. print "Warning: your version of pyOpenSSL doesn't support X509Extensions."
  748. print "Please consult the README file that came with StoneVPN in order to"
  749. print "fix this. This is not trivial. The certificate will be generated."
  750. print "==================================================================\n"
  751. # We're creating a X509 certificate version 2
  752. cert = crypto.X509()
  753. cert.set_version ( 2 )
  754. # Add the Extension to the certificate
  755. cert.add_extensions(extensions)
  756. # Create a valid hexidecimal serial number
  757. goodserial = atoi(str(serial), 16)
  758. cert.set_serial_number(goodserial)
  759. if self.debug: print "DEBUG: notBefore is %s, notAfter is %s" % (notBefore,notAfter)
  760. #cert.gmtime_adj_notBefore(notBefore)
  761. #cert.gmtime_adj_notAfter(notAfter)
  762. now = datetime.utcnow().strftime("%Y%m%d%H%M%SZ")
  763. if self.debug: print "DEBUG: days is %s" % timedelta(seconds=notAfter)
  764. expire = (datetime.utcnow() + timedelta(seconds=notAfter)).strftime("%Y%m%d%H%M%SZ")
  765. cert.set_notBefore(now)
  766. cert.set_notAfter(expire)
  767. cert.set_issuer(issuerCert.get_subject())
  768. cert.set_subject(req.get_subject())
  769. cert.set_pubkey(req.get_pubkey())
  770. cert.sign(issuerKey, digest)
  771. return cert
  772. # Passphrase
  773. def getPass(self):
  774. passA = getpass.getpass('Enter passphrase for private key: ')
  775. passB = getpass.getpass('Enter passphrase for private key (again): ')
  776. if passA == passB:
  777. return passB
  778. else:
  779. print "Error: passwords don't match!"
  780. return "password_error"
  781. # Simple routines to load/save files using crypto lib
  782. # Save private key to file
  783. def save_key (self, fn, key):
  784. global keyPass
  785. # Adding passphrase to private key
  786. # do we need a random passphrase?
  787. if self.randpass:
  788. if self.debug: print "DEBUG: generating a random passphrase of %s characters" % self.randpass
  789. keyPass = ""
  790. for i in range(int(self.randpass)):
  791. keyPass += random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
  792. fp = open ( fn, 'w' )
  793. fp.write ( crypto.dump_privatekey ( self.FILETYPE, key, self.ciphermethod, keyPass ) )
  794. print "Private key encrypted with RANDOM passphrase: '%s'" % keyPass
  795. elif self.passphrase:
  796. if self.passphrase == 'please_prompt_me':
  797. keyPass = self.getPass()
  798. if keyPass is "password_error":
  799. # Don't write keyfile if supplied passwords mismatch
  800. sys.exit()
  801. else:
  802. fp = open ( fn, 'w' )
  803. fp.write ( crypto.dump_privatekey ( self.FILETYPE, key, self.ciphermethod, keyPass ) )
  804. if self.debug: print "DEBUG: private key encrypted with passphrase: '%s'" % keyPass
  805. else:
  806. fp = open ( fn, 'w' )
  807. fp.write ( crypto.dump_privatekey ( self.FILETYPE, key, self.ciphermethod, self.passphrase ) )
  808. if self.debug: print "DEBUG: private key encrypted with passphrase: '%s'" % self.passphrase
  809. else:
  810. fp = open ( fn, 'w' )
  811. fp.write ( crypto.dump_privatekey ( self.FILETYPE, key ) )
  812. fp.close ()
  813. # Save certificate to file
  814. def save_cert (self, fn, cert):
  815. fp = open ( fn, 'w' )
  816. fp.write ( crypto.dump_certificate ( self.FILETYPE, cert ) )
  817. fp.close ()
  818. # Load private key from file
  819. def load_key (self, fn):
  820. fp = open ( fn, 'r' )
  821. ret = crypto.load_privatekey ( self.FILETYPE, fp.read() )
  822. fp.close ()
  823. return ret
  824. # Load certificate from file
  825. def load_cert (self, fn):
  826. fp = open ( fn, 'r' )
  827. ret = crypto.load_certificate ( self.FILETYPE, fp.read() )
  828. fp.close ()
  829. return ret
  830. # Print information retreived from a certificate file
  831. def print_cert (self, cert):
  832. try:
  833. certfile = self.load_cert( cert )
  834. except:
  835. print "Error opening certificate file"
  836. sys.exit()
  837. # Some objects are 'X509Name objects' so we have to fiddle a bit to output to a human-readable format
  838. certIssuerArray = str(certfile.get_issuer()).replace('<X509Name object \'', '').replace('\'>','').split('/')
  839. certIssuer = certIssuerArray[1] + ', ' + certIssuerArray[2] + ', ' + certIssuerArray[3] + ', ' + certIssuerArray[4]
  840. print "Issuer:\t\t" + str(certIssuer)
  841. certSubjectArray = str(certfile.get_subject()).replace('<X509Name object \'', '').replace('\'>','').split('/')
  842. certSubject = certSubjectArray[1] + ', ' + certSubjectArray[2] + ', ' + certSubjectArray[3] + ', ' + certSubjectArray[4]
  843. print "Subject:\t" + str(certSubject)
  844. print "Version:\t" + str(certfile.get_version())
  845. print "Serial number:\t" + str(certfile.get_serial_number())
  846. validFromYear = str(certfile.get_notBefore())[:4]
  847. validFromMonth = str(certfile.get_notBefore())[4:6]
  848. validFromDay = str(certfile.get_notBefore())[6:8]
  849. validFromTime = str(certfile.get_notBefore())[8:10] + ':' + str(certfile.get_notBefore())[10:12] + ':' + str(certfile.get_notBefore())[12:14]
  850. print "Valid from:\t" + validFromYear + '-' + validFromMonth + '-' + validFromDay + ' ' + validFromTime
  851. validUntilYear = str(certfile.get_notAfter())[:4]
  852. validUntilMonth = str(certfile.get_notAfter())[4:6]
  853. validUntilDay = str(certfile.get_notAfter())[6:8]
  854. validUntilTime = str(certfile.get_notAfter())[8:10] + ':' + str(certfile.get_notAfter())[10:12] + ':' + str(certfile.get_notAfter())[12:14]
  855. print "Valid until:\t" + validUntilYear + '-' + validUntilMonth + '-' + validUntilDay + ' ' + validUntilTime
  856. if str(certfile.has_expired()) == '1':
  857. print "Expired:\tyes"
  858. else:
  859. print "Expired:\tno"
  860. def createSelfSignedCertificate(self, years):
  861. # check if a CA certificate already exists
  862. if os.path.exists(self.cacertfile):
  863. print "Error: the CA certificate already exists at %s" % self.cacertfile
  864. sys.exit()
  865. # check if a CA key already exists
  866. if os.path.exists(self.cakeyfile):
  867. # file already exists
  868. print "Warning: the CA keyfile already exists at %s, so we'll use it" % self.cakeyfile
  869. k = self.load_key(self.cakeyfile)
  870. else:
  871. # create a key pair
  872. k = crypto.PKey()
  873. k.generate_key(crypto.TYPE_RSA, int(default_bits))
  874. valid_until = (date.today() + timedelta(int(years)*365)).isoformat()
  875. print "Generating a self-signed CA certificate."
  876. print "The certificate is valid until %s." % valid_until
  877. # create a self-signed cert
  878. cert = crypto.X509()
  879. # get some values from the openssl.cnf file
  880. cert.get_subject().C = countryName
  881. cert.get_subject().ST = stateOrProvinceName
  882. cert.get_subject().L = localityName
  883. cert.get_subject().O = organizationName
  884. cert.get_subject().OU = organizationalUnitName
  885. cert.get_subject().CN = gethostname()
  886. extensions = []
  887. extensions.append(crypto.X509Extension('basicConstraints',0, 'CA:TRUE'))
  888. cert.add_extensions(extensions)
  889. serial = self.hex2dec( self.readSerial() )
  890. cert.set_serial_number(serial)
  891. cert.gmtime_adj_notBefore(0)
  892. cert.gmtime_adj_notAfter(int(years)*365*24*60*60)
  893. cert.set_issuer(cert.get_subject())
  894. cert.set_pubkey(k)
  895. cert.sign(k, default_md)
  896. # write key and cert file
  897. print "Writing CA private key to %s" % self.cakeyfile
  898. self.save_key (self.cakeyfile, k)
  899. print "Writing CA certificate to %s" % self.cacertfile
  900. self.save_cert (self.cacertfile, cert)
  901. # Generate keyfile and certificate
  902. def makeCert(self, fname, cname):
  903. # remove possible leftover files to prevent the zipfile from packing these
  904. for f in glob.glob(self.working + '/' + self.fprefix + fname + '.*'):
  905. if self.debug: print "DEBUG: removing old file %s" % f
  906. os.remove(f)
  907. pkey = self.createKeyPair(self.TYPE_RSA, int(default_bits))
  908. req = self.createCertRequest(pkey, CN=cname, C=countryName, ST=stateOrProvinceName, O=organizationName, OU=organizationalUnitName)
  909. try:
  910. cacert = self.load_cert( self.cacertfile )
  911. except:
  912. print "Error opening CA cert file"
  913. sys.exit()
  914. try:
  915. cakey = self.load_key(self.cakeyfile)
  916. except:
  917. print "Error opening CA key file"
  918. sys.exit()
  919. # check if the 'next serial number' in serialfile is the same as the serial number of the
  920. # last entry in the indexdb. If it is, increase the next serial by one (hex) and write a
  921. # new serialfile
  922. last = None
  923. for line in open(indexdb):
  924. last=line
  925. if last:
  926. last_serial = last.split("\t")[3].strip()
  927. else:
  928. last_serial = 0
  929. if self.debug: print "Last serial in indexdb: '%s'" % last_serial
  930. f=open(serialfile, 'r')
  931. serial = f.readline().strip()
  932. f.close()
  933. if self.debug: print "Next serial in serialfile: '%s'" % serial
  934. if serial == last_serial:
  935. print "Whoops! Last serial number in indexdb is the same as the next"
  936. print "one in serialfile: %s. This is probably caused by an older version" % serial
  937. print "of StoneVPN. We'll need to correct this (once) by increasing"
  938. newSerialDec = self.hex2dec(serial) + 1
  939. newSerial = self.dec2hex(newSerialDec)
  940. print "the value for next serial number to %s" % newSerial
  941. if len(newSerial) == 1:
  942. newSerial = '0' + str(newSerial)
  943. if self.debug: print "Now increasing %s by 1 to %s" % (serial,newSerial)
  944. f=open(serialfile, 'w')
  945. f.write(newSerial)
  946. f.close()
  947. # read next serial number from serialfile
  948. curSerial = self.readSerial()
  949. # format current time as UTC, for certificate
  950. timeNow = datetime.utcnow()
  951. # format current time as local, for indexdb
  952. timeNowIdx = datetime.now()
  953. # We can't work with hex numbers. Convert them to dec first and increase its value by 1
  954. newSerial = self.hex2dec(curSerial) + 1
  955. newSerialDec = newSerial
  956. # Now convert dec back to hex
  957. newSerial = self.dec2hex(newSerial)
  958. # Check if a different expiration date for certificate
  959. if self.expiredate:
  960. # Check for valid arguments: (h)ours, (d)ays, (y)ears.
  961. # For example: 2h or 6d or 2y. A combination is not (yet?) possible.
  962. expList = list(self.expiredate)
  963. try:
  964. unit = list(self.expiredate)[-1]
  965. if self.debug: print "DEBUG: time unit is %s" % unit
  966. except:
  967. print "Incorrect or missing time unit. Use h(ours), d(ays) or y(ears)."
  968. sys.exit()
  969. countRest = len(expList) - 1
  970. exp_time = ''.join(expList[0:countRest])
  971. if self.debug: print "DEBUG: exp_time is %s" % exp_time
  972. if unit not in ('h', 'H', 'd', 'D', 'y', 'Y'):
  973. print "Invalid time unit provided. Use h(ours), d(ays) or y(ears)."
  974. sys.exit()
  975. elif unit in ('h', 'H'):
  976. cert = self.createCertificate(req, (cacert, cakey), curSerial, (0, 60 * 60 * int(exp_time)))
  977. expDate = timeNow + timedelta(hours=int(exp_time))
  978. expDateIdx = timeNowIdx + timedelta(hours=int(exp_time))
  979. print "Certificate is valid for %s hour(s)." % exp_time
  980. elif unit in ('d', 'D'):
  981. cert = self.createCertificate(req, (cacert, cakey), curSerial, (0, 24 * 60 * 60 * int(exp_time)))
  982. expDate = timeNow + timedelta(days=int(exp_time))
  983. expDateIdx = timeNowIdx + timedelta(days=int(exp_time))
  984. print "Certificate is valid for %s day(s)." % exp_time
  985. elif unit in ('y', 'Y'):
  986. cert = self.createCertificate(req, (cacert, cakey), curSerial, (0, 24 * 60 * 60 * 365 * int(exp_time)))
  987. expDate = timeNow + timedelta(days=int(exp_time) * 365)
  988. expDateIdx = timeNowIdx + timedelta(days=int(exp_time) * 365)
  989. print "Certificate is valid for %s year(s)." % exp_time
  990. else:
  991. cert = self.createCertificate(req, (cacert, cakey), curSerial, (0, 24 * 60 * 60 * int(defaultDays)))
  992. expDate = timeNow + timedelta(days=int(defaultDays))
  993. expDateIdx = timeNowIdx + timedelta(days=int(defaultDays))
  994. print "Certificate is valid for %s day(s)." % defaultDays
  995. self.save_key ( self.working + '/' + self.fprefix + fname + '.key', pkey )
  996. self.save_cert ( self.working + '/' + self.fprefix + fname + '.crt', cert )
  997. # OpenSSL only accepts serials of 2 digits, so check for the length and prepend a 0 if necessary
  998. if len(str(newSerial)) == 1:
  999. serialIdx = '0' + str(newSerial)
  1000. else:
  1001. serialIdx = newSerial
  1002. # Write serial (hex) to serial file
  1003. self.writeSerial(serialIdx)
  1004. # copy CA certificate to working dir
  1005. shutil.copy(self.cacertfile, self.working)
  1006. # create the configuration files (default 'unix' unless specified with option -c)
  1007. self.makeConfs(self.confs, fname)
  1008. # write index to file
  1009. if self.debug: print "DEBUG: timeNow is %s" % timeNow
  1010. if self.debug: print "DEBUG: expDate is %s" % expDate
  1011. # OpenSSL only accepts serials of 2 digits, so check for the length and prepend a 0 if necessary
  1012. if len(str(curSerial)) == 1:
  1013. serialNumber = '0' + str(curSerial)
  1014. else:
  1015. serialNumber = curSerial
  1016. # convert cname: spaces to underscores for inclusion in indexdb
  1017. nospaces_cname = cname.replace(' ', '_')
  1018. # the expire date for the index file needs some conversion
  1019. indexDate = expDateIdx.strftime("%y%m%d%H%M%S")
  1020. if self.debug: print "DEBUG: indexDate is %s" % indexDate
  1021. # Format index line and write to OpenSSL index file
  1022. index = 'V\t' + str(indexDate) + 'Z\t\t' + str(serialNumber.strip()) + '\tunknown\t' + '/C=' + str(countryName) + '/ST=' + str(stateOrProvinceName) + '/O=' + str(organizationName) + '/OU=' + str(organizationalUnitName) + '/CN=' + str(nospaces_cname) + '/emailAddress=' + str(fname) + '@local\n'
  1023. self.writeIndex(index)
  1024. # Make config files for OpenVPN
  1025. def makeConfs(self, sname, fname):
  1026. config = ConfigObj(self.stonevpnconf)
  1027. # Generate appropriate (according to specified OS) configuration for OpenVPN
  1028. if sname == 'unix' or sname == 'linux':
  1029. sectionname = 'unix conf'
  1030. print "Generating UNIX configuration file"
  1031. f=open(self.working + '/' + self.fprefix + fname + '.conf', 'w')
  1032. elif sname == 'windows':
  1033. sectionname = 'windows conf'
  1034. print "Generating Windows configuration file"
  1035. f=open(self.working + '/' + self.fprefix + fname + '.ovpn', 'w')
  1036. elif sname == 'mac':
  1037. sectionname = 'mac conf'
  1038. print "Generating Mac configuration file"
  1039. f=open(self.working + '/' + self.fprefix + fname + '.conf', 'w')
  1040. elif sname == 'android':
  1041. sectionname = 'android conf'
  1042. print "Generating Android configuration file"
  1043. f=open(self.working + '/' + self.fprefix + fname + '.ovpn', 'w')
  1044. elif sname == 'all':
  1045. print "Generating all configuration files"
  1046. else:
  1047. print "Incorrect OS type specified. Valid options are 'unix', 'windows', 'mac', 'android' or 'all'."
  1048. sys.exit()
  1049. if sname != 'all':
  1050. section=config[sectionname]
  1051. # Go over each entry (variable) and write it to the OpenVPN configuration file
  1052. for var in section:
  1053. # Fill in correct path to generated cert/key/cacert files
  1054. if var == 'ca':
  1055. cacertfilenopath = self.cacertfile.split('/')[int(len(self.cacertfile.split('/')) - 1)]
  1056. f.write(section[var].replace('cacertfile', cacertfilenopath) + '\n')
  1057. elif var == 'cert':
  1058. f.write(section[var].replace('clientcertfile', self.fprefix + fname + '.crt') + '\n')
  1059. elif var == 'key':
  1060. f.write(section[var].replace('clientkeyfile', self.fprefix + fname + '.key') + '\n')
  1061. elif var == 'ip':
  1062. if self.server_ip:
  1063. f.write("remote " + str(self.server_ip) + "\n")
  1064. else:
  1065. f.write(section[var] + '\n')
  1066. else:
  1067. f.write(section[var] + '\n')
  1068. if sname == 'android':
  1069. fp = open ( self.cacertfile, 'r' )
  1070. f.write('\n' + "<ca>" + '\n' + fp.read() + "</ca>" + '\n')
  1071. fp.close ()
  1072. fp = open ( self.working + '/' + self.fprefix + fname + '.crt', 'r' )
  1073. f.write('\n' + "<cert>" + '\n' + fp.read() + "</cert>" + '\n')
  1074. fp.close ()
  1075. fp = open ( self.working + '/' + self.fprefix + fname + '.key', 'r' )
  1076. f.write('\n' + "<key>" + '\n' + fp.read() + "</key>" + '\n')
  1077. fp.close ()
  1078. f.close()
  1079. else:
  1080. os_versions = ["windows", "linux", "mac", "android"]
  1081. for os_type in os_versions:
  1082. # soort extensie ipv deze regel <<
  1083. if os_type == 'linux':
  1084. sectionname = 'unix conf'
  1085. print "Generating Linux configuration file"
  1086. f=open(self.working + '/' + self.fprefix + fname + '.linux.conf', 'w')
  1087. elif os_type == 'windows':
  1088. sectionname = 'windows conf'
  1089. print "Generating Windows configuration file"
  1090. f=open(self.working + '/' + self.fprefix + fname + '.windows.ovpn', 'w')
  1091. elif os_type == 'mac':
  1092. sectionname = 'mac conf'
  1093. print "Generating Mac configuration file"
  1094. f=open(self.working + '/' + self.fprefix + fname + '.mac.conf', 'w')
  1095. elif os_type == 'android':
  1096. sectionname = 'android conf'
  1097. print "Generating Android configuration file"
  1098. f=open(self.working + '/' + self.fprefix + fname + '.android.ovpn', 'w')
  1099. section=config[sectionname]
  1100. for var in section:
  1101. if var == 'ca':
  1102. cacertfilenopath = self.cacertfile.split('/')[int(len(self.cacertfile.split('/')) - 1)]
  1103. f.write(section[var].replace('cacertfile', cacertfilenopath) + '\n')
  1104. elif var == 'cert':
  1105. f.write(section[var].replace('clientcertfile', self.fprefix + fname + '.crt') + '\n')
  1106. elif var == 'key':
  1107. f.write(section[var].replace('clientkeyfile', self.fprefix + fname + '.key') + '\n')
  1108. else:
  1109. f.write(section[var] + '\n')
  1110. if os_type == 'android':
  1111. fp = open ( self.cacertfile, 'r' )
  1112. f.write('\n' + "<ca>" + '\n' + fp.read() + "</ca>" + '\n')
  1113. fp.close ()
  1114. fp = open ( self.working + '/' + self.fprefix + fname + '.crt', 'r' )
  1115. f.write('\n' + "<cert>" + '\n' + fp.read() + "</cert>" + '\n')
  1116. fp.close ()
  1117. fp = open ( self.working + '/' + self.fprefix + fname + '.key', 'r' )
  1118. f.write('\n' + "<key>" + '\n' + fp.read() + "</key>" + '\n')
  1119. fp.close ()
  1120. f.close()
  1121. # Revoke certificate
  1122. def revokeCert(self, serial):
  1123. if not os.path.exists(self.crlfile):
  1124. print "Error: CRL file not found at: " + self.crlfile + " or insufficient rights."
  1125. sys.exit()
  1126. try:
  1127. crl = crypto.CRL()
  1128. except:
  1129. print "\nError: CRL support is not available in your version of"
  1130. print "pyOpenSSL. Please check the README file that came with"
  1131. print "StoneVPN to see what you can do about this. For now, "
  1132. print "you will have to revoke certificates manually.\n"
  1133. sys.exit()
  1134. # we can't replace stuff in the original index file, so we have to create
  1135. # a new one and in the end rename the original one and move the temp file
  1136. # to the final location (usually /etc/ssl/index.txt)
  1137. t=open(self.working + '/index.tmp', 'w')
  1138. # read SSL dbase from the index file
  1139. # this file has 5 columns: Status, Expiry date, Revocation date, Serial nr, file?, Distinguished Name (DN)
  1140. print "Reading SSL database: " + indexdb
  1141. input = open(indexdb, 'r')
  1142. f=open(self.working + '/revoked.crl', 'w')
  1143. crlTime = str(strftime("%y%m%d%H%M%S")) + 'Z'
  1144. for line in input:
  1145. # first check if the line contains a revoked cert:
  1146. if line.split()[0] == 'R':
  1147. # then check if the revoked cert has the same serial nr as the one we're trying to revoke
  1148. # if so, exit immediately since we can't revoke twice (duh)
  1149. if line.split()[3] == serial.upper():
  1150. print "Certificate with serial %s already revoked!" % serial.upper()
  1151. os.remove(self.working + '/index.tmp')
  1152. os.remove(self.working + '/revoked.crl')
  1153. sys.exit()
  1154. else:
  1155. revSerial = str(line.split()[3])
  1156. revDate = str(line.split()[2])
  1157. revoked = crypto.Revoked()
  1158. revoked.set_rev_date('20' + str(revDate))
  1159. revoked.set_serial(revSerial)
  1160. #no reason needed?
  1161. #revoked.set_reason('revoked')
  1162. crl.add_revoked(revoked)
  1163. # /new way
  1164. print "Re-adding existing revoked certificate to CRL with date " + revDate + " and serial " + revSerial
  1165. t.write(line)
  1166. else:
  1167. # the line contains a valid certificate. Check if the serial is the same as the
  1168. # one we're trying to revoke
  1169. if line.split()[2] == serial.upper():
  1170. # we have a match! do not write this line again to the new index file
  1171. # instead, change it to the revoked-format
  1172. if self.debug: print "DEBUG: found match for %s in index file" % serial.upper()
  1173. newDN = '/'.join(line.split('/')[1:])
  1174. revokedLine = 'R\t' + str(line.split()[1]) + '\t' + crlTime + '\t' + serial.upper() + '\tunknown\t' + str(newDN)
  1175. t.write(revokedLine)
  1176. else:
  1177. # this is not the match we're looking for, so just write the line again
  1178. # to the index file
  1179. t.write(line)
  1180. # crlTime = str(strftime("%y%m%d%H%M%S")) + 'Z'
  1181. print "Adding new revoked certificate to CRL with date " + crlTime + " and serial " + serial.upper()
  1182. t.close()
  1183. revoked = crypto.Revoked()
  1184. now = datetime.utcnow().strftime("%Y%m%d%H%M%SZ")
  1185. revoked.set_rev_date(now)
  1186. revoked.set_serial(serial)
  1187. #no reason needed?
  1188. #revoked.set_reason('sUpErSeDEd')
  1189. crl.add_revoked(revoked)
  1190. cacert = self.load_cert(self.cacertfile)
  1191. cakey = self.load_key(self.cakeyfile)
  1192. newCRL = crl.export(cacert, cakey, days=3650)
  1193. f.write(newCRL)
  1194. f.close()
  1195. shutil.move(indexdb,indexdb + '.old')
  1196. shutil.move(self.working + '/index.tmp',indexdb)
  1197. shutil.move(self.crlfile,self.crlfile + '.old')
  1198. shutil.move(self.working + '/revoked.crl',self.crlfile)
  1199. print "New CRL written to: %s. Backup created as: %s." % (self.crlfile,self.crlfile + '.old')
  1200. print "New index written to: %s. Backup created as: %s." % (indexdb,indexdb + '.old')
  1201. def displayCRL(self):
  1202. if not os.path.exists(self.crlfile):
  1203. print "Error: CRL file not found at %s" % self.crlfile
  1204. print "You can create one with: stonevpn --newcrl"
  1205. sys.exit()
  1206. text = open(self.crlfile, 'r').read()
  1207. print "Parsing CRL file %s" % self.crlfile
  1208. try:
  1209. crl = crypto.load_crl(crypto.FILETYPE_PEM, text)
  1210. revs = crl.get_revoked()
  1211. except:
  1212. print "\nError: CRL support is not available in your version of"
  1213. print "pyOpenSSL. Please check the README file that came with"
  1214. print "StoneVPN to see what you can do about this. For now, "
  1215. print "you will have to display the CRL file manually using:\n"
  1216. print "$ openssl crl -in %s -noout -text\n" % self.crlfile
  1217. sys.exit()
  1218. if not revs is None:
  1219. print "Total certificates revoked: %s\n" % len(revs)
  1220. print "Serial\tRevoked at date"
  1221. print "======\t========================"
  1222. for revoked in revs:
  1223. revSerial = revoked.get_serial()
  1224. revDate = revoked.get_rev_date()[0:-1]
  1225. revoDate = time.strptime(revDate, "%Y%m%d%H%M%S")
  1226. print str(revSerial) + "\t" + time.strftime("%c", revoDate)
  1227. else:
  1228. print "No revoked certificates found."
  1229. def listRevokedCerts(self):
  1230. # read SSL dbase (usually index.txt)
  1231. # this file has 5 columns: Status, Expiry date, Revocation date, Serial nr, unknown, Distinguished Name (DN)
  1232. print "Reading SSL database: " + indexdb
  1233. input = open(indexdb, 'r')
  1234. revCerts = []
  1235. print "Finding revoked certificates..."
  1236. for line in input:
  1237. if line.split()[0] == 'R':
  1238. revCerts.append(line)
  1239. count = 0
  1240. while count < len(revCerts):
  1241. #print "Revoked certificate:\t" + str(revCerts[count].split()[5].split('/CN=')[1])
  1242. print "Issued to:\t\t%s" % str(revCerts[count]).split('CN=')[1].split('/')[0]
  1243. print "Status:\t\t\tRevoked"
  1244. expDate = str(revCerts[count].split()[1])
  1245. print "Expiry date:\t\t20%s-%s-%s %s:%s:%s" % (expDate[:2],expDate[2:4],expDate[4:6],expDate[6:8],expDate[8:10],expDate[10:12])
  1246. revDate = str(revCerts[count].split()[2])
  1247. print "Revocation date:\t20%s-%s-%s %s:%s:%s" % (revDate[:2],revDate[2:4],revDate[4:6],revDate[6:8],revDate[8:10],revDate[10:12])
  1248. print "Serial:\t\t\t%s" % str(revCerts[count].split()[3])
  1249. lineDN = line.split('unknown')[1].strip()
  1250. newDN = ''.join(lineDN).replace('/',',')
  1251. print "DN:\t\t\t%s" % newDN
  1252. print "\n"
  1253. count = count + 1
  1254. def indent(self, rows, hasHeader=False, headerChar='-', delim=' | ', justify='left',
  1255. separateRows=False, prefix='', postfix='', wrapfunc=lambda x:x):
  1256. # closure for breaking logical rows to physical, using wrapfunc
  1257. def rowWrapper(row):
  1258. newRows = [wrapfunc(item).split('\n') for item in row]
  1259. return [[substr or '' for substr in item] for item in map(None,*newRows)]
  1260. # break each logical row into one or more physical ones
  1261. logicalRows = [rowWrapper(row) for row in rows]
  1262. # columns of physical rows
  1263. columns = map(None,*reduce(operator.add,logicalRows))
  1264. # get the maximum of each column by the string length of its items
  1265. maxWidths = [max([len(str(item)) for item in column]) for column in columns]
  1266. rowSeparator = headerChar * (len(prefix) + len(postfix) + sum(maxWidths) + \
  1267. len(delim)*(len(maxWidths)-1))
  1268. # select the appropriate justify method
  1269. justify = {'center':str.center, 'right':str.rjust, 'left':str.ljust}[justify.lower()]
  1270. output=cStringIO.StringIO()
  1271. if separateRows: print >> output, rowSeparator
  1272. for physicalRows in logicalRows:
  1273. for row in physicalRows:
  1274. print >> output, \
  1275. prefix \
  1276. + delim.join([justify(str(item),width) for (item,width) in zip(row,maxWidths)]) \
  1277. + postfix
  1278. if separateRows or hasHeader: print >> output, rowSeparator; hasHeader=False
  1279. return output.getvalue()
  1280. def listAllCerts(self):
  1281. # list all certificates in indexdb, output as pretty table
  1282. input = open(indexdb, 'r')
  1283. data = ''
  1284. for line in input:
  1285. if line.split()[0] == 'R':
  1286. issuee = line.split('/')[-2:][0].replace('CN=','').replace('_',' ').replace(',','_')
  1287. data = str(data) + str(line.split()[3]) + ',' + str(issuee)
  1288. data = str(data) + ',' + "Revoked"
  1289. revDate = str(line.split()[2]).replace('Z','')
  1290. data = str(data) + ',' + '20' + revDate[:2] + '-' + revDate[2:4] + '-' + revDate[4:6] + ' ' + revDate[6:8] + ':' + revDate[8:10] + ':' + revDate[10:12] + '\n'
  1291. else:
  1292. issuee = line.split('/')[-2:-1][0].split('\t')[0].replace('CN=','').replace('_',' ').replace(',','_')
  1293. data = str(data) + str(line.split()[2]) + ',' + str(issuee)
  1294. expDate = str(line.split()[1]).replace('Z','')
  1295. ed_long = "20" + str(expDate)
  1296. expiredate = int(time.mktime(time.strptime(str(datetime(int(ed_long[:4]),int(ed_long[4:6]),int(ed_long[6:8]),int(ed_long[8:10]),int(ed_long[10:12]),int(ed_long[12:14]))),"%Y-%m-%d %H:%M:%S")))
  1297. timenow = int(time.mktime(time.localtime()))
  1298. if int(timenow) < int(expiredate):
  1299. data = str(data) + ',Valid'
  1300. else:
  1301. data = str(data) + ',Expired'
  1302. data = str(data) + ',' + '20' + expDate[:2] + '-' + expDate[2:4] + '-' + expDate[4:6] + ' ' + expDate[6:8] + ':' + expDate[8:10] + ':' + expDate[10:12] + '\n'
  1303. rows = []
  1304. rows.append(('Serial','Name','Status','Expiry date'))
  1305. for row in data.splitlines():
  1306. rows.append(row.strip().split(','))
  1307. width = 60
  1308. print self.indent(rows,hasHeader=True)
  1309. def listAllCertsCSV(self):
  1310. # same routine as listAllCerts() except print as
  1311. # comma seperated values and without the DN.
  1312. input = open(indexdb, 'r')
  1313. for line in input:
  1314. if line.split()[0] == 'R':
  1315. issuee = line.split('/')[-2:][0].replace('CN=','').replace('_',' ').replace(',','_')
  1316. sys.stdout.write(str(issuee) + ",")
  1317. sys.stdout.write("Revoked,")
  1318. revDate = str(line.split()[2]).replace('Z','')
  1319. sys.stdout.write("20%s-%s-%s %s:%s:%s," % (revDate[:2],revDate[2:4],revDate[4:6],revDate[6:8],revDate[8:10],revDate[10:12]))
  1320. sys.stdout.write(str(line.split()[3]) + ",")
  1321. else:
  1322. issuee = line.split('/')[-2:-1][0].split('\t')[0].replace('CN=','').replace('_',' ').replace(',','_')
  1323. sys.stdout.write(str(issuee) + ",")
  1324. expDate = str(line.split()[1]).replace('Z','')
  1325. ed_long = "20" + str(expDate)
  1326. expiredate = int(time.mktime(time.strptime(str(datetime(int(ed_long[:4]),int(ed_long[4:6]),int(ed_long[6:8]),int(ed_long[8:10]),int(ed_long[10:12]),int(ed_long[12:14]))),"%Y-%m-%d %H:%M:%S")))
  1327. timenow = int(time.mktime(time.localtime()))
  1328. if int(timenow) < int(expiredate):
  1329. sys.stdout.write("Valid,")
  1330. else:
  1331. sys.stdout.write("Expired,")
  1332. sys.stdout.write("20%s-%s-%s %s:%s:%s," % (expDate[:2],expDate[2:4],expDate[4:6],expDate[6:8],expDate[8:10],expDate[10:12]))
  1333. sys.stdout.write(str(line.split()[2]) + ",")
  1334. def send_mail(self, send_from, send_to, subject, text, attachment=[]):
  1335. print "Generating e-mail"
  1336. msg = MIMEMultipart()
  1337. msg['From'] = send_from
  1338. msg['To'] = send_to
  1339. msg['CC'] = self.mail_cc
  1340. msg['Date'] = formatdate(localtime=True)
  1341. msg['Subject'] = subject
  1342. text = text.replace('EMAILRECIPIENT', self.cname)
  1343. # Append a helpful text when a password was given, but only when specified on the commandline
  1344. if self.mailpass:
  1345. if self.passphrase is None and self.randpass is None:
  1346. print "Error: you need to specify either a passphrase or generate a random one."
  1347. sys.exit()
  1348. if keyPass:
  1349. if self.debug: print "DEBUG: including password help text in email body"
  1350. text = text.replace('PASSPHRASETXT', self.mail_passtxt)
  1351. # And replace the password placeholder with the actual passphrase
  1352. text = text.replace('OPENSSLPASS', keyPass)
  1353. else:
  1354. text = text.replace('PASSPHRASETXT', '')
  1355. msg.attach( MIMEText(text, 'html') )
  1356. # Attachment(s)
  1357. if type(attachment) == 'string':
  1358. part = MIMEBase('application', "octet-stream")
  1359. part.set_payload( open(attachment,"rb").read() )
  1360. Encoders.encode_base64(part)
  1361. part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(attachment))
  1362. msg.attach(part)
  1363. else:
  1364. for f in attachment:
  1365. print "Attaching file %s" % f
  1366. part = MIMEBase('application', "octet-stream")
  1367. part.set_payload( open(f,"rb").read() )
  1368. Encoders.encode_base64(part)
  1369. part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))
  1370. msg.attach(part)
  1371. # Now to send the entire message
  1372. print 'Sending e-mail with attachment(s) to %s' % self.emailaddress
  1373. smtp = smtplib.SMTP(self.mail_server)
  1374. smtp.sendmail(send_from, send_to, msg.as_string())
  1375. smtp.close()