diff --git a/lib/dnssec.c b/lib/dnssec.c
index 3c6f51ccbe35f79cd8923802edcb5328099e7876..0103ab8b4a6384e4115ad360aed3472675581d11 100644
--- a/lib/dnssec.c
+++ b/lib/dnssec.c
@@ -23,6 +23,7 @@
 #include <libknot/rdataset.h>
 #include <libknot/rrset.h>
 #include <libknot/rrtype/dnskey.h>
+#include <libknot/rrtype/rrsig.h>
 
 
 #include "lib/defines.h"
@@ -76,17 +77,15 @@ void _knot_ds_digest(const knot_rdataset_t *rrs, size_t pos,
 	*digest_size = knot_rdata_rdlen(rr) - 4;
 }
 
-/* RFC4035 5.2, second bullet */
-static int authenticate_referral(const knot_rdata_t *krdata, const knot_dname_t *kown,
-                                 const knot_rrset_t *ds)
+/* RFC4035 5.2, bullet 2 */
+static int authenticate_referral(const dnssec_key_t *key, const knot_rrset_t *ds)
 {
-	assert(krdata && kown && ds);
+	assert(key && ds);
 	assert(ds->type == KNOT_RRTYPE_DS);
 
 	int ret = 0;
 	dnssec_binary_t orig_ds_rdata;
 	dnssec_binary_t generated_ds_rdata = {0, };
-	dnssec_key_t *key = NULL;
 
 	{
 		/* Obtain RDATA of the supplied DS. */
@@ -95,32 +94,6 @@ static int authenticate_referral(const knot_rdata_t *krdata, const knot_dname_t
 		orig_ds_rdata.data = knot_rdata_data(rr);
 	}
 
-	/* Set-up DNSKEY. */
-	ret = dnssec_key_new(&key);
-	if (ret != DNSSEC_EOK) {
-		ret = kr_error(ENOMEM);
-		goto fail;
-	}
-	{
-		dnssec_binary_t binary_key;
-		binary_key.size = knot_rdata_rdlen(krdata);
-		binary_key.data = knot_rdata_data(krdata);
-		if (!binary_key.size || !binary_key.data) {
-			ret = kr_error(KNOT_DNSSEC_ENOKEY);
-			goto fail;
-		}
-		ret = dnssec_key_set_rdata(key, &binary_key);
-		if (ret != DNSSEC_EOK) {
-			ret = kr_error(ENOMEM);
-			goto fail;
-		}
-	}
-	ret = dnssec_key_set_dname(key, kown);
-	if (ret != DNSSEC_EOK) {
-		ret = kr_error(ENOMEM);
-		goto fail;
-	}
-
 	/* Compute DS RDATA from the DNSKEY. */
 	ret = dnssec_key_create_ds(key, _knot_ds_dtype(&ds->rrs, 0), &generated_ds_rdata);
 	if (ret != DNSSEC_EOK) {
@@ -136,32 +109,121 @@ static int authenticate_referral(const knot_rdata_t *krdata, const knot_dname_t
 
 fail:
 	dnssec_binary_free(&generated_ds_rdata);
-	dnssec_key_free(key);
 	return ret;
 }
 
-int kr_dnskey_trusted(const knot_pktsection_t *sec, const knot_rrset_t *keys, const knot_rrset_t *ta)
+/* RFC4035 5.3.1 */
+static int validate_rrsig_rr(const knot_rrset_t *rrset, const knot_rrset_t *rrsig,
+                             const knot_rrset_t *keys, size_t pos, const dnssec_key_t *key,
+                             const knot_dname_t *zone_name, uint32_t timestamp)
 {
-	if (!sec || !keys || !ta) {
+	if (!rrset || !rrsig || !keys || !key || !zone_name) {
+		return kr_error(EINVAL);
+	}
+#warning TODO: Make the comparison case-insensitive.
+	/* bullet 1 */
+	if ((rrset->rclass != rrsig->rclass) || (knot_dname_cmp(rrset->owner, rrsig->owner) != 0)) {
+		return kr_error(EINVAL);
+	}
+	/* bullet 2 */
+	const knot_dname_t *signer_name = knot_rrsig_signer_name(&rrsig->rrs, 0);
+	if (signer_name == NULL) {
+		return kr_error(EINVAL);
+	}
+	if (knot_dname_cmp(signer_name, zone_name) != 0) {
+		return kr_error(EINVAL);
+	}
+	/* bullet 3 */
+	uint16_t tcovered = knot_rrsig_type_covered(&rrsig->rrs, 0);
+	if (tcovered != rrset->type) {
+		return kr_error(EINVAL);
+	}
+	/* bullet 4 */
+	if (knot_rrsig_labels(&rrsig->rrs, 0) > knot_dname_labels(rrset->owner, NULL)) {
 		return kr_error(EINVAL);
 	}
+	/* bullet 5 */
+	if (knot_rrsig_sig_expiration(&rrsig->rrs, 0) < timestamp) {
+		return kr_error(EINVAL);
+	}
+	/* bullet 6 */
+	if (knot_rrsig_sig_inception(&rrsig->rrs, 0) > timestamp) {
+		return kr_error(EINVAL);
+	}
+	/* bullet 7 */
+	if ((knot_dname_cmp(keys->owner, signer_name) != 0) ||
+	    (knot_dnskey_alg(&keys->rrs, pos) != knot_rrsig_algorithm(&rrsig->rrs, 0)) ||
+	    (dnssec_key_get_keytag(key) != knot_rrsig_key_tag(&rrsig->rrs, 0))) {
+		return kr_error(EINVAL);
+	}
+	/* bullet 8 */
+	/* Checked somewhere else. */
+	/* bullet 9 and 10 */
+	/* One of the requirements should be always fulfilled. */
 
-#warning TODO: there should be an error saying that there is no matching key
+	return kr_ok();
+}
+
+/** Validate RRSet in canonical format. */
+static int crrset_validate(const knot_pktsection_t *sec, const knot_rrset_t *rrset,
+                           const knot_rrset_t *keys, size_t pos, const dnssec_key_t *key,
+                           const knot_dname_t *zone_name, uint32_t timestamp)
+{
 	int ret = kr_error(KNOT_DNSSEC_ENOKEY);
+	for (unsigned i = 0; i < sec->count; ++i) {
+		/* Try every RRSIG. */
+		const knot_rrset_t *rr = knot_pkt_rr(sec, i);
+		if (rr->type != KNOT_RRTYPE_RRSIG) {
+			continue;
+		}
+		if (validate_rrsig_rr(rrset, rr, keys, pos, key, zone_name, timestamp) != 0) {
+			continue;
+		}
+		ret = kr_ok();
+		break;
+	}
+
+	return kr_error(ENOSYS);
+}
+
+int kr_dnskeys_trusted(const knot_pktsection_t *sec, const knot_rrset_t *keys,
+                       const knot_rrset_t *ta, const knot_dname_t *zone_name, uint32_t timestamp)
+{
+	if (!sec || !keys || !ta) {
+		return kr_error(EINVAL);
+	}
 
-	/* The supplied DS record has been authenticated.
+	/* RFC4035 5.2, bullet 1
+	 * The supplied DS record has been authenticated.
 	 * It has been validated or is part of a configured trust anchor.
 	 *
 	 * This implementation actually ignores the SEP flag.
 	 */
 
+#warning TODO: there should be an error saying that there is no matching key
+	int ret = kr_error(KNOT_DNSSEC_ENOKEY);
 	for (uint16_t i = 0; i < keys->rrs.rr_count; ++i) {
+		/* RFC4035 5.3.1, bullet 8 */ /* ZSK */
+		if (!(knot_dnskey_flags(&keys->rrs, i) & 0x0100)) {
+			continue;
+		}
 		const knot_rdata_t *krr = knot_rdataset_at(&keys->rrs, i);
-		if (authenticate_referral(krr, keys->owner, ta) != 0) {
+		struct dseckey *key;
+		if (kr_dnssec_key_from_rdata(&key, krr, keys->owner) != 0) {
+			continue;
+		}
+		if (authenticate_referral((dnssec_key_t *) key, ta) != 0) {
+			kr_dnssec_key_free(&key);
 			continue;
 		}
 #warning TODO: Check the signature of the rrset.
+		if (crrset_validate(sec, keys, keys, i, (dnssec_key_t *) key, zone_name, timestamp) != 0) {
+			kr_dnssec_key_free(&key);
+			continue;
+		}
+		kr_dnssec_key_free(&key);
 		ret = kr_ok();
+		break;
 	}
 
 	if (ret != 0) {
@@ -170,3 +232,46 @@ int kr_dnskey_trusted(const knot_pktsection_t *sec, const knot_rrset_t *keys, co
 
 	return kr_error(ENOSYS);
 }
+
+int kr_dnssec_key_from_rdata(struct dseckey **key, const knot_rdata_t *krdata, const knot_dname_t *kown)
+{
+	assert(key);
+
+	dnssec_key_t *new_key = NULL;
+	dnssec_binary_t binary_key;
+	int ret;
+
+	ret = dnssec_key_new(&new_key);
+	if (ret != DNSSEC_EOK) {
+		return kr_error(ENOMEM);
+	}
+
+	binary_key.size = knot_rdata_rdlen(krdata);
+	binary_key.data = knot_rdata_data(krdata);
+	if (!binary_key.size || !binary_key.data) {
+		dnssec_key_free(new_key);
+		return kr_error(KNOT_DNSSEC_ENOKEY);
+	}
+	ret = dnssec_key_set_rdata(new_key, &binary_key);
+	if (ret != DNSSEC_EOK) {
+		dnssec_key_free(new_key);
+		return kr_error(ENOMEM);
+	}
+
+	ret = dnssec_key_set_dname(new_key, kown);
+	if (ret != DNSSEC_EOK) {
+		dnssec_key_free(new_key);
+		return kr_error(ENOMEM);
+	}
+
+	*key = (struct dseckey *) new_key;
+	return kr_ok();
+}
+
+void kr_dnssec_key_free(struct dseckey **key)
+{
+	assert(key);
+
+	dnssec_key_free((dnssec_key_t *) *key);
+	*key = NULL;
+}
diff --git a/lib/dnssec.h b/lib/dnssec.h
index 2e89c3f3f90a198c1656394c9dfc630e9d6a9023..f10cbc5db4a825a2b87be77ca64c527f6cdc31a5 100644
--- a/lib/dnssec.h
+++ b/lib/dnssec.h
@@ -36,9 +36,29 @@ void kr_crypto_reinit(void);
 
 /**
  * Check whether the DNSKEY rrset matches the supplied trust anchor RRSet.
- * @param sec Packet section containing the DNSKEY RRSet including its signatures.
- * @param key DNSKEY RRSet to check.
- * @param ta  Trust anchor RRSet agains which to validate the DNSKEY.
+ * @param sec       Packet section containing the DNSKEY RRSet including its signatures.
+ * @param key       DNSKEY RRSet to check.
+ * @param ta        Trust anchor RRSet agains which to validate the DNSKEY.
+ * @param zone_name Name of the zone containing the RRSet.
+ * @param timestamp Time stamp.
  * @return     0 or error code.
  */
-int kr_dnskey_trusted(const knot_pktsection_t *sec, const knot_rrset_t *keys, const knot_rrset_t *ta);
+int kr_dnskeys_trusted(const knot_pktsection_t *sec, const knot_rrset_t *keys,
+                       const knot_rrset_t *ta, const knot_dname_t *zone_name, uint32_t timestamp);
+
+/** Opaque DNSSEC key pointer. */
+struct dseckey;
+
+/**
+ * Construct a DNSSEC key.
+ * @param key    Pointer to be set to newly created DNSSEC key.
+ * @param krdata Key RDATA.
+ * @param kown   DNSKEY RRSet owner name.
+ */
+int kr_dnssec_key_from_rdata(struct dseckey **key, const knot_rdata_t *krdata, const knot_dname_t *kown);
+
+/**
+ * Frees the DNSSEC key.
+ * @param key Pointer to freed key.
+ */
+void kr_dnssec_key_free(struct dseckey **key);
diff --git a/lib/layer/validate.c b/lib/layer/validate.c
index c4cc6dd62fdea3e156e3fdc871ad34bad374c8bf..efe247cfb0c1c633edf51be8525483d2f89be869 100644
--- a/lib/layer/validate.c
+++ b/lib/layer/validate.c
@@ -540,9 +540,11 @@ static int validate_keyset(struct kr_query *qry, knot_pkt_t *answer)
 		return kr_error(KNOT_DNSSEC_ENOKEY);
 	}
 
+#warning TODO: Ensure canonical format of the whole DNSKEY RRSet. (Also remove duplicities?)
+
 	/* Check if there's a key for current TA. */
 #warning TODO: check if there is a DNSKEY we can trust (matching current TA)
-	int ret = kr_dnskey_trusted(an, qry->zone_cut.key, qry->zone_cut.trust_anchor);
+	int ret = kr_dnskeys_trusted(an, qry->zone_cut.key, qry->zone_cut.trust_anchor, qry->zone_cut.name, qry->timestamp.tv_sec);
 	if (ret != 0) {
 		knot_rrset_free(&qry->zone_cut.key, qry->zone_cut.pool);
 		return ret;