def test_parse_public_key(self): data = read_test_data("test.txt") key = parse_public_key(base64.b64decode(parse_tag_value(data)[b"p"])) self.assertEqual(key["modulus"], TEST_KEY_MODULUS) self.assertEqual(key["publicExponent"], TEST_KEY_PUBLIC_EXPONENT) try: data = read_test_data("test_bad.txt") key = parse_public_key(base64.b64decode(parse_tag_value(data)[b"p"])) except UnparsableKeyError: return self.fail("failed to reject invalid public key")
def test_parse_public_key(self): data = read_test_data('test.txt') key = parse_public_key(base64.b64decode(parse_tag_value(data)[b'p'])) self.assertEqual(key['modulus'], TEST_KEY_MODULUS) self.assertEqual(key['publicExponent'], TEST_KEY_PUBLIC_EXPONENT) try: data = read_test_data('test_bad.txt') key = parse_public_key( base64.b64decode(parse_tag_value(data)[b'p'])) except UnparsableKeyError: return self.fail("failed to reject invalid public key")
def test_trailing_whitespace(self): hval = b'''v=1; a=rsa-sha256; d=facebookmail.com; s=s1024-2011-q2; c=relaxed/simple; q=dns/txt; [email protected]; t=1308078492; h=From:Subject:Date:To:MIME-Version:Content-Type; bh=+qPyCOiDQkusTPstCoGjimgDgeZbUaJWIr1mdE6RFxk=; b=EUmDmdnAsNtjSEHGHNTa8PXgGaEUtOVezagmninX5Bs/Q26R9r3AMgawyUSKkbHp /bQZU6QPZfdvmLMPdIWCQPo8SP+gsz4dpox2efO61DlvgYaxBRhwFedAW9LjYhQc 3KzW0yB9JHwiDCw1EioVkv+OMHhAYzoIypA0bQyi2bc=; ''' sig = parse_tag_value(hval) self.assertEquals(sig[b't'], b'1308078492') self.assertEquals(len(sig), 11)
def test_trailing_whitespace(self): hval = b'''v=1; a=rsa-sha256; d=facebookmail.com; s=s1024-2011-q2; c=relaxed/simple; q=dns/txt; [email protected]; t=1308078492; h=From:Subject:Date:To:MIME-Version:Content-Type; bh=+qPyCOiDQkusTPstCoGjimgDgeZbUaJWIr1mdE6RFxk=; b=EUmDmdnAsNtjSEHGHNTa8PXgGaEUtOVezagmninX5Bs/Q26R9r3AMgawyUSKkbHp /bQZU6QPZfdvmLMPdIWCQPo8SP+gsz4dpox2efO61DlvgYaxBRhwFedAW9LjYhQc 3KzW0yB9JHwiDCw1EioVkv+OMHhAYzoIypA0bQyi2bc=; ''' sig = parse_tag_value(hval) self.assertEquals(sig[b't'],b'1308078492') self.assertEquals(len(sig),11)
def load_pk_from_dns(name, dnsfunc=get_txt): s = dnsfunc(name) if not s: raise KeyFormatError("missing public key: %s" % name) try: if type(s) is str: s = s.encode('ascii') pub = parse_tag_value(s) except InvalidTagValueList as e: raise KeyFormatError(e) try: pk = parse_public_key(base64.b64decode(pub[b'p'])) keysize = bitsize(pk['modulus']) except KeyError: raise KeyFormatError("incomplete public key: %s" % s) except (TypeError, UnparsableKeyError) as e: raise KeyFormatError("could not parse public key (%s): %s" % (pub[b'p'], e)) return pk, keysize
def verify(self, idx=0, dnsfunc=get_txt): sigheaders = [(x, y) for x, y in self.headers if x.lower() == b"dkim-signature"] if len(sigheaders) <= idx: return False # By default, we validate the first DKIM-Signature line found. try: sig = parse_tag_value(sigheaders[idx][1]) self.signature_fields = sig except InvalidTagValueList as e: raise MessageFormatError(e) self.logger.debug("sig: %r" % sig) validate_signature_fields(sig) self.domain = sig[b'd'] self.selector = sig[b's'] include_headers = [x.lower() for x in re.split(br"\s*:\s*", sig[b'h'])] self.include_headers = tuple(include_headers) return self.verify_sig(sig, include_headers, sigheaders[idx], dnsfunc)
def test_multiple(self): self.assertEqual({ b'foo': b'bar', b'baz': b'foo' }, parse_tag_value(b'foo=bar;baz=foo'))
def verify_instance(self, arc_headers_w_instance, instance, dnsfunc=get_txt): if (instance == 0) or (len(arc_headers_w_instance) == 0): raise ParameterError("request to verify instance %d not present" % (instance)) aar_value = None ams_value = None as_value = None arc_headers = [] output = {'instance': instance} for i, arc_header in arc_headers_w_instance: if i > instance: continue arc_headers.append(arc_header) if i == instance: if arc_header[0].lower() == b"arc-authentication-results": if aar_value is not None: raise MessageFormatError( "Duplicate ARC-Authentication-Results for instance %d" % instance) aar_value = arc_header[1] elif arc_header[0].lower() == b"arc-message-signature": if ams_value is not None: raise MessageFormatError( "Duplicate ARC-Message-Signature for instance %d" % instance) ams_value = arc_header[1] elif arc_header[0].lower() == b"arc-seal": if as_value is not None: raise MessageFormatError( "Duplicate ARC-Seal for instance %d" % instance) as_value = arc_header[1] if (aar_value is None) or (ams_value is None) or (as_value is None): raise MessageFormatError("Incomplete ARC set for instance %d" % instance) output['aar-value'] = aar_value # Validate Arc-Message-Signature try: sig = parse_tag_value(ams_value) except InvalidTagValueList as e: raise MessageFormatError(e) self.logger.debug("ams sig[%d]: %r" % (instance, sig)) validate_signature_fields( sig, [b'i', b'a', b'b', b'c', b'bh', b'd', b'h', b's'], True) output['ams-domain'] = sig[b'd'] output['ams-selector'] = sig[b's'] include_headers = [x.lower() for x in re.split(br"\s*:\s*", sig[b'h'])] if b'arc-seal' in include_headers: raise ParameterError( "The Arc-Message-Signature MUST NOT sign ARC-Seal") ams_header = (b'ARC-Message-Signature', b' ' + ams_value) ams_valid = self.verify_sig(sig, include_headers, ams_header, dnsfunc) output['ams-valid'] = ams_valid self.logger.debug("ams valid: %r" % ams_valid) # Validate Arc-Seal try: sig = parse_tag_value(as_value) except InvalidTagValueList as e: raise MessageFormatError(e) self.logger.debug("as sig[%d]: %r" % (instance, sig)) validate_signature_fields(sig, [b'i', b'a', b'b', b'cv', b'd', b's', b't'], True) output['as-domain'] = sig[b'd'] output['as-selector'] = sig[b's'] output['cv'] = sig[b'cv'] as_include_headers = [x[0].lower() for x in arc_headers] as_include_headers.reverse() as_header = (b'ARC-Seal', b' ' + as_value) as_valid = self.verify_sig(sig, as_include_headers[:-1], as_header, dnsfunc) output['as-valid'] = as_valid self.logger.debug("as valid: %r" % as_valid) return output
def test_trailing_separator_ignored(self): self.assertEqual( {b'foo': b'bar'}, parse_tag_value(b'foo=bar;'))
def verify(self,idx=0,dnsfunc=get_txt): sigheaders = [(x,y) for x,y in self.headers if x.lower() == b"dkim-signature"] if len(sigheaders) <= idx: return False # By default, we validate the first DKIM-Signature line found. try: sig = parse_tag_value(sigheaders[idx][1]) self.signature_fields = sig except InvalidTagValueList as e: raise MessageFormatError(e) logger = self.logger logger.debug("sig: %r" % sig) validate_signature_fields(sig) self.domain = sig[b'd'] self.selector = sig[b's'] try: canon_policy = CanonicalizationPolicy.from_c_value(sig.get(b'c')) except InvalidCanonicalizationPolicyError as e: raise MessageFormatError("invalid c= value: %s" % e.args[0]) headers = canon_policy.canonicalize_headers(self.headers) body = canon_policy.canonicalize_body(self.body) try: hasher = HASH_ALGORITHMS[sig[b'a']] except KeyError as e: raise MessageFormatError("unknown signature algorithm: %s" % e.args[0]) if b'l' in sig: body = body[:int(sig[b'l'])] h = hasher() h.update(body) bodyhash = h.digest() logger.debug("bh: %s" % base64.b64encode(bodyhash)) try: bh = base64.b64decode(re.sub(br"\s+", b"", sig[b'bh'])) except TypeError as e: raise MessageFormatError(str(e)) if bodyhash != bh: raise ValidationError( "body hash mismatch (got %s, expected %s)" % (base64.b64encode(bodyhash), sig[b'bh'])) name = sig[b's'] + b"._domainkey." + sig[b'd'] + b"." s = dnsfunc(name) if not s: raise KeyFormatError("missing public key: %s"%name) try: if type(s) is str: s = s.encode('ascii') pub = parse_tag_value(s) except InvalidTagValueList as e: raise KeyFormatError(e) try: pk = parse_public_key(base64.b64decode(pub[b'p'])) self.keysize = bitsize(pk['modulus']) except KeyError: raise KeyFormatError("incomplete public key: %s" % s) except (TypeError,UnparsableKeyError) as e: raise KeyFormatError("could not parse public key (%s): %s" % (pub[b'p'],e)) include_headers = [x.lower() for x in re.split(br"\s*:\s*", sig[b'h'])] self.include_headers = tuple(include_headers) # address bug#644046 by including any additional From header # fields when verifying. Since there should be only one From header, # this shouldn't break any legitimate messages. This could be # generalized to check for extras of other singleton headers. if b'from' in include_headers: include_headers.append(b'from') h = hasher() self.signed_headers = hash_headers( h, canon_policy, headers, include_headers, sigheaders[idx], sig) try: signature = base64.b64decode(re.sub(br"\s+", b"", sig[b'b'])) res = RSASSA_PKCS1_v1_5_verify(h, signature, pk) if res and self.keysize < self.minkey: raise KeyFormatError("public key too small: %d" % self.keysize) return res except (TypeError,DigestTooLargeError) as e: raise KeyFormatError("digest too large for modulus: %s"%e)
def test_multiple(self): self.assertEqual( {b'foo': b'bar', b'baz': b'foo'}, parse_tag_value(b'foo=bar;baz=foo'))
def test_whitespace_is_stripped(self): self.assertEqual({ b'foo': b'bar', b'baz': b'f oo=bar' }, parse_tag_value(b' foo \t= bar;\tbaz= f oo=bar '))
def test_value_with_equals(self): self.assertEqual( {b'foo': b'bar', b'baz': b'foo=bar'}, parse_tag_value(b'foo=bar;baz=foo=bar'))
def test_value_with_equals(self): self.assertEqual({ b'foo': b'bar', b'baz': b'foo=bar' }, parse_tag_value(b'foo=bar;baz=foo=bar'))
def test_single(self): self.assertEqual( {b'foo': b'bar'}, parse_tag_value(b'foo=bar'))
def test_trailing_separator_ignored(self): self.assertEqual({b'foo': b'bar'}, parse_tag_value(b'foo=bar;'))
def test_single(self): self.assertEqual({b'foo': b'bar'}, parse_tag_value(b'foo=bar'))
def test_whitespace_is_stripped(self): self.assertEqual( {b'foo': b'bar', b'baz': b'f oo=bar'}, parse_tag_value(b' foo \t= bar;\tbaz= f oo=bar '))