Skip to content

Commit

Permalink
Change: Show extra details for report TLS certificates
Browse files Browse the repository at this point in the history
The first column now shows subject_dn instead of issuer_dn and
the certificate entries are now expandable to show extra details
such as issuer_dn, sha256_fingerprint, md5_fingerprint and valid.
  • Loading branch information
a-h-abdelsalam authored and timopollmeier committed Nov 3, 2023
1 parent b584515 commit 6b15fec
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 166 deletions.
133 changes: 102 additions & 31 deletions src/gmp/models/report/__tests__/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
134 changes: 52 additions & 82 deletions src/gmp/models/report/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
56 changes: 30 additions & 26 deletions src/web/entities/withRowDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,34 +57,38 @@ const StyledTableRow = styled(TableRow)`
}
`;

const withRowDetails = (type, colSpan = '10') => Component => {
const RowDetailsWrapper = ({entity, links = true, ...props}) => (
<StyledTableRow>
<TableData colSpan={colSpan} flex align={['start', 'stretch']}>
{links && (
<Layout align={['start', 'start']}>
<DetailsLink
type={isFunction(type) ? type(entity) : type}
id={entity.id}
>
<DetailsIcon size="small" title={_('Open all details')} />
</DetailsLink>
const withRowDetails =
(type, colSpan = '10', details = true) =>
Component => {
const RowDetailsWrapper = ({entity, links = true, ...props}) => (
<StyledTableRow>
<TableData colSpan={colSpan} flex align={['start', 'stretch']}>
{links && (
<Layout align={['start', 'start']}>
{details && (
<DetailsLink
type={isFunction(type) ? type(entity) : type}
id={entity.id}
>
<DetailsIcon size="small" title={_('Open all details')} />
</DetailsLink>
)}
</Layout>
)}
<Indent />
<Layout flex="column" grow="1">
<Component {...props} links={links} entity={entity} />
</Layout>
)}
<Indent />
<Layout flex="column" grow="1">
<Component {...props} links={links} entity={entity} />
</Layout>
</TableData>
</StyledTableRow>
);

RowDetailsWrapper.propTypes = {
entity: PropTypes.model.isRequired,
links: PropTypes.bool,
</TableData>
</StyledTableRow>
);

RowDetailsWrapper.propTypes = {
entity: PropTypes.model.isRequired,
links: PropTypes.bool,
};
return RowDetailsWrapper;
};
return RowDetailsWrapper;
};

export default withRowDetails;

Expand Down
Loading

0 comments on commit 6b15fec

Please sign in to comment.