// Copyright (c) 2016, Google Inc. // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. package main import ( "bufio" "bytes" "errors" "fmt" "io/ioutil" "os" "os/exec" "sort" "strconv" "strings" ) func sanitizeName(in string) string { in = strings.Replace(in, "-", "_", -1) in = strings.Replace(in, ".", "_", -1) in = strings.Replace(in, " ", "_", -1) return in } type object struct { name string // shortName and longName are the short and long names, respectively. If // one is missing, it takes the value of the other, but the // corresponding SN_foo or LN_foo macro is not defined. shortName, longName string hasShortName, hasLongName bool oid []int encoded []byte } type objects struct { // byNID is the list of all objects, indexed by nid. byNID []object // nameToNID is a map from object name to nid. nameToNID map[string]int } func readNumbers(path string) (nameToNID map[string]int, numNIDs int, err error) { in, err := os.Open(path) if err != nil { return nil, 0, err } defer in.Close() nameToNID = make(map[string]int) nidsSeen := make(map[int]struct{}) // Reserve NID 0 for NID_undef. numNIDs = 1 nameToNID["undef"] = 0 nidsSeen[0] = struct{}{} var lineNo int scanner := bufio.NewScanner(in) for scanner.Scan() { line := scanner.Text() lineNo++ withLine := func(err error) error { return fmt.Errorf("%s:%d: %s", path, lineNo, err) } fields := strings.Fields(line) if len(fields) == 0 { // Skip blank lines. continue } // Each line is a name and a nid, separated by space. if len(fields) != 2 { return nil, 0, withLine(errors.New("syntax error")) } name := fields[0] nid, err := strconv.Atoi(fields[1]) if err != nil { return nil, 0, withLine(err) } if nid < 0 { return nil, 0, withLine(errors.New("invalid NID")) } // NID_undef is implicitly defined. if name == "undef" && nid == 0 { continue } // Forbid duplicates. if _, ok := nameToNID[name]; ok { return nil, 0, withLine(fmt.Errorf("duplicate name %q", name)) } if _, ok := nidsSeen[nid]; ok { return nil, 0, withLine(fmt.Errorf("duplicate NID %d", nid)) } nameToNID[name] = nid nidsSeen[nid] = struct{}{} if nid >= numNIDs { numNIDs = nid + 1 } } if err := scanner.Err(); err != nil { return nil, 0, fmt.Errorf("error reading %s: %s", path, err) } return nameToNID, numNIDs, nil } func parseOID(aliases map[string][]int, in []string) (oid []int, err error) { if len(in) == 0 { return } // The first entry may be a reference to a previous alias. if alias, ok := aliases[sanitizeName(in[0])]; ok { in = in[1:] oid = append(oid, alias...) } for _, c := range in { val, err := strconv.Atoi(c) if err != nil { return nil, err } if val < 0 { return nil, fmt.Errorf("negative component") } oid = append(oid, val) } return } func appendBase128(dst []byte, value int) []byte { // Zero is encoded with one, not zero bytes. if value == 0 { return append(dst, 0) } // Count how many bytes are needed. var l int for n := value; n != 0; n >>= 7 { l++ } for ; l > 0; l-- { b := byte(value>>uint(7*(l-1))) & 0x7f if l > 1 { b |= 0x80 } dst = append(dst, b) } return dst } func encodeOID(oid []int) []byte { if len(oid) < 2 { return nil } var der []byte der = appendBase128(der, 40*oid[0]+oid[1]) for _, value := range oid[2:] { der = appendBase128(der, value) } return der } func readObjects(numPath, objectsPath string) (*objects, error) { nameToNID, numNIDs, err := readNumbers(numPath) if err != nil { return nil, err } in, err := os.Open(objectsPath) if err != nil { return nil, err } defer in.Close() // Implicitly define NID_undef. objs := &objects{ byNID: make([]object, numNIDs), nameToNID: make(map[string]int), } objs.byNID[0] = object{ name: "undef", shortName: "UNDEF", longName: "undefined", hasShortName: true, hasLongName: true, } objs.nameToNID["undef"] = 0 var module, nextName string var lineNo int longNamesSeen := make(map[string]struct{}) shortNamesSeen := make(map[string]struct{}) aliases := make(map[string][]int) scanner := bufio.NewScanner(in) for scanner.Scan() { line := scanner.Text() lineNo++ withLine := func(err error) error { return fmt.Errorf("%s:%d: %s", objectsPath, lineNo, err) } // Remove comments. idx := strings.IndexRune(line, '#') if idx >= 0 { line = line[:idx] } // Skip empty lines. line = strings.TrimSpace(line) if len(line) == 0 { continue } if line[0] == '!' { args := strings.Fields(line) switch args[0] { case "!module": if len(args) != 2 { return nil, withLine(errors.New("too many arguments")) } module = sanitizeName(args[1]) + "_" case "!global": module = "" case "!Cname": // !Cname directives override the name for the // next object. if len(args) != 2 { return nil, withLine(errors.New("too many arguments")) } nextName = sanitizeName(args[1]) case "!Alias": // !Alias directives define an alias for an OID // without emitting an object. if len(nextName) != 0 { return nil, withLine(errors.New("!Cname directives may not modify !Alias directives.")) } if len(args) < 3 { return nil, withLine(errors.New("not enough arguments")) } aliasName := module + sanitizeName(args[1]) oid, err := parseOID(aliases, args[2:]) if err != nil { return nil, withLine(err) } if _, ok := aliases[aliasName]; ok { return nil, withLine(fmt.Errorf("duplicate name '%s'", aliasName)) } aliases[aliasName] = oid default: return nil, withLine(fmt.Errorf("unknown directive '%s'", args[0])) } continue } fields := strings.Split(line, ":") if len(fields) < 2 || len(fields) > 3 { return nil, withLine(errors.New("invalid field count")) } obj := object{name: nextName} nextName = "" var err error obj.oid, err = parseOID(aliases, strings.Fields(fields[0])) if err != nil { return nil, withLine(err) } obj.encoded = encodeOID(obj.oid) obj.shortName = strings.TrimSpace(fields[1]) if len(fields) == 3 { obj.longName = strings.TrimSpace(fields[2]) } // Long and short names default to each other if missing. if len(obj.shortName) == 0 { obj.shortName = obj.longName } else { obj.hasShortName = true } if len(obj.longName) == 0 { obj.longName = obj.shortName } else { obj.hasLongName = true } if len(obj.shortName) == 0 || len(obj.longName) == 0 { return nil, withLine(errors.New("object with no name")) } // If not already specified, prefer the long name if it has no // spaces, otherwise the short name. if len(obj.name) == 0 && strings.IndexRune(obj.longName, ' ') < 0 { obj.name = sanitizeName(obj.longName) } if len(obj.name) == 0 { obj.name = sanitizeName(obj.shortName) } obj.name = module + obj.name // Check for duplicate names. if _, ok := aliases[obj.name]; ok { return nil, withLine(fmt.Errorf("duplicate name '%s'", obj.name)) } if _, ok := shortNamesSeen[obj.shortName]; ok && len(obj.shortName) > 0 { return nil, withLine(fmt.Errorf("duplicate short name '%s'", obj.shortName)) } if _, ok := longNamesSeen[obj.longName]; ok && len(obj.longName) > 0 { return nil, withLine(fmt.Errorf("duplicate long name '%s'", obj.longName)) } // Allocate a NID. nid, ok := nameToNID[obj.name] if !ok { nid = len(objs.byNID) objs.byNID = append(objs.byNID, object{}) } objs.byNID[nid] = obj objs.nameToNID[obj.name] = nid longNamesSeen[obj.longName] = struct{}{} shortNamesSeen[obj.shortName] = struct{}{} aliases[obj.name] = obj.oid } if err := scanner.Err(); err != nil { return nil, err } return objs, nil } func writeNumbers(path string, objs *objects) error { out, err := os.Create(path) if err != nil { return err } defer out.Close() for nid, obj := range objs.byNID { if len(obj.name) == 0 { continue } if _, err := fmt.Fprintf(out, "%s\t\t%d\n", obj.name, nid); err != nil { return err } } return nil } func clangFormat(input string) (string, error) { var b bytes.Buffer cmd := exec.Command("clang-format") cmd.Stdin = strings.NewReader(input) cmd.Stdout = &b cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return "", err } return b.String(), nil } func writeHeader(path string, objs *objects) error { var b bytes.Buffer fmt.Fprintf(&b, `/* Copyright (C) 1995-1997 Eric Young (eay@cryptsoft.com) * All rights reserved. * * This package is an SSL implementation written * by Eric Young (eay@cryptsoft.com). * The implementation was written so as to conform with Netscapes SSL. * * This library is free for commercial and non-commercial use as long as * the following conditions are aheared to. The following conditions * apply to all code found in this distribution, be it the RC4, RSA, * lhash, DES, etc., code; not just the SSL code. The SSL documentation * included with this distribution is covered by the same copyright terms * except that the holder is Tim Hudson (tjh@cryptsoft.com). * * Copyright remains Eric Young's, and as such any Copyright notices in * the code are not to be removed. * If this package is used in a product, Eric Young should be given attribution * as the author of the parts of the library used. * This can be in the form of a textual message at program startup or * in documentation (online or textual) provided with the package. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * "This product includes cryptographic software written by * Eric Young (eay@cryptsoft.com)" * The word 'cryptographic' can be left out if the rouines from the library * being used are not cryptographic related :-). * 4. If you include any Windows specific code (or a derivative thereof) from * the apps directory (application code) you must include an acknowledgement: * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" * * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG `+"``"+`AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * The licence and distribution terms for any publically available version or * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] */ /* This file is generated by crypto/obj/objects.go. */ #ifndef OPENSSL_HEADER_NID_H #define OPENSSL_HEADER_NID_H #include #if defined(__cplusplus) extern "C" { #endif /* The nid library provides numbered values for ASN.1 object identifiers and * other symbols. These values are used by other libraries to identify * cryptographic primitives. * * A separate objects library, obj.h, provides functions for converting between * nids and object identifiers. However it depends on large internal tables with * the encodings of every nid defined. Consumers concerned with binary size * should instead embed the encodings of the few consumed OIDs and compare * against those. * * These values should not be used outside of a single process; they are not * stable identifiers. */ `) for nid, obj := range objs.byNID { if len(obj.name) == 0 { continue } if obj.hasShortName { fmt.Fprintf(&b, "#define SN_%s \"%s\"\n", obj.name, obj.shortName) } if obj.hasLongName { fmt.Fprintf(&b, "#define LN_%s \"%s\"\n", obj.name, obj.longName) } fmt.Fprintf(&b, "#define NID_%s %d\n", obj.name, nid) // Although NID_undef does not have an OID, OpenSSL emits // OBJ_undef as if it were zero. oid := obj.oid if nid == 0 { oid = []int{0} } if len(oid) != 0 { var oidStr string for _, val := range oid { if len(oidStr) != 0 { oidStr += "," } oidStr += fmt.Sprintf("%dL", val) } fmt.Fprintf(&b, "#define OBJ_%s %s\n", obj.name, oidStr) } fmt.Fprintf(&b, "\n") } fmt.Fprintf(&b, ` #if defined(__cplusplus) } /* extern C */ #endif #endif /* OPENSSL_HEADER_NID_H */ `) formatted, err := clangFormat(b.String()) if err != nil { return err } return ioutil.WriteFile(path, []byte(formatted), 0666) } // TODO(davidben): Replace this with sort.Slice once Go 1.8 is sufficiently // common. type nidSorter struct { nids []int objs *objects cmp func(a, b object) bool } func (a nidSorter) obj(i int) object { return a.objs.byNID[a.nids[i]] } func (a nidSorter) Len() int { return len(a.nids) } func (a nidSorter) Swap(i, j int) { a.nids[i], a.nids[j] = a.nids[j], a.nids[i] } func (a nidSorter) Less(i, j int) bool { return a.cmp(a.obj(i), a.obj(j)) } func sortNIDs(nids []int, objs *objects, cmp func(a, b object) bool) { sort.Sort(&nidSorter{nids, objs, cmp}) } func writeData(path string, objs *objects) error { var b bytes.Buffer fmt.Fprintf(&b, `/* Copyright (C) 1995-1997 Eric Young (eay@cryptsoft.com) * All rights reserved. * * This package is an SSL implementation written * by Eric Young (eay@cryptsoft.com). * The implementation was written so as to conform with Netscapes SSL. * * This library is free for commercial and non-commercial use as long as * the following conditions are aheared to. The following conditions * apply to all code found in this distribution, be it the RC4, RSA, * lhash, DES, etc., code; not just the SSL code. The SSL documentation * included with this distribution is covered by the same copyright terms * except that the holder is Tim Hudson (tjh@cryptsoft.com). * * Copyright remains Eric Young's, and as such any Copyright notices in * the code are not to be removed. * If this package is used in a product, Eric Young should be given attribution * as the author of the parts of the library used. * This can be in the form of a textual message at program startup or * in documentation (online or textual) provided with the package. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * "This product includes cryptographic software written by * Eric Young (eay@cryptsoft.com)" * The word 'cryptographic' can be left out if the rouines from the library * being used are not cryptographic related :-). * 4. If you include any Windows specific code (or a derivative thereof) from * the apps directory (application code) you must include an acknowledgement: * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" * * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG `+"``"+`AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * The licence and distribution terms for any publically available version or * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] */ /* This file is generated by crypto/obj/objects.go. */ `) fmt.Fprintf(&b, "#define NUM_NID %d\n", len(objs.byNID)) // Emit each object's DER encoding, concatenated, and save the offsets. fmt.Fprintf(&b, "\nstatic const uint8_t kObjectData[] = {\n") offsets := make([]int, len(objs.byNID)) var nextOffset int for nid, obj := range objs.byNID { if len(obj.name) == 0 || len(obj.encoded) == 0 { offsets[nid] = -1 continue } offsets[nid] = nextOffset nextOffset += len(obj.encoded) fmt.Fprintf(&b, "/* NID_%s */\n", obj.name) for _, val := range obj.encoded { fmt.Fprintf(&b, "0x%02x, ", val) } fmt.Fprintf(&b, "\n") } fmt.Fprintf(&b, "};\n") // Emit an ASN1_OBJECT for each object. fmt.Fprintf(&b, "\nstatic const ASN1_OBJECT kObjects[NUM_NID] = {\n") for nid, obj := range objs.byNID { if len(obj.name) == 0 { fmt.Fprintf(&b, "{NULL, NULL, NID_undef, 0, NULL, 0},\n") continue } fmt.Fprintf(&b, "{\"%s\", \"%s\", NID_%s, ", obj.shortName, obj.longName, obj.name) if offset := offsets[nid]; offset >= 0 { fmt.Fprintf(&b, "%d, &kObjectData[%d], 0},\n", len(obj.encoded), offset) } else { fmt.Fprintf(&b, "0, NULL, 0},\n") } } fmt.Fprintf(&b, "};\n") // Emit a list of NIDs sorted by short name. var nids []int for nid, obj := range objs.byNID { if len(obj.name) == 0 || len(obj.shortName) == 0 { continue } nids = append(nids, nid) } sortNIDs(nids, objs, func(a, b object) bool { return a.shortName < b.shortName }) fmt.Fprintf(&b, "\nstatic const unsigned kNIDsInShortNameOrder[] = {\n") for _, nid := range nids { fmt.Fprintf(&b, "%d /* %s */,\n", nid, objs.byNID[nid].shortName) } fmt.Fprintf(&b, "};\n") // Emit a list of NIDs sorted by long name. nids = nil for nid, obj := range objs.byNID { if len(obj.name) == 0 || len(obj.longName) == 0 { continue } nids = append(nids, nid) } sortNIDs(nids, objs, func(a, b object) bool { return a.longName < b.longName }) fmt.Fprintf(&b, "\nstatic const unsigned kNIDsInLongNameOrder[] = {\n") for _, nid := range nids { fmt.Fprintf(&b, "%d /* %s */,\n", nid, objs.byNID[nid].longName) } fmt.Fprintf(&b, "};\n") // Emit a list of NIDs sorted by OID. nids = nil for nid, obj := range objs.byNID { if len(obj.name) == 0 || len(obj.encoded) == 0 { continue } nids = append(nids, nid) } sortNIDs(nids, objs, func(a, b object) bool { // This comparison must match the definition of |obj_cmp|. if len(a.encoded) < len(b.encoded) { return true } if len(a.encoded) > len(b.encoded) { return false } return bytes.Compare(a.encoded, b.encoded) < 0 }) fmt.Fprintf(&b, "\nstatic const unsigned kNIDsInOIDOrder[] = {\n") for _, nid := range nids { obj := objs.byNID[nid] fmt.Fprintf(&b, "%d /* ", nid) for i, c := range obj.oid { if i > 0 { fmt.Fprintf(&b, ".") } fmt.Fprintf(&b, "%d", c) } fmt.Fprintf(&b, " (OBJ_%s) */,\n", obj.name) } fmt.Fprintf(&b, "};\n") formatted, err := clangFormat(b.String()) if err != nil { return err } return ioutil.WriteFile(path, []byte(formatted), 0666) } func main() { objs, err := readObjects("obj_mac.num", "objects.txt") if err != nil { fmt.Fprintf(os.Stderr, "Error reading objects: %s\n", err) os.Exit(1) } if err := writeNumbers("obj_mac.num", objs); err != nil { fmt.Fprintf(os.Stderr, "Error writing numbers: %s\n", err) os.Exit(1) } if err := writeHeader("../../include/openssl/nid.h", objs); err != nil { fmt.Fprintf(os.Stderr, "Error writing header: %s\n", err) os.Exit(1) } if err := writeData("obj_dat.h", objs); err != nil { fmt.Fprintf(os.Stderr, "Error writing data: %s\n", err) os.Exit(1) } }