def decode_qualifier(cls, oid, qualifier_data): decoded_qualifier = None if oid == OIDDB.X509ExtensionCertificatePolicyQualifierOIDs.inverse( "id-qt-cps"): decoded_qualifier = ASN1Tools.safe_decode( qualifier_data, asn1_spec=(rfc5280.CPSuri(), ASN1Models.RelaxedCPSuri())) elif oid == OIDDB.X509ExtensionCertificatePolicyQualifierOIDs.inverse( "id-qt-unotice"): decoded_qualifier = ASN1Tools.safe_decode( qualifier_data, asn1_spec=(rfc5280.UserNotice(), ASN1Models.RelaxedUserNotice())) return decoded_qualifier
def from_subject_pubkey_info(cls, pk_alg, params_asn1, pubkey_data): params = ASN1Tools.safe_decode(params_asn1, asn1_spec=ECParameters()) accessible_parameters = {} if params.asn1 is not None: accessible_parameters["curve_source"] = params.asn1.getName() if accessible_parameters["curve_source"] == "namedCurve": # Named curve curve_oid = OID.from_asn1(params.asn1.getComponent()) curve = CurveDB().instantiate(oid=curve_oid) accessible_parameters.update({ "curve_oid": curve_oid, "curve": curve, }) elif accessible_parameters["curve_source"] == "specifiedCurve": # Explicit curve or implicit curve curve = EllipticCurve.from_asn1(params.asn1.getComponent()) accessible_parameters.update({ "curve": curve, }) else: # Implicit curve pass if accessible_parameters.get("curve") is not None: pk_point = curve.decode_point(pubkey_data) accessible_parameters.update({ "x": pk_point.x, "y": pk_point.y, }) else: pk_point = None return cls(accessible_parameters=accessible_parameters, decoding_details=[params, pk_point])
def test_safedecode_ok_nomodel(self): der_data = bytes.fromhex("02 01 7b") # 123 asn1_details = ASN1Tools.safe_decode(der_data) self.assertEqual(asn1_details.flags, set()) self.assertEqual(asn1_details.tail, bytes()) self.assertEqual(asn1_details.asn1, Integer(123)) self.assertEqual(asn1_details.generic_asn1, None) self.assertEqual(asn1_details.encoded_der, None) self.assertEqual(asn1_details.original_der, der_data) self.assertEqual(asn1_details.model_index, 0)
def test_safedecode_undecodable(self): der_data = bytes.fromhex("aa bb cc") asn1_details = ASN1Tools.safe_decode(der_data) self.assertEqual(asn1_details.flags, set(["undecodable"])) self.assertEqual(asn1_details.tail, None) self.assertEqual(asn1_details.asn1, None) self.assertEqual(asn1_details.generic_asn1, None) self.assertEqual(asn1_details.encoded_der, None) self.assertEqual(asn1_details.original_der, der_data) self.assertEqual(asn1_details.model_index, None)
def __init__(self, oid, critical, data): assert (isinstance(oid, OID)) assert (isinstance(critical, bool)) assert (isinstance(data, bytes)) self._oid = oid self._critical = critical self._data = data self._detailed_asn1 = None spec = self._ASN1_MODEL() if (self._ASN1_MODEL is not None) else None self._detailed_asn1 = ASN1Tools.safe_decode(self.data, asn1_spec=spec) self._decode_hook()
def test_safedecode_ok_nonder_trailing_data(self): der_data = bytes.fromhex("02 02 007b aabb") # 123 non-DER asn1_details = ASN1Tools.safe_decode(der_data, asn1_spec=RestrictiveASN1Model()) self.assertEqual(asn1_details.flags, set(["non_der", "trailing_data"])) self.assertEqual(asn1_details.asn1, Integer(123)) self.assertEqual(asn1_details.tail, bytes.fromhex("aabb")) self.assertEqual(asn1_details.generic_asn1, None) self.assertEqual(asn1_details.encoded_der, bytes.fromhex("02 01 7b")) self.assertEqual(asn1_details.original_der, der_data) self.assertEqual(asn1_details.model_index, 0)
def test_safedecode_ok_trailing_data(self): der_data = bytes.fromhex("02 02 01c8 aabb") # 456 asn1_details = ASN1Tools.safe_decode(der_data, asn1_spec=BasicASN1Model()) self.assertEqual(asn1_details.flags, set(["trailing_data"])) self.assertEqual(asn1_details.asn1, Integer(456)) self.assertEqual(asn1_details.tail, bytes.fromhex("aa bb")) self.assertEqual(asn1_details.generic_asn1, None) self.assertEqual(asn1_details.encoded_der, None) self.assertEqual(asn1_details.original_der, der_data) self.assertEqual(asn1_details.model_index, 0)
def test_safedecode_fail_model_generic(self): der_data = bytes.fromhex("02 02 01c8") # 456 asn1_details = ASN1Tools.safe_decode(der_data, asn1_spec=RestrictiveASN1Model()) self.assertEqual(asn1_details.flags, set(["unexpected_type"])) self.assertEqual(asn1_details.tail, bytes()) self.assertEqual(asn1_details.asn1, None) self.assertEqual(asn1_details.generic_asn1, Integer(456)) self.assertEqual(asn1_details.encoded_der, None) self.assertEqual(asn1_details.original_der, der_data) self.assertEqual(asn1_details.model_index, None)
def _decode_hook(self): self._attributes = [] if self.asn1 is not None: for attribute in self.asn1: self._attributes.append( self._Attribute(attribute_type=OID.from_asn1( attribute["type"]), values=[ ASN1Tools.safe_decode(value) for value in attribute["values"] ]))
def test_safedecode_wrong_type(self): der_data = bytes.fromhex("04 06 666f6f626172") # "foobar" OctetString asn1_details = ASN1Tools.safe_decode(der_data, asn1_spec=RestrictiveASN1Model()) self.assertEqual(asn1_details.flags, set(["unexpected_type"])) self.assertEqual(asn1_details.asn1, None) self.assertEqual(asn1_details.tail, bytes()) self.assertEqual(asn1_details.generic_asn1, OctetString(b"foobar")) self.assertEqual(asn1_details.encoded_der, None) self.assertEqual(asn1_details.original_der, der_data) self.assertEqual(asn1_details.model_index, None)
def test_safedecode_non_encodable(self): der_data = bytes.fromhex( "17 11 31 36 30 32 31 37 31 30 32 37 30 30 2b 30 31 30 30" ) # UTCTime("160217102700+0100") asn1_details = ASN1Tools.safe_decode(der_data) self.assertEqual(asn1_details.flags, set(["non_encodable"])) self.assertEqual(asn1_details.asn1, UTCTime("160217102700+0100")) self.assertEqual(asn1_details.tail, bytes()) self.assertEqual(asn1_details.generic_asn1, None) self.assertEqual(asn1_details.encoded_der, None) self.assertEqual(asn1_details.original_der, der_data) self.assertEqual(asn1_details.model_index, 0)
def test_safedecode_ok_model_fallback(self): der_data = bytes.fromhex("02 02 01c8") # 456 asn1_details = ASN1Tools.safe_decode(der_data, asn1_spec=(RestrictiveASN1Model(), BasicASN1Model())) self.assertEqual(asn1_details.flags, set(["fallback"])) self.assertEqual(asn1_details.tail, bytes()) self.assertEqual(asn1_details.asn1, Integer(456)) self.assertEqual(asn1_details.generic_asn1, None) self.assertEqual(asn1_details.encoded_der, None) self.assertEqual(asn1_details.original_der, der_data) self.assertEqual(asn1_details.model_index, 1)
def from_subject_pubkey_info(cls, pk_alg, params_asn1, pubkey_data): params = ASN1Tools.safe_decode(params_asn1, asn1_spec=rfc3279.Dss_Parms()) pubkey = ASN1Tools.safe_decode(pubkey_data, asn1_spec=rfc3279.DSAPublicKey()) accessible_parameters = {} if params.asn1 is not None: accessible_parameters.update({ "p": int(params.asn1["p"]), "q": int(params.asn1["q"]), "g": int(params.asn1["g"]), }) if pubkey.asn1 is not None: accessible_parameters.update({ "pubkey": int(pubkey.asn1), }) return cls(accessible_parameters=accessible_parameters, decoding_details=[params, pubkey])
def __init__(self, der_data, source=None): assert (isinstance(der_data, bytes)) self._der_data = der_data self._asn1_details = ASN1Tools.safe_decode( der_data, asn1_spec=self._ASN1_MODEL()) if self._asn1_details.asn1 is None: raise UnexpectedFileContentException( "Could not decode ASN.1 blob of length %d as %s." % (len(der_data), self.__class__.__name__)) self._hashval = hashlib.sha256(self._der_data).digest() self._source = source self._post_decode_hook()
def from_subject_pubkey_info(cls, pk_alg, params_asn1, pubkey_data): pubkey = ASN1Tools.safe_decode(pubkey_data, asn1_spec=rfc2437.RSAPublicKey()) if pubkey.asn1 is not None: accessible_parameters = { "n": int(pubkey.asn1["modulus"]), "e": int(pubkey.asn1["publicExponent"]), "params": params_asn1 if params_asn1.hasValue() else None, } else: accessible_parameters = None return cls(accessible_parameters=accessible_parameters, decoding_details=pubkey)
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)
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