1#!/usr/bin/env python 2# Copyright 2015 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""This script is called without any arguments to re-format all of the *.pem 7files in the script's parent directory. 8 9The main formatting change is to run "openssl asn1parse" for each of the PEM 10block sections (except for DATA), and add that output to the comment. 11 12Refer to the README file for more information. 13""" 14 15import glob 16import os 17import re 18import base64 19import subprocess 20 21 22def Transform(file_data): 23 """Returns a transformed (formatted) version of file_data""" 24 25 result = '' 26 27 for block in GetPemBlocks(file_data): 28 if len(result) != 0: 29 result += '\n' 30 31 # If there was a user comment (non-script-generated comment) associated 32 # with the block, output it immediately before the block. 33 user_comment = GetUserComment(block.comment) 34 if user_comment: 35 result += user_comment 36 37 # For every block except for DATA, try to pretty print the parsed ASN.1. 38 # DATA blocks likely would be DER in practice, but for the purposes of 39 # these tests seeing its structure doesn't clarify 40 # anything and is just a distraction. 41 if block.name != 'DATA': 42 generated_comment = GenerateCommentForBlock(block.name, block.data) 43 result += generated_comment + '\n' 44 45 46 result += MakePemBlockString(block.name, block.data) 47 48 return result 49 50 51def GenerateCommentForBlock(block_name, block_data): 52 """Returns a string describing the ASN.1 structure of block_data""" 53 54 p = subprocess.Popen(['openssl', 'asn1parse', '-i', '-inform', 'DER'], 55 stdout=subprocess.PIPE, stdin=subprocess.PIPE, 56 stderr=subprocess.PIPE) 57 stdout_data, stderr_data = p.communicate(input=block_data) 58 generated_comment = '$ openssl asn1parse -i < [%s]\n%s' % (block_name, 59 stdout_data) 60 return generated_comment.strip('\n') 61 62 63 64def GetUserComment(comment): 65 """Removes any script-generated lines (everything after the $ openssl line)""" 66 67 # Consider everything after "$ openssl" to be a generated comment. 68 comment = comment.split('$ openssl asn1parse -i', 1)[0] 69 if IsEntirelyWhiteSpace(comment): 70 comment = '' 71 return comment 72 73 74def MakePemBlockString(name, data): 75 return ('-----BEGIN %s-----\n' 76 '%s' 77 '-----END %s-----\n') % (name, EncodeDataForPem(data), name) 78 79 80def GetPemFilePaths(): 81 """Returns an iterable for all the paths to the PEM test files""" 82 83 base_dir = os.path.dirname(os.path.realpath(__file__)) 84 return glob.iglob(os.path.join(base_dir, '*.pem')) 85 86 87def ReadFileToString(path): 88 with open(path, 'r') as f: 89 return f.read() 90 91 92def WrapTextToLineWidth(text, column_width): 93 result = '' 94 pos = 0 95 while pos < len(text): 96 result += text[pos : pos + column_width] + '\n' 97 pos += column_width 98 return result 99 100 101def EncodeDataForPem(data): 102 result = base64.b64encode(data) 103 return WrapTextToLineWidth(result, 75) 104 105 106class PemBlock(object): 107 def __init__(self): 108 self.name = None 109 self.data = None 110 self.comment = None 111 112 113def StripAllWhitespace(text): 114 pattern = re.compile(r'\s+') 115 return re.sub(pattern, '', text) 116 117 118def IsEntirelyWhiteSpace(text): 119 return len(StripAllWhitespace(text)) == 0 120 121 122def DecodePemBlockData(text): 123 text = StripAllWhitespace(text) 124 return base64.b64decode(text) 125 126 127def GetPemBlocks(data): 128 """Returns an iterable of PemBlock""" 129 130 comment_start = 0 131 132 regex = re.compile(r'-----BEGIN ([\w ]+)-----(.*?)-----END \1-----', 133 re.DOTALL) 134 135 for match in regex.finditer(data): 136 block = PemBlock() 137 138 block.name = match.group(1) 139 block.data = DecodePemBlockData(match.group(2)) 140 141 # Keep track of any non-PEM text above blocks 142 block.comment = data[comment_start : match.start()].strip() 143 comment_start = match.end() 144 145 yield block 146 147 148def WriteStringToFile(data, path): 149 with open(path, "w") as f: 150 f.write(data) 151 152 153def main(): 154 for path in GetPemFilePaths(): 155 print "Processing %s ..." % (path) 156 original_data = ReadFileToString(path) 157 transformed_data = Transform(original_data) 158 if original_data != transformed_data: 159 WriteStringToFile(transformed_data, path) 160 print "Rewrote %s" % (path) 161 162 163if __name__ == "__main__": 164 main() 165