]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[sedhcpv6] wrote most of handling code (checkpoint)
authorFrancis Dupont <fdupont@isc.org>
Sat, 30 May 2015 07:38:29 +0000 (09:38 +0200)
committerFrancis Dupont <fdupont@isc.org>
Sat, 30 May 2015 07:38:29 +0000 (09:38 +0200)
src/bin/dhcp6/dhcp6_srv.cc
src/lib/cryptolink/cryptolink.cc
src/lib/dhcp/std_option_defs.h
src/lib/dhcpsrv/host.h
src/lib/util/ntp_utils.cc
src/lib/util/ntp_utils.h

index cf067c5f91ab5027d5851ea3f6d8f7ace15bf120..a91a88a4412f5eabd5c46351a05da1af81c7d869 100644 (file)
 #include <hooks/hooks_manager.h>
 #include <util/encode/hex.h>
 #include <util/io_utilities.h>
+#include <util/ntp_utils.h>
 #include <util/range_utilities.h>
 #include <log/logger.h>
 #include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_asym.h>
 #include <cfgrpt/config_report.h>
 
 #ifdef HAVE_MYSQL
@@ -2871,94 +2873,274 @@ void Dhcpv6Srv::processRSOO(const Pkt6Ptr& query, const Pkt6Ptr& rsp) {
 
 bool Dhcpv6Srv::validateSeDhcpOptions(const Pkt6Ptr& query, Pkt6Ptr& answer,
                                       const AllocEngine::ClientContext6 ctx) {
-    // TODO return true if secure DHCPv6 is not enabled
-    if (true) {
+    // Get the secure DHCPv6 global configuration state
+    ConstCfgSeDhcp6Ptr state =
+        CfgMgr::instance().getCurrentCfg()->getCfgSeDhcp6();
+    // If doesn't exit give up
+    if (!state) {
         return (true);
     }
-    bool has_pubkey = false;
-    if (query->getOption(D6O_PUBLIC_KEY)) {
-        has_pubkey = true;
-        if (query->getOptions(D6O_PUBLIC_KEY).size() > 1) {
+
+    // Is check-signatures enabled
+    if (state->getCheckSignatures()) {
+        // Get the public key option
+        bool has_pubkey = false;
+        if (query->getOption(D6O_PUBLIC_KEY)) {
+            has_pubkey = true;
+            if (query->getOptions(D6O_PUBLIC_KEY).size() > 1) {
+                answer->addOption(createStatusCode(STATUS_UnspecFail,
+                            "More than one public key option"));
+                return (false);
+            }
+        }
+        // Get the certificate option
+        bool has_cert = false;
+        if (query->getOption(D6O_CERTIFICATE)) {
+            has_cert = true;
+            if (query->getOptions(D6O_CERTIFICATE).size() > 1) {
+                answer->addOption(createStatusCode(STATUS_UnspecFail,
+                            "More than one certificate option"));
+                return (false);
+            }
+        }
+        // Both must not be presented at the same time
+        if (has_pubkey && has_cert) {
             answer->addOption(createStatusCode(STATUS_UnspecFail,
-                                               "More than one "
-                                               "public key option"));
+                        "Both public key and certificate options"));
             return (false);
         }
-    }
-    bool has_cert = false;
-    if (query->getOption(D6O_CERTIFICATE)) {
-        has_cert = true;
-        if (query->getOptions(D6O_CERTIFICATE).size() > 1) {
+        // Is signature required?
+        bool signopt_required = state->getCheckAuthorizations();
+        string host_credential = "";
+        if (ctx.host_) {
+            host_credential = ctx.host_->getCredential();
+        }
+        if (signopt_required && host_credential.empty()) {
+            answer->addOption(createStatusCode(STATUS_AuthenticationFail,
+                        "No configured credentials"));
+            return (false);
+        }
+        // Get the signature option
+        OptionPtr signopt = query->getOption(D6O_SIGNATURE);
+        if (signopt_required && !signopt) {
             answer->addOption(createStatusCode(STATUS_UnspecFail,
-                                               "More than one "
-                                               "certificate option"));
+                        "No signature option"));
             return (false);
         }
+        // Unsecure
+        if (!signopt_required && !signopt) {
+            return (true);
+        }
+        // signopt is true
+        // Either public key or certificate must available
+        if (!has_pubkey && !has_cert) {
+            answer->addOption(createStatusCode(STATUS_UnspecFail,
+                        "No public key or certificate options"));
+            return (false);
+        }
+        if (query->getOptions(D6O_SIGNATURE).size() > 1) {
+            answer->addOption(createStatusCode(STATUS_UnspecFail,
+                        "More than one signature options"));
+            return (false);
+        }
+        OptionCustomPtr signature =
+            boost::dynamic_pointer_cast<OptionCustom>(signopt);
+        if (!signature) {
+            answer->addOption(createStatusCode(STATUS_UnspecFail,
+                        "Invalid signature option"));
+            return (false);
+        }
+        // Check algorithms
+        uint8_t ha_id = signature->readInteger<uint8_t>(0);
+        if ((ha_id != SHA_256) && (ha_id != SHA_512)) {
+            answer->addOption(createStatusCode(STATUS_AlgorithmNotSupported,
+                        "Unsupported hash algorithm"));
+            return (false);
+        }
+        HashAlgorithm hash_algo = SHA256;
+        if (ha_id == SHA_512) {
+            hash_algo = SHA512;
+        }
+        uint8_t sa_id = signature->readInteger<uint8_t>(1);
+        if (sa_id != RSASSA_PKCS1v1_5) {
+            answer->addOption(createStatusCode(STATUS_AlgorithmNotSupported,
+                        "Unsupported signature algorithm"));
+            return (false);
+        }
+        AsymAlgorithm sign_algo = RSA_;
+        AsymKeyKind key_kind = PUBLIC;
+        if (has_cert) {
+            key_kind = CERT;
+        }
+        // Create the asym crypto object
+        vector<uint8_t> keybin;
+        if (has_pubkey) {
+            keybin = query->getOption(D6O_PUBLIC_KEY)->getData();
+        } else {
+            keybin = query->getOption(D6O_CERTIFICATE)->getData();
+        }
+        CryptoLink& crypto = CryptoLink::getCryptoLink();
+        CfgSeDhcp6::AsymPtr key(crypto.createAsym(keybin,
+                                                  sign_algo,
+                                                  hash_algo,
+                                                  key_kind,
+                                                  ASN1),
+                                deleteAsym);
+        if (!key && has_pubkey) {
+            answer->addOption(createStatusCode(STATUS_UnspecFail,
+                        "Malformed public key option"));
+            return (false);
+        }
+        if (!key) {
+            answer->addOption(createStatusCode(STATUS_UnspecFail,
+                        "Malformed certificate option"));
+            return (false);
+        }
+        // Compare with the credential when it is available
+        if (!host_credential.empty()) {
+            CfgSeDhcp6::AsymPtr cred(crypto.createAsym(host_credential,
+                                                       "",
+                                                       sign_algo,
+                                                       hash_algo,
+                                                       key_kind,
+                                                       ASN1),
+                                     deleteAsym);
+            if (!cred) {
+                answer->addOption(createStatusCode(STATUS_AuthenticationFail,
+                            "Bad configured credentials"));
+                return (false);
+            }
+            if (!cred->compare(key.get(), key_kind)) {
+                answer->addOption(createStatusCode(STATUS_AuthenticationFail,
+                            "Credential mismatch"));
+                return (false);
+            }
+        }
+        // Handle the timestamp option
+        OptionPtr tmstmp_opt;
+        Ntp rd_new;
+        Ntp ts_new;
+        Ntp rd_last;
+        Ntp ts_last;
+        bool update_tmstmp = false;
+        if (state->getCheckTimestamps()) {
+            tmstmp_opt = query->getOption(D6O_TIMESTAMP);
+        }
+        if (tmstmp_opt) {
+            // Get timestamps in NTP format
+            vector<uint8_t> tmstmp_bin = tmstmp_opt->getData();
+            if (!ts_new.from_binary(tmstmp_bin)) {
+                answer->addOption(createStatusCode(STATUS_UnspecFail,
+                            "Malformed timestamp option"));
+                return (false);
+            }
+            rd_new = Ntp(query->getTimestamp());
+            if (ctx.host_) {
+                rd_last = Ntp(ctx.host_->getRDlast());
+                ts_last = Ntp(ctx.host_->getTSlast());
+            }
+            // Verify the given timestamp
+            bool valid = false;
+            if (rd_last.is_zero() || ts_last.is_zero()) {
+                valid = Ntp::verify_new(rd_new, ts_new);
+                if (!valid) {
+                    answer->addOption(createStatusCode(STATUS_TimestampFail,
+                                "New timestamp too far"));
+                    return (false);
+                }
+                if (valid && ctx.host_) {
+                    update_tmstmp = true;
+                }
+            } else {
+                valid = Ntp::verify(rd_new, ts_new, rd_last, ts_last,
+                                    &update_tmstmp);
+                if (!valid) {
+                    answer->addOption(createStatusCode(STATUS_TimestampFail,
+                                "Timestamp out of acceptable range"));
+                    return (false);
+                }
+            }
+        }
+
+        // TODO check signature
+
+        // Update timestamps
+        if (update_tmstmp) {
+            // TODO (ctx.host_ is a const)
+            // ctx.host_->setRDlast(rd_new);
+            // ctx.host_->setTSlast(ts_new);
+        }
     }
-    if (has_pubkey && has_cert) {
-        answer->addOption(createStatusCode(STATUS_UnspecFail,
-                                           "Both public key and "
-                                           "certificate options"));
-        return (false);
-    }
-    if (!has_pubkey && !has_cert) {
-        answer->addOption(createStatusCode(STATUS_UnspecFail,
-                                           "No public key or "
-                                           "certificate options"));
-        return (false);
-    }
-    OptionPtr signopt = query->getOption(D6O_SIGNATURE);
-    if (!signopt) {
-        answer->addOption(createStatusCode(STATUS_UnspecFail,
-                                           "No signature option"));
-        return (false);
-    }
-    if (query->getOptions(D6O_SIGNATURE).size() > 1) {
-        answer->addOption(createStatusCode(STATUS_UnspecFail,
-                                           "More than one signature options"));
-        return (false);
-    }
-    OptionCustomPtr signature =
-        boost::dynamic_pointer_cast<OptionCustom>(signopt);
-    if (!signature) {
-        answer->addOption(createStatusCode(STATUS_UnspecFail,
-                                           "Invalid signature option"));
-        return (false);
-    }
-    uint8_t ha_id = signature->readInteger<uint8_t>(0);
-    if ((ha_id != SHA_256) && (ha_id != SHA_512)) {
-        answer->addOption(createStatusCode(STATUS_AlgorithmNotSupported,
-                                           "Unsupported hash algorithm"));
-        return (false);
-    }
-    uint8_t sa_id = signature->readInteger<uint8_t>(1);
-    if (sa_id != RSASSA_PKCS1v1_5) {
-        answer->addOption(createStatusCode(STATUS_AlgorithmNotSupported,
-                                           "Unsupported signature algorithm"));
-        return (false);
-    }
-    if (!ctx.host_ || ctx.host_->getCredential().empty()) {
-        answer->addOption(createStatusCode(STATUS_AuthenticationFail,
-                                           "No configured credentials"));
-        return (false);
-    }
-    // TODO map ha_id and sa_id, get public key or certificate, compare
-    // TODO... timestamp
-    // TODO check signature
+
+    // Done
     return (true);
 }
 
-void Dhcpv6Srv::appendSeDhcpOptions(Pkt6Ptr& /*answer*/) {
-    // TODO return if secure DHCPv6 is not enabled
-    // TODO add public key or certificate
-    // TODO add signature
-    // TODO... add timestamp
+void Dhcpv6Srv::appendSeDhcpOptions(Pkt6Ptr& answer) {
+    // Get the secure DHCPv6 global configuration state
+    ConstCfgSeDhcp6Ptr state =
+        CfgMgr::instance().getCurrentCfg()->getCfgSeDhcp6();
+    // If doesn't exit give up
+    if (!state) {
+        return;
+    }
+
+    CfgSeDhcp6::AsymPtr key = state->getPrivateKey();
+    CfgSeDhcp6::AsymPtr cred = state->getCredential();
+    if (state->getSignAnswers() && key && cred) {
+        // Add the credential (public key or certificate) option
+        uint16_t cred_type = D6O_PUBLIC_KEY;
+        if (cred->getAsymKeyKind() == CERT) {
+            cred_type = D6O_CERTIFICATE;
+        }
+        OptionBuffer buf = cred->exportkey(cred->getAsymKeyKind(), ASN1);
+        OptionPtr cred_opt(new Option(Option::V6, cred_type, buf));
+        answer->addOption(cred_opt);
+
+        // Add the signature option
+        uint8_t ha_id = SHA_256;
+        if (key->getHashAlgorithm() == SHA512) {
+            ha_id = SHA_512;
+        }
+        uint8_t sa_id = RSASSA_PKCS1v1_5;
+        size_t sig_len = (key->getKeySize() + 7) / 8;
+        OptionBuffer sig(sig_len + 2);
+        sig[0] = ha_id;
+        sig[1] = sa_id;
+        OptionDefinitionPtr sig_def =
+            LibDHCP::getOptionDef(Option::V6, D6O_SIGNATURE);
+        assert(sig_def);
+        OptionCustomPtr sig_opt(new OptionCustom(*sig_def, Option::V6, sig));
+        answer->addOption(sig_opt);
+    }
+
+    // Add timestamps
+    if (state->getTimestampAnswers()) {
+        struct timeval now;
+        gettimeofday(&now, NULL);
+        const Ntp val(&now);
+        const OptionBuffer buf = val.to_binary();
+        OptionPtr tmsmtp_opt(new Option(Option::V6, D6O_TIMESTAMP, buf));
+        answer->addOption(tmsmtp_opt);
+    }
 }
 
 void Dhcpv6Srv::finalizeSignature(Pkt6Ptr& tbs) {
-    // TODO (sanity) throw if secure DHCPv6 is not enabled
+    // Get the secure DHCPv6 global configuration state
+    ConstCfgSeDhcp6Ptr state =
+        CfgMgr::instance().getCurrentCfg()->getCfgSeDhcp6();
+    if (!state) {
+        isc_throw(Unexpected, "no secure DHCPv6 configuration state");
+    }
+    if (!state->getSignAnswers()) {
+        isc_throw(Unexpected, "Signing answers is disabled");
+    }
+    CfgSeDhcp6::AsymPtr key = state->getPrivateKey();
+    if (!key) {
+        isc_throw(Unexpected, "No private key configured");
+    }
     if (!tbs->getSignatureOffset()) {
-        isc_throw(isc::Unexpected, "null signature offset");
+        isc_throw(Unexpected, "null signature offset");
     }
     // TODO
 }
index de6b464a6fe9e49f2de65b7a770ce6cb38ede7c4..ab385fa7568f9b45549ff2df560b710b4dc5d4c5 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2011, 2014  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2014, 2015  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -59,6 +59,17 @@ CryptoLink::createAsym(const void* key, size_t key_len,
                      key_kind, key_format));
 }
 
+Asym*
+CryptoLink::createAsym(const std::vector<uint8_t> key,
+                       const AsymAlgorithm asym_algorithm,
+                       const HashAlgorithm hash_algorithm,
+                       const AsymKeyKind key_kind,
+                       const AsymFormat key_format)
+{
+    return (new Asym(key, asym_algorithm, hash_algorithm,
+                     key_kind, key_format));
+}
+
 Asym*
 CryptoLink::createAsym(const std::string& filename,
                        const std::string& password,
index 284038eca2ea7839a95b4ea6cbdd0f1e6e782947..e70d1b9653636598067513dece14f71a3482d703 100644 (file)
@@ -240,8 +240,6 @@ RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
 // sedhcpv6 signature
 RECORD_DECL(SIGNATURE_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
             OPT_BINARY_TYPE);
-// sedhcpv6 timestamp (should be uint64)
-RECORD_DECL(TIMESTAMP_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
 
 /// Standard DHCPv6 option definitions.
 ///
@@ -343,8 +341,8 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = {
       NO_RECORD_DEF, "" },
     { "signature", D6O_SIGNATURE, OPT_RECORD_TYPE, false,
       RECORD_DEF(SIGNATURE_RECORDS), "" },
-    { "timestamp", D6O_TIMESTAMP, OPT_RECORD_TYPE, false,
-      RECORD_DEF(TIMESTAMP_RECORDS), "" }
+    { "timestamp", D6O_TIMESTAMP, OPT_BINARY_TYPE, false,
+      NO_RECORD_DEF, "" }
 
     // @todo There is still a bunch of options for which we have to provide
     // definitions but we don't do it because they are not really
index 74d2a740e04048fc6d7169533e8bc5be1c4a864d..9341228e50ecae0887b1d2b5ff34aaab83ca7717 100644 (file)
@@ -20,6 +20,7 @@
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/subnet_id.h>
+#include <util/ntp_utils.h>
 #include <boost/shared_ptr.hpp>
 #include <list>
 #include <map>
@@ -432,6 +433,30 @@ public:
         return (credential_);
     }
 
+    /// @brief Sets the date of the last received and accepted packet
+    ///
+    /// @param timestamp Date in NTP format
+    void setRDlast(const isc::util::Ntp& timestamp) {
+       rd_last_ = timestamp;
+    }
+
+    /// @brief Returns RDlast
+    const isc::util::Ntp& getRDlast() const {
+       return (rd_last_);
+    }
+
+    /// @brief Sets the date of the last received and accepted timestamp
+    ///
+    /// @param timestamp Date in NTP format
+    void setTSlast(const isc::util::Ntp& timestamp) {
+       ts_last_ = timestamp;
+    }
+
+    /// @brief Returns TSlast
+    const isc::util::Ntp& getTSlast() const {
+       return (ts_last_);
+    }
+
     /// @brief Returns information about the host in the textual format.
     std::string toText() const;
 
@@ -472,6 +497,10 @@ private:
     ClientClasses dhcp6_client_classes_;
     /// @brief Credential (filename of public key or certificate)
     std::string credential_;
+    /// @brief Date of the last received and accepted packet
+    isc::util::Ntp rd_last_;
+    /// @brief Date of the last received and accepted timestamp
+    isc::util::Ntp ts_last_;
 };
 
 /// @brief Pointer to the @c Host object.
index fe733297268ae30895aee4411a535152a9de0d0e..17434b0e5b3f3a3f0f2b777b313769df27ae3d11 100644 (file)
@@ -15,6 +15,9 @@
 #include <cmath>
 #include <util/ntp_utils.h>
 
+using namespace boost::posix_time;
+using namespace boost::gregorian;
+
 namespace isc {
 namespace util {
 
@@ -33,6 +36,10 @@ const double FUZZ = 1.;
 const double DRIFT = .01;
 }
 
+bool Ntp::is_zero() const {
+    return (ntp_sec_ == 0);
+}
+
 Ntp::Ntp() : ntp_sec_(0), ntp_fraction_(0)
 {
 }
@@ -42,10 +49,20 @@ Ntp::Ntp(uint64_t sec, uint16_t fraction)
 {
 }
 
-Ntp::Ntp(struct timeval tv)
+Ntp::Ntp(const struct timeval* tv)
+{
+    ntp_sec_ = static_cast<uint32_t>(tv->tv_sec) + EPOCH_ADJUST;
+    uint32_t fcvt = (tv->tv_usec * 65536U) / 1000000UL;
+    ntp_fraction_ = static_cast<uint16_t>(fcvt & 0xffff);
+}
+
+Ntp::Ntp(const ptime pt)
 {
-    ntp_sec_ = static_cast<uint32_t>(tv.tv_sec) + EPOCH_ADJUST;
-    uint32_t fcvt = (tv.tv_usec * 65536U) / 1000000UL;
+    ptime epoch(date(1900, Jan, 1));
+    time_duration dur(pt - epoch);
+    ntp_sec_ = static_cast<uint64_t>(dur.total_seconds());
+    uint64_t fcvt = dur.fractional_seconds() * 65536U;
+    fcvt /= time_duration::ticks_per_second();
     ntp_fraction_ = static_cast<uint16_t>(fcvt & 0xffff);
 }
 
@@ -57,7 +74,7 @@ Ntp::Ntp(double secs, time_t base)
     ntp_fraction_ = static_cast<uint16_t>(floor(fracpart * 65536.));
 }
 
-bool Ntp::from_binary(std::vector<uint8_t> binary)
+bool Ntp::from_binary(const std::vector<uint8_t> binary)
 {
     if (binary.size() != 8) {
         return (false);
@@ -94,8 +111,9 @@ double Ntp::secs(time_t base) const
     return (ret);
 }
 
-bool Ntp::verify_new(const Ntp& rd_new, const Ntp& ts_new, time_t base)
+bool Ntp::verify_new(const Ntp& rd_new, const Ntp& ts_new)
 {
+    time_t base = time(NULL);
     double drd_new = rd_new.secs(base);
     double dts_new = ts_new.secs(base);
     if (drd_new >= dts_new) {
@@ -114,9 +132,9 @@ bool Ntp::verify_new(const Ntp& rd_new, const Ntp& ts_new, time_t base)
 }
 
 bool Ntp::verify(const Ntp& rd_new, const Ntp& ts_new,
-                 const Ntp& rd_last, const Ntp& ts_last,
-                 time_t base, bool* to_update)
+                 const Ntp& rd_last, const Ntp& ts_last, bool* to_update)
 {
+    time_t base = time(NULL);
     double drd_new = rd_new.secs(base);
     double drd_last = rd_last.secs(base);
     if (drd_new < drd_last) {
index bc2fcfcf983a06d91317d0fbaa6f1514c612dbe3..f083e7132c5a81adf43dee11f30a906c1aefdc43 100644 (file)
@@ -21,6 +21,8 @@
 #include <stdint.h>
 #include <sys/time.h>
 
+#include <boost/date_time/posix_time/posix_time.hpp>
+
 namespace isc {
 namespace util {
 
@@ -36,6 +38,9 @@ struct Ntp {
     // \brief 1/65536th of seconds
     uint16_t ntp_fraction_;
 
+    // \brief Test if the object got a value
+    bool is_zero() const;
+
     // \brief Default constructor
     Ntp();
 
@@ -43,13 +48,16 @@ struct Ntp {
     Ntp(uint64_t sec, uint16_t fraction);
 
     // \brief Conversion from timeval
-    Ntp(struct timeval tv);
+    Ntp(const struct timeval* tv);
+
+    // \brief Conversion from boost posix_time
+    Ntp(const boost::posix_time::ptime pt);
 
     // \brief Conversion from based double
     Ntp(double secs, time_t base);
 
     // \brief Conversion from network
-    bool from_binary(std::vector<uint8_t> binary);
+    bool from_binary(const std::vector<uint8_t> binary);
 
     // \brief Conversion to network
     std::vector<uint8_t> to_binary() const;
@@ -58,12 +66,12 @@ struct Ntp {
     double secs(time_t base) const;
 
     // \brief Verify a timestamp without cache
-    static bool verify_new(const Ntp& rd_new, const Ntp& ts_new, time_t base);
+    static bool verify_new(const Ntp& rd_new, const Ntp& ts_new);
 
     // \brief Verify a timestamp with cache
     static bool verify(const Ntp& rd_new, const Ntp& ts_new,
                        const Ntp& rd_last, const Ntp& ts_last,
-                       time_t base, bool* to_update);
+                       bool* to_update);
 };
 
 } // end of isc::util namespace