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_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 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 __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 create(cls, parameters): asn1 = rfc2459.SubjectPublicKeyInfo() asn1["algorithm"] = rfc2459.AlgorithmIdentifier() asn1["algorithm"][ "algorithm"] = OIDDB.KeySpecificationAlgorithms.inverse( "id-dsa").to_asn1() asn1["algorithm"]["algorithm"] = parameters["curve"].oid.to_asn1() inner_key = parameters["curve"].point(parameters["x"], parameters["y"]).encode() asn1["subjectPublicKey"] = ASN1Tools.bytes2bitstring(inner_key) return asn1
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 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 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 __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 _decode_hook(self): if self.asn1 is None: self._distribution_points = None return self._distribution_points = [] for asn1_point in self.asn1: point_name_rdn_malformed = False if asn1_point["distributionPoint"].hasValue(): point_name_asn1 = asn1_point["distributionPoint"].getComponent( ) if asn1_point["distributionPoint"]["fullName"].hasValue(): # GeneralNames point_name = ASN1GeneralNamesWrapper.from_asn1( point_name_asn1) else: # RelativeDistinguishedName try: point_name = RelativeDistinguishedName.from_asn1( point_name_asn1) except InvalidInputException: point_name = None point_name_rdn_malformed = True else: point_name = None if asn1_point["reasons"].hasValue(): reasons = set() for (bitno, value) in enumerate(asn1_point["reasons"]): if value == 1: value = self._KNOWN_REASON_BITS.get(bitno, bitno) reasons.add(value) reasons_trailing_zero = ASN1Tools.bitstring_has_trailing_zeros( asn1_point["reasons"]) else: reasons = None reasons_trailing_zero = False if asn1_point["cRLIssuer"].hasValue(): crl_issuer = ASN1GeneralNamesWrapper.from_asn1( asn1_point["cRLIssuer"]) else: crl_issuer = None cdp = self._DistributionPoint( point_name=point_name, point_name_rdn_malformed=point_name_rdn_malformed, reasons=reasons, reasons_trailing_zero=reasons_trailing_zero, crl_issuer=crl_issuer) self._distribution_points.append(cdp)
def test_bitstring_empty(self): self.assertTrue(ASN1Tools.bitstring_is_empty(BitString([]))) self.assertTrue(ASN1Tools.bitstring_is_empty(BitString([0]))) self.assertTrue(ASN1Tools.bitstring_is_empty(BitString([0, 0, 0]))) self.assertFalse(ASN1Tools.bitstring_is_empty(BitString([1]))) self.assertFalse(ASN1Tools.bitstring_is_empty(BitString([0, 1]))) self.assertFalse(ASN1Tools.bitstring_is_empty(BitString([0, 0, 1, 0])))
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 _post_decode_hook(self): if self.asn1["parameters"] is None: raise InvalidInputException( "ECC private key does not contain curve OID. Cannot proceed.") if self.asn1["publicKey"] is None: raise InvalidInputException( "ECC private key does not contain public key. Cannot proceed.") curve_oid = OID.from_asn1(self.asn1["parameters"]) self._curve = CurveDB().instantiate(oid=curve_oid) self._d = int.from_bytes(self.asn1["privateKey"], byteorder="big") (self._x, self._y) = ECCTools.decode_enc_pubkey( ASN1Tools.bitstring2bytes(self.asn1["publicKey"]))
def _forge_cert(self, cert_issuer_id, issuer, cert_subject_id, subject): self._log.debug("Forging chain element %d -> %d: %s -> %s", cert_issuer_id, cert_subject_id, issuer, subject) issuer_key_filename = self._args.key_template % (cert_issuer_id) key_filename = self._args.key_template % (cert_subject_id) crt_filename = self._args.cert_template % (cert_subject_id) if (not os.path.isfile(key_filename)) or self._args.force: OpenSSLTools.create_private_key(PrivateKeyStorage(storage_form = PrivateKeyStorageForm.PEM_FILE, filename = key_filename), subject.pubkey.keyspec) # Read new private key and convert to public key with tempfile.NamedTemporaryFile(prefix = "pubkey_", suffix = ".pem") as pubkey_file: OpenSSLTools.private_to_public(key_filename, pubkey_file.name) subject_pubkey = PublicKey.read_pemfile(pubkey_file.name)[0] # Do the same for the issuer key to get the issuer key ID with tempfile.NamedTemporaryFile(prefix = "pubkey_", suffix = ".pem") as pubkey_file: OpenSSLTools.private_to_public(issuer_key_filename, pubkey_file.name) issuer_pubkey = PublicKey.read_pemfile(pubkey_file.name)[0] # Replace public key first forged_cert_asn1 = subject.asn1_clone forged_cert_asn1["tbsCertificate"]["subjectPublicKeyInfo"] = subject_pubkey.asn1 # Do identifiers need to be recalculated? if self._args.recalculate_keyids: if subject.extensions.has(OIDDB.X509Extensions.inverse("SubjectKeyIdentifier")): # Replace subject key identifier new_key_id = subject_pubkey.keyid() replacement_extension = X509SubjectKeyIdentifierExtension.construct(new_key_id) subject.extensions.filter(OIDDB.X509Extensions.inverse("SubjectKeyIdentifier"), replacement_extension) if subject.extensions.has(OIDDB.X509Extensions.inverse("AuthorityKeyIdentifier")): # Replace authority key identifier new_key_id = issuer_pubkey.keyid() replacement_extension = X509AuthorityKeyIdentifierExtension.construct(new_key_id) subject.extensions.filter(OIDDB.X509Extensions.inverse("AuthorityKeyIdentifier"), replacement_extension) forged_cert_asn1["tbsCertificate"]["extensions"] = subject.extensions.to_asn1() # Re-serialize certificate forged_cert = X509Certificate.from_asn1(forged_cert_asn1) # Then sign the modified certifiate signature = OpenSSLTools.sign_data(subject.signature_algorithm, issuer_key_filename, forged_cert.signed_payload) # Finally, place the signature into the certificate forged_cert_asn1["signatureValue"] = ASN1Tools.bytes2bitstring(signature) forged_cert = X509Certificate.from_asn1(forged_cert_asn1) forged_cert.write_pemfile(crt_filename)
def create(cls, parameters): asn1 = rfc2459.SubjectPublicKeyInfo() asn1["algorithm"] = rfc2459.AlgorithmIdentifier() asn1["algorithm"][ "algorithm"] = OIDDB.KeySpecificationAlgorithms.inverse( "rsaEncryption").to_asn1() asn1["algorithm"]["parameters"] = pyasn1.type.univ.Any( value=pyasn1.codec.der.encoder.encode(pyasn1.type.univ.Null())) inner_key = rfc2437.RSAPublicKey() inner_key["modulus"] = pyasn1.type.univ.Integer(parameters["n"]) inner_key["publicExponent"] = pyasn1.type.univ.Integer(parameters["e"]) inner_key = pyasn1.codec.der.encoder.encode(inner_key) asn1["subjectPublicKey"] = ASN1Tools.bytes2bitstring(inner_key) return asn1
def create(cls, parameters): asn1 = rfc2459.SubjectPublicKeyInfo() asn1["algorithm"] = rfc2459.AlgorithmIdentifier() asn1["algorithm"][ "algorithm"] = OIDDB.KeySpecificationAlgorithms.inverse( "id-dsa").to_asn1() asn1["algorithm"]["parameters"] = rfc3279.Dss_Parms() asn1["algorithm"]["parameters"]["p"] = parameters["p"] asn1["algorithm"]["parameters"]["q"] = parameters["q"] asn1["algorithm"]["parameters"]["g"] = parameters["g"] inner_key = rfc3279.DSAPublicKey(parameters["pubkey"]) inner_key = pyasn1.codec.der.encoder.encode(inner_key) asn1["subjectPublicKey"] = ASN1Tools.bytes2bitstring(inner_key) return asn1
def test_bitstring_trailing_zero(self): self.assertTrue( ASN1Tools.bitstring_has_trailing_zeros(BitString([0, 1, 0]))) self.assertTrue(ASN1Tools.bitstring_has_trailing_zeros(BitString([0]))) self.assertTrue( ASN1Tools.bitstring_has_trailing_zeros( BitString([1, 1, 1, 1, 1, 1, 1, 0]))) self.assertTrue( ASN1Tools.bitstring_has_trailing_zeros( BitString([1, 1, 1, 1, 1, 1, 1, 1, 0]))) self.assertFalse( ASN1Tools.bitstring_has_trailing_zeros(BitString([0, 1]))) self.assertFalse(ASN1Tools.bitstring_has_trailing_zeros(BitString([]))) self.assertFalse( ASN1Tools.bitstring_has_trailing_zeros(BitString([0, 0, 0, 0, 1]))) self.assertFalse( ASN1Tools.bitstring_has_trailing_zeros( BitString([0, 0, 0, 0, 0, 0, 1]))) self.assertFalse( ASN1Tools.bitstring_has_trailing_zeros( BitString([0, 0, 0, 0, 0, 0, 0, 1])))
def _post_decode_hook(self): alg_oid = OID.from_asn1(self.asn1["algorithm"]["algorithm"]) self._pk_alg = PublicKeyAlgorithms.lookup("oid", alg_oid) if self._pk_alg is None: self._key_decoding_error = "Unable to determine public key algorithm for OID %s." % ( alg_oid) return if self._pk_alg not in self._HANDLERS_BY_PK_ALG: self._key_decoding_error = "Unable to determine public key handler for public key algorithm %s." % ( self._pk_alg.name) return handler_class = self._HANDLERS_BY_PK_ALG[self._pk_alg] key_data = ASN1Tools.bitstring2bytes(self.asn1["subjectPublicKey"]) self._key = handler_class.from_subject_pubkey_info( self._pk_alg, self.asn1["algorithm"]["parameters"], key_data) self._key_decoding_error = None
def create(cls, parameters): asn1 = rfc2459.SubjectPublicKeyInfo() asn1["algorithm"][ "algorithm"] = OIDDB.KeySpecificationAlgorithms.inverse( "ecPublicKey").to_asn1() if parameters["curve"].oid is not None: asn1["algorithm"]["parameters"] = pyasn1.codec.der.encoder.encode( parameters["curve"].oid.to_asn1()) else: # TODO not implemented #domain_params = SpecifiedECDomain() #domain_params["version"] = 1 #asn1["algorithm"]["parameters"] = domain_params raise NotImplementedError( "Creation of explicitly specified elliptic curve domain parameters (i.e., non-named curves) is not implemented in x509sak" ) inner_key = parameters["curve"].point(parameters["x"], parameters["y"]).encode() asn1["subjectPublicKey"] = ASN1Tools.bytes2bitstring(inner_key) return asn1
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
def valid_not_before(self): return ASN1Tools.parse_datetime( str(self.asn1["tbsCertificate"]["validity"] ["notBefore"].getComponent()))
def signature(self): signature = ASN1Tools.bitstring2bytes(self.asn1["signatureValue"]) return signature
def keyid(self, hashfnc="sha1"): hashval = hashlib.new(hashfnc) inner_key = ASN1Tools.bitstring2bytes(self.asn1["subjectPublicKey"]) hashval.update(inner_key) return hashval.digest()