| # This code is made available to you under your choice of the following sets |
| # of licensing terms: |
| ############################################################################### |
| # This Source Code Form is subject to the terms of the Mozilla Public |
| # License, v. 2.0. If a copy of the MPL was not distributed with this |
| # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| ############################################################################### |
| # Copyright 2013 Mozilla Contributors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| from __future__ import print_function |
| import argparse |
| import itertools |
| import sys |
| |
| |
| def base128(value): |
| """ |
| Given an integral value, returns an array of the base-128 representation |
| of that value, with all but the last byte having the high bit set as |
| required by the DER rules for the nodes of an OID after the first two |
| bytes. |
| |
| >>> base128(1) |
| [1] |
| >>> base128(10045) |
| [206, 61] |
| """ |
| |
| if value < 0: |
| raise ValueError("An OID must have only positive-value nodes.") |
| |
| # least significant byte has highest bit unset |
| result = [value % 0x80] |
| value /= 0x80 |
| |
| while value != 0: |
| result = [0x80 | (value % 0x80)] + result |
| value /= 0x80 |
| |
| return result |
| |
| |
| def dottedOIDToEncodedArray(dottedOID): |
| """ |
| Takes a dotted OID string (e.g. '1.2.840.10045.4.3.4') as input, and |
| returns an array that contains the DER encoding of its value, without |
| the tag and length (e.g. [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04]). |
| """ |
| nodes = [int(x) for x in dottedOID.strip().split(".")] |
| if len(nodes) < 2: |
| raise ValueError("An OID must have at least two nodes.") |
| if not (0 <= nodes[0] <= 2): |
| raise ValueError("The first node of an OID must be 0, 1, or 2.") |
| if not (0 <= nodes[1] <= 39): |
| # XXX: Does this restriction apply when the first part is 2? |
| raise ValueError("The second node of an OID must be 0-39.") |
| firstByte = (40 * nodes[0]) + nodes[1] |
| restBase128 = [base128(x) for x in nodes[2:]] |
| return [firstByte] + list(itertools.chain.from_iterable(restBase128)) |
| |
| |
| def dottedOIDToCArray(dottedOID, mode): |
| """ |
| Takes a dotted OID string (e.g. '1.2.840.10045.4.3.4') as input, and |
| returns a string that contains the hex encoding of the OID in C++ literal |
| notation, e.g. '0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04'. |
| """ |
| bytes = dottedOIDToEncodedArray(dottedOID) |
| |
| if mode != "value" and mode != "prefixdefine": |
| bytes = [0x06, len(bytes)] + bytes |
| |
| if mode == "alg": |
| # Wrap the DER-encoded OID in a SEQUENCE to create an |
| # AlgorithmIdentifier with no parameters. |
| bytes = [0x30, len(bytes)] + bytes |
| |
| return ", ".join(["0x%.2x" % b for b in bytes]) |
| |
| |
| def specNameToCName(specName): |
| """ |
| Given an string containing an ASN.1 name, returns a string that is a valid |
| C++ identifier that is as similar to that name as possible. Since most |
| ASN.1 identifiers used in PKIX specifications are legal C++ names except |
| for containing hyphens, this function just converts the hyphens to |
| underscores. This may need to be improved in the future if we encounter |
| names with other funny characters. |
| """ |
| return specName.replace("-", "_") |
| |
| |
| def toCode(programName, specName, dottedOID, mode): |
| """ |
| Given an ASN.1 name and a string containing the dotted representation of an |
| OID, returns a string that contains a C++ declaration for a named constant |
| that contains that OID value. If mode is "value" then only the value of |
| the OID (without the tag or length) will be included in the output. If mode |
| is "tlv" then the value will be prefixed with the tag and length. If mode |
| is "alg" then the value will be a complete der-encoded AlgorithmIdentifier |
| with no parameters. |
| |
| This: |
| |
| toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4", |
| "value") |
| |
| would result in a string like: |
| |
| // python DottedOIDToCode.py ecdsa-with-SHA512 1.2.840.10045.4.3.4 |
| static const uint8_t ecdsa_with_SHA512[] = { |
| 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 |
| }; |
| |
| This: |
| |
| toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4", |
| "tlv") |
| |
| would result in a string like: |
| |
| // python DottedOIDToCode.py --tlv ecdsa-with-SHA512 1.2.840.10045.4.3.4 |
| static const uint8_t tlv_ecdsa_with_SHA512[] = { |
| 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 |
| }; |
| |
| This: |
| |
| toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4", |
| "alg") |
| |
| would result in a string like: |
| |
| // python DottedOIDToCode.py --alg ecdsa-with-SHA512 1.2.840.10045.4.3.4 |
| static const uint8_t alg_ecdsa_with_SHA512[] = { |
| 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 |
| }; |
| |
| This: |
| |
| toCode("DottedOIDToCode.py", "PREFIX_1_2_840_10045", "1.2.840.10045", |
| "prefixdefine") |
| |
| would result in a string like this (note the lack of indention): |
| |
| // python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10045 1.2.840.10045 |
| #define PREFIX_1_2_840_10045 0x2a, 0x86, 0x48, 0xce, 0x3d |
| """ |
| programNameWithOptions = programName |
| |
| if mode == "prefixdefine": |
| programNameWithOptions += " --prefixdefine" |
| varName = specName |
| return ("// python %s %s %s\n" + |
| "#define %s %s\n") % (programNameWithOptions, specName, |
| dottedOID, varName, |
| dottedOIDToCArray(dottedOID, mode)) |
| |
| varName = specNameToCName(specName) |
| if mode == "tlv": |
| programNameWithOptions += " --tlv" |
| varName = "tlv_" + varName |
| elif mode == "alg": |
| programNameWithOptions += " --alg" |
| varName = "alg_" + varName |
| elif mode == "prefixdefine": |
| programNameWithOptions += " --alg" |
| varName = varName |
| |
| return (" // python %s %s %s\n" + |
| " static const uint8_t %s[] = {\n" + |
| " %s\n" + |
| " };\n") % (programNameWithOptions, specName, dottedOID, varName, |
| dottedOIDToCArray(dottedOID, mode)) |
| |
| |
| if __name__ == "__main__": |
| parser = argparse.ArgumentParser( |
| description="Generate code snippets to handle OIDs in C++", |
| epilog="example: python %s ecdsa-with-SHA1 1.2.840.10045.4.1" |
| % sys.argv[0]) |
| group = parser.add_mutually_exclusive_group() |
| group.add_argument("--tlv", action='store_true', |
| help="Wrap the encoded OID value with the tag and length") |
| group.add_argument("--alg", action='store_true', |
| help="Wrap the encoded OID value in an encoded SignatureAlgorithm") |
| group.add_argument("--prefixdefine", action='store_true', |
| help="generate a OID prefix #define") |
| parser.add_argument("name", |
| help="The name given to the OID in the specification") |
| parser.add_argument("dottedOID", metavar="dotted-oid", |
| help="The OID value, in dotted notation") |
| |
| args = parser.parse_args() |
| if args.alg: |
| mode = 'alg' |
| elif args.tlv: |
| mode = 'tlv' |
| elif args.prefixdefine: |
| mode = 'prefixdefine' |
| else: |
| mode = 'value' |
| |
| print(toCode(sys.argv[0], args.name, args.dottedOID, mode)) |