DottedOIDToCode.py (8001B)
1 # This code is made available to you under your choice of the following sets 2 # of licensing terms: 3 ############################################################################### 4 # This Source Code Form is subject to the terms of the Mozilla Public 5 # License, v. 2.0. If a copy of the MPL was not distributed with this 6 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 ############################################################################### 8 # Copyright 2013 Mozilla Contributors 9 # 10 # Licensed under the Apache License, Version 2.0 (the "License"); 11 # you may not use this file except in compliance with the License. 12 # You may obtain a copy of the License at 13 # 14 # http://www.apache.org/licenses/LICENSE-2.0 15 # 16 # Unless required by applicable law or agreed to in writing, software 17 # distributed under the License is distributed on an "AS IS" BASIS, 18 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 # See the License for the specific language governing permissions and 20 # limitations under the License. 21 22 from __future__ import print_function 23 import argparse 24 import itertools 25 import sys 26 27 28 def base128(value): 29 """ 30 Given an integral value, returns an array of the base-128 representation 31 of that value, with all but the last byte having the high bit set as 32 required by the DER rules for the nodes of an OID after the first two 33 bytes. 34 35 >>> base128(1) 36 [1] 37 >>> base128(10045) 38 [206, 61] 39 """ 40 41 if value < 0: 42 raise ValueError("An OID must have only positive-value nodes.") 43 44 # least significant byte has highest bit unset 45 result = [value % 0x80] 46 value = value // 0x80 47 48 while value != 0: 49 result = [0x80 | (value % 0x80)] + result 50 value = value // 0x80 51 52 return result 53 54 55 def dottedOIDToEncodedArray(dottedOID): 56 """ 57 Takes a dotted OID string (e.g. '1.2.840.10045.4.3.4') as input, and 58 returns an array that contains the DER encoding of its value, without 59 the tag and length (e.g. [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04]). 60 """ 61 nodes = [int(x) for x in dottedOID.strip().split(".")] 62 if len(nodes) < 2: 63 raise ValueError("An OID must have at least two nodes.") 64 if not (0 <= nodes[0] <= 2): 65 raise ValueError("The first node of an OID must be 0, 1, or 2.") 66 if not (0 <= nodes[1] <= 39): 67 # XXX: Does this restriction apply when the first part is 2? 68 raise ValueError("The second node of an OID must be 0-39.") 69 firstByte = (40 * nodes[0]) + nodes[1] 70 restBase128 = [base128(x) for x in nodes[2:]] 71 return [firstByte] + list(itertools.chain.from_iterable(restBase128)) 72 73 74 def dottedOIDToCArray(dottedOID, mode): 75 """ 76 Takes a dotted OID string (e.g. '1.2.840.10045.4.3.4') as input, and 77 returns a string that contains the hex encoding of the OID in C++ literal 78 notation, e.g. '0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04'. 79 """ 80 bytes = dottedOIDToEncodedArray(dottedOID) 81 82 if mode != "value" and mode != "prefixdefine": 83 bytes = [0x06, len(bytes)] + bytes 84 85 if mode == "alg": 86 # Wrap the DER-encoded OID in a SEQUENCE to create an 87 # AlgorithmIdentifier with no parameters. 88 bytes = [0x30, len(bytes)] + bytes 89 90 return ", ".join(["0x%.2x" % b for b in bytes]) 91 92 93 def specNameToCName(specName): 94 """ 95 Given an string containing an ASN.1 name, returns a string that is a valid 96 C++ identifier that is as similar to that name as possible. Since most 97 ASN.1 identifiers used in PKIX specifications are legal C++ names except 98 for containing hyphens, this function just converts the hyphens to 99 underscores. This may need to be improved in the future if we encounter 100 names with other funny characters. 101 """ 102 return specName.replace("-", "_") 103 104 105 def toCode(programName, specName, dottedOID, mode): 106 """ 107 Given an ASN.1 name and a string containing the dotted representation of an 108 OID, returns a string that contains a C++ declaration for a named constant 109 that contains that OID value. If mode is "value" then only the value of 110 the OID (without the tag or length) will be included in the output. If mode 111 is "tlv" then the value will be prefixed with the tag and length. If mode 112 is "alg" then the value will be a complete der-encoded AlgorithmIdentifier 113 with no parameters. 114 115 This: 116 117 toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4", 118 "value") 119 120 would result in a string like: 121 122 // python DottedOIDToCode.py ecdsa-with-SHA512 1.2.840.10045.4.3.4 123 static const uint8_t ecdsa_with_SHA512[] = { 124 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 125 }; 126 127 This: 128 129 toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4", 130 "tlv") 131 132 would result in a string like: 133 134 // python DottedOIDToCode.py --tlv ecdsa-with-SHA512 1.2.840.10045.4.3.4 135 static const uint8_t tlv_ecdsa_with_SHA512[] = { 136 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 137 }; 138 139 This: 140 141 toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4", 142 "alg") 143 144 would result in a string like: 145 146 // python DottedOIDToCode.py --alg ecdsa-with-SHA512 1.2.840.10045.4.3.4 147 static const uint8_t alg_ecdsa_with_SHA512[] = { 148 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 149 }; 150 151 This: 152 153 toCode("DottedOIDToCode.py", "PREFIX_1_2_840_10045", "1.2.840.10045", 154 "prefixdefine") 155 156 would result in a string like this (note the lack of indention): 157 158 // python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10045 1.2.840.10045 159 #define PREFIX_1_2_840_10045 0x2a, 0x86, 0x48, 0xce, 0x3d 160 """ 161 programNameWithOptions = programName 162 163 if mode == "prefixdefine": 164 programNameWithOptions += " --prefixdefine" 165 varName = specName 166 return ("// python %s %s %s\n" + 167 "#define %s %s\n") % (programNameWithOptions, specName, 168 dottedOID, varName, 169 dottedOIDToCArray(dottedOID, mode)) 170 171 varName = specNameToCName(specName) 172 if mode == "tlv": 173 programNameWithOptions += " --tlv" 174 varName = "tlv_" + varName 175 elif mode == "alg": 176 programNameWithOptions += " --alg" 177 varName = "alg_" + varName 178 elif mode == "prefixdefine": 179 programNameWithOptions += " --alg" 180 varName = varName 181 182 return (" // python %s %s %s\n" + 183 " static const uint8_t %s[] = {\n" + 184 " %s\n" + 185 " };\n") % (programNameWithOptions, specName, dottedOID, varName, 186 dottedOIDToCArray(dottedOID, mode)) 187 188 189 if __name__ == "__main__": 190 parser = argparse.ArgumentParser( 191 description="Generate code snippets to handle OIDs in C++", 192 epilog="example: python %s ecdsa-with-SHA1 1.2.840.10045.4.1" 193 % sys.argv[0]) 194 group = parser.add_mutually_exclusive_group() 195 group.add_argument("--tlv", action='store_true', 196 help="Wrap the encoded OID value with the tag and length") 197 group.add_argument("--alg", action='store_true', 198 help="Wrap the encoded OID value in an encoded SignatureAlgorithm") 199 group.add_argument("--prefixdefine", action='store_true', 200 help="generate a OID prefix #define") 201 parser.add_argument("name", 202 help="The name given to the OID in the specification") 203 parser.add_argument("dottedOID", metavar="dotted-oid", 204 help="The OID value, in dotted notation") 205 206 args = parser.parse_args() 207 if args.alg: 208 mode = 'alg' 209 elif args.tlv: 210 mode = 'tlv' 211 elif args.prefixdefine: 212 mode = 'prefixdefine' 213 else: 214 mode = 'value' 215 216 print(toCode(sys.argv[0], args.name, args.dottedOID, mode))