Remove link to GitHub sponsors
[openssl-web.git] / bin / mk-cvepage
1 #! /usr/bin/python
2 #
3 # Convert our XML file to a HTML file for the web page
4 # let's replace vulnerabilities.xsl
5 #
6
7 from xml.dom import minidom
8 import simplejson as json
9 import codecs
10 import re
11 from optparse import OptionParser
12 import datetime
13 import sys
14
15 # Versions of OpenSSL we never released, to allow us to display ranges, it's not a big deal if they
16 # are not included here, it just makes things look better if they are.
17 neverreleased = "1.0.0h,";
18
19 def merge_affects(issue,base):
20     # let's merge the affects into a nice list which is better for Mitre text but we have to take into account our stange lettering scheme
21     prev = ""
22     anext = ""
23     alist = list()
24     vlist = list()
25     for affects in issue.getElementsByTagName('affects'): # so we can sort them
26        version = affects.getAttribute("version")
27        if (not base or base in version):
28            vlist.append(version)
29     for ver in sorted(vlist):
30        # print "version %s (last was %s, next was %s)" %(ver,prev,anext)
31        if (ver != anext):
32           alist.append([ver])
33        elif len(alist[-1]) > 1:
34           alist[-1][-1] = ver
35        else:
36           alist[-1].append(ver)
37        prev = ver
38        if (unicode.isdigit(ver[-1])):   # First version after 1.0.1 is 1.0.1a
39            anext = ver + "a"
40        elif (ver[-1] == "y"):
41            anext = ver[:-1] + "za"    # We ran out of letters once so y->za->zb....
42        else:
43            anext = ver[:-1]+chr(ord(ver[-1])+1) # otherwise after 1.0.1a is 1.0.1b
44        while (anext in neverreleased): # skip unreleased versions
45           anext = anext[:-1]+chr(ord(anext[-1])+1)
46
47     return ",".join(['-'.join(map(str,aff)) for aff in alist])
48
49 def allyourbase(issues):
50     allbase = []
51     # find all the major versions of OpenSSL we have vulnerabilities fixed in
52     for affects in issues.getElementsByTagName('fixed'):
53         if (affects.getAttribute("base") not in allbase):
54             if ("fips" not in affects.getAttribute("base")):  # temporary hack 
55                 allbase.append(affects.getAttribute("base"))
56     return sorted(allbase, reverse=True)
57
58
59 parser = OptionParser()
60 parser.add_option("-i", "--input", help="input vulnerability file live openssl-web/news/vulnerabilities.xml", dest="input")
61 parser.add_option("-b", "--base", help="only include vulnerabilities for this major version (i.e. 1.0.1)", dest="base")
62 (options, args) = parser.parse_args()
63
64 # We need an output directory not stdout because we might write multiple files
65 if not options.input:
66    print "needs input file"
67    parser.print_help()
68    exit();
69
70 cvej = list()
71     
72 with codecs.open(options.input,"r","utf-8") as vulnfile:
73     vulns = vulnfile.read()
74 dom = minidom.parseString(vulns.encode("utf-8"))
75 issues = dom.getElementsByTagName('issue')
76
77 thisyear = ""
78 allyears = []
79 # Display issues latest by date first, if same date then by highest CVE
80 allissues = ""
81 for issue in sorted(issues, key=lambda x: (x.getAttribute('public'), x.getElementsByTagName('cve')[0].getAttribute('name')),reverse=True):
82
83     if options.base:
84         include = 0
85         for affects in issue.getElementsByTagName('fixed'):
86             if (affects.getAttribute("base") in options.base):
87                 include = 1
88         if (include == 0):
89             continue
90     
91     date = issue.getAttribute('public')
92     year = date[:-4]
93     if (year != thisyear):
94         if (thisyear != ""):
95             allissues += "</dl>";
96         allissues += "<h3><a name=\"y%s\">%s</a></h3><dl>" %(year,year)
97         allyears.append(year)
98         thisyear = year
99     cve = issue.getElementsByTagName('cve')[0].getAttribute('name')
100
101     allissues += "<dt>"
102     if cve:
103         allissues += "<a href=\"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-%s\" name=\"CVE-%s\">CVE-%s</a> " %(cve,cve,cve)
104     for adv in issue.getElementsByTagName('advisory'):
105         allissues += "<a href=\"%s\">(OpenSSL advisory)</a> " %(adv.getAttribute("url"))
106     for sev in issue.getElementsByTagName('impact'):
107         allissues += "<a href=\"/policies/secpolicy.html#%s\">[%s severity]</a> " %(sev.getAttribute('severity').lower(),sev.getAttribute('severity'))
108     t = datetime.datetime(int(date[:4]), int(date[4:6]), int(date[6:8]), 0, 0)
109     allissues += t.strftime("%d %B %Y: ")
110
111     allissues += "<a href=\"#toc\"><img src=\"/img/up.gif\"/></a></dt><dd>"
112     allissues += issue.getElementsByTagName('description')[0].childNodes[0].nodeValue.strip()
113     for reported in issue.getElementsByTagName('reported'):
114        allissues += " Reported by %s. " %(reported.getAttribute("source"))
115     allissues += "<ul>"
116
117     also = []
118     for affects in sorted(issue.getElementsByTagName('fixed'), key=lambda x: (x.getAttribute("base")), reverse=True):
119         if options.base:
120             if (affects.getAttribute("base") not in options.base):
121                 also.append("OpenSSL <a href=\"vulnerabilities-%s.html#CVE-%s\">%s</a>" %( affects.getAttribute('base'), cve, affects.getAttribute('version')))
122                 continue
123         allissues += "<li>Fixed in OpenSSL %s " %(affects.getAttribute('version'))
124         for git in affects.getElementsByTagName('git'):
125             allissues += "<a href=\"https://github.com/openssl/openssl/commit/%s\">(git commit)</a> " %(git.getAttribute('hash'))            
126         allissues += "(Affected "+merge_affects(issue,affects.getAttribute("base"))+")"       
127         allissues += "</li>"
128     if also:
129         allissues += "<li>This issue was also addressed in "+ ", ".join( also)
130     allissues += "</ul></dd>"
131
132 preface = "<!-- do not edit this file it is autogenerated, edit vulnerabilities.xml -->"
133 bases = []
134 for base in allyourbase(dom):
135     if (options.base and base in options.base):
136         bases.append("%s" %(base))
137     else:
138         bases.append( "<a href=\"vulnerabilities-%s.html\">%s</a>" %(base,base))
139 preface += "<p>Show issues fixed only in OpenSSL " + ", ".join(bases)
140 if options.base:
141     preface += ", or <a href=\"vulnerabilities.html\">all versions</a></p>"
142     preface += "<h2>Fixed in OpenSSL %s</h2>" %(options.base)
143 else:
144     preface += "</p>"
145 for statement in dom.getElementsByTagName('statement'):
146     if (statement.getAttribute("base") in (options.base or "none")):
147         preface += "<p>"+statement.firstChild.data.strip()+"</p>"
148 if len(allyears)>1: # If only vulns in this year no need for the year table of contents
149     preface += "<p><a name=\"toc\">Jump to year: </a>" + ", ".join( "<a href=\"#y%s\">%s</a>" %(year,year) for year in allyears)
150 preface += "</p>"
151 if allissues != "":
152     preface += allissues + "</dl>"
153 else:
154     preface += "No vulnerabilities fixed"
155
156 sys.stdout.write(preface.encode('utf-8'))
157