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

gurux.dlms.asn.GXAsn1Converter Maven / Gradle / Ivy

There is a newer version: 4.0.72
Show newest version
//
// --------------------------------------------------------------------------
//  Gurux Ltd

package gurux.dlms.asn;

import java.io.StringReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import gurux.dlms.GXByteBuffer;
import gurux.dlms.GXDLMSTranslator;
import gurux.dlms.GXSimpleEntry;
import gurux.dlms.asn.enums.Ecc;
import gurux.dlms.asn.enums.KeyUsage;
import gurux.dlms.asn.enums.PkcsType;
import gurux.dlms.asn.enums.X509Name;
import gurux.dlms.enums.BerType;
import gurux.dlms.internal.GXCommon;
import gurux.dlms.objects.enums.CertificateType;

/*
 * ASN1 converter. This class is used to convert 
 * public and private keys to byte array and vice verse.
 */
public final class GXAsn1Converter {

    /*
     * Constructor.
     */
    private GXAsn1Converter() {

    }

    /**
     * Returns default file path.
     * 
     * @param scheme
     *            Used scheme.
     * @param certificateType
     *            Certificate type.
     * @param systemTitle
     *            System title.
     * @return File path.
     */
    public static Path getFilePath(Ecc scheme, CertificateType certificateType, byte[] systemTitle) {
        Path path;
        switch (certificateType) {
        case DIGITAL_SIGNATURE:
            path = Paths.get("D");
            break;
        case KEY_AGREEMENT:
            path = Paths.get("A");
            break;
        case TLS:
            path = Paths.get("T");
            break;
        default:
            throw new IllegalArgumentException("Unknown certificate type.");
        }
        path = Paths.get(path.toString() + GXDLMSTranslator.toHex(systemTitle, false) + ".pem");
        if (scheme == Ecc.P256) {
            path = Paths.get("Keys", path.toString());
        } else {
            path = Paths.get("Keys384", path.toString());
        }
        return path;
    }

    /**
     * Get private key from bytes.
     * 
     * @param value
     *            Private key bytes.
     * @return Private key.
     * @throws InvalidKeySpecException
     *             Invalid key spec.
     * @throws NoSuchAlgorithmException
     *             No such Algorithm.
     */
    public static PrivateKey getPrivateKey(final byte[] value)
            throws InvalidKeySpecException, NoSuchAlgorithmException {
        if (value != null && (value.length == 32 || value.length == 48)) {
            byte[] privKeyBytes;
            if (value.length == 32) {
                privKeyBytes = GXCommon
                        .hexToBytes("3041020100301306072A8648CE3D0201" + "06082A8648CE3D030107 042730250201010420");
            } else {
                privKeyBytes =
                        GXCommon.hexToBytes("304E020100301006072A8648CE3D0201" + "06052B81040022 043730350201010430");
            }
            byte[] key = new byte[privKeyBytes.length + value.length];
            System.arraycopy(privKeyBytes, 0, key, 0, privKeyBytes.length);
            System.arraycopy(value, 0, key, privKeyBytes.length, value.length);
            PKCS8EncodedKeySpec priv = new PKCS8EncodedKeySpec(key);
            KeyFactory kf = KeyFactory.getInstance("EC");
            return kf.generatePrivate(priv);
        } else {
            throw new IllegalArgumentException("Invalid private key.");
        }
    }

    private static byte[] p256Head = GXCommon.fromBase64("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE");

    private static byte[] p384Head =
            GXCommon.hexToBytes("30 76 30 10 06 07 2A 86 48 CE 3D 02 01" + "06 05 2B 81 04 00 22 03 62 00 04");

    /**
     * Get public key from bytes.
     * 
     * @param value
     *            Public key bytes.
     * @return Public key.
     */
    public static PublicKey getPublicKey(final byte[] value) {
        if (value != null && (value.length == 64 || value.length == 96)) {
            byte[] head;
            if (value.length == 64) {
                head = p256Head;
            } else {
                head = p384Head;
            }
            byte[] encodedKey;
            encodedKey = new byte[head.length + value.length];
            System.arraycopy(head, 0, encodedKey, 0, head.length);
            System.arraycopy(value, 0, encodedKey, head.length, value.length);
            KeyFactory eckf;
            try {
                eckf = KeyFactory.getInstance("EC");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("EC key factory not present in runtime");
            }
            try {
                X509EncodedKeySpec ecpks = new X509EncodedKeySpec(encodedKey);
                return eckf.generatePublic(ecpks);
            } catch (InvalidKeySpecException e) {
                throw new IllegalArgumentException(e.getMessage());
            }

        } else {
            throw new IllegalArgumentException("Invalid public key.");
        }
    }

    /**
     * Convert public key to byte array.
     * 
     * @param key
     *            Public key.
     * @return Public key in byte array.
     */
    public static byte[] rawValue(final PublicKey key) {
        if (key == null) {
            throw new IllegalArgumentException("Invalid public key.");
        }
        GXAsn1BitString tmp =
                (GXAsn1BitString) ((GXAsn1Sequence) GXAsn1Converter.fromByteArray(key.getEncoded())).get(1);
        GXByteBuffer bb = new GXByteBuffer();
        if (key.getEncoded().length == 91) {
            bb.set(tmp.getValue(), 1, 64);
        } else if (key.getEncoded().length == 120) {
            bb.set(tmp.getValue(), 1, 96);
        } else {
            throw new IllegalArgumentException("Invalid public key.");
        }
        return bb.array();
    }

    /**
     * Convert private key to byte array.
     * 
     * @param key
     *            Public key.
     * @return Private key in byte array.
     */
    public static byte[] rawValue(final PrivateKey key) {
        if (key == null) {
            throw new IllegalArgumentException("Invalid private key.");
        }
        byte[] tmp =
                (byte[]) ((GXAsn1Sequence) ((GXAsn1Sequence) GXAsn1Converter.fromByteArray(key.getEncoded())).get(2))
                        .get(1);
        GXByteBuffer bb = new GXByteBuffer();
        bb.set(tmp);
        return bb.array();
    }

    static List> encodeSubject(final String value) {
        X509Name name;
        Object val;
        List> list = new ArrayList>();
        for (String tmp : value.split("[,]")) {
            String[] it = tmp.split("[=]");
            if (it.length != 2) {
                throw new IllegalArgumentException("Invalid subject.");
            }
            name = X509Name.valueOf(it[0].trim());
            switch (name) {
            case C:
                // Country code is printable string
                val = it[1].trim();
                break;
            case E:
                // email address in Verisign certificates
                val = new GXAsn1Ia5String(it[1].trim());
                break;
            default:
                val = new GXAsn1Utf8String(it[1].trim());
            }
            String oid = name.getValue();
            list.add(new GXSimpleEntry(new GXAsn1ObjectIdentifier(oid), val));
        }
        return list;
    }

    public static String getSubject(final GXAsn1Sequence values) {
        Object value;
        StringBuilder sb = new StringBuilder();
        for (Object tmp : values) {
            Map.Entry it = (Map.Entry) tmp;
            sb.append(X509Name.forValue(it.getKey().toString()));
            sb.append('=');
            value = it.getValue();
            sb.append(value);
            sb.append(", ");
        }
        // Remove last comma.
        if (sb.length() != 0) {
            sb.setLength(sb.length() - 2);
        }
        return sb.toString();
    }

    private static void getValue(final GXByteBuffer bb, final List objects, final GXAsn1Settings s,
            final boolean getNext) {
        int len;
        short type;
        List tmp = null;
        byte[] tmp2;
        type = bb.getUInt8();
        len = GXCommon.getObjectCount(bb);
        if (len > bb.size() - bb.position()) {
            throw new IllegalArgumentException("Not enought memory.");
        }
        int connectPos = 0;
        if (s != null) {
            connectPos = s.getXmlLength();
        }
        int start = bb.position();
        String tagString = null;
        if (s != null) {
            s.appendSpaces();
            if (type == BerType.INTEGER) {
                if (len == 1 || len == 2 || len == 4 || len == 8) {
                    tagString = s.getTag((short) -len);
                } else {
                    tagString = s.getTag(BerType.INTEGER);
                }
            } else {
                tagString = s.getTag(type);
            }
            s.append("<" + tagString + ">");
        }

        switch (type) {
        case BerType.CONSTRUCTED | BerType.CONTEXT:
        case BerType.CONSTRUCTED | BerType.CONTEXT | 1:
        case BerType.CONSTRUCTED | BerType.CONTEXT | 2:
        case BerType.CONSTRUCTED | BerType.CONTEXT | 3:
        case BerType.CONSTRUCTED | BerType.CONTEXT | 4:
        case BerType.CONSTRUCTED | BerType.CONTEXT | 5:
            if (s != null) {
                s.increase();
            }
            tmp = new GXAsn1Context();
            ((GXAsn1Context) tmp).setIndex(type & 0xF);
            objects.add(tmp);
            while (bb.position() < start + len) {
                getValue(bb, tmp, s, false);
            }
            if (s != null) {
                s.decrease();
            }
            break;
        case BerType.CONSTRUCTED | BerType.SEQUENCE:
            if (s != null) {
                s.increase();
            }
            tmp = new GXAsn1Sequence();
            objects.add(tmp);
            int cnt = 0;
            while (bb.position() < start + len) {
                ++cnt;
                getValue(bb, tmp, s, false);
                if (getNext) {
                    break;
                }
            }
            if (s != null) {
                // Append comment.
                s.appendComment(connectPos, String.valueOf(cnt) + " elements.");
                s.decrease();
            }
            break;
        case BerType.CONSTRUCTED | BerType.SET:
            if (s != null) {
                s.increase();
            }
            tmp = new ArrayList();
            getValue(bb, tmp, s, false);
            if (tmp.get(0) instanceof GXAsn1Sequence) {
                tmp = (GXAsn1Sequence) tmp.get(0);
                objects.add(new GXSimpleEntry(tmp.get(0), tmp.get(1)));
            } else {
                GXSimpleEntry e = new GXSimpleEntry(tmp, null);
                objects.add(e);
            }
            if (s != null) {
                s.decrease();
            }
            break;
        case BerType.OBJECT_IDENTIFIER:
        case BerType.CONTEXT | BerType.OBJECT_IDENTIFIER:
            GXAsn1ObjectIdentifier oi = new GXAsn1ObjectIdentifier(bb, len);
            objects.add(oi);
            if (s != null) {
                String str = oi.getDescription();
                if (str != null) {
                    s.appendComment(connectPos, str);
                }
                s.append(oi.toString());
            }

            break;
        case BerType.PRINTABLE_STRING:
            objects.add(bb.getString(len));
            if (s != null) {
                s.append(String.valueOf(objects.get(objects.size() - 1)));
            }

            break;
        case BerType.UTF8STRING:
            objects.add(new GXAsn1Utf8String(bb.getString(bb.position(), len, "UTF-8")));
            bb.position(bb.position() + len);
            if (s != null) {
                s.append(String.valueOf(objects.get(objects.size() - 1)));
            }

            break;
        case BerType.IA5_STRING:
            objects.add(new GXAsn1Ia5String(bb.getString(len)));
            if (s != null) {
                s.append(String.valueOf(objects.get(objects.size() - 1)));
            }
            break;
        case BerType.INTEGER:
            if (len == 1) {
                objects.add(bb.getInt8());
            } else if (len == 2) {
                objects.add(bb.getInt16());
            } else if (len == 4) {
                objects.add(bb.getInt32());
            } else {
                tmp2 = new byte[len];
                bb.get(tmp2);
                objects.add(new GXAsn1Integer(tmp2));
            }
            if (s != null) {
                s.append(String.valueOf(objects.get(objects.size() - 1)));
            }
            break;
        case BerType.NULL:
            objects.add(null);
            break;
        case BerType.BIT_STRING:
            GXAsn1BitString tmp3 = new GXAsn1BitString(bb.subArray(bb.position(), len));
            objects.add(tmp3);
            bb.position(bb.position() + len);
            if (s != null) {
                // Append comment.
                s.appendComment(connectPos, String.valueOf(tmp3.length()) + " bit.");
                s.append(tmp3.asString());
            }
            break;
        case BerType.UTC_TIME:
            tmp2 = new byte[len];
            bb.get(tmp2);
            objects.add(getUtcTime(new String(tmp2)));
            if (s != null) {
                DateFormat f = new SimpleDateFormat();
                s.append(f.format(objects.get(objects.size() - 1)));
            }
            break;
        case BerType.GENERALIZED_TIME:
            tmp2 = new byte[len];
            bb.get(tmp2);
            objects.add(GXCommon.getGeneralizedTime(new String(tmp2)));
            if (s != null) {
                s.append(String.valueOf(objects.get(objects.size() - 1)));
            }
            break;
        case BerType.CONTEXT:
        case BerType.CONTEXT | 1:
        case BerType.CONTEXT | 2:
        case BerType.CONTEXT | 3:
        case BerType.CONTEXT | 4:
            tmp = new GXAsn1Context();
            ((GXAsn1Context) tmp).setConstructed(false);
            ((GXAsn1Context) tmp).setIndex(type & 0xF);
            tmp2 = new byte[len];
            bb.get(tmp2);
            tmp.add(tmp2);
            objects.add(tmp);
            if (s != null) {
                s.append(GXCommon.toHex(tmp2));
            }
            break;
        case BerType.OCTET_STRING:
            int t = bb.getUInt8(bb.position());
            switch (t) {
            case BerType.CONSTRUCTED | BerType.SEQUENCE:
            case BerType.BIT_STRING:
                if (s != null) {
                    s.increase();
                }
                getValue(bb, objects, s, false);
                if (s != null) {
                    s.decrease();
                }
                break;
            default:
                tmp2 = new byte[len];
                bb.get(tmp2);
                objects.add(tmp2);
                if (s != null) {
                    s.append(GXCommon.toHex(tmp2));
                }
            }
            break;
        case BerType.BOOLEAN:
            boolean b = bb.getUInt8() != 0;
            objects.add(b);
            if (s != null) {
                s.append(String.valueOf(b));
            }
            break;
        default:
            throw new IllegalArgumentException("Invalid type: " + type);
        }
        if (s != null) {
            s.append("\r\n");
        }
    }

    private static Date getUtcTime(final String dateString) {
        int year, month, day, hour, minute, second = 0;
        Calendar calendar;
        year = 2000 + Integer.parseInt(dateString.substring(0, 2));
        month = Integer.parseInt(dateString.substring(2, 4)) - 1;
        day = Integer.parseInt(dateString.substring(4, 6));
        hour = Integer.parseInt(dateString.substring(6, 8));
        minute = Integer.parseInt(dateString.substring(8, 10));
        // If UTC time.
        if (dateString.endsWith("Z")) {
            if (dateString.length() > 11) {
                second = Integer.parseInt(dateString.substring(10, 12));
            }
            calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        } else {
            if (dateString.length() > 15) {
                second = Integer.parseInt(dateString.substring(10, 12));
            }
            calendar = Calendar.getInstance(TimeZone
                    .getTimeZone("GMT" + dateString.substring(dateString.length() - 6, dateString.length() - 1)));
        }
        calendar.set(year, month, day, hour, minute, second);
        return calendar.getTime();
    }

    private static String dateToString(final Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        long v = calendar.getTimeInMillis();
        calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
        calendar.setTimeInMillis(v);
        StringBuilder sb = new StringBuilder();
        sb.append(GXCommon.integerString(calendar.get(Calendar.YEAR) - 2000, 2));
        sb.append(GXCommon.integerString(1 + calendar.get(Calendar.MONTH), 2));
        sb.append(GXCommon.integerString(calendar.get(Calendar.DAY_OF_MONTH), 2));
        sb.append(GXCommon.integerString(calendar.get(Calendar.HOUR_OF_DAY), 2));
        sb.append(GXCommon.integerString(calendar.get(Calendar.MINUTE), 2));
        sb.append(GXCommon.integerString(calendar.get(Calendar.SECOND), 2));
        sb.append("Z");
        return sb.toString();
    }

    /**
     * Convert byte array to ASN1 objects.
     * 
     * @param data
     *            ASN-1 bytes.
     * @return Parsed objects.
     */
    public static Object fromByteArray(final byte[] data) {
        GXByteBuffer bb = new GXByteBuffer(data);
        List objects = new ArrayList();
        while (bb.position() != bb.size()) {
            getValue(bb, objects, null, false);
        }
        return objects.get(0);
    }

    /// 
    /// Get next ASN1 value from the byte buffer.
    /// 
    /// 
    /// 
    public static Object getNext(GXByteBuffer data) {
        List objects = new ArrayList();
        getValue(data, objects, null, true);
        return objects.get(0);
    }

    /**
     * Add ASN1 object to byte buffer.
     * 
     * @param bb
     *            Byte buffer where ANS1 object is serialized.
     * @param target
     *            ANS1 object
     * @return Size of object.
     */
    private static int getBytes(final GXByteBuffer bb, final Object target) {
        GXByteBuffer tmp;
        String str;
        int start = bb.size();
        int cnt = 0;
        if (target instanceof GXAsn1Context) {
            GXAsn1Context a = (GXAsn1Context) target;
            tmp = new GXByteBuffer();
            for (Object it : a) {
                cnt += getBytes(tmp, it);
            }
            start = bb.size();
            if (a.isConstructed()) {
                bb.setUInt8(BerType.CONSTRUCTED | BerType.CONTEXT | a.getIndex());
                GXCommon.setObjectCount(cnt, bb);
            } else {
                tmp.setUInt8(0, BerType.CONTEXT | a.getIndex());
            }
            cnt += bb.size() - start;
            bb.set(tmp);
            return cnt;
        } else if (target instanceof Object[]) {
            tmp = new GXByteBuffer();
            for (Object it : (Object[]) target) {
                cnt += getBytes(tmp, it);
            }
            start = bb.size();
            bb.setUInt8(BerType.CONSTRUCTED | BerType.SEQUENCE);
            GXCommon.setObjectCount(cnt, bb);
            cnt += bb.size() - start;
            bb.set(tmp);
            return cnt;
        } else if (target instanceof GXAsn1Sequence || target instanceof List) {
            tmp = new GXByteBuffer();
            for (Object it : (List) target) {
                cnt += getBytes(tmp, it);
            }
            start = bb.size();
            if (target instanceof GXAsn1Context) {
                GXAsn1Context c = (GXAsn1Context) target;
                if (c.isConstructed()) {
                    bb.setUInt8(BerType.CONSTRUCTED | BerType.SEQUENCE | c.getIndex());
                } else {
                    bb.setUInt8(BerType.SEQUENCE | c.getIndex());
                }
            } else {
                bb.setUInt8(BerType.CONSTRUCTED | BerType.SEQUENCE);
            }
            GXCommon.setObjectCount(cnt, bb);
            cnt += bb.size() - start;
            bb.set(tmp);
            return cnt;
        } else if (target instanceof String) {
            bb.setUInt8(BerType.PRINTABLE_STRING);
            GXCommon.setObjectCount(((String) target).length(), bb);
            bb.add(target);
        } else if (target instanceof Byte) {
            bb.setUInt8(BerType.INTEGER);
            GXCommon.setObjectCount(1, bb);
            bb.add(target);
        } else if (target instanceof Short) {
            bb.setUInt8(BerType.INTEGER);
            GXCommon.setObjectCount(2, bb);
            bb.add(target);
        } else if (target instanceof Integer) {
            bb.setUInt8(BerType.INTEGER);
            GXCommon.setObjectCount(4, bb);
            bb.add(target);
        } else if (target instanceof GXAsn1Integer) {
            bb.setUInt8(BerType.INTEGER);
            byte[] b = ((GXAsn1Integer) target).getByteArray();
            GXCommon.setObjectCount(b.length, bb);
            bb.set(b);
        } else if (target instanceof Long) {
            bb.setUInt8(BerType.INTEGER);
            GXCommon.setObjectCount(8, bb);
            bb.add(target);
        } else if (target instanceof byte[]) {
            bb.setUInt8(BerType.OCTET_STRING);
            GXCommon.setObjectCount(((byte[]) target).length, bb);
            bb.add(target);
        } else if (target == null) {
            bb.setUInt8(BerType.NULL);
            GXCommon.setObjectCount(0, bb);
        } else if (target instanceof Boolean) {
            bb.setUInt8(BerType.BOOLEAN);
            bb.setUInt8(1);
            if ((Boolean) target) {
                bb.setUInt8(255);
            } else {
                bb.setUInt8(0);
            }
        } else if (target instanceof GXAsn1ObjectIdentifier) {
            bb.setUInt8(BerType.OBJECT_IDENTIFIER);
            byte[] t = ((GXAsn1ObjectIdentifier) target).getEncoded();
            GXCommon.setObjectCount(t.length, bb);
            bb.add(t);
        } else if (target instanceof Entry) {
            Entry e = (Entry) target;
            GXByteBuffer tmp2 = new GXByteBuffer();
            if (e.getValue() != null) {
                tmp = new GXByteBuffer();
                cnt += getBytes(tmp2, e.getKey());
                cnt += getBytes(tmp2, e.getValue());
                tmp.setUInt8(BerType.CONSTRUCTED | BerType.SEQUENCE);
                GXCommon.setObjectCount(cnt, tmp);
                tmp.set(tmp2);
            } else {
                getBytes(tmp2, ((List) e.getKey()).get(0));
                tmp = tmp2;
            }
            // Update len.
            cnt = bb.size();
            bb.setUInt8(BerType.CONSTRUCTED | BerType.SET);
            GXCommon.setObjectCount(tmp.size(), bb);
            bb.set(tmp);
            return bb.size() - cnt;
        } else if (target instanceof GXAsn1Utf8String) {
            bb.setUInt8(BerType.UTF8STRING);
            str = target.toString();
            GXCommon.setObjectCount(str.length(), bb);
            bb.add(str);
        } else if (target instanceof GXAsn1Ia5String) {
            bb.setUInt8(BerType.IA5_STRING);
            str = target.toString();
            GXCommon.setObjectCount(str.length(), bb);
            bb.add(str);
        } else if (target instanceof GXAsn1BitString) {
            GXAsn1BitString bs = (GXAsn1BitString) target;
            bb.setUInt8(BerType.BIT_STRING);
            GXCommon.setObjectCount(1 + bs.getValue().length, bb);
            bb.setUInt8(bs.getPadBits());
            bb.add(bs.getValue());
        } else if (target instanceof GXAsn1PublicKey) {
            GXAsn1PublicKey bs = (GXAsn1PublicKey) target;
            bb.setUInt8(BerType.BIT_STRING);
            // Size is 64 bytes + padding and uncompressed point indicator.
            GXCommon.setObjectCount(66, bb);
            // Add padding.
            bb.setUInt8(0);
            // prefixed with the uncompressed point indicator 04
            bb.setUInt8(4);
            bb.add(bs.getValue());
            // Count is type + size + 64 bytes + padding + uncompressed point
            // indicator.
            return 68;
        } else if (target instanceof Date) {
            // Save date time in UTC.
            bb.setUInt8(BerType.UTC_TIME);
            str = dateToString((Date) target);
            bb.setUInt8(str.length());
            bb.add(str);
        } else {
            throw new IllegalArgumentException("Invalid type: " + target.getClass().toString());
        }
        return bb.size() - start;
    }

    /**
     * Convert ASN1 objects to byte array.
     * 
     * @param objects
     *            ASN.1 objects.
     * @return ASN.1 objects as byte array.
     */
    public static byte[] toByteArray(final Object objects) {
        GXByteBuffer bb = new GXByteBuffer();
        getBytes(bb, objects);
        return bb.array();
    }

    /**
     * Convert ASN1 PDU bytes to XML.
     * 
     * @param value
     *            Bytes to convert.
     * @return Converted XML.
     */
    public static String pduToXml(final String value) {
        if (value == null || value.length() == 0) {
            return "";
        }
        if (!GXCommon.isHexString(value)) {
            return pduToXml(GXCommon.fromBase64(value));
        }
        return pduToXml(new GXByteBuffer(GXCommon.hexToBytes(value)));
    }

    /**
     * Convert ASN1 PDU bytes to XML.
     * 
     * @param value
     *            Bytes to convert.
     * @return Converted XML.
     */
    public static String pduToXml(final byte[] value) {
        return pduToXml(new GXByteBuffer(value), false);
    }

    /**
     * Convert ASN1 PDU bytes to XML.
     * 
     * @param value
     *            Bytes to convert.
     * @param comments
     *            Are comments added to generated XML.
     * @return Converted XML.
     */
    public static String pduToXml(final byte[] value, final boolean comments) {
        return pduToXml(new GXByteBuffer(value), comments);
    }

    /**
     * Convert ASN.1 PDU bytes to XML.
     * 
     * @param value
     *            Bytes to convert.
     * @return Converted XML.
     */
    public static String pduToXml(final GXByteBuffer value) {
        return pduToXml(value, false);
    }

    /**
     * Convert ASN.1 PDU bytes to XML.
     * 
     * @param value
     *            Bytes to convert.
     * @param comments
     *            Are comments added to generated XML.
     * @return Converted XML.
     */
    public static String pduToXml(final GXByteBuffer value, final boolean comments) {
        GXAsn1Settings s = new GXAsn1Settings();
        s.setComments(comments);
        List objects = new ArrayList();
        while (value.position() != value.size()) {
            getValue(value, objects, s, false);
        }
        return s.toString();
    }

    @SuppressWarnings("rawtypes")
    private static int readNode(final Node node, final GXAsn1Settings s, final List list) {
        List tmp;
        String str = node.getNodeName().toLowerCase();
        int tag = s.getTag(str);
        switch (tag) {
        case BerType.APPLICATION:
            tmp = new ArrayList();
            for (int pos = 0; pos != node.getChildNodes().getLength(); ++pos) {
                Node node2 = node.getChildNodes().item(pos);
                if (node2.getNodeType() == Node.ELEMENT_NODE) {
                    readNode(node2, s, tmp);
                }
            }
            list.add(tmp);
            break;
        case BerType.CONSTRUCTED | BerType.CONTEXT:
            tmp = new GXAsn1Context();
            for (int pos = 0; pos != node.getChildNodes().getLength(); ++pos) {
                Node node2 = node.getChildNodes().item(pos);
                if (node2.getNodeType() == Node.ELEMENT_NODE) {
                    readNode(node2, s, tmp);
                }
            }
            list.add(tmp);
            break;
        case BerType.CONSTRUCTED | BerType.SEQUENCE:
            tmp = new GXAsn1Sequence();
            for (int pos = 0; pos != node.getChildNodes().getLength(); ++pos) {
                Node node2 = node.getChildNodes().item(pos);
                if (node2.getNodeType() == Node.ELEMENT_NODE) {
                    readNode(node2, s, tmp);
                }
            }
            list.add(tmp);
            break;
        case BerType.CONSTRUCTED | BerType.SET:
            tmp = new ArrayList();
            for (int pos = 0; pos != node.getChildNodes().getLength(); ++pos) {
                Node node2 = node.getChildNodes().item(pos);
                if (node2.getNodeType() == Node.ELEMENT_NODE) {
                    readNode(node2, s, tmp);
                }
            }
            for (Object val : tmp) {
                GXSimpleEntry e;
                if (val instanceof List) {
                    List t = (List) val;
                    e = new GXSimpleEntry(t.get(0), t.get(1));
                } else {
                    e = new GXSimpleEntry(tmp, null);
                }
                list.add(e);
            }
            break;
        case BerType.OBJECT_IDENTIFIER:
            list.add(new GXAsn1ObjectIdentifier(node.getChildNodes().item(0).getNodeValue()));
            break;
        case BerType.PRINTABLE_STRING:
            list.add(node.getChildNodes().item(0).getNodeValue());
            break;
        case BerType.UTF8STRING:
            list.add(new GXAsn1Utf8String(node.getChildNodes().item(0).getNodeValue()));
            break;
        case BerType.IA5_STRING:
            list.add(new GXAsn1Ia5String(node.getChildNodes().item(0).getNodeValue()));
            break;
        case BerType.INTEGER:
            list.add(new GXAsn1Integer(node.getChildNodes().item(0).getNodeValue()));
            break;
        case BerType.NULL:
            list.add(null);
            break;
        case BerType.BIT_STRING:
            list.add(new GXAsn1BitString(node.getChildNodes().item(0).getNodeValue()));
            break;
        case BerType.UTC_TIME:
            try {
                DateFormat f = new SimpleDateFormat();
                Date d = f.parse(node.getChildNodes().item(0).getNodeValue());
                list.add(d);
            } catch (DOMException | ParseException e) {
                throw new IllegalArgumentException(e.getMessage());
            }

            break;
        case BerType.GENERALIZED_TIME:
            break;
        case BerType.OCTET_STRING:
            list.add(GXCommon.hexToBytes(node.getChildNodes().item(0).getNodeValue()));
            break;
        case -1:
            list.add(Byte.parseByte(node.getChildNodes().item(0).getNodeValue()));
            break;
        case -2:
            list.add(Short.parseShort(node.getChildNodes().item(0).getNodeValue()));
            break;
        case -4:
            list.add(Integer.parseInt(node.getChildNodes().item(0).getNodeValue()));
            break;
        case -8:
            list.add(Long.parseLong(node.getChildNodes().item(0).getNodeValue()));
            break;
        default:
            throw new IllegalArgumentException("Invalid node: " + node.getNodeName());
        }
        return 0;
    }

    /**
     * Convert XML to ASN.1 PDU bytes.
     * 
     * @param xml
     *            XML.
     * @return ASN.1 PDU.
     */
    @SuppressWarnings("squid:S00112")
    public static byte[] xmlToPdu(final String xml) {
        DocumentBuilder docBuilder;
        Document doc;
        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
        try {
            docBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
            docBuilder = docBuilderFactory.newDocumentBuilder();
            doc = docBuilder.parse(new InputSource(new StringReader(xml)));
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
        List list = new ArrayList();
        GXAsn1Settings s = new GXAsn1Settings();
        readNode(doc.getDocumentElement(), s, list);
        return toByteArray(list.get(0));
    }

    /**
     * Convert system title to subject.
     * 
     * @param systemTitle
     *            System title.
     * @return Subject.
     */
    public static String systemTitleToSubject(final byte[] systemTitle) {
        GXByteBuffer bb = new GXByteBuffer(systemTitle);
        return "CN=" + bb.toHex(false, 0);
    }

    /**
     * Get system title from the common subject.
     * 
     * @param subject
     *            Subject.
     * @return System title.
     */
    public static byte[] systemTitleFromSubject(final String subject) {
        return GXDLMSTranslator.hexToBytes(hexSystemTitleFromSubject(subject));
    }

    /**
     * Get system title in hex string from the subject.
     * 
     * @param subject
     *            Subject.
     * @return System title.
     */
    public static String hexSystemTitleFromSubject(final String subject) {
        String cn = subject;
        int index = cn.indexOf("CN=");
        if (index == -1) {
            throw new IllegalArgumentException("Common Name is missing.");
        }
        cn = cn.substring(index + 3, index + 3 + 16);
        return cn;
    }

    public static Set certificateTypeToKeyUsage(final CertificateType type) {
        Set k = new HashSet();
        switch (type) {
        case DIGITAL_SIGNATURE:
            k.add(KeyUsage.DIGITAL_SIGNATURE);
            break;
        case KEY_AGREEMENT:
            k.add(KeyUsage.KEY_AGREEMENT);
            break;
        case TLS:
            k.add(KeyUsage.DIGITAL_SIGNATURE);
            k.add(KeyUsage.KEY_AGREEMENT);
            break;
        case OTHER:
            break;
        default:
            // At least one bit must be used.
            return null;
        }
        return k;
    }

    /**
     * Get certificate type from byte array.
     * 
     * @param data
     *            Byte array
     * @return certificate type.
     */
    public static PkcsType getCertificateType(final byte[] data) {
        return getCertificateType(data, null);
    }

    /**
     * Get certificate type from byte array.
     * 
     * @param data
     *            Byte array
     * @param seq
     *            parsed data.
     * @return certificate type.
     */
    static PkcsType getCertificateType(final byte[] data, final GXAsn1Sequence seq) {
        GXAsn1Sequence val = seq;
        if (val == null) {
            val = (GXAsn1Sequence) GXAsn1Converter.fromByteArray(data);
        }
        if (val.get(0) instanceof GXAsn1Sequence) {
            try {
                new GXx509Certificate(data);
                return PkcsType.x509_CERTIFICATE;
            } catch (Exception ex) {
                // It's ok if this fails.
            }
        }
        if (val.get(0) instanceof GXAsn1Sequence) {
            try {
                new GXPkcs10(data);
                return PkcsType.PKCS_10;
            } catch (Exception ex) {
                // It's ok if this fails.

            }
        }
        if (val.get(0) instanceof Byte) {
            try {
                new GXPkcs8(data);
                return PkcsType.PKCS_8;
            } catch (Exception ex) {
                // It's ok if this fails.
            }
        }
        return PkcsType.NONE;
    }

    /**
     * Get certificate type from DER string.
     * 
     * @param der
     *            DER string
     * @return certificate type.
     */
    public static PkcsType GetCertificateType(final String der) {
        return getCertificateType(GXCommon.fromBase64(der));
    }
}