All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tomitribe.churchkey.asn1.Oid Maven / Gradle / Ivy

/*
 * Copyright 2021 Tomitribe and community
 *
 * 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
 *
 *     https://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.
 */
package org.tomitribe.churchkey.asn1;

import org.tomitribe.util.Hex;
import org.tomitribe.util.Join;

import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.StreamCorruptedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * OIDs are encoded using Variable-Length Quantity.
 *
 * Good resources for understanding OID encoding
 *
 * https://en.wikipedia.org/wiki/Variable-length_quantity
 * https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier?redirectedfrom=MSDN
 */
public class Oid {

    final List oid = new ArrayList<>();

    public Oid(final int... oid) {
        for (final int i : oid) {
            this.oid.add(i);
        }
    }

    public Oid(final List oid) {
        this.oid.addAll(oid);
    }

    public int length() {
        return oid.size();
    }

    public int get(int index) {
        return oid.get(index);
    }

    public List getComponents() {
        return Collections.unmodifiableList(oid);
    }

    @Override
    public String toString() {
        return Join.join(".", oid);
    }

    public String toHex() {
        return Hex.toString(toBytes());
    }

    public byte[] toBytes() {

        final Iterator values = oid.iterator();
        final Integer one = values.next();
        final Integer two = values.next();

        final ByteArrayOutputStream out = new ByteArrayOutputStream();

        int val1 = one * 40 + two;
        out.write(val1);

        while (values.hasNext()) {
            int elem = values.next();

            if (elem <= 0b1111111) {
                out.write(elem);
            } else {
                final byte[] bytes = new byte[5];
                int pos = bytes.length - 1;

                bytes[pos--] = (byte) (elem & 0b1111111);
                elem = elem >> 7;
                while (elem > 0) {
                    bytes[pos--] = (byte) (elem | 0b10000000);
                    elem = elem >> 7;
                }
                pos++;
                final int length = bytes.length - pos;
                out.write(bytes, pos, length);
            }
        }
        return out.toByteArray();
    }

    public static Oid fromString(final String dottedIntegers) {
        final String[] strings = dottedIntegers.split("\\.");
        final List integers = Stream.of(strings)
                .map(Integer::valueOf)
                .collect(Collectors.toList());
        return new Oid(integers);
    }

    public static Oid fromHex(final String hex) throws IOException {
        final byte[] bytes = Hex.fromString(hex);
        return fromBytes(bytes);
    }

    public static Oid oid(final int... oid) {
        return new Oid(oid);
    }

    public static Oid fromBytes(final byte[] bytes) throws IOException {
        final int length = bytes.length;
        if (length <= 0) {
            throw new EOFException("Not enough data for an OID");
        }

        /*
         * Java bytes are signed (-128 to 127).
         * We need unsigned values (0 to 255)
         */
        final int toPositiveNumber = 0b11111111;

        /*
         * Anything 7 bits or under is valid as a
         * short form (not encoded) number
         */
        final int shortForm = 0b01111111;

        final List oid = new ArrayList<>(length + 1);

        /*
         * The first value is treated specially.  The first
         * to values of the OID are encoded in it using
         * the rule below.  This only works because there
         * are limits on how big these two numbers can be.
         */
        final int firstValue = bytes[0] & toPositiveNumber;
        oid.add(Integer.valueOf(firstValue / 40));
        oid.add(Integer.valueOf(firstValue % 40));

        /*
         * Now read each subsequent OID value from the remaining bytes
         */
        for (int position = 1; position < length; position++) {
            int b = bytes[position] & toPositiveNumber;

            /*
             * If the value can fit into 7 bits, we can simply
             * use it as-is.
             */
            if (b <= shortForm) { // short form
                oid.add(Integer.valueOf(b));
                continue;
            }

            /*
             * The value cannot fit into 7 bits, so we need
             * to read the next byte, chop off the 8th bit
             * so it is also 7 bits, then shift those 7
             * bits onto the value we just read.
             *
             * This can happen at most 5 times (for 5 bytes)
             * because OID numbers can only be 32 bits max.
             *
             * Basically we'll build the number 7 bits at a time
             * reading at most 5 bytes in the process.
             */
            long value = b & shortForm;
            position++;

            for (int subLen = 1; ; subLen++, position++) {
                if (position >= length) {
                    throw new EOFException("Incomplete OID value");
                }

                if (subLen > 5) { // 32 bit values can span at most 5 octets
                    throw new StreamCorruptedException("OID component encoding beyond 5 bytes");
                }

                b = bytes[position] & toPositiveNumber;
                value = ((value << 7) & 0xFFFFFFFF80L) | (b & 0x7FL);
                if (value > Integer.MAX_VALUE) {
                    // 7 * 5 = 35
                    // This means we could potentially get a 35-bit number
                    throw new StreamCorruptedException("OID value exceeds 32 bits: " + value);
                }

                if (b <= shortForm) { // found last octet ?
                    break;
                }
            }

            oid.add(Integer.valueOf((int) (value & 0x7FFFFFFFL)));
        }

        return new Oid(oid);
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        final Oid oid1 = (Oid) o;

        if (!oid.equals(oid1.oid)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return oid.hashCode();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy