diff --git a/src/gmp/models/report/__tests__/parser.js b/src/gmp/models/report/__tests__/parser.js index 693e3c5571..ab4754cc43 100644 --- a/src/gmp/models/report/__tests__/parser.js +++ b/src/gmp/models/report/__tests__/parser.js @@ -499,53 +499,124 @@ describe('report parser tests', () => { }, ], ssl_certs: {count: '123'}, + tls_certificates: { + tls_certificate: [ + { + name: '57610B6A3C73866870678E638C7825743145B24', + certificate: { + __text: '66870678E638C7825743145B247554E0D92C94', + _format: 'DER', + }, + sha256_fingerprint: '57610B6A3C73866870678E638C78', + md5_fingerprint: 'fa:a9:9d:f2:28:cc:2c:c0:80:16', + activation_time: '2019-08-10T12:51:27Z', + expiration_time: '2019-09-10T12:51:27Z', + valid: true, + subject_dn: 'CN=LoremIpsumSubject C=Dolor', + issuer_dn: 'CN=LoremIpsumIssuer C=Dolor', + serial: '00B49C541FF5A8E1D9', + host: {ip: '192.168.9.90', hostname: 'foo.bar'}, + ports: {port: ['4021', '4023']}, + }, + { + name: 'C137E9D559CC95ED130011FE4012DE56CAE2F8', + certificate: { + __text: 'MIICGTCCAYICCQDDh8Msu4YfXDANBgkqhkiG9w0B', + _format: 'DER', + }, + sha256_fingerprint: 'C137E9D559CC95ED130011FE4012', + md5_fingerprint: '63:70:d6:65:17:32:01:66:9e:7d:c4', + activation_time: 'unlimited', + expiration_time: 'undefined', + valid: false, + subject_dn: 'CN=LoremIpsumSubject2 C=Dolor', + issuer_dn: 'CN=LoremIpsumIssuer2 C=Dolor', + serial: '00C387C32CBB861F5C', + host: {ip: '191.164.9.93', hostname: ''}, + ports: {port: ['8445', '5061']}, + }, + { + name: 'C137E9D559CC95ED130011FE4012DE56CAE2F8', + certificate: {}, + sha256_fingerprint: 'C137E9D559CC95ED130011FE4012', + md5_fingerprint: '63:70:d6:65:17:32:01:66:9e:7d:c4', + activation_time: 'unlimited', + expiration_time: 'undefined', + valid: false, + subject_dn: 'CN=LoremIpsumSubject2 C=Dolor', + issuer_dn: 'CN=LoremIpsumIssuer2 C=Dolor', + serial: '00C387C32CBB861F5C', + host: {}, + ports: {port: ['8441']}, + }, + ], + }, }; const counts = { first: 1, all: 123, - filtered: 4, - length: 4, - rows: 4, - last: 4, + filtered: 5, + length: 5, + rows: 5, + last: 5, }; const tlsCerts = parseTlsCertificates(report, filterString); - expect(tlsCerts.entities.length).toEqual(4); + expect(tlsCerts.entities.length).toEqual(5); expect(tlsCerts.counts).toEqual(counts); expect(tlsCerts.filter).toEqual('foo=bar rows=5'); - const [cert1, cert2, cert3, cert4] = tlsCerts.entities; + const [cert1, cert2, cert3, cert4, cert5] = tlsCerts.entities; - expect(cert1.fingerprint).toEqual('fingerprint1'); + expect(cert1.fingerprint).toEqual( + '57610B6A3C73866870678E638C7825743145B24', + ); expect(cert1.hostname).toEqual('foo.bar'); - expect(cert1.ip).toEqual('1.1.1.1'); - expect(cert1.data).toEqual('foobar'); - expect(cert1._data).toEqual('x509:foobar'); + expect(cert1.ip).toEqual('192.168.9.90'); + expect(cert1.data).toEqual('66870678E638C7825743145B247554E0D92C94'); + expect(cert1.valid).toEqual(true); expect(cert1.ports).toBeUndefined(); - expect(cert1.port).toEqual(123); - - expect(cert2.fingerprint).toEqual('fingerprint2'); - expect(cert2.hostname).toBeUndefined(); - expect(cert2.ip).toEqual('2.2.2.2'); - expect(cert2.data).toBeUndefined(); - expect(cert2._data).toBeUndefined(); + expect(cert1.port).toEqual(4021); + + expect(cert2.fingerprint).toEqual( + '57610B6A3C73866870678E638C7825743145B24', + ); + expect(cert2.hostname).toEqual('foo.bar'); + expect(cert2.ip).toEqual('192.168.9.90'); + expect(cert2.data).toEqual('66870678E638C7825743145B247554E0D92C94'); + expect(cert2.valid).toEqual(true); expect(cert2.ports).toBeUndefined(); - expect(cert2.port).toEqual(123); - - expect(cert3.fingerprint).toEqual('fingerprint2'); - expect(cert3.hostname).toBeUndefined(); - expect(cert3.ip).toEqual('2.2.2.2'); - expect(cert3.data).toBeUndefined(); - expect(cert3._data).toBeUndefined(); + expect(cert2.port).toEqual(4023); + + expect(cert3.fingerprint).toEqual('C137E9D559CC95ED130011FE4012DE56CAE2F8'); + expect(cert3.hostname).toEqual(''); + expect(cert3.ip).toEqual('191.164.9.93'); + expect(cert3.data).toEqual('MIICGTCCAYICCQDDh8Msu4YfXDANBgkqhkiG9w0B'); + expect(cert3.valid).toEqual(false); + expect(cert3.activationTime).toBeUndefined(); + expect(cert3.expirationTime).toBeUndefined(); expect(cert3.ports).toBeUndefined(); - expect(cert3.port).toEqual(234); - - expect(cert4.fingerprint).toEqual('fingerprint1'); - expect(cert4.ip).toEqual('2.2.2.2'); - expect(cert4.data).toBeUndefined(); - expect(cert4._data).toBeUndefined(); + expect(cert3.port).toEqual(8445); + + expect(cert4.fingerprint).toEqual('C137E9D559CC95ED130011FE4012DE56CAE2F8'); + expect(cert4.hostname).toEqual(''); + expect(cert4.ip).toEqual('191.164.9.93'); + expect(cert4.data).toEqual('MIICGTCCAYICCQDDh8Msu4YfXDANBgkqhkiG9w0B'); + expect(cert4.valid).toEqual(false); + expect(cert4.activationTime).toBeUndefined(); + expect(cert4.expirationTime).toBeUndefined(); expect(cert4.ports).toBeUndefined(); - expect(cert4.port).toEqual(234); + expect(cert4.port).toEqual(5061); + + expect(cert5.fingerprint).toEqual('C137E9D559CC95ED130011FE4012DE56CAE2F8'); + expect(cert5.hostname).toBeUndefined(); + expect(cert5.ip).toBeUndefined(); + expect(cert5.data).toBeUndefined(); + expect(cert5.valid).toEqual(false); + expect(cert5.activationTime).toBeUndefined(); + expect(cert5.expirationTime).toBeUndefined(); + expect(cert5.ports).toBeUndefined(); + expect(cert5.port).toEqual(8441); }); test('should parse empty tls certificates', () => { diff --git a/src/gmp/models/report/parser.js b/src/gmp/models/report/parser.js index 6b5fc472a2..0be541734a 100644 --- a/src/gmp/models/report/parser.js +++ b/src/gmp/models/report/parser.js @@ -20,7 +20,7 @@ import {isDefined} from 'gmp/utils/identity'; import {isEmpty} from 'gmp/utils/string'; import {filter as filter_func, forEach, map} from 'gmp/utils/array'; -import {parseSeverity, parseDate} from 'gmp/parser'; +import {parseBoolean, parseSeverity, parseDate} from 'gmp/parser'; import { parseCollectionList, @@ -52,89 +52,60 @@ const emptyCollectionList = filter => { }; }; -const getTlsCertificate = (certs, fingerprint) => { - let cert = certs[fingerprint]; - - if (!isDefined(cert)) { - cert = ReportTLSCertificate.fromElement({fingerprint}); - certs[fingerprint] = cert; - } - return cert; -}; - export const parseTlsCertificates = (report, filter) => { - const {host: hosts, ssl_certs, results} = report; + const {ssl_certs, tls_certificates, results} = report; - if (!isDefined(ssl_certs) || !isReportWithDetails(results)) { + if ( + !isDefined(ssl_certs) || + !isDefined(tls_certificates) || + !isReportWithDetails(results) + ) { return emptyCollectionList(filter); } const {count: full_count} = ssl_certs; - let certs_array = []; - - forEach(hosts, host => { - const host_certs = {}; - let hostname; - - forEach(host.detail, detail => { - const {name = '', value = ''} = detail; - - if (name.startsWith('SSLInfo')) { - const [port, fingerprint] = value.split('::'); - - const cert = getTlsCertificate(host_certs, fingerprint); - - cert.ip = host.ip; - - cert.addPort(port); - } else if (name.startsWith('SSLDetails')) { - const [, fingerprint] = name.split(':'); - - const cert = getTlsCertificate(host_certs, fingerprint); - - value.split('|').reduce((c, v) => { - let [key, val] = v.split(':'); - if (key === 'notAfter' || key === 'notBefore') { - val = parseDate(val); - } - c[key.toLowerCase()] = val; - return c; - }, cert); - - cert.details = value; - } else if (name.startsWith('Cert')) { - const [, fingerprint] = name.split(':'); - - const cert = getTlsCertificate(host_certs, fingerprint); - - // currently cert data starts with x509: - // not sure if there are other types of certs - // therefore keep original data - - cert._data = value; - - if (value.includes(':')) { - const [, data] = value.split(':'); - cert.data = data; - } else { - cert.data = value; - } - } else if (name === 'hostname') { - // collect hostnames - hostname = value; - } + const certs_array = []; + + forEach(tls_certificates.tls_certificate, tls_cert => { + const { + name, + certificate, + sha256_fingerprint, + md5_fingerprint, + valid, + activation_time, + expiration_time, + subject_dn, + issuer_dn, + serial, + host, + ports, + } = tls_cert; + + const cert = ReportTLSCertificate.fromElement({fingerprint: name}); + cert.data = isDefined(certificate) ? certificate.__text : undefined; + cert.sha256Fingerprint = sha256_fingerprint; + cert.md5Fingerprint = md5_fingerprint; + cert.activationTime = + activation_time === 'undefined' || activation_time === 'unlimited' + ? undefined + : parseDate(activation_time); + cert.expirationTime = + expiration_time === 'undefined' || expiration_time === 'unlimited' + ? undefined + : parseDate(expiration_time); + cert.valid = parseBoolean(valid); + cert.subject_dn = subject_dn; + cert.issuer_dn = issuer_dn; + cert.serial = serial; + cert.hostname = isDefined(host) ? host.hostname : ''; + cert.ip = isDefined(host) ? host.ip : undefined; + + forEach(ports.port, port => { + cert.addPort(port); }); - - const certs = Object.values(host_certs); - - if (isDefined(hostname)) { - for (const cert of certs) { - cert.hostname = hostname; - } - } - - certs_array = certs_array.concat(certs); + certs_array.push(cert); }); // create a cert per port @@ -367,12 +338,11 @@ export const parseOperatingSystems = (report, filter) => { const severity = severities[ip]; if (!isDefined(os)) { - os = operating_systems[ - best_os_cpe - ] = ReportOperatingSystem.fromElement({ - best_os_cpe, - best_os_txt, - }); + os = operating_systems[best_os_cpe] = + ReportOperatingSystem.fromElement({ + best_os_cpe, + best_os_txt, + }); } os.addHost(host); diff --git a/src/web/entities/withRowDetails.js b/src/web/entities/withRowDetails.js index b0271abed2..fae9e992f1 100644 --- a/src/web/entities/withRowDetails.js +++ b/src/web/entities/withRowDetails.js @@ -57,34 +57,38 @@ const StyledTableRow = styled(TableRow)` } `; -const withRowDetails = (type, colSpan = '10') => Component => { - const RowDetailsWrapper = ({entity, links = true, ...props}) => ( - - - {links && ( - - - - +const withRowDetails = + (type, colSpan = '10', details = true) => + Component => { + const RowDetailsWrapper = ({entity, links = true, ...props}) => ( + + + {links && ( + + {details && ( + + + + )} + + )} + + + - )} - - - - - - - ); - - RowDetailsWrapper.propTypes = { - entity: PropTypes.model.isRequired, - links: PropTypes.bool, + + + ); + + RowDetailsWrapper.propTypes = { + entity: PropTypes.model.isRequired, + links: PropTypes.bool, + }; + return RowDetailsWrapper; }; - return RowDetailsWrapper; -}; export default withRowDetails; diff --git a/src/web/pages/reports/__mocks__/mockreport.js b/src/web/pages/reports/__mocks__/mockreport.js index 247353a68d..05df5ba85a 100644 --- a/src/web/pages/reports/__mocks__/mockreport.js +++ b/src/web/pages/reports/__mocks__/mockreport.js @@ -236,6 +236,44 @@ const error2 = { }, }; +// TLS certificates +const tlsCertificate1 = { + name: '57610B6A3C73866870678E638C7825743145B24', + certificate: { + __text: '66870678E638C7825743145B247554E0D92C94', + _format: 'DER', + }, + data: 'MIIDSzCCAjOgAwIBAgIJALScVB/zqOLZMA0GCSqGSIb3DQ', + sha256_fingerprint: '57610B6A3C73866870678E638C78', + md5_fingerprint: 'fa:a9:9d:f2:28:cc:2c:c0:80:16', + activation_time: '2019-08-10T12:51:27Z', + expiration_time: '2019-09-10T12:51:27Z', + valid: true, + subject_dn: 'CN=LoremIpsumSubject1 C=Dolor', + issuer_dn: 'CN=LoremIpsumIssuer1 C=Dolor', + serial: '00B49C541FF5A8E1D9', + host: {ip: '192.168.9.90', hostname: 'foo.bar'}, + ports: {port: ['4021', '4023']}, +}; + +const tlsCertificate2 = { + name: 'C137E9D559CC95ED130011FE4012DE56CAE2F8', + certificate: { + __text: 'MIICGTCCAYICCQDDh8Msu4YfXDANBgkqhkiG9w0B', + _format: 'DER', + }, + sha256_fingerprint: 'C137E9D559CC95ED130011FE4012', + md5_fingerprint: '63:70:d6:65:17:32:01:66:9e:7d:c4', + activation_time: 'unlimited', + expiration_time: 'undefined', + valid: false, + subject_dn: 'CN=LoremIpsumSubject2 C=Dolor', + issuer_dn: 'CN=LoremIpsumIssuer2 C=Dolor', + serial: '00C387C32CBB861F5C', + host: {ip: '191.164.9.93', hostname: ''}, + ports: {port: ['8445', '5061']}, +}; + export const getMockReport = () => { const report = { _id: '1234', @@ -255,6 +293,9 @@ export const getMockReport = () => { results: {result: [result1, result2, result3]}, hosts: {count: 2}, host: [host1, host2], + tls_certificates: { + tls_certificate: [tlsCertificate1, tlsCertificate2], + }, ports: { count: 2, port: [port1, port2], diff --git a/src/web/pages/reports/details/__tests__/tlscertificatestab.js b/src/web/pages/reports/details/__tests__/tlscertificatestab.js index 705ac88be0..03c3de3a58 100644 --- a/src/web/pages/reports/details/__tests__/tlscertificatestab.js +++ b/src/web/pages/reports/details/__tests__/tlscertificatestab.js @@ -32,7 +32,7 @@ import TLSCertificatesTab from '../tlscertificatestab'; setLocale('en'); const filter = Filter.fromString( - 'apply_overrides=0 levels=hml rows=2 min_qod=70 first=1 sort-reverse=severity', + 'apply_overrides=0 levels=hml rows=3 min_qod=70 first=1 sort-reverse=severity', ); describe('Report TLS Certificates Tab tests', () => { @@ -81,44 +81,59 @@ describe('Report TLS Certificates Tab tests', () => { expect(header[7]).toHaveTextContent('Actions'); // Row 1 - expect(rows[1]).toHaveTextContent('CN=foo'); - expect(rows[1]).toHaveTextContent('abcd'); - expect(rows[1]).toHaveTextContent('Wed, Jan 30, 2019'); - expect(rows[1]).toHaveTextContent('Thu, Aug 1, 2019'); + expect(rows[1]).toHaveTextContent('CN=LoremIpsumSubject1 C=Dolor'); + expect(rows[1]).toHaveTextContent('00B49C541FF5A8E1D9'); + expect(rows[1]).toHaveTextContent('Sat, Aug 10, 2019'); + expect(rows[1]).toHaveTextContent('Tue, Sep 10, 2019'); expect(links[7]).toHaveAttribute( 'href', - '/hosts?filter=name%3D123.456.78.910', + '/hosts?filter=name%3D192.168.9.90', ); expect(links[7]).toHaveAttribute( 'title', - 'Show all Hosts with IP 123.456.78.910', + 'Show all Hosts with IP 192.168.9.90', ); - expect(links[7]).toHaveTextContent('123.456.78.910'); + expect(links[7]).toHaveTextContent('192.168.9.90'); expect(rows[1]).toHaveTextContent('foo.bar'); - expect(rows[1]).toHaveTextContent('1234'); + expect(rows[1]).toHaveTextContent('4021'); expect(icons[4]).toHaveTextContent('download.svg'); // Row 2 - expect(rows[2]).toHaveTextContent('CN=bar'); - expect(rows[2]).toHaveTextContent('dcba'); - expect(rows[2]).toHaveTextContent('Sat, Mar 30, 2019'); - expect(rows[2]).toHaveTextContent('Tue, Oct 1, 2019'); + expect(rows[2]).toHaveTextContent('CN=LoremIpsumSubject1 C=Dolor'); + expect(rows[2]).toHaveTextContent('00B49C541FF5A8E1D9'); + expect(rows[2]).toHaveTextContent('Sat, Aug 10, 2019'); + expect(rows[2]).toHaveTextContent('Tue, Sep 10, 2019'); expect(links[8]).toHaveAttribute( 'href', - '/hosts?filter=name%3D109.876.54.321', + '/hosts?filter=name%3D192.168.9.90', ); expect(links[8]).toHaveAttribute( 'title', - 'Show all Hosts with IP 109.876.54.321', + 'Show all Hosts with IP 192.168.9.90', ); - expect(links[8]).toHaveTextContent('109.876.54.321'); - expect(rows[2]).toHaveTextContent('lorem.ipsum'); - expect(rows[2]).toHaveTextContent('5678'); + expect(links[8]).toHaveTextContent('192.168.9.90'); + expect(rows[2]).toHaveTextContent('foo.bar'); + expect(rows[2]).toHaveTextContent('4023'); expect(icons[5]).toHaveTextContent('download.svg'); + // Row 3 + expect(rows[3]).toHaveTextContent('CN=LoremIpsumSubject2 C=Dolor'); + expect(rows[3]).toHaveTextContent('00C387C32CBB861F5C'); + expect(links[9]).toHaveAttribute( + 'href', + '/hosts?filter=name%3D191.164.9.93', + ); + expect(links[9]).toHaveAttribute( + 'title', + 'Show all Hosts with IP 191.164.9.93', + ); + expect(links[9]).toHaveTextContent('191.164.9.93'); + expect(rows[3]).toHaveTextContent('8445'); + expect(icons[6]).toHaveTextContent('download.svg'); + // Filter expect(baseElement).toHaveTextContent( - '(Applied filter: apply_overrides=0 levels=hml rows=2 min_qod=70 first=1 sort-reverse=severity)', + '(Applied filter: apply_overrides=0 levels=hml rows=3 min_qod=70 first=1 sort-reverse=severity)', ); }); diff --git a/src/web/pages/reports/details/tlscertificatestab.js b/src/web/pages/reports/details/tlscertificatestab.js index 59c2b1fd7f..1df017d6db 100644 --- a/src/web/pages/reports/details/tlscertificatestab.js +++ b/src/web/pages/reports/details/tlscertificatestab.js @@ -31,10 +31,10 @@ import { } from 'web/utils/sort'; const tlsCertificatesSortFunctions = { - dn: makeCompareString('issuer'), + dn: makeCompareString('subject_dn'), serial: makeCompareString('serial'), - notvalidbefore: makeCompareDate('notbefore'), - notvalidafter: makeCompareDate('notafter'), + notvalidbefore: makeCompareDate('activationTime'), + notvalidafter: makeCompareDate('expirationTime'), ip: makeCompareIp('ip'), hostname: makeCompareString('hostname'), port: makeComparePort('port'), diff --git a/src/web/pages/reports/details/tlscertificatestable.js b/src/web/pages/reports/details/tlscertificatestable.js index b575f4674f..e38b79ec7b 100644 --- a/src/web/pages/reports/details/tlscertificatestable.js +++ b/src/web/pages/reports/details/tlscertificatestable.js @@ -36,6 +36,10 @@ import TableHead from 'web/components/table/head'; import TableHeader from 'web/components/table/header'; import TableRow from 'web/components/table/row'; +import TlsCertificateDetails from '../../tlscertificates/details'; +import withRowDetails from 'web/entities/withRowDetails'; +import {RowDetailsToggle} from 'web/entities/row'; + import {createEntitiesTable} from 'web/entities/table'; const Header = ({ @@ -58,7 +62,7 @@ const Header = ({ {...sortProps} sortBy="dn" width={actions ? '35%' : '40%'} - title={_('Issuer DN')} + title={_('Subject DN')} /> { - const {issuer, serial, notafter, notbefore, hostname, ip, port} = entity; + const {serial, activationTime, expirationTime, hostname, ip, port} = entity; return ( - {issuer} + + +
{entity.subject_dn}
+
+
{serial} - + - +