BIND 10 #781: Define cryptographic API
BIND 10 Development
do-not-reply at isc.org
Mon Apr 18 23:35:42 UTC 2011
#781: Define cryptographic API
-------------------------------------+-------------------------------------
Reporter: stephen | Owner: jinmei
Type: task | Status: reviewing
Priority: blocker | Milestone:
Component: | Sprint-20110419
Unclassified | Resolution:
Keywords: | Sensitive: 0
Estimated Number of Hours: 6.0 | Add Hours to Ticket: 0
Billable?: 1 | Total Hours: 0
Internal?: 0 |
-------------------------------------+-------------------------------------
Comment (by jinmei):
Replying to [comment:18 jelte]:
> > '''Trivial things'''
> > - I made some trivial editorial changes.
> > - I also changed 'catch exception by object' to 'by const reference'.
> > (I suggest you try cppcheck. It's very good at finding this issue)
> >
>
> thanks :) (i don't really use cppcheck much yet since it is a tad noisy
here, we should probably put in a bit of effort to make it clean and keep
it that way)
FYI on cppcheck: Older versions of cppcheck seem to complain about
more things (in some sense it's good, but it also means relatively
more false positives). I'm using 1.47, and it should run cleanly (via
'make cppcheck' at the top_dir). We even run it on a buildbot.
BTW: I've added this level of these fixes to the revised code, too.
'''Some higher level discussions'''
> > - I'm okay with the design decision of making this stuff DNS related
> > things only (but IMO it should be noted in the documentation). But
> > in that case should it better be in libdns++? [...]
>
> Right. Originally I did not intend to make libdns make direct use of
libcrypto, so having a dependency the other way wouldn't be a problem.
Right now we have no plans to use cryptographic functionality outside of
libdns++ (as far as i'm aware), but there is quite a big chance this may
change in the future. Another question is how 'clean' we want libdns++ to
be regarding non-dns specific things (like crypto :)).
Now that the mutual dependency problem is going to be solved, I'm okay
with separating these.
> > - I'm still not sure why we need to use OutputBuffer to store
> > results. Isn't a simple vector (or even a plain old array because
> > we should be able to know the size) sufficient?
>
> We don't *need* it, but i figured outputbuffer would me the most logical
type to have for this, which would save the caller from another
checkoutputlength/alloc set of calls. A vector would do that as well, but
will cause yet another copy afterwards. This was under the assumption that
the caller would have an outputbuffer already anyway, so as i noted in my
previous comment, it should be pretty easy to change or add sign() calls
with the needed types.
>
> Anyhow, I added both for flexibility. I also added a size_t len argument
for all three, so we can support truncated signatures.
Hmm, maybe you're thiking about updating a buffer to construct a
complete DNS message with a TSIG digest "on the fly"? That is,
conceptually like this?
{{{
message.renderHeader(buffer);
message.renderAnswer(buffer);
message.renderAuthority(buffer);
message.renderAdditional(buffer); // without TSIG
message.renderTSIGParameters(buffer); // render params before digest
signHMAC(buffer.getData(), buffer.getLength() - sizeof(TSIG), buffer);
}}}
If so, this is actually different from what I'd do for signing. I
suspect, due to various corner cases that would require the
incremental (init-)update-final steps, we'd normally end up getting
the digest in a separate space and copying it to the buffer/renderer.
But anyway, we cannot just be so sure how it would actually be used
yet, so I don't necessarily request this interface to be removed at
this stage.
> > - I noticed the new library is named "libbcrypto" (double b). [...]
>
> It was intentional, what about libb10crypto? (to make it more clear
> > that it is not a typo :), and leaves the option open to use it
> > for non-dns things)
>
> Changed it to that now.
Frankly, libb10crypto sounds a bit awkward:-) but I must confess I'm
not good at naming things. I just come up with a generic name
"libcryptolink", but since at least b10crypto is much less confusing
I'm okay with it, too if you like it better.
> > - do we need a python wrapper for the new crypt stuff?
>
> Probably, but it seemed like a good idea to agree on the API first.
Fair enough.
> > - getHash(): is the overhead of this function acceptable? (pure
> > question, I'm not sure). For sha256, we need name comparison
> > (relatively expensive) three times, and it happens every time we
> > need to sign/verify hmac.
>
> Well, this comparison has to occur somewhere when going from a TSIGKey
to an underlying algorithm. Since for the TSIGKey removal I introduced an
enum for hmac hash algorithms, this is no longer a problem of this
specific lib :)
Okay, we may want to convert string/name to the crypto definition at
the time of TSIGKey construction, but that's another issue.
> > - I suspect the way of defining 'init' can cause a static
> > initialization fiasco (of course it depends on the details of
> > LibraryInitializer, which I didn't dig into. this is a general
> > concern)
>
> I 'copied' the approach from botan; i.e. there is a RAII object for
> > this now, and the problem is passed off to the program using
> > this lib. We need something like this in the future anyway for
> > initialization options.
Okay, this is much safer, but the new approach seems to have another
issue: a buggy program can instantiate it multiple times (not sure if
it causes a bad thing, though). Also, do we really expect it to be
destructed at the end of the program? If not we don't even need the
RAII trick; we can simply provide an initialization function, say,
initCrypto(), and have the main program call it (this approach doesn't
solve the duplicate initialization problem, though).
Beside, requiring programs to do something specific (either
instantiate an RAII object or call an init function) is inconvenient;
I guess you were aware of that and that's why you hid it in crypto.cc
in the first implementation. IMO safety is more important than
convenience in general, so if the policy is to accept this
inconvenience, I'm okay with that. But if we want to avoid that while
avoiding the initialization fiasco, one approach would be to hide the
initialization logic in the wrapper classes such as HMACImpl. This
approach has a drawback that we must not forget doing it in every
wrapper class, so this is basically a design tradeoff. I'm okay with
any safer approach.
> > - HMACImpl constructor: this is not exception safe (hmac_ could leak).
>
> added delete
Ack, but to be super exception safe, I'd rather (e.g.) use
boost::scoped_ptr. I don't know the details of the Botan internals,
but, for example, if it internally performs (throwing version of) new
and doesn't convert the standard exceptions, the revised code is still
not exception safe. Using RAII like scoped_ptr will ensure the
safeness regardless of the Botan internals. (And if we use scoped_ptr
we don't need an explicit destructor)
> > - HMACImpl::update/sign/verify: can we assume the underlying Boton
> > methods don't throw?
>
> only invalidkeylength, afaict, should we add 'general'
> Botan::Exception catches?
It might depend on the internal details of Botan, but as long as we
want to prevent any Botan specific exceptions from being propagated,
I think it would be safer to catch larger exceptions. That way we can
ensure that even if the Botan implementation changes in its future
versions.
> > '''crypto_unittests.cc'''
> > - checkBuffer: in ASSERT_EQ() the expected data is the first
> > parameter. it seems to be reversed. Also, you may want to use
> > UnitTestUtil::matchWireData in lib/dns/tests for this type of test.
>
> That would introduce a build-order-dependency conflict again :)
Yeah I know. So for now it probably doesn't make sense to use
matchWireData. In future I think we should extract non DNS specific
test utilities into a shared place so that it can be used by other
modules.
> > - I'm not sure if the :-separated notation is a good one. [...]
>
> we already have the three-part constructor (the original one with Name
args). This is simply to make it possible to use the de facto standard for
the string representation, as introduced by dig and copied by multiple
other implementations. If the name contains a ':', it will simply fail to
create a key using that representation. We could add logic for escapes
etc. But we could also simply say not to use names with :.
Okay. I have more comments about the implementation, though. See below.
> > - in that constructor, is it a good idea to catch all isc exceptions?
>
> No, but it's better than enumerating them all and catching them one by
one (and almost certainly missing one or two). I think we should have a
hierarchy of exceptions actually (like nameparseerror that is a superclass
for emptylabl, toolonglabel, etc.etc.). Probably something to discuss
and/or make a task for.
>
> > can't we simply let various exceptions be propagated?
>
> Then the caller would have the same problem. This way I can honestly say
that the \exception list in the docs are complete :) (and, more
importantly, that the caller does not have to take 15 different exceptions
into account, which may or may not change, not to mention the fact that
the documentation will probably not be updated if it does indeed change).
For these points, I basically envision that higher level applications
catch exceptions at the level of isc::Exception (or even
std::exception, from which all isc::Exception classes are derived),
just like b10-auth does (in which case it returns SERVFAIL). The
above questions of mine are based on this observation. But I admit
this is probably a debatable point, and so I'm okay with keeping the
current code and defer the discussion to a separate ticket or dev list
thread.
'''proposed changelog entry:'''
> [func] Introduced an API for cryptographic operations. Currently it only
supports HMAC, intended for use with TSIG. The current implementation uses
Botan as the backend library.
I'd note that build now requests Botan. I'd also mark this entry with
'*' due to the additional build time dependency.
Now some more code level comments on the revised one:
'''crypto.cc'''
- HMACImpl::verify: is the "len > getOutputLength()" case tested?
(just checking)
- HMACImpl constructor now doesn't have to be "explicit" (because it
takes more than one parameters). but I don't mind if you
intentionally left it so that e.g. we don't forget to re-add it if
and when we re-revise it to a single-parameter constructor. same
for the HMAC constructor.
- HMAC::HashAlgorithm I'd add doxygen description for the enums (even
though the meaning is pretty clear from the symbol). also, I'd like
to see clarification of what "UNKNOWN" means (e.g. whether it's just
a placeholder and always trigger some error, or if it can be used
in some valid scenario)
'''crypto/tests'''
- in checkData(), expected/actual parameters to ASSERT_EQ() still seem
to be reversed (maybe it's helpful to name them "expected_data",
"actual_data" instead of "data1"/"data2")
- doHMACTestArray() uses a variable length array, which is
(unfortunately) not very portable. considering this is a test,
maybe we can just allocate a memory dynamically, noting it's not
exception safe as a comment (we could also use
std::vector/boost::scoped_array)
- about secret/expected data (I should have brought this up in the
first review, sorry for the raising a new topic): the arrays can be
const uint8_t[]. as for std::string data, shouldn't it actually
better be vector<uint8_t>? In either case, the initialization using
the for loop could be simplified:
{{{
const std::string data3(50, 0xdd);
// std::string data3;
// for (int i = 0; i < 50; ++i) {
// data3.push_back(0xdd);
//}
}}}
(as a bonus we can also make the variable const this way)
'''tsigkey test'''
- I'd use a different name than "TSIGTest" (for TSIGKeyFromToString)
because I'd like to use it for TSIG sign/verify (I hope it's fair to
say "TSIGTest" is more suitable for these kinds of tests:-). google
test doesn't seem to allow name conflict at this level.
'''tsigkey.cc'''
- TSIGKey from string constructor: (I should have pointed this out in
the first review) you may want to make it an 'explicit' constructor.
- TSIGKey from string constructor: this seems to be incorrect:
{{{
} else {
pos2 = str.size() - pos;
}
}}}
You can realize the bug by making the len(key name) <= len(secret):
{{{
EXPECT_EQ("example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.",
TSIGKey("example.:MSG6Ng==").toText());
}}}
We could fix it in a straightforward way, but I actually wonder
whether we could achieve what we want using a higher level
interface, rather than via lower level of "position + length" APIs,
which are generally error prone and difficult to understand.
So, for example, how about the attached diff? This one is not that
concise compared to the original code, but the logic should be more
intuitive and much safer.
- Regarding the previous point, if you choose fixing the original
code, I suggest deferring the definition of "secret_str" just before
it's needed.
- And, in either case, it would be good if we extract the alg_name
check used in the two constructors.
--
Ticket URL: <http://bind10.isc.org/ticket/781#comment:19>
BIND 10 Development <http://bind10.isc.org>
BIND 10 Development
More information about the bind10-tickets
mailing list