Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Code for libprio pilot #1

Merged
merged 13 commits into from
Jul 17, 2018
Merged

Code for libprio pilot #1

merged 13 commits into from
Jul 17, 2018

Conversation

henrycg
Copy link
Collaborator

@henrycg henrycg commented Jun 25, 2018

These commits contain all of the libprio library code, broken down by functional component. The four commits contain:

  • the core code for the libprio client and servers,
  • the tests and example code form invoking libprio,
  • a copy of the NSS MPI bignum library (needed to use libprio as a standalone library), and
  • the master build file for scons and a README.

The first commit (with the core code) contains the header files and .c files that will need to go into the browser implementation of libprio, so this code should receive the most scrutiny.

Henry Corrigan-Gibbs added 4 commits June 25, 2018 14:36
These files contain the core cryptographic routines needed
to implement the Prio client and Prio server.
Since NSS does not export the MPI bignum library, we ship a copy
with the standalone version of libprio.
Copy link
Contributor

@franziskuskiefer franziskuskiefer left a comment

Choose a reason for hiding this comment

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

First rounds of comments.

SConstruct Outdated
env.Append(CPPPATH = ["#include", "#."])

SConscript('prio/SConscript', variant_dir='build/prio')
SConscript('mpi/SConscript', variant_dir='build/mpi')
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to see that we can use another copy of MPI here. That would allow us to use the copy in the Firefox tree. That should be relatively easy to do. We can keep the copy in the repo here to allow stand-alone builds but not vendor it into Firefox.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@rhelmer Rob: Is this something that you can take care of on your side? I'm happy to modify the SConstruct file in libprio to accommodate using an external copy of MPI, but I'm not sure what the best way to do this is.

SConstruct Outdated
Copyright (c) 2012-2014 Stanford University
Copyright (c) 2018 Henry Corrigan-Gibbs

Permission to use, copy, modify, and distribute this software for any
Copy link
Contributor

Choose a reason for hiding this comment

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

When adding to Firefox you should check that the license is ok to use.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@rhelmer Rob: Let me know if you are going to need to include this file in the browser code. If using the ISC license here will be a problem, I can throw out this SConstruct file and write a new MPL-licensed one from scratch.

* Opaque types
*/
typedef struct prio_config *PrioConfig;
typedef const struct prio_config *const_PrioConfig;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I'm a fan of typedefing the const version of the struct. But nothing wrong with it I guess.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, got it. For my future reference, what is the right way to do this? Would it be better to not use these typedefd struct at all?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd probably just use a const *PrioConfig etc. But not really a big deal.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Got it. Thanks.

prio/prg.c Outdated
#include "share.h"
#include "util.h"

#define AES_BLOCK_SIZE 16
Copy link
Contributor

Choose a reason for hiding this comment

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

You could take this from blapit.h instead of re-defining it here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Great. Will do.

prio/prg.c Outdated
PRG
PRG_new (const PrioPRGSeed key_in)
{
PRG prg = malloc (sizeof (*prg));
Copy link
Contributor

Choose a reason for hiding this comment

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

sizeof(PRG)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These are equivalent, right? To make the code easier to read, I'll change to sizeof(PRG).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Whoops. After running valgrind, I realized that this change introduced a bug.

The sizeof(PRG) is the size of a struct prg* (i.e., a pointer) while sizeof(*prg) is the size of a struct prg (i.e., the struct itself). The latter one is what I wanted. To make this clear, I will change the code to say sizeof(struct prg).

#define MIN(a, b) ((a) < (b) ? (a) : (b))

// Check a Prio error code and return failure if the call fails.
#define P_CHECK(s) \
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a big fan of this style of macros for error checking. But nothing I'd change now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, this feels ugly to me too. Is there a cleaner design pattern that I should use in the future?

Copy link
Contributor

Choose a reason for hiding this comment

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

For simple error check's I'd just do it explicitly. One of the issues with these macros is that the have hidden returns/goto, which is error-prone. Always doing the verbose error check and return/goto is a little more code to write but is better to read.
The rv = SECFailure; goto cleanup isn't great either but something that's really hard to avoid. (You return rv in this case and must make sure that it's always set correctly, which is hard.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right. So I'll leave this as is for now.

prio/rand.c Outdated
SECStatus
rand_init (void)
{
SECStatus rv = NSS_NoDB_Init (NULL);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the first time you need NSS? How do you ensure that you initialise this only once?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, this is the first time that we need NSS. The Prio_init() function calls this one when the library is initialized.

To make sure that NSS only gets initialized once, I will wrap the NSS_NoDB_Init call with a check to NSS_IsInitialized.

prio/rand.c Outdated
return SECFailure;
}

SECStatus rv;
Copy link
Contributor

Choose a reason for hiding this comment

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

Init to SECFailure.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

rand_int_rng (mp_int *out, const mp_int *max,
RandBytesFunc rng_func, void *user_data)
{
SECStatus rv = SECSuccess;
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally, move these rv down to first use.

Copy link
Collaborator Author

@henrycg henrycg Jun 26, 2018

Choose a reason for hiding this comment

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

One question about this: Do you want me to try to do this throughout libprio? For consistency, I have been placing the rv initialization at the top of the function just to keep it out of the way of the meaningful/non-repetitive code. If it's worthwhile, I can go back through all of the code and move the rvs down, but I'm not sure if this will improve readability.

Copy link
Contributor

Choose a reason for hiding this comment

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

I generally like initialisations close to first use. Defining things on top is aa artefact from C89 where this was necessary.
I think readability is better then. But I wouldn't insist on it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, if it's acceptable, I will leave these as is.

prio/serial.c Outdated
P_CHECKCB (obj->type == MSGPACK_OBJECT_STR);

msgpack_object_str s = obj->via.str;
MP_CHECKC (mp_read_unsigned_octets (n, (unsigned char *)s.ptr, s.size));
Copy link
Contributor

Choose a reason for hiding this comment

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

Make sure n and other arguments are not NULL.

#define MIN(a, b) ((a) < (b) ? (a) : (b))

// Check a Prio error code and return failure if the call fails.
#define P_CHECK(s) \
Copy link
Contributor

Choose a reason for hiding this comment

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

For simple error check's I'd just do it explicitly. One of the issues with these macros is that the have hidden returns/goto, which is error-prone. Always doing the verbose error check and return/goto is a little more code to write but is better to read.
The rv = SECFailure; goto cleanup isn't great either but something that's really hard to avoid. (You return rv in this case and must make sure that it's always set correctly, which is hard.)

rand_int_rng (mp_int *out, const mp_int *max,
RandBytesFunc rng_func, void *user_data)
{
SECStatus rv = SECSuccess;
Copy link
Contributor

Choose a reason for hiding this comment

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

I generally like initialisations close to first use. Defining things on top is aa artefact from C89 where this was necessary.
I think readability is better then. But I wouldn't insist on it.

prio/encrypt.c Outdated
if (dataLen != CURVE25519_KEY_LEN)
return SECFailure;

// TODO: Horrible hack. We should be able to import a public key
Copy link
Contributor

Choose a reason for hiding this comment

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

You can import keys with pk8 (private) and spki (public). See [1] for example.
This isn't great. But that's the official format 😞

[1] https://searchfox.org/nss/source/gtests/pk11_gtest/pk11_curve25519_unittest.cc#66

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It would be really nice to be able to encode curve25519 public keys as fixed-length binary blobs (and also to be able to decode them from fixed-length blobs). If the keys have variable length, then parsing the ciphertexts that are output by PublicKey_encrypt will be more complicated and error-prone.

Since PublicKey_import is only used internally by the code in encrypt.c (to decode the ephemeral public key that's prepended to the ciphertext), I am wondering if there is maybe a simpler (though less portable) way to import the public key into NSS.

For example, I could hardcode an SPKI representation of the all-zeros curve25519 public key, import that using SECKEY_DecodeDERSubjectPublicKeyInfo and then set the bytes of the key as I am doing now. Would that work?

Copy link
Contributor

Choose a reason for hiding this comment

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

Since this has to go through PK11 NSS can't take byte blobs unfortunately.

For example, I could hardcode an SPKI representation of the all-zeros curve25519 public key, import that using SECKEY_DecodeDERSubjectPublicKeyInfo and then set the bytes of the key as I am doing now. Would that work?

That would be pretty ugly but should work 🙂

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

prio/encrypt.c Outdated

paramItem->type = siBuffer;
paramItem->data = (void *)param;
paramItem->len = sizeof (CK_AES_CTR_PARAMS);
Copy link
Contributor

Choose a reason for hiding this comment

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

You probably want to use CK_GCM_PARAMS here or param.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right. i will change this to sizeof(*param).

prio/encrypt.c Outdated
PublicKey_encryptSize (unsigned int inputLen)
{
// public key, IV, tag, and input
return CURVE25519_KEY_LEN + GCM_IV_LEN_BYTES + GCM_TAG_LEN_BYTES + inputLen;
Copy link
Contributor

Choose a reason for hiding this comment

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

This can overflow and thus cause badness. You should generally check lengths before adding them up to make sure they not getting too large.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh yes, thanks for pointing this out. I will change PublicKey_encryptSize to return an error flag to catch values so large as to cause integer overflow.

Henry Corrigan-Gibbs added 8 commits June 27, 2018 13:57
Now we can import a 32-byte curve25519 public key into NSS without
having to generate a new keypair from scratch.
- Public functions PublicKey_import_hex and PublicKey_export_hex
- Tests for these functions
Add browser-test utility that
1) generates new server keypairs,
2) uses xpcshell to call the PrioEncoder DOM routines,
3) parses the output of PrioEncoder,
4) validates the encoded packet, and
5) checks that the submitted data is what we expected.
@rhelmer rhelmer merged commit 5779b9c into mozilla:master Jul 17, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants