resolved-dns-dnssec.c revision 2cd8727718632ed80fa3871fddf71506c548d770
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek/***
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek This file is part of systemd.
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek Copyright 2015 Lennart Poettering
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek systemd is free software; you can redistribute it and/or modify it
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek under the terms of the GNU Lesser General Public License as published by
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek the Free Software Foundation; either version 2.1 of the License, or
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek (at your option) any later version.
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek systemd is distributed in the hope that it will be useful, but
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek WITHOUT ANY WARRANTY; without even the implied warranty of
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek Lesser General Public License for more details.
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek You should have received a copy of the GNU Lesser General Public License
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek along with systemd; If not, see <http://www.gnu.org/licenses/>.
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek***/
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering#include <gcrypt.h>
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "alloc-util.h"
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers#include "dns-domain.h"
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering#include "resolved-dns-dnssec.h"
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek#include "resolved-dns-packet.h"
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek#include "string-table.h"
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers/* Open question:
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers *
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * How does the DNSSEC canonical form of a hostname with a label
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * containing a dot look like, the way DNS-SD does it?
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers *
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * TODO:
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers *
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - Iterative validation
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - NSEC proof of non-existance
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - NSEC3 proof of non-existance
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - Trust anchor store
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - wildcard zones
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - multi-label zones
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - cname support
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - DNSSEC bus API extensions
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * - global + per-interface DNSSEC setting
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * - DSA support
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * - EC support?
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek *
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * */
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek#define VERIFY_RRS_MAX 256
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek#define MAX_KEY_SIZE (32*1024)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek/*
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * The DNSSEC Chain of trust:
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek *
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * DS RRs are protected like normal RRs
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek *
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * Example chain:
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek */
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmekstatic bool dnssec_algorithm_supported(int algorithm) {
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek return IN_SET(algorithm,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek DNSSEC_ALGORITHM_RSASHA1,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek DNSSEC_ALGORITHM_RSASHA256,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek DNSSEC_ALGORITHM_RSASHA512);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek}
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmekstatic bool dnssec_digest_supported(int digest) {
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek return IN_SET(digest,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek DNSSEC_DIGEST_SHA1,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek DNSSEC_DIGEST_SHA256);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek}
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmekuint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek const uint8_t *p;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek uint32_t sum;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek size_t i;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek /* The algorithm from RFC 4034, Appendix B. */
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek assert(dnskey);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek assert(dnskey->key->type == DNS_TYPE_DNSKEY);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek sum = (uint32_t) dnskey->dnskey.flags +
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek p = dnskey->dnskey.key;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek for (i = 0; i < dnskey->dnskey.key_size; i++)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek sum += (sum >> 16) & UINT32_C(0xFFFF);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek return sum & UINT32_C(0xFFFF);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek}
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmekstatic int rr_compare(const void *a, const void *b) {
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek size_t m;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek int r;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek /* Let's order the RRs according to RFC 4034, Section 6.3 */
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek assert(x);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek assert(*x);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek assert((*x)->wire_format);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek assert(y);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek assert(*y);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek assert((*y)->wire_format);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek r = memcmp((*x)->wire_format, (*y)->wire_format, m);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek if (r != 0)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek return r;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek if ((*x)->wire_format_size < (*y)->wire_format_size)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek return -1;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek else if ((*x)->wire_format_size > (*y)->wire_format_size)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek return 1;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek return 0;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek}
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmekstatic int dnssec_rsa_verify(
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek const char *hash_algorithm,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek const void *signature, size_t signature_size,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek const void *data, size_t data_size,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek const void *exponent, size_t exponent_size,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek const void *modulus, size_t modulus_size) {
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek gcry_mpi_t n = NULL, e = NULL, s = NULL;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek gcry_error_t ge;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek int r;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek assert(hash_algorithm);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak if (ge != 0) {
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak r = -EIO;
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak goto finish;
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak }
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak if (ge != 0) {
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak r = -EIO;
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak goto finish;
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak }
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak if (ge != 0) {
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak r = -EIO;
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak goto finish;
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak }
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak ge = gcry_sexp_build(&signature_sexp,
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak NULL,
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak "(sig-val (rsa (s %m)))",
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak s);
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak if (ge != 0) {
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak r = -EIO;
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak goto finish;
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak }
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak ge = gcry_sexp_build(&data_sexp,
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak NULL,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek "(data (flags pkcs1) (hash %s %b))",
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek hash_algorithm,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek (int) data_size,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek data);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek if (ge != 0) {
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek r = -EIO;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek goto finish;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek }
a75f4e2a02e287294b21ae9e5b1f28b2f8faea39Zbigniew Jędrzejewski-Szmek
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek ge = gcry_sexp_build(&public_key_sexp,
a75f4e2a02e287294b21ae9e5b1f28b2f8faea39Zbigniew Jędrzejewski-Szmek NULL,
a75f4e2a02e287294b21ae9e5b1f28b2f8faea39Zbigniew Jędrzejewski-Szmek "(public-key (rsa (n %m) (e %m)))",
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek n,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek e);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek if (ge != 0) {
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek r = -EIO;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek goto finish;
c5e04d51277994cca29234b33a6b8fc90a183cf3Zbigniew Jędrzejewski-Szmek }
c5e04d51277994cca29234b33a6b8fc90a183cf3Zbigniew Jędrzejewski-Szmek
c5e04d51277994cca29234b33a6b8fc90a183cf3Zbigniew Jędrzejewski-Szmek ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
a75f4e2a02e287294b21ae9e5b1f28b2f8faea39Zbigniew Jędrzejewski-Szmek if (ge == GPG_ERR_BAD_SIGNATURE)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek r = 0;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek else if (ge != 0)
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering r = -EIO;
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering else
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering r = 1;
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poetteringfinish:
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering if (e)
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering gcry_mpi_release(e);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering if (n)
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering gcry_mpi_release(n);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering if (s)
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering gcry_mpi_release(s);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering if (public_key_sexp)
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering gcry_sexp_release(public_key_sexp);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering if (signature_sexp)
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering gcry_sexp_release(signature_sexp);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering if (data_sexp)
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering gcry_sexp_release(data_sexp);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering return r;
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering}
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poetteringstatic void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering gcry_md_write(md, &v, sizeof(v));
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering}
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poetteringstatic void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering v = htobe16(v);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering gcry_md_write(md, &v, sizeof(v));
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering}
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poetteringstatic void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering v = htobe32(v);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering gcry_md_write(md, &v, sizeof(v));
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering}
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poetteringstatic int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering usec_t expiration, inception, skew;
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering assert(rrsig);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering if (realtime == USEC_INFINITY)
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering realtime = now(CLOCK_REALTIME);
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering inception = rrsig->rrsig.inception * USEC_PER_SEC;
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering if (inception > expiration)
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering return -EKEYREJECTED;
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering /* Permit a certain amount of clock skew of 10% of the valid
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering * time range. This takes inspiration from unbound's
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering * resolver. */
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering skew = (expiration - inception) / 10;
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering if (skew > SKEW_MAX)
6550203eb471595e41e27f46e5d0a00a4c0e47bbLennart Poettering skew = SKEW_MAX;
if (inception < skew)
inception = 0;
else
inception -= skew;
if (expiration + skew < expiration)
expiration = USEC_INFINITY;
else
expiration += skew;
return realtime < inception || realtime > expiration;
}
int dnssec_verify_rrset(
DnsAnswer *a,
DnsResourceKey *key,
DnsResourceRecord *rrsig,
DnsResourceRecord *dnskey,
usec_t realtime) {
uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
size_t exponent_size, modulus_size, hash_size;
void *exponent, *modulus, *hash;
DnsResourceRecord **list, *rr;
gcry_md_hd_t md = NULL;
size_t k, n = 0;
int r;
assert(key);
assert(rrsig);
assert(dnskey);
assert(rrsig->key->type == DNS_TYPE_RRSIG);
assert(dnskey->key->type == DNS_TYPE_DNSKEY);
/* Verifies the the RRSet matching the specified "key" in "a",
* using the signature "rrsig" and the key "dnskey". It's
* assumed the RRSIG and DNSKEY match. */
if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm))
return -EOPNOTSUPP;
if (a->n_rrs > VERIFY_RRS_MAX)
return -E2BIG;
r = dnssec_rrsig_expired(rrsig, realtime);
if (r < 0)
return r;
if (r > 0)
return DNSSEC_SIGNATURE_EXPIRED;
/* Collect all relevant RRs in a single array, so that we can look at the RRset */
list = newa(DnsResourceRecord *, a->n_rrs);
DNS_ANSWER_FOREACH(rr, a) {
r = dns_resource_key_equal(key, rr->key);
if (r < 0)
return r;
if (r == 0)
continue;
/* We need the wire format for ordering, and digest calculation */
r = dns_resource_record_to_wire_format(rr, true);
if (r < 0)
return r;
list[n++] = rr;
}
if (n <= 0)
return -ENODATA;
/* 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) {
_cleanup_free_ char *s = NULL;
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);