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 file contains helpers for representing, manipulating, and writing 7OpenSSL configuration files [1] 8 9Configuration files are simply a collection of name=value "properties", which 10are grouped into "sections". 11 12[1] https://www.openssl.org/docs/manmaster/apps/config.html 13""" 14 15class Property(object): 16 """Represents a key/value pair in OpenSSL .cnf files. 17 18 Names and values are not quoted in any way, so callers need to pass the text 19 exactly as it should be written to the file (leading and trailing whitespace 20 doesn't matter). 21 22 For instance: 23 baseConstraints = critical, CA:false 24 25 Could be represented by a Property where: 26 name = 'baseConstraints' 27 value = 'critical, CA:false' 28 """ 29 def __init__(self, name, value): 30 self.name = name 31 self.value = value 32 33 34 def write_to(self, out): 35 """Outputs this property to .cnf file.""" 36 out.write('%s = %s\n' % (self.name, self.value)) 37 38 39class Section(object): 40 """Represents a section in OpenSSL. For instance: 41 [CA_root] 42 preserve = true 43 44 Could be represented by a Section where: 45 name = 'CA_root' 46 properties = [Property('preserve', 'true')] 47 """ 48 def __init__(self, name): 49 self.name = name 50 self.properties = [] 51 52 53 def ensure_property_name_not_duplicated(self, name): 54 """Raises an exception of there is more than 1 property named |name|.""" 55 count = 0 56 for prop in self.properties: 57 if prop.name == name: 58 count += 1 59 if count > 1: 60 raise Exception('Duplicate property: %s' % (name)) 61 62 63 def set_property(self, name, value): 64 """Replaces, adds, or removes a Property from the Section: 65 66 - If |value| is None, then this is equivalent to calling 67 remove_property(name) 68 - If there is an existing property matching |name| then its value is 69 replaced with |value| 70 - If there are no properties matching |name| then a new one is added at 71 the end of the section 72 73 It is expected that there is AT MOST 1 property with the given name. If 74 that is not the case then this function will raise an error.""" 75 76 if value is None: 77 self.remove_property(name) 78 return 79 80 self.ensure_property_name_not_duplicated(name) 81 82 for prop in self.properties: 83 if prop.name == name: 84 prop.value = value 85 return 86 87 self.add_property(name, value) 88 89 90 def add_property(self, name, value): 91 """Adds a property (allows duplicates)""" 92 self.properties.append(Property(name, value)) 93 94 95 def remove_property(self, name): 96 """Removes the property with the indicated name, if it exists. 97 98 It is expected that there is AT MOST 1 property with the given name. If 99 that is not the case then this function will raise an error.""" 100 self.ensure_property_name_not_duplicated(name) 101 102 for i in range(len(self.properties)): 103 if self.properties[i].name == name: 104 self.properties.pop(i) 105 return 106 107 108 def clear_properties(self): 109 """Removes all configured properties.""" 110 self.properties = [] 111 112 113 def write_to(self, out): 114 """Outputs the section in the format used by .cnf files""" 115 out.write('[%s]\n' % (self.name)) 116 for prop in self.properties: 117 prop.write_to(out) 118 out.write('\n') 119 120 121class Config(object): 122 """Represents a .cnf (configuration) file in OpenSSL""" 123 def __init__(self): 124 self.sections = [] 125 126 127 def get_section(self, name): 128 """Gets or creates a section with the given name.""" 129 for section in self.sections: 130 if section.name == name: 131 return section 132 new_section = Section(name) 133 self.sections.append(new_section) 134 return new_section 135 136 137 def write_to_file(self, path): 138 """Outputs the Config to a .cnf files.""" 139 with open(path, 'w') as out: 140 for section in self.sections: 141 section.write_to(out) 142