Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IPAddrBlock and ASIdentifiers extensions (RFC 3779) #4443

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

arckoor
Copy link

@arckoor arckoor commented Nov 25, 2024

This PR implements two extensions required for working with secure routing. These extensions contain IP Addresses or AS numbers, as specified by RFC 3779.

Both encoding and decoding of AS numbers, single addresses, address prefixes and min-max ranges have been tested.

@coveralls
Copy link

coveralls commented Nov 25, 2024

Coverage Status

coverage: 91.294% (+0.04%) from 91.258%
when pulling 81ac46d on arckoor:master
into 09a7a98 on randombit:master.

@randombit
Copy link
Owner

@arckoor There are some CI failures which are relevant. Stylistically for calls to min,max where the compiler can’t deduce T we prefer std::min<T> vs use of casts.

@randombit
Copy link
Owner

@arckoor Remaining CI issues look related to the visibility annotations (eg BOTAN_PUBLIC_API) probably something is not being exported as a symbol.

@arckoor
Copy link
Author

arckoor commented Nov 27, 2024

@randombit We're currently chipping away at the fuzzers, but the visibility is also an issue. Previously linux/clang was failing, which I resolved by moving the definition of set_from_bytes to the .h file, the other errors could probably be solved by moving the rest there too, but that does not sound like a good solution to me. All of these errors are most likely related to the use of templates, though I don't know what the cleanest / recommended way to resolve them is.

@randombit
Copy link
Owner

@arckoor ok thanks. I’ll try to have a look over the weekend

@arckoor
Copy link
Author

arckoor commented Dec 10, 2024

@randombit Have you had the chance to take a look yet, and can I still contribute something to make your life easier?

@randombit
Copy link
Owner

@arckoor The main issue here is CI failing. In general this is a change that's appropriate to the library and the code from a quick look seems fine. But we certainly can't merge if it will break the build. Unfortunately I don't know the exact issue; I guess symbol visibility behaves differently on macOS/Windows vs Linux. My only suggestion would be to try adding BOTAN_PUBLIC_API macros to the various subclasses until the build succeeds.

@arckoor
Copy link
Author

arckoor commented Jan 7, 2025

@randombit The clang build should now succeed. After much debugging we realized the compiler was optimizing away the IPAddress class completely. We tried various things like including a dummy function that instantiates all variations at compile time, nothing helped. We worked around this issue by defining a new macro that forces compilation, the windows and macos builds are probably still going to fail, as there the compilers seems to optimize away even more. A potential workaround would be to add the forced compilation macro everywhere, or move the entire thing to x509_ext.h.

@arckoor arckoor force-pushed the master branch 4 times, most recently from cec640d to 4986bf0 Compare January 9, 2025 16:53
@butteronarchbtw
Copy link

@randombit we've now resolved the symbol issues by instantiating the template classes in x509_ext.cpp and also did some small tweaks to fix the other checks. Does this seem like a viable solution to you?

Copy link
Owner

@randombit randombit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arckoor @butteronarchbtw Not a full review and I still need to check the tests but enough for you to get started on. Overall things seem fine with two exceptions

  1. The various types have setters that seem to me quite easy to misuse. I would rather see values set using constructors, at least whenever possible.

  2. The decoding functions seem really complicated considering the types involved. BER_Decoder is designed to make this easy, so either there is improvements possible in the decoding or there are improvements possible in BER_Decoder. I left some suggestions which are untested but should help get you going.

template <Version V>
class BOTAN_PUBLIC_API(3, 6) IPAddress final {
public:
void set_from_bytes(const std::vector<uint8_t>& v);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input values should be span, here std::span<const uint8_t> v

* RFC 3779 X.509 Extensions for IP Addr
*
*/
class BOTAN_PUBLIC_API(3, 6) IPAddressBlocks final : public Certificate_Extension {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BOTAN_PUBLIC_API macros should read 3, 7 since that's the next release


void set_min(const IPAddress<V>& min) { m_min = min; }

void set_max(const IPAddress<V>& max) { m_max = max; }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment: I'd prefer if possible (and it seems like it is from my current understanding) that values are set once in a constructor and we don't have any setters.

uint16_t afi = (m_addr_family[0] << 8) | m_addr_family[1];
if(1 > afi || afi > 2) {
throw Decoding_Error("Only AFI IPv4 and IPv6 are supported.");
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here combine the check on afi and the decoding

if(afi == 1) {
  ...
} else if(afi == 2) {
  ...
} else {
   throw Decoding_Error("Only AFI IPv4 and IPv6 are supported.");
}

BER_Decoder obj_ber = BER_Decoder(obj);
BER_Object as_min_obj = obj_ber.get_next_object();
std::vector<uint8_t> bytes;
bytes.assign(as_min_obj.data().begin(), as_min_obj.data().end());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ASId is just an INTEGER, BER_Decoder supports this already


void ASBlocks::ASIdOrRange::decode_from(BER_Decoder& from) {
BER_Object obj = from.get_next_object();
ASN1_Type type_tag = obj.type_tag();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function can be quite a bit simpler overall, something like (untested)

ASN1_Tag next_tag = from.peek_next_object().type_tag();
if(next_tag == ASN1_Type::Integer) {
   from.decode(m_min);
   m_max = m_min;
} else if(next_tag == ASN11_Type::Sequence) {
  from.start_sequence().decode(m_min).decode(m_max).end_cons();
} else {
   throw ...
}


void ASBlocks::ASIdOrRange::encode_into(Botan::DER_Encoder& into) const {
if(m_min == m_max) {
into.add_object(ASN1_Type::Integer, ASN1_Class::Universal, encode_asnum(m_min));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DER_Encoder already knows to zero prefix, all that should be required here is

into.encode(m_min)

(likewise wherever encode_asnum is called, I don't believe this function is necessary at all)

ASN1_Type type_tag = obj.type_tag();

// this can either be a prefix or a single address
if(type_tag == ASN1_Type::BitString) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function similar to ASBlocks::ASIdOrRange::decode_from can be made much simpler by using peek_next_object to decide if you are in the IPAddress or IPAddressRange case. Then you can use the builtin functionality for decoding bit strings, parsing sequences etc without having to replicate logic here.

m_addr_family = addr_family;
size_t addr_family_length = addr_family.size();
if(2 > addr_family_length || addr_family_length > 3) {
throw Decoding_Error("AFI/SAFI too long / too short.");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this surpsisingly hard to parse. Maybe just

if(addr_family.size() != 2 && addr_family.size() != 3)

?

}

void ASBlocks::ASIdentifiers::decode_from(Botan::BER_Decoder& from) {
BER_Object obj = from.get_next_object();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be done as (untested)

from.start_sequence()
 .decode_optional(m_asnum, ASN1_Type(0), ASN1_Class::ExplicitContextSpecific);
 .decode_optional(m_rdi, ASN1_Type(1), ASN1_Class::ExplicitContextSpecific);
.end_cons()

If it's something much harder than that it suggests BER_Decoder needs a helper

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants