【问题标题】:Check SSL certificate Expiration Date检查 SSL 证书到期日期
【发布时间】:2012-03-29 14:40:00
【问题描述】:

我需要检查本地计算机的 Ssl 证书到期日期并将其与当前日期进行比较,并通知用户他/她的证书将在 X 天后到期。我需要在 JavaScript 中完成所有这些工作。

【问题讨论】:

标签: javascript


【解决方案1】:

目前唯一可用的选项是 forge - https://github.com/digitalbazaar/forge/blob/master/README.md 或客户端的某种自定义扩展。

客户端除了 DOM 元素之外一无所知,因此它无法检查 SSL 层。

更多关于Within a web browser, is it possible for JavaScript to obtain information about the SSL Certificate being used for the current page?

【讨论】:

    【解决方案2】:

    您的证书应如下所示:

    -----BEGIN CERTIFICATE-----
    MIIGoDCCBIigAwIBAgIJAICRY3cWdgK1MA0GCSqGSIb3DQEBCwUAMIGeMQswCQYD
    VQQGEwJCRzERMA8GA1UECAwIQnVsZ2FyaWExDjAMBgNVBAcMBVNvZmlhMQ8wDQYD
    ..
    ud5Nja8+xycA/Jk7bSvB1jJjpc3oL0G9j0HOcxqQKd4e1IQXuss5V7FnQxSOVCq4
    GVK0r3LkAxtl/EGmQC1DRlHAUWg=
    -----END CERTIFICATE-----
    

    您需要从证书数据中去除-----BEGIN CERTIFICATE----- 标头和-----END CERTIFICATE----- 尾标,剩下的是Base64 编码字节数组。 您需要将其解码为字节数组(在此示例中表示为数字数组,其中每个数字代表一个字节 - 数字介于 0 和 255 之间)。 该字节数组是DER 编码的ASN.1 结构,如RFC-5280 中定义的那样。

    下面的示例将在 -----BEGIN CERTIFICATE----------END CERTIFICATE----- 的尾部已经被剥离后解析证书的内容。

    用法:

    var pem =
    "MIIGijCCBXKgAwIBAgIQEpI/gkvDS6idH2m2Zwn7ZzANBgkqhkiG9w0BAQsFADCB\n"
    ...
    +"VQ+o34uWo7z19I8eXWSXN6P+Uj1OvHn8zNM1G/ddjQXBwMvzwwJEdVBhdK1uQw==\n";
    
    var bytes = fromBase64(pem);
    
    var validity = getValidity(bytes);
    var notBefore = validity.notBefore;
    var notAfter  = validity.notAfter;
    
    var now = new Date();
    
    if (    notBefore.getTime() < now.getTime() 
        &&  now.getTime() < notAfter.getTime()) 
    {
      // Certificate is withing its validity days
    } else {
      // Certificate is either not yet valid or has already expired.
    }
    

    解析:

    
    
    var TYPE_INTEGER = 0x02;
    var TYPE_SEQUENCE = 0x10;
    var TYPE_UTC_TIME = 0x17;
    var TYPE_GENERALIZED_TIME = 0x18;
    
    function subArray(original, start, end) {
        var subArr = [];
        var index = 0;
        for (var i = start; i < end; i++) {
            subArr[index++] = original[i];
        }
        return subArr;
    }
    
    function getDigit(d) {
        switch (d) {
            default:
            case 0x30: case '0': return 0;
            case 0x31: case '1': return 1;
            case 0x32: case '2': return 2;
            case 0x33: case '3': return 3;
            case 0x34: case '4': return 4;
            case 0x35: case '5': return 5;
            case 0x36: case '6': return 6;
            case 0x37: case '7': return 7;
            case 0x38: case '8': return 8;
            case 0x39: case '9': return 9;
        }
    }
    
    function enterTag(bytes, start, requiredTypes, name) {
        if (start + 1 > bytes.length) {
            throw new Error("Too short certificate input");
        }
        var typeByte = bytes[start   ] & 0x0FF;
        var lenByte  = bytes[start +1] & 0x0FF;
    
        var type     = typeByte & 0x1F;
        var len      = lenByte;
    
        var index    = start + 2;
        if (requiredTypes.length > 0 && requiredTypes.indexOf(type) == -1) {
            throw new Error("Invalid type");
        }
        var lengthOfLength = 0;
        if (len > 0x07F) {
            lengthOfLength = len & 0x7F;
            len = 0;
            for (var i =0; i < lengthOfLength && index < bytes.length; i++) {
                len = (len << 8 ) | (bytes[index] & 0x00FF);
                index++;
            }
        }
        if (index >= bytes.length) {
            throw new Error("Too short certificate input");
        }
        return {index: index, type: type, length: len}
    }
    
    function processTag(bytes, start, requiredTypes, name) {
        var result = enterTag(bytes, start, requiredTypes, name);
    
        var index = result.index + result.length;   
    
        if (index >= bytes.length) {
            throw new Error("Too short certificate input");
        }
        var valueStart = result.index;
        var valueEnd   = result.index + result.length;
        var value = subArray(bytes, valueStart, valueEnd);
        return { index: index, type: result.type, value: value};
    }
    
    function readDate(bytes, start, name) {
        var date = new Date();
        var result = processTag(bytes, start, 
                [TYPE_UTC_TIME, TYPE_GENERALIZED_TIME], name);
        var index, year;
        if (result.type == 0x17) { // UTCTime
            if (result.value.length < 12) {
                throw new Error("Invalid type");
            }
            var yearHigh = getDigit(result.value[0]);
            var yearLow  = getDigit(result.value[1]);
            var year2Digits = (yearHigh * 10 ) + (yearLow)
            if (year2Digits >= 50) {
                year = 1900 + year2Digits;
            } else {
                year = 2000 + year2Digits;
            }
            index = 2;
        } else if (result.type = 0x18) { // GeneralizedTime
            if (result.value.length < 14) {
                throw new Error("Invalid type");
            }
            var year1  = getDigit(result.value[0]);
            var year2  = getDigit(result.value[1]);
            var year3  = getDigit(result.value[2]);
            var year4  = getDigit(result.value[3]);
            year = (year1 * 1000) + (year2 * 100) + (year3*10) + year4;
            index = 4;
        }
        var monthHigh  = getDigit(result.value[index++]);
        var monthLow   = getDigit(result.value[index++]);
        var dayHigh    = getDigit(result.value[index++]);
        var dayhLow    = getDigit(result.value[index++]);
        var hourHigh   = getDigit(result.value[index++]);
        var hourLow    = getDigit(result.value[index++]);
        var minuteHigh = getDigit(result.value[index++]);
        var minuteLow  = getDigit(result.value[index++]);
        var secondHigh = getDigit(result.value[index++]);
        var secondLow  = getDigit(result.value[index]);
    
        var month = (monthHigh   * 10) + monthLow;
        var day     = (dayHigh   * 10) + dayhLow;
        var hour   = (hourHigh   * 10) + hourLow;
        var minute = (minuteHigh * 10) + minuteLow;
        var second = (secondHigh * 10) + secondLow;
    
        if (month < 1 || month > 12) {
            throw new Error("Invalid month");
        }
        if (day < 1 || day > 31) {
            throw new Error("Invalid day");
        }
        if (hour < 0 || hour > 24) {
            throw new Error("Invalid hour");
        }
        if (minute < 0 || minute > 59) {
            throw new Error("Invalid minute");
        }
        if (second < 0 || second > 59) {
            throw new Error("Invalid second ");
        }
    
        date.setUTCFullYear(year);
        date.setUTCMonth(month-1);
        date.setUTCDate(day);
        date.setUTCHours(hour);
        date.setUTCMinutes(minute);
        date.setUTCSeconds(second);
    
        return {
            index: result.index, 
            type: result.type, 
            length: result.length, 
            value: result.value, 
            date: date
        };
    }
    
    function getValidity(bytes) {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        var index = 0;
        index = enterTag(bytes, index, [TYPE_SEQUENCE], "Certificate").index;
        index = enterTag(bytes, index, [TYPE_SEQUENCE], "TBSCertificate").index;
        var result = processTag(bytes, index, [0x00, 0x02], 
                "Version or SerialNumber");
        if (result.type == 0) {
            index = result.index;
            result = processTag(bytes, index, [TYPE_INTEGER], "SerialNumber")
        }
        index = result.index;
        result = processTag(bytes, index, [TYPE_SEQUENCE], 
            "Signature AlgorithmIdentifier");
        index  = result.index;
        result = processTag(bytes, index, [], "Issuer Name");
        index  = result.index;
        index  = enterTag(bytes, index, [TYPE_SEQUENCE], "Validity").index;
        result = readDate(bytes, index, "Not Before");
        var notBefore = result.date;
        index = result.index;
        result = readDate(bytes, index, "Not After");
        var notAfter = result.date;
    
        return {notBefore: notBefore, notAfter: notAfter};
    }
    
    function getNextBase64Chr(str, index, equalSignReceived, alpha) {
        var chr = null;
        var code = 0;
        var padding = equalSignReceived;
        while (index < str.length) {
            chr = str.charAt(index);
            if (chr == " " || chr == "\r" || chr == "\n" || chr == "\t") {
                index++;
                continue;
            }
            if (chr == "=") {
                padding = true;
            } else {
                if (equalSignReceived) {
                    throw new Error("Invalid Base64 Endcoding.");
                }
                code = alpha.indexOf(chr);
                if (code == -1) {
                    throw new Error("Invalid Base64 Encoding .");
                }
            }
            break;
        }
        return { character: chr, code: code, padding: padding, nextIndex: ++index};
    }
    function fromBase64(str) {
        var alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        var value = [];
        var index = 0;
        var destIndex  = 0;
        var padding = false;
        while (true) {
    
            var first   = getNextBase64Chr(str, index, padding, alpha);
            var second  = getNextBase64Chr(str, first .nextIndex, first .padding, alpha);
            var third   = getNextBase64Chr(str, second.nextIndex, second.padding, alpha);
            var fourth  = getNextBase64Chr(str, third .nextIndex, third .padding, alpha);
    
            index = fourth.nextIndex;
            padding = fourth.padding;
    
            // ffffffss sssstttt ttffffff
            var base64_first  = first.code  == null ? 0 : first.code;
            var base64_second = second.code == null ? 0 : second.code;
            var base64_third  = third.code  == null ? 0 : third.code;
            var base64_fourth = fourth.code == null ? 0 : fourth.code;
    
            var a = (( base64_first  << 2 ) & 0xFC ) | ((base64_second >> 4) & 0x03);
            var b = (( base64_second << 4 ) & 0xF0 ) | ((base64_third  >> 2) & 0x0F);
            var c = (( base64_third  << 6 ) & 0xC0 ) | ((base64_fourth >> 0) & 0x3F);
    
            value [destIndex++] = a;
            if (!third.padding) {
                value [destIndex++] = b;
            } else {
                break;
            }
            if (!fourth.padding) {
                value [destIndex++] = c;
            } else {
                break;
            }
            if (index >= str.length) {
                break;
            }
        }
        return value;
    }
    
    

    使用过的资源:

    A Layman's Guide to a Subset of ASN.1, BER, and DER

    Encoding of ASN.1 UTC Time and GeneralizedTime

    RFC-5280

    【讨论】:

      【解决方案3】:

      这在客户端是不可能的,但是你可以用 node / shell 组合来做这样的事情。

      假设您可以访问运行证书的服务器,您可以在主机上执行以下操作:

      var execSync = require('child_process').execSync
      var cmd = "echo | openssl s_client -connect 127.0.0.1:443 2>/dev/null | openssl x509 -noout -dates"
      var stdout = execSync(cmd).toString()
      // "notBefore=Feb 16 15:33:00 2017 GMT\nnotAfter=May 17 15:33:00 2017 GMT"
      

      您可以从那里解析 stdout 报告的日期并将它们写入公共资源或 json 文件,以便客户端可以读取它们,然后从那里进行日期比较。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-08
        • 2023-04-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-20
        • 2012-03-19
        • 2014-02-13
        相关资源
        最近更新 更多