blob: 8e9262b5d000fc325f3a0dbc829897d88fbca58a [file] [log] [blame]
Dirk Pranke4d164bb2021-03-24 06:52:401#!/usr/bin/env python
Avi Drissman64595482022-09-14 20:52:292# Copyright 2016 The Chromium Authors
svaldezbe481782016-03-24 17:16:323# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5# TODO(svaldez): Deduplicate various annotate_test_data.
6
7"""This script is called without any arguments to re-format all of the *.pem
8files in the script's parent directory.
9
10The main formatting change is to run "openssl asn1parse" for each of the PEM
11block sections, and add that output to the comment. It also runs the command
12on the OCTET STRING representing BasicOCSPResponse.
13
14"""
15
16import glob
17import os
18import re
19import base64
20import subprocess
21
22
23def Transform(file_data):
24 """Returns a transformed (formatted) version of file_data"""
25
26 result = ''
27
28 for block in GetPemBlocks(file_data):
29 if len(result) != 0:
30 result += '\n'
31
32 # If there was a user comment (non-script-generated comment) associated
33 # with the block, output it immediately before the block.
34 user_comment = GetUserComment(block.comment)
35 if user_comment:
Eric Romanbce9c6b2017-09-26 00:02:1536 result += user_comment
svaldezbe481782016-03-24 17:16:3237
38 generated_comment = GenerateCommentForBlock(block.name, block.data)
39 result += generated_comment + '\n'
40
41
42 result += MakePemBlockString(block.name, block.data)
43
44 return result
45
46
47def GenerateCommentForBlock(block_name, block_data):
Eric Romanbce9c6b2017-09-26 00:02:1548 """Returns a string describing |block_data|. The format of |block_data| is
49 inferred from |block_name|, and is pretty-printed accordingly. For
50 instance CERTIFICATE is understood to be an X.509 certificate and pretty
51 printed using OpenSSL's x509 command. If there is no specific pretty-printer
52 for the data type, it is annotated using "openssl asn1parse"."""
53
54 # Try to pretty printing as X.509 certificate.
55 if "CERTIFICATE" in block_name:
56 p = subprocess.Popen(["openssl", "x509", "-text", "-noout",
57 "-inform", "DER"],
58 stdin=subprocess.PIPE,
59 stdout=subprocess.PIPE,
60 stderr=subprocess.PIPE)
61 stdout_data, stderr_data = p.communicate(block_data)
62
63 # If pretty printing succeeded, return it.
64 if p.returncode == 0:
65 stdout_data = stdout_data.strip()
66 return '$ openssl x509 -text < [%s]\n%s' % (block_name, stdout_data)
67
68 # Try pretty printing as OCSP Response.
69 if block_name == "OCSP RESPONSE":
70 tmp_file_path = "tmp_ocsp.der"
71 WriteStringToFile(block_data, tmp_file_path)
Matt Muellerf820ff012018-01-31 02:51:2672 p = subprocess.Popen(["openssl", "ocsp", "-noverify", "-resp_text",
73 "-respin", tmp_file_path],
Eric Romanbce9c6b2017-09-26 00:02:1574 stdin=subprocess.PIPE,
75 stdout=subprocess.PIPE,
76 stderr=subprocess.PIPE)
77 stdout_data, stderr_data = p.communicate(block_data)
78 os.remove(tmp_file_path)
79
80 # If pretty printing succeeded, return it.
81 if p.returncode == 0:
82 stdout_data = stdout_data.strip()
83 # May contain embedded CERTIFICATE pem blocks. Escape these since
84 # CERTIFICATE already has meanining in the test file.
85 stdout_data = stdout_data.replace("-----", "~~~~~")
86 return '$ openssl ocsp -resp_text -respin <([%s])\n%s' % (block_name,
87 stdout_data)
Matt Muellerf820ff012018-01-31 02:51:2688 print 'Error pretty printing OCSP response:\n',stderr_data
Eric Romanbce9c6b2017-09-26 00:02:1589
90 # Otherwise try pretty printing using asn1parse.
svaldezbe481782016-03-24 17:16:3291
92 p = subprocess.Popen(['openssl', 'asn1parse', '-i', '-inform', 'DER'],
93 stdout=subprocess.PIPE, stdin=subprocess.PIPE,
94 stderr=subprocess.PIPE)
95 stdout_data, stderr_data = p.communicate(input=block_data)
96 generated_comment = '$ openssl asn1parse -i < [%s]\n%s' % (block_name,
97 stdout_data)
98
99 # The OCTET STRING encoded BasicOCSPResponse is also parsed out using
100 #'openssl asn1parse'.
101 if block_name == 'OCSP RESPONSE':
102 if '[HEX DUMP]:' in generated_comment:
103 (generated_comment, response) = generated_comment.split('[HEX DUMP]:', 1)
104 response = response.replace('\n', '')
105 if len(response) % 2 != 0:
106 response = '0' + response
107 response = GenerateCommentForBlock('INNER', response.decode('hex'))
108 response = response.split('\n', 1)[1]
109 response = response.replace(': ', ': ')
110 generated_comment += '\n%s' % (response)
111 return generated_comment.strip('\n')
112
113
114
115def GetUserComment(comment):
116 """Removes any script-generated lines (everything after the $ openssl line)"""
117
118 # Consider everything after "$ openssl" to be a generated comment.
Eric Romanbce9c6b2017-09-26 00:02:15119 comment = comment.split('$ openssl', 1)[0]
svaldezbe481782016-03-24 17:16:32120 if IsEntirelyWhiteSpace(comment):
121 comment = ''
Eric Romanbc2c9db2017-09-26 00:11:55122 elif not comment.endswith("\n\n"):
123 comment += "\n\n"
svaldezbe481782016-03-24 17:16:32124 return comment
125
126
127def MakePemBlockString(name, data):
128 return ('-----BEGIN %s-----\n'
129 '%s'
130 '-----END %s-----\n') % (name, EncodeDataForPem(data), name)
131
132
133def GetPemFilePaths():
134 """Returns an iterable for all the paths to the PEM test files"""
135
136 base_dir = os.path.dirname(os.path.realpath(__file__))
137 return glob.iglob(os.path.join(base_dir, '*.pem'))
138
139
140def ReadFileToString(path):
141 with open(path, 'r') as f:
142 return f.read()
143
144
145def WrapTextToLineWidth(text, column_width):
146 result = ''
147 pos = 0
148 while pos < len(text):
149 result += text[pos : pos + column_width] + '\n'
150 pos += column_width
151 return result
152
153
154def EncodeDataForPem(data):
155 result = base64.b64encode(data)
156 return WrapTextToLineWidth(result, 75)
157
158
159class PemBlock(object):
160 def __init__(self):
161 self.name = None
162 self.data = None
163 self.comment = None
164
165
166def StripAllWhitespace(text):
167 pattern = re.compile(r'\s+')
168 return re.sub(pattern, '', text)
169
170
171def IsEntirelyWhiteSpace(text):
172 return len(StripAllWhitespace(text)) == 0
173
174
175def DecodePemBlockData(text):
176 text = StripAllWhitespace(text)
177 return base64.b64decode(text)
178
179
180def GetPemBlocks(data):
181 """Returns an iterable of PemBlock"""
182
183 comment_start = 0
184
185 regex = re.compile(r'-----BEGIN ([\w ]+)-----(.*?)-----END \1-----',
186 re.DOTALL)
187
188 for match in regex.finditer(data):
189 block = PemBlock()
190
191 block.name = match.group(1)
192 block.data = DecodePemBlockData(match.group(2))
193
194 # Keep track of any non-PEM text above blocks
195 block.comment = data[comment_start : match.start()].strip()
196 comment_start = match.end()
197
198 yield block
199
200
201def WriteStringToFile(data, path):
202 with open(path, "w") as f:
203 f.write(data)
204
205
206def main():
207 for path in GetPemFilePaths():
208 print "Processing %s ..." % (path)
209 original_data = ReadFileToString(path)
210 transformed_data = Transform(original_data)
211 if original_data != transformed_data:
212 WriteStringToFile(transformed_data, path)
213 print "Rewrote %s" % (path)
214
215
216if __name__ == "__main__":
217 main()