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