/*
 * Copyright (C) 2022 Tomas Tintera
 *
 * Permission to use, copy, modify, and/or distribute this
 * software for any purpose with or without fee is hereby
 * granted.
 *
 * 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.
 */

var X509 = (function (X690) {
    var X509 = function X509() {
        X690.apply(this, arguments);
    };

    X509.TYPE_VERSION = 0xa0;
    X509.TYPE_EXTENSIONS = 0xa3;
    X509.TYPE_DNS_NAME = 0x82;
    X509.VERSION_3 = 2;
    X509.EXTENSION_SUBJECT_ALT_NAME = 0x551d11;

    X509.prototype = Object.create(X690.prototype);

    X509.prototype.extractExtensions = function extractExtensions(certificateOffset) {
        /* Check that the X.509 version is 3 */
        var certificate = this.extractMeta(certificateOffset || 0);
        if (certificate.type !== X690.TYPE_SEQUENCE) {
            return;
        }
        var tbsCertificate = this.extractMeta(certificate.inside);
        if (tbsCertificate.type !== X690.TYPE_SEQUENCE) {
            return;
        }
        var versionContainer = this.extractMeta(tbsCertificate.inside);
        if (versionContainer.type !== X509.TYPE_VERSION) {
            return;
        }
        var version = this.extractMeta(versionContainer.inside);
        if (version.type !== X690.TYPE_INTEGER || !this.cmp(version, X509.VERSION_3)) {
            return;
        }

        /* Find and return the Extensions object */
        var extensionsContainer = this.searchType(X509.TYPE_EXTENSIONS, tbsCertificate, versionContainer);
        var extensions = this.extractMeta(extensionsContainer.inside);
        if (extensions.type !== X690.TYPE_SEQUENCE) {
            return;
        }
        return extensions;
    };

    X509.prototype.extractExtension = function extractExtension(identifier, extensionsMeta) {
        var cert = this;
        return this.search(function (extension) {
            if (extension.type !== X690.TYPE_SEQUENCE) {
                return;
            }
            var identifierMeta = cert.extractMeta(extension.inside);
            if (identifierMeta.type !== X690.TYPE_IDENTIFIER) {
                return;
            }
            if (cert.cmp(identifierMeta, identifier)) {
                return extension;
            }
        }, extensionsMeta);
    };

    X509.prototype.extractExtensionValue = function extractExtensionValue(extensionMeta) {
        var extensionValueWrapper = this.searchType(X690.TYPE_OCTET_STRING, extensionMeta);
        if (!extensionValueWrapper) {
            return;
        }
        var extensionValue = this.extractMeta(extensionValueWrapper.inside);
        return extensionValue;
    };

    X509.prototype.readGeneralNames = function readGeneralNames(generalNamesMeta) {
        if (generalNamesMeta.type !== X690.TYPE_SEQUENCE) {
            return;
        }
        var generalNamesArray = [];
        var generalName;
        while (generalName = this.searchType(X509.TYPE_DNS_NAME, generalNamesMeta, generalName)) {
            generalNamesArray.push(["DNS", this.content.substr(generalName.inside, generalName.length)]);
        }
        return generalNamesArray;
    };

    X509.prototype.formatGeneralNames = function formatGeneralNames(generalNamesArray) {
        return generalNamesArray.map(function (generalName) {
            return generalName.join(":");
        }).join(", ");
    };

    X509.prototype.getSubjectAltName = function getSubjectAltName(certificateOffset) {
        var extensions = this.extractExtensions(certificateOffset || 0);
        if (!extensions) {
            return;
        }
        var sanExtension = this.extractExtension(X509.EXTENSION_SUBJECT_ALT_NAME, extensions);
        if (!sanExtension) {
            return;
        }
        var sanValue = this.extractExtensionValue(sanExtension);
        if (!sanValue) {
            return;
        }
        var sanArray = this.readGeneralNames(sanValue);
        if (!sanArray) {
            return;
        }
        return this.formatGeneralNames(sanArray);
    };

    return X509;
}(X690));
