diff --git a/.prettierrc b/.prettierrc index e98ecea..d55c191 100644 --- a/.prettierrc +++ b/.prettierrc @@ -7,5 +7,13 @@ "singleQuote": true, "tabWidth": 2, "trailingComma": "none", - "useTabs": false + "useTabs": false, + "overrides": [ + { + "files": "*.hbs", + "options": { + "singleQuote": false + } + } + ] } diff --git a/config/config.js b/config/config.js index 7eae52c..286fb7f 100644 --- a/config/config.js +++ b/config/config.js @@ -24,7 +24,7 @@ module.exports = { * @optional */ description: 'IP Lookup Integration for Shodan', - entityTypes: ['IPv4', 'IPv6'], + entityTypes: ['IPv4', 'IPv6', 'IPv4CIDR'], /** * An array of style files (css or less) that will be included for your integration. Any styles specified in * the below files can be used in your custom template. @@ -64,7 +64,7 @@ module.exports = { ca: '', // An HTTP proxy to be used. Supports proxy Auth with Basic Auth, identical to support for // the url parameter (by embedding the auth info in the uri) - proxy: "" + proxy: '' }, logging: { level: 'info' //trace, debug, info, warn, error, fatal diff --git a/integration.js b/integration.js index b48a49d..206f37f 100644 --- a/integration.js +++ b/integration.js @@ -3,7 +3,21 @@ const request = require('postman-request'); const fs = require('fs'); const Bottleneck = require('bottleneck'); -const _ = require('lodash'); +const { + flow, + get, + partition, + isArray, + isEmpty, + join, + map, + size, + identity, + sortBy, + toArray, + values, + take +} = require('lodash/fp'); const cache = require('memory-cache'); const config = require('./config/config'); @@ -14,6 +28,7 @@ let Logger; let requestWithDefaults; const IGNORED_IPS = new Set(['127.0.0.1', '255.255.255.255', '0.0.0.0']); +const MAX_FACET_RESULTS = 1000; function doLookup(entities, options, cb) { let limiter = bottlneckApiKeyCache.get(options.apiKey); @@ -36,19 +51,43 @@ function doLookup(entities, options, cb) { (entity) => !entity.isPrivateIP && !IGNORED_IPS.has(entity.value) ); + let requestOptions; validEntities.forEach((entity) => { - let requestOptions = { - uri: 'https://api.shodan.io/shodan/host/' + entity.value + '?key=' + options.apiKey, - method: 'GET', - json: true, - maxResponseSize: 2000000 // 2MB in bytes - }; + if (entity.type === 'IPv4CIDR') { + requestOptions = { + uri: 'https://api.shodan.io/shodan/host/search', + qs: { + key: options.apiKey, + query: `net:${entity.value}`, + facets: `vuln:${MAX_FACET_RESULTS},port:${MAX_FACET_RESULTS},ip:${MAX_FACET_RESULTS},org:${MAX_FACET_RESULTS},product:${MAX_FACET_RESULTS}` + }, + method: 'GET', + json: true, + maxResponseSize: 10000000 // 10MB in bytes + }; + } else { + requestOptions = { + uri: `https://api.shodan.io/shodan/host/${entity.value}`, + qs: { + key: options.apiKey + }, + method: 'GET', + json: true, + maxResponseSize: 2000000 // 2MB in bytes + }; + } + + Logger.trace({ requestOptions }, 'Request Options'); limiter.submit(requestEntity, entity, requestOptions, (err, result) => { const maxRequestQueueLimitHit = - (_.isEmpty(err) && _.isEmpty(result)) || + (isEmpty(err) && isEmpty(result)) || (err && err.message === 'This job has been dropped by Bottleneck'); + if (entity.type === 'IPv4CIDR' && result && result.body) { + result = assembleCIDRResults(result); + } + requestResults.push([ err, maxRequestQueueLimitHit ? { ...result, entity, limitReached: true } : result @@ -56,7 +95,7 @@ function doLookup(entities, options, cb) { if (requestResults.length === validEntities.length) { const [errs, results] = transpose2DArray(requestResults); - const errors = errs.filter((err) => !_.isEmpty(err)); + const errors = errs.filter((err) => !isEmpty(err)); if (errors.length) { Logger.trace({ errors }, 'Something went wrong'); @@ -66,8 +105,7 @@ function doLookup(entities, options, cb) { }); } - // filter out empty results - const filteredResults = results.filter((result) => !_.isEmpty(result)); + const filteredResults = results.filter((result) => !isEmpty(result)); const lookupResults = filteredResults.map((result) => { if (result.limitReached) { @@ -122,19 +160,16 @@ const requestEntity = (entity, requestOptions, callback) => Logger.trace({ body }, 'Result of Lookup'); if (res.statusCode === 200) { - // we got data! return callback(null, { entity, body }); } else if (res.statusCode === 404) { - // no result found return callback(null, { entity, body: null }); } else if (res.statusCode === 401) { - // no result found return callback({ detail: 'Unauthorized: The provided API key is invalid.' }); @@ -227,16 +262,31 @@ function validateOptions(userOptions, cb) { cb(null, errors); } +const assembleCIDRResults = (apiResponse) => { + if (apiResponse.body.total < 1) { + return { + entity: apiResponse.entity, + data: { + summary: ['No Results Found'], + details: { tags: ['No Results Found'] } + } + }; + } + + let resultsFacets = { + ...apiResponse.body.facets + }; + + return { entity: apiResponse.entity, body: resultsFacets, limitReached: false }; +}; + /** * Creates the Summary Tags (currently just tags for ports) * @param apiResponse * @returns {string[]} */ const createSummary = (apiResponse) => { - Logger.trace({ apiResponse }, 'Creating Summary Tags'); - const tags = createPortTags(apiResponse); - Logger.trace({ tags }, 'Summary Tags Created'); if (Array.isArray(apiResponse.body.tags)) { const apiTags = apiResponse.body.tags; @@ -250,6 +300,8 @@ const createSummary = (apiResponse) => { } } + if (apiResponse.body.totalVuln) tags.push(`Vulnerabilities: ${apiResponse.body.totalVuln}`); + Logger.trace({ tags }, 'final tags'); return tags; }; @@ -279,56 +331,52 @@ const createSummary = (apiResponse) => { * @param apiResponse * @returns {[string]} */ + const createPortTags = (apiResponse) => { - Logger.trace({ apiResponse }, 'Creating Port Tags'); - const portTags = []; - const ports = Array.from(apiResponse.body.ports); + let getPorts; - // sort the ports from smallest to largest - ports.sort((a, b) => { - return a - b; - }); + if (Array.isArray(get('body.ports', apiResponse))) { + getPorts = get('body.ports'); + } else { + getPorts = flow(get('body.port'), map('value')); + } + + const ports = flow( + getPorts, + (data) => (isArray(data) ? data : values(data)), + toArray, + sortBy(identity) + )(apiResponse); - if (ports.length === 0) { + if (isEmpty(ports)) { return [`No Open Ports`]; - } else if (ports.length <= 10) { - return [`Ports: ${ports.join(', ')}`]; - } else { - let splitIndex = ports.length; - for (let i = 0; i < ports.length; i++) { - if (ports[i] > 1024) { - splitIndex = i; - break; - } - } + } - // ports array is for reserved ports - // ephemeralPorts is for ephemeral ports ( ports > 1024) - const ephemeralPorts = ports.splice(splitIndex); - const numEphemeralPorts = ephemeralPorts.length; - const firstTenReservedPorts = ports.slice(0, 10); - const extraReservedCount = ports.length > 10 ? ports.length - 10 : 0; - - if (firstTenReservedPorts.length > 0) { - portTags.push( - `Reserved Ports: ${firstTenReservedPorts.join(', ')}${ - extraReservedCount > 0 ? ', +' + extraReservedCount + ' more' : '' - }` - ); - } + const [reservedPorts, ephemeralPorts] = partition((port) => port <= 1024)(ports); - if (numEphemeralPorts > 0) { - portTags.push(`${numEphemeralPorts} ephemeral ports`); - } + var portTags = []; + + if (!isEmpty(reservedPorts)) { + const visibleReservedPorts = take(10)(reservedPorts); + const hiddenCount = Math.max(size(reservedPorts) - 10, 0); + const visibleText = join(', ')(visibleReservedPorts); + + portTags.push( + `Reserved Ports: ${visibleText}${hiddenCount > 0 ? `, +${hiddenCount} more` : ''}` + ); + } - Logger.trace({ portTags }, 'Port Tags Created'); - return portTags; + if (!isEmpty(ephemeralPorts)) { + portTags.push(`${size(ephemeralPorts)} ephemeral ports`); } + + Logger.trace({ portTags }, 'Port Tags Created'); + return portTags; }; module.exports = { - doLookup, startup, + doLookup, validateOptions, onMessage: retryEntity }; diff --git a/logging.js b/logging.js new file mode 100644 index 0000000..e323f1f --- /dev/null +++ b/logging.js @@ -0,0 +1,20 @@ +const fs = require('fs'); +const { flow, reduce } = require('lodash/fp'); + +const writeToDevRunnerResults = (loggingLevel) => (...content) => + fs.appendFileSync( + 'devRunnerResults.json', + '\n' + JSON.stringify({ SOURCE: `Logger.${loggingLevel}`, content }, null, 2) + ); + +let logger = flow( + reduce((agg, level) => ({ ...agg, [level]: writeToDevRunnerResults(level) }), {}) +)(['trace', 'debug', 'info', 'warn', 'error', 'fatal']); + +const setLogger = (_logger) => { + logger = _logger; +}; + +const getLogger = () => logger; + +module.exports = { setLogger, getLogger }; diff --git a/package-lock.json b/package-lock.json index a83aeb8..c62eaca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "shodan", - "version": "3.4.2", + "version": "3.4.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -15,9 +15,9 @@ } }, "@postman/tough-cookie": { - "version": "4.1.2-postman.1", - "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.2-postman.1.tgz", - "integrity": "sha512-keOKL3RQohnH5K4GNGSV7JE8+SU/ktWJ3h9ulqttOGUWCZWcbUOtMNXkC3PQ/R1hIa+2qEfh0/5NCcAhWqeMTQ==", + "version": "4.1.3-postman.1", + "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", + "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -275,12 +275,12 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "postman-request": { - "version": "2.88.1-postman.32", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.32.tgz", - "integrity": "sha512-Zf5D0b2G/UmnmjRwQKhYy4TBkuahwD0AMNyWwFK3atxU1u5GS38gdd7aw3vyR6E7Ii+gD//hREpflj2dmpbE7w==", + "version": "2.88.1-postman.33", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.33.tgz", + "integrity": "sha512-uL9sCML4gPH6Z4hreDWbeinKU0p0Ke261nU7OvII95NU22HN6Dk7T/SaVPaj6T4TsQqGKIFw6/woLZnH7ugFNA==", "requires": { "@postman/form-data": "~3.1.1", - "@postman/tough-cookie": "~4.1.2-postman.1", + "@postman/tough-cookie": "~4.1.3-postman.1", "@postman/tunnel-agent": "^0.6.3", "aws-sign2": "~0.7.0", "aws4": "^1.12.0", @@ -405,4 +405,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index f35a7bf..ffafb19 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "shodan", - "version": "3.4.2", + "version": "3.4.3", "main": "./integration.js", "private": true, "dependencies": { "bottleneck": "^2.19.5", "lodash": "^4.17.21", "memory-cache": "^0.2.0", - "postman-request": "^2.88.1-postman.32" + "postman-request": "^2.88.1-postman.33" } -} \ No newline at end of file +} diff --git a/styles/shodan.less b/styles/shodan.less index 6a25106..63d4e96 100644 --- a/styles/shodan.less +++ b/styles/shodan.less @@ -5,6 +5,21 @@ padding: 3px 3px 2px 3px; display: inline-block; } + +.scrollable-block { + position: relative; + overflow-y: auto; + min-height: 10px; + max-height: 150px; + margin-bottom: 5px; + width: 100%; + padding: 3px 0px 3px 0px; + background-color: #f9f9f9; + border-radius: 3px; + scrollbar-width: thin; + scrollbar-color: rgba(155, 155, 155, 0.7) transparent; +} + .summary-try-again-button { padding: 0px 3px; background-color: rgba(0, 0, 0, 0.3); @@ -24,11 +39,24 @@ margin: 0px 0px 1px 3px; } +.p-block { + word-break: break-word; +} + .data-block { border-left: 1px solid #eee; padding-left: 5px; margin-left: 0px; } + +ul { + list-style-image: none; + list-style-type: none; + list-style-position: outside; + padding: 5px 10px; + margin: 0px; +} + div.service-pill { display: inline-block; line-height: 2em; diff --git a/templates/shodan-block.hbs b/templates/shodan-block.hbs index 762a929..1521693 100644 --- a/templates/shodan-block.hbs +++ b/templates/shodan-block.hbs @@ -1,7 +1,9 @@ {{#if details.limitReached}} -

{{fa-icon "info-circle"}} Search Limit Reached

+

+ {{fa-icon "info-circle"}} Search Limit Reached +

- This entity could not be searched as you've temporarily reached your Shodan Search Limit. You + This entity could not be searched as you"ve temporarily reached your Shodan Search Limit. You can retry your search by pressing the "Try Again" button.

@@ -12,10 +14,10 @@ {{/if}}
@@ -31,305 +33,565 @@ {{/if}} {{else}} - {{#if details.noResultsFound}} -

{{fa-icon "info-circle"}} No Results Found

-

- This Entity does not exist in Shodan. -

- {{else}} -
  • - -
    - {{fa-icon icon="check" fixedWidth=true class="copy-success-icon"}} - Copied Information. +
  • + +
    + {{fa-icon icon="check" fixedWidth=true class="copy-success-icon"}} + Copied Information. +
    +
  • +
    + {{#if (eq entity.type "IPv4CIDR")}} +

    + {{fa-icon icon="location-arrow" fixedWidth=true}} + IP Addresses +

    + {{#if details.ip}} +
    + +
    + {{else}} +
    + + No IPs given for this CIDR. +
    - - - {{#if details.ip_str}} - - View in Shodan - {{fa-icon "external-link-square" fixedWidth=true class="external-icon"}} - {{/if}} -
    -

    {{fa-icon "globe"}} Summary

    - - {{#if details.city}} -
    - City: - {{details.city}} -
    - {{/if}} - - {{#if details.country_name}} -
    - Country: - {{details.country_name}} -
    - {{/if}} - - {{#if details.org}} -
    - Org: - {{details.org}} -
    - {{/if}} - - {{#if details.isp}} -
    - ISP: - {{details.isp}} -
    - {{/if}} - - {{#if details.last_update}} -
    - Last Update: - {{details.last_update}} -
    - {{/if}} - - {{#if details.hostnames}} -
    - Hostnames: - {{join ", " details.hostnames}} -
    - {{/if}} - - {{#if details.asn}} -
    - ASN: - {{details.asn}} -
    - {{/if}} - - {{#if details.ports}} -

    {{fa-icon "th-large" fixedWidth=true}} Ports

    - {{#each details.ports as |data|}} - - {{/each}} - {{/if}} - -

    {{fa-icon "th-list" fixedWidth=true}} Services

    - - {{#each details.data as |item index|}} -
    - {{item.port}}{{item.transport}}{{item._shodan.module}} -
    +

    + {{fa-icon icon="sitemap" fixedWidth=true}} + Organizations +

    + {{#if details.org}} + + {{else}} +
    + + No Orgs given for this CIDR. + +
    + {{/if}} +

    + {{fa-icon icon="box-open" fixedWidth=true}} + Products +

    + {{#if details.product}} + + {{else}} +
    + + No Products given for this CIDR. + +
    + {{/if}} +

    + {{fa-icon icon="fingerprint" fixedWidth=true}} + Vulnerabilities +

    + {{#if details.vuln}} +
    + +
      + {{#each details.vuln as |vuln|}} +
    • + {{vuln.value}} +
    • + {{/each}} +
    +
    +
    + {{else}} +
    + + No Vulnerabilities given for this CIDR. + +
    + {{/if}} + {{#if details.port}} +

    + {{fa-icon icon="th-large" fixedWidth=true}} Ports +

    + {{#each details.port as |port|}} + {{/each}} + {{/if}} + {{else}} + {{#if details.noResultsFound}} +

    + {{fa-icon "info-circle"}} No Results Found +

    +

    + This Entity does not exist in Shodan. +

    + {{else}} + {{#if details.ip_str}} + + View in Shodan + {{fa-icon "external-link-square" fixedWidth=true class="external-icon"}} + + {{/if}} -
    - {{#if block._state.showDetails}} - {{fa-icon "caret-up" fixedWidth=true}} - Hide Details - {{else}} - {{fa-icon "caret-down" fixedWidth=true}} - View Details +

    + {{fa-icon "globe"}} Summary +

    + + {{#if details.city}} +
    + + City: + + + {{details.city}} + +
    {{/if}} -
    - - {{#if block._state.showDetails}} - {{#each details.data as |item index|}} -
    - {{#if item.product}} -

    {{fa-icon "box" fixedWidth=true}} {{item.product}}

    - {{else}} -

    {{fa-icon "box" fixedWidth=true}} {{item._shodan.module}}

    - {{/if}} -
    - {{item.port}}{{item.transport}}{{item._shodan.module}} -
    - {{#if item.isp}} -
    - ISP: - {{item.isp}} -
    - {{/if}} - {{#if item.org}} -
    - Org: - {{item.org}} -
    - {{/if}} - - {{#if item.devicetype}} -
    - Device Type: - {{item.devicetype}} -
    - {{/if}} - - {{#if item.hash}} -
    - Hash: - {{item.hash}} -
    - {{/if}} + {{#if details.country_name}} +
    + + Country: + + + {{details.country_name}} + +
    + {{/if}} - {{#if item.version}} -
    - Version: - {{item.version}} -
    - {{/if}} + {{#if details.org}} +
    + + Org: + + + {{details.org}} + +
    + {{/if}} - {{#if item.os}} -
    - OS: - {{item.os}} -
    - {{/if}} + {{#if details.isp}} +
    + + ISP: + + + {{details.isp}} + +
    + {{/if}} - {{#if item.ssh.fingerprint}} -
    - SSH Fingerprint: - {{item.ssh.fingerprint}} -
    - {{/if}} + {{#if details.last_update}} +
    + + Last Update: + + + {{details.last_update}} + +
    + {{/if}} - {{#if item.ssh.mac}} -
    - SSH MAC: - {{item.ssh.mac}} -
    - {{/if}} + {{#if details.hostnames}} +
    + + Hostnames: + + + {{join ", " details.hostnames}} + +
    + {{/if}} - {{#if item.ssh.cipher}} -
    - SSH Cipher: - {{item.ssh.cipher}} -
    - {{/if}} + {{#if details.asn}} +
    + + ASN: + + + {{details.asn}} + +
    + {{/if}} - {{#if item.http.servers}} -
    - HTTP Servers: - {{item.http.servers}} -
    - {{/if}} + {{#if details.ports}} +

    + {{fa-icon "th-large" fixedWidth=true}} Ports +

    + {{#each details.ports as |data|}} + + {{/each}} + {{/if}} - {{#if item.hostnames}} -
    - Hostnames: - {{join ", " item.hostnames}} -
    - {{/if}} +

    + {{fa-icon "th-list" fixedWidth=true}} Services +

    - {{#if item.domains}} -
    - Domains: - {{join ", " item.domains}} -
    - {{/if}} + {{#each details.data as |item index|}} +
    + + {{item.port}} + + + {{item.transport}} + + + {{item._shodan.module}} + +
    + {{/each}} - {{#if item.http.robots}} -
    - HTTP Robots: -
    {{item.http.robots}}
    -
    - {{/if}} +
    + {{#if block._state.showDetails}} + {{fa-icon "caret-up" fixedWidth=true}} + Hide Details + {{else}} + {{fa-icon "caret-down" fixedWidth=true}} + View Details + {{/if}} +
    - {{#if item.data}} -
    - Data: -
    {{item.data}}
    -
    - {{/if}} - - {{#if item.ssl.cert}} -

    {{fa-icon "lock"}} Certificate Details

    - -
    - {{#if (eq item.ssl.cert.version 2)}} -
    - Version: - 3 (0x2) -
    - {{else}} -
    - Version: - {{item.ssl.cert.version}} -
    - {{/if}} - - {{#if item.ssl.cert.sig_alg}} -
    - Signature Algorithm: - {{item.ssl.cert.sig_alg}} -
    - {{/if}} - - {{#if item.ssl.cert.issuer}} -
    - Issuer: - C={{item.ssl.cert.issuer.C}}, O={{item.ssl.cert.issuer.O}}, - CN={{item.ssl.cert.issuer.CN}} -
    - {{/if}} - - {{#if item.ssl.cert.issued}} -
    - Valid Not Before: - {{moment-format - item.ssl.cert.issued - "YYYY-MM-DD HH:mm:ss z" - "YYYYMMDDHHmmssz" - timeZone=timezone - }} -
    - {{/if}} - - {{#if item.ssl.cert.expires}} -
    - Valid Not After: - {{moment-format - item.ssl.cert.expires - "YYYY-MM-DD HH:mm:ss z" - "YYYYMMDDHHmmssz" - timeZone=timezone - }} -
    - {{/if}} - - {{#if item.ssl.cert.subject}} -
    - Subject: - - C={{item.ssl.cert.subject.C}}, ST={{item.ssl.cert.subject.ST}}, L={{item.ssl.cert.subject.L}}, - 0={{item.ssl.cert.subject.O}}, CN={{item.ssl.cert.subject.CN}} - -
    - {{/if}} - - {{#if item.ssl.cert.pubkey.type}} -
    - Subject Public Key Algorithm: - {{item.ssl.cert.pubkey.type}} -
    - {{/if}} - - {{#if item.ssl.cert.pubkey.bits}} -
    - Subject Public Key Length: - {{item.ssl.cert.pubkey.bits}} bit -
    - {{/if}} + {{#if block._state.showDetails}} + {{#each details.data as |item index|}} +
    + {{#if item.product}} +

    + {{fa-icon "box" fixedWidth=true}} {{item.product}} +

    + {{else}} +

    + {{fa-icon "box" fixedWidth=true}} {{item._shodan.module}} +

    + {{/if}} +
    + + {{item.port}} + + + {{item.transport}} + + + {{item._shodan.module}} +
    - {{/if}} -
    - {{/each}} - {{/if}} -
    + {{#if item.isp}} +
    + + ISP: + + + {{item.isp}} + +
    + {{/if}} + + {{#if item.org}} +
    + + Org: + + + {{item.org}} + +
    + {{/if}} + + {{#if item.devicetype}} +
    + + Device Type: + + + {{item.devicetype}} + +
    + {{/if}} + + {{#if item.hash}} +
    + + Hash: + + + {{item.hash}} + +
    + {{/if}} + + {{#if item.version}} +
    + + Version: + + + {{item.version}} + +
    + {{/if}} + + {{#if item.os}} +
    + + OS: + + + {{item.os}} + +
    + {{/if}} + + {{#if item.ssh.fingerprint}} +
    + + SSH Fingerprint: + + + {{item.ssh.fingerprint}} + +
    + {{/if}} + + {{#if item.ssh.mac}} +
    + + SSH MAC: + + + {{item.ssh.mac}} + +
    + {{/if}} + + {{#if item.ssh.cipher}} +
    + + SSH Cipher: + + + {{item.ssh.cipher}} + +
    + {{/if}} + + {{#if item.http.servers}} +
    + + HTTP Servers: + + + {{item.http.servers}} + +
    + {{/if}} + + {{#if item.hostnames}} +
    + + Hostnames: + + + {{join ", " item.hostnames}} + +
    + {{/if}} + + {{#if item.domains}} +
    + + Domains: + + + {{join ", " item.domains}} + +
    + {{/if}} + + {{#if item.http.robots}} +
    + + HTTP Robots: + +
    +                    {{item.http.robots}}
    +                  
    +
    + {{/if}} + + {{#if item.data}} +
    + + Data: + +
    +                    {{item.data}}
    +                  
    +
    + {{/if}} + + {{#if item.ssl.cert}} +

    + {{fa-icon "lock"}} Certificate Details +

    + +
    + {{#if (eq item.ssl.cert.version 2)}} +
    + + Version: + + + 3 (0x2) + +
    + {{else}} +
    + + Version: + + + {{item.ssl.cert.version}} + +
    + {{/if}} + + {{#if item.ssl.cert.sig_alg}} +
    + + Signature Algorithm: + + + {{item.ssl.cert.sig_alg}} + +
    + {{/if}} + + {{#if item.ssl.cert.issuer}} +
    + + Issuer: + + + C={{item.ssl.cert.issuer.C}}, O={{ + item.ssl.cert.issuer.O + }}, + CN={{item.ssl.cert.issuer.CN}} + +
    + {{/if}} + + {{#if item.ssl.cert.issued}} +
    + + Valid Not Before: + + + {{moment-format + item.ssl.cert.issued + "YYYY-MM-DD HH:mm:ss z" + "YYYYMMDDHHmmssz" + timeZone=timezone + }} + +
    + {{/if}} + + {{#if item.ssl.cert.expires}} +
    + + Valid Not After: + + + {{moment-format + item.ssl.cert.expires + "YYYY-MM-DD HH:mm:ss z" + "YYYYMMDDHHmmssz" + timeZone=timezone + }} + +
    + {{/if}} + + {{#if item.ssl.cert.subject}} +
    + + Subject: + + + C={{item.ssl.cert.subject.C}}, ST={{item.ssl.cert.subject.ST}}, L={{ + item.ssl.cert.subject.L + }}, + 0={{item.ssl.cert.subject.O}}, CN={{ + item.ssl.cert.subject.CN + }} + +
    + {{/if}} + + {{#if item.ssl.cert.pubkey.type}} +
    + + Subject Public Key Algorithm: + + + {{item.ssl.cert.pubkey.type}} + +
    + {{/if}} + + {{#if item.ssl.cert.pubkey.bits}} +
    + + Subject Public Key Length: + + + {{item.ssl.cert.pubkey.bits}} bit + +
    + {{/if}} +
    + {{/if}} +
    + {{/each}} + {{/if}} + {{/if}} {{/if}} +
    {{/if}} \ No newline at end of file