Esempio n. 1
0
    def test_append_judgements(self):
        judgements = SecurityJudgements()
        self.assertEqual(len(judgements), 0)

        judgements += None
        sub = SecurityJudgements()
        sub += SecurityJudgement(
            code=JudgementCode.X509Cert_PublicKey_RSA_ParameterFieldNotPresent,
            text="Foo Bar! 1",
            verdict=Verdict.HIGH,
            commonness=Commonness.UNUSUAL)
        sub += SecurityJudgement(
            code=JudgementCode.X509Cert_PublicKey_RSA_ParameterFieldNotPresent,
            text="Foo Bar! 2",
            verdict=Verdict.HIGH,
            commonness=Commonness.UNUSUAL)
        judgements += sub
        self.assertEqual(len(judgements), 2)

        judgements += SecurityJudgement(
            code=JudgementCode.X509Cert_PublicKey_RSA_ParameterFieldNotPresent,
            text="Foo Bar! 3",
            verdict=Verdict.HIGH,
            commonness=Commonness.UNUSUAL)
        self.assertEqual(len(judgements), 3)

        self.assertEqual(judgements[0].text, "Foo Bar! 1")
        self.assertEqual(judgements[1].text, "Foo Bar! 2")
        self.assertEqual(judgements[2].text, "Foo Bar! 3")
Esempio n. 2
0
    def _judge_name(self, certificate, name):
        judgements = SecurityJudgements()
        rdns = certificate.subject.get_all(OIDDB.RDNTypes.inverse("CN"))
        have_valid_cn = False
        if len(rdns) > 0:
            found_rdn = None
            for rdn in rdns:
                value = rdn.get_value(OIDDB.RDNTypes.inverse("CN"))
                if ValidationTools.validate_domainname_template_match(
                        value.printable_value, name):
                    found_rdn = rdn
                    break
            if found_rdn is not None:
                if found_rdn.component_cnt == 1:
                    judgements += SecurityJudgement(
                        JudgementCode.CertUsage_Purpose_ServerCert_CN_Match,
                        "Common name (CN) matches '%s'." % (name),
                        commonness=Commonness.COMMON)
                else:
                    judgements += SecurityJudgement(
                        JudgementCode.
                        CertUsage_Purpose_ServerCert_CN_MatchMultivalueRDN,
                        "Common name (CN) matches '%s', but is part of a multi-valued RDN: %s"
                        % (name, found_rdn.pretty_str),
                        commonness=Commonness.HIGHLY_UNUSUAL)
            else:
                judgements += SecurityJudgement(
                    JudgementCode.CertUsage_Purpose_ServerCert_CN_Mismatch,
                    "No common name (CN) matches '%s'." % (name),
                    commonness=Commonness.UNUSUAL)

        have_valid_san = False
        extension = certificate.extensions.get_first(
            OIDDB.X509Extensions.inverse("SubjectAlternativeName"))
        if extension is not None:
            for san_name in extension.get_all("dNSName"):
                if ValidationTools.validate_domainname_template_match(
                        san_name.str_value, name):
                    have_valid_san = True
                    judgements += SecurityJudgement(
                        JudgementCode.CertUsage_Purpose_ServerCert_SAN_Match,
                        "Subject Alternative Name matches '%s'." % (name),
                        commonness=Commonness.COMMON)
                    break
            else:
                judgements += SecurityJudgement(
                    JudgementCode.CertUsage_Purpose_ServerCert_SAN_Mismatch,
                    "No Subject Alternative Name X.509 extension matches '%s'."
                    % (name),
                    commonness=Commonness.UNUSUAL)

        if (not have_valid_cn) and (not have_valid_san):
            judgements += SecurityJudgement(
                JudgementCode.
                CertUsage_Purpose_ServerCert_NameVerificationFailed,
                "Found neither valid common name (CN) nor valid subject alternative name (SAN).",
                commonness=Commonness.HIGHLY_UNUSUAL,
                verdict=Verdict.NO_SECURITY)

        return judgements
Esempio n. 3
0
    def _judge_curve_cofactor(self, curve):
        judgements = SecurityJudgements()

        if curve.h is None:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_Cofactor_Missing,
                "Curve cofactor h is not present in explicit domain parameter encoding. This is allowed, but highly unusual.",
                commonness=Commonness.HIGHLY_UNUSUAL)
        else:
            if curve.h <= 0:
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_Cofactor_Invalid,
                    "Curve cofactor h = %d is zero or negative. This is invalid."
                    % (curve.h),
                    bits=0,
                    commonness=Commonness.HIGHLY_UNUSUAL)
            elif curve.h > 8:
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_Cofactor_Large,
                    "Curve cofactor is unusually large, h = %d. This is an indication the curve has non-ideal cryptographic properties; would expect h <= 8."
                    % (curve.h),
                    commonness=Commonness.HIGHLY_UNUSUAL)

            if curve.curvetype == "prime":
                field_size = curve.p
            elif curve.curvetype == "binary":
                # TODO: For GF(p), the number of group elements is simply p.
                # For GF(2^m), I'm fairly certiain it is the reduction
                # polynomial (essentially, it's modular polynomial arithmetic).
                # Not 100% sure though. Verify.
                field_size = curve.int_poly

            # Hasse Theorem on Elliptic Curves:
            # p - (2 * sqrt(p)) + 1 <= #E(F_p) <= p + (2 * sqrt(p)) + 1
            # #E(F_p) = n h
            # h >= (p - (2 * sqrt(p)) + 1) / n
            # h <= (p + (2 * sqrt(p)) + 1) / n
            sqrt_p = NumberTheory.isqrt(field_size)
            hasse_h_min = (field_size - (2 * (sqrt_p + 1)) + 1) // curve.n
            hasse_h_max = (field_size + (2 * (sqrt_p + 1)) + 1) // curve.n

            if not (hasse_h_min <= curve.h <= hasse_h_max):
                literature = LiteratureReference(
                    author="Helmut Hasse",
                    title=
                    "Zur Theorie der abstrakten elliptischen Funktionenkörper. I, II & III",
                    year=1936,
                    source="Crelle's Journal")
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_Cofactor_OutsideHasseBound,
                    "Curve cofactor h = %d is outside the Hasse bound (%d <= h <= %d). Cofactor therefore is invalid."
                    % (curve.h, hasse_h_min, hasse_h_max),
                    bits=0,
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    literature=literature)
        return judgements
Esempio n. 4
0
    def analyze(self, hash_fnc):
        if hash_fnc.value.derating is None:
            bits_security = hash_fnc.value.output_bits / 2
        else:
            bits_security = hash_fnc.value.derating.security_lvl_bits

        judgements = SecurityJudgements()
        judgements += self.algorithm("bits").analyze(
            JudgementCode.X509Cert_Signature_HashFunction_DigestLengthInBits,
            bits_security)
        if hash_fnc.value.derating is not None:
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Signature_HashFunction_Derated,
                "Derated from ideal %d bits security level because of %s." %
                (hash_fnc.value.output_bits / 2,
                 hash_fnc.value.derating.reason))

        result = {
            "name": hash_fnc.value.name,
            "pretty": hash_fnc.value.pretty_name,
            "bitlen": hash_fnc.value.output_bits,
            "security": judgements,
        }
        if self._analysis_options.include_raw_data:
            result["oid"] = str(hash_fnc.value.oid)
        return result
Esempio n. 5
0
    def _judge_signature(self, certificate, ca_certificate):
        judgements = SecurityJudgements()

        if not ca_certificate.is_ca_certificate:
            judgements += SecurityJudgement(
                JudgementCode.
                CertUsage_CARelationship_CACertificateInvalidAsCA,
                "CA certificate is not valid as a CA.",
                verdict=Verdict.NO_SECURITY,
                commonness=Commonness.HIGHLY_UNUSUAL)

        if not certificate.signed_by(ca_certificate):
            judgements += SecurityJudgement(
                JudgementCode.
                CertUsage_CARelationship_Signature_VerificationFailure,
                "Certificate signature is not verifiable with CA public key.",
                verdict=Verdict.NO_SECURITY,
                commonness=Commonness.HIGHLY_UNUSUAL)
        else:
            judgements += SecurityJudgement(
                JudgementCode.
                CertUsage_CARelationship_Signature_VerificationSuccess,
                "Certificate signature is valid.",
                commonness=Commonness.COMMON)

        return judgements
Esempio n. 6
0
    def _check_explicit_curve_params(self, curve):
        judgements = SecurityJudgements()

        curve_db = CurveDB()
        known_curve = curve_db.lookup_by_params(curve)
        if known_curve is None:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_Name_UnknownExplicit,
                "Explicit curve domain parameter encoding with domain parameters that are not present in the database. Highly suspect, convervatively rating as broken security.",
                commonness=Commonness.HIGHLY_UNUSUAL,
                bits=0)
        else:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_Name_UnusedName,
                "Explicit curve domain parameter encoding is used; curve domain parameters are equal to curve %s (OID %s). Recommend switching to that named curve."
                % (known_curve.name, known_curve.oid))

        judgements += self._judge_curve_cofactor(curve)

        if curve.curvetype == "binary":
            if len(curve.poly) != len(set(curve.poly)):
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_ECC_DomainParameters_BinaryField_DuplicatePolynomialPower,
                    "ECC field polynomial contains duplicate powers: %s -- Conservatively rating as broken security."
                    % (str(curve.poly)),
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    bits=0)

            for custom_coeff in curve.poly[1:-1]:
                if custom_coeff <= 1:
                    judgements += SecurityJudgement(
                        JudgementCode.
                        X509Cert_PublicKey_ECC_DomainParameters_BinaryField_InvalidPolynomialPower,
                        "ECC field polynomial contains x^%d where it would be expected to see a power of two or higher."
                        % (custom_coeff),
                        commonness=Commonness.HIGHLY_UNUSUAL,
                        bits=0)
                elif custom_coeff >= curve.m:
                    judgements += SecurityJudgement(
                        JudgementCode.
                        X509Cert_PublicKey_ECC_DomainParameters_BinaryField_InvalidPolynomialPower,
                        "ECC field polynomial contains x^%d where it would be expected to see a power of less than x^m (i.e., x^%d)."
                        % (custom_coeff, curve.m),
                        commonness=Commonness.HIGHLY_UNUSUAL,
                        bits=0)
        elif curve.curvetype == "prime":
            # Only check embedding degree for non-named (explicit) curves; it's
            # a comparatively expensive test and we assume that curves in the
            # database are all cryptographically sound.
            judgements += self._judge_curve_embedding_degree(curve)

            # This isn't computationally expensive, but we also assume database
            # curves are cryptographically sound.
            judgements += self._judge_curve_cofactor(curve)

        return judgements
Esempio n. 7
0
    def analyze(self, pubkey):
        judgements = SecurityJudgements()
        result = {
            "cryptosystem": "rsa",
            "specific": {
                "n": {
                    "bits": pubkey.n.bit_length(),
                    "security": self.analyze_n(pubkey.n),
                },
                "e": {
                    "security": self.analyze_e(pubkey.e),
                },
            },
            "security": judgements,
        }

        if pubkey.params is None:
            standard = RFCReference(
                rfcno=3279,
                sect="2.2.1",
                verb="MUST",
                text=
                "When any of these three OIDs appears within the ASN.1 type AlgorithmIdentifier, the parameters component of that type SHALL be the ASN.1 type NULL."
            )
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_RSA_ParameterFieldNotPresent,
                "RSA parameter field should be present and should be of Null type, but is not present at all.",
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.STANDARDS_DEVIATION,
                standard=standard)
        else:
            # There is a parameters field present, it must be NULL
            (asn1_params,
             tail) = pyasn1.codec.der.decoder.decode(bytes(pubkey.params))
            if not isinstance(asn1_params, pyasn1.type.univ.Null):
                standard = RFCReference(
                    rfcno=3279,
                    sect="2.2.1",
                    verb="MUST",
                    text=
                    "When any of these three OIDs appears within the ASN.1 type AlgorithmIdentifier, the parameters component of that type SHALL be the ASN.1 type NULL."
                )
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_RSA_RSAEncryption_ParameterFieldNotNULL,
                    "RSA parameter field should be present and should be of Null type, but has different ASN.1 type %s."
                    % (type(asn1_params).__name__),
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    compatibility=Compatibility.STANDARDS_DEVIATION,
                    standard=standard)

        judgements += result["specific"]["n"]["security"]
        judgements += result["specific"]["e"]["security"]

        if self._analysis_options.include_raw_data:
            result["specific"]["n"]["value"] = pubkey.n
            result["specific"]["e"]["value"] = pubkey.e
        return result
Esempio n. 8
0
	def _error_public_key_type_unknown(self, certificate, decoding_error):
		judgements = SecurityJudgements()
		judgements += SecurityJudgement(JudgementCode.X509Cert_PublicKey_UnknownKeyType, "Certificate public key uses unknown key system: %s Conservatively estimating broken security." % (decoding_error), bits = 0, compatibility = Compatibility.LIMITED_SUPPORT, commonness = Commonness.HIGHLY_UNUSUAL)
		result = {
			"pubkey_alg":	None,
			"pretty":		"Undecodable public key: %s" % (decoding_error),
			"security":		judgements,
		}
		return result
Esempio n. 9
0
	def _error_curve_not_found(self, certificate, exception):
		judgements = SecurityJudgements()
		judgements += SecurityJudgement(JudgementCode.X509Cert_PublicKey_ECC_DomainParameters_Name_UnkownName, "Certificate public key relies on unknown elliptic curve: %s Conservatively estimating broken security." % (str(exception)), bits = 0, compatibility = Compatibility.LIMITED_SUPPORT, commonness = Commonness.HIGHLY_UNUSUAL)
		result = {
			"pubkey_alg":	None,
			"pretty":		"Unrecognized elliptic curve: %s" % (str(exception)),
			"security":		judgements,
		}
		return result
Esempio n. 10
0
    def _judge_prime_field_curve(self, curve):
        judgements = SecurityJudgements()

        judgements += SecurityJudgement(
            JudgementCode.X509Cert_PublicKey_ECC_DomainParameters_PrimeField,
            "Prime finite field elliptic curve is used.",
            commonness=Commonness.COMMON)

        if curve.h is not None:
            # TODO: We might be able to guess the cofactor because of the Hasse bound.
            # E(Fp) = p + 1 - t
            # t = p - E(Fp) + 1
            EFp = curve.n * curve.h
            trace = curve.p - EFp + 1
            if trace == 0:
                literature = LiteratureReference(
                    author=[
                        "Alfred Menezes", "Scott Vanstone", "Tatsuaki Okamoto"
                    ],
                    title=
                    "Reducing Elliptic Curve Logarithms to Logarithms in a Finite Field",
                    year=1991,
                    source="ACM")
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_SupersingularCurve,
                    "This curve is supersingular, trace is zero. The curve can be attacked using the probabilistic polynomial-time MOV attack.",
                    bits=0,
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    literature=literature)
            elif trace == 1:
                literature = LiteratureReference(
                    author=["Nigel P. Smart"],
                    title=
                    "The discrete logarithm problem on elliptic curves of trace one",
                    year=1997,
                    month=10,
                    source="HP Laboratories Bristol")
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_AnomalousCurve,
                    "This curve is anomalous, #E(F_p) is equal to p. The curve can be attacked in linear time.",
                    bits=0,
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    literature=literature)

        p = ((4 * (curve.a**3)) + (27 * (curve.b**2))) % curve.p
        if p == 0:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_SingularCurve,
                "This curve is singular, 4a³ + 27b² = 0 mod p. Mathematical assumptions about the structure of the curve do not hold.",
                bits=0,
                commonness=Commonness.HIGHLY_UNUSUAL)

        return judgements
Esempio n. 11
0
    def test_serialize_judgements(self):
        judgements = SecurityJudgements()
        judgements += SecurityJudgement(
            code=JudgementCode.X509Cert_PublicKey_RSA_ParameterFieldNotPresent,
            text="Foo Bar! 1",
            verdict=Verdict.HIGH,
            commonness=Commonness.UNUSUAL)
        judgements += SecurityJudgement(
            code=JudgementCode.X509Cert_PublicKey_RSA_ParameterFieldNotPresent,
            text="Foo Bar! 2",
            verdict=Verdict.HIGH,
            commonness=Commonness.UNUSUAL)

        serialized = judgements.to_dict()
        self.assertEqual(serialized["components"][0]["text"], "Foo Bar! 1")
        self.assertEqual(serialized["components"][1]["text"], "Foo Bar! 2")

        judgements = SecurityJudgements.from_dict(serialized)
        self.assertEqual(judgements[0].text, "Foo Bar! 1")
        self.assertEqual(judgements[1].text, "Foo Bar! 2")
Esempio n. 12
0
    def _judge_binary_field_curve(self, curve):
        judgements = SecurityJudgements()

        literature = LiteratureReference(
            author=["Steven D. Galbraith", "Shishay W. Gebregiyorgis"],
            title=
            "Summation polynomial algorithms for elliptic curves in characteristic two",
            year=2014,
            source="Progress in Cryptology -- INDOCRYPT 2014; LNCS 8885")
        judgements += SecurityJudgement(
            JudgementCode.X509Cert_PublicKey_ECC_DomainParameters_BinaryField,
            "Binary finite field elliptic curve is used. Recent advances in cryptography show there might be efficient attacks on such curves, hence it is recommended to use prime-field curves instead.",
            commonness=Commonness.UNUSUAL,
            literature=literature)

        EFpm = curve.n * curve.h
        if (EFpm % 2) == 1:
            # Supersingular: #E(F_p^m) = 1 mod p
            literature = LiteratureReference(
                author=[
                    "Alfred Menezes", "Scott Vanstone", "Tatsuaki Okamoto"
                ],
                title=
                "Reducing Elliptic Curve Logarithms to Logarithms in a Finite Field",
                year=1991,
                source="ACM")
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_SupersingularCurve,
                "This curve is supersingular, #E(F_p^m) = 1 mod p. The curve can be attacked using an probabilistic polynomial-time MOV attack.",
                bits=0,
                commonness=Commonness.HIGHLY_UNUSUAL,
                literature=literature)

        if not NumberTheory.is_probable_prime(curve.m):
            literature = LiteratureReference(
                author=[
                    "Jeffrey Hoffstein", "Jill Pipher", "Joseph Silverman"
                ],
                title="An Introduction to Mathematical Cryptography",
                year=2008,
                source="Springer")
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_WeilDescent,
                "Binary finite field elliptic curve has a field size that is non-primem, F(2^%d). Weil Descent attacks could be successful.",
                bits=0,
                commonness=Commonness.HIGHLY_UNUSUAL,
                literature=literature)

        return judgements
Esempio n. 13
0
    def _determine_hash_function(self, signature_alg, signature_alg_params):
        hash_fnc = None
        judgements = SecurityJudgements()
        if signature_alg == SignatureAlgorithms.RSASSA_PSS:
            (hash_fnc, new_judgements
             ) = self._analyze_rsa_pss_signature_params(signature_alg_params)
            judgements += new_judgements
        else:
            judgements += SecurityJudgement(
                JudgementCode.X509sakIssues_AnalysisNotImplemented,
                "Cannot determine hash function for signature algorithm %s. This might be a shortcoming of x509sak; please report the certificate in question to the developers."
                % (signature_alg.name))

        return (hash_fnc, judgements)
Esempio n. 14
0
    def analyze(self, pubkey):
        judgements = SecurityJudgements()

        if pubkey.curve_source != "implicitCurve":
            # Can only check most of the things when there's a curve we can actually check
            judgements += self._analyze_curve(pubkey)

        if pubkey.curve_source == "namedCurve":
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_Name_NamedCurve,
                "Curve referenced by name in parameter field.",
                commonness=Commonness.COMMON)
        elif pubkey.curve_source == "specifiedCurve":
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_Name_ExplicitCurve,
                "Curve uses explicit encoding for domain parameters. Typically, named curves are used; explicit encoding of domain parameters is not recommended and may be rejected by implementations for simplicity reasons.",
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.LIMITED_SUPPORT)
            judgements += self._check_explicit_curve_encoding(pubkey)
            judgements += self._check_explicit_curve_params(pubkey.curve)
        else:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_Name_ImplicitCurve,
                "Curve uses implicit curve for domain parameters. This is highly uncommon practice and strongly discouraged.",
                bits=0,
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.LIMITED_SUPPORT)

        result = {
            "specific": {},
            "security": judgements,
        }

        if pubkey.pk_alg.value.cryptosystem == Cryptosystems.ECC_ECDSA:
            result["cryptosystem"] = "ecc/ecdsa"
        elif pubkey.pk_alg.value.cryptosystem == Cryptosystems.ECC_EdDSA:
            result["cryptosystem"] = "ecc/eddsa"
        else:
            raise NotImplementedError(
                "ECC estimator currently not fit to analyze a %s pubkey." %
                (pubkey.pk_alg.value.cryptosystem.name))

        return result
Esempio n. 15
0
    def _print_security_judgements(self, judgements_data, indent=""):
        judgements = SecurityJudgements.from_dict(judgements_data)
        if len(judgements) == 0:
            self._printer.print("%sNo comments regarding this check." %
                                (indent))
        elif len(judgements) == 1:
            self._printer.print(
                "%s%s" % (indent, self._fmt_security_judgement(judgements[0])))
        else:
            for (jid, judgement) in enumerate(judgements, 1):
                self._printer.print("%s%d / %d: %s" %
                                    (indent, jid, len(judgements),
                                     self._fmt_security_judgement(judgement)))

            summary_judgement = judgements.summary_judgement()
            color = self._fmt_color(summary_judgement)
            self._printer.print(
                "%s    -> Summary: <%s>%s<end>" %
                (indent, color, self._fmt_textual_verdict(summary_judgement)))
Esempio n. 16
0
    def _judge_validity_timestamps(self, certificate, ca_certificate):
        judgements = SecurityJudgements()
        if any(ts is None for ts in [
                certificate.valid_not_before, certificate.valid_not_after,
                ca_certificate.valid_not_before, ca_certificate.valid_not_after
        ]):
            judgements += SecurityJudgement(
                JudgementCode.
                CertUsage_CARelationship_Validity_MalformedTimestamp,
                "Certificate or CA certificate validity field malformed, cannot perform checking of time intervals.",
                verdict=Verdict.NO_SECURITY,
                commonness=Commonness.HIGHLY_UNUSUAL)
        else:
            if certificate.valid_not_before > ca_certificate.valid_not_after:
                judgements += SecurityJudgement(
                    JudgementCode.CertUsage_CARelationship_Validity_NoOverlap,
                    "Certifiate becomes valid after CA certificate has expired.",
                    verdict=Verdict.NO_SECURITY,
                    commonness=Commonness.HIGHLY_UNUSUAL)
            elif certificate.valid_not_after < ca_certificate.valid_not_before:
                judgements += SecurityJudgement(
                    JudgementCode.CertUsage_CARelationship_Validity_NoOverlap,
                    "Certifiate becomes invalid before CA certificate becomes valid.",
                    verdict=Verdict.NO_SECURITY,
                    commonness=Commonness.HIGHLY_UNUSUAL)
            elif ca_certificate.valid_not_before <= certificate.valid_not_before <= certificate.valid_not_after <= ca_certificate.valid_not_after:
                judgements += SecurityJudgement(
                    JudgementCode.
                    CertUsage_CARelationship_Validity_FullOverlap,
                    "Certifiate validity interval falls fully into CA certificate validity.",
                    commonness=Commonness.COMMON)
            else:
                judgements += SecurityJudgement(
                    JudgementCode.
                    CertUsage_CARelationship_Validity_PartialOverlap,
                    "Certifiate validity interval falls partially into CA certificate validity. The CA certificate therefore is only useful for a portion of lifetime of the certificate under test.",
                    commonness=Commonness.UNUSUAL)

        return judgements
Esempio n. 17
0
	def analyze(self, certificate):
		try:
			pubkey = certificate.pubkey
			if not pubkey.key_decodable:
				return self._error_public_key_type_unknown(certificate, pubkey.key_decoding_error)
		except CurveNotFoundException as e:
			return self._error_curve_not_found(certificate, e)

		result = {
			"pubkey_alg":	pubkey.pk_alg.value.name,
			"security":		SecurityJudgements(),
		}

		if pubkey.key.malformed:
			result["pretty"] = "%s with malformed encoding" % (pubkey.pk_alg.name)
		else:
			if pubkey.pk_alg.value.cryptosystem == Cryptosystems.RSA:
				result["pretty"] = "RSA with %d bit modulus" % (pubkey.n.bit_length())
				result.update(self.algorithm("rsa").analyze(pubkey))
			elif pubkey.pk_alg.value.cryptosystem == Cryptosystems.DSA:
				result["pretty"] = "DSA with %d bit modulus and %d bit output" % (pubkey.p.bit_length(), pubkey.q.bit_length())
				result.update(self.algorithm("dsa").analyze(pubkey))
			elif pubkey.pk_alg.value.cryptosystem == Cryptosystems.ECC_ECDSA:
				if pubkey.curve_source == "namedCurve":
					result["pretty"] = "ECC on %s" % (pubkey.curve.name)
				elif pubkey.curve_source == "specifiedCurve":
					result["pretty"] = "ECC on specified %s" % (str(pubkey.curve))
				else:
					result["pretty"] = "ECC on %s" % (str(pubkey.curve_source))
				result.update(self.algorithm("ecc").analyze(pubkey))
			elif pubkey.pk_alg.value.cryptosystem == Cryptosystems.ECC_EdDSA:
				result["pretty"] = "EdDSA on %s" % (pubkey.curve.name)
				result.update(self.algorithm("ecc").analyze(pubkey))
			else:
				raise LazyDeveloperException(NotImplemented, pubkey.pk_alg.value.cryptosystem)

		result["security"] += self._analyze_pubkey_encoding(certificate.pubkey.key)
		return result
Esempio n. 18
0
    def _check_explicit_curve_encoding(self, pubkey):
        judgements = SecurityJudgements()
        param_decoding = pubkey.key.decoding_details[0]
        if param_decoding.asn1 is not None:
            seed_bitstring = param_decoding.asn1.getComponent(
            )["curve"]["seed"]
            if seed_bitstring.hasValue():
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_ECC_DomainParameters_Seed_Present,
                    "Explicitly encoded public key contains seed. This is unnecessary, but not uncommon."
                )

                if len(seed_bitstring) == 0:
                    judgements += SecurityJudgement(
                        JudgementCode.
                        X509Cert_PublicKey_ECC_DomainParameters_Seed_Empty,
                        "Explicitly encoded public key contains seed, but that seed is empty.",
                        commonness=Commonness.UNUSUAL)
                elif len(seed_bitstring) > 256:
                    judgements += SecurityJudgement(
                        JudgementCode.
                        X509Cert_PublicKey_ECC_DomainParameters_Seed_Long,
                        "Explicitly encoded public key contains long seed (%d bits)."
                        % (len(seed_bitstring)),
                        commonness=Commonness.UNUSUAL)

                if (len(seed_bitstring) % 8) != 0:
                    judgements += SecurityJudgement(
                        JudgementCode.
                        X509Cert_PublicKey_ECC_DomainParameters_Seed_NoByteString,
                        "Explicitly encoded public key seed has uneven bit length (%d bits)."
                        % (len(seed_bitstring)),
                        commonness=Commonness.HIGHLY_UNUSUAL)

        return judgements
Esempio n. 19
0
    def test_judgements(self):
        judgements = SecurityJudgements()
        self.assertTrue(judgements.uniform_topic)
        self.assertEqual(len(judgements), 0)

        judgements += SecurityJudgement(
            code=JudgementCode.X509Cert_PublicKey_RSA_ParameterFieldNotPresent,
            text="Foo Bar! 1",
            verdict=Verdict.HIGH,
            commonness=Commonness.UNUSUAL)
        self.assertTrue(judgements.uniform_topic)
        self.assertEqual(len(judgements), 1)

        judgements += SecurityJudgement(
            code=JudgementCode.X509Cert_PublicKey_RSA_ParameterFieldNotPresent,
            text="Foo Bar! 2",
            verdict=Verdict.BROKEN,
            commonness=Commonness.COMMON)
        self.assertTrue(judgements.uniform_topic)
        self.assertEqual(len(judgements), 2)

        self.assertEqual(judgements.verdict, Verdict.BROKEN)
        self.assertEqual(judgements.commonness, Commonness.UNUSUAL)
        self.assertEqual(judgements.bits, None)
Esempio n. 20
0
    def analyze(self, sig_fnc):
        judgements = SecurityJudgements()
        if sig_fnc.value.name == "rsa-ssa-pss":
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Signature_Function_UncommonPadding,
                "Not widely used padding scheme for RSA.",
                compatibility=Compatibility.LIMITED_SUPPORT)
        elif sig_fnc.value.name == "eddsa":
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Signature_Function_UncommonCryptosystem,
                "Not widely used cryptosystem.",
                verdict=Verdict.BEST_IN_CLASS,
                compatibility=Compatibility.LIMITED_SUPPORT)
        else:
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Signature_Function_Common,
                "Commonly used signature function.",
                commonness=Commonness.COMMON)

        return {
            "name": sig_fnc.name,
            "pretty": sig_fnc.value.pretty_name,
            "security": judgements,
        }
Esempio n. 21
0
	def _analyze_pubkey_encoding_rsa(self, pubkey):
		judgements = SecurityJudgements()
		asn1_details = pubkey.decoding_details
		judgements += self._RSA_PUBKEY_DER_VALIDATOR.validate(asn1_details)
		return judgements
Esempio n. 22
0
    def analyze_n(self, n):
        judgements = SecurityJudgements()

        if n < 0:
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_RSA_Modulus_Negative,
                "Modulus uses incorrect encoding, representation is a negative integer.",
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.STANDARDS_DEVIATION)

            # Fix up n so it's a positive integer for the rest of the tests
            bitlen = (n.bit_length() + 7) // 8 * 8
            mask = (1 << bitlen) - 1
            n = n & mask
        elif n == 0:
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_RSA_Modulus_Zero,
                "Modulus is zero, this is definitely a broken RSA public key.",
                bits=0,
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.STANDARDS_DEVIATION)
        elif n == 1:
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_RSA_Modulus_One,
                "Modulus is one, this is definitely a broken RSA public key.",
                bits=0,
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.STANDARDS_DEVIATION)

        if self._test_probable_prime:
            if NumberTheory.is_probable_prime(n):
                judgements += SecurityJudgement(
                    JudgementCode.X509Cert_PublicKey_RSA_Modulus_Prime,
                    "Modulus is prime, not a compound integer as we would expect for RSA.",
                    bits=0)

        if self._pollards_rho_iterations > 0:
            small_factor = NumberTheory.pollard_rho(
                n, max_iterations=self._pollards_rho_iterations)
            if small_factor is not None:
                judgements += SecurityJudgement(
                    JudgementCode.X509Cert_PublicKey_RSA_Modulus_Factorable,
                    "Modulus has small factor (%d) and is therefore trivially factorable."
                    % (small_factor),
                    bits=0)

        match = ModulusDB().find(n)
        if match is not None:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_RSA_Modulus_FactorizationKnown,
                "Modulus is known to be compromised: %s" % (match.text),
                bits=0)

        hweight_analysis = NumberTheory.hamming_weight_analysis(n)
        if (hweight_analysis
                is not None) and (not hweight_analysis.plausibly_random):
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_RSA_Modulus_BitBiasPresent,
                "Modulus does not appear to be random. Expected a Hamming weight between %d and %d for a %d bit modulus, but found Hamming weight %d."
                % (hweight_analysis.rnd_min_hweight,
                   hweight_analysis.rnd_max_hweight, hweight_analysis.bitlen,
                   hweight_analysis.hweight),
                commonness=Commonness.HIGHLY_UNUSUAL)

        # We estimate the complexity of factoring the modulus by the asymptotic
        # complexity of the GNFS.
        bits_security = NumberTheory.asymtotic_complexity_gnfs_bits(n)
        judgements += self.algorithm("bits").analyze(
            JudgementCode.X509Cert_PublicKey_RSA_Modulus_LengthInBits,
            bits_security)

        return judgements
Esempio n. 23
0
 def __init__(self, validator, subject):
     self._validator = validator
     self._subject = subject
     self._result = SecurityJudgements()
Esempio n. 24
0
    def _analyze_curve(self, pubkey):
        judgements = SecurityJudgements()
        curve = pubkey.curve

        # Check that the encoded public key point is on curve first
        Q = curve.point(pubkey.x, pubkey.y)
        if not Q.on_curve():
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_ECC_PublicKeyPoint_NotOnCurve,
                "Public key point Q is not on the underlying curve %s." %
                (pubkey.curve),
                bits=0)

        # Check that the encoded public key is not Gx
        if Q.x == curve.Gx:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_PublicKeyPoint_IsGenerator,
                "Public key point Q_x is equal to generator G_x on curve %s." %
                (pubkey.curve),
                bits=0)

        # We assume, completely out-of-the-blue and worst-case estimate, 32
        # automorphisms that could be present for any curve (see Duursma et
        # al., "Speeding up the discrete log computation on curves with
        # automorphisms"). Therefore, for a given order n, we estimate the
        # complexity in bits as:
        #
        # b = log2(sqrt(n / 32)) = (log2(n) / 2) - 2.5
        approx_curve_order_bits = curve.order_bits
        bits_security = (approx_curve_order_bits / 2) - 2.5

        # We then take into account anomalous binary curves (Koblitz curves) as
        # well and use the approximations of Wiener/Zuccherato ("Faster Attacks
        # on Elliptic Curve Cryptosystems")
        literature = LiteratureReference(
            author=["Michael J. Wiener", "Robert J. Zuccherato"],
            title="Faster Attacks on Elliptic Curve Cryptosystems",
            year=1999,
            source="Selected Areas in Cryptography 1998; LNCS 1556")
        if isinstance(curve, BinaryFieldEllipticCurve) and curve.is_koblitz:
            speedup = math.sqrt(2 * curve.m)
            bits_security -= math.log(speedup, 2)
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_KoblitzCurve,
                "Binary field Koblitz curves (anomalous binary curves) have more efficient attacks than their non-anomalous binary curves; in this case improving attack performance by a factor of ~%.1f."
                % (speedup),
                commonness=Commonness.UNUSUAL,
                literature=literature)

        if isinstance(curve, PrimeFieldEllipticCurve) and curve.is_koblitz:
            # The math here is a bit shady. Firstly, Koblitz curves over F_p
            # only mean there's an efficiently computable endomorphism (e.g.,
            # R. Gallant (1999); "Faster elliptic curve cryptography using
            # efficient endomorphisms"). We do not check for that, however, but
            # instead rely on dull "b = 0 and a is small" check.
            # Additionally, Wiener and Zuccherato describe curves of form
            # y^2 = x^3 - ax or y^2 = x^3 + b (which, for our a/b check, is not
            # the case) and, for the latter, describe a sqrt(6) speedup. We
            # just take that as is, knowing full well it's just guesswork.
            speedup = math.sqrt(6)
            bits_security -= math.log(speedup, 2)
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_KoblitzCurve,
                "Prime field Koblitz curves might have more efficient attacks than non-Koblitz curves. In this case, attack performance improves roughly by a factor of ~%.1f."
                % (speedup),
                commonness=Commonness.UNUSUAL,
                literature=literature)

        bits_security = math.floor(bits_security)
        judgements += self.algorithm("bits").analyze(
            JudgementCode.X509Cert_PublicKey_ECC_CurveOrderInBits,
            bits_security)

        # Check if the affine X/Y coordinates of the public key are about the
        # same length as the curve order. If randomly generated, both X and Y
        # should be about the same bitlength as the generator order and the
        # hamming weight should be roughly half of the bitlength of the curve
        # order.
        hweight_analysis = NumberTheory.hamming_weight_analysis(
            pubkey.x, min_bit_length=curve.field_bits)
        if not hweight_analysis.plausibly_random:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_PublicKeyPoint_X_BitBiasPresent,
                "Hamming weight of public key field element's X coordinate is %d at bitlength %d, but expected a weight between %d and %d when randomly chosen; this is likely not coincidential."
                % (hweight_analysis.hweight, hweight_analysis.bitlen,
                   hweight_analysis.rnd_min_hweight,
                   hweight_analysis.rnd_max_hweight),
                commonness=Commonness.HIGHLY_UNUSUAL)

        hweight_analysis = NumberTheory.hamming_weight_analysis(
            pubkey.y, min_bit_length=curve.field_bits)
        if not hweight_analysis.plausibly_random:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_ECC_PublicKeyPoint_Y_BitBiasPresent,
                "Hamming weight of public key field element's Y coordinate is %d at bitlength %d, but expected a weight between %d and %d when randomly chosen; this is likely not coincidential."
                % (hweight_analysis.hweight, hweight_analysis.bitlen,
                   hweight_analysis.rnd_min_hweight,
                   hweight_analysis.rnd_max_hweight),
                commonness=Commonness.HIGHLY_UNUSUAL)

        if isinstance(curve, BinaryFieldEllipticCurve):
            judgements += self._judge_binary_field_curve(curve)
        elif isinstance(curve, PrimeFieldEllipticCurve):
            judgements += self._judge_prime_field_curve(curve)

        return judgements
Esempio n. 25
0
    def _judge_purpose(self, certificate, purpose):
        judgements = SecurityJudgements()
        ku_ext = certificate.extensions.get_first(
            OIDDB.X509Extensions.inverse("KeyUsage"))
        eku_ext = certificate.extensions.get_first(
            OIDDB.X509Extensions.inverse("ExtendedKeyUsage"))
        ns_ext = certificate.extensions.get_first(
            OIDDB.X509Extensions.inverse("NetscapeCertificateType"))

        if certificate.is_ca_certificate:
            if purpose == AnalysisOptions.CertificatePurpose.TLSServerCertificate:
                judgements += SecurityJudgement(
                    JudgementCode.CertUsage_Purpose_ServerCert_IsCACert,
                    "Certificate is a valid CA certificate even though it's supposed to be a TLS server.",
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    verdict=Verdict.NO_SECURITY)
            elif purpose == AnalysisOptions.CertificatePurpose.TLSClientCertificate:
                judgements += SecurityJudgement(
                    JudgementCode.CertUsage_Purpose_ClientCert_IsCACert,
                    "Certificate is a valid CA certificate even though it's supposed to be a TLS client.",
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    verdict=Verdict.NO_SECURITY)

        if ku_ext is not None:
            # ('digitalSignature', 0),	The digitalSignature bit is asserted when the subject public key is used for verifying digital signatures, other than signatures on certificates (bit 5) and CRLs (bit 6), such as those used in an entity authentication service, a data origin authentication service, and/or an integrity service.
            # ('nonRepudiation', 1),	The nonRepudiation bit is asserted when the subject public key is used to verify digital signatures, other than signatures on certificates (bit 5) and CRLs (bit 6), used to provide a non-repudiation service that protects against the signing entity falsely denying some action.
            # ('keyEncipherment', 2),	The keyEncipherment bit is asserted when the subject public key is used for enciphering private or secret keys, i.e., for key transport.
            # ('dataEncipherment', 3),	The dataEncipherment bit is asserted when the subject public key is used for directly enciphering raw user data without the use of an intermediate symmetric cipher.  Note that the use of this bit is extremely uncommon
            # ('keyAgreement', 4),		The keyAgreement bit is asserted when the subject public key is used for key agreement.
            # ('keyCertSign', 5),
            # ('cRLSign', 6),
            # ('encipherOnly', 7),		The meaning of the encipherOnly bit is undefined in the absence of the keyAgreement bit. When the encipherOnly bit is asserted and the keyAgreement bit is also set, the subject public key may be used only for enciphering data while performing key agreement.
            # ('decipherOnly', 8)		The meaning of the decipherOnly bit is undefined in the absence of the keyAgreement bit. When the decipherOnly bit is asserted and the keyAgreement bit is also set, the subject public key may be used only for deciphering data while performing key agreement.

            flag_checker = FlagChecker()
            if purpose == AnalysisOptions.CertificatePurpose.CACertificate:
                flag_checker.may_not_have("nonRepudiation", "keyEncipherment",
                                          "dataEncipherment", "keyAgreement",
                                          "encipherOnly", "decipherOnly")
                flag_checker.must_have("keyCertSign")
                flag_checker.may_have("digitalSignature", "cRLSign")
            elif purpose in [
                    AnalysisOptions.CertificatePurpose.TLSClientCertificate,
                    AnalysisOptions.CertificatePurpose.TLSServerCertificate
            ]:
                flag_checker.may_not_have("nonRepudiation", "dataEncipherment",
                                          "keyCertSign", "cRLSign",
                                          "encipherOnly", "decipherOnly")
                flag_checker.complex_check(
                    ["digitalSignature", "keyEncipherment", "keyAgreement"],
                    min_count=1)
            else:
                raise NotImplementedError(purpose)

            jcode = {
                "missing": {
                    AnalysisOptions.CertificatePurpose.CACertificate:
                    JudgementCode.CertUsage_Purpose_CACert_KU_MissingBits,
                    AnalysisOptions.CertificatePurpose.TLSClientCertificate:
                    JudgementCode.CertUsage_Purpose_ClientCert_KU_MissingBits,
                    AnalysisOptions.CertificatePurpose.TLSServerCertificate:
                    JudgementCode.CertUsage_Purpose_ServerCert_KU_MissingBits,
                }[purpose],
                "unusual": {
                    AnalysisOptions.CertificatePurpose.CACertificate:
                    JudgementCode.CertUsage_Purpose_CACert_KU_UnusualBits,
                    AnalysisOptions.CertificatePurpose.TLSClientCertificate:
                    JudgementCode.CertUsage_Purpose_ClientCert_KU_UnusualBits,
                    AnalysisOptions.CertificatePurpose.TLSServerCertificate:
                    JudgementCode.CertUsage_Purpose_ServerCert_KU_UnusualBits,
                }[purpose],
                "excess": {
                    AnalysisOptions.CertificatePurpose.CACertificate:
                    JudgementCode.CertUsage_Purpose_CACert_KU_ExcessBits,
                    AnalysisOptions.CertificatePurpose.TLSClientCertificate:
                    JudgementCode.CertUsage_Purpose_ClientCert_KU_ExcessBits,
                    AnalysisOptions.CertificatePurpose.TLSServerCertificate:
                    JudgementCode.CertUsage_Purpose_ServerCert_KU_ExcessBits,
                }[purpose],
            }

            for violation in flag_checker.check(ku_ext.all_flags):
                if violation.check_type == "missing":
                    judgements += SecurityJudgement(
                        jcode["missing"],
                        "Certificate with purpose %s should have at least key usage flags %s, but %s is missing."
                        %
                        (purpose.name, ", ".join(sorted(violation.reference)),
                         ", ".join(sorted(violation.flags))),
                        commonness=Commonness.HIGHLY_UNUSUAL)
                elif violation.check_type == "excess":
                    judgements += SecurityJudgement(
                        jcode["excess"],
                        "Certificate with purpose %s has excessive key usage bits set: %s"
                        % (purpose.name, ", ".join(sorted(violation.flags))),
                        commonness=Commonness.HIGHLY_UNUSUAL)
                elif violation.check_type == "unusual":
                    judgements += SecurityJudgement(
                        jcode["unusual"],
                        "For certificate with purpose %s it is uncommon to have KeyUsage %s."
                        % (purpose.name, ", ".join(sorted(violation.flags))),
                        commonness=Commonness.UNUSUAL)
                elif violation.check_type == "complex_too_few":
                    if len(violation.flags) == 0:
                        judgements += SecurityJudgement(
                            jcode["missing"],
                            "Certificate with purpose %s should have at least %d flag(s) out of %s, but none were present."
                            % (purpose.name, violation.reference_count,
                               ", ".join(sorted(violation.reference))),
                            commonness=Commonness.HIGHLY_UNUSUAL)
                    else:
                        judgements += SecurityJudgement(
                            jcode["missing"],
                            "Certificate with purpose %s should have at least %d flag(s) out of %s, but only %s were present."
                            % (purpose.name, violation.reference_count,
                               ", ".join(sorted(violation.reference)),
                               ", ".join(sorted(violation.flags))),
                            commonness=Commonness.HIGHLY_UNUSUAL)
                else:
                    raise Exception(NotImplemented)

        if eku_ext is not None:
            if (purpose
                    == AnalysisOptions.CertificatePurpose.TLSClientCertificate
                ) and (not eku_ext.client_auth):
                judgements += SecurityJudgement(
                    JudgementCode.CertUsage_Purpose_ClientCert_EKUMismatch,
                    "Certificate is supposed to be a client certificate and has an Extended Key Usage extension, but no clientAuth flag set within that extension.",
                    commonness=Commonness.UNUSUAL)

            if (purpose
                    == AnalysisOptions.CertificatePurpose.TLSServerCertificate
                ) and (not eku_ext.server_auth):
                judgements += SecurityJudgement(
                    JudgementCode.CertUsage_Purpose_ServerCert_EKUMismatch,
                    "Certificate is supposed to be a server certificate and has an Extended Key Usage extension, but no serverAuth flag set within that extension.",
                    commonness=Commonness.UNUSUAL)

        if ns_ext is not None:
            if (purpose
                    == AnalysisOptions.CertificatePurpose.TLSClientCertificate
                ) and (not ns_ext.ssl_client):
                judgements += SecurityJudgement(
                    JudgementCode.
                    CertUsage_Purpose_ClientCert_NSCT_NoSSLClient,
                    "Certificate is supposed to be a client certificate and has an Netscape Certificate Type extension, but no sslClient flag set within that extension.",
                    commonness=Commonness.UNUSUAL)

            if (purpose
                    == AnalysisOptions.CertificatePurpose.TLSServerCertificate
                ) and (not ns_ext.ssl_server):
                judgements += SecurityJudgement(
                    JudgementCode.
                    CertUsage_Purpose_ServerCert_NSCT_NoSSLServer,
                    "Certificate is supposed to be a server certificate and has an Netscape Certificate Type extension, but no sslServer flag set within that extension.",
                    commonness=Commonness.UNUSUAL)

            if (purpose == AnalysisOptions.CertificatePurpose.CACertificate):
                if not any(flag in ns_ext.flags
                           for flag in ["sslCA", "emailCA", "objCA"]):
                    # No CA bit is set
                    judgements += SecurityJudgement(
                        JudgementCode.CertUsage_Purpose_CACert_NSCT_NoCA,
                        "Certificate is supposed to be a CA certificate and has an Netscape Certificate Type extension, but neither sslCA/emailCA/objCA flag set within that extension.",
                        commonness=Commonness.UNUSUAL)
                else:
                    # At least it's some type of CA. But is it a SSL CA?
                    if "sslCA" not in ns_ext.flags:
                        judgements += SecurityJudgement(
                            JudgementCode.
                            CertUsage_Purpose_CACert_NSCT_NoSSLCA,
                            "Certificate is supposed to be a CA certificate and has an Netscape Certificate Type extension, but it is not marked as an SSL CA. It can only be used for S/MIME or object signing.",
                            commonness=Commonness.UNUSUAL)

        if purpose == AnalysisOptions.CertificatePurpose.CACertificate:
            if not certificate.is_ca_certificate:
                judgements += SecurityJudgement(
                    JudgementCode.CertUsage_Purpose_CACert_NoCACert,
                    "Certificate is not a valid CA certificate even though it's supposed to be.",
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    verdict=Verdict.NO_SECURITY)

        return judgements
Esempio n. 26
0
    def _analyze_certificate_general_issues(self, certificate):
        judgements = SecurityJudgements()
        if certificate.version != 3:
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Body_Version_Not3,
                "Certificate version is v%d, usually would expect a v3 certificate."
                % (certificate.version),
                commonness=Commonness.HIGHLY_UNUSUAL)

        if certificate.serial < 0:
            standard = RFCReference(
                rfcno=5280,
                sect="4.1.2.2",
                verb="MUST",
                text=
                "The serial number MUST be a positive integer assigned by the CA to each certificate."
            )
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Body_SerialNumber_BasicChecks_Negative,
                "Certificate serial number is a negative value.",
                compatibility=Compatibility.STANDARDS_DEVIATION,
                standard=standard)
        elif certificate.serial == 0:
            standard = RFCReference(
                rfcno=5280,
                sect="4.1.2.2",
                verb="MUST",
                text=
                "The serial number MUST be a positive integer assigned by the CA to each certificate."
            )
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Body_SerialNumber_BasicChecks_Zero,
                "Certificate serial number is zero.",
                compatibility=Compatibility.STANDARDS_DEVIATION,
                standard=standard)
        elif certificate.serial >= (2**(8 * 20)):
            standard = RFCReference(
                rfcno=5280,
                sect="4.1.2.2",
                verb="MUST",
                text=
                "Conforming CAs MUST NOT use serialNumber values longer than 20 octets."
            )
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Body_SerialNumber_BasicChecks_Large,
                "Certificate serial number is too large.",
                compatibility=Compatibility.STANDARDS_DEVIATION,
                standard=standard)

        if "non_der" in certificate.asn1_details.flags:
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Malformed_NonDEREncoding,
                "Certificate uses invalid DER encoding. Original certificate is %d bytes; DER would be %d bytes."
                % (len(certificate.asn1_details.original_der),
                   len(certificate.asn1_details.encoded_der)),
                compatibility=Compatibility.STANDARDS_DEVIATION)

        if "trailing_data" in certificate.asn1_details.flags:
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_TrailingData,
                "Certificate contains %d bytes of trailing data." %
                (len(certificate.asn1_details.tail)),
                compatibility=Compatibility.STANDARDS_DEVIATION)

        standard = RFCReference(
            rfcno=5280,
            sect=["4.1.1.2", "4.1.2.3"],
            verb="MUST",
            text=
            "This field MUST contain the same algorithm identifier as the signature field in the sequence tbsCertificate (Section 4.1.2.3)."
        )
        oid_header = OID.from_asn1(
            certificate.asn1["tbsCertificate"]["signature"]["algorithm"])
        oid_sig = OID.from_asn1(
            certificate.asn1["signatureAlgorithm"]["algorithm"])
        if oid_header != oid_sig:
            name_header = OIDDB.SignatureAlgorithms.get(
                oid_header, str(oid_header))
            name_sig = OIDDB.SignatureAlgorithms.get(oid_sig, str(oid_sig))
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Signature_Function_BodyMismatch,
                "Certificate indicates signature algorithm %s in header section and %s in signature section."
                % (name_header, name_sig),
                compatibility=Compatibility.STANDARDS_DEVIATION,
                standard=standard)
        else:
            # OIDs might be same, but parameters could differ (e.g., for RSA-PSS)
            header_hasvalue = certificate.asn1["tbsCertificate"]["signature"][
                "parameters"].hasValue()
            signature_hasvalue = certificate.asn1["signatureAlgorithm"][
                "parameters"].hasValue()
            if header_hasvalue and signature_hasvalue:
                name_header = OIDDB.SignatureAlgorithms.get(
                    oid_header, str(oid_header))
                parameters_header = bytes(certificate.asn1["tbsCertificate"]
                                          ["signature"]["parameters"])
                parameters_signature = bytes(
                    certificate.asn1["signatureAlgorithm"]["parameters"])
                if parameters_header != parameters_signature:
                    judgements += SecurityJudgement(
                        JudgementCode.X509Cert_Signature_Function_BodyMismatch,
                        "Certificate indicates same signature algorithm in both header section and signature section (%s), but parameterization of each differ."
                        % (name_header),
                        compatibility=Compatibility.STANDARDS_DEVIATION,
                        standard=standard)
            elif header_hasvalue != signature_hasvalue:
                judgements += SecurityJudgement(
                    JudgementCode.X509Cert_Signature_Function_BodyMismatch,
                    "Certificate indicates same signature algorithm in both header section and signature section, but header %s while signature section %s."
                    % ("has parameters"
                       if header_hasvalue else "has no parameters",
                       "does" if signature_hasvalue else "does not"),
                    compatibility=Compatibility.STANDARDS_DEVIATION,
                    standard=standard)

        return judgements
Esempio n. 27
0
    def analyze(self, certificate, ca_certificate):
        judgements = SecurityJudgements()

        if certificate.issuer != ca_certificate.subject:
            standard = RFCReference(
                rfcno=5280,
                sect="4.1.2.4",
                verb="MUST",
                text=
                "The issuer field identifies the entity that has signed and issued the certificate."
            )
            judgements += SecurityJudgement(
                JudgementCode.CertUsage_CARelationship_Subject_Issuer_Mismatch,
                "Certificate issuer does not match CA subject.",
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.STANDARDS_DEVIATION,
                standard=standard)
        else:
            judgements += SecurityJudgement(
                JudgementCode.CertUsage_CARelationship_Subject_Issuer_Match,
                "Certificate issuer matches CA subject.",
                commonness=Commonness.COMMON)

        aki = certificate.extensions.get_first(
            OIDDB.X509Extensions.inverse("AuthorityKeyIdentifier"))
        ski = ca_certificate.extensions.get_first(
            OIDDB.X509Extensions.inverse("SubjectKeyIdentifier"))

        if aki is not None:
            if aki.keyid is not None:
                if ski is None:
                    standard = RFCReference(
                        rfcno=5280,
                        sect="4.2.1.2",
                        verb="MUST",
                        text=
                        "In conforming CA certificates, the value of the subject key identifier MUST be the value placed in the key identifier field of the authority key identifier extension (Section 4.2.1.1) of certificates issued by the subject of this certificate."
                    )
                    judgements += SecurityJudgement(
                        JudgementCode.
                        CertUsage_CARelationship_AKI_KeyID_Uncheckable,
                        "Key ID specified in the Authority Key Identifier extension of the certificate cannot be validated because CA does not have a Subject Key Identifier extension.",
                        commonness=Commonness.HIGHLY_UNUSUAL)
                else:
                    if ski.keyid != aki.keyid:
                        standard = RFCReference(
                            rfcno=5280,
                            sect="4.2.1.2",
                            verb="MUST",
                            text=
                            "In conforming CA certificates, the value of the subject key identifier MUST be the value placed in the key identifier field of the authority key identifier extension (Section 4.2.1.1) of certificates issued by the subject of this certificate."
                        )
                        judgements += SecurityJudgement(
                            JudgementCode.
                            CertUsage_CARelationship_AKI_KeyID_Mismatch,
                            "Key ID specified in the Authority Key Identifier extension of the certificate does not match the Key ID of the CA Subject Key Identifier extension.",
                            commonness=Commonness.HIGHLY_UNUSUAL)
                    else:
                        judgements += SecurityJudgement(
                            JudgementCode.
                            CertUsage_CARelationship_AKI_KeyID_Match,
                            "Key ID specified in the Authority Key Identifier extension matches that of the CA Subject Key Identifier extension.",
                            commonness=Commonness.COMMON)

            if aki.ca_names is not None:
                dir_name_count = 0
                for ca_name in aki.ca_names:
                    if ca_name.name == "directoryName":
                        dir_name_count += 1
                        if ca_name.directory_name == ca_certificate.subject:
                            judgements += SecurityJudgement(
                                JudgementCode.
                                CertUsage_CARelationship_AKI_CAName_Match,
                                "Directory name specified in the Authority Key Identifier extension of the certificate matches the CA subject.",
                                commonness=Commonness.COMMON)
                            break
                else:
                    names = "None of the %d CA names" % (len(
                        aki.ca_names)) if (
                            len(aki.ca_names) != 1) else "The CA name"
                    judgements += SecurityJudgement(
                        JudgementCode.
                        CertUsage_CARelationship_AKI_CAName_Mismatch,
                        "%s (%s) specified in the Authority Key Identifier extension of the certificate matches the CA subject."
                        %
                        (names, TextTools.sp(dir_name_count, "directoryName")),
                        commonness=Commonness.HIGHLY_UNUSUAL,
                        compatibility=Compatibility.STANDARDS_DEVIATION)

            if aki.serial is not None:
                if aki.serial != ca_certificate.serial:
                    judgements += SecurityJudgement(
                        JudgementCode.
                        CertUsage_CARelationship_AKI_Serial_Mismatch,
                        "Serial number specified in the Authority Key Identifier extension of the certificate does not match the serial number of the CA certificate.",
                        commonness=Commonness.HIGHLY_UNUSUAL)
                else:
                    judgements += SecurityJudgement(
                        JudgementCode.
                        CertUsage_CARelationship_AKI_Serial_Match,
                        "Serial number specified in the Authority Key Identifier extension matches that of the CA certificate.",
                        commonness=Commonness.COMMON)

        judgements += self._judge_validity_timestamps(certificate,
                                                      ca_certificate)
        judgements += self._judge_signature(certificate, ca_certificate)

        return {
            "ca_subject":
            self.algorithm("dn").analyze(ca_certificate.subject,
                                         self._DN_VALIDATOR_CA_SUBJECT),
            "ca_issuer":
            self.algorithm("dn").analyze(ca_certificate.issuer,
                                         self._DN_VALIDATOR_CA_ISSUER),
            "security":
            judgements,
        }
Esempio n. 28
0
    def analyze(self,
                signature_alg_oid,
                signature_alg_params,
                signature,
                root_cert=None):
        judgements = SecurityJudgements()
        signature_alg = SignatureAlgorithms.lookup("oid", signature_alg_oid)
        if signature_alg is None:
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_Signature_Function_Unknown,
                "Certificate has unknown signature algorithm with OID %s. Cannot make security determination."
                % (signature_alg_oid),
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.LIMITED_SUPPORT)
            result = {
                "name": str(signature_alg_oid),
                "pretty": str(signature_alg_oid),
                "security": judgements,
            }
            return result

        if isinstance(signature_alg.value.oid, (tuple, list)):
            # Have more than one OID for this
            if signature_alg.value.oid[0] != signature_alg_oid:
                judgements += SecurityJudgement(
                    JudgementCode.X509Cert_Signature_Function_DeprecatedOID,
                    "Signature algorithm uses alternate OID %s for algorithm %s. Preferred OID would be %s."
                    % (signature_alg_oid, signature_alg.name,
                       signature_alg.value.oid[0]),
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    compatibility=Compatibility.LIMITED_SUPPORT)

        if signature_alg.value.hash_fnc is not None:
            # Signature algorithm already implies a concrete hash function, already done.
            hash_fnc = signature_alg.value.hash_fnc
        else:
            # Signature algorithms depends and is not implied.
            (hash_fnc, new_judgements) = self._determine_hash_function(
                signature_alg, signature_alg_params)
            judgements += new_judgements

        if signature_alg.value.sig_fnc == SignatureFunctions.ecdsa:
            # Decode ECDSA signature
            asn1_details = ASN1Tools.safe_decode(
                signature, asn1_spec=rfc3279.ECDSA_Sig_Value())
            judgements += self._DER_VALIDATOR_ECDSA_SIGNATURE.validate(
                asn1_details)
            if (asn1_details.asn1 is not None) and (root_cert is not None):
                if root_cert.pubkey.pk_alg.value.cryptosystem == Cryptosystems.ECC_ECDSA:
                    # Check that this is really a potential parent CA certificate
                    ca_curve = root_cert.pubkey.curve
                    hweight_analysis = NumberTheory.hamming_weight_analysis(
                        int(asn1_details.asn1["r"]),
                        min_bit_length=ca_curve.field_bits)
                    if not hweight_analysis.plausibly_random:
                        judgements += SecurityJudgement(
                            JudgementCode.
                            X509Cert_Signature_ECDSA_R_BitBiasPresent,
                            "Hamming weight of ECDSA signature R parameter is %d at bitlength %d, but expected a weight between %d and %d when randomly chosen; this is likely not coincidential."
                            %
                            (hweight_analysis.hweight, hweight_analysis.bitlen,
                             hweight_analysis.rnd_min_hweight,
                             hweight_analysis.rnd_max_hweight),
                            commonness=Commonness.HIGHLY_UNUSUAL)
                    hweight_analysis = NumberTheory.hamming_weight_analysis(
                        int(asn1_details.asn1["s"]),
                        min_bit_length=ca_curve.field_bits)
                    if not hweight_analysis.plausibly_random:
                        judgements += SecurityJudgement(
                            JudgementCode.
                            X509Cert_Signature_ECDSA_S_BitBiasPresent,
                            "Hamming weight of ECDSA signature S parameter is %d at bitlength %d, but expected a weight between %d and %d when randomly chosen; this is likely not coincidential."
                            %
                            (hweight_analysis.hweight, hweight_analysis.bitlen,
                             hweight_analysis.rnd_min_hweight,
                             hweight_analysis.rnd_max_hweight),
                            commonness=Commonness.HIGHLY_UNUSUAL)

        elif signature_alg.value.sig_fnc == SignatureFunctions.dsa:
            # Decode DSA signature
            asn1_details = ASN1Tools.safe_decode(
                signature, asn1_spec=rfc3279.Dss_Sig_Value())
            judgements += self._DER_VALIDATOR_DSA_SIGNATURE.validate(
                asn1_details)
            if (asn1_details.asn1 is not None) and (root_cert is not None):
                if root_cert is not None:
                    if root_cert.pubkey.pk_alg.value.cryptosystem == Cryptosystems.DSA:
                        field_width = root_cert.pubkey.q.bit_length()

                        hweight_analysis = NumberTheory.hamming_weight_analysis(
                            int(asn1_details.asn1["r"]),
                            min_bit_length=field_width)
                        if not hweight_analysis.plausibly_random:
                            judgements += SecurityJudgement(
                                JudgementCode.
                                X509Cert_Signature_DSA_R_BitBiasPresent,
                                "Hamming weight of DSA signature R parameter is %d at bitlength %d, but expected a weight between %d and %d when randomly chosen; this is likely not coincidential."
                                % (hweight_analysis.hweight,
                                   hweight_analysis.bitlen,
                                   hweight_analysis.rnd_min_hweight,
                                   hweight_analysis.rnd_max_hweight),
                                commonness=Commonness.HIGHLY_UNUSUAL)

                        hweight_analysis = NumberTheory.hamming_weight_analysis(
                            int(asn1_details.asn1["s"]),
                            min_bit_length=field_width)
                        if not hweight_analysis.plausibly_random:
                            judgements += SecurityJudgement(
                                JudgementCode.
                                X509Cert_Signature_DSA_S_BitBiasPresent,
                                "Hamming weight of DSA signature S parameter is %d at bitlength %d, but expected a weight between %d and %d when randomly chosen; this is likely not coincidential."
                                % (hweight_analysis.hweight,
                                   hweight_analysis.bitlen,
                                   hweight_analysis.rnd_min_hweight,
                                   hweight_analysis.rnd_max_hweight),
                                commonness=Commonness.HIGHLY_UNUSUAL)

        result = {
            "name":
            signature_alg.name,
            "sig_fnc":
            self.algorithm("sig_fnc").analyze(signature_alg.value.sig_fnc),
            "security":
            judgements,
        }
        if hash_fnc is not None:
            result.update({
                "pretty":
                signature_alg.value.sig_fnc.value.pretty_name + " with " +
                hash_fnc.value.pretty_name,
                "hash_fnc":
                self.algorithm("hash_fnc").analyze(hash_fnc),
            })
        else:
            result.update({
                "pretty":
                "%s with undetermined hash function" %
                (signature_alg.value.sig_fnc.value.pretty_name)
            })
        return result
Esempio n. 29
0
    def _analyze_rsa_pss_signature_params(self, signature_alg_params):
        judgements = SecurityJudgements()
        asn1_details = ASN1Tools.safe_decode(
            signature_alg_params, asn1_spec=ASN1Models.RSASSA_PSS_Params())
        judgements += self._DER_VALIDATOR_RSAPSS_PARAMETERS.validate(
            asn1_details)
        if asn1_details.asn1 is not None:
            rsapss = RSAPSSParameters.from_asn1(asn1_details.asn1)
            if rsapss.hash_algorithm is None:
                judgements += SecurityJudgement(
                    JudgementCode.X509Cert_Signature_HashFunction_Unknown,
                    "Certificate has unknown hash function for use in RSA-PSS, OID %s. Cannot make security determination for that part."
                    % (rsapss.hash_algorithm_oid),
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    compatibility=Compatibility.LIMITED_SUPPORT)

            if rsapss.mask_algorithm is None:
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_RSA_RSA_PSS_UnknownMaskFunction,
                    "Certificate has unknown mask function for use in RSA-PSS, OID %s."
                    % (rsapss.mask_algorithm_oid),
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    compatibility=Compatibility.LIMITED_SUPPORT)

            if rsapss.mask_hash_algorithm is None:
                judgements += SecurityJudgement(
                    JudgementCode.X509Cert_Signature_HashFunction_Unknown,
                    "Certificate has unknown mask hash function for use in RSA-PSS, OID %s."
                    % (rsapss.mask_hash_algorithm_oid),
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    compatibility=Compatibility.LIMITED_SUPPORT)

            if rsapss.trailer_field_value is None:
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_RSA_RSA_PSS_UnknownTrailerField,
                    "Certificate has unknown trailer field for use in RSA-PSS, trailer field ID %d."
                    % (rsapss.trailer_field),
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    compatibility=Compatibility.LIMITED_SUPPORT)

            if (rsapss.hash_algorithm is not None) and (
                    rsapss.mask_hash_algorithm is not None) and (
                        rsapss.hash_algorithm != rsapss.mask_hash_algorithm):
                standard = RFCReference(
                    rfcno=3447,
                    sect="8.1",
                    verb="RECOMMEND",
                    text=
                    "Therefore, it is recommended that the EMSA-PSS mask generation function be based on the same hash function."
                )
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_RSA_RSA_PSS_MultipleHashFunctions,
                    "RSA-PSS uses hash function %s for hashing, but %s for masking. This is discouraged."
                    % (rsapss.hash_algorithm.name,
                       rsapss.mask_hash_algorithm.name),
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    compatibility=Compatibility.LIMITED_SUPPORT,
                    standard=standard)

            if rsapss.salt_length < 0:
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_RSA_RSAPSS_InvalidSaltLength,
                    "Certificate has negative salt length for use in RSA-PSS, %d bytes specified."
                    % (rsapss.salt_length),
                    commonness=Commonness.HIGHLY_UNUSUAL,
                    compatibility=Compatibility.STANDARDS_DEVIATION,
                    bits=0)
            elif rsapss.salt_length == 0:
                judgements += SecurityJudgement(
                    JudgementCode.X509Cert_PublicKey_RSA_RSA_PSS_NoSaltUsed,
                    "RSA-PSS does not use any salt.",
                    commonness=Commonness.HIGHLY_UNUSUAL)
            elif rsapss.salt_length < 16:
                judgements += SecurityJudgement(
                    JudgementCode.X509Cert_PublicKey_RSA_RSA_PSS_ShortSaltUsed,
                    "RSA-PSS uses a comparatively short salt value of %d bits."
                    % (rsapss.salt_length * 8),
                    commonness=Commonness.UNUSUAL)
            else:
                judgements += SecurityJudgement(
                    JudgementCode.
                    X509Cert_PublicKey_RSA_RSA_PSS_SaltLengthInBytes,
                    "RSA-PSS uses a salt of %d bits." %
                    (rsapss.salt_length * 8))
            return (rsapss.hash_algorithm, judgements)
        else:
            return (None, judgements)
Esempio n. 30
0
    def analyze(self, pubkey):
        judgements = SecurityJudgements()

        L = pubkey.p.bit_length()
        N = pubkey.q.bit_length()

        if not NumberTheory.is_probable_prime(pubkey.p):
            standard = LiteratureReference(
                quote="p: a prime modulus",
                sect="4.1",
                author="National Institute of Standards and Technology",
                title="FIPS PUB 186-4: Digital Signature Standard (DSS)",
                year=2013,
                month=7,
                doi="10.6028/NIST.FIPS.186-4")
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_DSA_Parameters_P_NotPrime,
                "DSA parameter p is not prime.",
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.STANDARDS_DEVIATION,
                bits=0,
                standard=standard)

        if not NumberTheory.is_probable_prime(pubkey.q):
            standard = LiteratureReference(
                quote="q: a prime divisor of (p - 1)",
                sect="4.1",
                author="National Institute of Standards and Technology",
                title="FIPS PUB 186-4: Digital Signature Standard (DSS)",
                year=2013,
                month=7,
                doi="10.6028/NIST.FIPS.186-4")
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_DSA_Parameters_Q_NotPrime,
                "DSA parameter q is not prime.",
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.STANDARDS_DEVIATION,
                bits=0,
                standard=standard)

        if ((pubkey.p - 1) % pubkey.q) != 0:
            standard = LiteratureReference(
                quote="q: a prime divisor of (p - 1)",
                sect="4.1",
                author="National Institute of Standards and Technology",
                title="FIPS PUB 186-4: Digital Signature Standard (DSS)",
                year=2013,
                month=7,
                doi="10.6028/NIST.FIPS.186-4")
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_DSA_Parameters_Q_NoDivisorOfP1,
                "DSA parameter q is not a divisor of (p - 1).",
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.STANDARDS_DEVIATION,
                bits=0,
                standard=standard)

        if pow(pubkey.g, pubkey.q, pubkey.p) != 1:
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_DSA_Parameters_G_Invalid,
                "DSA parameter g is not valid. In particular, g^q mod p != 1.",
                commonness=Commonness.HIGHLY_UNUSUAL,
                bits=0)

        if (pubkey.g <= 1) or (pubkey.g >= pubkey.p):
            standard = LiteratureReference(
                quote=
                "g: a generator of a subgroup of order q in the multiplicative group of GF(p), such that 1 < g < p",
                sect="4.1",
                author="National Institute of Standards and Technology",
                title="FIPS PUB 186-4: Digital Signature Standard (DSS)",
                year=2013,
                month=7,
                doi="10.6028/NIST.FIPS.186-4")
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_DSA_Parameters_G_InvalidRange,
                "DSA parameter g is not inside the valid range (1 < g < p).",
                commonness=Commonness.HIGHLY_UNUSUAL,
                compatibility=Compatibility.STANDARDS_DEVIATION,
                bits=0,
                standard=standard)

        hweight_analysis = NumberTheory.hamming_weight_analysis(pubkey.p)
        if not hweight_analysis.plausibly_random:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_DSA_Parameters_P_BitBiasPresent,
                "Hamming weight of DSA prime p is %d at bitlength %d, but expected a weight between %d and %d when randomly chosen; this is likely not coincidential."
                % (hweight_analysis.hweight, hweight_analysis.bitlen,
                   hweight_analysis.rnd_min_hweight,
                   hweight_analysis.rnd_max_hweight),
                commonness=Commonness.HIGHLY_UNUSUAL)

        hweight_analysis = NumberTheory.hamming_weight_analysis(pubkey.q)
        if not hweight_analysis.plausibly_random:
            judgements += SecurityJudgement(
                JudgementCode.
                X509Cert_PublicKey_DSA_Parameters_Q_BitBiasPresent,
                "Hamming weight of DSA prime q is %d at bitlength %d, but expected a weight between %d and %d when randomly chosen; this is likely not coincidential."
                % (hweight_analysis.hweight, hweight_analysis.bitlen,
                   hweight_analysis.rnd_min_hweight,
                   hweight_analysis.rnd_max_hweight),
                commonness=Commonness.HIGHLY_UNUSUAL)

        if (L in self._TYPICAL_L_N_VALUES) and (
                N in self._TYPICAL_L_N_VALUES[L]):
            # Typical
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_DSA_L_N_Common,
                "DSA parameter values L/N (%d/%d) are common." % (L, N),
                commonness=Commonness.COMMON)
        else:
            # Non-typical
            judgements += SecurityJudgement(
                JudgementCode.X509Cert_PublicKey_DSA_L_N_Uncommon,
                "DSA parameter values L/N (%d/%d) are uncommon." % (L, N),
                commonness=Commonness.UNUSUAL)

        L_strength_bits = NumberTheory.asymtotic_complexity_gnfs_bits(pubkey.p)
        N_strength_bits = math.floor(N / 2)
        bits_security = min(L_strength_bits, N_strength_bits)
        judgements += self.algorithm("bits").analyze(
            JudgementCode.X509Cert_PublicKey_DSA_L_N, bits_security)

        result = {
            "cryptosystem": "dsa",
            "specific": {
                "L": L,
                "N": N,
            },
            "security": judgements,
        }

        if self._analysis_options.include_raw_data:
            result["specific"]["p"] = {
                "value": pubkey.p,
            }
            result["specific"]["q"] = {
                "value": pubkey.q,
            }
            result["specific"]["g"] = {
                "value": pubkey.g,
            }
            result["specific"]["pubkey"] = {
                "value": pubkey.pubkey,
            }
        return result