Mir

Merge lp:~mir-team/mir/attestable-timestamps-server into lp:mir

Proposed by Brandon Schaefer
Status: Merged
Approved by: Alan Griffiths
Approved revision: no longer in the source branch.
Merged at revision: 2943
Proposed branch: lp:~mir-team/mir/attestable-timestamps-server
Merge into: lp:mir
Diff against target: 1375 lines (+951/-5)
32 files modified
CMakeLists.txt (+1/-0)
cmake/ABICheck.cmake (+2/-1)
debian/control (+28/-0)
debian/libmircookie-dev.install (+3/-0)
debian/libmircookie1.install (+1/-0)
debian/mir-test-tools.install (+1/-0)
include/cookie/mir/cookie_factory.h (+104/-0)
include/cookie/mir_toolkit/cookie.h (+44/-0)
include/server/mir/server.h (+12/-0)
include/test/mir_test_framework/executable_path.h (+1/-0)
src/CMakeLists.txt (+3/-0)
src/cookie/CMakeLists.txt (+48/-0)
src/cookie/cookie_factory.cpp (+161/-0)
src/cookie/mircookie.pc.in (+11/-0)
src/cookie/symbols.map (+9/-0)
src/include/server/mir/default_server_configuration.h (+7/-1)
src/include/server/mir/server_configuration.h (+5/-0)
src/server/CMakeLists.txt (+2/-1)
src/server/default_server_configuration.cpp (+21/-1)
src/server/server.cpp (+17/-0)
src/server/symbols.map (+2/-0)
tests/acceptance-tests/CMakeLists.txt (+3/-0)
tests/include/mir_test_framework/udev_environment.h (+26/-0)
tests/integration-tests/CMakeLists.txt (+1/-0)
tests/mir_test_framework/CMakeLists.txt (+4/-0)
tests/mir_test_framework/executable_path.cpp (+13/-0)
tests/mir_test_framework/udev_environment.cpp (+38/-1)
tests/mir_test_framework/udev_recordings/CMakeLists.txt (+3/-0)
tests/mir_test_framework/udev_recordings/laptop-keyboard-hello.evemu (+272/-0)
tests/unit-tests/CMakeLists.txt (+2/-0)
tests/unit-tests/test_mir_cookie.cpp (+105/-0)
tools/update_package_abis.sh (+1/-0)
To merge this branch: bzr merge lp:~mir-team/mir/attestable-timestamps-server
Reviewer Review Type Date Requested Status
Tyler Hicks (community) Needs Fixing
Brandon Schaefer (community) Approve
Andreas Pokorny (community) Approve
Alexandros Frantzis (community) Approve
Kevin DuBois (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Alan Griffiths Approve
Chris Halse Rogers Approve
Review via email: mp+270457@code.launchpad.net

Commit message

server: Added a CookieFactory which generates macs for keyboard/motions events.

library: CookieFactory creates macs based on timestamp info

Description of the change

Splitting most the server bits from here:
https://code.launchpad.net/~mir-team/mir/attestable-timestamps/+merge/267866

The main parts are:
CookieFactory library
Integration with the server (init the cookie factory by seeding it with a random key)

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

From the other review:

AFAICS libmircookie is only used by libmirserver.

What is the advantage to having a shared library over linking the code directly into libmirserver?

review: Needs Information
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

+std::string mir_test_framework::udev_recordings_path()
+{
+ std::string bin_path = MIR_BUILD_PREFIX"/bin/udev_recordings";
+ std::string share_path = MIR_INSTALL_PREFIX"/share/udev_recordings";
+
+ if (boost::filesystem::exists(bin_path))
+ return bin_path;
+ else if (boost::filesystem::exists(share_path))
+ return share_path;
+
+ BOOST_THROW_EXCEPTION(std::runtime_error("Failed to find udev_recordings in standard search locations"));
+}

Can this really be the right behaviour? An installed test binary looks in the location where it was built? Even though a developer may be making changes there to compare results with.

~~~~

+ char const* RANDOM_DEVICE_PATH = "/dev/random";
+ int const WAIT_SECONDS = 30;
...
+ int const MAX_WAIT = 4;

http://unity.ubuntu.com/mir/cppguide/index.html?showone=Constant_Names#Constant_Names

~~~~

AFAICS libmircookie is only used by libmirserver.

What is the advantage to having a shared library over linking the code directly into libmirserver?

~~~~

/mir/tests/acceptance-tests/test_mir_cookie.cpp:39:13: error: unused variable 'MAX_WAIT' [-Werror,-Wunused-const-variable]
  int const MAX_WAIT = 4;
            ^
1 error generated.

review: Needs Fixing
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

>
> +std::string mir_test_framework::udev_recordings_path()
> +{
> + std::string bin_path = MIR_BUILD_PREFIX"/bin/udev_recordings";
> + std::string share_path = MIR_INSTALL_PREFIX"/share/udev_recordings";
> +
> + if (boost::filesystem::exists(bin_path))
> + return bin_path;
> + else if (boost::filesystem::exists(share_path))
> + return share_path;
> +
> + BOOST_THROW_EXCEPTION(std::runtime_error("Failed to find udev_recordings
> in standard search locations"));
> +}
>
> Can this really be the right behaviour? An installed test binary looks in the
> location where it was built? Even though a developer may be making changes
> there to compare results with.

I should check the share folder first. Other then that... I think its correct behaviour. We still want to be able to test it in a build directory. Unless I add an env variable that we set for the test it self. Let me know which one you would like!

>
> ~~~~
>
> + char const* RANDOM_DEVICE_PATH = "/dev/random";
> + int const WAIT_SECONDS = 30;
> ...
> + int const MAX_WAIT = 4;
>
> http://unity.ubuntu.com/mir/cppguide/index.html?showone=Constant_Names#Constan
> t_Names
>

I need to read through the coding standard for mir :). Use to compiz/unity7/nux.

> ~~~~
>
> AFAICS libmircookie is only used by libmirserver.
>
> What is the advantage to having a shared library over linking the code
> directly into libmirserver?
>

This is how it currently stands but we are planning on using this for the content hub as well. So best to keep it shared now, as it will be used by something else.

> ~~~~
>
> /mir/tests/acceptance-tests/test_mir_cookie.cpp:39:13: error: unused variable
> 'MAX_WAIT' [-Werror,-Wunused-const-variable]
> int const MAX_WAIT = 4;
> ^
> 1 error generated.

Thanks! Missed removing that.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Sorry about the long list here, most of it is about fitting in with the design styles that have grown in Mir and you would not yet be familiar with. I've voted "Needs Fixing" but many of the points might be addressed by discussion.

~~~~

src/server/default_server_configuration.cpp

There's a lot of code added here (to generate a seed) that probably doesn't belong in a translation unit that is about constructing the right objects to configure the system. I'd prefer to see it put in a separate file. (Is there any reason it can't be part of the new library?)

~~~~

+void fill_vector_with_random_data(std::vector<uint8_t>& buffer)

Could we not use a return value instead of an out parameter?

~~~~

+std::string mir_test_framework::udev_recordings_path()
+{
+ std::string share_path = MIR_INSTALL_PREFIX"/share/udev_recordings";
+ std::string bin_path = MIR_BUILD_PREFIX"/bin/udev_recordings";
+
+ if (boost::filesystem::exists(share_path))
+ return share_path;
+ else if (boost::filesystem::exists(bin_path))
+ return bin_path;
+
+ BOOST_THROW_EXCEPTION(std::runtime_error("Failed to find udev_recordings in standard search locations"));
+}

Can this be right? A development test binary looks in the location where it would be installed? Even though a developer may be making changes to the version in the build path.

The older logic (based on the executable path) seems less surprising.

~~~~

+class CookieFactory
+{
+public:
+ CookieFactory(std::vector<uint8_t> const& secret);
+ ~CookieFactory() noexcept;
+
+ MirCookie timestamp_to_cookie(uint64_t const& timestamp);
+
+ bool attest_timestamp(MirCookie const& cookie);
+
+private:
+ class CookieImpl;
+ std::unique_ptr<CookieImpl> impl;
+};

What is the reason for preferring the Cheshire Cat idiom over Interface Class? This makes it far harder to substitute alternative implementations (e.g. for testing that cookies actually are validated when necessary).

I can also imagine a desire to provide a different verification model in a (hypothetical) downstream server.

~~~~~

+void mir::Server::set_cookie_secret(std::vector<uint8_t> const& secret)
+{
+ verify_setting_allowed(self->server_config);
+ self->cookie_factory = std::make_shared<CookieFactory>(secret);
+}

It is a little surprising that this sets the cookie factory, not the secret. I can't immediately think of anything this breaks but...

Between this and the previous, I think this would fit better into Mir's design patterns if:

1. CookieFactory were an interface and CookieImpl a derived class.
2. the configuration point were override_the_cookie_factory

~~~~~

=== added file 'tests/acceptance-tests/test_mir_cookie.cpp'

This reads more like a unit test

~~~~~

+ mir_discover_tests_with_fd_leak_detection(mir_acceptance_tests LD_PRELOAD=libumockdev-preload.so.0 G_SLICE=always-malloc G_DEBUG=gc-friendly)

The acceptance tests (before and after this MP) don't need libumockdev-preload.so.0 (which is convenient when running them by hand) why change that?

~~~~~

AFAICS the_cookie_factory() isn't used. I've not looked in the parent branch - I assume use (and real acceptance tests) is coming soon?

review: Needs Fixing
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :
Download full text (4.6 KiB)

> Sorry about the long list here, most of it is about fitting in with the design
> styles that have grown in Mir and you would not yet be familiar with. I've
> voted "Needs Fixing" but many of the points might be addressed by discussion.
>
> ~~~~
>

No problem at all :), Thanks for going through in depth!

> src/server/default_server_configuration.cpp
>
> There's a lot of code added here (to generate a seed) that probably doesn't
> belong in a translation unit that is about constructing the right objects to
> configure the system. I'd prefer to see it put in a separate file. (Is there
> any reason it can't be part of the new library?)
>
> ~~~~

This is true, Im not 100% sure why it cant be in the library it self. Ill have to talk to Chris about that.

>
> +void fill_vector_with_random_data(std::vector<uint8_t>& buffer)
>
> Could we not use a return value instead of an out parameter?
>
> ~~~~

Changed!

>
> +std::string mir_test_framework::udev_recordings_path()
> +{
> + std::string share_path = MIR_INSTALL_PREFIX"/share/udev_recordings";
> + std::string bin_path = MIR_BUILD_PREFIX"/bin/udev_recordings";
> +
> + if (boost::filesystem::exists(share_path))
> + return share_path;
> + else if (boost::filesystem::exists(bin_path))
> + return bin_path;
> +
> + BOOST_THROW_EXCEPTION(std::runtime_error("Failed to find udev_recordings
> in standard search locations"));
> +}
>
> Can this be right? A development test binary looks in the location where it
> would be installed? Even though a developer may be making changes to the
> version in the build path.
>
> The older logic (based on the executable path) seems less surprising.
>
> ~~~~

Yes... Hmm so my overall thought here is do we need to always install the file to run the tests? Or should we ever be able to run the tests on a build directory? If we just want the test to run off the install location im 100% fine with that. All else I can think about is setting an env variable so we can run it to the build directory (and the test can set it).

Either way im happy to change it.

>
> +class CookieFactory
> +{
> +public:
> + CookieFactory(std::vector<uint8_t> const& secret);
> + ~CookieFactory() noexcept;
> +
> + MirCookie timestamp_to_cookie(uint64_t const& timestamp);
> +
> + bool attest_timestamp(MirCookie const& cookie);
> +
> +private:
> + class CookieImpl;
> + std::unique_ptr<CookieImpl> impl;
> +};
>
> What is the reason for preferring the Cheshire Cat idiom over Interface Class?
> This makes it far harder to substitute alternative implementations (e.g. for
> testing that cookies actually are validated when necessary).
>
> I can also imagine a desire to provide a different verification model in a
> (hypothetical) downstream server.
>
> ~~~~~

Not sure the reasoning here, Ill have to talk to Chris about it. Wouldnt be to hard to switch over to just an interface.
>
> +void mir::Server::set_cookie_secret(std::vector<uint8_t> const& secret)
> +{
> + verify_setting_allowed(self->server_config);
> + self->cookie_factory = std::make_shared<CookieFactory>(secret);
> +}
>
> It is a little surprising that this sets the cookie factory, n...

Read more...

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Chris Halse Rogers (raof) wrote :

> What is the reason for preferring the Cheshire Cat idiom over Interface Class?
> This makes it far harder to substitute alternative implementations (e.g. for
> testing that cookies actually are validated when necessary).
>
> I can also imagine a desire to provide a different verification model in a
> (hypothetical) downstream server.

libmircookie is going to be used by Content Hub; Mir will share the secret with CH, and then CH will be able to verify cookies sent to it (for copy/paste and drag/drop operations). This is why it's a split-out library.

As such, it's actively harmful for downstream servers to override the implementation, as multiple processes need to agree on the method to verify cookies.

As an added bonus feature, it also means that libmircookie can have an actual ABI that's stable even under mild code churn :).

> Can this be right? A development test binary looks in the location where it
> would be installed? Even though a developer may be making changes to the
> version in the build path.
>
> The older logic (based on the executable path) seems less surprising.

Yeah, I'd have the test look first in the bin dir and *then* in the system-install location.

> src/server/default_server_configuration.cpp
>
> There's a lot of code added here (to generate a seed) that probably doesn't
> belong in a translation unit that is about constructing the right objects to
> configure the system. I'd prefer to see it put in a separate file. (Is there
> any reason it can't be part of the new library?)

I'd have no objections to it being in the new library.

Other comments:

+ * This is not in any way cryptographically secure. This DOES NOT provide security.

We should drop this bit of the comment.

Once the above are dealt with: approve.

We should probably add a debian/libmircookie1.symbols file, but that can easily be done later.

review: Approve
Revision history for this message
Chris Halse Rogers (raof) wrote :

Oh, whitespace before '}':
362 + MirCookie cookie { timestamp, 0};

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> > I can also imagine a desire to provide a different verification model in a
> > (hypothetical) downstream server.
>
> libmircookie is going to be used by Content Hub; Mir will share the secret
> with CH, and then CH will be able to verify cookies sent to it (for copy/paste
> and drag/drop operations). This is why it's a split-out library.
>
> As such, it's actively harmful for downstream servers to override the
> implementation, as multiple processes need to agree on the method to verify
> cookies.

Oh, I agree that within a UI stack it ought to be fixed. Just imagining that someone /could/ want a different implementation in a different stack.

> As an added bonus feature, it also means that libmircookie can have an actual
> ABI that's stable even under mild code churn :).

OK, that's an argument for a separate library. In fact, is it really something that will need to be released with every Mir release? Or should it be a separate project?

BTW this last argument doesn't address the "Cheshire Cat vs Interface" point. The ABI could easily be:

std::unique_ptr<CookieFactory> CookieFactory::create_with_secret(std::vector<uint8_t> const& secret);
std::unique_ptr<CookieFactory> CookieFactory::create_without_secret();

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> > === added file 'tests/acceptance-tests/test_mir_cookie.cpp'
> >
> > This reads more like a unit test
> >
> > ~~~~~
> test_cookie_factory sound any better? (Im bad at names)

You misunderstand, I mean the test body reads like a unit test, not a test of the system.

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

O yeah sorry, I was planning on moving all those tests to unit tests in the client code ... but i should do that now and save on diff +/- :)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

+namespace mir
+{
...
+class CookieFactory
...
+std::vector<uint8_t> get_random_data(unsigned size);

We reserve the general mir namespace for system setup. These could be in a "cookie" namespace.

~~~~

+class CookieFactory
+{
+public:
+ CookieFactory(std::vector<uint8_t> const& secret);
+ ~CookieFactory() noexcept;
+
+ MirCookie timestamp_to_cookie(uint64_t const& timestamp);
+
+ bool attest_timestamp(MirCookie const& cookie);
+
+private:
+ class CookieImpl;
+ std::unique_ptr<CookieImpl> impl;
+};

I'm still not convinced we should prefer the Cheshire Cat idiom (over Interface Class) here, but if we are taking this approach then impl should be const.

~~~~

+std::string mir_test_framework::udev_recordings_path()
+{
+ std::string bin_path = MIR_BUILD_PREFIX"/bin/udev_recordings";
+ std::string share_path = MIR_INSTALL_PREFIX"/share/udev_recordings";
+
+ if (boost::filesystem::exists(bin_path))
+ return bin_path;
+ else if (boost::filesystem::exists(share_path))
+ return share_path;
+
+ BOOST_THROW_EXCEPTION(std::runtime_error("Failed to find udev_recordings in standard search locations"));
+}

Can this really be the right behaviour? An installed test binary looks in the location where it was built? Even though a developer may be making changes there?

The older logic (based on the executable path) seems less surprising.

~~~~

+ CookieFactory(std::vector<uint8_t> const& secret);

This will throw if the secret is too small, but the behaviour and minimum size is not documented anywhere.

I suggest providing a static class member (minimum_secret_size) and documenting the requirement.

review: Needs Fixing
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Is libmircookie really something that will need to be released in sync with every Mir release? Or should it be a separate project?

~~~~

+std::vector<uint8_t> get_random_data(unsigned size);

Could be:

CookieFactory::CookieFactory(unsigned size);

~~~~

+void mir::Server::set_cookie_secret(std::vector<uint8_t> const& secret)
+{
+ verify_setting_allowed(self->server_config);
+ self->cookie_factory = std::make_shared<CookieFactory>(secret);
+}

It is a little surprising that this sets the cookie factory, not the secret.

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

> Is libmircookie really something that will need to be released in sync with
> every Mir release? Or should it be a separate project?

I dont have a clear answer for this one. I just imagine since the project is pretty small it makes it easier to keep it in lp:mir atm? Will have to poke raof about that one.
>
> ~~~~
>
> +std::vector<uint8_t> get_random_data(unsigned size);
>
> Could be:
>
> CookieFactory::CookieFactory(unsigned size);
>

I had this at first, but switched up because one of the primary use-cases for CookieFactory is when you've shared the secret with another process, which you can't do if you've called CookieFactory(unsigned size)

> ~~~~
>
> +void mir::Server::set_cookie_secret(std::vector<uint8_t> const& secret)
> +{
> + verify_setting_allowed(self->server_config);
> + self->cookie_factory = std::make_shared<CookieFactory>(secret);
> +}
>
> It is a little surprising that this sets the cookie factory, not the secret.

I think this function should just be renamed to initialize_cookie_factory(..)

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

> +namespace mir
> +{
> ...
> +class CookieFactory
> ...
> +std::vector<uint8_t> get_random_data(unsigned size);
>
> We reserve the general mir namespace for system setup. These could be in a
> "cookie" namespace.
>
> ~~~~

Done.

>
> +class CookieFactory
> +{
> +public:
> + CookieFactory(std::vector<uint8_t> const& secret);
> + ~CookieFactory() noexcept;
> +
> + MirCookie timestamp_to_cookie(uint64_t const& timestamp);
> +
> + bool attest_timestamp(MirCookie const& cookie);
> +
> +private:
> + class CookieImpl;
> + std::unique_ptr<CookieImpl> impl;
> +};
>
> I'm still not convinced we should prefer the Cheshire Cat idiom (over
> Interface Class) here, but if we are taking this approach then impl should be
> const.
>
> ~~~~

Move to an NVI idiom:
http://www.gotw.ca/publications/mill18.htm
>
> +std::string mir_test_framework::udev_recordings_path()
> +{
> + std::string bin_path = MIR_BUILD_PREFIX"/bin/udev_recordings";
> + std::string share_path = MIR_INSTALL_PREFIX"/share/udev_recordings";
> +
> + if (boost::filesystem::exists(bin_path))
> + return bin_path;
> + else if (boost::filesystem::exists(share_path))
> + return share_path;
> +
> + BOOST_THROW_EXCEPTION(std::runtime_error("Failed to find udev_recordings
> in standard search locations"));
> +}
>
> Can this really be the right behaviour? An installed test binary looks in the
> location where it was built? Even though a developer may be making changes
> there?
>
> The older logic (based on the executable path) seems less surprising.
>
> ~~~~
Not sure what the correct answer is here. What would be the best solution?

>
> + CookieFactory(std::vector<uint8_t> const& secret);
>
> This will throw if the secret is too small, but the behaviour and minimum size
> is not documented anywhere.
>
> I suggest providing a static class member (minimum_secret_size) and
> documenting the requirement.

Done.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> > +std::vector<uint8_t> get_random_data(unsigned size);
> >
> > Could be:
> >
> > CookieFactory::CookieFactory(unsigned size);
> >
>
> I had this at first, but switched up because one of the primary use-cases for
> CookieFactory is when you've shared the secret with another process, which you
> can't do if you've called CookieFactory(unsigned size)

I was thinking two constructors would be:

   explicit CookieFactory(std::vector<uint8_t> const& secret);
   explicit CookieFactory(unsigned size);

But I take your point - you need access to the "random data" to share the secret.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> > Is libmircookie really something that will need to be released in sync with
> > every Mir release? Or should it be a separate project?
>
> I dont have a clear answer for this one. I just imagine since the project is
> pretty small it makes it easier to keep it in lp:mir atm? Will have to poke
> raof about that one.

A further thought, the header is dependent on MirCookie (introduced here into include/common/mir_toolkit/common.h). That makes client code (compile time) dependent on libmircommon-dev.

As we're introducing this dependency on mircommon, could we just put the code into libmircommon and not a separate library?

Or should MirCookie be defined in libmircookie-dev and not libmircommon-dev?

review: Needs Information
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

+#include <nettle/hmac.h>
...
+ struct hmac_sha1_ctx ctx;

Surely we don't want to introduce this dependency to user code? (See [1].)

There is no point in this class heirarchy if you are putting the derived class definition in the same header.

An option to consider here would the Named Constructor Idiom.

~~~~

+// Using the NVI Idiom

Nobody takes NVPI seriously - it is a solution in search of a problem.

The principle options to consider are Interface and Cheshire Cat (a.k.a. Pimpl). For a comparison of the different options see[2].

~~~~~

+ CookieFactoryNettle(std::vector<uint8_t> const& secret);

This will throw if the secret is too small, but the behaviour and minimum size is not documented in a doc comment on the function. (Having a non-doc comment in the private section of the class doesn't help the user.)

(But as I don't think this class should be made public anyway the documentation should go on the corresponding factory function.)

~~~~

> > The older logic (based on the executable path) seems less surprising.
> >
> > ~~~~
>
> Not sure what the correct answer is here. What would be the best solution?

I think to first check relative to the executable path (as it did before this MP), then to check the install location.

E.g. if running from a build directory it should pick up the one from e.g. <build>/bin/udev_recordings/ if not it picks up the installed version.

~~~~

[1] http://wiki.hsr.ch/Prog3/files/overload72-FINAL_DesigningHeaderFiles.pdf

[2] http://www.twonine.co.uk/articles/SeparatingInterfaceAndImplementation.pdf

review: Needs Fixing
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

> +#include <nettle/hmac.h>
> ...
> + struct hmac_sha1_ctx ctx;
>
> Surely we don't want to introduce this dependency to user code? (See [1].)
>
> There is no point in this class heirarchy if you are putting the derived class
> definition in the same header.
>
> An option to consider here would the Named Constructor Idiom.

Done.

>
> ~~~~
>
> +// Using the NVI Idiom
>
> Nobody takes NVPI seriously - it is a solution in search of a problem.
>
> The principle options to consider are Interface and Cheshire Cat (a.k.a.
> Pimpl). For a comparison of the different options see[2].
>
> ~~~~~

Done.

>
> + CookieFactoryNettle(std::vector<uint8_t> const& secret);
>
> This will throw if the secret is too small, but the behaviour and minimum size
> is not documented in a doc comment on the function. (Having a non-doc comment
> in the private section of the class doesn't help the user.)
>
> (But as I don't think this class should be made public anyway the
> documentation should go on the corresponding factory function.)
>
> ~~~~

Done.

>
> > > The older logic (based on the executable path) seems less surprising.
> > >
> > > ~~~~
> >
> > Not sure what the correct answer is here. What would be the best solution?
>
> I think to first check relative to the executable path (as it did before this
> MP), then to check the install location.
>
> E.g. if running from a build directory it should pick up the one from e.g.
> <build>/bin/udev_recordings/ if not it picks up the installed version.
>
> ~~~~

Done.

>
> [1] http://wiki.hsr.ch/Prog3/files/overload72-FINAL_DesigningHeaderFiles.pdf
>
> [2] http://www.twonine.co.uk/articles/SeparatingInterfaceAndImplementation.pdf

Thanks! Those were good reads. If you've more articles, I would be more then happy to read :).

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

> > > Is libmircookie really something that will need to be released in sync
> with
> > > every Mir release? Or should it be a separate project?
> >
> > I dont have a clear answer for this one. I just imagine since the project is
> > pretty small it makes it easier to keep it in lp:mir atm? Will have to poke
> > raof about that one.
>
> A further thought, the header is dependent on MirCookie (introduced here into
> include/common/mir_toolkit/common.h). That makes client code (compile time)
> dependent on libmircommon-dev.
>
> As we're introducing this dependency on mircommon, could we just put the code
> into libmircommon and not a separate library?
>
> Or should MirCookie be defined in libmircookie-dev and not libmircommon-dev?

Well I dont see why it couldnt be defined in libmircookie-dev. I think *if* we keep libmircookie-dev we should move the MirCookie into it (unless Im missing something for why this isnt possible). If we move to libmircommon-dev then we'll keep it :).

Need to talk with RAOF about it as well

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

+class CookieFactory
...

Should delete copy constructor and assignment.

~~~~

+ static unsigned const minimum_secret_size;

1. Ought to be public, so that it can be accessed by client code.

2. Not providing the value in the header allows it to be changed without breaking ABI, but it would probably be more convenient for users to have the value visible.

~~~~

+ * Contruction function used to create a CookieFactory. The secret size must be
+ * greater then minimum_secret_size otherwise an expection will be thrown
...
+ * Contruction function used to create a CookieFactory as well as a secret.
+ * The secret size must be greater then minimum_secret_size otherwise an expection will be thrown

s/greater then/no less than/

~~~~

+/**
+* Checks entropy exists on the system, then fills in a vector with random numbers
+* up to size.
+*
+* \param [in] size The number of random numbers to generate.
+* \return A filled in vector with random numbers up to size
+*/
+std::vector<uint8_t> get_random_data(unsigned size);

Not needed in the API

~~~~

+unsigned const min_secret_size{8};

This isn't needed, minimum_secret_size should be initialized directly.

~~~~~

+namespace
+{
+ unsigned const secret_size{64};
+}

There ought to be a guarantee (e.g. a static assert) that this is greater than CookieFactory::minimum_secret_size. (And that is why I think CookieFactory::minimum_secret_size should be public and evaluate at compile time.)

~~~~

+TEST(MirCookieFactory, throw_when_secret_size_to_small)
+{
+ std::vector<uint8_t> bob{ 0x01 };
+ EXPECT_THROW({
+ auto factory = mir::cookie::CookieFactory::create(bob);
+ }, std::logic_error);
+}

OK, but it is better to test the "off by one" case. I.e. CookieFactory::minimum_secret_size (as the documentation says) or CookieFactory::minimum_secret_size-1 (as the code says).

~~~~

There are missing tests:

1. create(unsigned secret_size, std::vector<uint8_t>& save_secret);

This should be tested to check that a secret of the right size is saved, and that it throws if too small a size is requested.

2. get_random_data(unsigned size);

*If* this is in the API there should be corresponding tests.

~~~~

+MIR_COOKIE_1 {
+ global:
+ extern "C++" {
+ mir::cookie::CookieFactory::CookieFactory*;
+ mir::cookie::CookieFactory::?CookieFactory*;
+ mir::cookie::CookieFactory::create*;
+ mir::cookie::CookieFactory::timestamp_to_cookie*;
+ mir::cookie::CookieFactory::attest_timestamp*;
+ mir::cookie::get_random_data*;
+ };
+ local: *;
+};

The only functions that need to be exported are mir::cookie::CookieFactory::create*; (and, if it is in the API mir::cookie::get_random_data*;)

review: Needs Fixing
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Just a bit of musing, not anything that requires action:

The create functions could be:

std::unique_ptr<CookieFactory> create_from_secret(std::vector<uint8_t> const& save_secret);
std::unique_ptr<CookieFactory> create_saving_secret(std::vector<uint8_t>& save_secret, unsigned secret_size = 2*minimum_secret_size);

That might be more convenient for code that doesn't care about the secret size. There could also be other variants:

std::unique_ptr<CookieFactory> create_keeping_secret(unsigned secret_size = 2*minimum_secret_size);

Which would support user code that just does:

    auto const cf = CookieFactory::create_keeping_secret();

Maybe not an important use case?

~~~~

It could also be convenient to include a definition of Secret:

    using Secret = std::vector<uint8_t>;

and use that in both the interface and user code.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

+TEST(MirCookieFactory, timestamp_trusted_with_saved_secret_does_attest)
+{
+ uint64_t timestamp = 23;
+ unsigned secret_size = 64;
+ std::vector<uint8_t> secret;
+
+ auto factory = mir::cookie::CookieFactory::create_saving_secret(secret, secret_size);
+ auto cookie = factory->timestamp_to_cookie(timestamp);
+
+ EXPECT_TRUE(factory->attest_timestamp(cookie));
+}

Not unreasonable, but I was actually thinking of...

    uint64_t timestamp = 23;
    unsigned secret_size = 64;
    std::vector<uint8_t> secret;

    auto const source_factory = mir::cookie::CookieFactory::create_saving_secret(secret, secret_size);
    auto const sink_factory = mir::cookie::CookieFactory::create_from_secret(secret);
    auto cookie = source_factory->timestamp_to_cookie(timestamp);

    EXPECT_TRUE(sink_factory->attest_timestamp(cookie));

Which as one of the use cases we intend to support is something we should be testing.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

For me, this is the remaining blocker:

> > > > Is libmircookie really something that will need to be released in sync
> > with
> > > > every Mir release? Or should it be a separate project?
> > >
> > > I dont have a clear answer for this one. I just imagine since the project
> is
> > > pretty small it makes it easier to keep it in lp:mir atm? Will have to
> poke
> > > raof about that one.
> >
> > A further thought, the header is dependent on MirCookie (introduced here
> into
> > include/common/mir_toolkit/common.h). That makes client code (compile time)
> > dependent on libmircommon-dev.
> >
> > As we're introducing this dependency on mircommon, could we just put the
> code
> > into libmircommon and not a separate library?
> >
> > Or should MirCookie be defined in libmircookie-dev and not libmircommon-dev?
>
> Well I dont see why it couldnt be defined in libmircookie-dev. I think *if* we
> keep libmircookie-dev we should move the MirCookie into it (unless Im missing
> something for why this isnt possible). If we move to libmircommon-dev then
> we'll keep it :).
>
> Need to talk with RAOF about it as well

review: Needs Information
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

It would be good for cookie clients to have a narrow, stable API.

To which end we should keep libmircookie as a separate project and make it independent of libmircommon-dev.

review: Needs Fixing
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

> It would be good for cookie clients to have a narrow, stable API.
>
> To which end we should keep libmircookie as a separate project and make it
> independent of libmircommon-dev.

Done. Is it fine to keep the entire mir cookie project inside lp:mir or should a new project be created?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

On Wednesday, 16 September 2015 20:47:24 BST, Brandon Schaefer wrote:
>> It would be good for cookie clients to have a narrow, stable API.
>>
>> To which end we should keep libmircookie as a separate project
>> and make it
>> independent of libmircommon-dev.
>
> Done. Is it fine to keep the entire mir cookie project inside
> lp:mir or should a new project be created?

Fine in mir tree. (For now anyway)

--
Alan Griffiths. +44 (0)798 9938 758
Octopull Limited. http://www.octopull.co.uk/

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

Hmm the only issue im not 100% sure about now. Is how we are going to expose the MirCookie to the mir_toolkit C api. I suppose Ill need to create a C api for the CookieFactory just to hold the MirCookie?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Pushed trivial fixes:

1. src/CMakeLists.txt to build cookie independently of any other Mir code
2. include/cookie/mir_toolkit/cookie.h to compile standalone and add to mir_toolkit docs
3. include/cookie/mir/cookie_factory.h include include/cookie/mir_toolkit/cookie.h first (to ensure it compiles standalone) and move doc comment to correct type.

review: Approve
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

NB I think the server API needs extending to retrieve the generated secret, but that can come in the next MP.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Kevin DuBois (kdub) wrote :

+ static std::unique_ptr<CookieFactory> create_saving_secret(Secret& save_secret,
+ unsigned secret_size = 2 * minimum_secret_size);
+ static std::unique_ptr<CookieFactory> create_keeping_secret(unsigned secret_size = 2 * minimum_secret_size);

http://unity.ubuntu.com/mir/cppguide/index.html?showone=Default_Arguments#Default_Arguments

not too flummoxed by that, the rest lgtm

review: Approve
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

Looks good.

Not blocking, but I find the following API simpler to understand:

create_with_new_secret(unsigned int secret_size);
create_with_existing_secret(Secret const& secret);
Secret secret();

Of course, with this API the secret is no longer really "secret" (i.e. internal to the class), but I don't think it matters. If one has access to the class, then it doesn't really matter if one also has the secret (for our use cases at least).

review: Approve
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

Overall looks good for me.

This has changed quite a bit since the last time. When do we need override_the_cookie_factory(secret)?

nit :+ 470 braces of lambda functor..

review: Approve
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

> Looks good.
>
> Not blocking, but I find the following API simpler to understand:
>
> create_with_new_secret(unsigned int secret_size);
> create_with_existing_secret(Secret const& secret);
> Secret secret();
>
> Of course, with this API the secret is no longer really "secret" (i.e.
> internal to the class), but I don't think it matters. If one has access to the
> class, then it doesn't really matter if one also has the secret (for our use
> cases at least).

A main use of the API is to share the secret around once its been created. So we need to be able to expose the secret on creation so we can share it with another factory.

Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

LGTM

review: Approve
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

> Overall looks good for me.
>
> This has changed quite a bit since the last time. When do we need
> override_the_cookie_factory(secret)?
>

This will be used in the client side of things. Not a perfect split from server/client but it will be used :).

> nit :+ 470 braces of lambda functor..

Revision history for this message
Tyler Hicks (tyhicks) wrote :

Hello - A review from the security team was requested and I've started to look at the code. However, I feel like I'm missing a lot of context from the greater design. It seems like this is only the very low-level building blocks needed by the greater design.

1) Does any documentation exist on the design? Thomas spoke with us about potential high-level designs months ago and I roughly understand which direction you all chose but this HMAC design was not one that we discussed at that time.

2) How will content-hub register itself with Mir? What's the flow of cookies between Mir, the foreground app, and content-hub during copy and paste events?

3) What prevents malicious apps from attempting to brute force cookies? I don't see any type of penalty implementation for repeated, bad guesses. Is content-hub expected to implement that?

review: Needs Information
Revision history for this message
Chris Halse Rogers (raof) wrote :

1) The copy/paste doc is at https://docs.google.com/document/d/16qCQ8vYJmS7da1yfzBuKWnlUCrw_IXXsTIhWruPN0nk

2) The design is for content-hub to share a secret with Mir, and each construct their own instance of CookieFactory from that secret. Content-hub does not need to register itself with Mir. The copy/paste doc has some cookie flow examples.

3) The Mir calls which accept a MirCookie¹ will disconnect any client that submits an invalid cookie. Content-hub should probably do the same. Would it be better to (a) document that any attestation failure should be fatal to the client, and (b) rename “bool attest_timestamp(MirCookie const& cookie)” to “assert_timestamp()” and have it throw an exception on failure?

¹: Here's an example: https://code.launchpad.net/~mir-team/mir/cookie-raise-surface/+merge/274728

Revision history for this message
Tyler Hicks (tyhicks) wrote :

1) Thanks! The copy/paste doc helps a lot but it lacks implementation details. For example, "secret" isn't mentioned anywhere in the document. It does get me closer to understanding the design.

2) I'm confused by the "Content-hub does not need to register itself with Mir" bit since content-hub must share a secret with Mir and then Mir's CookieFactory is constructed from that secret. How does Mir know that the CookieFactory that it constructed was done so with a secret from Content-hub and not some other process?

3) If Mir disconnects a client that submits an invalid cookie, what prevents the client from reconnecting and submitting another invalid cookie?
  - Documenting that any attestation failure should be fatal to the client would be a good thing as long as a reconnecting is expensive.
  - IIUC, assert_timestamp() is in the address space of the client, correct? If so, throwing an exception on failure would do no good because a malicious client could link against a modified library that doesn't throw an exception.

review: Needs Information
Revision history for this message
Chris Halse Rogers (raof) wrote :

1, 2) Yeah, that bit of the design is unspecified. I was assuming that we'd either go for:
 a) A parent process of unity8 and content-hub generates a secret and fd-passes it to both, or
 b) Unity8 and content-hub share a file readable only by them, or
 c) content-hub is started by unity8 and gets the secret fd-passed to it.

I'm not sufficiently familiar with the global design to know which one is most appropriate.

3) Unity8 only allows one connection per client. Actually, that's a lie; Unity8 effectively allows any client to connect to it, but in *theory* Unity8 only allows one connection per client. In theory, unity8 only allows applications started by upstart-app-launch and only allows one connection per launch, but in practice it allows anything that has --desktop-file-hint=valid.desktop on its commandline, or any binary whose name starts with maliit-server or which contains qt5/libexec/QtWebProcess.

So reconnection *should* be expensive, but currently isn't.

assert_timestamp() is in the server's address space; the secret required to assert a timestamp isn't available in the client address space.

Revision history for this message
Tyler Hicks (tyhicks) wrote :

1, 2) Ok, I'll follow up with tvoss on that aspect of the design.

3) We could probably do something as simple as adding a penalty delay after each failed attest attempt, before disconnecting the client.

Note that I also included a couple inline questions.

review: Needs Information
Revision history for this message
Chris Halse Rogers (raof) wrote :

3) It'd be reasonably complex to add a penalty delay after a failed attest attempt, but possible. It's much simpler for us to just disconnect the client.

Replies inline.

Revision history for this message
Tyler Hicks (tyhicks) wrote :

Left some inline replies/comments.

review: Needs Fixing
Revision history for this message
Tyler Hicks (tyhicks) wrote :

One more inline comment...

Revision history for this message
Chris Halse Rogers (raof) wrote :

Replies also inline.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2015-08-12 09:52:57 +0000
3+++ CMakeLists.txt 2015-09-17 09:09:28 +0000
4@@ -175,6 +175,7 @@
5 find_package(LTTngUST REQUIRED)
6 pkg_check_modules(UDEV REQUIRED libudev)
7 pkg_check_modules(GLIB REQUIRED glib-2.0)
8+pkg_check_modules(NETTLE REQUIRED nettle)
9 pkg_check_modules(UUID REQUIRED uuid)
10
11 include_directories (${GLESv2_INCLUDE_DIRS})
12
13=== modified file 'cmake/ABICheck.cmake'
14--- cmake/ABICheck.cmake 2015-07-23 02:39:20 +0000
15+++ cmake/ABICheck.cmake 2015-09-17 09:09:28 +0000
16@@ -79,6 +79,7 @@
17 make_lib_descriptor(client)
18 make_lib_descriptor(server)
19 make_lib_descriptor(common INCLUDE_PRIVATE EXCLUDE_HEADERS ${mircommon-exclude-headers})
20+make_lib_descriptor(cookie)
21 make_lib_descriptor(platform INCLUDE_PRIVATE EXCLUDE_HEADERS ${mirplatform-exclude-headers})
22 if(MIR_BUILD_PLATFORM_MESA_KMS)
23 make_lib_descriptor(clientplatformmesa LIBRARY_HEADER ${CMAKE_SOURCE_DIR}/src/include/client/mir/client_platform_factory.h)
24@@ -124,7 +125,7 @@
25 )
26 endmacro(_define_abi_check_for)
27
28-set(the_libs mirserver mirclient mircommon mirplatform)
29+set(the_libs mirserver mirclient mircommon mirplatform mircookie)
30 if(MIR_BUILD_PLATFORM_MESA_KMS)
31 set(the_libs ${the_libs} mirclientplatformmesa mirplatformgraphicsmesakms)
32 endif()
33
34=== modified file 'debian/control'
35--- debian/control 2015-09-15 12:37:25 +0000
36+++ debian/control 2015-09-17 09:09:28 +0000
37@@ -43,6 +43,7 @@
38 uuid-dev,
39 python3:any,
40 dh-python,
41+ nettle-dev,
42 Standards-Version: 3.9.4
43 Homepage: https://launchpad.net/mir
44 # If you aren't a member of ~mir-team but need to upload packaging changes,
45@@ -402,6 +403,33 @@
46 This package depends on a full set of graphics drivers for running Mir on top
47 of an existing Android driver stack.
48
49+Package: libmircookie1
50+Section: libs
51+Architecture: any
52+Multi-Arch: same
53+Pre-Depends: ${misc:Pre-Depends}
54+Depends: ${misc:Depends},
55+Description: Produce and verify spoof-resistant timestamps - runtime library
56+ libmircookie provides a simple mechanism for a group of cooperating processes
57+ to hand out and verify difficult-to-forge timestamps to untrusted 3rd parties.
58+ .
59+ This package contains the runtime library for generating and verifying the
60+ attestable timestamps.
61+
62+Package: libmircookie-dev
63+Section: libdevel
64+Architecture: any
65+Multi-Arch: same
66+Pre-Depends: ${misc:Pre-Depends}
67+Depends: libmircookie1 (= ${binary:Version}),
68+ ${misc:Depends},
69+Description: Produce and verify spoof-resistant timestamps - development headers
70+ libmircookie provides a simple mechanism for a group of cooperating processes
71+ to hand out and verify difficult-to-forge timestamps to untrusted 3rd parties.
72+ .
73+ This package contains the development headers for building programs that
74+ generate or verify the attestable timestamps.
75+
76 Package: python3-mir-perf-framework
77 Section: python
78 Architecture: all
79
80=== added file 'debian/libmircookie-dev.install'
81--- debian/libmircookie-dev.install 1970-01-01 00:00:00 +0000
82+++ debian/libmircookie-dev.install 2015-09-17 09:09:28 +0000
83@@ -0,0 +1,3 @@
84+/usr/include/mircookie/
85+/usr/lib/*/libmircookie.so
86+/usr/lib/*/pkgconfig/mircookie.pc
87
88=== added file 'debian/libmircookie1.install'
89--- debian/libmircookie1.install 1970-01-01 00:00:00 +0000
90+++ debian/libmircookie1.install 2015-09-17 09:09:28 +0000
91@@ -0,0 +1,1 @@
92+/usr/lib/*/libmircookie.so.1
93
94=== modified file 'debian/mir-test-tools.install'
95--- debian/mir-test-tools.install 2015-06-23 08:00:53 +0000
96+++ debian/mir-test-tools.install 2015-09-17 09:09:28 +0000
97@@ -10,3 +10,4 @@
98 usr/lib/*/mir/server-platform/graphics-dummy.so
99 usr/lib/*/mir/server-platform/input-stub.so
100 usr/lib/*/mir/client-platform/dummy.so
101+usr/share/udev_recordings
102
103=== added directory 'include/cookie'
104=== added directory 'include/cookie/mir'
105=== added file 'include/cookie/mir/cookie_factory.h'
106--- include/cookie/mir/cookie_factory.h 1970-01-01 00:00:00 +0000
107+++ include/cookie/mir/cookie_factory.h 2015-09-17 09:09:28 +0000
108@@ -0,0 +1,104 @@
109+/*
110+ * Copyright © 2015 Canonical Ltd.
111+ *
112+ * This program is free software: you can redistribute it and/or modify
113+ * it under the terms of the GNU General Public License version 3 as
114+ * published by the Free Software Foundation.
115+ *
116+ * This program is distributed in the hope that it will be useful,
117+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
118+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
119+ * GNU General Public License for more details.
120+ *
121+ * You should have received a copy of the GNU General Public License
122+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
123+ *
124+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
125+ */
126+
127+#ifndef MIR_COOKIE_COOKIE_FACTORY_H_
128+#define MIR_COOKIE_COOKIE_FACTORY_H_
129+
130+#include "mir_toolkit/cookie.h"
131+
132+#include <vector>
133+#include <memory>
134+
135+namespace mir
136+{
137+namespace cookie
138+{
139+using Secret = std::vector<uint8_t>;
140+
141+/**
142+ * \brief A source of moderately-difficult-to-spoof cookies.
143+ *
144+ * The primary motivation for this is to provide event timestamps that clients find it difficult to spoof.
145+ * This is useful for focus grant and similar operations where shell behaviour should be dependent on
146+ * the timestamp of the client event that caused the request.
147+ *
148+ * Some spoofing protection is desirable; experience with X clients shows that they will go to some effort
149+ * to attempt to bypass focus stealing prevention.
150+ *
151+ */
152+class CookieFactory
153+{
154+public:
155+ /**
156+ * Contruction function used to create a CookieFactory. The secret size must be
157+ * no less then minimum_secret_size otherwise an expection will be thrown
158+ *
159+ * \param [in] secret A filled in secret used to set the key for the hash function
160+ * \return A unique_ptr CookieFactory
161+ */
162+ static std::unique_ptr<CookieFactory> create_from_secret(Secret const& secret);
163+
164+ /**
165+ * Contruction function used to create a CookieFactory as well as a secret.
166+ * The secret size must be no less then minimum_secret_size otherwise an expection will be thrown
167+ *
168+ * \param [in] secret_size The size of the secret to create, must be larger then minimum_secret_size
169+ * \param [out] save_secret The secret that was created.
170+ * \return A unique_ptr CookieFactory
171+ */
172+ static std::unique_ptr<CookieFactory> create_saving_secret(Secret& save_secret,
173+ unsigned secret_size = 2 * minimum_secret_size);
174+
175+ /**
176+ * Contruction function used to create a CookieFactory and a secret which it keeps internally.
177+ * The secret size must be no less then minimum_secret_size otherwise an expection will be thrown
178+ *
179+ * \param [in] secret_size The size of the secret to create, must be larger then minimum_secret_size
180+ * \return A unique_ptr CookieFactory
181+ */
182+ static std::unique_ptr<CookieFactory> create_keeping_secret(unsigned secret_size = 2 * minimum_secret_size);
183+
184+ CookieFactory(CookieFactory const& factory) = delete;
185+ CookieFactory& operator=(CookieFactory const& factory) = delete;
186+ virtual ~CookieFactory() noexcept = default;
187+
188+ /**
189+ * Turns a timestamp into a MAC and returns a MirCookie.
190+ *
191+ * \param [in] timestamp The timestamp
192+ * \return MirCookie with the stored MAC and timestamp
193+ */
194+ virtual MirCookie timestamp_to_cookie(uint64_t const& timestamp) = 0;
195+
196+ /**
197+ * Checks that a MirCookie is a valid MirCookie.
198+ *
199+ * \param [in] cookie A created MirCookie
200+ * \return True when the MirCookie is valid, False when the MirCookie is not valid
201+ */
202+ virtual bool attest_timestamp(MirCookie const& cookie) = 0;
203+
204+ static unsigned const minimum_secret_size = 8;
205+
206+protected:
207+ CookieFactory() = default;
208+};
209+
210+}
211+}
212+#endif // MIR_COOKIE_COOKIE_FACTORY_H_
213
214=== added directory 'include/cookie/mir_toolkit'
215=== added file 'include/cookie/mir_toolkit/cookie.h'
216--- include/cookie/mir_toolkit/cookie.h 1970-01-01 00:00:00 +0000
217+++ include/cookie/mir_toolkit/cookie.h 2015-09-17 09:09:28 +0000
218@@ -0,0 +1,44 @@
219+/*
220+ * Copyright © 2015 Canonical Ltd.
221+ *
222+ * This program is free software: you can redistribute it and/or modify
223+ * it under the terms of the GNU General Public License version 3 as
224+ * published by the Free Software Foundation.
225+ *
226+ * This program is distributed in the hope that it will be useful,
227+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
228+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
229+ * GNU General Public License for more details.
230+ *
231+ * You should have received a copy of the GNU General Public License
232+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
233+ *
234+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
235+ */
236+
237+#ifndef MIR_TOOLKIT_COOKIE_FACTORY_H_
238+#define MIR_TOOLKIT_COOKIE_FACTORY_H_
239+
240+#include <stdint.h>
241+
242+/**
243+ * \addtogroup mir_toolkit
244+ * @{
245+ */
246+/* This is C code. Not C++. */
247+#ifdef __cplusplus
248+extern "C" {
249+#endif
250+
251+typedef struct MirCookie
252+{
253+ uint64_t timestamp;
254+ uint64_t mac;
255+} MirCookie;
256+
257+#ifdef __cplusplus
258+}
259+#endif
260+/**@}*/
261+
262+#endif // MIR_TOOLKIT_COOKIE_FACTORY_H_
263
264=== modified file 'include/server/mir/server.h'
265--- include/server/mir/server.h 2015-09-08 03:25:06 +0000
266+++ include/server/mir/server.h 2015-09-17 09:09:28 +0000
267@@ -34,6 +34,10 @@
268 namespace input { class CompositeEventFilter; class InputDispatcher; class CursorListener; class TouchVisualizer; class InputDeviceHub;}
269 namespace logging { class Logger; }
270 namespace options { class Option; }
271+namespace cookie
272+{
273+using Secret = std::vector<uint8_t>;
274+}
275 namespace shell
276 {
277 class DisplayLayout;
278@@ -79,6 +83,13 @@
279 /// This must remain valid while apply_settings() and run() are called.
280 void set_command_line(int argc, char const* argv[]);
281
282+ /// creates the CookieFactory from the given secret
283+ /// This secret is used to generate timestamps that can be attested to by
284+ /// libmircookie. Any process this secret is shared with can verify Mir-generated
285+ /// cookies, or produce their own.
286+ /// \note If not explicitly set, a random secret will be chosen.
287+ void override_the_cookie_factory(mir::cookie::Secret const& secret);
288+
289 /// Applies any configuration options, hooks, or custom implementations.
290 /// Must be called before calling run() or accessing any mir subsystems.
291 void apply_settings();
292@@ -381,6 +392,7 @@
293 /// using the format "fd://%d".
294 auto open_prompt_socket() -> Fd;
295 /** @} */
296+
297 private:
298 struct ServerConfiguration;
299 struct Self;
300
301=== modified file 'include/test/mir_test_framework/executable_path.h'
302--- include/test/mir_test_framework/executable_path.h 2015-06-22 03:04:56 +0000
303+++ include/test/mir_test_framework/executable_path.h 2015-09-17 09:09:28 +0000
304@@ -26,6 +26,7 @@
305 std::string executable_path();
306
307 std::string library_path();
308+std::string udev_recordings_path();
309 std::string server_platform(std::string const& name);
310 std::string client_platform(std::string const& name);
311 }
312
313=== modified file 'src/CMakeLists.txt'
314--- src/CMakeLists.txt 2015-09-07 11:54:56 +0000
315+++ src/CMakeLists.txt 2015-09-17 09:09:28 +0000
316@@ -1,6 +1,9 @@
317 # We need MIRPLATFORM_ABI in both libmirplatform and the platform implementations.
318 set(MIRPLATFORM_ABI 10)
319
320+# Add the cookie implementation before exposing any APIs
321+add_subdirectory(cookie/)
322+
323 # We need MIR_CLIENT_PLATFORM_PATH in both libmirclient and the platform
324 # implementations
325 set(MIR_CLIENT_PLATFORM_PATH
326
327=== added directory 'src/cookie'
328=== added file 'src/cookie/CMakeLists.txt'
329--- src/cookie/CMakeLists.txt 1970-01-01 00:00:00 +0000
330+++ src/cookie/CMakeLists.txt 2015-09-17 09:09:28 +0000
331@@ -0,0 +1,48 @@
332+set(PREFIX "${CMAKE_INSTALL_PREFIX}")
333+set(EXEC_PREFIX "${CMAKE_INSTALL_PREFIX}")
334+set(LIBDIR "${CMAKE_INSTALL_FULL_LIBDIR}")
335+set(INCLUDEDIR "${CMAKE_INSTALL_PREFIX}/include")
336+
337+configure_file(
338+ ${CMAKE_CURRENT_SOURCE_DIR}/mircookie.pc.in
339+ ${CMAKE_CURRENT_BINARY_DIR}/mircookie.pc
340+ @ONLY
341+)
342+
343+include_directories(
344+ ${PROJECT_SOURCE_DIR}/include/cookie
345+ ${NETTLE_INCLUDE_DIRS}
346+)
347+
348+set(MIRCOOKIE_ABI 1)
349+set(symbol_map ${CMAKE_SOURCE_DIR}/src/cookie/symbols.map)
350+
351+add_library(mircookie SHARED
352+
353+ cookie_factory.cpp
354+)
355+
356+set_target_properties(mircookie
357+ PROPERTIES
358+ SOVERSION ${MIRCOOKIE_ABI}
359+ LINK_FLAGS "-Wl,--exclude-libs=ALL -Wl,--version-script,${symbol_map}"
360+)
361+
362+target_link_libraries(mircookie
363+ ${NETTLE_LDFLAGS} ${NETTLE_LIBS}
364+)
365+
366+install(
367+ TARGETS mircookie
368+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
369+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
370+
371+install(
372+ DIRECTORY ${CMAKE_SOURCE_DIR}/include/cookie/mir
373+ DESTINATION "include/mircookie"
374+)
375+
376+install(
377+ FILES ${CMAKE_CURRENT_BINARY_DIR}/mircookie.pc
378+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
379+)
380
381=== added file 'src/cookie/cookie_factory.cpp'
382--- src/cookie/cookie_factory.cpp 1970-01-01 00:00:00 +0000
383+++ src/cookie/cookie_factory.cpp 2015-09-17 09:09:28 +0000
384@@ -0,0 +1,161 @@
385+/*
386+ * Copyright © 2015 Canonical Ltd.
387+ *
388+ * This program is free software: you can redistribute it and/or modify
389+ * it under the terms of the GNU General Public License version 3 as
390+ * published by the Free Software Foundation.
391+ *
392+ * This program is distributed in the hope that it will be useful,
393+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
394+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
395+ * GNU General Public License for more details.
396+ *
397+ * You should have received a copy of the GNU General Public License
398+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
399+ *
400+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
401+ */
402+
403+#include "mir/cookie_factory.h"
404+
405+#include <algorithm>
406+#include <random>
407+#include <memory>
408+#include <system_error>
409+
410+#include <nettle/hmac.h>
411+#include <linux/random.h>
412+#include <sys/types.h>
413+#include <sys/stat.h>
414+#include <fcntl.h>
415+#include <sys/select.h>
416+
417+#include <boost/throw_exception.hpp>
418+
419+namespace
420+{
421+std::string const random_device_path{"/dev/random"};
422+std::string const urandom_device_path{"/dev/urandom"};
423+int const wait_seconds{30};
424+}
425+
426+static mir::cookie::Secret get_random_data(unsigned size)
427+{
428+ mir::cookie::Secret buffer(size);
429+ int random_fd;
430+ int retval;
431+ fd_set rfds;
432+
433+ struct timeval tv;
434+ tv.tv_sec = wait_seconds;
435+ tv.tv_usec = 0;
436+
437+ if ((random_fd = open(random_device_path.c_str(), O_RDONLY)) == -1)
438+ {
439+ int error = errno;
440+ BOOST_THROW_EXCEPTION(std::system_error(error, std::system_category(),
441+ "open failed on device " + random_device_path));
442+ }
443+
444+ FD_ZERO(&rfds);
445+ FD_SET(random_fd, &rfds);
446+
447+ /* We want to block until *some* entropy exists on boot, then use urandom once we have some */
448+ retval = select(random_fd + 1, &rfds, NULL, NULL, &tv);
449+
450+ /* We are done with /dev/random at this point, and it is either an error or ready to be read */
451+ if (close(random_fd) == -1)
452+ {
453+ int error = errno;
454+ BOOST_THROW_EXCEPTION(std::system_error(error, std::system_category(),
455+ "close failed on device " + random_device_path));
456+ }
457+
458+ if (retval == -1)
459+ {
460+ int error = errno;
461+ BOOST_THROW_EXCEPTION(std::system_error(error, std::system_category(),
462+ "select failed on file descriptor " + std::to_string(random_fd) +
463+ " from device " + random_device_path));
464+ }
465+ else if (retval && FD_ISSET(random_fd, &rfds))
466+ {
467+ std::uniform_int_distribution<uint8_t> dist;
468+ std::random_device rand_dev(urandom_device_path);
469+
470+ std::generate(std::begin(buffer), std::end(buffer), [&]() {
471+ return dist(rand_dev);
472+ });
473+ }
474+ else
475+ {
476+ BOOST_THROW_EXCEPTION(std::runtime_error("Failed to read from device: " + random_device_path +
477+ " after: " + std::to_string(wait_seconds) + " seconds"));
478+ }
479+
480+ return buffer;
481+}
482+
483+class CookieFactoryNettle : public mir::cookie::CookieFactory
484+{
485+public:
486+ CookieFactoryNettle(mir::cookie::Secret const& secret)
487+ {
488+ if (secret.size() < minimum_secret_size)
489+ BOOST_THROW_EXCEPTION(std::logic_error("Secret size " + std::to_string(secret.size()) + " is to small, require " +
490+ std::to_string(minimum_secret_size) + " or greater."));
491+
492+ hmac_sha1_set_key(&ctx, secret.size(), secret.data());
493+ }
494+
495+ virtual ~CookieFactoryNettle() noexcept = default;
496+
497+ MirCookie timestamp_to_cookie(uint64_t const& timestamp) override
498+ {
499+ MirCookie cookie { timestamp, 0 };
500+ calculate_mac(cookie);
501+ return cookie;
502+ }
503+
504+ bool attest_timestamp(MirCookie const& cookie) override
505+ {
506+ return verify_mac(cookie);
507+ }
508+
509+private:
510+ void calculate_mac(MirCookie& cookie)
511+ {
512+ hmac_sha1_update(&ctx, sizeof(cookie.timestamp), reinterpret_cast<uint8_t*>(&cookie.timestamp));
513+ hmac_sha1_digest(&ctx, sizeof(cookie.mac), reinterpret_cast<uint8_t*>(&cookie.mac));
514+ }
515+
516+ bool verify_mac(MirCookie const& cookie)
517+ {
518+ decltype(cookie.mac) calculated_mac;
519+ uint8_t* message = reinterpret_cast<uint8_t*>(const_cast<decltype(cookie.timestamp)*>(&cookie.timestamp));
520+ hmac_sha1_update(&ctx, sizeof(cookie.timestamp), message);
521+ hmac_sha1_digest(&ctx, sizeof(calculated_mac), reinterpret_cast<uint8_t*>(&calculated_mac));
522+
523+ return calculated_mac == cookie.mac;
524+ }
525+
526+ struct hmac_sha1_ctx ctx;
527+};
528+
529+std::unique_ptr<mir::cookie::CookieFactory> mir::cookie::CookieFactory::create_from_secret(mir::cookie::Secret const& secret)
530+{
531+ return std::make_unique<CookieFactoryNettle>(secret);
532+}
533+
534+std::unique_ptr<mir::cookie::CookieFactory> mir::cookie::CookieFactory::create_saving_secret(mir::cookie::Secret& save_secret,
535+ unsigned secret_size)
536+{
537+ save_secret = get_random_data(secret_size);
538+ return std::make_unique<CookieFactoryNettle>(save_secret);
539+}
540+
541+std::unique_ptr<mir::cookie::CookieFactory> mir::cookie::CookieFactory::create_keeping_secret(unsigned secret_size)
542+{
543+ auto secret = get_random_data(secret_size);
544+ return std::make_unique<CookieFactoryNettle>(secret);
545+}
546
547=== added file 'src/cookie/mircookie.pc.in'
548--- src/cookie/mircookie.pc.in 1970-01-01 00:00:00 +0000
549+++ src/cookie/mircookie.pc.in 2015-09-17 09:09:28 +0000
550@@ -0,0 +1,11 @@
551+prefix=@PREFIX@
552+exec_prefix=@EXEC_PREFIX@
553+libdir=@LIBDIR@
554+includedir=@INCLUDEDIR@/mircookie
555+
556+Name: mircookie
557+Description: Mir client library
558+Version: @MIR_VERSION@
559+Requires.private: nettle
560+Libs: -L${libdir} -lmircookie
561+Cflags: -I${includedir}
562
563=== added file 'src/cookie/symbols.map'
564--- src/cookie/symbols.map 1970-01-01 00:00:00 +0000
565+++ src/cookie/symbols.map 2015-09-17 09:09:28 +0000
566@@ -0,0 +1,9 @@
567+MIR_COOKIE_1 {
568+ global:
569+ extern "C++" {
570+ mir::cookie::CookieFactory::create_from_secret*;
571+ mir::cookie::CookieFactory::create_saving_secret*;
572+ mir::cookie::CookieFactory::create_keeping_secret*;
573+ };
574+ local: *;
575+};
576
577=== modified file 'src/include/server/mir/default_server_configuration.h'
578--- src/include/server/mir/default_server_configuration.h 2015-09-08 03:25:06 +0000
579+++ src/include/server/mir/default_server_configuration.h 2015-09-17 09:09:28 +0000
580@@ -41,6 +41,10 @@
581 class SharedLibrary;
582 class SharedLibraryProberReport;
583
584+namespace cookie
585+{
586+class CookieFactory;
587+}
588 namespace dispatch
589 {
590 class MultiplexingDispatchable;
591@@ -186,7 +190,8 @@
592 std::shared_ptr<DisplayChanger> the_display_changer() override;
593 std::shared_ptr<graphics::Platform> the_graphics_platform() override;
594 std::shared_ptr<input::InputDispatcher> the_input_dispatcher() override;
595- std::shared_ptr<EmergencyCleanup> the_emergency_cleanup() override;
596+ std::shared_ptr<EmergencyCleanup> the_emergency_cleanup() override;
597+ std::shared_ptr<cookie::CookieFactory> the_cookie_factory() override;
598 /**
599 * Function to call when a "fatal" error occurs. This implementation allows
600 * the default strategy to be overridden by --on-fatal-error-abort to force a
601@@ -441,6 +446,7 @@
602 CachedPtr<SharedLibraryProberReport> shared_library_prober_report;
603 CachedPtr<shell::Shell> shell;
604 CachedPtr<scene::ApplicationNotRespondingDetector> application_not_responding_detector;
605+ CachedPtr<cookie::CookieFactory> cookie_factory;
606
607 private:
608 std::shared_ptr<options::Configuration> const configuration_options;
609
610=== modified file 'src/include/server/mir/server_configuration.h'
611--- src/include/server/mir/server_configuration.h 2015-06-18 04:38:28 +0000
612+++ src/include/server/mir/server_configuration.h 2015-09-17 09:09:28 +0000
613@@ -22,6 +22,10 @@
614
615 namespace mir
616 {
617+namespace cookie
618+{
619+class CookieFactory;
620+}
621 namespace compositor
622 {
623 class Compositor;
624@@ -75,6 +79,7 @@
625 virtual std::shared_ptr<DisplayChanger> the_display_changer() = 0;
626 virtual std::shared_ptr<graphics::Platform> the_graphics_platform() = 0;
627 virtual std::shared_ptr<EmergencyCleanup> the_emergency_cleanup() = 0;
628+ virtual std::shared_ptr<cookie::CookieFactory> the_cookie_factory() = 0;
629 virtual auto the_fatal_error_strategy() -> void (*)(char const* reason, ...) = 0;
630 virtual std::shared_ptr<scene::ApplicationNotRespondingDetector> the_application_not_responding_detector() = 0;
631
632
633=== modified file 'src/server/CMakeLists.txt'
634--- src/server/CMakeLists.txt 2015-09-07 11:54:56 +0000
635+++ src/server/CMakeLists.txt 2015-09-17 09:09:28 +0000
636@@ -10,6 +10,7 @@
637 ${PROJECT_SOURCE_DIR}/src/include/platform
638 ${PROJECT_SOURCE_DIR}/src/include/client
639 ${PROJECT_SOURCE_DIR}/src/include/server
640+ ${PROJECT_SOURCE_DIR}/include/cookie
641 ${GLIB_INCLUDE_DIRS}
642 )
643 add_definitions(-DMIR_SERVER_INPUT_PLATFORM_VERSION="${MIR_SERVER_INPUT_PLATFORM_VERSION}")
644@@ -95,7 +96,7 @@
645 mirplatform
646 mircommon
647 mirprotobuf
648-
649+ mircookie
650 xcursorloader-static
651
652 ${GLog_LIBRARY}
653
654=== modified file 'src/server/default_server_configuration.cpp'
655--- src/server/default_server_configuration.cpp 2015-06-17 05:20:42 +0000
656+++ src/server/default_server_configuration.cpp 2015-09-17 09:09:28 +0000
657@@ -1,5 +1,5 @@
658 /*
659- * Copyright © 2012-2014 Canonical Ltd.
660+ * Copyright © 2012-2015 Canonical Ltd.
661 *
662 * This program is free software: you can redistribute it and/or modify it
663 * under the terms of the GNU General Public License version 3,
664@@ -24,6 +24,7 @@
665 #include "mir/default_server_status_listener.h"
666 #include "mir/emergency_cleanup.h"
667 #include "mir/default_configuration.h"
668+#include "mir/cookie_factory.h"
669
670 #include "mir/logging/dumb_console_logger.h"
671 #include "mir/options/program_option.h"
672@@ -41,6 +42,8 @@
673 #include "mir/scene/null_prompt_session_listener.h"
674 #include "default_emergency_cleanup.h"
675
676+#include <type_traits>
677+
678 namespace mc = mir::compositor;
679 namespace geom = mir::geometry;
680 namespace mf = mir::frontend;
681@@ -51,6 +54,11 @@
682 namespace msh = mir::shell;
683 namespace mi = mir::input;
684
685+namespace
686+{
687+ unsigned const secret_size{64};
688+}
689+
690 mir::DefaultServerConfiguration::DefaultServerConfiguration(int argc, char const* argv[]) :
691 DefaultServerConfiguration(std::make_shared<mo::DefaultConfiguration>(argc, argv))
692 {
693@@ -173,6 +181,18 @@
694 });
695 }
696
697+std::shared_ptr<mir::cookie::CookieFactory> mir::DefaultServerConfiguration::the_cookie_factory()
698+{
699+ return cookie_factory(
700+ []()
701+ {
702+ static_assert(secret_size >= mir::cookie::CookieFactory::minimum_secret_size,
703+ "Secret size is smaller then the minimum secret size");
704+
705+ return mir::cookie::CookieFactory::create_keeping_secret(secret_size);
706+ });
707+}
708+
709 auto mir::DefaultServerConfiguration::the_fatal_error_strategy()
710 -> void (*)(char const* reason, ...)
711 {
712
713=== modified file 'src/server/server.cpp'
714--- src/server/server.cpp 2015-09-08 03:25:06 +0000
715+++ src/server/server.cpp 2015-09-17 09:09:28 +0000
716@@ -29,6 +29,7 @@
717 #include "mir/main_loop.h"
718 #include "mir/report_exception.h"
719 #include "mir/run_mir.h"
720+#include "mir/cookie_factory.h"
721
722 // TODO these are used to frig a stub renderer when running headless
723 #include "mir/compositor/renderer.h"
724@@ -101,6 +102,7 @@
725 std::weak_ptr<options::Option> options;
726 std::string config_file;
727 std::shared_ptr<ServerConfiguration> server_config;
728+ std::shared_ptr<cookie::CookieFactory> cookie_factory;
729
730 std::function<void()> init_callback{[]{}};
731 int argc{0};
732@@ -207,6 +209,15 @@
733 return mir::DefaultServerConfiguration::the_renderer_factory();
734 }
735
736+ auto the_cookie_factory() -> std::shared_ptr<cookie::CookieFactory> override
737+ {
738+ if (self->cookie_factory)
739+ {
740+ return self->cookie_factory;
741+ }
742+ return mir::DefaultServerConfiguration::the_cookie_factory();
743+ }
744+
745 using mir::DefaultServerConfiguration::the_options;
746
747 FOREACH_OVERRIDE(MIR_SERVER_CONFIG_OVERRIDE)
748@@ -277,6 +288,12 @@
749 self->argv = argv;
750 }
751
752+void mir::Server::override_the_cookie_factory(mir::cookie::Secret const& secret)
753+{
754+ verify_setting_allowed(self->server_config);
755+ self->cookie_factory = mir::cookie::CookieFactory::create_from_secret(secret);
756+}
757+
758 void mir::Server::add_init_callback(std::function<void()> const& init_callback)
759 {
760 auto const& existing = self->init_callback;
761
762=== modified file 'src/server/symbols.map'
763--- src/server/symbols.map 2015-09-11 12:29:40 +0000
764+++ src/server/symbols.map 2015-09-17 09:09:28 +0000
765@@ -135,6 +135,7 @@
766 mir::ServerActionQueue::?ServerActionQueue*;
767 mir::ServerActionQueue::ServerActionQueue*;
768 mir::Server::add_configuration_option*;
769+ mir::Server::override_the_cookie_factory*;
770 mir::Server::add_emergency_cleanup*;
771 mir::Server::add_init_callback*;
772 mir::Server::apply_settings*;
773@@ -590,6 +591,7 @@
774 mir::DefaultServerConfiguration::the_buffer_stream_factory*;
775 mir::DefaultServerConfiguration::the_clock*;
776 mir::DefaultServerConfiguration::the_composite_event_filter*;
777+ mir::DefaultServerConfiguration::the_cookie_factory*;
778 mir::DefaultServerConfiguration::the_event_filter_chain_dispatcher*;
779 mir::DefaultServerConfiguration::the_surface_input_dispatcher;
780 mir::DefaultServerConfiguration::the_compositor*;
781
782=== modified file 'tests/acceptance-tests/CMakeLists.txt'
783--- tests/acceptance-tests/CMakeLists.txt 2015-09-09 18:59:38 +0000
784+++ tests/acceptance-tests/CMakeLists.txt 2015-09-17 09:09:28 +0000
785@@ -2,6 +2,8 @@
786
787 include_directories(
788 ${CMAKE_SOURCE_DIR}
789+ ${PROJECT_SOURCE_DIR}/include/cookie
790+ ${UMOCKDEV_INCLUDE_DIRS}
791 )
792
793 set(
794@@ -80,6 +82,7 @@
795 mirclient-debug-extension
796 mirserver
797
798+ ${UMOCKDEV_LIBRARIES}
799 ${CMAKE_THREAD_LIBS_INIT} # Link in pthread.
800 )
801
802
803=== modified file 'tests/include/mir_test_framework/udev_environment.h'
804--- tests/include/mir_test_framework/udev_environment.h 2015-02-22 07:46:25 +0000
805+++ tests/include/mir_test_framework/udev_environment.h 2015-09-17 09:09:28 +0000
806@@ -53,6 +53,32 @@
807 */
808 void add_standard_device(std::string const& name);
809
810+ /**
811+ * Load an ioctl recording for a UMockdev device
812+ *
813+ * Looks for a <tt>name</tt>.ioctl file recorded with umockdev-record --ioctl
814+ * and adds it to the associated device in the testbed.
815+ *
816+ * The udev records for the device these ioctl records will be associated with
817+ * must already exist in the testbed
818+ *
819+ * @param name The unadorned filename for the ioctl records to add
820+ */
821+ void load_device_ioctls(std::string const& name);
822+
823+ /**
824+ * Load an evemu evdev recording for a UMockdev device
825+ *
826+ * Looks for a <tt>name</tt>.evemu file recorded with umockdev-record --evemu
827+ * (or evemu-record) and associates it with the udev device it was recorded from.
828+ *
829+ * The udev records for the device this recording is associated with
830+ * must already exist in the testbed
831+ *
832+ * @param name The unadorned filename for the ioctl records to add
833+ */
834+ void load_device_evemu(std::string const& name);
835+
836 UMockdevTestbed *testbed;
837 std::string const recordings_path;
838 };
839
840=== modified file 'tests/integration-tests/CMakeLists.txt'
841--- tests/integration-tests/CMakeLists.txt 2015-09-09 18:59:38 +0000
842+++ tests/integration-tests/CMakeLists.txt 2015-09-17 09:09:28 +0000
843@@ -4,6 +4,7 @@
844 ${CMAKE_SOURCE_DIR}
845 ${PROTOBUF_INCLUDE_DIRS}
846 ${CMAKE_CURRENT_BINARY_DIR}
847+ ${PROJECT_SOURCE_DIR}/include/cookie
848 ${PROJECT_SOURCE_DIR}/src/include/platform
849 ${PROJECT_SOURCE_DIR}/src/include/common
850 ${PROJECT_SOURCE_DIR}/src/include/server
851
852=== modified file 'tests/mir_test_framework/CMakeLists.txt'
853--- tests/mir_test_framework/CMakeLists.txt 2015-09-14 17:04:48 +0000
854+++ tests/mir_test_framework/CMakeLists.txt 2015-09-17 09:09:28 +0000
855@@ -16,6 +16,8 @@
856 -DMIR_SERVER_PLATFORM_PATH="${MIR_SERVER_PLATFORM_PATH}"
857 -DMIR_CLIENT_PLATFORM_ABI_STRING="${MIR_CLIENT_PLATFORM_ABI}"
858 -DMIR_SERVER_GRAPHICS_PLATFORM_ABI_STRING="${MIR_SERVER_GRAPHICS_PLATFORM_ABI}"
859+ -DMIR_INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}"
860+ -DMIR_BUILD_PREFIX="${CMAKE_BINARY_DIR}"
861 )
862
863 add_library(mir-public-test-framework OBJECT
864@@ -161,3 +163,5 @@
865 install(TARGETS mirplatformgraphicsstub LIBRARY DESTINATION ${MIR_SERVER_PLATFORM_PATH})
866
867 install(TARGETS mirclientplatformstub LIBRARY DESTINATION ${MIR_CLIENT_PLATFORM_PATH})
868+
869+add_subdirectory(udev_recordings/)
870
871=== modified file 'tests/mir_test_framework/executable_path.cpp'
872--- tests/mir_test_framework/executable_path.cpp 2015-06-17 05:20:42 +0000
873+++ tests/mir_test_framework/executable_path.cpp 2015-09-17 09:09:28 +0000
874@@ -45,6 +45,19 @@
875 return executable_path() + "/../lib";
876 }
877
878+std::string mir_test_framework::udev_recordings_path()
879+{
880+ std::string run_path = executable_path() + "/udev_recordings";
881+ std::string install_path = MIR_INSTALL_PREFIX"/share/udev_recordings";
882+
883+ if (boost::filesystem::exists(run_path))
884+ return run_path;
885+ else if (boost::filesystem::exists(install_path))
886+ return install_path;
887+
888+ BOOST_THROW_EXCEPTION(std::runtime_error("Failed to find udev_recordings in standard search locations"));
889+}
890+
891 std::string mir_test_framework::server_platform(std::string const& name)
892 {
893 std::string libname{name};
894
895=== modified file 'tests/mir_test_framework/udev_environment.cpp'
896--- tests/mir_test_framework/udev_environment.cpp 2015-02-22 07:46:25 +0000
897+++ tests/mir_test_framework/udev_environment.cpp 2015-09-17 09:09:28 +0000
898@@ -38,7 +38,7 @@
899 namespace mtf = mir_test_framework;
900
901 mtf::UdevEnvironment::UdevEnvironment()
902- : recordings_path(mtf::executable_path() + "/udev_recordings")
903+ : recordings_path(mtf::udev_recordings_path())
904 {
905 testbed = umockdev_testbed_new();
906 }
907@@ -109,4 +109,41 @@
908 }
909 }
910 }
911+
912+ auto script_filename = recordings_path + "/" + name + ".script";
913+ if (stat(script_filename.c_str(), &sb) == 0)
914+ {
915+ if (S_ISREG(sb.st_mode) || S_ISLNK(sb.st_mode))
916+ {
917+ if (!umockdev_testbed_load_script(testbed, NULL, script_filename.c_str(), &err))
918+ {
919+ BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Failed to load device recording: ") +
920+ err->message));
921+ }
922+ }
923+ }
924+}
925+
926+void mtf::UdevEnvironment::load_device_ioctls(std::string const& name)
927+{
928+ auto ioctls_filename = recordings_path + "/" + name + ".ioctl";
929+
930+ GError* err = nullptr;
931+ if (!umockdev_testbed_load_ioctl(testbed, NULL, ioctls_filename.c_str(), &err))
932+ {
933+ BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Failed to load ioctl recording: ") +
934+ err->message));
935+ }
936+}
937+
938+void mtf::UdevEnvironment::load_device_evemu(std::string const& name)
939+{
940+ auto evemu_filename = recordings_path + "/" + name + ".evemu";
941+
942+ GError* err = nullptr;
943+ if (!umockdev_testbed_load_evemu_events(testbed, NULL, evemu_filename.c_str(), &err))
944+ {
945+ BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Failed to load evemu recording: ") +
946+ err->message));
947+ }
948 }
949
950=== added file 'tests/mir_test_framework/udev_recordings/CMakeLists.txt'
951--- tests/mir_test_framework/udev_recordings/CMakeLists.txt 1970-01-01 00:00:00 +0000
952+++ tests/mir_test_framework/udev_recordings/CMakeLists.txt 2015-09-17 09:09:28 +0000
953@@ -0,0 +1,3 @@
954+file(GLOB UDEV_FILES *.umockdev *.ioctl *.evemu)
955+
956+install(FILES ${UDEV_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/share/udev_recordings)
957
958=== added file 'tests/mir_test_framework/udev_recordings/laptop-keyboard-hello.evemu'
959--- tests/mir_test_framework/udev_recordings/laptop-keyboard-hello.evemu 1970-01-01 00:00:00 +0000
960+++ tests/mir_test_framework/udev_recordings/laptop-keyboard-hello.evemu 2015-09-17 09:09:28 +0000
961@@ -0,0 +1,272 @@
962+# device /dev/input/event4
963+# EVEMU 1.2
964+# Input device name: "AT Translated Set 2 keyboard"
965+# Input device ID: bus 0x11 vendor 0x01 product 0x01 version 0xab83
966+# Supported events:
967+# Event type 0 (EV_SYN)
968+# Event code 0 (SYN_REPORT)
969+# Event code 1 (SYN_CONFIG)
970+# Event code 4 (FF_STATUS_STOPPED)
971+# Event code 17 ((null))
972+# Event code 20 ((null))
973+# Event type 1 (EV_KEY)
974+# Event code 1 (KEY_ESC)
975+# Event code 2 (KEY_1)
976+# Event code 3 (KEY_2)
977+# Event code 4 (KEY_3)
978+# Event code 5 (KEY_4)
979+# Event code 6 (KEY_5)
980+# Event code 7 (KEY_6)
981+# Event code 8 (KEY_7)
982+# Event code 9 (KEY_8)
983+# Event code 10 (KEY_9)
984+# Event code 11 (KEY_0)
985+# Event code 12 (KEY_MINUS)
986+# Event code 13 (KEY_EQUAL)
987+# Event code 14 (KEY_BACKSPACE)
988+# Event code 15 (KEY_TAB)
989+# Event code 16 (KEY_Q)
990+# Event code 17 (KEY_W)
991+# Event code 18 (KEY_E)
992+# Event code 19 (KEY_R)
993+# Event code 20 (KEY_T)
994+# Event code 21 (KEY_Y)
995+# Event code 22 (KEY_U)
996+# Event code 23 (KEY_I)
997+# Event code 24 (KEY_O)
998+# Event code 25 (KEY_P)
999+# Event code 26 (KEY_LEFTBRACE)
1000+# Event code 27 (KEY_RIGHTBRACE)
1001+# Event code 28 (KEY_ENTER)
1002+# Event code 29 (KEY_LEFTCTRL)
1003+# Event code 30 (KEY_A)
1004+# Event code 31 (KEY_S)
1005+# Event code 32 (KEY_D)
1006+# Event code 33 (KEY_F)
1007+# Event code 34 (KEY_G)
1008+# Event code 35 (KEY_H)
1009+# Event code 36 (KEY_J)
1010+# Event code 37 (KEY_K)
1011+# Event code 38 (KEY_L)
1012+# Event code 39 (KEY_SEMICOLON)
1013+# Event code 40 (KEY_APOSTROPHE)
1014+# Event code 41 (KEY_GRAVE)
1015+# Event code 42 (KEY_LEFTSHIFT)
1016+# Event code 43 (KEY_BACKSLASH)
1017+# Event code 44 (KEY_Z)
1018+# Event code 45 (KEY_X)
1019+# Event code 46 (KEY_C)
1020+# Event code 47 (KEY_V)
1021+# Event code 48 (KEY_B)
1022+# Event code 49 (KEY_N)
1023+# Event code 50 (KEY_M)
1024+# Event code 51 (KEY_COMMA)
1025+# Event code 52 (KEY_DOT)
1026+# Event code 53 (KEY_SLASH)
1027+# Event code 54 (KEY_RIGHTSHIFT)
1028+# Event code 55 (KEY_KPASTERISK)
1029+# Event code 56 (KEY_LEFTALT)
1030+# Event code 57 (KEY_SPACE)
1031+# Event code 58 (KEY_CAPSLOCK)
1032+# Event code 59 (KEY_F1)
1033+# Event code 60 (KEY_F2)
1034+# Event code 61 (KEY_F3)
1035+# Event code 62 (KEY_F4)
1036+# Event code 63 (KEY_F5)
1037+# Event code 64 (KEY_F6)
1038+# Event code 65 (KEY_F7)
1039+# Event code 66 (KEY_F8)
1040+# Event code 67 (KEY_F9)
1041+# Event code 68 (KEY_F10)
1042+# Event code 69 (KEY_NUMLOCK)
1043+# Event code 70 (KEY_SCROLLLOCK)
1044+# Event code 71 (KEY_KP7)
1045+# Event code 72 (KEY_KP8)
1046+# Event code 73 (KEY_KP9)
1047+# Event code 74 (KEY_KPMINUS)
1048+# Event code 75 (KEY_KP4)
1049+# Event code 76 (KEY_KP5)
1050+# Event code 77 (KEY_KP6)
1051+# Event code 78 (KEY_KPPLUS)
1052+# Event code 79 (KEY_KP1)
1053+# Event code 80 (KEY_KP2)
1054+# Event code 81 (KEY_KP3)
1055+# Event code 82 (KEY_KP0)
1056+# Event code 83 (KEY_KPDOT)
1057+# Event code 85 (KEY_ZENKAKUHANKAKU)
1058+# Event code 86 (KEY_102ND)
1059+# Event code 87 (KEY_F11)
1060+# Event code 88 (KEY_F12)
1061+# Event code 89 (KEY_RO)
1062+# Event code 90 (KEY_KATAKANA)
1063+# Event code 91 (KEY_HIRAGANA)
1064+# Event code 92 (KEY_HENKAN)
1065+# Event code 93 (KEY_KATAKANAHIRAGANA)
1066+# Event code 94 (KEY_MUHENKAN)
1067+# Event code 95 (KEY_KPJPCOMMA)
1068+# Event code 96 (KEY_KPENTER)
1069+# Event code 97 (KEY_RIGHTCTRL)
1070+# Event code 98 (KEY_KPSLASH)
1071+# Event code 99 (KEY_SYSRQ)
1072+# Event code 100 (KEY_RIGHTALT)
1073+# Event code 102 (KEY_HOME)
1074+# Event code 103 (KEY_UP)
1075+# Event code 104 (KEY_PAGEUP)
1076+# Event code 105 (KEY_LEFT)
1077+# Event code 106 (KEY_RIGHT)
1078+# Event code 107 (KEY_END)
1079+# Event code 108 (KEY_DOWN)
1080+# Event code 109 (KEY_PAGEDOWN)
1081+# Event code 110 (KEY_INSERT)
1082+# Event code 111 (KEY_DELETE)
1083+# Event code 112 (KEY_MACRO)
1084+# Event code 113 (KEY_MUTE)
1085+# Event code 114 (KEY_VOLUMEDOWN)
1086+# Event code 115 (KEY_VOLUMEUP)
1087+# Event code 116 (KEY_POWER)
1088+# Event code 117 (KEY_KPEQUAL)
1089+# Event code 118 (KEY_KPPLUSMINUS)
1090+# Event code 119 (KEY_PAUSE)
1091+# Event code 121 (KEY_KPCOMMA)
1092+# Event code 122 (KEY_HANGEUL)
1093+# Event code 123 (KEY_HANJA)
1094+# Event code 124 (KEY_YEN)
1095+# Event code 125 (KEY_LEFTMETA)
1096+# Event code 126 (KEY_RIGHTMETA)
1097+# Event code 127 (KEY_COMPOSE)
1098+# Event code 128 (KEY_STOP)
1099+# Event code 140 (KEY_CALC)
1100+# Event code 142 (KEY_SLEEP)
1101+# Event code 143 (KEY_WAKEUP)
1102+# Event code 155 (KEY_MAIL)
1103+# Event code 156 (KEY_BOOKMARKS)
1104+# Event code 157 (KEY_COMPUTER)
1105+# Event code 158 (KEY_BACK)
1106+# Event code 159 (KEY_FORWARD)
1107+# Event code 163 (KEY_NEXTSONG)
1108+# Event code 164 (KEY_PLAYPAUSE)
1109+# Event code 165 (KEY_PREVIOUSSONG)
1110+# Event code 166 (KEY_STOPCD)
1111+# Event code 172 (KEY_HOMEPAGE)
1112+# Event code 173 (KEY_REFRESH)
1113+# Event code 183 (KEY_F13)
1114+# Event code 184 (KEY_F14)
1115+# Event code 185 (KEY_F15)
1116+# Event code 217 (KEY_SEARCH)
1117+# Event code 226 (KEY_MEDIA)
1118+# Event type 4 (EV_MSC)
1119+# Event code 4 (MSC_SCAN)
1120+# Event type 17 (EV_LED)
1121+# Event code 0 (LED_NUML)
1122+# Event code 1 (LED_CAPSL)
1123+# Event code 2 (LED_SCROLLL)
1124+# Event type 20 (EV_REP)
1125+# Properties:
1126+# N: AT Translated Set 2 keyboard
1127+# I: 0011 0001 0001 ab83
1128+# P: 00 00 00 00 00 00 00 00
1129+# B: 00 13 00 12 00 00 00 00 00
1130+# B: 01 fe ff ff ff ff ff ff ff
1131+# B: 01 ff ff ef ff df ff ff fe
1132+# B: 01 01 d0 00 f8 78 30 80 03
1133+# B: 01 00 00 00 02 04 00 00 00
1134+# B: 01 00 00 00 00 00 00 00 00
1135+# B: 01 00 00 00 00 00 00 00 00
1136+# B: 01 00 00 00 00 00 00 00 00
1137+# B: 01 00 00 00 00 00 00 00 00
1138+# B: 01 00 00 00 00 00 00 00 00
1139+# B: 01 00 00 00 00 00 00 00 00
1140+# B: 01 00 00 00 00 00 00 00 00
1141+# B: 01 00 00 00 00 00 00 00 00
1142+# B: 02 00 00 00 00 00 00 00 00
1143+# B: 03 00 00 00 00 00 00 00 00
1144+# B: 04 10 00 00 00 00 00 00 00
1145+# B: 05 00 00 00 00 00 00 00 00
1146+# B: 11 07 00 00 00 00 00 00 00
1147+# B: 12 00 00 00 00 00 00 00 00
1148+# B: 15 00 00 00 00 00 00 00 00
1149+# B: 15 00 00 00 00 00 00 00 00
1150+################################
1151+# Waiting for events #
1152+################################
1153+E: 0.000000 0004 0004 0028 # EV_MSC / MSC_SCAN 28
1154+E: 0.000000 0001 001c 0000 # EV_KEY / KEY_ENTER 0
1155+E: 0.000000 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1156+E: 0.825945 0004 0004 0054 # EV_MSC / MSC_SCAN 54
1157+E: 0.825945 0001 0036 0001 # EV_KEY / KEY_RIGHTSHIFT 1
1158+E: 0.825945 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1159+E: 1.101404 0004 0004 0054 # EV_MSC / MSC_SCAN 54
1160+E: 1.101404 0001 0036 0002 # EV_KEY / KEY_RIGHTSHIFT 2
1161+E: 1.101404 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1162+E: 1.131449 0004 0004 0054 # EV_MSC / MSC_SCAN 54
1163+E: 1.131449 0001 0036 0002 # EV_KEY / KEY_RIGHTSHIFT 2
1164+E: 1.131449 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1165+E: 1.161472 0004 0004 0036 # EV_MSC / MSC_SCAN 36
1166+E: 1.161472 0001 0024 0001 # EV_KEY / KEY_J 1
1167+E: 1.161472 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1168+E: 1.221981 0004 0004 0036 # EV_MSC / MSC_SCAN 36
1169+E: 1.221981 0001 0024 0000 # EV_KEY / KEY_J 0
1170+E: 1.221981 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1171+E: 1.241906 0004 0004 0054 # EV_MSC / MSC_SCAN 54
1172+E: 1.241906 0001 0036 0000 # EV_KEY / KEY_RIGHTSHIFT 0
1173+E: 1.241906 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1174+E: 1.346773 0004 0004 0032 # EV_MSC / MSC_SCAN 32
1175+E: 1.346773 0001 0020 0001 # EV_KEY / KEY_D 1
1176+E: 1.346773 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1177+E: 1.432213 0004 0004 0032 # EV_MSC / MSC_SCAN 32
1178+E: 1.432213 0001 0020 0000 # EV_KEY / KEY_D 0
1179+E: 1.432213 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1180+E: 1.562060 0004 0004 0025 # EV_MSC / MSC_SCAN 25
1181+E: 1.562060 0001 0019 0001 # EV_KEY / KEY_P 1
1182+E: 1.562060 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1183+E: 1.622477 0004 0004 0025 # EV_MSC / MSC_SCAN 25
1184+E: 1.622477 0001 0019 0000 # EV_KEY / KEY_P 0
1185+E: 1.622477 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1186+E: 1.687305 0004 0004 0025 # EV_MSC / MSC_SCAN 25
1187+E: 1.687305 0001 0019 0001 # EV_KEY / KEY_P 1
1188+E: 1.687305 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1189+E: 1.777807 0004 0004 0025 # EV_MSC / MSC_SCAN 25
1190+E: 1.777807 0001 0019 0000 # EV_KEY / KEY_P 0
1191+E: 1.777807 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1192+E: 1.867577 0004 0004 0031 # EV_MSC / MSC_SCAN 31
1193+E: 1.867577 0001 001f 0001 # EV_KEY / KEY_S 1
1194+E: 1.867577 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1195+E: 1.953019 0004 0004 0031 # EV_MSC / MSC_SCAN 31
1196+E: 1.953019 0001 001f 0000 # EV_KEY / KEY_S 0
1197+E: 1.953019 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1198+E: 2.118004 0004 0004 0054 # EV_MSC / MSC_SCAN 54
1199+E: 2.118004 0001 0036 0001 # EV_KEY / KEY_RIGHTSHIFT 1
1200+E: 2.118004 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1201+E: 2.348316 0004 0004 0002 # EV_MSC / MSC_SCAN 2
1202+E: 2.348316 0001 0002 0001 # EV_KEY / KEY_1 1
1203+E: 2.348316 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1204+E: 2.489561 0004 0004 0002 # EV_MSC / MSC_SCAN 2
1205+E: 2.489561 0001 0002 0000 # EV_KEY / KEY_1 0
1206+E: 2.489561 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1207+E: 2.523740 0004 0004 0054 # EV_MSC / MSC_SCAN 54
1208+E: 2.523740 0001 0036 0000 # EV_KEY / KEY_RIGHTSHIFT 0
1209+E: 2.523740 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1210+E: 6.714286 0004 0004 0029 # EV_MSC / MSC_SCAN 29
1211+E: 6.714286 0001 001d 0001 # EV_KEY / KEY_LEFTCTRL 1
1212+E: 6.714286 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1213+E: 6.989728 0004 0004 0029 # EV_MSC / MSC_SCAN 29
1214+E: 6.989728 0001 001d 0002 # EV_KEY / KEY_LEFTCTRL 2
1215+E: 6.989728 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1216+E: 7.019767 0004 0004 0029 # EV_MSC / MSC_SCAN 29
1217+E: 7.019767 0001 001d 0002 # EV_KEY / KEY_LEFTCTRL 2
1218+E: 7.019767 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1219+E: 7.049847 0004 0004 0029 # EV_MSC / MSC_SCAN 29
1220+E: 7.049847 0001 001d 0002 # EV_KEY / KEY_LEFTCTRL 2
1221+E: 7.049847 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1222+E: 7.079889 0004 0004 0029 # EV_MSC / MSC_SCAN 29
1223+E: 7.079889 0001 001d 0002 # EV_KEY / KEY_LEFTCTRL 2
1224+E: 7.079889 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1225+E: 7.109934 0004 0004 0029 # EV_MSC / MSC_SCAN 29
1226+E: 7.109934 0001 001d 0002 # EV_KEY / KEY_LEFTCTRL 2
1227+E: 7.109934 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1228+E: 7.139943 0004 0004 0029 # EV_MSC / MSC_SCAN 29
1229+E: 7.139943 0001 001d 0002 # EV_KEY / KEY_LEFTCTRL 2
1230+E: 7.139943 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1231+E: 7.170005 0004 0004 0023 # EV_MSC / MSC_SCAN 23
1232+E: 7.170005 0001 0017 0001 # EV_KEY / KEY_I 1
1233+E: 7.170005 0000 0000 0000 # ------------ SYN_REPORT (0) ----------
1234
1235=== modified file 'tests/unit-tests/CMakeLists.txt'
1236--- tests/unit-tests/CMakeLists.txt 2015-09-07 11:54:56 +0000
1237+++ tests/unit-tests/CMakeLists.txt 2015-09-17 09:09:28 +0000
1238@@ -25,6 +25,7 @@
1239 ${GBM_INCLUDE_DIRS}
1240 ${UMOCKDEV_INCLUDE_DIRS}
1241
1242+ ${PROJECT_SOURCE_DIR}/include/cookie
1243 ${PROJECT_SOURCE_DIR}/src/include/platform
1244 ${PROJECT_SOURCE_DIR}/src/include/server
1245 ${PROJECT_SOURCE_DIR}/src/include/client
1246@@ -67,6 +68,7 @@
1247 test_shared_library_prober.cpp
1248 test_lockable_callback.cpp
1249 test_module_deleter.cpp
1250+ test_mir_cookie.cpp
1251 )
1252
1253 if (MIR_TEST_PLATFORM STREQUAL "mesa-x11")
1254
1255=== added file 'tests/unit-tests/test_mir_cookie.cpp'
1256--- tests/unit-tests/test_mir_cookie.cpp 1970-01-01 00:00:00 +0000
1257+++ tests/unit-tests/test_mir_cookie.cpp 2015-09-17 09:09:28 +0000
1258@@ -0,0 +1,105 @@
1259+/*
1260+ * Copyright © 2015 Canonical Ltd.
1261+ *
1262+ * This program is free software: you can redistribute it and/or modify
1263+ * it under the terms of the GNU General Public License version 3 as
1264+ * published by the Free Software Foundation.
1265+ *
1266+ * This program is distributed in the hope that it will be useful,
1267+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1268+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1269+ * GNU General Public License for more details.
1270+ *
1271+ * You should have received a copy of the GNU General Public License
1272+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1273+ *
1274+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1275+ */
1276+
1277+#include "mir_toolkit/mir_client_library.h"
1278+#include "mir/cookie_factory.h"
1279+
1280+#include "mir_test_framework/headless_test.h"
1281+#include "mir_test_framework/connected_client_with_a_surface.h"
1282+#include "mir/test/doubles/wrap_shell_to_track_latest_surface.h"
1283+#include "mir/shell/shell_wrapper.h"
1284+#include "mir/test/validity_matchers.h"
1285+#include "mir/test/wait_condition.h"
1286+
1287+#include "boost/throw_exception.hpp"
1288+
1289+#include <gtest/gtest.h>
1290+
1291+namespace mtf = mir_test_framework;
1292+namespace mtd = mir::test::doubles;
1293+namespace msh = mir::shell;
1294+
1295+TEST(MirCookieFactory, attests_real_timestamp)
1296+{
1297+ std::vector<uint8_t> secret{ 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0xde, 0x01 };
1298+ auto factory = mir::cookie::CookieFactory::create_from_secret(secret);
1299+
1300+ uint64_t mock_timestamp{0x322322322332};
1301+
1302+ auto cookie = factory->timestamp_to_cookie(mock_timestamp);
1303+
1304+ EXPECT_TRUE(factory->attest_timestamp(cookie));
1305+}
1306+
1307+TEST(MirCookieFactory, doesnt_attest_faked_timestamp)
1308+{
1309+ std::vector<uint8_t> secret{ 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0xde, 0x01 };
1310+ auto factory = mir::cookie::CookieFactory::create_from_secret(secret);
1311+
1312+ MirCookie bad_client_no_biscuit{ 0x33221100, 0x33221100 };
1313+
1314+ EXPECT_FALSE(factory->attest_timestamp(bad_client_no_biscuit));
1315+}
1316+
1317+TEST(MirCookieFactory, timestamp_trusted_with_different_secret_doesnt_attest)
1318+{
1319+ std::vector<uint8_t> alice{ 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0xde, 0x01 };
1320+ std::vector<uint8_t> bob{ 0x01, 0x02, 0x44, 0xd8, 0xee, 0x0f, 0xde, 0x01 };
1321+
1322+ auto alices_factory = mir::cookie::CookieFactory::create_from_secret(alice);
1323+ auto bobs_factory = mir::cookie::CookieFactory::create_from_secret(bob);
1324+
1325+ uint64_t mock_timestamp{0x01020304};
1326+
1327+ auto alices_cookie = alices_factory->timestamp_to_cookie(mock_timestamp);
1328+ auto bobs_cookie = bobs_factory->timestamp_to_cookie(mock_timestamp);
1329+
1330+ EXPECT_FALSE(alices_factory->attest_timestamp(bobs_cookie));
1331+ EXPECT_FALSE(bobs_factory->attest_timestamp(alices_cookie));
1332+}
1333+
1334+TEST(MirCookieFactory, throw_when_secret_size_to_small)
1335+{
1336+ std::vector<uint8_t> bob(mir::cookie::CookieFactory::minimum_secret_size - 1);
1337+ EXPECT_THROW({
1338+ auto factory = mir::cookie::CookieFactory::create_from_secret(bob);
1339+ }, std::logic_error);
1340+}
1341+
1342+TEST(MirCookieFactory, saves_a_secret)
1343+{
1344+ std::vector<uint8_t> secret;
1345+ unsigned secret_size = 64;
1346+
1347+ mir::cookie::CookieFactory::create_saving_secret(secret, secret_size);
1348+
1349+ EXPECT_EQ(secret.size(), secret_size);
1350+}
1351+
1352+TEST(MirCookieFactory, timestamp_trusted_with_saved_secret_does_attest)
1353+{
1354+ uint64_t timestamp = 23;
1355+ unsigned secret_size = 64;
1356+ std::vector<uint8_t> secret;
1357+
1358+ auto source_factory = mir::cookie::CookieFactory::create_saving_secret(secret, secret_size);
1359+ auto sink_factory = mir::cookie::CookieFactory::create_from_secret(secret);
1360+ auto cookie = source_factory->timestamp_to_cookie(timestamp);
1361+
1362+ EXPECT_TRUE(sink_factory->attest_timestamp(cookie));
1363+}
1364
1365=== modified file 'tools/update_package_abis.sh'
1366--- tools/update_package_abis.sh 2015-08-07 05:46:34 +0000
1367+++ tools/update_package_abis.sh 2015-09-17 09:09:28 +0000
1368@@ -16,6 +16,7 @@
1369 libmirplatform:MIRPLATFORM_ABI \
1370 libmirprotobuf:MIRPROTOBUF_ABI \
1371 libmirserver:MIRSERVER_ABI \
1372+ libmircookie:MIRCOOKIE_ABI \
1373 mir-client-platform-android:MIR_CLIENT_PLATFORM_ABI \
1374 mir-client-platform-mesa:MIR_CLIENT_PLATFORM_ABI \
1375 mir-platform-graphics-android:MIR_SERVER_GRAPHICS_PLATFORM_ABI \

Subscribers

People subscribed via source and target branches