servlib/security/Ciphersuite_BA1.cc
changeset 0 2b3e5ec03512
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlib/security/Ciphersuite_BA1.cc	Thu Apr 21 14:57:45 2011 +0100
@@ -0,0 +1,607 @@
+/*
+ *    Copyright 2006 SPARTA Inc
+ * 
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ * 
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <dtn-config.h>
+#endif
+
+#ifdef BSP_ENABLED
+
+#include "Ciphersuite_BA1.h"
+#include "bundling/Bundle.h"
+#include "bundling/BundleDaemon.h"
+#include "bundling/BundleProtocol.h"
+#include "bundling/SDNV.h"
+#include "contacts/Link.h"
+#include "KeyDB.h"
+#include "BP_Tag.h"
+#include "openssl/hmac.h"
+
+namespace dtn {
+
+static const char* log = "/dtn/bundle/ciphersuite";
+
+//----------------------------------------------------------------------
+Ciphersuite_BA1::Ciphersuite_BA1()
+{
+}
+
+//----------------------------------------------------------------------
+u_int16_t
+Ciphersuite_BA1::cs_num(void)
+{
+    return CSNUM_BA1;
+}
+
+//----------------------------------------------------------------------
+int
+Ciphersuite_BA1::consume(Bundle* bundle, BlockInfo* block,
+                         u_char* buf, size_t len)
+{
+    log_debug_p(log, "Ciphersuite_BA1::consume()");
+    int cc = block->owner()->consume(bundle, block, buf, len);
+
+    if (cc == -1) {
+        return -1; // protocol error
+    }
+    
+    
+    // in on-the-fly scenario, process this data for those interested
+    
+    if (! block->complete()) {
+        ASSERT(cc == (int)len);
+        return cc;
+    }
+
+    if ( block->locals() == NULL ) {      // then we need to parse it
+        parse(block);
+    }
+    
+    return cc;
+}
+
+//----------------------------------------------------------------------
+bool
+Ciphersuite_BA1::validate(const Bundle*           bundle,
+                          BlockInfoVec*           block_list,
+                          BlockInfo*              block,
+                          status_report_reason_t* reception_reason,
+                          status_report_reason_t* deletion_reason)
+{
+    (void)block_list;
+    size_t          offset;
+    size_t          len;
+    size_t          rem;
+    HMAC_CTX        ctx;
+    OpaqueContext*   r = reinterpret_cast<OpaqueContext*>(&ctx);
+    const BlockInfoVec& recv_blocks = bundle->recv_blocks();
+    u_char          result[EVP_MAX_MD_SIZE];
+    u_int32_t       rlen = 0;
+    BP_Local_CS*    locals = NULL;
+    u_char*         buf;
+    u_int64_t       cs_flags;
+    u_int64_t       suite_num;
+    u_int64_t       field_length           = 0LL;
+    int             sdnv_len = 0;        // use an int to handle -1 return values
+    (void)reception_reason;
+    
+    log_debug_p(log, "Ciphersuite_BA1::validate()");
+    // if first block
+    locals = dynamic_cast<BP_Local_CS*>(block->locals());
+    CS_FAIL_IF_NULL(locals);
+    if ( !(locals->cs_flags() & Ciphersuite::CS_BLOCK_HAS_RESULT) ) {            
+        const KeyDB::Entry* key_entry =
+            KeyDB::find_key(EndpointID(locals->security_src()).uri().host().c_str(), cs_num());
+        if (key_entry == NULL) {
+            log_warn_p(log, "unable to find verification key for this block");
+            goto fail;
+        }
+        ASSERT(key_entry->key_len() == res_len);
+        
+        // dump key_entry to debugging output
+//         oasys::StringBuffer ksbuf;
+//         key_entry->dump(&ksbuf);
+//         log_debug_p(log, "Ciphersuite_BA1::validate(): using key entry:\n%s",
+//                     ksbuf.c_str());
+        
+        // prepare the digest context in "result"
+        HMAC_CTX_init(&ctx);
+        HMAC_Init_ex(&ctx, key_entry->key(), key_entry->key_len(),
+                     EVP_sha1(), NULL);
+        
+        // walk the list and process each of the blocks
+        for ( BlockInfoVec::const_iterator iter = recv_blocks.begin();
+              iter != recv_blocks.end();
+              ++iter)
+        {
+            offset = 0;
+            len = iter->full_length();
+            
+            if ( iter->type() == BundleProtocol::BUNDLE_AUTHENTICATION_BLOCK ) {
+                // This is a BA block but might or might not be BA1.
+                // So we need to see if there is a security-result field
+                // which needs exclusion
+                
+                // ciphersuite number and flags
+                u_char* ptr = iter->data();
+                rem = iter->full_length();
+
+                sdnv_len = SDNV::decode(ptr,
+                                        rem,
+                                        &suite_num);
+                ptr += sdnv_len;
+                rem -= sdnv_len;
+
+                sdnv_len = SDNV::decode(ptr,
+                                        rem,
+                                        &cs_flags);
+                ptr += sdnv_len;
+                rem -= sdnv_len;
+
+                if ( cs_flags & CS_BLOCK_HAS_RESULT ) {
+                    // if there's a security-result we have to ease up to it
+                    
+                    sdnv_len =  SDNV::len(ptr);        //step over correlator
+                    ptr += sdnv_len;
+                    rem -= sdnv_len;
+                    
+                    sdnv_len =  SDNV::len(ptr);        //step over security-result-length field
+                    ptr += sdnv_len;
+                    rem -= sdnv_len;
+                    
+                    len = ptr - iter->contents().buf();  //this is the length to use
+                }
+            }
+            
+            iter->owner()->process( Ciphersuite_BA1::digest,
+                                    bundle,
+                                    block,
+                                    &*iter,
+                                    offset,
+                                    len,
+                                    r);
+        }
+        
+        // finalize the digest
+        HMAC_Final(&ctx, result, &rlen);
+        HMAC_cleanup(&ctx);
+        ASSERT(rlen == Ciphersuite_BA1::res_len);
+        
+        // check the digest in the result - in the *second* block
+        // walk the list to find it
+        for (BlockInfoVec::iterator iter = block_list->begin();
+             iter != block_list->end();
+             ++iter)
+        {
+            BP_Local_CS* target_locals;
+            if ( iter->type() != BundleProtocol::BUNDLE_AUTHENTICATION_BLOCK )
+                continue;
+            
+            target_locals = dynamic_cast<BP_Local_CS*>(iter->locals());
+            CS_FAIL_IF_NULL(target_locals);
+            if ( target_locals->owner_cs_num() != CSNUM_BA1 )
+                continue;
+            
+            if (target_locals->correlator() != locals->correlator() )
+                continue;
+            
+            // Now we're at the block which is ...
+            //   1. BA block
+            //   2. BA1 ciphersuite
+            //   3. same correlator as the main one        
+            
+            if ( target_locals->cs_flags() & Ciphersuite::CS_BLOCK_HAS_RESULT ) {
+                buf = target_locals->security_result().buf();
+                len = target_locals->security_result().len();
+                
+                // we expect only one item in the field, the BA signature
+                if ( *buf++ != Ciphersuite::CS_signature_field ) {        // item type
+                    log_err_p(log, "Ciphersuite_BA1 item type incorrect");
+                    goto fail;                //field type is bad
+                }
+                len--;
+                
+                sdnv_len = SDNV::decode(buf, len, &field_length);        // item length
+                buf += sdnv_len;
+                len -= sdnv_len;
+                ASSERT(field_length == Ciphersuite_BA1::res_len);
+                ASSERT(         len == Ciphersuite_BA1::res_len);
+                
+                if ( memcmp(buf, result, Ciphersuite_BA1::res_len) != 0) {
+                    log_err_p(log, "block failed security validation Ciphersuite_BA1");
+                    goto fail;
+                } else {
+                    log_debug_p(log, "block passed security validation Ciphersuite_BA1");
+                    locals->set_proc_flag(CS_BLOCK_PASSED_VALIDATION);
+                    return true;
+                }
+            }
+            else 
+            {
+                continue;
+            }
+        }
+        log_err_p(log, "block failed security validation Ciphersuite_BA1 - result is missing");
+        goto fail;
+    }
+    else    
+    {
+        //  do NOT set a proc_flag here, for this block as it's not the owner of the correlated set
+        log_debug_p(log, "BA1BlockProcessor::validate(): no check on this block");
+    }
+
+    return true;
+
+ fail:
+    locals->set_proc_flag(CS_BLOCK_FAILED_VALIDATION | CS_BLOCK_COMPLETED_DO_NOT_FORWARD);
+    *deletion_reason = BundleProtocol::REASON_SECURITY_FAILED;
+    return false;
+}
+
+//----------------------------------------------------------------------
+int
+Ciphersuite_BA1::prepare(const Bundle*    bundle,
+                         BlockInfoVec*    xmit_blocks,
+                         const BlockInfo* source,
+                         const LinkRef&   link,
+                         list_owner_t     list)
+{
+    (void)bundle;
+    (void)link;
+
+    int             result = BP_FAIL;
+    u_int64_t       correlator = CSNUM_BA1 << 16;     //also need to add a low-order piece
+    u_int16_t       flags = CS_BLOCK_HAS_CORRELATOR;
+    BP_Local_CS*    locals = NULL;
+
+    log_debug_p(log, "Ciphersuite_BA1::prepare()");
+    if ( list == BlockInfo::LIST_RECEIVED )
+        return BP_SUCCESS;   //don't forward received BA blocks
+        
+    // Need to add two blocks, one at the start, one after payload
+    // It's simpler to fill in the pieces and then insert them.
+    BlockInfo       bi = BlockInfo(BundleProtocol::find_processor(BundleProtocol::BUNDLE_AUTHENTICATION_BLOCK), source);
+
+    // initialize the first block
+    BundleDaemon* bd = BundleDaemon::instance();
+    bi.set_locals(new BP_Local_CS);
+    locals = dynamic_cast<BP_Local_CS*>(bi.locals());
+    CS_FAIL_IF_NULL(locals);
+    locals->set_owner_cs_num(CSNUM_BA1);
+    locals->set_cs_flags(flags | CS_BLOCK_HAS_SOURCE);
+    locals->set_security_src(bd->local_eid().str());
+    correlator = create_correlator(bundle, xmit_blocks);
+    correlator |= (int)CSNUM_BA1 << 16;      // add our ciphersuite number
+    locals->set_correlator( correlator );
+    locals->set_correlator_sequence( 0 );
+    
+    
+    // We should already have the primary block in the list.
+    // If primary is there then insert after it.
+    // If not, insert first in the list.
+    // If list is empty then just add to back
+    //   -- this will be troublesome later but we have no choice
+    if ( xmit_blocks->size() > 0 ) {
+        BlockInfoVec::iterator iter = xmit_blocks->begin();
+        if ( iter->type() == BundleProtocol::PRIMARY_BLOCK)
+            ++iter;
+        xmit_blocks->insert(iter, bi);
+    } else {
+        xmit_blocks->push_back(bi);
+    }
+    
+    // initialize the second (trailing) block
+    bi.set_locals(new BP_Local_CS);
+    locals = dynamic_cast<BP_Local_CS*>(bi.locals());
+    CS_FAIL_IF_NULL(locals);
+    locals->set_owner_cs_num(CSNUM_BA1);
+    locals->set_cs_flags(flags | CS_BLOCK_HAS_RESULT);
+    locals->set_correlator( correlator );       // same one created above, obviously
+    locals->set_correlator_sequence( 1 );
+    xmit_blocks->push_back(bi);
+
+    result = BP_SUCCESS;
+    return result;
+    
+ fail:
+    if ( locals !=  NULL )
+        locals->set_proc_flag(CS_BLOCK_PROCESSING_FAILED_DO_NOT_SEND);
+    return BP_FAIL;
+}
+
+//----------------------------------------------------------------------
+int
+Ciphersuite_BA1::generate(const Bundle*  bundle,
+                          BlockInfoVec*  xmit_blocks,
+                          BlockInfo*     block,
+                          const LinkRef& link,
+                          bool           last)
+{
+    (void)bundle;
+    (void)link;
+    (void)xmit_blocks;
+
+    log_debug_p(log, "Ciphersuite_BA1::generate()");
+    int             result = BP_FAIL;
+    BP_Local_CS*    locals = dynamic_cast<BP_Local_CS*>(block->locals());
+    u_int16_t       flags = locals->cs_flags();
+    size_t          item_len = 0;
+    u_char*         buf = NULL;
+    int             len = 0;
+    size_t          length = 0;    
+    int             sdnv_len = 0;        // use an int to handle -1 return values
+    BlockInfo::DataBuffer* contents = NULL;
+            
+    CS_FAIL_IF_NULL(locals);
+    // add security-source to EID-list
+    if ( flags & CS_BLOCK_HAS_SOURCE ) {
+        block->add_eid(locals->security_src());
+        /* xmit_blocks->dict()->add_eid() is done for us in
+         * generate_preamble() below */
+    }
+    
+    length = 0;         // ciphersuite number and flags
+    length += SDNV::encoding_len(CSNUM_BA1);
+    length += SDNV::encoding_len(locals->cs_flags());
+    length += SDNV::encoding_len(locals->correlator());  
+    
+    if (flags & CS_BLOCK_HAS_RESULT) {      
+        item_len = 1 + 1 + Ciphersuite_BA1::res_len; // type + length + result item
+        length += SDNV::encoding_len(item_len) + item_len;
+    }
+    
+    generate_preamble(xmit_blocks, 
+                      block,
+                      BundleProtocol::BUNDLE_AUTHENTICATION_BLOCK,
+                      BundleProtocol::BLOCK_FLAG_DISCARD_BUNDLE_ONERROR |
+                      (last ? BundleProtocol::BLOCK_FLAG_LAST_BLOCK : 0),
+                      length);
+
+    contents = block->writable_contents();
+    contents->reserve(block->data_offset() + length);
+    contents->set_len(block->data_offset() + length);
+
+    buf = contents->buf() + block->data_offset();
+    len = length;
+    
+    // ciphersuite number and flags
+    sdnv_len = SDNV::encode(CSNUM_BA1, buf, len);
+    CS_FAIL_IF(sdnv_len <= 0);
+    buf += sdnv_len;
+    len -= sdnv_len;
+    
+    sdnv_len = SDNV::encode(locals->cs_flags(), buf, len);
+    CS_FAIL_IF(sdnv_len <= 0);
+    buf += sdnv_len;
+    len -= sdnv_len;
+            
+    // correlator
+    sdnv_len = SDNV::encode(locals->correlator(), buf, len);
+    CS_FAIL_IF(sdnv_len <= 0);
+    buf += sdnv_len;
+    len -= sdnv_len;
+    
+    if (flags & CS_BLOCK_HAS_RESULT) {      
+        // security-result offset
+        size_t result_offset = buf - block->data();
+        locals->set_security_result_offset(result_offset);
+        
+        // security-result length
+        sdnv_len = SDNV::encode(item_len, buf, len);
+        CS_FAIL_IF(sdnv_len <= 0);
+        buf += sdnv_len;
+        len -= sdnv_len;
+    }
+    CS_FAIL_IF(len != (int)item_len);
+        
+    result = BP_SUCCESS;
+    return result;
+
+ fail:
+    if ( locals !=  NULL )
+        locals->set_proc_flag(CS_BLOCK_PROCESSING_FAILED_DO_NOT_SEND);
+    return BP_FAIL;
+}
+
+//----------------------------------------------------------------------
+int
+Ciphersuite_BA1::finalize(const Bundle*  bundle, 
+                          BlockInfoVec*  xmit_blocks,
+                          BlockInfo*     block, 
+                          const LinkRef& link)
+{
+    (void)link;
+    
+    size_t          offset;
+    size_t          len;
+    size_t          rem;
+    HMAC_CTX        ctx;
+    OpaqueContext*   r = reinterpret_cast<OpaqueContext*>(&ctx);
+    u_char          digest_result[EVP_MAX_MD_SIZE];
+    u_int32_t       rlen = 0;
+    int             result = BP_FAIL;
+    BP_Local_CS*    locals = NULL;
+    u_int64_t       cs_flags;
+    u_int64_t       suite_num;
+    int sdnv_len = 0;        // use an int to handle -1 return values
+    log_debug_p(log, "Ciphersuite_BA1::finalize()");
+    
+    /* The processing for BundleAuthentication takes place
+     * when finalize() is called for the "front" block, even though
+     * the result itself goes into the trailing block, after the payload.
+     * It is an error to calculate the digest during the finalize() call
+     * for the trailing block itself, as other needed results have not
+     * been created at that time. Remember that the finalize() processing
+     * is a reverse iteration over all the blocks.
+     */
+     
+    locals = dynamic_cast<BP_Local_CS*>(block->locals());
+    CS_FAIL_IF_NULL(locals);
+    if ( locals->correlator_sequence() == 0 ) {       // front block is zero
+        // fetch key
+        const KeyDB::Entry* key_entry = KeyDB::find_key("*", cs_num());
+        // XXX/ngoffee -- fix this ASSERT later, but it's what we have
+        // to do until the prepare()/generate()/finalize() interface
+        // is changed to allow more subtle return codes.
+        CS_FAIL_IF(key_entry == NULL);
+        CS_FAIL_IF(key_entry->key_len() != res_len);
+        
+        // dump key_entry to debugging output
+//         oasys::StringBuffer ksbuf;
+//         key_entry->dump(&ksbuf);
+//         log_debug_p(log, "Ciphersuite_BA1::finalize(): using key entry:\n%s",
+//                     ksbuf.c_str());
+        
+        // prepare the digest context in "digest_result"
+        HMAC_CTX_init(&ctx);
+        HMAC_Init_ex(&ctx, key_entry->key(), key_entry->key_len(),
+                     EVP_sha1(), NULL);
+        
+        // walk the list and process each of the blocks
+        for (BlockInfoVec::const_iterator iter = xmit_blocks->begin();
+             iter != xmit_blocks->end();
+             ++iter)
+        {
+            offset = 0;
+            len = iter->full_length();
+            
+            // If this is a BA block then we exclude the security result data
+            // from the digest, but include its length field
+            
+            if ( iter->type() == BundleProtocol::BUNDLE_AUTHENTICATION_BLOCK ) {
+                // This is a BA block but might or might not be BA1.
+                // So we need to see if there is a security-result field
+                // which needs exclusion
+                
+                // ciphersuite number and flags
+                u_char* ptr = iter->data();
+                rem = iter->full_length();
+                sdnv_len = SDNV::decode(ptr,
+                                        rem,
+                                        &suite_num);
+                ptr += sdnv_len;
+                rem -= sdnv_len;
+
+                sdnv_len = SDNV::decode(ptr,
+                                        rem,
+                                        &cs_flags);
+                ptr += sdnv_len;
+                rem -= sdnv_len;
+                
+                if ( cs_flags & CS_BLOCK_HAS_RESULT ) {
+                    // if there's a security-result we have to ease up to it
+                    
+                    sdnv_len =  SDNV::len(ptr);        //step over correlator
+                    ptr += sdnv_len;
+                    rem -= sdnv_len;
+                    
+                    sdnv_len =  SDNV::len(ptr);        //step over security-result-length field
+                    ptr += sdnv_len;
+                    rem -= sdnv_len;
+                    
+                    len = ptr - iter->contents().buf();  //this is the length to use
+                }
+            }
+            
+            iter->owner()->process( Ciphersuite_BA1::digest,
+                                    bundle,
+                                    block,
+                                    &*iter,
+                                    offset,
+                                    len,
+                                    r );
+        }
+                
+        // finalize the digest
+        HMAC_Final(&ctx, digest_result, &rlen);
+        HMAC_cleanup(&ctx);
+        CS_FAIL_IF(rlen != Ciphersuite_BA1::res_len);
+        
+        // place the digest into the block - it goes into the *second* block
+        // walk the list to find it
+        for (BlockInfoVec::iterator iter = xmit_blocks->begin();
+             iter != xmit_blocks->end();
+             ++iter)
+        {
+            BP_Local_CS* target_locals;
+            if ( iter->type() != BundleProtocol::BUNDLE_AUTHENTICATION_BLOCK )
+                continue;
+            
+            target_locals = dynamic_cast<BP_Local_CS*>(iter->locals());
+            CS_FAIL_IF_NULL(target_locals);
+            if ( target_locals->owner_cs_num() != CSNUM_BA1 )
+                continue;
+            
+            if (target_locals->correlator() != locals->correlator() )
+                continue;
+            
+            if (target_locals->correlator_sequence() != 1 )
+                continue;
+            
+            // Now we're at the block which is ...
+            //   1. BA block
+            //   2. BA1 ciphersuite
+            //   3. same correlator as the main one
+            //   4. correlator sequence is 1, which means second block
+        
+            u_char* buf = iter->writable_contents()->buf() + iter->data_offset() + target_locals->security_result_offset();
+            size_t  rem = iter->data_length() - target_locals->security_result_offset();
+            sdnv_len = SDNV::len(buf);            //length of security-result field
+            CS_FAIL_IF(sdnv_len != 1);
+            buf += sdnv_len;
+            rem -= sdnv_len;
+            *buf++ = Ciphersuite::CS_signature_field;                // item type
+            rem--;
+            sdnv_len = SDNV::encode(Ciphersuite_BA1::res_len, buf, rem);    // item length
+            CS_FAIL_IF(sdnv_len != 1);
+            buf += sdnv_len;
+            rem -= sdnv_len;
+            CS_FAIL_IF (rem != Ciphersuite_BA1::res_len);
+            memcpy(buf, digest_result, Ciphersuite_BA1::res_len);
+        }
+    }
+    
+    result = BP_SUCCESS;
+    return result;
+
+ fail:
+    if ( locals !=  NULL )
+        locals->set_proc_flag(CS_BLOCK_PROCESSING_FAILED_DO_NOT_SEND);
+    return BP_FAIL;
+}
+
+//----------------------------------------------------------------------
+void
+Ciphersuite_BA1::digest(const Bundle*    bundle,
+                        const BlockInfo* caller_block,
+                        const BlockInfo* target_block,
+                        const void*      buf,
+                        size_t           len,
+                        OpaqueContext*   r)
+{
+    (void)bundle;
+    (void)caller_block;
+    (void)target_block;
+    log_debug_p(log, "Ciphersuite_BA1::digest() %zu bytes", len);
+    
+    HMAC_CTX*       pctx = reinterpret_cast<HMAC_CTX*>(r);
+    
+    HMAC_Update( pctx, reinterpret_cast<const u_char*>(buf), len );
+}
+
+} // namespace dtn
+
+#endif /* BSP_ENABLED */