diff --git a/src/components/Results/TlsCipherSuites.tsx b/src/components/Results/TlsCipherSuites.tsx new file mode 100644 index 0000000..57cc581 --- /dev/null +++ b/src/components/Results/TlsCipherSuites.tsx @@ -0,0 +1,68 @@ + +import { useState } from 'react'; +import styled from 'styled-components'; +import colors from 'styles/colors'; +import { Card } from 'components/Form/Card'; +import Button from 'components/Form/Button'; +import Row, { ExpandableRow } from 'components/Form/Row'; + +const makeCipherSuites = (results: any) => { + if (!results || !results.connection_info || (results.connection_info.ciphersuite || [])?.length === 0) { + return []; + } + return results.connection_info.ciphersuite.map((ciphersuite: any) => { + return { + title: ciphersuite.cipher, + fields: [ + { lbl: 'Code', val: ciphersuite.code }, + { lbl: 'Protocols', val: ciphersuite.protocols.join(', ') }, + { lbl: 'Pubkey', val: ciphersuite.pubkey }, + { lbl: 'Sigalg', val: ciphersuite.sigalg }, + { lbl: 'Ticket Hint', val: ciphersuite.ticket_hint }, + { lbl: 'OCSP Stapling', val: ciphersuite.ocsp_stapling ? '✅ Enabled' : '❌ Disabled' }, + { lbl: 'PFS', val: ciphersuite.pfs }, + ciphersuite.curves ? { lbl: 'Curves', val: ciphersuite.curves.join(', ') } : {}, + ]}; + }); +}; + +const TlsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => { + + const [cipherSuites, setCipherSuites] = useState(makeCipherSuites(props.data)); + const [loadState, setLoadState] = useState(undefined); + + const updateData = (id: number) => { + setCipherSuites([]); + setLoadState('loading'); + const fetchUrl = `https://tls-observatory.services.mozilla.com/api/v1/results?id=${id}`; + fetch(fetchUrl) + .then((response) => response.json()) + .then((data) => { + setCipherSuites(makeCipherSuites(data)); + setLoadState('success'); + }).catch((error) => { + setLoadState('error'); + }); + }; + + const scanId = props.data?.id; + return ( + + { cipherSuites.length && cipherSuites.map((cipherSuite: any, index: number) => { + return ( + + ); + })} + { !cipherSuites.length && ( +
+

No cipher suites found.
+ This sometimes happens when the report didn't finish generating in-time, you can try re-requesting it. +

+ +
+ )} +
+ ); +} + +export default TlsCard; diff --git a/src/components/Results/TlsClientSupport.tsx b/src/components/Results/TlsClientSupport.tsx new file mode 100644 index 0000000..609e69b --- /dev/null +++ b/src/components/Results/TlsClientSupport.tsx @@ -0,0 +1,87 @@ + +import { useState } from 'react'; +import styled from 'styled-components'; +import colors from 'styles/colors'; +import { Card } from 'components/Form/Card'; +import Button from 'components/Form/Button'; +import Row, { ExpandableRow } from 'components/Form/Row'; + + +// "name": "Android", +// "curve": "secp256r1", +// "version": "6.0", +// "platform": "", +// "protocol": "TLSv1.2", +// "curve_code": 23, +// "ciphersuite": "ECDHE-RSA-CHACHA20-POLY1305-OLD", +// "is_supported": true, +// "protocol_code": 771, +// "ciphersuite_code": 52243 + +const makeClientSupport = (results: any) => { + if (!results?.analysis) return []; + const target = results.target; + const sslLabsClientSupport = ( + results.analysis.find((a: any) => a.analyzer === 'sslLabsClientSupport') + ).result; + + return sslLabsClientSupport.map((sup: any) => { + return { + title: `${sup.name} ${sup.platform ? `(on ${sup.platform})`: sup.version}`, + value: sup.is_supported ? '✅' : '❌', + fields: sup.is_supported ? [ + sup.curve ? { lbl: 'Curve', val: sup.curve } : {}, + { lbl: 'Protocol', val: sup.protocol }, + { lbl: 'Cipher Suite', val: sup.ciphersuite }, + { lbl: 'Protocol Code', val: sup.protocol_code }, + { lbl: 'Cipher Suite Code', val: sup.ciphersuite_code }, + { lbl: 'Curve Code', val: sup.curve_code }, + ] : [ + { lbl: '', val: '', + plaintext: `The host ${target} does not support ${sup.name} ` + +`${sup.version ? `version ${sup.version} `: ''} ` + + `${sup.platform ? `on ${sup.platform} `: ''}`} + ], + }; + }); + +}; + +const TlsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => { + + const [clientSupport, setClientSupport] = useState(makeClientSupport(props.data)); + const [loadState, setLoadState] = useState(undefined); + + const updateData = (id: number) => { + setClientSupport([]); + setLoadState('loading'); + const fetchUrl = `https://tls-observatory.services.mozilla.com/api/v1/results?id=${id}`; + fetch(fetchUrl) + .then((response) => response.json()) + .then((data) => { + setClientSupport(makeClientSupport(data)); + setLoadState('success'); + }).catch(() => { + setLoadState('error'); + }); + }; + + const scanId = props.data?.id; + return ( + + {clientSupport.map((support: any) => { + return () + })} + { !clientSupport.length && ( +
+

No entries available to analyze.
+ This sometimes happens when the report didn't finish generating in-time, you can try re-requesting it. +

+ +
+ )} +
+ ); +} + +export default TlsCard; diff --git a/src/components/Results/TlsIssueAnalysis.tsx b/src/components/Results/TlsIssueAnalysis.tsx new file mode 100644 index 0000000..c696a44 --- /dev/null +++ b/src/components/Results/TlsIssueAnalysis.tsx @@ -0,0 +1,120 @@ + +import { useState } from 'react'; +import styled from 'styled-components'; +import colors from 'styles/colors'; +import { Card } from 'components/Form/Card'; +import Button from 'components/Form/Button'; +import Row, { ExpandableRow } from 'components/Form/Row'; + +const Expandable = styled.details` +margin-top: 0.5rem; +cursor: pointer; +summary::marker { + color: ${colors.primary}; +} +`; + +const makeExpandableData = (results: any) => { + if (!results || !results.analysis || results.analysis.length === 0) { + return []; + } + return results.analysis.map((analysis: any) => { + const fields = Object.keys(analysis.result).map((label) => { + const lbl = isNaN(parseInt(label, 10)) ? label : ''; + const val = analysis.result[label] || 'None'; + if (typeof val !== 'object') { + return { lbl, val }; + } + return { lbl, val: '', plaintext: JSON.stringify(analysis.result[label])}; + }); + return { + title: analysis.analyzer, + value: analysis.success ? '✅' : '❌', + fields, + }; + }); +}; + +const makeResults = (results: any) => { + const rows: { lbl: string; val?: any; plaintext?: string; list?: string[] }[] = []; + if (!results || !results.analysis || results.analysis.length === 0) { + return rows; + } + const caaWorker = results.analysis.find((a: any) => a.analyzer === 'caaWorker'); + if (caaWorker.result.host) rows.push({ lbl: 'Host', val: caaWorker.result.host }); + if (typeof caaWorker.result.has_caa === 'boolean') rows.push({ lbl: 'CA Authorization', val: caaWorker.result.has_caa }); + if (caaWorker.result.issue) rows.push({ lbl: 'CAAs allowed to Issue Certs', plaintext: caaWorker.result.issue.join('\n') }); + + const mozillaGradingWorker = (results.analysis.find((a: any) => a.analyzer === 'mozillaGradingWorker')).result; + if (mozillaGradingWorker.grade) rows.push({ lbl: 'Mozilla Grading', val: mozillaGradingWorker.grade }); + if (mozillaGradingWorker.gradeTrust) rows.push({ lbl: 'Mozilla Trust', val: mozillaGradingWorker.gradeTrust }); + + const symantecDistrust = (results.analysis.find((a: any) => a.analyzer === 'symantecDistrust')).result; + if (typeof symantecDistrust.isDistrusted === 'boolean') rows.push({ lbl: 'No distrusted symantec SSL?', val: !symantecDistrust.isDistrusted }); + if (symantecDistrust.reasons) rows.push({ lbl: 'Symantec Distrust', plaintext: symantecDistrust.reasons.join('\n') }); + + const top1m = (results.analysis.find((a: any) => a.analyzer === 'top1m')).result; + if (top1m.certificate.rank) rows.push({ lbl: 'Certificate Rank', val: top1m.certificate.rank.toLocaleString() }); + + const mozillaEvaluationWorker = (results.analysis.find((a: any) => a.analyzer === 'mozillaEvaluationWorker')).result; + if (mozillaEvaluationWorker.level) rows.push({ lbl: 'Mozilla Evaluation Level', val: mozillaEvaluationWorker.level }); + if (mozillaEvaluationWorker.failures) { + const { bad, old, intermediate, modern } = mozillaEvaluationWorker.failures; + if (bad) rows.push({ lbl: `Critical Security Issues (${bad.length})`, list: bad }); + if (old) rows.push({ lbl: `Compatibility Config Issues (${old.length})`, list: old }); + if (intermediate) rows.push({ lbl: `Intermediate Issues (${intermediate.length})`, list: intermediate }); + if (modern) rows.push({ lbl: `Modern Issues (${modern.length})`, list: modern }); + } + return rows; +}; + +const TlsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => { + + const [tlsRowData, setTlsRowWata] = useState(makeExpandableData(props.data)); + const [tlsResults, setTlsResults] = useState(makeResults(props.data)); + const [loadState, setLoadState] = useState(undefined); + + const updateData = (id: number) => { + setTlsRowWata([]); + setLoadState('loading'); + const fetchUrl = `https://tls-observatory.services.mozilla.com/api/v1/results?id=${id}`; + fetch(fetchUrl) + .then((response) => response.json()) + .then((data) => { + setTlsRowWata(makeExpandableData(data)); + setTlsResults(makeResults(data)); + setLoadState('success'); + }).catch(() => { + setLoadState('error'); + }); + }; + + const scanId = props.data?.id; + return ( + + { tlsResults.length > 0 && tlsResults.map((row: any, index: number) => { + return ( + + ); + })} + + Full Analysis Results + { tlsRowData.length > 0 && tlsRowData.map((cipherSuite: any, index: number) => { + return ( + + ); + })} + + { !tlsRowData.length && ( +
+

No entries available to analyze.
+ This sometimes happens when the report didn't finish generating in-time, you can try re-requesting it. +

+ +
+ )} +
+ ); +} + +export default TlsCard;