-
Notifications
You must be signed in to change notification settings - Fork 15
Conversation
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.
There was a problem hiding this 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') |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 typedef
d struct at all?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sizeof(PRG)
?
There was a problem hiding this comment.
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)
.
There was a problem hiding this comment.
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) \ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Init to SECFailure
.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 rv
s down, but I'm not sure if this will improve readability.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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) \ |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 🙂
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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.
These commits contain all of the libprio library code, broken down by functional component. The four commits contain:
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.