检查SSL证书过期日期

时间:2012-03-13 07:30:02

标签: javascript

我需要检查本地计算机的SSl证书到期日期并将其与当前日期进行比较,并通知用户他/她的证书将在X天内到期。我需要用JavaScript做这一切。

3 个答案:

答案 0 :(得分:1)

您的证书应如下所示:

-----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

答案 1 :(得分:0)

目前唯一可用的选项是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 :(得分:0)

这不可能来自客户端,但您可以使用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文件,以便它们可以从客户端读取,并从那里进行日期比较。