resolved-dns-dnssec.c revision d12bf2bdff8d616b7e59fc480c7e610003b494df
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering/***
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering This file is part of systemd.
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering Copyright 2015 Lennart Poettering
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering systemd is free software; you can redistribute it and/or modify it
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering under the terms of the GNU Lesser General Public License as published by
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering (at your option) any later version.
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering systemd is distributed in the hope that it will be useful, but
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering Lesser General Public License for more details.
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering You should have received a copy of the GNU Lesser General Public License
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering***/
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek#include <gcrypt.h>
a09561746f15b84da9471b5c4be74e53d19e4f3fLennart Poettering
3ffd4af22052963e7a29431721ee204e634bea75Lennart Poettering#include "alloc-util.h"
6482f6269c87d2249e52e889a63adbdd50f2d691Ronny Chevalier#include "dns-domain.h"
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "resolved-dns-dnssec.h"
0b452006de98294d1690f045f6ea2f7f6630ec3bRonny Chevalier#include "resolved-dns-packet.h"
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering#include "string-table.h"
3ffd4af22052963e7a29431721ee204e634bea75Lennart Poettering
3ffd4af22052963e7a29431721ee204e634bea75Lennart Poettering/* Open question:
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek *
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering * How does the DNSSEC canonical form of a hostname with a label
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * containing a dot look like, the way DNS-SD does it?
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek *
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek * TODO:
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek *
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek * - Iterative validation
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek * - NSEC proof of non-existance
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * - NSEC3 proof of non-existance
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * - Make trust anchor store read additional DS+DNSKEY data from disk
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek * - wildcard zones compatibility
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering * - multi-label zone compatibility
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * - DNSSEC cname/dname compatibility
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * - per-interface DNSSEC setting
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * - DSA support
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering * - EC support?
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering *
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering * */
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering#define VERIFY_RRS_MAX 256
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering#define MAX_KEY_SIZE (32*1024)
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek/*
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * The DNSSEC Chain of trust:
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek *
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * DS RRs are protected like normal RRs
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering *
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * Example chain:
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering */
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poetteringstatic bool dnssec_algorithm_supported(int algorithm) {
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering return IN_SET(algorithm,
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering DNSSEC_ALGORITHM_RSASHA1,
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering DNSSEC_ALGORITHM_RSASHA256,
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering DNSSEC_ALGORITHM_RSASHA512);
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering}
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poetteringstatic bool dnssec_digest_supported(int digest) {
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering return IN_SET(digest,
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering DNSSEC_DIGEST_SHA1,
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering DNSSEC_DIGEST_SHA256);
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering}
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poetteringuint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek const uint8_t *p;
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek uint32_t sum;
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek size_t i;
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering /* The algorithm from RFC 4034, Appendix B. */
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering assert(dnskey);
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering sum = (uint32_t) dnskey->dnskey.flags +
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering p = dnskey->dnskey.key;
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering for (i = 0; i < dnskey->dnskey.key_size; i++)
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek sum += (sum >> 16) & UINT32_C(0xFFFF);
320814811417146cfa1e416f69f1101eed630c36Luke Shumaker
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering return sum & UINT32_C(0xFFFF);
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering}
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poetteringstatic int rr_compare(const void *a, const void *b) {
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering size_t m;
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering int r;
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek /* Let's order the RRs according to RFC 4034, Section 6.3 */
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering assert(x);
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering assert(*x);
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering assert((*x)->wire_format);
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering assert(y);
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering assert(*y);
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering assert((*y)->wire_format);
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering r = memcmp((*x)->wire_format, (*y)->wire_format, m);
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering if (r != 0)
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering return r;
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering if ((*x)->wire_format_size < (*y)->wire_format_size)
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering return -1;
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering else if ((*x)->wire_format_size > (*y)->wire_format_size)
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering return 1;
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering return 0;
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering}
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poetteringstatic int dnssec_rsa_verify(
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering const char *hash_algorithm,
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering const void *signature, size_t signature_size,
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering const void *data, size_t data_size,
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering const void *exponent, size_t exponent_size,
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering const void *modulus, size_t modulus_size) {
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering gcry_mpi_t n = NULL, e = NULL, s = NULL;
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering gcry_error_t ge;
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering int r;
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering assert(hash_algorithm);
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering if (ge != 0) {
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering r = -EIO;
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering goto finish;
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering }
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering if (ge != 0) {
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering r = -EIO;
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering goto finish;
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering }
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering if (ge != 0) {
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering r = -EIO;
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering goto finish;
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering }
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering ge = gcry_sexp_build(&signature_sexp,
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering NULL,
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering "(sig-val (rsa (s %m)))",
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering s);
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering if (ge != 0) {
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering r = -EIO;
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering goto finish;
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering }
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering ge = gcry_sexp_build(&data_sexp,
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering NULL,
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering "(data (flags pkcs1) (hash %s %b))",
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering hash_algorithm,
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering (int) data_size,
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering data);
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering if (ge != 0) {
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering r = -EIO;
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering goto finish;
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering }
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering ge = gcry_sexp_build(&public_key_sexp,
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering NULL,
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering "(public-key (rsa (n %m) (e %m)))",
e9174f29c7e3ee45137537b126458718913a3ec5Lennart Poettering n,
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering e);
fed1e721fd0c81e60c77120539f34e16c2585634Lennart Poettering if (ge != 0) {
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering r = -EIO;
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering goto finish;
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering }
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering r = 0;
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering else if (ge != 0) {
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering log_debug("RSA signature check failed: %s", gpg_strerror(ge));
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering r = -EIO;
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering } else
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering r = 1;
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering
e9174f29c7e3ee45137537b126458718913a3ec5Lennart Poetteringfinish:
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering if (e)
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering gcry_mpi_release(e);
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering if (n)
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering gcry_mpi_release(n);
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering if (s)
1021b21bc6f8dd522b46116e8598b17f9f93f1b7Lennart Poettering gcry_mpi_release(s);
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering
de0671ee7fe465e108f62dcbbbe9366f81dd9e9aZbigniew Jędrzejewski-Szmek if (public_key_sexp)
de0671ee7fe465e108f62dcbbbe9366f81dd9e9aZbigniew Jędrzejewski-Szmek gcry_sexp_release(public_key_sexp);
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering if (signature_sexp)
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering gcry_sexp_release(signature_sexp);
de0671ee7fe465e108f62dcbbbe9366f81dd9e9aZbigniew Jędrzejewski-Szmek if (data_sexp)
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering gcry_sexp_release(data_sexp);
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering return r;
1021b21bc6f8dd522b46116e8598b17f9f93f1b7Lennart Poettering}
1021b21bc6f8dd522b46116e8598b17f9f93f1b7Lennart Poettering
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poetteringstatic void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering gcry_md_write(md, &v, sizeof(v));
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering}
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poetteringstatic void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering v = htobe16(v);
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering gcry_md_write(md, &v, sizeof(v));
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering}
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poetteringstatic void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering v = htobe32(v);
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering gcry_md_write(md, &v, sizeof(v));
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering}
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poetteringstatic int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
a0ab566574303be1ca12cdb334f284cfd407caa5Lennart Poettering usec_t expiration, inception, skew;
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering assert(rrsig);
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
e13bb5d2b133f9ae51c0a2d20aa51071c780e9aeKay Sievers
e13bb5d2b133f9ae51c0a2d20aa51071c780e9aeKay Sievers if (realtime == USEC_INFINITY)
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering realtime = now(CLOCK_REALTIME);
a0ab566574303be1ca12cdb334f284cfd407caa5Lennart Poettering
a0ab566574303be1ca12cdb334f284cfd407caa5Lennart Poettering expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
a0ab566574303be1ca12cdb334f284cfd407caa5Lennart Poettering inception = rrsig->rrsig.inception * USEC_PER_SEC;
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering if (inception > expiration)
78edb35ab4f4227485cb9ec816b43c37e0d5e62aLennart Poettering return -EKEYREJECTED;
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov /* Permit a certain amount of clock skew of 10% of the valid
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov * time range. This takes inspiration from unbound's
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov * resolver. */
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov skew = (expiration - inception) / 10;
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov if (skew > SKEW_MAX)
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov skew = SKEW_MAX;
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov if (inception < skew)
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov inception = 0;
78edb35ab4f4227485cb9ec816b43c37e0d5e62aLennart Poettering else
78edb35ab4f4227485cb9ec816b43c37e0d5e62aLennart Poettering inception -= skew;
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering if (expiration + skew < expiration)
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering expiration = USEC_INFINITY;
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering else
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering expiration += skew;
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering return realtime < inception || realtime > expiration;
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering}
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poetteringint dnssec_verify_rrset(
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering DnsAnswer *a,
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering DnsResourceKey *key,
c96cc5822c165e86be78ed96dac6573986032fabLennart Poettering DnsResourceRecord *rrsig,
c96cc5822c165e86be78ed96dac6573986032fabLennart Poettering DnsResourceRecord *dnskey,
c96cc5822c165e86be78ed96dac6573986032fabLennart Poettering usec_t realtime) {
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
c96cc5822c165e86be78ed96dac6573986032fabLennart Poettering size_t exponent_size, modulus_size, hash_size;
e66e5b612a9e5921d79a6aedab4983e33dff8cb1Lennart Poettering void *exponent, *modulus, *hash;
c96cc5822c165e86be78ed96dac6573986032fabLennart Poettering DnsResourceRecord **list, *rr;
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering gcry_md_hd_t md = NULL;
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering size_t k, n = 0;
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering int r;
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering assert(key);
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering assert(rrsig);
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering assert(dnskey);
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering /* Verifies the the RRSet matching the specified "key" in "a",
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering * using the signature "rrsig" and the key "dnskey". It's
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering * assumed the RRSIG and DNSKEY match. */
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm))
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering return -EOPNOTSUPP;
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering if (a->n_rrs > VERIFY_RRS_MAX)
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering return -E2BIG;
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering r = dnssec_rrsig_expired(rrsig, realtime);
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering if (r < 0)
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering return r;
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering if (r > 0)
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering return DNSSEC_SIGNATURE_EXPIRED;
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering /* Collect all relevant RRs in a single array, so that we can look at the RRset */
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering list = newa(DnsResourceRecord *, a->n_rrs);
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering DNS_ANSWER_FOREACH(rr, a) {
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek r = dns_resource_key_equal(key, rr->key);
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering if (r < 0)
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering return r;
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering if (r == 0)
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering continue;
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering /* We need the wire format for ordering, and digest calculation */
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering r = dns_resource_record_to_wire_format(rr, true);
143bfdaf0b890fa7acadf02d1eafacaef1b696bdHolger Hans Peter Freyther if (r < 0)
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering return r;
143bfdaf0b890fa7acadf02d1eafacaef1b696bdHolger Hans Peter Freyther
78edb35ab4f4227485cb9ec816b43c37e0d5e62aLennart Poettering list[n++] = rr;
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering }
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering
b3a7ba896851708cce0e5026a814007fbb11b4cdMartin Pitt if (n <= 0)
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek return -ENODATA;
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek /* Bring the RRs into canonical order */
qsort_safe(list, n, sizeof(DnsResourceRecord), rr_compare);
/* OK, the RRs are now in canonical order. Let's calculate the digest */
switch (rrsig->rrsig.algorithm) {
case DNSSEC_ALGORITHM_RSASHA1:
case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
gcry_md_open(&md, GCRY_MD_SHA1, 0);
hash_size = 20;
break;
case DNSSEC_ALGORITHM_RSASHA256:
gcry_md_open(&md, GCRY_MD_SHA256, 0);
hash_size = 32;
break;
case DNSSEC_ALGORITHM_RSASHA512:
gcry_md_open(&md, GCRY_MD_SHA512, 0);
hash_size = 64;
break;
default:
assert_not_reached("Unknown digest");
}
if (!md)
return -EIO;
md_add_uint16(md, rrsig->rrsig.type_covered);
md_add_uint8(md, rrsig->rrsig.algorithm);
md_add_uint8(md, rrsig->rrsig.labels);
md_add_uint32(md, rrsig->rrsig.original_ttl);
md_add_uint32(md, rrsig->rrsig.expiration);
md_add_uint32(md, rrsig->rrsig.inception);
md_add_uint16(md, rrsig->rrsig.key_tag);
r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
if (r < 0)
goto finish;
gcry_md_write(md, wire_format_name, r);
for (k = 0; k < n; k++) {
size_t l;
rr = list[k];
r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true);
if (r < 0)
goto finish;
gcry_md_write(md, wire_format_name, r);
md_add_uint16(md, rr->key->type);
md_add_uint16(md, rr->key->class);
md_add_uint32(md, rrsig->rrsig.original_ttl);
assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
l = rr->wire_format_size - rr->wire_format_rdata_offset;
assert(l <= 0xFFFF);
md_add_uint16(md, (uint16_t) l);
gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
}
hash = gcry_md_read(md, 0);
if (!hash) {
r = -EIO;
goto finish;
}
if (*(uint8_t*) dnskey->dnskey.key == 0) {
/* exponent is > 255 bytes long */
exponent = (uint8_t*) dnskey->dnskey.key + 3;
exponent_size =
((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
if (exponent_size < 256) {
r = -EINVAL;
goto finish;
}
if (3 + exponent_size >= dnskey->dnskey.key_size) {
r = -EINVAL;
goto finish;
}
modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
} else {
/* exponent is <= 255 bytes long */
exponent = (uint8_t*) dnskey->dnskey.key + 1;
exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
if (exponent_size <= 0) {
r = -EINVAL;
goto finish;
}
if (1 + exponent_size >= dnskey->dnskey.key_size) {
r = -EINVAL;
goto finish;
}
modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
}
r = dnssec_rsa_verify(
gcry_md_algo_name(gcry_md_get_algo(md)),
rrsig->rrsig.signature, rrsig->rrsig.signature_size,
hash, hash_size,
exponent, exponent_size,
modulus, modulus_size);
if (r < 0)
goto finish;
r = r ? DNSSEC_VERIFIED : DNSSEC_INVALID;
finish:
gcry_md_close(md);
return r;
}
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
assert(rrsig);
assert(dnskey);
/* Checks if the specified DNSKEY RR matches the key used for
* the signature in the specified RRSIG RR */
if (rrsig->key->type != DNS_TYPE_RRSIG)
return -EINVAL;
if (dnskey->key->type != DNS_TYPE_DNSKEY)
return 0;
if (dnskey->key->class != rrsig->key->class)
return 0;
if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
return 0;
if (dnskey->dnskey.protocol != 3)
return 0;
if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
return 0;
if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag)
return 0;
return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(rrsig->key));
}
int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) {
assert(key);
assert(rrsig);
/* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
if (rrsig->key->type != DNS_TYPE_RRSIG)
return 0;
if (rrsig->key->class != key->class)
return 0;
if (rrsig->rrsig.type_covered != key->type)
return 0;
return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
}
int dnssec_verify_rrset_search(
DnsAnswer *a,
DnsResourceKey *key,
DnsAnswer *validated_dnskeys,
usec_t realtime) {
bool found_rrsig = false, found_dnskey = false;
DnsResourceRecord *rrsig;
int r;
assert(key);
/* Verifies all RRs from "a" that match the key "key", against DNSKEY RRs in "validated_dnskeys" */
if (!a || a->n_rrs <= 0)
return -ENODATA;
/* Iterate through each RRSIG RR. */
DNS_ANSWER_FOREACH(rrsig, a) {
DnsResourceRecord *dnskey;
r = dnssec_key_match_rrsig(key, rrsig);
if (r < 0)
return r;
if (r == 0)
continue;
found_rrsig = true;
DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) {
r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
if (r < 0)
return r;
if (r == 0)
continue;
found_dnskey = true;
/* Take the time here, if it isn't set yet, so
* that we do all validations with the same
* time. */
if (realtime == USEC_INFINITY)
realtime = now(CLOCK_REALTIME);
/* Yay, we found a matching RRSIG with a matching
* DNSKEY, awesome. Now let's verify all entries of
* the RRSet against the RRSIG and DNSKEY
* combination. */
r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime);
if (r < 0 && r != EOPNOTSUPP)
return r;
if (r == DNSSEC_VERIFIED)
return DNSSEC_VERIFIED;
/* If the signature is invalid, or done using
an unsupported algorithm, let's try another
key and/or signature. After all they
key_tags and stuff are not unique, and
might be shared by multiple keys. */
}
}
if (found_dnskey)
return DNSSEC_INVALID;
if (found_rrsig)
return DNSSEC_MISSING_KEY;
return DNSSEC_NO_SIGNATURE;
}
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
size_t c = 0;
int r;
/* Converts the specified hostname into DNSSEC canonicalized
* form. */
if (buffer_max < 2)
return -ENOBUFS;
for (;;) {
size_t i;
r = dns_label_unescape(&n, buffer, buffer_max);
if (r < 0)
return r;
if (r == 0)
break;
if (r > 0) {
int k;
/* DNSSEC validation is always done on the ASCII version of the label */
k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
if (k < 0)
return k;
if (k > 0)
r = k;
}
if (buffer_max < (size_t) r + 2)
return -ENOBUFS;
/* The DNSSEC canonical form is not clear on what to
* do with dots appearing in labels, the way DNS-SD
* does it. Refuse it for now. */
if (memchr(buffer, '.', r))
return -EINVAL;
for (i = 0; i < (size_t) r; i ++) {
if (buffer[i] >= 'A' && buffer[i] <= 'Z')
buffer[i] = buffer[i] - 'A' + 'a';
}
buffer[r] = '.';
buffer += r + 1;
c += r + 1;
buffer_max -= r + 1;
}
if (c <= 0) {
/* Not even a single label: this is the root domain name */
assert(buffer_max > 2);
buffer[0] = '.';
buffer[1] = 0;
return 1;
}
return (int) c;
}
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
gcry_md_hd_t md = NULL;
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
void *result;
int r;
assert(dnskey);
assert(ds);
/* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
if (dnskey->key->type != DNS_TYPE_DNSKEY)
return -EINVAL;
if (ds->key->type != DNS_TYPE_DS)
return -EINVAL;
if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
return -EKEYREJECTED;
if (dnskey->dnskey.protocol != 3)
return -EKEYREJECTED;
if (!dnssec_algorithm_supported(dnskey->dnskey.algorithm))
return -EOPNOTSUPP;
if (!dnssec_digest_supported(ds->ds.digest_type))
return -EOPNOTSUPP;
if (dnskey->dnskey.algorithm != ds->ds.algorithm)
return 0;
if (dnssec_keytag(dnskey) != ds->ds.key_tag)
return 0;
switch (ds->ds.digest_type) {
case DNSSEC_DIGEST_SHA1:
if (ds->ds.digest_size != 20)
return 0;
gcry_md_open(&md, GCRY_MD_SHA1, 0);
break;
case DNSSEC_DIGEST_SHA256:
if (ds->ds.digest_size != 32)
return 0;
gcry_md_open(&md, GCRY_MD_SHA256, 0);
break;
default:
assert_not_reached("Unknown digest");
}
if (!md)
return -EIO;
r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
if (r < 0)
goto finish;
gcry_md_write(md, owner_name, r);
md_add_uint16(md, dnskey->dnskey.flags);
md_add_uint8(md, dnskey->dnskey.protocol);
md_add_uint8(md, dnskey->dnskey.algorithm);
gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
result = gcry_md_read(md, 0);
if (!result) {
r = -EIO;
goto finish;
}
r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;
finish:
gcry_md_close(md);
return r;
}
static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
[DNSSEC_NO] = "no",
[DNSSEC_TRUST] = "trust",
[DNSSEC_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);