pax_global_header00006660000000000000000000000064145254674060014527gustar00rootroot0000000000000052 comment=b1655f3538677b170be50e7170c7d9e9b483220c libopaque-0.99.3/000077500000000000000000000000001452546740600136125ustar00rootroot00000000000000libopaque-0.99.3/.github/000077500000000000000000000000001452546740600151525ustar00rootroot00000000000000libopaque-0.99.3/.github/workflows/000077500000000000000000000000001452546740600172075ustar00rootroot00000000000000libopaque-0.99.3/.github/workflows/codeql-analysis.yml000066400000000000000000000053251452546740600230270ustar00rootroot00000000000000name: "CodeQL" on: push: branches: [master] pull_request: # The branches below must be a subset of the branches above branches: [master] schedule: - cron: '0 3 * * 2' jobs: analyze: name: Analyze runs-on: ubuntu-20.04 steps: - name: Checkout repository uses: actions/checkout@v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 # Override language selection by uncommenting this and choosing your languages # with: # languages: go, javascript, csharp, python, cpp, java # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) #- name: Autobuild # uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language - run: | sudo apt update sudo apt install -y libsodium-dev pkgconf # build-essential git # liboprf git clone https://github.com/stef/liboprf/ cd liboprf sudo PREFIX=/usr make install # main libopaque cd ../src git submodule update --init --recursive tests/munit make OPRFHOME=../liboprf debug test # Test a debug build. make OPRFHOME=../liboprf clean all test # Test a production build. sudo PREFIX=/usr make install # python3 cd ../python/test sudo apt install -y python3-pip pip3 install pysodium LD_LIBRARY_PATH="$(pwd)/../../src" PYTHONPATH="$(pwd)/.." python3 simple.py # php7 cd ../../php7 sudo apt install -y php php-dev phpize LIBOPAQUE_CFLAGS='-I ../src' LIBOPAQUE_LIBS='-lopaque' ./configure LD_LIBRARY_PATH=../src TEST_PHP_ARGS=-q make EXTRA_CFLAGS=-I../src EXTRA_LDFLAGS=-L../src test # ruby cd ../ruby sudo apt install -y ruby ruby-dev ruby extconf.rb make ./test.rb # lua cd ../lua sudo apt install -y lua5.3 liblua5.3-dev make ./test.lua # java cd ../java sudo apt install -y openjdk-11-jdk openjdk-11-jre make JAVAINCLUDES=/usr/lib/jvm/java-11-openjdk-amd64/include all test # erlang sudo apt install -y erlang cd ../erlang make all test # golang sudo apt install -y golang cd ../go make tests - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 libopaque-0.99.3/.github/workflows/js-bindings.yml000066400000000000000000000016701452546740600221450ustar00rootroot00000000000000name: "Build and test JavaScript bindings" on: push: branches: [master] pull_request: # The branches below must be a subset of the branches above branches: [master] jobs: build: name: Build and test JavaScript bindings runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 - run: | sudo apt install -y libsodium-dev nodejs pkgconf python3-pip uncrustify # build-essential git # liboprf git clone https://github.com/stef/liboprf/ cd js git clone https://github.com/emscripten-core/emsdk.git # https://emscripten.org/docs/getting_started/downloads.html cd emsdk ./emsdk install 1.40.1 ./emsdk activate 1.40.1 source ./emsdk_env.sh cd .. make LIBOPRFHOME=../liboprf echo emsdk >.prettierignore echo libsodium.js >>.prettierignore make LIBOPRFHOME=../liboprf format es-check test libopaque-0.99.3/.gitmodules000066400000000000000000000001461452546740600157700ustar00rootroot00000000000000[submodule "js/libsodium.js"] path = js/libsodium.js url = https://github.com/jedisct1/libsodium.js libopaque-0.99.3/AUTHORS000066400000000000000000000001001452546740600146510ustar00rootroot00000000000000Stefan Marsiske Chris Topher libopaque-0.99.3/LICENSE000066400000000000000000000167441452546740600146330ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. libopaque-0.99.3/README.md000066400000000000000000000254101452546740600150730ustar00rootroot00000000000000# libopaque This library implements the OPAQUE protocol as proposed in the IRTF CFRG draft at https://github.com/cfrg/draft-irtf-cfrg-opaque. It comes with bindings for js, php7, ruby, java, erlang, lua, python, go and SASL. There are also a 3rd party bindings for: - [dart](https://github.com/tibotix/opaque-dart) - rust [libopaque-sys](https://github.com/dnet/libopaque-sys) + [opaqueoxide](https://github.com/dnet/opaqueoxide/) Some more information about OPAQUE can be found in a series of blogposts: - [OPAQUE](https://www.ctrlc.hu/~stef/blog/posts/opaque.html) - [Why and how to use OPAQUE for user authentication](https://www.ctrlc.hu/~stef/blog/posts/Why_and_how_to_use_OPAQUE_for_user_authentication.html) - [opaque demo](https://www.ctrlc.hu/~stef/blog/posts/opaque_demo.html) There is a [live demo](https://ctrlc.hu/opaque/) between a python/flask backend and a js/html frontend. ## Dependencies libopaque depends on libsodium[1] and on liboprf[2] [1]: https://github.com/jedisct1/libsodium [2]: https://github.com/stef/liboprf/ Both must be installed to use libopaque. ## The OPAQUE Protocol The OPAQUE protocol is an asymmetric password-authenticated key-exchange. Essentially it allows a client to establish a shared secret with a server based on only having a password. The client doesn't need to store any state. The protocol has two phases: - In the initialization phase a client registers with the server. - In the AKE phase the client and server establish a shared secret. The initialization only needs to be executed once, the key-exchange can be executed as many times as necessary. The following sections provide an abstract overview of the various steps and their inputs and outputs, this is to provide an understanding of the protocol. The various language bindings have - language-specific - slightly different APIs in the way the input/output parameters are provided to the functions, see details in the READMEs of the bindings sub-directories. ### Initialization The original paper and the IRTF CFRG draft differ, in the original paper a one-step registration is specified which allows the server during initialization to inspect the password of the client in cleartext. This allows the server to enforce password sanity rules (e.g. not being listed in hacked user databases), however this also implies that the client has to trust the server with this password. The IRTF CFRG draft doesn't specify this registration, instead it specifies a four-step protocol which results in exactly the same result being stored on the server, without the client ever exposing the password to the server. #### One-step registration revealing password to server Before calling the registration function the server should check the strength of the password by obeying [NIST SP 800-63-3b](https://pages.nist.gov/800-63-3/sp800-63b.html#memsecret)) and if insufficient reject the registration. The registration function takes the following parameters: - the client password - the optional long-term server private key skS - the IDs The result of the registration is a record that the server should store to be provided to the client in the key-exchange phase. Additionally an `export_key` is also generated which can be used to encrypt additional data that can be decrypted by the client in the key-exchange phase. Where and how this additional `export_key` encrypted data is stored and how it is retrieved by the client is out of scope of the protocol, for example this could be used to store additional keys, personal data, or other sensitive client state. #### Password Privacy Preserving registration This registration is a four step protocol which results in exactly the same outcome as the one-step variant, without the server learning the client password. It is recommended to have the client do a password strength according to NIST SP 800-63-3b check before engaging in the following protocol. The following steps are executed, starting with the client: 1. client: sec, req = CreateRegistrationRequest(pwd) The outputs in the first step are - a sensitive client context `sec` that is needed in step 3, this should be kept secret as it also contains the plaintext password. - and request `req` that should be sent to the server, this request does not need to be encrypted (it is already). 2. server: ssec, resp = CreateRegistrationResponse(req, skS) In the second step the server takes the request and an optional long-term server private key skS. In case no skS is supplied a user-specific long-term server keypair is generated. The output of this step is: - a sensitive server context `ssec`, which must be kept secret and secure until step 4 of this registration protocol. - a response, which needs to be sent back to the client, this response does not need to be encrypted (it is already). 3. client: recU, export_key = FinalizeRequest(sec, resp, ids) In the third step the client takes its context from step 1, the servers response from step 2, and the IDs of the server and client to assemble a record stub `recU` and an `export_key`. In case the client wishes to (and the server supports it) to encrypt and store additional data at the server, it uses the `export_key` to encrypt it and sends it over to the server together with the record stub. The record stub might or might not be needed to be encrypted, depending on the OPAQUE envelope configuration. 4. server: rec = StoreUserRecord(ssec, recU, rec) In the last - fourth - step of the registration protocol, the server receives the record stub `recU` from the client step 3, it's own sensitive context `ssec` from step 2. These parameters are used to complete the record stub into a full record `rec`, which then the server must store for later retrieval. ### The key-exchange The key-exchange is a three-step protocol with an optional fourth step for explicit client authentication: 1. client: sec, req = CreateCredentialRequest(pwd) The client initiates a key-exchange taking the password as input and outputting a sensitive client context `sec` which should be kept secret until step 3 of this protocol. This step also produces a request `req` - which doesn't need to be encrypted (it is already) - to be passed to the server executing step 2: 2. server: resp, sk, ssec = CreateCredentialResponse(req, rec, ids, context) The server receives a request from the client, retrieves record belonging to the client, the IDs of itself and the client, and a context string. Based on these inputs the server produces: - a response `resp` which needs to be sent to client, - its own copy of the shared key produced by the key-exchange, and - a sensitive context `ssec` which it needs to protect until the optional step 4. 3. client: sk, authU, export_key, ids = RecoverCredentials(resp, sec, context, ids) The client receives the servers response `resp`, and - takes its own sensitive context `sec` from step 1., - in case the envelope configuration has set the servers public key set to not-packaged the servers public key, - a context string, - the ids of the server and client. Processing all these inputs results in: - the shared secret key produced by the key exchange, which must be the same as what the server has, - an authentication token `authU` which can be sent to the server in case the optional fourth step of the protocol is needed to explicitly authenticate the client to the server. - and finally the client also computes the `export_key` which was used to encrypt additional data during the registration phase. 4. optionally server: UserAuth(ssec, authU) This step is not needed in case the shared key is used for example to set up an encrypted channel between the server and client. Otherwise the `authU` token is sent to the server, which using its previously stored sensitive context `ssec` verifies that the client has indeed computed the same shared secret as a result of the key-exchange and thus explicitly authenticating the client. ## Installing Install `libsodium-dev` and `pkgconf` using your operating system's package manager. Building everything should (hopefully) be quite simple afterwards: ``` git submodule update --init --recursive --remote cd src make ``` ## OPAQUE API The API is described in the header file: [`src/opaque.h`](https://github.com/stef/libopaque/blob/master/src/opaque.h). The library implements the OPAQUE protocol with the following deviations from the original paper: 0. It does not implement any persistence/lookup functionality. 1. Instead of HMQV (which is patented), it implements a Triple-DH. 2. It implements "user iterated hashing" from page 29 of the paper. 3. It additionally implements a variant where U secrets never hit S unprotected. For more information, see the [IRTF CFRG specification](https://github.com/cfrg/draft-irtf-cfrg-opaque/blob/master/draft-irtf-cfrg-opaque.md), the [original paper](https://github.com/stef/libopaque/blob/master/doc/opaque.pdf) and the [`src/tests/opaque-test.c`](https://github.com/stef/libopaque/blob/master/src/tests/opaque-test.c) example file. ## OPAQUE Parameters Currently all parameters are hardcoded, but there is nothing stopping you from setting stronger values for the password hash. ### The Curve This OPAQUE implementation is based on libsodium's ristretto25519 curve. This means currently all keys are 32 bytes long. ### Other Crypto Building Blocks This OPAQUE implementation relies on libsodium as a dependency to provide all other cryptographic primitives: - `crypto_pwhash`[3] uses the Argon2 function with `crypto_pwhash_OPSLIMIT_INTERACTIVE` and `crypto_pwhash_MEMLIMIT_INTERACTIVE` as security parameters. - `randombytes` attempts to use the cryptographic random source of the underlying operating system[4]. [3]: https://doc.libsodium.org/password_hashing/default_phf [4]: https://download.libsodium.org/doc/generating_random_data ## Debugging To aid in debugging and testing, there are two macros available: | Macro | Description | | ---------- | ------------------------------------------------- | | `TRACE` | outputs extra information to stderr for debugging | | `NORANDOM` | removes randomness for deterministic results | To use these macros, specify the `DEFINES` Makefile variable when calling `make`: ``` $ make DEFINES='-DTRACE -DNORANDOM' clean libopaque.so tests $ LD_LIBRARY_PATH=. ./tests/opaque-test ``` As a shortcut, calling `make debug` also sets these variables. This code block is equivalent to the one above: ``` $ make clean debug $ LD_LIBRARY_PATH=. ./tests/opaque-test ``` ## Credits This project was funded through the NGI0 PET Fund, a fund established by NLnet with financial support from the European Commission's Next Generation Internet programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825310. libopaque-0.99.3/TODO000066400000000000000000000004001452546740600142740ustar00rootroot00000000000000* expose crypto_pwhash hardness params in the API * document API in the README * adopt to new dumbed down envelope format base: {skU}{pkS}, customIdentifier: {skU}{pkS,idU,idS} * change hkdf to use sha512 instead of sha256 to be compliant with the draft libopaque-0.99.3/demos/000077500000000000000000000000001452546740600147215ustar00rootroot00000000000000libopaque-0.99.3/demos/auth-py-js/000077500000000000000000000000001452546740600167225ustar00rootroot00000000000000libopaque-0.99.3/demos/auth-py-js/README.md000066400000000000000000000003511452546740600202000ustar00rootroot00000000000000see the accompanying blog post: - https://www.ctrlc.hu/~stef/blog/posts/Why_and_how_to_use_OPAQUE_for_user_authentication.html - https://www.ctrlc.hu/~stef/blog/posts/opaque_demo.html and the live demo at: https://ctrlc.hu/opaque/ libopaque-0.99.3/demos/auth-py-js/app.py000077500000000000000000000053311452546740600200610ustar00rootroot00000000000000#!/usr/bin/env python3 from binascii import hexlify, unhexlify from flask import Flask, request, render_template from opaque import (CreateRegistrationResponse, StoreUserRecord, CreateCredentialResponse, UserAuth, Register, Ids) from pysodium import crypto_secretbox, crypto_secretbox_open, randombytes app = Flask(__name__) server_key = randombytes(32) users = {} fake, _ = Register(hexlify(randombytes(16)), Ids(hexlify(randombytes(16)), hexlify(randombytes(16)))) # the server is stateless apart from the user dict # between protocol steps the local sensitive context is encrypted and # sent to the client, who has to send it back for the final server # step. This is also how you would implement OPAQUE in a # load-balancing setup without synching protocol state between all # backend servers. def seal(data): nonce = randombytes(24) return nonce+crypto_secretbox(data, nonce, server_key) def unseal(data): nonce = data[:24] return crypto_secretbox_open(data[24:],data[:24],server_key) @app.route("/") def start(): return render_template('index.html') @app.route("/request-creds", methods=['POST']) def req_creds(): req = unhexlify(request.form['request']) idU = request.form['id'] rec=users.get(idU, fake) # wrap the IDs into an opaque.Ids struct: ids=Ids(idU, "demo server") # create a context string context = b"pyopaque-v0.2.0-demo" # server responds to credential request resp, _, authU = CreateCredentialResponse(req, rec, ids, context) return { "response": resp.hex(), "ctx": seal(authU).hex() } @app.route("/authenticate", methods=['POST']) def authenticate(): authU = unhexlify(request.form['authU']) authU0 = unseal(unhexlify(request.form['ctx'])) # server authenticates user try: UserAuth(authU0, authU) except: return { "response": False } return { "response": True } @app.route("/register", methods=['POST']) def register(): req = request.form['request'] sec, resp = CreateRegistrationResponse(unhexlify(req)) return { 'response': resp.hex(), "ctx": seal(sec).hex() } @app.route("/store", methods=['POST']) def store(): reg_rec = unhexlify(request.form['rec']) ctx = unseal(unhexlify(request.form['ctx'])) idU = request.form['id'] if idU in users: return { "response": False } rec = StoreUserRecord(ctx, reg_rec) users[idU]=rec return { "response": True} @app.after_request def add_header(response): response.headers['Content-Security-Policy'] = "default-src *; style-src 'self' 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'" return response if __name__ == '__main__': app.run(host='0.0.0.0', threaded=False) libopaque-0.99.3/demos/auth-py-js/blogpost.org000066400000000000000000000270771452546740600213010ustar00rootroot00000000000000#+OPTIONS: H:2 num:nil toc:nil \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t #+OPTIONS: TeX:t LaTeX:t skip:nil d:nil todo:t pri:nil tags:not-in-toc [[https://plaintextoffenders.com/][Storing clear text passwords]] in your user database is very stupid. Traditionally we store hashed passwords of users. When a user logs in, then the user sends their cleartext password to the server (hopefully protected by TLS), the server hashes the password and compares it to to the stored hash, if it matches, all is good. Lets call this symmetric authentication, where both the client and the server knows the cleartext password. ** A simple threat model With authentication you have the channel over which the token is transmitted: - here you don't want a passive adversary learn your cleartext password (sniffing), - or an active adversary being able to replay your password as in pass-the-hash attacks (replays). You say that but its all protected by TLS, maybe, but maybe your TLS terminates at cloudflare or another content delivery network (CDN), or the TLS configuration fails for some reason (e.g. certificate verify = false, or you push your private key to github), or your certificate store has certificate authorities that are controlled by some adversary. On the server itself: - you want to be safe against [[https://en.wikipedia.org/wiki/SNAFU][SNAFUs]] like when Twitter was [[https://www.theverge.com/2018/5/3/17316684/twitter-password-bug-security-flaw-exposed-change-now][logging clear text passwords]], or [[https://en.wikipedia.org/wiki/Heartbleed][heartbleed]] is leaking them. Also you don't want active attackers on the server to be able to read the plaintext passwords (direct password leaks). - And finally you want to avoid in case an attacker leaks your user database that there are already pre-computations available that simplify the recovery of passwords from hashes into cheap lookups, so you use salts. (pre-computation) You want to protect those passwords of your user so that in case your password database leaks, you want to make it as difficult as possible to anyone to recover passwords, in case those passwords are reused at other sites and enable attackers to abuse these accounts. So you really don't want your cleartext password to be sent to the server, ever. And it's possible, for example [[https://en.wikipedia.org/wiki/CRAM-MD5][CRAM]] - a challenge-response protocol - eliminates the sniffing vector. The [[https://en.wikipedia.org/wiki/Socialist_millionaires][socialist millionaire protocol]] (SMP) eliminates both sniffing and replay issues. But they still need the cleartext password on the server. So, that leads us to... ** Asymmetric authentication If there is symmetric authentication, there must be also asymmetric authentication, where only one party knows the password. And the nice thing about asymmetric auth is that it immediately eliminates sniffing and direct password leaks problems. Let's hypothesize a simple hash-based protocol where password is never sent in cleartext to the server. If instead of directly sending cleartext password, 1. since we're hashing on the client, we need to know the salt with which the server has the password hashed with during registration, so the client first asks for the global or user specific salt. 2. the server sends the salt via tls, then 3. client calculates hash(per-user-salt, pwd) sends to server, and 4. server hashes this again (otherwise the once hashed password could be used by anyone having access to the user database) and compares that to the stored value that has also been hashed twice. In case of SNAFU (TLS fail, [[https://en.wikipedia.org/wiki/Man-in-the-middle_attack][MitM]], CDN, attacker on server, twitter fumbling, etc) the salt leaks, and possibly a unique hash, that needs to be bruteforced to recover the password. However there are a few problems: 1. the salt must be requested from the server, adding 2 more steps to the auth protocol. Caching the salt [[https://en.wikipedia.org/wiki/Trust_on_first_use][TOFU]]-style in a cookie solves this. 2. salts mustn't leak if a user exists or not, that means for non-existing users, the same salt must be returned for the same non-existing username. This can be solved by calculating the salt like this: #+BEGIN_EXAMPLE salt:=hmac(username, serverkey) #+END_EXAMPLE 3. global salts enable an attacker to acquire the salt and pre-calculate. 4. if a username is known, an attacker can query the salt and prepare a pre-calculation. 5. the hashed password can still be used in a pass-the-hash style replay attack by anyone stealing it. 6. an active attacker could respond with a salt, for which they have a pre-computation, and then recover the client's password from the received hash. Of course this hypothetical protocol is only a *gedankenspiel*, an example of why we should not invent our own crypto, and that every solution also bears new problems. What we really want is an asymmetric Password-Authenticated Key Exchange (aPAKE), of which the most prominent one is SRP. Coincidentally the first versions of [[https://blog.cryptographyengineering.com/should-you-use-srp/][SRP]] also were fraught with serious issues. With [[https://ctrlc.hu/~stef/blog/posts/opaque.html][OPAQUE]] you never send your password to the server, eliminating sniffing and replay attacks, and an attacker has no pre-computation opportunity. Since using [[https://ctrlc.hu/~stef/blog/posts/oprf.html][OPRF]] shields the "salt" and the password from an attacker OPAQUE also eliminate direct password leaks. Beyond this OPAQUE has proofs in a very strong model. Another benefit of using OPAQUE is that the memory-hard key-stretching function can run on the client, and thus reduces the attack surface for computational denial-of-service (DoS) vectors against the server. Also notable is that OPAQUE can run over an insecure communication medium, there is no need for TLS or anything else. Another nice feature of OPAQUE is, that the server can return a fake record to mitigate user enumeration attacks, an attacker will not be able to decide if the user exists or not. ** How to Authenticate with OPAQUE? So you want to eliminate the burden of cleartext passwords on your service, and you are willing to go from one message to 3 messages in the authentication dance. Assuming a user is already registered at your service, which might have been done by running the following binary as supplied by libopaque and storing the resulting record at the server: #+BEGIN_SRC sh echo -n password | ./opaque init user server >record 3>/dev/null #+END_SRC The following examples are using the javascript and python wrappers provided by [[https://github.com/stef/libopaque/][libopaque]]. *** 1. The client initiates a credential request For example using javascript in a web browser: **client.js:** #+BEGIN_SRC javascript const opaque = require("../dist/libopaque.debug.js"); (async () => { await opaque.ready; const pwdU = "password"; let { ctx, pub } = opaque.createCredentialRequest({ pwdU }); const userid = "user@example.com"; send_to_server(request, userid); // you need to implement this fn ... #+END_SRC The client sends "request" over to the server, and holds onto "ctx" as securely as possible. *** 2. The server handles the "request" from the client **server.py:** #+BEGIN_SRC python from opaque import CreateCredentialResponse, UserAuth, Ids) ... # server reads the request from the client request, userid = get_request() # you need to implement get_request() # load the record record = load_record(userid) # you need to implement load_record() # wrap the IDs into an opaque.Ids struct: ids=Ids(userid, "servername") # create a context string context = "pyopaque-v0.2.0-demo" # server responds to credential request response, _, authU = CreateCredentialResponse(request, record, ids, context) send_to_client(response) # you need to implement send_to_client() #+END_SRC The request is probably read from the network. The user record that has been created during user registration is loaded probably from disk or a database based on the user id. By default peers in OPAQUE are identified by their long-term public keys, in case you want to use something else as identifiers, you need to specify them when creating the credential response, in our example we use the userid as provided by the client and "servername". Also important is to provide some context-string to prevent cross-protocol or downgrade attacks, hence we provide context string. When creating a credential response the output does not need any kind of extra protection, it is already encrypted and authenticated. Another output of this functions is a shared key, which is not needed in this case, where we are using OPAQUE only to authenticate. However the third output, the users expected authentication token is needed by the server in the last step of this protocol. *** 3. The client recovers its credentials **client.js:** #+BEGIN_SRC javascript ... const response = read_response(); // you need to implement this function const ids = { idU: userid, idS: "servername" }; const context = "pyopaque-v0.2.0-demo"; const {sk, authU, export_key,} = opaque.recoverCredentials({resp, ctx, context, ids}); send_to_server(authU); // you need to implement this fn. } #+END_SRC The client receives the servers response and uses its private context "ctx" from the first step, to recover its credentials. The recovery needs the same ids and context string as the server was using. The result of the recover credentials are: - a shared key, which is not needed in case we use OPAQUE only to authenticate, - the authentication token of the user, and - an export key which is also not needed for authentication-only use of OPAQUE. Finally client sends the user's authentication token to the server to explicitly authenticate itself to the server. This concludes OPAQUE for the client. *** 4. The server authenticates the client **server.py:** #+BEGIN_SRC python authU0 = receive_authU() # you need to implement this function # server authenticates user if not UserAuth(authU0, authU): raise AuthenticationFailure #+END_SRC The server takes the user authentication token it generated in the second step and compares it to the token it received from the client, if it matches the user is authenticated. ** Conclusion With OPAQUE, you never send your password to the server, so there is nothing that can be sniffed, or replayed, nor can your password leak in any way. The "salt" is also never available to eavesdroppers, which makes pre-computation impossible. Furthermore memory-hard password hashing functions are running on the client, which makes computational denial of service attacks against servers less of a problem. And there is even support to mitigate user enumeration attacks. The only two major problems are phishing attacks, if you get tricked to reveal your password, then it's game over. And offline bruteforce attacks in case a user database leaks, but that should be also more difficult due to the used memory-hard password hashing function used. All in all OPAQUE is a very efficent protocol with very strong security guarantees which, thanks to [[https://github.com/stef/libopaque/][libopaque]] is easy to integrate into you application. This project was funded through the NGI0 PET Fund, a fund established by NLnet with financial support from the European Commission's Next Generation Internet programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825310. libopaque-0.99.3/demos/auth-py-js/requirements.txt000066400000000000000000000000261452546740600222040ustar00rootroot00000000000000flask opaque pysodium libopaque-0.99.3/demos/auth-py-js/static/000077500000000000000000000000001452546740600202115ustar00rootroot00000000000000libopaque-0.99.3/demos/auth-py-js/static/index-worker.js000066400000000000000000000154351452546740600231750ustar00rootroot00000000000000(function (root) { "use strict"; const idS = "demo server"; const context = "pyopaque-v0.2.0-demo"; function requestCredentials(idU, module, pw) { try { var request = module.createCredentialRequest({ pwdU: pw }); var pub_base16 = module.uint8ArrayToHex(request.pub); var xhr = new XMLHttpRequest(); xhr.open("POST", "/request-creds", true); xhr.onreadystatechange = function () { var response = onreadystatechange(module, xhr); if (response) recoverCredentials(idU, module, response, request); }; xhrSend( "id=" + encodeURIComponent(idU) + "&request=" + pub_base16, module, xhr ); } catch (e) { module.printErr(e); } } function recoverCredentials(idU, module, response, request) { try { var resp_base16 = response.response; var credentials = module.recoverCredentials({ resp: module.hexToUint8Array(resp_base16), sec: request.sec, context: context, ids: { idS: idS, idU: idU }, }); var authU_base16 = module.uint8ArrayToHex(credentials.authU); var xhr = new XMLHttpRequest(); xhr.open("POST", "/authenticate", true); xhr.onreadystatechange = function () { var response = onreadystatechange(module, xhr); if (response != null) module.print( response.response === true ? "Success!" : "You are not authorized!" ); }; xhrSend( "authU=" + encodeURIComponent(authU_base16) + "&ctx=" + response.ctx, module, xhr ); } catch (e) { module.printErr(e); } } function register(idU, module, pwdU) { try { var request = module.createRegistrationRequest({ pwdU: pwdU }); var M_base16 = module.uint8ArrayToHex(request.M); var xhr = new XMLHttpRequest(); xhr.open("POST", "/register", true); xhr.onreadystatechange = function () { var response = onreadystatechange(module, xhr); if (response) finalizeRequest(idU, module, request, response, pwdU); }; xhrSend( "request=" + encodeURIComponent(M_base16) + "&id=" + idU, module, xhr ); } catch (e) { module.printErr(e); } } function finalizeRequest(idU, module, request, response, pwdU) { try { var pub_base16 = response.response; var result = module.finalizeRequest({ sec: request.sec, pub: module.hexToUint8Array(pub_base16), ids: { idS: idS, idU: idU }, }); var rec_base16 = module.uint8ArrayToHex(result.rec); var xhr = new XMLHttpRequest(); xhr.open("POST", "/store", true); xhr.onreadystatechange = function () { var response = onreadystatechange(module, xhr); if (response != null && response.response === true) module.print("registered successfully"); //requestCredentials(idU, module, pwdU); }; xhrSend( "rec=" + encodeURIComponent(rec_base16) + "&id=" + idU + "&ctx=" + response.ctx, module, xhr ); } catch (e) { console.log(e); module.printErr(e); } } function onreadystatechange(module, xhr) { try { if (xhr.readyState !== XMLHttpRequest.DONE) return; if (xhr.status !== 200 || !xhr.response) { module.printErr(xhr.responseURL + " failed."); return; } module.print(xhr.responseURL); module.print("FROM SERVER: " + xhr.response); var json = JSON.parse(xhr.response); if (!json) { module.printErr(xhr.responseURL + " failed."); return; } if (json.error) { module.printErr(json.error); return; } return json; } catch (e) { module.printErr(e); } } function xhrSend(body, module, xhr) { module.print("TO SERVER: " + body); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(body); } // Module is boilerplate generated by Emscripten. slightly modified. var Module = { preRun: [], postRun: [], print: function (text) { if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(" "); if (typeof text === "string" || text instanceof String) postMessage({ print: text }); else if (text instanceof Error) postMessage({ print: text.message }); else postMessage({ print: JSON.stringify(text) }); }, printErr: function (text) { if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(" "); if (typeof text === "string" || text instanceof String) postMessage({ printErr: text }); else if (text instanceof Error) postMessage({ printErr: text.message }); else postMessage({ printErr: JSON.stringify(text) }); }, setStatus: function (text) { if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: "" }; if (text === Module.setStatus.last.text) return; var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); var now = Date.now(); if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon Module.setStatus.last.time = now; Module.setStatus.last.text = text; // Send a message to index.js. // https://developer.mozilla.org/en-US/docs/Web/API/Worker/postmessage postMessage({ print: text }); }, totalDependencies: 0, monitorRunDependencies: function (left) { this.totalDependencies = Math.max(this.totalDependencies, left); Module.setStatus( left ? "Preparing... (" + (this.totalDependencies - left) + "/" + this.totalDependencies + ")" : "All downloads complete." ); }, }; Module.setStatus("Downloading..."); root.onerror = function (event) { // TODO: do not warn on ok events like simulating an infinite loop or exitStatus Module.setStatus("Exception thrown, see JavaScript console"); Module.setStatus = function (text) { if (text) Module.printErr("[post-exception status] " + text); }; }; // See the end of libopaque-post.js for where we hook up root.libopaque_mod. root.libopaque_mod = Module; // https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts root.importScripts("libopaque.debug.js"); // Receive a message from index.js. // https://developer.mozilla.org/en-US/docs/Web/API/Worker/onmessage root.onmessage = function (e) { var action = e.data.action; var idU = e.data.id; var pwdU = e.data.pw; if (action === "register") { register(idU, Module, pwdU); } else if (action === "login") { requestCredentials(idU, Module, pwdU); } else { Module.printErr(action + " is invalid."); } }; })(this); libopaque-0.99.3/demos/auth-py-js/static/index.js000066400000000000000000000025521452546740600216620ustar00rootroot00000000000000(function () { "use strict"; var button_register = document.getElementsByName("register")[0]; var button_login = document.getElementsByName("authenticate")[0]; var pre = document.getElementsByTagName("pre")[0]; function register(event) { postMessage("register"); } function login(event) { postMessage("login"); } function postMessage(action) { var id = document.getElementById("id").value; var pw = document.getElementById("pw").value; // Send a message to index-worker.js. // https://developer.mozilla.org/en-US/docs/Web/API/Worker/postmessage pre.innerHTML = "
" + pre.innerHTML; worker.postMessage({ action: action, id: id, pw: pw }); } button_register.addEventListener("click", register); button_login.addEventListener("click", login); // Use a web worker to prevent the main thread from blocking. // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers var worker = new Worker("/static/index-worker.js"); // Receive a message from index-worker.js. // https://developer.mozilla.org/en-US/docs/Web/API/Worker/onmessage var i = 0; worker.onmessage = function (e) { if (e.data.printErr) pre.innerHTML = i++ + ": " + e.data.printErr + "
" + pre.innerHTML; if (e.data.print) pre.innerHTML = i++ + ": " + e.data.print + "
" + pre.innerHTML; }; })(); libopaque-0.99.3/demos/auth-py-js/templates/000077500000000000000000000000001452546740600207205ustar00rootroot00000000000000libopaque-0.99.3/demos/auth-py-js/templates/index.html000066400000000000000000000010561452546740600227170ustar00rootroot00000000000000 OPAQUE Auth Demo

OPAQUE Auth Demo

Username:
Password:

    
  

libopaque-0.99.3/demos/blob-ruby-js/000077500000000000000000000000001452546740600172305ustar00rootroot00000000000000libopaque-0.99.3/demos/blob-ruby-js/README.md000066400000000000000000000002421452546740600205050ustar00rootroot00000000000000see the accompanying blogpost:
https://www.ctrlc.hu/~stef/blog/posts/All_OPAQUE_and_SPHINX_related_posts.html

and the live demo at https://ctrlc.hu/opaque-blob/
libopaque-0.99.3/demos/blob-ruby-js/blogpost.org000066400000000000000000000232531452546740600215770ustar00rootroot00000000000000#+OPTIONS:   H:2 num:nil toc:nil \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t
#+OPTIONS:   TeX:t LaTeX:t skip:nil d:nil todo:t pri:nil tags:not-in-toc

We have already seen two use-cases for OPAQUE: [[https://www.ctrlc.hu/~stef/blog/posts/Why_and_how_to_use_OPAQUE_for_user_authentication.html][authentication]] and
[[https://www.ctrlc.hu/~stef/blog/posts/How_to_use_OPAQUE_for_setting_up_a_secure_channel.html][securing a channel]]. A third less obvious - if you think of it as a
(P)ake - use of OPAQUE is to store and retrieve static and probably
sensitive data. In the previous example we always ignored the
export-key, in this installment it will be the main instrument.

** The export-key

The export-key is a key derived from your password during
registration. This can then be used to encrypt additional data that is
independent of the data needed for a run of the OPAQUE protocol.

** Where to store the encrypted blob?

Export-key encrypted data can then be stored anywhere, but it makes
most sense to store it on the server running OPAQUE. This allows a
client to still remain free of any account specific state.

The blob could be of course stored on the client, but then if you are
doing multi-device setups you have to sync it between all your
devices.

Or you could store this data at another server, in which case your
multi-device clients still need to sync at least the address pointing
to this encrypted blob.

So the simplest choice in a multi-device setting is to store the blob
next to your OPAQUE user record on the OPAQUE server.

** What to store in the blob?

Well this is an excellent question, it could be some crapto wallet
key, some other password, some long-term key-pair, user ids to some
service or simply the anniversaries of/with your spouse. Maybe if you
are a ransomware group you could store the unlock key in such a blob?
(just kidding)

** The example

You can find the complete source code to the following example in the
[[https://github.com/stef/libopaque/tree/master/demos/blob-ruby-js][git repo]]. You can also try out the example as a [[https://ctrlc.hu/opaque-blob/][live demo]]. Unlike with
the [[https://ctrlc.hu/opaque/][previous demo]] we do not provide a registration flow. There is one
hardcoded OPAQUE record and encrypted message on the server. This also
allowed us to get rid of the username entry in the "form". The correct
password "password" of the hard-coded opaque blob will give you a
short message, while anything else a failure. Let's dive into the example.

*** 0. Starting a web worker and communication with it.

Since this example is running in the browser, we start a web worker
thread so that the main thread of the page is not blocked while the
OPAQUE protocol runs. This is how we start and dispatch between main
thread and webworker:

**index.js**
#+BEGIN_SRC javascript
(function () {
  "use strict";

  var button_fetch = document.getElementsByName("fetch")[0];

  function fetch(event) {
    postMessage("fetch");
  }
  button_fetch.addEventListener("click", fetch);
#+END_SRC

Here we just bind the button to trigger the web worker when clicked.

#+BEGIN_SRC javascript
  var pre = document.getElementsByTagName("pre")[0];

  function postMessage(action) {
    var pw = document.getElementById("pw").value;
    // Send a message to index-worker.js.
    // https://developer.mozilla.org/en-US/docs/Web/API/Worker/postmessage
    pre.innerHTML = "
" + pre.innerHTML; worker.postMessage({ action: action, pw: pw }); } #+END_SRC This is our wrapper that logs any messages to the web worker to our makeshift "console". #+BEGIN_SRC javascript // Use a web worker to prevent the main thread from blocking. // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers var worker = new Worker("/index-worker.js"); #+END_SRC This instantiates our web worker with the code doing all the OPAQUE back-and-forth. #+BEGIN_SRC javascript // Receive a message from index-worker.js. // https://developer.mozilla.org/en-US/docs/Web/API/Worker/onmessage var i = 0; worker.onmessage = function (e) { if (e.data.printErr) pre.innerHTML = i++ + ": " + e.data.printErr + "
" + pre.innerHTML; if (e.data.print) pre.innerHTML = i++ + ": " + e.data.print + "
" + pre.innerHTML; }; })(); #+END_SRC And finally a callback for any messages coming from the web worker to be printed to our simple "console". More initialization happens in the web worker itself, it initializes a Module object which is really just boilerplate generated from emscripten. The most important part there is the "root.onmessage" callback which dispatches the commands coming from the main thread. We omit this code here, as it is mostly generic boilerplate. The curious among you might have a look at it in the [[https://github.com/stef/libopaque/blob/master/demos/blob-ruby-js/static/index-worker.js#L86][git repo.]] *** 1. The client initiates a credential request When the fetch button on the HTML page is clicked, the main thread sends a request to the web worker thread, which initiates the OPAQUE protocol: **index-worker.js:** #+BEGIN_SRC javascript function requestCredentials(module, pw) { try { var request = module.createCredentialRequest({ pwdU: pw }); var pub_base16 = module.uint8ArrayToHex(request.pub); var xhr = new XMLHttpRequest(); xhr.open("POST", "/request-creds", true); xhr.onreadystatechange = function () { var response = onreadystatechange(module, xhr); if (response) recoverCredentials(module, response, request); }; xhrSend("request=" + pub_base16, module, xhr); } catch (e) { module.printErr(e); } } #+END_SRC Everything pretty straightforward, creating a request, serializing and sending it with a "XMLHttpRequest()" and chaining the final OPAQUE step in the "onreadystatechange" callback. *** 2. The server created a response and sends it back with the blob In our demo the server is implemented in ruby, using the sinatra framework. In the example below the hardcoded OPAQUE user record and the hardcoded encrypted blob are omitted for brevity. The result is small and simple: #+BEGIN_SRC ruby post '/request-creds' do request.body.rewind req = hex_to_bin(params['request']) rec = hex_to_bin("an opaque user record encoded as hex") blob = 'some encrypted blob encoded as hex' resp, _, _ = create_credential_response(req, rec, "demo user", "demo server", "rbopaque-v0.2.0-demo") content_type :json { response: bin_to_hex(resp), blob: blob }.to_json end #+END_SRC The server side is really simple as you can see. The final step on the client is not much more exciting: *** 3. The client recovers its credentials and decrypts the blob The response from the server is received through the "onreadystatechange" callback of the XMLHttpRequest, which calls this function: **index-worker.js:** #+BEGIN_SRC javascript function recoverCredentials(module, response, request) { const ids = { idS: "demo server", idU: "demo user" } const context = "rbopaque-v0.2.0-demo"; try { var resp_base16 = response.response; var credentials = module.recoverCredentials({ resp: module.hexToUint8Array(resp_base16), sec: request.sec, context: context, ids: ids, }); const blob = module.hexToUint8Array(response.blob); module.print("Decoded blob: " + xor(credentials.export_key, blob)); } catch (e) { module.printErr(e); } } #+END_SRC Again nothing really surprising here, parameters get deserialized and "recoverCredentials()" is called. The only result we care about in this case is now the export-key, which in our case is used as a kind of one-time-pad to decrypt the message received in the encrypted blob. If the export-key is correct the message will decrypt in any other case gibberish will be the result. ** Some Warnings It is importantt to use real encryption with the export-key and the blob you want to protect, use something like "crypto_secretbox" from [[https://github.com/jedisct1/libsodium.js][libsodium.js]] or similar. **Do not** use the simple one-time-pad mechanism used in this example, unless you really do understand what the implications of that are. It is also important to note, that the live demo uses a debug version of [[https://github.com/stef/libopaque][libopaque]] which - not only dumps trace messages, but also - **does not** use any random source thus everything is always deterministic. Thus do not copy the libopaque.debug.js and deploy it in your own production setup, it is not secure! You have to build your own libopaque.js, or get one that is not compiled with "-DNORANDOM". If you have the idea to implement a password manager storing passwords in the export-key protected blobs, that is a great idea! I had the same. There is only one problem, you cannot use OPAQUE authentication as a way to authorize change and deletion of export-key blobs, as this voids the offline-bruteforce resistance of OPAQUE for the server operator, which is something you really don't want to do ([[https://github.com/stef/pwdsphinx/tree/opaquify][we tried]], don't be like us. learn from our faults!). ** Summary In this post we have seen how to use the OPAQUE export-key to protect some at-rest blob. The ruby server code shows clearly how simple and how little is needed to implement this. The javascript client implementation is a bit more work, but most of it is either boilerplate, or based on functionality that most javascript frameworks provide already. It really is a bit unfair to compare something written with sinatra to something vanilla js. This post concludes the series on generic use of OPAQUE, we hope you will find this useful and find a good use for libopaque in your own system. libopaque-0.99.3/demos/blob-ruby-js/encrypt_msg.py000066400000000000000000000005601452546740600221350ustar00rootroot00000000000000#!/usr/bin/env python3 import pysodium, binascii export_key = binascii.unhexlify('ed23adbfc61c9462ee501bee51c9ba75e21915b550b00eb1f5babde5f251dea91897d87200e73013b92a81d74e2a951d9fe017ada17af570c537ef7b061f38c2') msg = b"OPAQUE is versatile, it can auth, protect at-rest and in-flight!" res = b''.join(bytes([x^y]) for x,y in zip(export_key, msg)) print(res.hex()) libopaque-0.99.3/demos/blob-ruby-js/server.rb000066400000000000000000000023151452546740600210640ustar00rootroot00000000000000require 'sinatra' require './opaque.so' include Opaque def bin_to_hex(s) s.unpack('H*').first end def hex_to_bin(s) s.scan(/../).map { |x| x.hex }.pack('c*') end set :public_folder, __dir__ + '/static' get '/' do send_file File.join(settings.public_folder, 'index.html') end post '/request-creds' do request.body.rewind req = hex_to_bin(params['request']) rec = hex_to_bin("7a3c6282f02d37a05023b60d5428e6cc5961d4c31221937adae0b574e4d07205000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f8046ad706b646aa60db1e34399313dfe447af7065d3ef802ed8198beeb50033912cc4e58f95b7a54b4b3978a9ca29b8c06e298899de0622bb50ab6353c056a152864e882b72832e5978766b16d590d904f584e797c6781f095a8c241320f0be2000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fe8fa434d3f4b0e6b6e7504dba82007ec2cadd271e64a570d5adb083a5eadcff3d26fe084b0e147a0f3aa74ea4b5b699981ff98500a45f50f8990cc8b18918491") blob = 'a273ecee9359b40b9d706d8b23badb018b75709970d97a9196dbd3c59324aac134b7a8006f935570cd0ae0a36358f06eebc076c3c55a9c1ee851831261774ce3' resp, _, _ = create_credential_response(req, rec, "demo user", "demo server", "rbopaque-v0.2.0-demo") content_type :json { response: bin_to_hex(resp), blob: blob }.to_json end libopaque-0.99.3/demos/blob-ruby-js/static/000077500000000000000000000000001452546740600205175ustar00rootroot00000000000000libopaque-0.99.3/demos/blob-ruby-js/static/index-worker.js000066400000000000000000000121471452546740600235000ustar00rootroot00000000000000(function (root) { "use strict"; function xor(a, b) { console.log("xor"); console.log(a); console.log(b); var res = "", i = a.length, j = b.length; while (i-->0 && j-->0) res = String.fromCharCode(a[i] ^ b[j]) + res; return res; } function requestCredentials(module, pw) { try { var request = module.createCredentialRequest({ pwdU: pw }); var pub_base16 = module.uint8ArrayToHex(request.pub); var xhr = new XMLHttpRequest(); xhr.open("POST", "/request-creds", true); xhr.onreadystatechange = function () { var response = onreadystatechange(module, xhr); if (response) recoverCredentials(module, response, request); }; xhrSend( "request=" + pub_base16, module, xhr ); } catch (e) { module.printErr(e); } } function recoverCredentials(module, response, request) { const ids = { idS: "demo server", idU: "demo user" } const context = "rbopaque-v0.2.0-demo"; try { var resp_base16 = response.response; var credentials = module.recoverCredentials({ resp: module.hexToUint8Array(resp_base16), sec: request.sec, context: context, ids: ids, }); //var export_key_base16 = module.uint8ArrayToHex(credentials.export_key); //module.print("export_key "+ export_key_base16); const blob = module.hexToUint8Array(response.blob); module.print("Decoded blob: " + xor(credentials.export_key, blob)); } catch (e) { module.printErr(e); } } function onreadystatechange(module, xhr) { try { if (xhr.readyState !== XMLHttpRequest.DONE) return; if (xhr.status !== 200 || !xhr.response) { module.printErr(xhr.responseURL + " failed."); return; } module.print(xhr.responseURL); module.print("FROM SERVER: " + xhr.response); var json = JSON.parse(xhr.response); if (!json) { module.printErr(xhr.responseURL + " failed."); return; } if (json.error) { module.printErr(json.error); return; } return json; } catch (e) { module.printErr(e); } } function xhrSend(body, module, xhr) { module.print("TO SERVER: " + body); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(body); } // Module is boilerplate generated by Emscripten. slightly modified. var Module = { preRun: [], postRun: [], print: function (text) { if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(" "); if (typeof text === "string" || text instanceof String) postMessage({ print: text }); else if (text instanceof Error) postMessage({ print: text.message }); else postMessage({ print: JSON.stringify(text) }); }, printErr: function (text) { if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(" "); if (typeof text === "string" || text instanceof String) postMessage({ printErr: text }); else if (text instanceof Error) postMessage({ printErr: text.message }); else postMessage({ printErr: JSON.stringify(text) }); }, setStatus: function (text) { if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: "" }; if (text === Module.setStatus.last.text) return; var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); var now = Date.now(); if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon Module.setStatus.last.time = now; Module.setStatus.last.text = text; // Send a message to index.js. // https://developer.mozilla.org/en-US/docs/Web/API/Worker/postmessage postMessage({ print: text }); }, totalDependencies: 0, monitorRunDependencies: function (left) { this.totalDependencies = Math.max(this.totalDependencies, left); Module.setStatus( left ? "Preparing... (" + (this.totalDependencies - left) + "/" + this.totalDependencies + ")" : "All downloads complete." ); }, }; Module.setStatus("Downloading..."); root.onerror = function (event) { // TODO: do not warn on ok events like simulating an infinite loop or exitStatus Module.setStatus("Exception thrown, see JavaScript console"); Module.setStatus = function (text) { if (text) Module.printErr("[post-exception status] " + text); }; }; // See the end of libopaque-post.js for where we hook up root.libopaque_mod. root.libopaque_mod = Module; // https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts root.importScripts("libopaque.debug.js"); // Receive a message from index.js. // https://developer.mozilla.org/en-US/docs/Web/API/Worker/onmessage root.onmessage = function (e) { var action = e.data.action; var pwdU = e.data.pw; if (action === "fetch") { requestCredentials(Module, pwdU); } else { Module.printErr(action + " is invalid."); } }; })(this); libopaque-0.99.3/demos/blob-ruby-js/static/index.html000066400000000000000000000006741452546740600225230ustar00rootroot00000000000000 OPAQUE Blob Demo

OPAQUE Blob Demo

Password:

    
  

libopaque-0.99.3/demos/blob-ruby-js/static/index.js000066400000000000000000000021511452546740600221630ustar00rootroot00000000000000(function () {
  "use strict";

  var button_fetch = document.getElementsByName("fetch")[0];
  var pre = document.getElementsByTagName("pre")[0];

  function fetch(event) {
    postMessage("fetch");
  }

  function postMessage(action) {
    var pw = document.getElementById("pw").value;
    // Send a message to index-worker.js.
    // https://developer.mozilla.org/en-US/docs/Web/API/Worker/postmessage
    pre.innerHTML = "
" + pre.innerHTML; worker.postMessage({ action: action, pw: pw }); } button_fetch.addEventListener("click", fetch); // Use a web worker to prevent the main thread from blocking. // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers var worker = new Worker("/index-worker.js"); // Receive a message from index-worker.js. // https://developer.mozilla.org/en-US/docs/Web/API/Worker/onmessage var i = 0; worker.onmessage = function (e) { if (e.data.printErr) pre.innerHTML = i++ + ": " + e.data.printErr + "
" + pre.innerHTML; if (e.data.print) pre.innerHTML = i++ + ": " + e.data.print + "
" + pre.innerHTML; }; })(); libopaque-0.99.3/demos/chan-c-go/000077500000000000000000000000001452546740600164555ustar00rootroot00000000000000libopaque-0.99.3/demos/chan-c-go/README.md000066400000000000000000000001701452546740600177320ustar00rootroot00000000000000see the accompanying blogpost: https://ctrlc.hu/~stef/blog/posts/How_to_use_OPAQUE_for_setting_up_a_secure_channel.html libopaque-0.99.3/demos/chan-c-go/blogpost.org000066400000000000000000000156501452546740600210260ustar00rootroot00000000000000#+OPTIONS: H:2 num:nil toc:nil \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t #+OPTIONS: TeX:t LaTeX:t skip:nil d:nil todo:t pri:nil tags:not-in-toc I heard you need to write a tool that sets up a secure channel between two peers, but you cannot afford the luxury of using certificates for doing so. Maybe you want to develop a less braindead protocol than shadowsocks? All you have is a password? How about combining OPAQUE with a double-ratchet? You heard about OPAQUE, but are afraid to ask how to do this? Fear not, the following bits are here to enlighten you. ** A warning First a warning, since the security of the channel depends on the strength of your password it is essential that password is of high entropy and that your server employs some kind of rate-limiting, and possibly a whole range of other limitations and defense-in-depth mitigations. If possible try to use certificates, preferably in HW tokens though. Of course the best solution would be to use a strong password storage like [[https://www.ctrlc.hu/~stef/blog/posts/sphinx.html][SPHINX]]. ** An example The source code to this example can be found in the [[https://github.com/stef/libopaque/tree/master/demos/chan-c-go][demos/chan-c-go]] directory of the [[https://github.com/stef/libopaque/][libopaque]] sources. The example shows a simple client written in C connecting to a server written in Go. The client takes the password and the ip address of the server as command-line parameters. The server has one user record hard-coded that opens with the "super secure" password: "password". After completing an OPAQUE run, the client and server tries to exchange a message which is protected by the shared session key derived via OPAQUE and used with crypto_secretbox mechanism from libsodium. This will only succeed if the user provided the correct password at the command-line of the client. Let's have a look at the relevant steps the client and server execute. *** First step: client initiates Initiating a OPAQUE session always starts with the client and the user providing the password to it. Well wrap the whole OPAQUE flow in a function that takes a password as input, and when everything is done returns the shared session secret: #+BEGIN_SRC C #include #include "opaque.h" int get_session_secret(const int sock, const uint8_t *pwdU, const size_t pwdU_len, uint8_t sk[OPAQUE_SHARED_SECRETBYTES]) { // let's prepare to make create a credential request, we need some // data to store it in: uint8_t request[OPAQUE_USER_SESSION_PUBLIC_LEN]; uint8_t ctx[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len]; // ctx is sensitive data we should protect it! with c you can // actually protect sensitive data much better than with other // languages, sodium wraps this up nicely and portably: if(-1==sodium_mlock(ctx,sizeof ctx)) { fprintf(stderr,"Failed to protect sensitive context\n"); return -1; } // let's create the credential request if(0!=opaque_CreateCredentialRequest(pwdU, pwdU_len, ctx, request)) { fprintf(stderr,"Failed to create credential request\n"); return -1; } // send off the request to the server if(sizeof request != write(sock, request, sizeof request)) { //fprintf(stderr,); perror("failed to send the request\n"); return -1; } .... #+END_SRC *** Second step: the server prepares a response The server must read a user id and a request from the connection. Using the user id it can load the according user record, and with it can create a credential response. #+BEGIN_SRC go import libopaque ... // we need to store the request request := make([]byte, libopaque.OPAQUE_USER_SESSION_PUBLIC_LEN) // read the request from the connection b, err := c.Read(request) if err != nil || b != libopaque.OPAQUE_USER_SESSION_PUBLIC_LEN { fmt.Println(b) panic(err) } // create a response based on the request, the hard-coded user // record, the ids and the context. resp, sk, _, err := libopaque.CreateCredResp(request, rec, ids, context) if err != nil { panic(err) } // send the response over to the client b, err = c.Write(resp) if err != nil || b != libopaque.OPAQUE_SERVER_SESSION_LEN { fmt.Println(b) panic(err) } ... #+END_SRC And this concludes the servers OPAQUE part, there is no further step necessary. The session secret should be used to setup an encrypted channel, like using the session secret as a key in a sodium/secretbox, or perhaps indeed feeding it to a double ratchet. Authentication will be implicit, if the authentication of any packages between the server and the client fails, then there is something afoul and you should abort your connection. *** Last step: the client finishes the session setup The last step is quite simple, the client reads the servers response, and recovers its credentials. Most notable are the two NULL parameters, one for the authentication token, which we don't need since we are doing implicit user authentication. And the other NULL is for the export_key which we also do not need in our case. #+BEGIN_SRC c ... // we need to store the servers response uint8_t response[OPAQUE_SERVER_SESSION_LEN]; // receive a response from the server if(sizeof response != read(sock, response, sizeof response )) { perror("failed to read the response\n"); return -1; } // we need to supply the same context and user ids to the final step // as have been used by the server const uint8_t context[]="context"; const Opaque_Ids ids={4,(uint8_t*) "user",6,(uint8_t*)"server"}; // we recover the shared session key, and we set the authorization // token and the export_key parameters to NULL since we do not care // about them in this demo. if(0!=opaque_RecoverCredentials(response, ctx, context, strlen((char*)context), &ids, sk, NULL, NULL)) { fprintf(stderr,"Failed to recovercredential\n"); return 1; } // yay everything went fine. return 0; } #+END_SRC The result of this function is in the parameter sk, which should be fed into a counter-part of whatever the server is doing - be it a secretbox, or a double ratchet. ** Summary There is strong initiatives trying to get rid of passwords, and in some cases it makes sense. However there will be many use-cases where this cannot work. If the usage of certificates or hardware tokens is not possible, passwords if strong enough can still provide adequate protection even for protected communication channels. The nice thing about passwords is, that you do not need anything to use them, nothing to carry, nothing to sync, no state, no worries (ok, you still need a client though). And in this example and the accompanying demo we showed how simple it is to use OPAQUE with a password to create a forward secure communication channel between a client and a server. Don't forget to come back for the next episode where we'll have a look how to store securely some sensitive data with OPAQUE. libopaque-0.99.3/demos/chan-c-go/client.c000066400000000000000000000112161452546740600201000ustar00rootroot00000000000000// a simple demo client showing how to establish a secure channel with // a server using OPAQUE // compile it with: // gcc -Wall -o client client.c -lsodium -lopaque #include #include #include #include #include #include #include #include #include "opaque.h" // this function implements the client-side OPAQUE steps, // it requires a socket to the server, // a password unlocking the OPAQUE user record // and it returns a shared session key. int get_session_secret(const int sock, const uint8_t *pwdU, const size_t pwdU_len, uint8_t sk[OPAQUE_SHARED_SECRETBYTES]) { // let's prepare to make create a credential request, we need some // data to store it in: uint8_t request[OPAQUE_USER_SESSION_PUBLIC_LEN]; uint8_t ctx[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len]; // ctx is sensitive data we should protect it! with c you can // actually protect sensitive data much better than with other // languages, sodium wraps this up nicely and portably: if(-1==sodium_mlock(ctx,sizeof ctx)) { fprintf(stderr,"Failed to protect sensitive context\n"); return -1; } // let's create the credential request if(0!=opaque_CreateCredentialRequest(pwdU, pwdU_len, ctx, request)) { fprintf(stderr,"Failed to create credential request\n"); return -1; } // send off the request to the server if(sizeof request != write(sock, request, sizeof request)) { //fprintf(stderr,); perror("failed to send the request\n"); return -1; } // we need to store the servers response uint8_t response[OPAQUE_SERVER_SESSION_LEN]; // receive a response from the server if(sizeof response != read(sock, response, sizeof response )) { perror("failed to read the response\n"); return -1; } // we need to supply the same context and user ids to the final step // as have been used by the server const uint8_t context[]="context"; const Opaque_Ids ids={4,(uint8_t*) "user",6,(uint8_t*)"server"}; // we recover the shared session key, and we set the authorization // token and the export_key parameters to NULL since we do not care // about them in this demo. if(0!=opaque_RecoverCredentials(response, ctx, context, strlen((char*)context), &ids, sk, NULL, NULL)) { fprintf(stderr,"Failed to recovercredential\n"); return 1; } // yay everything went fine. return 0; } int main(int argc, char** argv) { if(argc!=3) { fprintf(stderr,"%s \"password\" \"ip-addr\"\n", argv[0]); exit(1); } // boiler-plate setting up a tcp connection with the server int sock; struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(1337), .sin_addr.s_addr = inet_addr(argv[2]), .sin_zero = {0} }; sock = socket(PF_INET, SOCK_STREAM, 0); if(sock<0) { perror("failed socket call\n"); return 1; } if(0!=connect(sock, (struct sockaddr *) &addr, sizeof addr)) { perror("failed to connect\n"); return 1; } // we are connected, let's do OPAQUE to establish a shared session // key with the server: uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; if(0!=get_session_secret(sock, (uint8_t*) argv[1], strlen(argv[1]), sk)) { fprintf(stderr,"something went wrong establishing session secret\n"); exit(1); } // Now use the shared session key to exchange some messages struct { uint8_t nonce[24]; uint8_t msg[32]; uint8_t mac[16]; } pkt = { .msg = {0} }; const int ct_size=sizeof pkt - sizeof pkt.nonce; memcpy(pkt.msg, &"a secret message", 16); randombytes_buf(pkt.nonce, sizeof pkt.nonce); crypto_secretbox_easy(pkt.msg, pkt.msg, sizeof pkt.msg, pkt.nonce, sk); // pkt now contains an encrypted message using the shared session key // send the encrypted messsage over if(sizeof pkt != write(sock, &pkt, sizeof pkt)) { perror("failed to send the secret message\n"); return -1; } // receive an answer, first a nonce if(sizeof pkt.nonce != read(sock, &pkt, sizeof pkt.nonce)) { perror("failed to read the nonce res"); return -1; } // and the encrypted answer if(ct_size != read(sock, &pkt.msg, ct_size)) { perror("failed to read the response res"); return -1; } // try to decrypt the answer if(0!=crypto_secretbox_open_easy(pkt.msg, pkt.msg, ct_size, pkt.nonce, sk)) { fprintf(stderr,"failed to decrypt the response\n"); return -1; } // all is good, the message is: printf("received message: \"%s\"\n", pkt.msg); return 0; } libopaque-0.99.3/demos/chan-c-go/go.mod000066400000000000000000000002101452546740600175540ustar00rootroot00000000000000module test go 1.17 require ( github.com/jamesruan/sodium v1.0.14 github.com/stef/libopaque/go v0.0.0-20220221211752-d1a24354ba0e ) libopaque-0.99.3/demos/chan-c-go/go.sum000066400000000000000000000006261452546740600176140ustar00rootroot00000000000000github.com/jamesruan/sodium v1.0.14 h1:JfOHobip/lUWouxHV3PwYwu3gsLewPrDrZXO3HuBzUU= github.com/jamesruan/sodium v1.0.14/go.mod h1:GK2+LACf7kuVQ9k7Irk0MB2B65j5rVqkz+9ylGIggZk= github.com/stef/libopaque/go v0.0.0-20220221211752-d1a24354ba0e h1:zFwxw7o5B5sB+Og7/q/ruO8fe73CfRYlNdiMg4C9J0o= github.com/stef/libopaque/go v0.0.0-20220221211752-d1a24354ba0e/go.mod h1:XK7LCqV5P10g3Xa6Jf6dLCLQmsAIQWUHEoewxcyFKwg= libopaque-0.99.3/demos/chan-c-go/server.go000066400000000000000000000057261452546740600203240ustar00rootroot00000000000000package main // a simple example of how to set up an encrypted channel between a // client and server using the OPAQUE protocol this file implements // the server in go. It creates a simple server that listens for // incoming requests, tries to respond using OPAQUE and exchanges a // message using the session key derived using OPAQUE. import ( "encoding/hex" "fmt" "github.com/jamesruan/sodium" "github.com/stef/libopaque/go" "net" ) func main() { // some boilerplate to have a server responding fmt.Println("listening") l, err := net.Listen("tcp", ":1337") if err != nil { fmt.Println(err) return } defer l.Close() // a hardcoded OPAQUE user record, it opens up with the password: "password" rec, err := hex.DecodeString("7a3c6282f02d37a05023b60d5428e6cc5961d4c31221937adae0b574e4d07205000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fc8485b707c63275ec0ca1af4480fb84c3c3ca6984aecbfb1a86d1782b5cccf005f206357c4f6e718c15dd6575e54b8b1fdd94a2b050261f5e12b94587c4d258c68fe296966007b462627b572b17f3a91897d994fadb4ad54946539d0f02550d0000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fe79b50b65fe2b00c48522c0343c92a1d8f8587b0e6a5729690a663babd7dc43eeb7e71fb9b2af2b0185e45c36ab67fd483e9cb9f6296021af6773e2403faad15") if err != nil { panic(err) } // these are the ids also used by the client, they must match ids := libopaque.OpaqueIDS{ IdU: []byte("user"), IdS: []byte("server"), } // the context must also match with the client context := "context" // handle any incoming request for { c, err := l.Accept() if err != nil { fmt.Println(err) return } request := make([]byte, libopaque.OPAQUE_USER_SESSION_PUBLIC_LEN) b, err := c.Read(request) if err != nil || b != libopaque.OPAQUE_USER_SESSION_PUBLIC_LEN { fmt.Println(b) panic(err) } // create a response based on the request, the hard-coded user // record, the ids and the context. resp, sk, _, err := libopaque.CreateCredResp(request, rec, ids, context) if err != nil { panic(err) } // send the response over b, err = c.Write(resp) if err != nil || b != libopaque.OPAQUE_SERVER_SESSION_LEN { fmt.Println(b) panic(err) } // just a dummy fixed size message expected msg := make([]byte, 24+32+16) b, err = c.Read(msg) if err != nil || b != 24+32+16 { fmt.Println(b) panic(err) } // decrypt the message using the shared session key sk msg, err = sodium.Bytes(msg[24:]).SecretBoxOpen(sodium.SecretBoxNonce{msg[:24]}, sodium.SecretBoxKey{sk[:32]}) if err != nil { fmt.Println("failed to decrypt message") panic(err) } fmt.Println("got message:", string(msg)) // create an answer message and encrypt it with the shared // session key sk n := sodium.SecretBoxNonce{} sodium.Randomize(&n) response := "msg acknowledged. Hello World!!\x00" ct := sodium.Bytes([]byte(response)).SecretBox(n, sodium.SecretBoxKey{sk[:32]}) // send the answer c.Write(n.Bytes) c.Write(ct) // and close the connection c.Close() } } libopaque-0.99.3/erlang/000077500000000000000000000000001452546740600150625ustar00rootroot00000000000000libopaque-0.99.3/erlang/README.md000066400000000000000000000140341452546740600163430ustar00rootroot00000000000000# Erlang bindings for libopaque These bindings provide access to libopaque which implements the [IRTF CFRG RFC draft](https://github.com/cfrg/draft-irtf-cfrg-opaque) or you can read the [original paper](https://eprint.iacr.org/2018/163). ## Dependencies These bindings depend on the following: - libopaque: https://github.com/stef/libopaque/ - libsodium ## Building You need to have libopaque installed, and working (thus also libsodium), then: ``` make ``` ## Examples see test.erl ## API There is one data structure that is used by libopaque: ### `Ids` The IDs of the client (idU) and the server (idS) are passed as lists containing two binary items to functions that need to handle IDs. ## 1-step registration 1-step registration is only specified in the original paper. It is not specified by the IRTF CFRG draft. 1-step registration has the benefit that the supplied password (`pwd`) can be checked on the server for password rules (e.g., occurrence in common password lists, please obey [NIST SP 800-63-3b](https://pages.nist.gov/800-63-3/sp800-63b.html#memsecret)). It has the drawback that the password is exposed to the server. ```erlang {Rec, Export_key} = opaque:register(Pwd, Ids, SkS). ``` The function expects these paramters: - `Pwd` is the user's password. - `Ids` is a list containing the the clients and the servers ID as binaries. - `SkS` is an optional explicitly specified server long-term private-key This function returns: - `Rec` should be stored by the server associated with the ID of the user. - `Export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ## 4-step registration Registration as specified in the IRTF CFRG draft consists of the following 4 steps: ### Step 1: The user creates a registration request. ```erlang {Sec, Req} = opaque:create_reg_req(Pwd). ``` - `Pwd` is the user's password. The user should hold on to `Sec` securely until step 3 of the registration process. `Req` needs to be passed to the server running step 2. ### Step 2: The server responds to the registration request. ```erlang {Sec, Resp} = opaque:create_reg_resp(Req, SkS). ``` - `Req` comes from the user running the previous step. - `SkS` is an optional explicitly specified server long-term private-key The server should hold onto `Sec` securely until step 4 of the registration process. `Resp` should be passed to the user running step 3. ### Step 3: The user finalizes the registration using the response from the server. ```erlang {Rec, Export_key } = opaque:finalize_req(Sec, Resp, Ids). ``` - `Sec` contains sensitive data and should be disposed securely after usage in this step. - `Resp` comes from the server running the previous step. - `Ids` is the clients and the servers ID. The function outputs: - `Rec` should be passed to the server running step 4. - `Export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ### Step 4: The server finalizes the user's record. ```erlang Rec = opaque:store_rec(Sec, Sks, Rec). ``` - `Rec` comes from the client running the previous step. - `Sec` contains sensitive data and should be disposed securely after usage in this step. The function returns: - `Rec` should be stored by the server associated with the ID of the user. **Important Note**: Confusingly this function is called `StoreUserRecord`, yet it does not do any storage. How you want to store the record (`Rec`) is up to the implementor using this API. ## Establishing an opaque session After a user has registered with a server, the user can initiate the AKE and thus request its credentials in the following 3(+1)-step protocol: ### Step 1: The user initiates a credential request. ```erlang {Sec, Req} = opaque:create_cred_req(Pwd). ``` - `Pwd` is the user's password. The user should hold onto `Sec` securely until step 3 of the protocol. `Pub` needs to be passed to the server running step 2. ### Step 2: The server responds to the credential request. ```erlang {Resp, Sk, Sec} = opaque:create_cred_resp(Req, Rec, Ids, Context). ``` - `Req` comes from the user running the previous step. - `Rec` is the user's record stored by the server at the end of the registration protocol. - `Ids` is the clients and the servers ID, - `Context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" This function returns: - `Resp` needs to be passed to the user running step 3. - `Sk` is a shared secret, the result of the AKE. - `Sec` is the servers sensitive context. The server should hold onto this valuesecurely until the optional step 4 of the protocol, if needed. otherwise this value should be discarded securely. ### Step 3: The user recovers its credentials from the server's response. ```erlang {Sk, AuthU, Export_key} = opaque:recover_cred(Resp, Sec, Context, Ids). ``` - `Resp` comes from the server running the previous step. - `Sec` contains the client sensitive data from the first step and should be disposed securely after this step. - `Context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" - `Ids` is an array containing the clients and/or servers ID, these must be specified in case they are marked `notPackaged` in `Cfg`. This function returns: - `Sk` is a shared secret, the result of the AKE. - `AuthU` is an authentication tag that can be passed in step 4 for explicit user authentication. - `Export_key` can be used to decrypt additional data stored by the server. ### Step 4 (Optional): The server authenticates the user. This step is only needed if there is no encrypted channel setup towards the server using the shared secret. ```erlang ok = opaque:user_auth(Sec, AuthU). ``` - `Sec` contains the servers sensitive context from the second step and should be disposed securely after usage in this step. - `AuthU` comes from the user running the previous step. The function returns `ok` in case the authentication succeeded, otherwise `fail`. libopaque-0.99.3/erlang/makefile000066400000000000000000000004341452546740600165630ustar00rootroot00000000000000all: opaque.beam opaque.so opaque.beam: opaque.erl erlc opaque.erl opaque.so: opaque.c gcc -fPIC -shared -o opaque.so opaque.c -lopaque -I/usr/lib/erlang/usr/include/ test: opaque.so opaque.beam escript test.erl clean: rm -f erl_crash.dump rm -f opaque.beam rm -f opaque.so libopaque-0.99.3/erlang/opaque.c000066400000000000000000000322101452546740600165160ustar00rootroot00000000000000#include #include #include #include #define OPAQUE_ERL_NOT_TUPLE 1 static int is_nil(ErlNifEnv* env, const ERL_NIF_TERM term) { int res; char buf[5]; res = enif_get_atom(env, term, buf, sizeof buf, ERL_NIF_LATIN1); if(memcmp(buf,"nil", 4)==0) return 1; return 0; } static void getids(ErlNifEnv* env, const ERL_NIF_TERM tuple, Opaque_Ids *ids) { int items=0; const ERL_NIF_TERM* array; if(!enif_get_tuple(env, tuple, &items, &array)) { enif_raise_exception(env, enif_make_atom(env, "ids_invalid_elems")); return; } if(items!=2) { enif_raise_exception(env, enif_make_atom(env, "ids_invalid_size")); return; } ErlNifBinary bin; if(is_nil(env,array[0])) { ids->idU=NULL; ids->idU_len=0; } else { if(enif_inspect_binary(env, array[0], &bin)) { if(bin.size>=(2<<16)) { enif_raise_exception(env, enif_make_atom(env, "idU_too_big")); return; } ids->idU=(uint8_t*) bin.data; ids->idU_len=bin.size; } else { enif_raise_exception(env, enif_make_atom(env, "idU_missing")); return; } } if(is_nil(env,array[1])) { ids->idS=NULL; ids->idS_len=0; } else { if(enif_inspect_binary(env, array[1], &bin)) { if(bin.size>=(2<<16)) { enif_raise_exception(env, enif_make_atom(env, "idU_too_big")); return; } ids->idS=(uint8_t*) bin.data; ids->idS_len=bin.size; } else { enif_raise_exception(env, enif_make_atom(env, "idS_missing")); return; } } } static ERL_NIF_TERM c_register(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { unsigned pwdU_len; if(!enif_get_list_length(env, argv[0], &pwdU_len)) { return enif_raise_exception(env, enif_make_atom(env, "pwdU_not_list")); } char pwdU[pwdU_len+1]; if (!enif_get_string(env, argv[0], pwdU, pwdU_len+1, ERL_NIF_LATIN1)) { return enif_raise_exception(env, enif_make_atom(env, "pwdU_not_string")); } Opaque_Ids ids; getids(env, argv[1], &ids); ERL_NIF_TERM exc; if(enif_has_pending_exception(env,&exc)) return exc; uint8_t *skS=NULL; if(argc == 3) { ErlNifBinary skS_bin; if(!enif_inspect_binary(env, argv[2], &skS_bin)) { return enif_raise_exception(env, enif_make_atom(env, "skS_not_binary")); } if(skS_bin.size!=crypto_scalarmult_SCALARBYTES) { return enif_raise_exception(env, enif_make_atom(env, "skS_invalid_size")); } skS=(uint8_t*) skS_bin.data; } uint8_t export_key[crypto_hash_sha512_BYTES]; uint8_t rec[OPAQUE_USER_RECORD_LEN]; if(0!=opaque_Register(pwdU, pwdU_len, skS, &ids, rec, export_key)) { return enif_raise_exception(env, enif_make_atom(env, "register_failed")); } ERL_NIF_TERM r, ek; memcpy(enif_make_new_binary(env, sizeof rec, &r), rec, sizeof rec); memcpy(enif_make_new_binary(env, sizeof export_key, &ek), export_key, sizeof export_key); return enif_make_tuple2(env, r, ek); } static ERL_NIF_TERM c_create_cred_req(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { unsigned pwdU_len; if(!enif_get_list_length(env, argv[0], &pwdU_len)) { return enif_raise_exception(env, enif_make_atom(env, "pwdU_not_list")); } //fprintf(stderr,"strsize: %d\n", pwdU_len); char pwdU[pwdU_len+1]; if (!enif_get_string(env, argv[0], pwdU, pwdU_len+1, ERL_NIF_LATIN1)) { return enif_raise_exception(env, enif_make_atom(env, "pwdU_not_string")); } uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], pub[OPAQUE_USER_SESSION_PUBLIC_LEN]; // todo sodium_mlock(sec) if(0!=opaque_CreateCredentialRequest(pwdU, pwdU_len, sec, pub)) { return enif_raise_exception(env, enif_make_atom(env, "create_cred_req")); } ERL_NIF_TERM s, p; memcpy(enif_make_new_binary(env, sizeof sec, &s), sec, sizeof sec); memcpy(enif_make_new_binary(env, sizeof pub, &p), pub, sizeof pub); return enif_make_tuple2(env, s, p); } static ERL_NIF_TERM c_create_cred_resp(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ErlNifBinary bin; uint8_t *pub=NULL; if(!enif_inspect_binary(env, argv[0], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "pub_not_binary")); } if(bin.size!=OPAQUE_USER_SESSION_PUBLIC_LEN) { return enif_raise_exception(env, enif_make_atom(env, "pub_invalid_size")); } pub=(uint8_t*) bin.data; uint8_t *rec=NULL; if(!enif_inspect_binary(env, argv[1], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "rec_not_binary")); } if(bin.size!=OPAQUE_USER_RECORD_LEN) { return enif_raise_exception(env, enif_make_atom(env, "rec_invalid_size")); } rec=(uint8_t*) bin.data; Opaque_Ids ids; getids(env, argv[2], &ids); ERL_NIF_TERM exc; if(enif_has_pending_exception(env,&exc)) return exc; unsigned context_len; if(!enif_get_list_length(env, argv[3], &context_len)) { return enif_raise_exception(env, enif_make_atom(env, "context_not_list")); } //fprintf(stderr,"strsize: %d\n", pwdU_len); char context[context_len+1]; if (!enif_get_string(env, argv[3], context, context_len+1, ERL_NIF_LATIN1)) { return enif_raise_exception(env, enif_make_atom(env, "context_not_string")); } uint8_t resp[OPAQUE_SERVER_SESSION_LEN]; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t sec[crypto_auth_hmacsha512_BYTES]={0}; if(0!=opaque_CreateCredentialResponse(pub, rec, &ids, context, context_len, resp, sk, sec)) { return enif_raise_exception(env, enif_make_atom(env, "create_cred_resp")); } ERL_NIF_TERM resp_, sk_, sec_; memcpy(enif_make_new_binary(env, sizeof resp, &resp_), resp, sizeof resp); memcpy(enif_make_new_binary(env, sizeof sk, &sk_), sk, sizeof sk); memcpy(enif_make_new_binary(env, sizeof sec, &sec_), sec, sizeof sec); return enif_make_tuple3(env, resp_, sk_, sec_); } static ERL_NIF_TERM c_recover_cred(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ErlNifBinary bin; uint8_t *resp=NULL; if(!enif_inspect_binary(env, argv[0], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "resp_not_binary")); } if(bin.size!=OPAQUE_SERVER_SESSION_LEN) { return enif_raise_exception(env, enif_make_atom(env, "resp_invalid_size")); } resp=(uint8_t*) bin.data; uint8_t *sec=NULL; if(!enif_inspect_binary(env, argv[1], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "sec_not_binary")); } if(bin.size<=OPAQUE_USER_SESSION_SECRET_LEN) { return enif_raise_exception(env, enif_make_atom(env, "sec_invalid_size")); } sec=(uint8_t*) bin.data; unsigned context_len; if(!enif_get_list_length(env, argv[2], &context_len)) { return enif_raise_exception(env, enif_make_atom(env, "context_not_list")); } char context[context_len+1]; if (!enif_get_string(env, argv[2], context, context_len+1, ERL_NIF_LATIN1)) { return enif_raise_exception(env, enif_make_atom(env, "context_not_string")); } Opaque_Ids ids={0}; getids(env, argv[3], &ids); uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t authU[crypto_auth_hmacsha512_BYTES]; uint8_t export_key[crypto_hash_sha512_BYTES]; if(0!=opaque_RecoverCredentials(resp, sec, context, context_len, &ids, sk, authU, export_key)) { return enif_raise_exception(env, enif_make_atom(env, "recover_cred_failed")); } ERL_NIF_TERM authU_, sk_, export_key_; memcpy(enif_make_new_binary(env, sizeof authU, &authU_), authU, sizeof authU); memcpy(enif_make_new_binary(env, sizeof sk, &sk_), sk, sizeof sk); memcpy(enif_make_new_binary(env, sizeof export_key, &export_key_), export_key, sizeof export_key); return enif_make_tuple3(env, sk_, authU_, export_key_); } static ERL_NIF_TERM c_user_auth(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ErlNifBinary bin; uint8_t *sec=NULL; if(!enif_inspect_binary(env, argv[0], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "sec_not_binary")); } if(bin.size!=crypto_auth_hmacsha512_BYTES) { return enif_raise_exception(env, enif_make_atom(env, "sec_invalid_size")); } sec=(uint8_t*) bin.data; uint8_t *authU=NULL; if(!enif_inspect_binary(env, argv[1], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "authU_not_binary")); } if(bin.size!=crypto_auth_hmacsha512_BYTES) { return enif_raise_exception(env, enif_make_atom(env, "authU_invalid_size")); } authU=(uint8_t*) bin.data; if(0!=opaque_UserAuth(sec, authU)) { return enif_make_atom(env,"fail"); } return enif_make_atom(env,"ok"); } static ERL_NIF_TERM c_create_reg_req(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { unsigned pwdU_len; if(!enif_get_list_length(env, argv[0], &pwdU_len)) { return enif_raise_exception(env, enif_make_atom(env, "pwdU_not_list")); } //fprintf(stderr,"strsize: %d\n", pwdU_len); char pwdU[pwdU_len+1]; if (!enif_get_string(env, argv[0], pwdU, pwdU_len+1, ERL_NIF_LATIN1)) { return enif_raise_exception(env, enif_make_atom(env, "pwdU_not_string")); } //fprintf(stderr,"> %.*s\n", pwdU_len, pwdU); uint8_t sec[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len], M[crypto_core_ristretto255_BYTES]; if(0!=opaque_CreateRegistrationRequest(pwdU, pwdU_len, sec, M)) { return enif_raise_exception(env, enif_make_atom(env, "create_reg_req_failed")); } ERL_NIF_TERM sec_, m_; memcpy(enif_make_new_binary(env, sizeof sec, &sec_), sec, sizeof sec); memcpy(enif_make_new_binary(env, sizeof M, &m_), M, sizeof M); return enif_make_tuple2(env, sec_, m_); } static ERL_NIF_TERM c_create_reg_resp(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ErlNifBinary bin; uint8_t *M=NULL; if(!enif_inspect_binary(env, argv[0], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "m_not_binary")); } if(bin.size!=crypto_core_ristretto255_BYTES) { return enif_raise_exception(env, enif_make_atom(env, "m_invalid_size")); } M=(uint8_t*) bin.data; uint8_t *skS=NULL; if(argc==2) { if(!enif_inspect_binary(env, argv[1], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "skS_not_binary")); } if(bin.size!=crypto_scalarmult_BYTES) { return enif_raise_exception(env, enif_make_atom(env, "skS_invalid_size")); } skS=(uint8_t*) bin.data; } uint8_t sec[OPAQUE_REGISTER_SECRET_LEN], pub[OPAQUE_REGISTER_PUBLIC_LEN]; if(0!=opaque_CreateRegistrationResponse(M, skS, sec, pub)) { return enif_raise_exception(env, enif_make_atom(env, "create_reg_resp_failed")); } ERL_NIF_TERM sec_, pub_; memcpy(enif_make_new_binary(env, sizeof sec, &sec_), sec, sizeof sec); memcpy(enif_make_new_binary(env, sizeof pub, &pub_), pub, sizeof pub); return enif_make_tuple2(env, sec_, pub_); } static ERL_NIF_TERM c_finalize_reg(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ErlNifBinary bin; uint8_t *sec=NULL; if(!enif_inspect_binary(env, argv[0], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "sec_not_binary")); } if(bin.size<=OPAQUE_REGISTER_USER_SEC_LEN) { return enif_raise_exception(env, enif_make_atom(env, "sec_invalid_size")); } sec=(uint8_t*) bin.data; uint8_t *pub=NULL; if(!enif_inspect_binary(env, argv[1], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "pub_not_binary")); } if(bin.size!=OPAQUE_REGISTER_PUBLIC_LEN) { return enif_raise_exception(env, enif_make_atom(env, "pub_invalid_size")); } pub=(uint8_t*) bin.data; Opaque_Ids ids; getids(env, argv[2], &ids); ERL_NIF_TERM exc; if(enif_has_pending_exception(env,&exc)) return exc; uint8_t export_key[crypto_hash_sha512_BYTES]; uint8_t rec[OPAQUE_REGISTRATION_RECORD_LEN]; if(0!=opaque_FinalizeRequest(sec, pub, &ids, rec, export_key)) { return enif_raise_exception(env, enif_make_atom(env, "finalize_reg_failed")); } ERL_NIF_TERM r, ek; memcpy(enif_make_new_binary(env, sizeof rec, &r), rec, sizeof rec); memcpy(enif_make_new_binary(env, sizeof export_key, &ek), export_key, sizeof export_key); return enif_make_tuple2(env, r, ek); } static ERL_NIF_TERM c_store_rec(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ErlNifBinary bin; uint8_t *sec=NULL; if(!enif_inspect_binary(env, argv[0], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "sec_not_binary")); } if(bin.size!=OPAQUE_REGISTER_SECRET_LEN) { return enif_raise_exception(env, enif_make_atom(env, "sec_invalid_size")); } sec=(uint8_t*) bin.data; uint8_t *recU=NULL; if(!enif_inspect_binary(env, argv[1], &bin)) { return enif_raise_exception(env, enif_make_atom(env, "rec_not_binary")); } if(bin.size!=OPAQUE_REGISTRATION_RECORD_LEN) { return enif_raise_exception(env, enif_make_atom(env, "rec_invalid_size")); } recU=(uint8_t*) bin.data; uint8_t rec[OPAQUE_USER_RECORD_LEN]; opaque_StoreUserRecord(sec, recU, rec); ERL_NIF_TERM rec_; memcpy(enif_make_new_binary(env, sizeof rec, &rec_), rec, sizeof rec); return rec_; } static ErlNifFunc nif_funcs[] = { {"register", 2, c_register}, {"register", 3, c_register}, {"create_cred_req", 1, c_create_cred_req}, {"create_cred_resp", 4, c_create_cred_resp}, {"recover_cred", 4, c_recover_cred}, {"user_auth", 2, c_user_auth}, {"create_reg_req", 1, c_create_reg_req}, {"create_reg_resp", 1, c_create_reg_resp}, {"create_reg_resp", 2, c_create_reg_resp}, {"finalize_reg", 3, c_finalize_reg}, {"store_rec", 2, c_store_rec}, }; ERL_NIF_INIT(opaque,nif_funcs,NULL,NULL,NULL,NULL) libopaque-0.99.3/erlang/opaque.erl000066400000000000000000000025311452546740600170610ustar00rootroot00000000000000-module(opaque). -export([init/0, register/2, register/3, create_cred_req/1, create_cred_resp/4, recover_cred/4, user_auth/2, create_reg_req/1, create_reg_resp/1, create_reg_resp/2, finalize_reg/3, store_rec/2]). -on_load(init/0). init() -> case os:getenv("NIF_DIR") of false -> Path = "."; Path -> Path end, ok = erlang:load_nif(Path ++ "/opaque", 0). register(_,_) -> erlang:nif_error("opaque bindings library not loaded"). register(_,_,_) -> erlang:nif_error("opaque bindings library not loaded"). create_cred_req(_) -> erlang:nif_error("opaque bindings library not loaded"). create_cred_resp(_,_,_,_) -> erlang:nif_error("opaque bindings library not loaded"). recover_cred(_,_,_,_) -> erlang:nif_error("opaque bindings library not loaded"). user_auth(_,_) -> erlang:nif_error("opaque bindings library not loaded"). create_reg_req(_) -> erlang:nif_error("opaque bindings library not loaded"). create_reg_resp(_) -> erlang:nif_error("opaque bindings library not loaded"). create_reg_resp(_,_) -> erlang:nif_error("opaque bindings library not loaded"). finalize_reg(_,_,_) -> erlang:nif_error("opaque bindings library not loaded"). store_rec(_,_) -> erlang:nif_error("opaque bindings library not loaded"). libopaque-0.99.3/erlang/test.erl000077500000000000000000000106621452546740600165550ustar00rootroot00000000000000-module(test). -compile({no_auto_import,[register/2]}). -import(opaque,[register/2, register/3, create_cred_req/1, create_cred_resp/4, recover_cred/4, user_auth/2, create_reg_req/1, create_reg_resp/1, create_reg_resp/2, finalize_reg/3, store_rec/2]). test_reg_no_sks() -> Ids = {<<"idU">>, <<"idS">>}, Sks = <<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31>>, {Rec_ns, Ek_ns} = opaque:register("asdf", Ids), io:format("rec: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Rec_ns ]]), io:format("ek: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Ek_ns ]]), {Rec, Ek} = opaque:register("asdf", Ids, Sks), io:format("rec: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Rec ]]), io:format("ek: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Ek ]]), {Sec, Pub} = opaque:create_cred_req("asdf"), io:format("sec: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Sec ]]), io:format("pub: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Pub ]]), {Resp, Sk_serv, Sec_serv} = opaque:create_cred_resp(Pub, Rec, Ids, "context"), io:format("resp: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Resp ]]), io:format("sk: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Sk_serv ]]), io:format("sec: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Sec_serv ]]), {Sk_user, AuthU, Export_key} = opaque:recover_cred(Resp, Sec, "context", Ids), io:format("sk: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Sk_user ]]), io:format("authU: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= AuthU ]]), io:format("ek: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Export_key ]]), ok = opaque:user_auth(Sec_serv, AuthU). test_private_reg() -> {Sec_user, M} = opaque:create_reg_req("asdf"), {Sec_serv, Pub} = opaque:create_reg_resp(M), Ids = {<<"idU">>, <<"idS">>}, {Rec0, Export_key} = opaque:finalize_reg(Sec_user, Pub, Ids), Rec = opaque:store_rec(Sec_serv, Rec0), {SSec, SPub} = opaque:create_cred_req("asdf"), {Resp, Sk, SSec_serv} = opaque:create_cred_resp(SPub, Rec, Ids, "context"), {Sk, AuthU, Export_key} = opaque:recover_cred(Resp, SSec, "context", Ids), ok = opaque:user_auth(SSec_serv, AuthU). test_private_1kreg() -> {Sec_user, M} = opaque:create_reg_req("asdf"), SkS = <<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31>>, {Sec_serv, Pub} = opaque:create_reg_resp(M, SkS), Ids = {nil, nil}, {Rec0, Export_key} = opaque:finalize_reg(Sec_user, Pub, Ids), Rec = opaque:store_rec(Sec_serv, Rec0), {SSec, SPub} = opaque:create_cred_req("asdf"), {Resp, Sk, SSec_serv} = opaque:create_cred_resp(SPub, Rec, Ids, "context"), {Sk, AuthU, Export_key} = opaque:recover_cred(Resp, SSec, "context", Ids), ok = opaque:user_auth(SSec_serv, AuthU). test_reg_sks() -> Ids = {<<"idU">>, <<"idS">>}, Sks = <<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31>>, {Rec, Ek} = opaque:register("asdf", Ids, Sks), io:format("rec: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Rec ]]), io:format("ek: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Ek ]]), {Sec, Pub} = opaque:create_cred_req("asdf"), io:format("sec: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Sec ]]), io:format("pub: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Pub ]]), {Resp, Sk_serv, Sec_serv} = opaque:create_cred_resp(Pub, Rec, Ids, "context"), io:format("resp: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Resp ]]), io:format("sk: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Sk_serv ]]), io:format("sec: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Sec_serv ]]), {Sk_user, AuthU, Export_key} = opaque:recover_cred(Resp, Sec, "context", Ids), io:format("sk: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Sk_user ]]), io:format("authU: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= AuthU ]]), io:format("ek: ~s~n", [[io_lib:format("~2.16.0b",[X]) || <> <= Export_key ]]), ok = opaque:user_auth(Sec_serv, AuthU). main([]) -> test_reg_no_sks(), test_reg_sks(), test_private_reg(), test_private_1kreg(), io:format("all ok~n", []). libopaque-0.99.3/go/000077500000000000000000000000001452546740600142175ustar00rootroot00000000000000libopaque-0.99.3/go/go.mod000066400000000000000000000000731452546740600153250ustar00rootroot00000000000000module github.com/stef/libopaque/go go 1.12 //require () libopaque-0.99.3/go/makefile000066400000000000000000000001341452546740600157150ustar00rootroot00000000000000doc: README.txt README.txt: opaque.go go doc -all . >README.txt tests: go test -v ./... libopaque-0.99.3/go/opaque.go000066400000000000000000000401411452546740600160400ustar00rootroot00000000000000package libopaque // #cgo CFLAGS: -g -Wall // #cgo LDFLAGS: -lopaque // #include // #include import "C" import ( "errors" "unsafe" ) // Type wrapping the IDs of the user and the server type OpaqueIDS struct { // users id IdU []byte // servers id IdS []byte } const OPAQUE_SHARED_SECRETBYTES = C.OPAQUE_SHARED_SECRETBYTES const OPAQUE_ENVELOPE_NONCEBYTES = C.OPAQUE_ENVELOPE_NONCEBYTES const OPAQUE_NONCE_BYTES = C.OPAQUE_NONCE_BYTES const OPAQUE_REGISTRATION_RECORD_LEN = C.OPAQUE_REGISTRATION_RECORD_LEN const OPAQUE_USER_RECORD_LEN = C.OPAQUE_USER_RECORD_LEN const OPAQUE_USER_SESSION_PUBLIC_LEN = C.OPAQUE_USER_SESSION_PUBLIC_LEN const OPAQUE_USER_SESSION_SECRET_LEN = C.OPAQUE_USER_SESSION_SECRET_LEN const OPAQUE_SERVER_SESSION_LEN = C.OPAQUE_SERVER_SESSION_LEN const OPAQUE_REGISTER_USER_SEC_LEN = C.OPAQUE_REGISTER_USER_SEC_LEN const OPAQUE_REGISTER_PUBLIC_LEN = C.OPAQUE_REGISTER_PUBLIC_LEN const OPAQUE_REGISTER_SECRET_LEN = C.OPAQUE_REGISTER_SECRET_LEN // This function implements the storePwdFile function from the paper // it is not specified by the RFC. This function runs on the server // and creates a new output record rec of secret key material. The // function accepts an optional long-term private key in the `skS` // parameter. The server needs to implement the storage of this record // and any binding to user names or as the paper suggests sid. func Register(pwdU string, skS []byte, ids OpaqueIDS) ([]byte, []byte, error) { // int opaque_Register( // const uint8_t *pwdU, // const uint16_t pwdU_len, // const uint8_t skS[crypto_scalarmult_SCALARBYTES], // const Opaque_Ids *ids, // out: // uint8_t rec[OPAQUE_USER_RECORD_LEN/*+envU_len*/], // uint8_t export_key[crypto_hash_sha512_BYTES]); if len(ids.IdU) > (2 << 16) { return nil, nil, errors.New("idU too big") } if len(ids.IdS) > (2 << 16) { return nil, nil, errors.New("idS too big") } idCC := C.Opaque_Ids{ idU: (*C.uchar)(C.CBytes(ids.IdU)), idU_len: C.ushort(len(ids.IdU)), idS: (*C.uchar)(C.CBytes(ids.IdS)), idS_len: C.ushort(len(ids.IdS)), } skS_ptr := (*C.uchar)(nil) if skS != nil { if len(skS) != C.crypto_scalarmult_SCALARBYTES { return nil, nil, errors.New("invalid skS") } skS_ptr = (*C.uchar)(C.CBytes(skS)) } pwdB := []byte(pwdU) rec := C.malloc(C.OPAQUE_USER_RECORD_LEN) if rec == nil { return nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(rec)) ek := C.malloc(C.crypto_hash_sha512_BYTES) if ek == nil { return nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(ek)) ret := C.opaque_Register( (*C.uchar)(C.CBytes(pwdB)), C.ushort(len(pwdB)), skS_ptr, &idCC, (*C.uchar)(rec), (*C.uchar)(ek), ) if ret != 0 { return nil, nil, errors.New("Register failed") } r := C.GoBytes(rec, (C.int)(C.OPAQUE_USER_RECORD_LEN)) e := C.GoBytes(ek, (C.crypto_hash_sha512_BYTES)) return r, e, nil } // This function initiates a new OPAQUE session, is the same as the // function defined in the paper with the name usrSession. func CreateCredReq(pwdU string) ([]byte, []byte, error) { //int opaque_CreateCredentialRequest( // in: // const uint8_t *pwdU, const uint16_t pwdU_len, // out: // uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], // uint8_t pub[OPAQUE_USER_SESSION_PUBLIC_LEN]); pwdB := []byte(pwdU) sec := C.malloc(C.OPAQUE_USER_SESSION_SECRET_LEN + C.ulong(len(pwdB))) if sec == nil { return nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(sec)) pub := C.malloc(C.OPAQUE_USER_SESSION_PUBLIC_LEN) if pub == nil { return nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(pub)) ret := C.opaque_CreateCredentialRequest( (*C.uchar)(C.CBytes(pwdB)), C.ushort(len(pwdB)), (*C.uchar)(sec), (*C.uchar)(pub), ) if ret != 0 { return nil, nil, errors.New("CreateCredReq failed") } s := C.GoBytes(sec, (C.int)(C.OPAQUE_USER_SESSION_SECRET_LEN+len(pwdB))) p := C.GoBytes(pub, (C.OPAQUE_USER_SESSION_PUBLIC_LEN)) return s, p, nil } // This is the same function as defined in the paper with name // srvSession name. This function runs on the server and receives the // output pub from the user running CreateCredReq(), furthermore the // server needs to load the user record created when registering the // user with Register() or StoreUserRec(). These input parameters are // transformed into a secret/shared session key sk and a response resp // to be sent back to the user. func CreateCredResp(pub []byte, rec []byte, ids OpaqueIDS, context string) ([]byte, []byte, []byte, error) { // int opaque_CreateCredentialResponse( // in: // const uint8_t pub[OPAQUE_USER_SESSION_PUBLIC_LEN], // const uint8_t rec[OPAQUE_USER_RECORD_LEN], // const Opaque_Ids *ids, // const uint8_t *context, size_t context_len // out: // uint8_t resp[OPAQUE_SERVER_SESSION_LEN], // uint8_t sk[OPAQUE_SHARED_SECRETBYTES], // uint8_t sec[crypto_auth_hmacsha512_BYTES]); if len(pub) != C.OPAQUE_USER_SESSION_PUBLIC_LEN { return nil, nil, nil, errors.New("invalid pub param") } ctxB := []byte(context) if len(ids.IdU) > (2 << 16) { return nil, nil, nil, errors.New("idU too big") } if len(ids.IdS) > (2 << 16) { return nil, nil, nil, errors.New("idS too big") } idCC := C.Opaque_Ids{ idU: (*C.uchar)(C.CBytes(ids.IdU)), idU_len: C.ushort(len(ids.IdU)), idS: (*C.uchar)(C.CBytes(ids.IdS)), idS_len: C.ushort(len(ids.IdS)), } if len(rec) != (C.OPAQUE_USER_RECORD_LEN) { return nil, nil, nil, errors.New("invalid rec param") } resp := C.malloc(C.OPAQUE_SERVER_SESSION_LEN) if resp == nil { return nil, nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(resp)) sk := C.malloc(C.OPAQUE_SHARED_SECRETBYTES) if sk == nil { return nil, nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(sk)) sec := C.malloc(C.crypto_auth_hmacsha512_BYTES) if sec == nil { return nil, nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(sec)) ret := C.opaque_CreateCredentialResponse( (*C.uchar)(C.CBytes(pub)), (*C.uchar)(C.CBytes(rec)), &idCC, (*C.uchar)(C.CBytes(ctxB)), C.ushort(len(ctxB)), (*C.uchar)(resp), (*C.uchar)(sk), (*C.uchar)(sec), ) if ret != 0 { return nil, nil, nil, errors.New("CreateCredResp failed") } r := C.GoBytes(resp, (C.int)(C.OPAQUE_SERVER_SESSION_LEN)) k := C.GoBytes(sk, (C.OPAQUE_SHARED_SECRETBYTES)) s := C.GoBytes(sec, (C.crypto_auth_hmacsha512_BYTES)) return r, k, s, nil } // This is the same function as defined in the paper with the // usrSessionEnd name. It is run by the user and receives as input the // response from the previous server CreateCredResp() function as well // as the sec value from running the CreateCredReq() function // that initiated this instantiation of this protocol, All these input // parameters are transformed into a shared/secret session key pk, // which should be the same as the one calculated by the // CreateCredResp() function. func RecoverCred(resp []byte, sec []byte, context string, ids OpaqueIDS) ([]byte, []byte, []byte, error) { // int opaque_RecoverCredentials( // in: // const uint8_t resp[OPAQUE_SERVER_SESSION_LEN/*+envU_len*/], // const uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN/*+pwdU_len*/], // const uint8_t *context, const size_t context_len, // Opaque_Ids *ids, // out: // uint8_t sk[OPAQUE_SHARED_SECRETBYTES], // uint8_t authU[crypto_auth_hmacsha512_BYTES], // uint8_t export_key[crypto_hash_sha512_BYTES]); if len(resp) != C.OPAQUE_SERVER_SESSION_LEN { return nil, nil, nil, errors.New("invalid resp param") } if len(sec) <= C.OPAQUE_USER_SESSION_SECRET_LEN { return nil, nil, nil, errors.New("invalid sec param") } ctxB := []byte(context) // handle opaque ids if len(ids.IdU) > (2 << 16) { return nil, nil, nil, errors.New("idU too big") } if len(ids.IdS) > (2 << 16) { return nil, nil, nil, errors.New("idS too big") } idCC := C.Opaque_Ids{ idU: (*C.uchar)(C.CBytes(ids.IdU)), idU_len: C.ushort(len(ids.IdU)), idS: (*C.uchar)(C.CBytes(ids.IdS)), idS_len: C.ushort(len(ids.IdS)), } sk := C.malloc(C.OPAQUE_SHARED_SECRETBYTES) if sk == nil { return nil, nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(sk)) authU := C.malloc(C.crypto_auth_hmacsha512_BYTES) if authU == nil { return nil, nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(authU)) export_key := C.malloc(C.crypto_hash_sha512_BYTES) if export_key == nil { return nil, nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(export_key)) ret := C.opaque_RecoverCredentials( (*C.uchar)(C.CBytes(resp)), (*C.uchar)(C.CBytes(sec)), (*C.uchar)(C.CBytes(ctxB)), C.ushort(len(ctxB)), &idCC, (*C.uchar)(sk), (*C.uchar)(authU), (*C.uchar)(export_key), ) if ret != 0 { return nil, nil, nil, errors.New("recover creds failed") } s := C.GoBytes(sk, (C.int)(C.OPAQUE_SHARED_SECRETBYTES)) a := C.GoBytes(authU, (C.crypto_auth_hmacsha512_BYTES)) e := C.GoBytes(export_key, (C.crypto_hash_sha512_BYTES)) return s, a, e, nil } // This is a function not explicitly specified in the original paper. In the // irtf cfrg draft authentication is done using a hmac of the session // transcript with different keys coming out of a hkdf after the key // exchange. func UserAuth(sec []byte, authU []byte) error { // int opaque_UserAuth( // in // const uint8_t sec[OPAQUE_SERVER_AUTH_CTX_LEN], // const uint8_t authU[crypto_auth_hmacsha512_BYTES]); if len(sec) != C.crypto_auth_hmacsha512_BYTES { return errors.New("invalid sec param") } if len(authU) != C.crypto_auth_hmacsha512_BYTES { return errors.New("invalid authU param") } ret := C.opaque_UserAuth( (*C.uchar)(C.CBytes(sec)), (*C.uchar)(C.CBytes(authU)), ) if ret != 0 { return errors.New("user auth failed") } return nil } // Initial step to start registering a new user/client with the server. // The user inputs its password pwdU, and receives a secret context sec // and a blinded value M as output. sec should be protected until // step 3 of this registration protocol and the value req should be // passed to the server. func CreateRegReq(pwdU string) ([]byte, []byte, error) { // int opaque_CreateRegistrationRequest( // int // const uint8_t *pwdU, // const uint16_t pwdU_len, // out // uint8_t sec[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len], // uint8_t M[crypto_core_ristretto255_BYTES]); pwdB := []byte(pwdU) pwdU_len := C.ushort(len(pwdB)) sec := C.malloc(C.OPAQUE_REGISTER_USER_SEC_LEN + C.ulong(pwdU_len)) if sec == nil { return nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(sec)) M := C.malloc(C.crypto_core_ristretto255_BYTES) if M == nil { return nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(M)) ret := C.opaque_CreateRegistrationRequest( (*C.uchar)(C.CBytes(pwdB)), C.ushort(pwdU_len), (*C.uchar)(sec), (*C.uchar)(M), ) if ret != 0 { return nil, nil, errors.New("create reg req failed") } s := C.GoBytes(sec, (C.int)(C.OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len)) m := C.GoBytes(M, (C.crypto_core_ristretto255_BYTES)) return s, m, nil } // Server evaluates OPRF and creates a user-specific public/private keypair // // The server receives req from the users invocation of its // CreateRegReq() function as well as an optional long-term // private-key, it outputs a value sec which needs to be protected // until step 4 by the server. This function also outputs a value resp // which needs to be passed to the user. func CreateRegResp(req []byte, skS []byte) ([]byte, []byte, error) { // int opaque_CreateRegistrationResponse( // in // const uint8_t M[crypto_core_ristretto255_BYTES], // const uint8_t skS[crypto_scalarmult_SCALARBYTES], // out // uint8_t sec[OPAQUE_REGISTER_SECRET_LEN], // uint8_t pub[OPAQUE_REGISTER_PUBLIC_LEN]); if len(req) != C.crypto_core_ristretto255_BYTES { return nil, nil, errors.New("invalid req param") } if skS != nil && len(skS) != C.crypto_scalarmult_SCALARBYTES { return nil, nil, errors.New("invalid pkS param") } sec := C.malloc(C.OPAQUE_REGISTER_SECRET_LEN) if sec == nil { return nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(sec)) resp := C.malloc(C.OPAQUE_REGISTER_PUBLIC_LEN) if resp == nil { return nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(resp)) ret := C.int(0) ret = C.opaque_CreateRegistrationResponse( (*C.uchar)(C.CBytes(req)), (*C.uchar)(C.CBytes(skS)), (*C.uchar)(sec), (*C.uchar)(resp)) if ret != 0 { return nil, nil, errors.New("create reg resp failed") } s := C.GoBytes(sec, C.OPAQUE_REGISTER_SECRET_LEN) r := C.GoBytes(resp, C.OPAQUE_REGISTER_PUBLIC_LEN) return s, r, nil } // Final Registration step - server adds own info to the record to be stored. // // The rfc does not explicitly specify this function. The server // combines the sec value from its run of its CreateRegResp() function // with the rec output of the users opaque_FinalizeRequest() function, // creating the final record, which should be the same as the output // of the 1-step storePwdFile() init function of the paper. The server // should save this record in combination with a user id and/or sid // value as suggested in the paper. func FinalizeReq(sec []byte, resp []byte, ids OpaqueIDS) ([]byte, []byte, error) { // int opaque_FinalizeRequest( // in // const uint8_t sec[OPAQUE_REGISTER_USER_SEC_LEN/*+pwdU_len*/], // const uint8_t pub[OPAQUE_REGISTER_PUBLIC_LEN], // const Opaque_Ids *ids, // out // uint8_t rec[OPAQUE_REGISTRATION_RECORD_LEN], // uint8_t export_key[crypto_hash_sha512_BYTES]); if len(sec) <= C.OPAQUE_REGISTER_USER_SEC_LEN { return nil, nil, errors.New("invalid sec param") } if len(resp) != C.OPAQUE_REGISTER_PUBLIC_LEN { return nil, nil, errors.New("invalid resp param") } if len(ids.IdU) > (2 << 16) { return nil, nil, errors.New("idU too big") } if len(ids.IdS) > (2 << 16) { return nil, nil, errors.New("idS too big") } idCC := C.Opaque_Ids{ idU: (*C.uchar)(C.CBytes(ids.IdU)), idU_len: C.ushort(len(ids.IdU)), idS: (*C.uchar)(C.CBytes(ids.IdS)), idS_len: C.ushort(len(ids.IdS)), } rec := C.malloc(C.OPAQUE_REGISTRATION_RECORD_LEN) if rec == nil { return nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(rec)) ek := C.malloc(C.crypto_hash_sha512_BYTES) if ek == nil { return nil, nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(ek)) ret := C.opaque_FinalizeRequest( (*C.uchar)(C.CBytes(sec)), (*C.uchar)(C.CBytes(resp)), &idCC, (*C.uchar)(rec), (*C.uchar)(ek), ) if ret != 0 { return nil, nil, errors.New("finalize req failed") } r := C.GoBytes(rec, (C.int)(C.OPAQUE_REGISTRATION_RECORD_LEN)) e := C.GoBytes(ek, (C.crypto_hash_sha512_BYTES)) return r, e, nil } // Final Registration step Global Server Key Version - server adds own // info to the record to be stored. // // The server combines the sec value from its run of its // CreateRegResp() function with the rec output of the users // FinalizeReq() function, creating the final record, which should be // the same as the output of the 1-step storePwdFile() init function // of the paper. The server should save this record in combination // with a user id and/or sid value as suggested in the paper. func StoreUserRec(sec []byte, recU []byte) ([]byte, error) { // void opaque_StoreUserRecord( // in // const uint8_t sec[OPAQUE_REGISTER_SECRET_LEN], // uint8_t recU[OPAQUE_REGISTRATION_RECORD_LEN]); // out // uint8_t rec[OPAQUE_USER_RECORD_LEN]); if len(sec) != C.OPAQUE_REGISTER_SECRET_LEN { return nil, errors.New("invalid sec param") } if len(recU) != C.OPAQUE_REGISTRATION_RECORD_LEN { return nil, errors.New("invalid recU param") } //uint8_t rec[OPAQUE_USER_RECORD_LEN] rec := C.malloc(C.OPAQUE_USER_RECORD_LEN) if rec == nil { return nil, errors.New("out of memory") } defer C.free(unsafe.Pointer(rec)) C.opaque_StoreUserRecord( (*C.uchar)(C.CBytes(sec)), (*C.uchar)(C.CBytes(recU)), (*C.uchar)(rec), ) r := C.GoBytes(rec, (C.int)(C.OPAQUE_USER_RECORD_LEN)) return r, nil } libopaque-0.99.3/go/opaque_test.go000066400000000000000000000127121452546740600171020ustar00rootroot00000000000000package libopaque import ( "bytes" "encoding/hex" "errors" "fmt" "testing" ) func TestRegister(t *testing.T) { ids := OpaqueIDS{ IdU: []byte("user"), IdS: []byte("server"), } context := "context" rec, ek, err := Register("asdf", nil, ids) if err != nil { t.Error(err) } else { fmt.Printf("register success\n%x\n%x\n", rec, ek) } sec, pub, err := CreateCredReq("asdf") if err != nil { t.Error(err) } else { fmt.Printf("Success\nsec: %x\npub: %x\n", sec, pub) } resp, sk, ssec, err := CreateCredResp(pub, rec, ids, context) if err != nil { t.Error(err) } else { fmt.Printf("Success\nresp: %x\nsk: %x\nsec: %x\n", resp, sk, ssec) } skU, authU, export_key, err := RecoverCred(resp, sec, context, ids) if err != nil { t.Error(err) } else { if !bytes.Equal(skU, sk) { t.Error(errors.New("sk doesn't match")) } else if !bytes.Equal(ek, export_key) { t.Error(errors.New("export_key doesn't match")) } else { fmt.Printf("Success\nsk: %x\nauthU: %x\nexport_key: %x\n", skU, authU, export_key) } } err = UserAuth(ssec, authU) if err != nil { t.Error(err) } else { fmt.Printf("Auth Success\n") } } func TestRegisterSks(t *testing.T) { skS, err := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") if err != nil { t.Error(err) } ids := OpaqueIDS{ IdS: []byte("server"), IdU: []byte("user"), } context := "context" rec, ek, err := Register("asdf", skS, ids) if err != nil { t.Error(err) } else { fmt.Printf("Success\nrec: %x\nek: %x\n", rec, ek) } sec, pub, err := CreateCredReq("asdf") if err != nil { t.Error(err) } else { fmt.Printf("Success\nsec: %x\npub: %x\n", sec, pub) } resp, sk, ssec, err := CreateCredResp(pub, rec, ids, context) if err != nil { t.Error(err) } else { fmt.Printf("Success\nresp: %x\nsk: %x\nsec: %x\n", resp, sk, ssec) } skU, authU, export_key, err := RecoverCred(resp, sec, context, ids) if err != nil { t.Error(err) } else { if !bytes.Equal(skU, sk) { t.Error(errors.New("sk doesn't match")) } else if !bytes.Equal(ek, export_key) { t.Error(errors.New("export_key doesn't match")) } else { fmt.Printf("Success\nsk: %x\nauthU: %x\nexport_key: %x\n", skU, authU, export_key) } } err = UserAuth(ssec, authU) if err != nil { t.Error(err) } else { fmt.Printf("Auth Success\n") } } func TestRegDance(t *testing.T) { sec, req, err := CreateRegReq("asdf") if err != nil { t.Error(err) } else { fmt.Printf("create reg req success\n%x\n%x\n", sec, req) } ssec, resp, err := CreateRegResp(req, nil) if err != nil { t.Error(err) } else { fmt.Printf("create reg resp success\n%x\n%x\n", ssec, resp) } ids := OpaqueIDS{ IdS: []byte("server"), IdU: []byte("user"), } recU, ek0, err := FinalizeReq(sec, resp, ids) if err != nil { t.Error(err) } else { fmt.Printf("finalize req success\nrecU: %x\nek: %x\n", recU, ek0) } rec, err := StoreUserRec(ssec, recU) if err != nil { t.Error(err) } else { fmt.Printf("store user rec success\nrec: %x\n", rec) } sec, pub, err := CreateCredReq("asdf") if err != nil { t.Error(err) } else { fmt.Printf("Success\nsec: %x\npub: %x\n", sec, pub) } context := "context" resp, sk, ssec, err := CreateCredResp(pub, rec, ids, context) if err != nil { t.Error(err) } else { fmt.Printf("Success\nresp: %x\nsk: %x\nsec: %x\n", resp, sk, ssec) } skU, authU, export_key, err := RecoverCred(resp, sec, context, ids) if err != nil { t.Error(err) } else { if !bytes.Equal(skU, sk) { t.Error(errors.New("sk doesn't match")) } else if !bytes.Equal(ek0, export_key) { t.Error(errors.New("export_key doesn't match")) } else { fmt.Printf("Success\nsk: %x\nauthU: %x\nexport_key: %x\n", skU, authU, export_key) } } err = UserAuth(ssec, authU) if err != nil { t.Error(err) } else { fmt.Printf("Auth Success\n") } } func TestRegDance1k(t *testing.T) { sec, req, err := CreateRegReq("asdf") if err != nil { t.Error(err) } else { fmt.Printf("create reg req success\n%x\n%x\n", sec, req) } skS, err := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") if err != nil { t.Error(err) } ssec, resp, err := CreateRegResp(req, skS) if err != nil { t.Error(err) } else { fmt.Printf("create reg resp success\n%x\n%x\n", ssec, resp) } ids := OpaqueIDS{ IdS: []byte("server"), IdU: []byte("user"), } recU, ek0, err := FinalizeReq(sec, resp, ids) if err != nil { t.Error(err) } else { fmt.Printf("finalize req success\nrecU: %x\nek: %x\n", recU, ek0) } rec, err := StoreUserRec(ssec, recU) if err != nil { t.Error(err) } else { fmt.Printf("store user rec success\nrec: %x\n", rec) } sec, pub, err := CreateCredReq("asdf") if err != nil { t.Error(err) } else { fmt.Printf("Success\nsec: %x\npub: %x\n", sec, pub) } context := "context" resp, sk, ssec, err := CreateCredResp(pub, rec, ids, context) if err != nil { t.Error(err) } else { fmt.Printf("Success\nresp: %x\nsk: %x\nsec: %x\n", resp, sk, ssec) } skU, authU, export_key, err := RecoverCred(resp, sec, context, ids) if err != nil { t.Error(err) } else { if !bytes.Equal(skU, sk) { t.Error(errors.New("sk doesn't match")) } else if !bytes.Equal(ek0, export_key) { t.Error(errors.New("export_key doesn't match")) } else { fmt.Printf("Success\nsk: %x\nauthU: %x\nexport_key: %x\n", skU, authU, export_key) } } err = UserAuth(ssec, authU) if err != nil { t.Error(err) } else { fmt.Printf("Auth Success\n") } } libopaque-0.99.3/java/000077500000000000000000000000001452546740600145335ustar00rootroot00000000000000libopaque-0.99.3/java/README.md000066400000000000000000000160621452546740600160170ustar00rootroot00000000000000# Java JNI bindings for libopaque These bindings provide access to libopaque which implements the [IRTF CFRG RFC draft](https://github.com/cfrg/draft-irtf-cfrg-opaque) or you can read the [original paper](https://eprint.iacr.org/2018/163). ## Dependencies These bindings depend on the following: - java-openjdk-dev - libopaque: https://github.com/stef/libopaque/ - libsodium ## Building You need to have the dependencies installed, then: ``` make ``` ## Examples see test.java ## API There is onedata structure that is used by libopaque: ### `Ids` The IDs of the client (idU) and the server (idS) are passed using instances of the `OpaqueIds` class to functions that need to handle IDs. example: ```java OpaqueIds ids = new OpaqueIds("idU".getBytes(Charset.forName("UTF-8")), "idS".getBytes(Charset.forName("UTF-8"))); ``` ## 1-step registration 1-step registration is only specified in the original paper. It is not specified by the IRTF CFRG draft. 1-step registration has the benefit that the supplied password (`pwd`) can be checked on the server for password rules (e.g., occurrence in common password lists, please obey [NIST SP 800-63-3b](https://pages.nist.gov/800-63-3/sp800-63b.html#memsecret)). It has the drawback that the password is exposed to the server. ```java Opaque o = new Opaque(); OpaqueRecExpKey ret = o.register(pwd, skS, ids); ``` The function expects these paramters: - `pwd` is the user's password, - `skS` is an optional explicitly specified server long-term private key, - `ids` is an `OpaqueIds` instance containing the IDs of the client and server. This function returns an instance of the `OpaqueRecExpKey` class, this class has two member variables: - `rec` should be stored by the server associated with the ID of the user. - `export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ## 4-step registration Registration as specified in the IRTF CFRG draft consists of the following 4 steps: ### Step 1: The user creates a registration request. ```java OpaqueRegReq regReq = o.createRegReq(pwd); ``` - `pwd` is the user's password. The function returns an instance of the `OpaqueRegReq` class, which has two member variables: - `sec` The user should hold on to it securely until step 3 of the registration process. - `M` needs to be passed to the server running step 2. ### Step 2: The server responds to the registration request. ```java OpaqueRegResp regResp = o.createRegResp(regReq.M, skS); ``` The input parameters are: - `req` comes from the user running the previous step. - `skS` is an optional explicitly specified server long-term private key The function returns an instance of the `OpaqueRegResp` class, which has two member variables: - `sec`: The server should hold onto `sec` securely until step 4 of the registration process. - `pub` should be passed to the user running step 3. ### Step 3: The user finalizes the registration using the response from the server. ```java OpaquePreRecExpKey prerec = o.finalizeReg(regReq.sec, regResp.pub, ids); ``` The input parameters are: - `sec` contains sensitive data and should be disposed securely after usage in this step. - `pub` comes from the server running the previous step. - `ids` is the an `OpaqueIds` instance containing the clients and servers ID. The function outputs an instance of the `OpaquePreRecExpKey` class, which has two member variables: - `rec` should be passed to the server running step 4. - `export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ### Step 4: The server finalizes the user's record. ```java byte[] rec = o.storeRec(regResp.sec, prerec.rec); ``` The input parameters are: - `rec` comes from the client running the previous step. - `sec` contains sensitive data and should be disposed securely after usage in this step. The function returns a byte array: - `rec` should be stored by the server associated with the ID of the user. **Important Note**: Confusingly this function is called `StoreUserRecord`, yet it does not do any storage. How you want to store the record (`rec`) is up to the implementor using this API. ## Establishing an opaque session After a user has registered with a server, the user can initiate the AKE and thus request its credentials in the following 3(+1)-step protocol: ### Step 1: The user initiates a credential request. ```java OpaqueCredReq creq = o.createCredReq(pwd); ``` - `pwd` is the user's password. The output of this function is an instance of the `OpaqueCredReq` class, which has two member variables: - `sec`: The user should hold onto this securely until step 3 of the protocol. - `pub` needs to be passed to the server running step 2. ### Step 2: The server responds to the credential request. ```java OpaqueCredResp cresp = o.createCredResp(creq.pub, rec, ids, context); ``` The input parameters: - `pub` comes from the user running the previous step. - `rec` is the user's record stored by the server at the end of the registration protocol. - `ids` is the an `OpaqueIds` instance containing the clients and servers ID. - `context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" This function returns an instance of the `OpaqueCredResp` class, which has three member variables: - `resp` needs to be passed to the user running step 3. - `sk` is a shared secret, the result of the AKE. - `sec` is the servers sensitive context. The server should hold onto this valuesecurely until the optional step 4 of the protocol, if needed. otherwise this value should be discarded securely. ### Step 3: The user recovers its credentials from the server's response. ```java OpaqueCreds creds = o.recoverCreds(cresp.pub, creq.sec, context, ids); ``` The input parameters: - `pub` comes from the server running the previous step. - `sec` contains the client sensitive data from the first step and should be disposed securely after this step. - `context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" - `ids` is an instance of `OpaqueIds`. This function returns an instance of the `OpaqueCreds` class, which has four member variables: - `sk` is a shared secret, the result of the AKE. - `authU` is an authentication tag that can be passed in step 4 for explicit user authentication. - `export_key` can be used to decrypt additional data stored by the server. ### Step 4 (Optional): The server authenticates the user. This step is only needed if there is no encrypted channel setup towards the server using the shared secret. ```java if(!o.userAuth(cresp.sec, creds.authU)) throw new Exception("Authentication failed!"); ``` The input parameters are: - `sec` contains the servers sensitive context from the second step and should be disposed securely after usage in this step. - `authU` comes from the user running the previous step. The function returns a boolean `false` in case the authentication failed, otherwise `true`. libopaque-0.99.3/java/jni.c000066400000000000000000000402731452546740600154650ustar00rootroot00000000000000#include #include #include #include "opaque.h" static const char *JNIT_CLASS = "Opaque"; static void exception(JNIEnv *env, const char* msg) { jclass cls = (*env)->FindClass(env, "java/lang/Exception"); (*env)->ThrowNew(env, cls, msg); (*env)->DeleteLocalRef(env, cls); } typedef struct { const char *key; char* val; size_t len; } RetVal; static jobject retlist(JNIEnv *env, const char* cls, RetVal *vals) { // Get the class we wish to return an instance of jclass clazz = (*env)->FindClass(env, cls); // Get the method id of an empty constructor in clazz jmethodID constructor = (*env)->GetMethodID(env, clazz, "", "()V"); // Create an instance of clazz jobject obj = (*env)->NewObject(env, clazz, constructor); int i; for(i=0;vals[i].key!=NULL;i++) { jfieldID attr = (*env)->GetFieldID(env, clazz, vals[i].key, "[B"); jbyteArray arr = (*env)->NewByteArray(env, vals[i].len); (*env)->SetByteArrayRegion(env, arr, 0, vals[i].len, vals[i].val); (*env)->SetObjectField(env, obj, attr, arr); (*env)->DeleteLocalRef(env, arr); } (*env)->ExceptionClear(env); (*env)->DeleteLocalRef(env, clazz); return obj; } typedef struct { jbyte *idU_jb; jbyteArray idU; jbyte *idS_jb; jbyteArray idS; } IdGC; static void getids(JNIEnv *env, jobject ids_, Opaque_Ids *ids, IdGC *gc) { jclass cls; jfieldID idUfid, idSfid; cls = (*env)->FindClass(env, "OpaqueIds"); idUfid = (*env)->GetFieldID(env, cls, "idU", "[B"); gc->idU = (jbyteArray)(*env)->GetObjectField(env, ids_, idUfid); idSfid = (*env)->GetFieldID(env, cls, "idS", "[B"); gc->idS = (jbyteArray)(*env)->GetObjectField(env, ids_, idSfid); size_t len; if(NULL==gc->idU) { ids->idU = NULL; ids->idU_len = 0; gc->idU=NULL; } else { len = (*env)->GetArrayLength(env, gc->idU); if(len>=65536) { exception(env, "idU too big"); } gc->idU_jb = (*env)->GetByteArrayElements(env, gc->idU, NULL); ids->idU=(uint8_t*) gc->idU_jb; ids->idU_len = len; } if(NULL==gc->idS) { ids->idS = NULL; ids->idS_len = 0; gc->idS=NULL; } else { len = (*env)->GetArrayLength(env, gc->idS); if(len>=65536) { exception(env, "idS too big"); } gc->idS_jb = (*env)->GetByteArrayElements(env, gc->idS, NULL); ids->idS=(uint8_t*) gc->idS_jb; ids->idS_len = len; } } static jobject c_register(JNIEnv *env, jobject obj, jstring pwd_, jbyteArray skS_, jobject ids_) { const char *pwdU, *skS=NULL; jbyte *skS_jb=NULL; size_t pwdU_len; if(NULL!=skS_) { if((*env)->GetArrayLength(env, skS_)!=crypto_scalarmult_SCALARBYTES) { exception(env, "skS has invalid size"); } skS_jb = (*env)->GetByteArrayElements(env, skS_, NULL); skS = (char*) skS_jb; } pwdU = (*env)->GetStringUTFChars(env, pwd_, 0); pwdU_len = (*env)->GetStringLength(env, pwd_); //fprintf(stderr,"pwdU: %s, pwdU_len: %ld\n", pwdU, pwdU_len); Opaque_Ids ids = {0}; IdGC gc; if(NULL!=ids_) getids(env, ids_, &ids, &gc); uint8_t export_key[crypto_hash_sha512_BYTES]; uint8_t rec[OPAQUE_USER_RECORD_LEN]; if(0!=opaque_Register(pwdU, pwdU_len, skS, &ids, rec, export_key)) { exception(env,"opaque register() failed..."); } (*env)->ReleaseStringUTFChars(env, pwd_, pwdU); if(skS_!=NULL) { (*env)->ReleaseByteArrayElements(env, skS_, skS_jb, JNI_ABORT); } if(NULL!=ids_) { (*env)->ReleaseByteArrayElements(env, gc.idU, gc.idU_jb, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, gc.idS, gc.idS_jb, JNI_ABORT); } RetVal ret[] = {{.key = "rec", .val = rec, .len = sizeof(rec) }, {.key = "export_key", .val = export_key, .len = sizeof(export_key)}, { .key = NULL, .val = NULL}}; return retlist(env, "OpaqueRecExpKey", ret); } static jobject c_register_noIds(JNIEnv *env, jobject obj, jstring pwd_, jbyteArray sks_) { return c_register(env, obj, pwd_, sks_, NULL); } static jobject c_register_noSks(JNIEnv *env, jobject obj, jstring pwd_, jobject ids_) { return c_register(env, obj, pwd_, NULL, ids_); } static jobject c_register1(JNIEnv *env, jobject obj, jstring pwd_) { return c_register(env, obj, pwd_, NULL, NULL); } static jobject c_createCredReq(JNIEnv *env, jobject obj, jstring pwd_) { const char *pwdU; size_t pwdU_len; pwdU = (*env)->GetStringUTFChars(env, pwd_, 0); pwdU_len = (*env)->GetStringLength(env, pwd_); //fprintf(stderr,"pwdU: %s, pwdU_len: %ld\n", pwdU, pwdU_len); uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], pub[OPAQUE_USER_SESSION_PUBLIC_LEN]; if(0!=opaque_CreateCredentialRequest(pwdU, pwdU_len, sec, pub)) { exception(env,"opaque createCredReq() failed..."); } (*env)->ReleaseStringUTFChars(env, pwd_, pwdU); RetVal ret[] = {{.key = "sec", .val = sec, .len = sizeof(sec) }, {.key = "pub", .val = pub, .len = sizeof(pub)}, { .key = NULL, .val = NULL}}; return retlist(env, "OpaqueCredReq", ret); } static jobject c_createCredResp(JNIEnv *env, jobject obj, jbyteArray pub_, jbyteArray rec_, jobject ids_, jstring context_) { const uint8_t *pub, // OPAQUE_USER_SESSION_PUBLIC_LEN *rec; // OPAQUE_USER_RECORD_LEN+envU_len if((*env)->GetArrayLength(env, pub_)!=OPAQUE_USER_SESSION_PUBLIC_LEN) { exception(env, "invalid request size"); } jbyte *pub_jb=NULL; pub_jb = (*env)->GetByteArrayElements(env, pub_, NULL); pub = (char*) pub_jb; if((*env)->GetArrayLength(env, rec_)!=OPAQUE_USER_RECORD_LEN) { exception(env, "invalid record size"); } jbyte *rec_jb=NULL; rec_jb = (*env)->GetByteArrayElements(env, rec_, NULL); rec = (char*) rec_jb; const char *context; size_t context_len; context = (*env)->GetStringUTFChars(env, context_, 0); context_len = (*env)->GetStringLength(env, context_); Opaque_Ids ids = {0}; IdGC gc; getids(env, ids_, &ids, &gc); uint8_t resp[OPAQUE_SERVER_SESSION_LEN]; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t sec[crypto_auth_hmacsha512_BYTES]={0}; if(0!=opaque_CreateCredentialResponse(pub, rec, &ids, context, context_len, resp, sk, sec)) { exception(env,"opaque createCredResp() failed..."); } (*env)->ReleaseByteArrayElements(env, pub_, pub_jb, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, rec_, rec_jb, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, gc.idU, gc.idU_jb, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, gc.idS, gc.idS_jb, JNI_ABORT); (*env)->ReleaseStringUTFChars(env, context_, context); RetVal ret[] = {{.key = "sec", .val = sec, .len = sizeof(sec) }, {.key = "sk", .val = sk, .len = sizeof(sk)}, {.key = "pub", .val = resp, .len = sizeof(resp)}, { .key = NULL, .val = NULL}}; return retlist(env, "OpaqueCredResp", ret); } static jobject c_recoverCredentials(JNIEnv *env, jobject obj, jbyteArray resp_, jbyteArray sec_, jstring context_, jobject ids_) { Opaque_Ids ids = {0}; IdGC gc={0}; getids(env, ids_, &ids, &gc); const size_t resp_len = (*env)->GetArrayLength(env, resp_); if(resp_len!=OPAQUE_SERVER_SESSION_LEN) { exception(env, "invalid response size"); } uint8_t *resp; jbyte *resp_jb; resp_jb = (*env)->GetByteArrayElements(env, resp_, NULL); resp = (char*) resp_jb; const size_t sec_len = (*env)->GetArrayLength(env, sec_); if(sec_len<=OPAQUE_USER_SESSION_SECRET_LEN) { exception(env, "invalid secret context size"); } uint8_t *sec; jbyte *sec_jb; sec_jb = (*env)->GetByteArrayElements(env, sec_, NULL); sec = (char*) sec_jb; const char *context; size_t context_len; context = (*env)->GetStringUTFChars(env, context_, 0); context_len = (*env)->GetStringLength(env, context_); // exception(env,"opaque createCredResp() failed..."); uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t authU[crypto_auth_hmacsha512_BYTES]; uint8_t export_key[crypto_hash_sha512_BYTES]; if(0!=opaque_RecoverCredentials(resp, sec, context, context_len, &ids, sk, authU, export_key)) { exception(env,"opaque recoverCredentials() failed..."); } if(NULL!=gc.idU_jb) { (*env)->ReleaseByteArrayElements(env, gc.idU, gc.idU_jb, JNI_ABORT); } if(NULL!=gc.idS_jb) { (*env)->ReleaseByteArrayElements(env, gc.idS, gc.idS_jb, JNI_ABORT); } (*env)->ReleaseByteArrayElements(env, resp_, resp_jb, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, sec_, sec_jb, JNI_ABORT); (*env)->ReleaseStringUTFChars(env, context_, context); RetVal retvals[] = {{.key = "authU", .val = authU, .len = sizeof(authU) }, {.key = "sk", .val = sk, .len = sizeof(sk)}, {.key = "export_key", .val = export_key, .len = sizeof(export_key)}, { .key = NULL, .val = NULL}}; jobject ret = retlist(env, "OpaqueCreds", retvals); jclass clazz = (*env)->FindClass(env, "OpaqueCreds"); return ret; } static jboolean c_userAuth(JNIEnv *env, jobject obj, jbyteArray sec_, jbyteArray authU_) { //int opaque_UserAuth(const uint8_t sec[OPAQUE_SERVER_AUTH_CTX_LEN], const uint8_t authU[crypto_auth_hmacsha512_BYTES]); const uint8_t *sec, *authU; if((*env)->GetArrayLength(env, sec_)!=crypto_auth_hmacsha512_BYTES) { exception(env, "invalid secret context size"); return JNI_FALSE; } if((*env)->GetArrayLength(env, authU_)!=crypto_auth_hmacsha512_BYTES) { exception(env, "invalid auth token size"); return JNI_FALSE; } jbyte *sec_jb=NULL; sec_jb = (*env)->GetByteArrayElements(env, sec_, NULL); sec = (char*) sec_jb; jbyte *authU_jb=NULL; authU_jb = (*env)->GetByteArrayElements(env, authU_, NULL); authU = (char*) authU_jb; int ret = opaque_UserAuth(sec, authU); (*env)->ReleaseByteArrayElements(env, sec_, sec_jb, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, authU_, authU_jb, JNI_ABORT); if(0!=ret) { return JNI_FALSE; } return JNI_TRUE; } static jobject c_createRegReq(JNIEnv *env, jobject obj, jstring pwd_) { // int opaque_CreateRegistrationRequest(const uint8_t *pwdU, const uint16_t pwdU_len, // uint8_t sec[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len], uint8_t M[crypto_core_ristretto255_BYTES]); const char *pwdU; size_t pwdU_len; pwdU = (*env)->GetStringUTFChars(env, pwd_, 0); pwdU_len = (*env)->GetStringLength(env, pwd_); uint8_t sec[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]; uint8_t M[crypto_core_ristretto255_BYTES]; int res = opaque_CreateRegistrationRequest(pwdU, pwdU_len, sec, M); (*env)->ReleaseStringUTFChars(env, pwd_, pwdU); if(0!=res) { exception(env,"opaque register() failed..."); return NULL; } RetVal ret[] = {{.key = "sec", .val = sec, .len = sizeof(sec) }, {.key = "M", .val = M, .len = sizeof(M)}, { .key = NULL, .val = NULL}}; return retlist(env, "OpaqueRegReq", ret); } static jobject c_createRegResp(JNIEnv *env, jobject obj, jbyteArray M_, jbyteArray sks_) { const char *M=NULL; jbyte *M_jb=NULL; if((*env)->GetArrayLength(env, M_)!=crypto_core_ristretto255_BYTES) { exception(env, "M has invalid size"); } M_jb = (*env)->GetByteArrayElements(env, M_, NULL); M = (char*) M_jb; const char *sks=NULL; jbyte *sks_jb=NULL; if(sks_!=NULL) { if((*env)->GetArrayLength(env, sks_)!=crypto_scalarmult_SCALARBYTES) { exception(env, "skS has invalid size"); } sks_jb = (*env)->GetByteArrayElements(env, sks_, NULL); sks = (char*) sks_jb; } uint8_t sec[OPAQUE_REGISTER_SECRET_LEN]; uint8_t pub[OPAQUE_REGISTER_PUBLIC_LEN]; int res = opaque_CreateRegistrationResponse(M, sks, sec, pub); (*env)->ReleaseByteArrayElements(env, M_, M_jb, JNI_ABORT); if(sks_!=NULL) (*env)->ReleaseByteArrayElements(env, sks_, sks_jb, JNI_ABORT); if(0!=res) { exception(env,"opaque create registration response () failed..."); return NULL; } RetVal ret[] = {{.key = "sec", .val = sec, .len = sizeof(sec) }, {.key = "pub", .val = pub, .len = sizeof(pub)}, { .key = NULL, .val = NULL}}; return retlist(env, "OpaqueRegResp", ret); } static jobject c_createRegResp1(JNIEnv *env, jobject obj, jbyteArray M_) { return c_createRegResp(env, obj, M_, NULL); } static jobject c_finalizeReg(JNIEnv *env, jobject obj, jbyteArray sec_, jbyteArray pub_, jobject ids_) { const char *sec=NULL, *pub=NULL; jbyte *sec_jb=NULL, *pub_jb = NULL; if((*env)->GetArrayLength(env, sec_)<=OPAQUE_REGISTER_USER_SEC_LEN) { exception(env, "sec has invalid size"); } sec_jb = (*env)->GetByteArrayElements(env, sec_, NULL); sec = (char*) sec_jb; if((*env)->GetArrayLength(env, pub_)!=OPAQUE_REGISTER_PUBLIC_LEN) { exception(env, "pub has invalid size"); } pub_jb = (*env)->GetByteArrayElements(env, pub_, NULL); pub = (char*) pub_jb; Opaque_Ids ids = {0}; IdGC gc; getids(env, ids_, &ids, &gc); uint8_t export_key[crypto_hash_sha512_BYTES]; uint8_t rec[OPAQUE_USER_RECORD_LEN]; if(0!=opaque_FinalizeRequest(sec, pub, &ids, rec, export_key)) { exception(env,"opaque register() failed..."); } (*env)->ReleaseByteArrayElements(env, sec_, sec_jb, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, pub_, pub_jb, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, gc.idU, gc.idU_jb, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, gc.idS, gc.idS_jb, JNI_ABORT); RetVal ret[] = {{.key = "rec", .val = rec, .len = sizeof(rec) }, {.key = "export_key", .val = export_key, .len = sizeof(export_key)}, { .key = NULL, .val = NULL}}; return retlist(env, "OpaquePreRecExpKey", ret); } static jbyteArray c_storeRec(JNIEnv *env, jobject obj, jbyteArray sec_, jbyteArray recU_) { const char *sec=NULL; char *recU=NULL; jbyte *sec_jb=NULL, *recU_jb = NULL; if((*env)->GetArrayLength(env, sec_)<=OPAQUE_REGISTER_USER_SEC_LEN) { exception(env, "sec has invalid size"); return NULL; } sec_jb = (*env)->GetByteArrayElements(env, sec_, NULL); sec = (char*) sec_jb; size_t recU_len= (*env)->GetArrayLength(env, recU_); if(recU_len<=OPAQUE_REGISTRATION_RECORD_LEN) { exception(env, "recU has invalid size"); return NULL; } recU_jb = (*env)->GetByteArrayElements(env, recU_, NULL); recU = (char*) recU_jb; uint8_t rec[OPAQUE_USER_RECORD_LEN]; opaque_StoreUserRecord(sec, recU, rec); (*env)->ReleaseByteArrayElements(env, sec_, sec_jb, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, recU_, recU_jb, 0); jbyteArray rec_ = (*env)->NewByteArray(env, sizeof rec); (*env)->SetByteArrayRegion(env, rec_, 0, sizeof rec, rec); return rec_; } static JNINativeMethod funcs[] = { { "c_register", "(Ljava/lang/String;[BLOpaqueIds;)LOpaqueRecExpKey;", (void *)&c_register }, { "c_register", "(Ljava/lang/String;[B)LOpaqueRecExpKey;", (void *)&c_register_noIds }, { "c_register", "(Ljava/lang/String;LOpaqueIds;)LOpaqueRecExpKey;", (void *)&c_register_noSks }, { "c_register", "(Ljava/lang/String;)LOpaqueRecExpKey;", (void *)&c_register1 }, { "c_createCredReq", "(Ljava/lang/String;)LOpaqueCredReq;", (void *)&c_createCredReq }, { "c_createCredResp", "([B[BLOpaqueIds;Ljava/lang/String;)LOpaqueCredResp;", (void *)&c_createCredResp }, { "c_recoverCreds", "([B[BLjava/lang/String;LOpaqueIds;)LOpaqueCreds;", (void *)&c_recoverCredentials }, { "c_userAuth", "([B[B)Z", (void *)&c_userAuth }, { "c_createRegReq", "(Ljava/lang/String;)LOpaqueRegReq;", (void *)&c_createRegReq }, { "c_createRegResp", "([B[B)LOpaqueRegResp;", (void *)&c_createRegResp}, { "c_createRegResp", "([B)LOpaqueRegResp;", (void *)&c_createRegResp1}, { "c_finalizeReg", "([B[BLOpaqueIds;)LOpaquePreRecExpKey;", (void *)&c_finalizeReg }, { "c_storeRec", "([B[B)[B", (void *)&c_storeRec }, }; JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv *env; jclass cls; jint res; (void)reserved; if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) return -1; cls = (*env)->FindClass(env, JNIT_CLASS); if (cls == NULL) return -1; res = (*env)->RegisterNatives(env, cls, funcs, sizeof(funcs)/sizeof(*funcs)); if (res != 0) return -1; return JNI_VERSION_1_8; } JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { JNIEnv *env; jclass cls; (void)reserved; if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) return; cls = (*env)->FindClass(env, JNIT_CLASS); if (cls == NULL) return; (*env)->UnregisterNatives(env, cls); } libopaque-0.99.3/java/makefile000066400000000000000000000007541452546740600162410ustar00rootroot00000000000000JAVAINCLUDES?=/usr/lib/jvm/java-11-openjdk/include/ all: opaque.jar libopaquejni.so opaque.jar: test.java opaque.java jni.c javac -d build test.java opaque.java jar -cvfe opaque.jar Main build libopaquejni.so: jni.c gcc jni.c -shared -fPIC -o libopaquejni.so -I$(JAVAINCLUDES) -I$(JAVAINCLUDES)/linux -lopaque test: opaque.jar libopaquejni.so java -enableassertions -Djava.library.path=. -classpath build:opaque.jar Main clean: rm -rf build rm -f opaque.jar rm -f libopaquejni.so libopaque-0.99.3/java/opaque.java000066400000000000000000000064541452546740600167010ustar00rootroot00000000000000class OpaqueIds { public OpaqueIds() {} public OpaqueIds(byte[] idU_, byte[] idS_) { idU = idU_; idS = idS_; } public byte[] idU; public byte[] idS; } class OpaqueRecExpKey { public byte[] rec; public byte[] export_key; } class OpaqueCredReq { public byte[] sec; public byte[] pub; } class OpaqueCredResp { public byte[] sec; public byte[] sk; public byte[] pub; } class OpaqueCreds { public byte[] sk; public byte[] authU; public byte[] export_key; } class OpaqueRegReq { public byte[] sec; public byte[] M; } class OpaqueRegResp { public byte[] sec; public byte[] pub; } class OpaquePreRecExpKey { public byte[] rec; public byte[] export_key; } class Opaque { static { System.loadLibrary("opaquejni"); } public OpaqueRecExpKey register(String pwd, byte[] skS, OpaqueIds ids) { return c_register(pwd, skS, ids); } public OpaqueRecExpKey register(String pwd, OpaqueIds ids) { return c_register(pwd, ids); } public OpaqueRecExpKey register(String pwd, byte[] skS) { return c_register(pwd, skS); } public OpaqueRecExpKey register(String pwd) { return c_register(pwd); } public OpaqueCredReq createCredReq(String pwd) { return c_createCredReq(pwd); } public OpaqueCredResp createCredResp(byte[] req, byte[] rec, OpaqueIds ids, String context) { return c_createCredResp(req, rec, ids, context); } public OpaqueCreds recoverCreds(byte[] resp, byte[] sec, String context, OpaqueIds ids) { return c_recoverCreds(resp, sec, context, ids); } public boolean userAuth(byte[] sec, byte[] authU) { return c_userAuth(sec, authU); } public OpaqueRegReq createRegReq(String pwd) { return c_createRegReq(pwd); } public OpaqueRegResp createRegResp(byte[] M, byte[] skS) { return c_createRegResp(M, skS); } public OpaqueRegResp createRegResp(byte[] M) { return c_createRegResp(M); } public OpaquePreRecExpKey finalizeReg(byte[] sec, byte[] pub, OpaqueIds ids) { return c_finalizeReg(sec, pub, ids); } public byte[] storeRec(byte[] sec, byte[] rec) { return c_storeRec(sec, rec); } private static native OpaqueRecExpKey c_register(String pwd, byte[] skS, OpaqueIds ids); private static native OpaqueRecExpKey c_register(String pwd, OpaqueIds ids); private static native OpaqueRecExpKey c_register(String pwd, byte[] skS); private static native OpaqueRecExpKey c_register(String pwd); private static native OpaqueCredReq c_createCredReq(String pwd); private static native OpaqueCredResp c_createCredResp(byte[] req, byte[] rec, OpaqueIds ids, String context); private static native OpaqueCreds c_recoverCreds(byte[] resp, byte[] sec, String context, OpaqueIds ids); private static native boolean c_userAuth(byte[] sec, byte[] authU); private static native OpaqueRegReq c_createRegReq(String pwd); private static native OpaqueRegResp c_createRegResp(byte[] M, byte[] skS); private static native OpaqueRegResp c_createRegResp(byte[] M); private static native OpaquePreRecExpKey c_finalizeReg(byte[] sec, byte[] pub, OpaqueIds ids); private static native byte[] c_storeRec(byte[] sec, byte[] rec); } libopaque-0.99.3/java/test.java000066400000000000000000000113261452546740600163600ustar00rootroot00000000000000import java.nio.charset.*; class Main { public static void main(String args[]) { test1(); test_noPks_noIds(); test_privreg(); test_priv1kreg(); System.out.println("everything ok"); } private static void test1() { OpaqueIds ids = new OpaqueIds("idU".getBytes(Charset.forName("UTF-8")), "idS".getBytes(Charset.forName("UTF-8"))); Opaque o = new Opaque(); OpaqueRecExpKey ret = o.register("password", ids); System.out.println("rec=" + ret.rec + ", ek=" + ret.export_key); OpaqueCredReq creq = o.createCredReq("password"); System.out.println("sec=" + creq.sec + ", pub=" + creq.pub); OpaqueCredResp cresp = o.createCredResp(creq.pub, ret.rec, ids, "context"); System.out.println("sec=" + cresp.sec + ", pub=" + cresp.pub); OpaqueCreds creds = o.recoverCreds(cresp.pub, creq.sec, "context", ids); assert o.userAuth(cresp.sec, creds.authU); } private static void test_noPks_noIds() { OpaqueIds ids = new OpaqueIds("idU".getBytes(Charset.forName("UTF-8")), "idS".getBytes(Charset.forName("UTF-8"))); Opaque o = new Opaque(); byte[] skS = fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); OpaqueRecExpKey ret =o.register("password", skS, ids); System.out.println("rec=" + ret.rec + ", ek=" + ret.export_key); OpaqueCredReq creq = o.createCredReq("password"); System.out.println("sec=" + creq.sec + ", pub=" + creq.pub); OpaqueCredResp cresp = o.createCredResp(creq.pub, ret.rec, ids, "context"); System.out.println("sec=" + cresp.sec + ", pub=" + cresp.pub); OpaqueCreds creds = o.recoverCreds(cresp.pub, creq.sec, "context", ids); assert o.userAuth(cresp.sec, creds.authU); } private static void test_privreg() { Opaque o = new Opaque(); OpaqueRegReq regReq = o.createRegReq("password"); OpaqueRegResp regResp = o.createRegResp(regReq.M); OpaqueIds ids = new OpaqueIds("idU".getBytes(Charset.forName("UTF-8")), "idS".getBytes(Charset.forName("UTF-8"))); OpaquePreRecExpKey prerec = o.finalizeReg(regReq.sec, regResp.pub, ids); byte[] rec = o.storeRec(regResp.sec, prerec.rec); System.out.println("rec: " + toHex(rec) + "\n"); OpaqueCredReq creq = o.createCredReq("password"); System.out.println("sec=" + creq.sec + ", pub=" + creq.pub); OpaqueCredResp cresp = o.createCredResp(creq.pub, rec, ids, "context"); System.out.println("sec=" + cresp.sec + ", pub=" + cresp.pub); OpaqueCreds creds = o.recoverCreds(cresp.pub, creq.sec, "context", ids); assert o.userAuth(cresp.sec, creds.authU); } private static void test_priv1kreg() { Opaque o = new Opaque(); OpaqueRegReq regReq = o.createRegReq("password"); byte[] skS = fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); OpaqueRegResp regResp = o.createRegResp(regReq.M, skS); OpaqueIds ids = new OpaqueIds("idU".getBytes(Charset.forName("UTF-8")), "idS".getBytes(Charset.forName("UTF-8"))); OpaquePreRecExpKey prerec = o.finalizeReg(regReq.sec, regResp.pub, ids); byte[] rec = o.storeRec(regResp.sec, prerec.rec); System.out.println("rec: " + toHex(rec) + "\n"); OpaqueCredReq creq = o.createCredReq("password"); System.out.println("sec=" + creq.sec + ", pub=" + creq.pub); OpaqueCredResp cresp = o.createCredResp(creq.pub, rec, ids, "context"); System.out.println("sec=" + cresp.sec + ", pub=" + cresp.pub); OpaqueCreds creds = o.recoverCreds(cresp.pub, creq.sec, "context", ids); assert o.userAuth(cresp.sec, creds.authU); } // stackoverflowd from https://stackoverflow.com/a/140861 public static byte[] fromHex(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data; } // strackoverflowed from: https://stackoverflow.com/a/9855338 private static final byte[] HEX_ARRAY = "0123456789abcdef".getBytes(StandardCharsets.US_ASCII); public static String toHex(byte[] bytes) { byte[] hexChars = new byte[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = HEX_ARRAY[v >>> 4]; hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; } return new String(hexChars, StandardCharsets.UTF_8); } } libopaque-0.99.3/libopaque.pc000066400000000000000000000002461452546740600161210ustar00rootroot00000000000000prefix=/usr includedir=${prefix}/include Name: libopaque Description: Implementation of the OPAQUE protocol Version: 1.0.0 Cflags: -I${includedir}/foo Libs: -lopaquelibopaque-0.99.3/lua/000077500000000000000000000000001452546740600143735ustar00rootroot00000000000000libopaque-0.99.3/lua/README.md000066400000000000000000000140521452546740600156540ustar00rootroot00000000000000# Lua bindings for libopaque These bindings provide access to libopaque which implements the [IRTF CFRG RFC draft](https://github.com/cfrg/draft-irtf-cfrg-opaque) or you can read the [original paper](https://eprint.iacr.org/2018/163). ## Dependencies These bindings depend on the following: - libopaque: https://github.com/stef/libopaque/ - libsodium ## Building You need to have libopaque installed, and working (thus also libsodium), then: ``` make ``` ## Examples see test.lua ## API There is one data structure that is used by libopaque: ### `Ids` The IDs of the client (idU) and the server (idS) are passed directly as seperate parameters to functions that need to handle IDs. ## 1-step registration 1-step registration is only specified in the original paper. It is not specified by the IRTF CFRG draft. 1-step registration has the benefit that the supplied password (`pwd`) can be checked on the server for password rules (e.g., occurrence in common password lists, please obey [NIST SP 800-63-3b](https://pages.nist.gov/800-63-3/sp800-63b.html#memsecret)). It has the drawback that the password is exposed to the server. ```lua rec, export_key = register(pwd, skS, idU, idS); ``` The function expects these paramters: - `pwd` is the user's password. - `skS` is an optional explicitly specified server long-term key - `idU` is the clients ID, - `idS` is the servers ID, This function returns: - `rec` should be stored by the server associated with the ID of the user. - `export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ## 4-step registration Registration as specified in the IRTF CFRG draft consists of the following 4 steps: ### Step 1: The user creates a registration request. ```lua sec, req = createRegistrationReq(pwd); ``` - `pwd` is the user's password. The user should hold on to `sec` securely until step 3 of the registration process. `req` needs to be passed to the server running step 2. ### Step 2: The server responds to the registration request. ```lua sec, resp = createRegistrationResp(req, skS); ``` - `req` comes from the user running the previous step. - `skS` is an optional explicitly specified server long-term private key The server should hold onto `sec` securely until step 4 of the registration process. `resp` should be passed to the user running step 3. ### Step 3: The user finalizes the registration using the response from the server. ```lua rec, export_key = finalizeReq(sec, resp, idU, idS); ``` - `sec` contains sensitive data and should be disposed securely after usage in this step. - `resp` comes from the server running the previous step. - `idU` is the clients ID, - `idS` is the servers ID, The function outputs: - `rec` should be passed to the server running step 4. - `export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ### Step 4: The server finalizes the user's record. ```lua rec = storeRec(sec, rec); ``` or alternatively with explicitly specified long-term server private key: ```lua rec = store1kRec(sec, skS, rec); ``` - `rec` comes from the client running the previous step. - `skS` is an explicitly specified server long-term key - `sec` contains sensitive data and should be disposed securely after usage in this step. The function returns: - `rec` should be stored by the server associated with the ID of the user. **Important Note**: Confusingly this function is called `StoreUserRecord`, yet it does not do any storage. How you want to store the record (`rec`) is up to the implementor using this API. ## Establishing an opaque session After a user has registered with a server, the user can initiate the AKE and thus request its credentials in the following 3(+1)-step protocol: ### Step 1: The user initiates a credential request. ```lua sec, req = createCredentialReq(pwd) ``` - `pwd` is the user's password. The user should hold onto `sec` securely until step 3 of the protocol. `pub` needs to be passed to the server running step 2. ### Step 2: The server responds to the credential request. ```lua resp, sk, sec = createCredentialResp(req, rec, cfg, idU, idS, infos); ``` - `req` comes from the user running the previous step. - `rec` is the user's record stored by the server at the end of the registration protocol. - `context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" - `idU` is the clients ID, - `idS` is the servers ID, This function returns: - `resp` needs to be passed to the user running step 3. - `sk` is a shared secret, the result of the AKE. - `sec` is the servers sensitive context. The server should hold onto this values ecurely until the optional step 4 of the protocol, if needed. otherwise this value should be discarded securely. ### Step 3: The user recovers its credentials from the server's response. ```lua sk, authU, export_key = recoverCredentials(resp, sec, context, idU, idS); ``` - `resp` comes from the server running the previous step. - `sec` contains the client sensitive data from the first step and should be disposed securely after this step. - `context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" - `idU` is the client ID. - `idS` is the server ID. This function returns: - `sk` is a shared secret, the result of the AKE. - `authU` is an authentication tag that can be passed in step 4 for explicit user authentication. - `export_key` can be used to decrypt additional data stored by the server. ### Step 4 (Optional): The server authenticates the user. This step is only needed if there is no encrypted channel setup towards the server using the shared secret. ```lua userAuth(sec, authU); ``` - `sec` contains the servers sensitive context from the second step and should be disposed securely after usage in this step. - `authU` comes from the user running the previous step. The function returns a boolean `false` in case the authentication failed, otherwise `true`. libopaque-0.99.3/lua/makefile000066400000000000000000000002541452546740600160740ustar00rootroot00000000000000all: opaque.so opaque.so: opaque.c gcc opaque.c -g -shared -o opaque.so -fPIC $(shell pkgconf --libs lua) -lopaque test: opaque.so ./test.lua clean: rm -f opaque.so libopaque-0.99.3/lua/opaque.c000066400000000000000000000262431452546740600160400ustar00rootroot00000000000000#include #include #include #include #include static int reg(lua_State *L) { const uint8_t *pwdU, *skS; size_t pwdU_len, skS_len; pwdU=(const uint8_t *)luaL_checklstring(L,1,&pwdU_len); skS=(const uint8_t *) luaL_optlstring(L,2,NULL,&skS_len); if(skS != NULL && skS_len!=crypto_scalarmult_SCALARBYTES) { lua_pushstring(L, "invalid server key length, must be 32"); return lua_error(L); } Opaque_Ids ids; size_t id_len; ids.idU=(uint8_t *) luaL_optlstring(L,3,NULL,&id_len); if(id_len>(2<<16)-1) { lua_pushstring(L, "idU too long"); return lua_error(L); } ids.idU_len=id_len; ids.idS=(uint8_t *) luaL_optlstring(L,4,NULL,&id_len); if(id_len>(2<<16)-1) { lua_pushstring(L, "idU too long"); return lua_error(L); } ids.idS_len=id_len; uint8_t export_key[crypto_hash_sha512_BYTES]; uint8_t rec[OPAQUE_USER_RECORD_LEN]; if(0!=opaque_Register(pwdU, pwdU_len, skS, &ids, rec, export_key)) { lua_pushstring(L, "opaque register failed."); return lua_error(L); } luaL_Buffer r, ek; char *ptr = luaL_buffinitsize(L, &r, OPAQUE_USER_RECORD_LEN); memcpy(ptr,rec,OPAQUE_USER_RECORD_LEN); luaL_pushresultsize(&r, OPAQUE_USER_RECORD_LEN); ptr = luaL_buffinitsize(L, &ek, crypto_hash_sha512_BYTES); memcpy(ptr,export_key, crypto_hash_sha512_BYTES); luaL_pushresultsize(&ek, crypto_hash_sha512_BYTES); return 2; } static int create_cred_req(lua_State *L) { const uint8_t *pwdU; size_t pwdU_len; pwdU=(const uint8_t *)luaL_checklstring(L,1,&pwdU_len); uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], pub[OPAQUE_USER_SESSION_PUBLIC_LEN]; // todo sodium_mlock(sec) if(0!=opaque_CreateCredentialRequest(pwdU, pwdU_len, sec, pub)) { lua_pushstring(L, "failed to create an opaque credential request."); return lua_error(L); } luaL_Buffer s, p; char *ptr = luaL_buffinitsize(L, &s, OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len); memcpy(ptr,sec,OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len); // todo sodium_mlock(ptr) luaL_pushresultsize(&s, OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len); ptr = luaL_buffinitsize(L, &p, OPAQUE_USER_SESSION_PUBLIC_LEN); memcpy(ptr,pub, OPAQUE_USER_SESSION_PUBLIC_LEN); luaL_pushresultsize(&p, OPAQUE_USER_SESSION_PUBLIC_LEN); return 2; } static int create_cred_resp(lua_State *L) { const uint8_t *pub, // OPAQUE_USER_SESSION_PUBLIC_LEN *rec, // OPAQUE_USER_RECORD_LEN+envU_len *context; size_t pub_len, rec_len, context_len; pub=(const uint8_t *)luaL_checklstring(L,1,&pub_len); if(pub_len!=OPAQUE_USER_SESSION_PUBLIC_LEN) { lua_pushstring(L, "invalid request size"); return lua_error(L); } rec=(const uint8_t *) luaL_checklstring(L,2,&rec_len); if(rec_len!=OPAQUE_USER_RECORD_LEN) { lua_pushstring(L, "invalid record size"); return lua_error(L); } context=(const uint8_t *) luaL_checklstring(L,3,&context_len); Opaque_Ids ids; size_t id_len; ids.idU=(uint8_t *) luaL_optlstring(L,4,NULL,&id_len); if(id_len>(2<<16)-1) { lua_pushstring(L, "idU too long"); return lua_error(L); } ids.idU_len=id_len; ids.idS=(uint8_t *) luaL_optlstring(L,5,NULL,&id_len); if(id_len>(2<<16)-1) { lua_pushstring(L, "idU too long"); return lua_error(L); } ids.idS_len=id_len; uint8_t resp[OPAQUE_SERVER_SESSION_LEN]; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t sec[crypto_auth_hmacsha512_BYTES]={0}; if(0!=opaque_CreateCredentialResponse(pub, rec, &ids, context, context_len, resp, sk, sec)) { lua_pushstring(L, "opaque create credential response failed."); return lua_error(L); } luaL_Buffer resp_, sk_, sec_; char *ptr = luaL_buffinitsize(L, &resp_, OPAQUE_SERVER_SESSION_LEN); memcpy(ptr,resp,OPAQUE_SERVER_SESSION_LEN); luaL_pushresultsize(&resp_, OPAQUE_SERVER_SESSION_LEN); ptr = luaL_buffinitsize(L, &sk_, OPAQUE_SHARED_SECRETBYTES); memcpy(ptr,sk, OPAQUE_SHARED_SECRETBYTES); luaL_pushresultsize(&sk_, OPAQUE_SHARED_SECRETBYTES); ptr = luaL_buffinitsize(L, &sec_, crypto_auth_hmacsha512_BYTES); memcpy(ptr,sec, crypto_auth_hmacsha512_BYTES); luaL_pushresultsize(&sec_, crypto_auth_hmacsha512_BYTES); return 3; } static int recover_creds(lua_State *L) { const uint8_t *resp, *sec, *context, *pub; size_t resp_len, sec_len, context_len, pub_len; resp=(const uint8_t *)luaL_checklstring(L,1,&resp_len); // length validation below after we have envU_len if(resp_len!=OPAQUE_SERVER_SESSION_LEN) { lua_pushstring(L, "invalid response size"); return lua_error(L); } sec=(const uint8_t *) luaL_checklstring(L,2,&sec_len); if(sec_len<=OPAQUE_USER_SESSION_SECRET_LEN) { lua_pushstring(L, "sec parameter too short"); return lua_error(L); } context=(const uint8_t *) luaL_checklstring(L,3,&context_len); Opaque_Ids ids; size_t id_len; ids.idU=(uint8_t *) luaL_optlstring(L,4,NULL,&id_len); if(id_len>(2<<16)-1) { lua_pushstring(L, "idU too long"); return lua_error(L); } ids.idU_len=id_len; ids.idS=(uint8_t *) luaL_optlstring(L,5,NULL,&id_len); if(id_len>(2<<16)-1) { lua_pushstring(L, "idU too long"); return lua_error(L); } ids.idS_len=id_len; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t authU[crypto_auth_hmacsha512_BYTES]; uint8_t export_key[crypto_hash_sha512_BYTES]; if(0!=opaque_RecoverCredentials(resp, sec, context, context_len, &ids, sk, authU, export_key)) { lua_pushstring(L, "opaque recover credentials failed."); return lua_error(L); } luaL_Buffer sk_, authU_, ek_; char *ptr = luaL_buffinitsize(L, &sk_, OPAQUE_SHARED_SECRETBYTES); memcpy(ptr,sk,OPAQUE_SHARED_SECRETBYTES); luaL_pushresultsize(&sk_, OPAQUE_SHARED_SECRETBYTES); ptr = luaL_buffinitsize(L, &authU_, crypto_auth_hmacsha512_BYTES); memcpy(ptr,authU, crypto_auth_hmacsha512_BYTES); luaL_pushresultsize(&authU_, crypto_auth_hmacsha512_BYTES); ptr = luaL_buffinitsize(L, &ek_, crypto_hash_sha512_BYTES); memcpy(ptr,export_key, crypto_hash_sha512_BYTES); luaL_pushresultsize(&ek_, crypto_hash_sha512_BYTES); return 3; } static int user_auth(lua_State *L) { const uint8_t *sec, *authU; size_t sec_len, authU_len; sec=(const uint8_t *)luaL_checklstring(L,1,&sec_len); if(sec_len!=crypto_auth_hmacsha512_BYTES) { lua_pushstring(L, "sec parameter too short"); return lua_error(L); } authU=(const uint8_t *) luaL_checklstring(L,2,&authU_len); if(authU_len!=crypto_auth_hmacsha512_BYTES) { lua_pushstring(L, "authU parameter too short"); return lua_error(L); } lua_pushboolean(L, !opaque_UserAuth(sec, authU)); return 1; } static int create_reg_req(lua_State *L) { const uint8_t *pwdU; size_t pwdU_len; pwdU=(const uint8_t *)luaL_checklstring(L,1,&pwdU_len); uint8_t sec[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]; uint8_t M[crypto_core_ristretto255_BYTES]; if(0!=opaque_CreateRegistrationRequest(pwdU, pwdU_len, sec, M)) { lua_pushstring(L, "opaque create registation request failed."); return lua_error(L); } luaL_Buffer s, m; char *ptr = luaL_buffinitsize(L, &s, OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len); memcpy(ptr,sec,OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len); luaL_pushresultsize(&s, OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len); ptr = luaL_buffinitsize(L, &m, crypto_core_ristretto255_BYTES); memcpy(ptr,M, crypto_core_ristretto255_BYTES); luaL_pushresultsize(&m, crypto_core_ristretto255_BYTES); return 2; } static int create_reg_resp(lua_State *L) { const uint8_t *M, *skS=NULL; size_t M_len, skS_len; M=(const uint8_t *)luaL_checklstring(L,1,&M_len); if(M_len!=crypto_core_ristretto255_BYTES) { lua_pushstring(L, "invalid message length"); return lua_error(L); } skS=(const uint8_t *)luaL_optlstring(L,2,NULL,&skS_len); if(skS != NULL && skS_len!=crypto_scalarmult_SCALARBYTES) { lua_pushstring(L, "invalid skS size"); return lua_error(L); } uint8_t sec[OPAQUE_REGISTER_SECRET_LEN]; uint8_t pub[OPAQUE_REGISTER_PUBLIC_LEN]; if(0!=opaque_CreateRegistrationResponse(M, skS, sec, pub)) { lua_pushstring(L, "opaque create registration response failed."); return lua_error(L); } luaL_Buffer s, p; char *ptr = luaL_buffinitsize(L, &s, OPAQUE_REGISTER_SECRET_LEN); memcpy(ptr,sec,OPAQUE_REGISTER_SECRET_LEN); luaL_pushresultsize(&s, OPAQUE_REGISTER_SECRET_LEN); ptr = luaL_buffinitsize(L, &p, OPAQUE_REGISTER_PUBLIC_LEN); memcpy(ptr,pub, OPAQUE_REGISTER_PUBLIC_LEN); luaL_pushresultsize(&p, OPAQUE_REGISTER_PUBLIC_LEN); return 2; } static int finalize_req(lua_State *L) { const uint8_t *sec, *pub; size_t sec_len, pub_len; sec=(const uint8_t *)luaL_checklstring(L,1,&sec_len); if(sec_len<=OPAQUE_REGISTER_USER_SEC_LEN) { lua_pushstring(L, "invalid secret client context size"); return lua_error(L); } pub=(const uint8_t *) luaL_optlstring(L,2,NULL,&pub_len); if(pub_len!=OPAQUE_REGISTER_PUBLIC_LEN) { lua_pushstring(L, "invalid response size"); return lua_error(L); } Opaque_Ids ids; size_t id_len; ids.idU=(uint8_t *) luaL_optlstring(L,3,NULL,&id_len); if(id_len>(2<<16)-1) { lua_pushstring(L, "idU too long"); return lua_error(L); } ids.idU_len=id_len; ids.idS=(uint8_t *) luaL_optlstring(L,4,NULL,&id_len); if(id_len>(2<<16)-1) { lua_pushstring(L, "idU too long"); return lua_error(L); } ids.idS_len=id_len; uint8_t export_key[crypto_hash_sha512_BYTES]; uint8_t rec[OPAQUE_REGISTRATION_RECORD_LEN]; if(0!=opaque_FinalizeRequest(sec, pub, &ids, rec, export_key)) { lua_pushstring(L, "opaque finalize request failed."); return lua_error(L); } luaL_Buffer r, ek; char *ptr = luaL_buffinitsize(L, &r, OPAQUE_REGISTRATION_RECORD_LEN); memcpy(ptr,rec,OPAQUE_REGISTRATION_RECORD_LEN); luaL_pushresultsize(&r, OPAQUE_REGISTRATION_RECORD_LEN); ptr = luaL_buffinitsize(L, &ek, crypto_hash_sha512_BYTES); memcpy(ptr,export_key, crypto_hash_sha512_BYTES); luaL_pushresultsize(&ek, crypto_hash_sha512_BYTES); return 2; } static int store_rec(lua_State *L) { //void opaque_StoreUserRecord(const uint8_t sec[OPAQUE_REGISTER_SECRET_LEN], uint8_t rec[OPAQUE_USER_RECORD_LEN/*+envU_len*/]); const uint8_t *sec; uint8_t *recU; size_t sec_len, recU_len; sec=(const uint8_t *)luaL_checklstring(L,1,&sec_len); if(sec_len!=OPAQUE_REGISTER_SECRET_LEN) { lua_pushstring(L, "invalid message length"); return lua_error(L); } recU=(uint8_t *)luaL_checklstring(L,2,&recU_len); if(recU_len!=OPAQUE_REGISTRATION_RECORD_LEN) { lua_pushstring(L, "invalid record size"); return lua_error(L); } uint8_t rec[OPAQUE_USER_RECORD_LEN]; opaque_StoreUserRecord(sec, recU, rec); luaL_Buffer r; char *ptr = luaL_buffinitsize(L, &r, sizeof rec); memcpy(ptr,rec,sizeof rec); luaL_pushresultsize(&r, sizeof rec); return 1; } static const struct luaL_Reg opaque_registry[] = { { "register", reg}, { "createCredentialReq", create_cred_req}, { "createCredentialResp", create_cred_resp}, { "recoverCredentials", recover_creds}, { "userAuth", user_auth}, { "createRegistrationReq", create_reg_req}, { "createRegistrationResp", create_reg_resp}, { "finalizeReq", finalize_req}, { "storeRec", store_rec}, { NULL, NULL } }; int luaopen_opaque(lua_State *L) { luaL_newlib(L,opaque_registry); return 1; } libopaque-0.99.3/lua/test.lua000077500000000000000000000031631452546740600160630ustar00rootroot00000000000000#!/usr/bin/env lua5.3 o = require 'opaque' rec, ek = o.register("asdf", nil, "idU", "idS") sec, pub = o.createCredentialReq("asdf") resp, ssk, ssec = o.createCredentialResp(pub, rec, "context", "idU", "idS") csk, authU, export_key = o.recoverCredentials(resp, sec, "context", "idU", "idS") assert(csk==ssk) assert(o.userAuth(ssec,authU)) -- failing sec, pub = o.createCredentialReq("qwer") resp, ssk, ssec = o.createCredentialResp(pub, rec, "context", "idU", "idS") ok, csk, authU, export_key = pcall(o.recoverCredentials, resp, sec, "context", "idU", "idS") assert(not ok) print(ok, csk, authU, export_key) -- registration sec, msg = o.createRegistrationReq("asdf") ssec, resp = o.createRegistrationResp(msg, nil) rec, ek = o.finalizeReq(sec,resp,"idU","idS") rec = o.storeRec(ssec, rec) sec, pub = o.createCredentialReq("asdf") resp, ssk, ssec = o.createCredentialResp(pub, rec, "context", "idU", "idS") ok, csk, authU, export_key = pcall(o.recoverCredentials, resp, sec, "context", "idU", "idS") assert(ok) assert(csk==ssk) assert(o.userAuth(ssec,authU)) -- 1k variant sec, msg = o.createRegistrationReq("asdf") ssec, resp = o.createRegistrationResp(msg, '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f') rec, ek = o.finalizeReq(sec,resp,"idU","idS") rec = o.storeRec(ssec, rec) sec, pub = o.createCredentialReq("asdf") resp, ssk, ssec = o.createCredentialResp(pub, rec, "context", "idU", "idS") ok, csk, authU, export_key = pcall(o.recoverCredentials, resp, sec, "context", "idU", "idS") assert(ok) assert(csk==ssk) assert(o.userAuth(ssec,authU)) print("all ok") libopaque-0.99.3/php7/000077500000000000000000000000001452546740600144705ustar00rootroot00000000000000libopaque-0.99.3/php7/README000066400000000000000000000013601452546740600153500ustar00rootroot00000000000000taken from: https://www.zend.com/building-and-installing-php-extension Building and Installing the opaque extension This extension skeleton can be compiled without any changes. The first “phpize” command is a part of the PHP build we created in the first step. (It should still be in the PATH.) $ phpize $ ./configure $ make $ make install These commands should build our shared extension “opaque.so” and copy it into appropriate directory of our PHP installation. To load it, we need to add a line into our custom php.ini $ vi ~/php-bin/DEBUG/etc/php.ini Add the following line: extension=opaque.so Check that extension is loaded and works. “php -m” command prints the list of loaded extensions: $ php -m | grep opaque opaque libopaque-0.99.3/php7/README.md000066400000000000000000000153531452546740600157560ustar00rootroot00000000000000# libopaque PHP7 bindings These bindings provide access to libopaque which implements the [IRTF CFRG RFC draft](https://github.com/cfrg/draft-irtf-cfrg-opaque) or you can read the [original paper](https://eprint.iacr.org/2018/163). ## Dependencies These bindings depend on the following: - php7-dev - libopaque: https://github.com/stef/libopaque/ - libsodium ## Building and Installing the opaque php7 extension ``` $ phpize $ ./configure $ make $ make install ``` These commands should build our shared extension “opaque.so” and copy it into appropriate directory of our PHP installation. To load it, we need to add a line into our custom php.ini ``` $ vi /etc/php.ini ``` Add the following line: ``` extension=opaque.so ``` Check that extension is loaded and works. `php -m` command prints the list of loaded extensions: ``` $ php -m | grep opaque opaque ``` ## Demo and Examples For a simple demo please check the `demo` subdirectory, for simple examples please check the test-cases: `002.phpt`, `003.phpt, `004.phpt`. ## API There is one data structure which is used by libopaque: ### `Ids` The IDs of the client (idU) and the server (idS) are passed directly as seperate parameters to functions that need to handle IDs. ## One-key Server convenience function For the case that a setup requires externally generated long-term server keys the php bindings provide a convenience function `opaque_create_server_keys()` which can be used to generate a server key-pair. The functions returns `$pkS` and `$skS` in this order. ## 1-step registration 1-step registration is only specified in the original paper. It is not specified by the IRTF CFRG draft. 1-step registration has the benefit that the supplied password (`pwd`) can be checked on the server for password rules (e.g., occurrence in common password lists). It has the drawback that the password is exposed to the server. ```php $rec, $export_key = opaque_register($pwd, $idU, $idS, $skS); ``` The function expects these paramters: - `$pwd` is the user's password. - `$idU` is the optional client ID, - `$idS` is the optional server ID, - `$skS` is an optional explicitly specified server long-term key This function returns: - `$rec` should be stored by the server associated with the ID of the user. - `$export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ## 4-step registration Registration as specified in the IRTF CFRG draft consists of the following 4 steps: ### Step 1: The user creates a registration request. ```php $sec, $req = opaque_create_registration_request(pwd); ``` - `$pwd` is the user's password. The user should hold on to `$sec` securely until step 3 of the registration process. `$req` needs to be passed to the server running step 2. ### Step 2: The server responds to the registration request. ```php $sec, $resp = opaque_create_registration_response($req, $skS); ``` - `$req` comes from the user running the previous step. - `$skS` is an optional explicitly specified server long-term private-key The server should hold onto `$sec` securely until step 4 of the registration process. `$resp` should be passed to the user running step 3. ### Step 3: The user finalizes the registration using the response from the server. ```php $rec, $export_key = opaque_finalize_request($sec, $resp, $idU, $idS); ``` - `$sec` contains sensitive data and should be disposed securely after usage in this step. - `$resp` comes from the server running the previous step. - `$idU` is the clients ID, - `$idS` is the servers ID, - `$rec` should be passed to the server running step 4. - `$export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ### Step 4: The server finalizes the user's record. ```php $rec = opaque_store_user_record($sec, $rec); ``` - `$rec` comes from the client running the previous step. - `$sec` contains sensitive data and should be disposed securely after usage in this step. - `$rec` should be stored by the server associated with the ID of the user. **Important Note**: Confusingly this function is called `StoreUserRecord`, yet it does not do any storage. How you want to store the record (`$rec`) is up to the implementor using this API. ## Establishing an opaque session After a user has registered with a server, the user can initiate the AKE and thus request its credentials in the following 3(+1)-step protocol: ### Step 1: The user initiates a credential request. ```php $req, $sec = opaque_create_credential_request($pwd) ``` - `$pwd` is the user's password. The user should hold onto `$sec` securely until step 3 of the protocol. `$pub` needs to be passed to the server running step 2. ### Step 2: The server responds to the credential request. ```php $resp, $sk, $sec = opaque_create_credential_response($req, $rec, $context, $idU, $idS); ``` - `$req` comes from the user running the previous step. - `$rec` is the user's record stored by the server at the end of the registration protocol. - `$context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" - `$idU` is the client ID, - `$idS` is the server ID, This function returns: - `$resp` needs to be passed to the user running step 3. - `$sk` is a shared secret, the result of the AKE. - The server should hold onto `$sec` securely until the optional step 4 of the protocol, if needed. otherwise this value should be discarded securely. ### Step 3: The user recovers its credentials from the server's response. ```php $sk, $authU, $export_key = opaque_recover_credentials($resp, $sec, $context, $idU, $idS); ``` - `$resp` comes from the server running the previous step. - `$sec` contains sensitive data and should be disposed securely after usage in this step. - `$context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" - `$idU` is the clients ID. - `$idS` is the servers ID. This function returns: - `$sk` is a shared secret, the result of the AKE. - `$authU` is an authentication tag that can be passed in step 4 for explicit user authentication. - `$export_key` can be used to decrypt additional data stored by the server. ### Step 4 (Optional): The server authenticates the user. This step is only needed if there is no encrypted channel setup towards the server using the shared secret. ```php opaque_user_auth($sec, $authU); ``` - `$sec` is the `$sec` output of `opaque_create_credential_response()`, it contains sensitive data and should be disposed securely after usage in this step. - `$authU` comes from the user running the previous step. The function returns a boolean `false` in case the authentication failed, otherwise `true`. libopaque-0.99.3/php7/config.m4000066400000000000000000000063121452546740600162010ustar00rootroot00000000000000dnl config.m4 for extension opaque dnl Comments in this file start with the string 'dnl'. dnl Remove where necessary. dnl If your extension references something external, use 'with': PHP_ARG_WITH([opaque], [for opaque support], [AS_HELP_STRING([--with-opaque], [Include opaque support])]) dnl Otherwise use 'enable': dnl PHP_ARG_ENABLE([opaque], dnl [whether to enable opaque support], dnl [AS_HELP_STRING([--enable-opaque], dnl [Enable opaque support])], dnl [no]) if test "$PHP_OPAQUE" != "no"; then dnl Write more examples of tests here... dnl Remove this code block if the library does not support pkg-config. PKG_CHECK_MODULES([LIBOPAQUE], [libopaque]) PHP_EVAL_INCLINE($LIBOPAQUE_CFLAGS) PHP_EVAL_LIBLINE($LIBOPAQUE_LIBS, OPAQUE_SHARED_LIBADD) dnl If you need to check for a particular library version using PKG_CHECK_MODULES, dnl you can use comparison operators. For example: dnl PKG_CHECK_MODULES([LIBFOO], [foo >= 1.2.3]) dnl PKG_CHECK_MODULES([LIBFOO], [foo < 3.4]) dnl PKG_CHECK_MODULES([LIBFOO], [foo = 1.2.3]) dnl Remove this code block if the library supports pkg-config. dnl --with-opaque -> check with-path dnl SEARCH_PATH="/usr/local /usr" # you might want to change this dnl SEARCH_FOR="/include/opaque.h" # you most likely want to change this dnl if test -r $PHP_OPAQUE/$SEARCH_FOR; then # path given as parameter dnl OPAQUE_DIR=$PHP_OPAQUE dnl else # search default path list dnl AC_MSG_CHECKING([for opaque files in default path]) dnl for i in $SEARCH_PATH ; do dnl if test -r $i/$SEARCH_FOR; then dnl OPAQUE_DIR=$i dnl AC_MSG_RESULT(found in $i) dnl fi dnl done dnl fi dnl dnl if test -z "$OPAQUE_DIR"; then dnl AC_MSG_RESULT([not found]) dnl AC_MSG_ERROR([Please reinstall the opaque distribution]) dnl fi dnl Remove this code block if the library supports pkg-config. dnl --with-opaque -> add include path dnl PHP_ADD_INCLUDE($OPAQUE_DIR/include) dnl Remove this code block if the library supports pkg-config. dnl --with-opaque -> check for lib and symbol presence dnl LIBNAME=libopaque # you may want to change this dnl LIBSYMBOL=OPAQUE # you most likely want to change this dnl If you need to check for a particular library function (e.g. a conditional dnl or version-dependent feature) and you are using pkg-config: dnl PHP_CHECK_LIBRARY($LIBNAME, $LIBSYMBOL, dnl [ dnl AC_DEFINE(HAVE_OPAQUE_FEATURE, 1, [ ]) dnl ],[ dnl AC_MSG_ERROR([FEATURE not supported by your opaque library.]) dnl ], [ dnl $LIBFOO_LIBS dnl ]) dnl If you need to check for a particular library function (e.g. a conditional dnl or version-dependent feature) and you are not using pkg-config: dnl PHP_CHECK_LIBRARY($LIBNAME, $LIBSYMBOL, dnl [ dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $OPAQUE_DIR/$PHP_LIBDIR, OPAQUE_SHARED_LIBADD) dnl AC_DEFINE(HAVE_OPAQUE_FEATURE, 1, [ ]) dnl ],[ dnl AC_MSG_ERROR([FEATURE not supported by your opaque library.]) dnl ],[ dnl -L$OPAQUE_DIR/$PHP_LIBDIR -lm dnl ]) dnl PHP_SUBST(OPAQUE_SHARED_LIBADD) dnl In case of no dependencies AC_DEFINE(HAVE_OPAQUE, 1, [ Have opaque support ]) PHP_NEW_EXTENSION(opaque, opaque.c, $ext_shared) fi libopaque-0.99.3/php7/config.w32000066400000000000000000000003231452546740600162700ustar00rootroot00000000000000ARG_ENABLE('opaque', 'opaque support', 'no'); if (PHP_OPAQUE != 'no') { AC_DEFINE('HAVE_OPAQUE', 1, 'opaque support enabled'); EXTENSION('opaque', 'opaque.c', null, '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1'); } libopaque-0.99.3/php7/demo/000077500000000000000000000000001452546740600154145ustar00rootroot00000000000000libopaque-0.99.3/php7/demo/README.md000066400000000000000000000013641452546740600166770ustar00rootroot00000000000000# libopaque PHP Server and JavaScript Web Client Demo ## Building and Running ```sh $ docker run -it -p 8080:8080 --rm --volume $(pwd):/tmp --workdir /tmp ubuntu:focal bash $ cd src $ apt update $ apt install -y build-essential git libsodium-dev php php-apcu php-dev pkgconf vim $ git submodule update --init --recursive tests/munit $ make $ cd ../php7 $ phpize $ LIBOPAQUE_CFLAGS='-I ../src' LIBOPAQUE_LIBS='-lopaque' ./configure $ LD_LIBRARY_PATH=../src TEST_PHP_ARGS=-q make EXTRA_CFLAGS=-I../src EXTRA_LDFLAGS=-L../src test $ ln -s /tmp/php7/modules/opaque.so /usr/lib/php/20190902/opaque.so $ vi /etc/php/7.4/cli/php.ini # Add extension=opaque to the "Dynamic Extensions" section. $ demo/app.sh # Navigate to http://localhost:8080 in a browser. ``` libopaque-0.99.3/php7/demo/app.sh000077500000000000000000000004421452546740600165330ustar00rootroot00000000000000#!/bin/sh IFS=$(printf '\n\t') set -o errexit -o nounset if [ -n "${BASH_VERSION:-}" ]; then # shellcheck disable=SC2039 set -o pipefail fi set -o xtrace script_dir="$( cd "$(dirname "$0")" pwd -P )" cd "$script_dir" LD_LIBRARY_PATH="$(pwd)/../../src" php -S 0.0.0.0:8080 router.php libopaque-0.99.3/php7/demo/index-worker.js000077700000000000000000000000001452546740600266032../../js/demo/public/index-worker.jsustar00rootroot00000000000000libopaque-0.99.3/php7/demo/index.html000077700000000000000000000000001452546740600246452../../js/demo/public/index.htmlustar00rootroot00000000000000libopaque-0.99.3/php7/demo/index.js000077700000000000000000000000001452546740600237652../../js/demo/public/index.jsustar00rootroot00000000000000libopaque-0.99.3/php7/demo/router.php000066400000000000000000000162621452546740600174540ustar00rootroot00000000000000error = $e->getMessage(); echo json_encode($json_obj); } break; case "/request-credentials": try { header('Content-Type: application/json'); $pub = hex2bin($_POST["request"]); $idU = $_POST["id"]; $rec = hex2bin(opaque_fetch("users", $idU)); if (!isset($rec)) { // TODO Prevent user enumeration attacks. $json_obj = (object)[]; $json_obj->error = "Requesting credentials for the user failed."; echo json_encode($json_obj); } else { $r=opaque_create_credential_response($pub, $rec, $idU, $idS, $cfg); if (is_null($r)) { $json_obj = (object)[]; $json_obj->error = "Requesting credentials for the user failed."; echo json_encode($json_obj); break; } $resp=$r[0]; $sk=$r[1]; $secS=$r[2]; opaque_store("credential", $idU, bin2hex($secS)); // error_log(bin2hex($secS)); $json_obj = (object)[]; $json_obj->response = bin2hex($resp); // TODO Handle pkS not packaged. echo json_encode($json_obj); } } catch (Exception $e) { $json_obj = (object)[]; $json_obj->error = $e->getMessage(); echo json_encode($json_obj); } break; case "/authorize": try { header('Content-Type: application/json'); $idU = $_POST["id"]; $secS = hex2bin(opaque_fetch("credential", $idU)); // error_log(bin2hex($secS)); $authU = hex2bin($_POST["auth"]); echo opaque_user_auth($secS, $authU) ? "true" : "false"; opaque_store("credential", $idU, null); } catch (Exception $e) { $json_obj = (object)[]; $json_obj->error = $e->getMessage(); echo json_encode($json_obj); } break; case "/register-without-password": try { header('Content-Type: application/json'); $idU = $_POST["id"]; $M = hex2bin($_POST["request"]); $r = opaque_create_registration_response($M); $secS = $r[0]; $pub = $r[1]; $json_obj = (object)[]; $json_obj->response = bin2hex($pub); echo json_encode($json_obj); opaque_store("registration", $idU, bin2hex($secS)); // error_log(bin2hex($secS)); } catch (Exception $e) { $json_obj = (object)[]; $json_obj->error = $e->getMessage(); echo json_encode($json_obj); } break; case "/store-user-record": try { header('Content-Type: application/json'); $idU = $_POST["id"]; $rec = hex2bin($_POST["rec"]); $secS = hex2bin(opaque_fetch("registration", $idU)); // error_log(bin2hex($secS)); $rec = opaque_store_user_record($secS, $rec); $user = opaque_fetch("users", $idU); $hex = bin2hex($rec); if (!empty($hex)) { if ($user === null) { opaque_store("users", $idU, $hex); } else { // We allow registration to go through to prevent user-enumeration attacks. opaque_store("dummy", "", $hex); } } opaque_store("registration", $idU, null); echo "true"; } catch (Exception $e) { $json_obj = (object)[]; $json_obj->error = $e->getMessage(); echo json_encode($json_obj); } break; case "/register-with-global-server-key": try { header('Content-Type: application/json'); $idU = $_POST["id"]; $M = hex2bin($_POST["request"]); $r = opaque_create_registration_response($M, $pkS); $secS = $r[0]; $pub = $r[1]; $json_obj = (object)[]; $json_obj->response = bin2hex($pub); $json_obj->type = "global-server-key"; echo json_encode($json_obj); opaque_store("registration", $idU, bin2hex($secS)); // error_log(bin2hex($secS)); } catch (Exception $e) { $json_obj = (object)[]; $json_obj->error = $e->getMessage(); echo json_encode($json_obj); } break; case "/store-user-record-using-global-server-key": try { header('Content-Type: application/json'); $idU = $_POST["id"]; $rec = hex2bin($_POST["rec"]); $secS = hex2bin(opaque_fetch("registration", $idU)); // error_log(bin2hex($secS)); $rec = opaque_store_user_record($secS, $rec, $skS); $user = opaque_fetch("users", $idU); $hex = bin2hex($rec); if (!empty($hex)) { if ($user === null) { opaque_store("users", $idU, $hex); } else { // We allow registration to go through to prevent user-enumeration attacks. opaque_store("dummy", "", $hex); } } opaque_store("registration", $idU, null); echo "true"; } catch (Exception $e) { $json_obj = (object)[]; $json_obj->error = $e->getMessage(); echo json_encode($json_obj); } break; default: return false; } ?> libopaque-0.99.3/php7/opaque.c000066400000000000000000000305351452546740600161340ustar00rootroot00000000000000/* opaque extension for PHP */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "php.h" #include "ext/standard/info.h" #include "php_opaque.h" #include "opaque.h" /* For compatibility with older PHP versions */ #ifndef ZEND_PARSE_PARAMETERS_NONE #define ZEND_PARSE_PARAMETERS_NONE() \ ZEND_PARSE_PARAMETERS_START(0, 0) \ ZEND_PARSE_PARAMETERS_END() #endif /* {{{ */ PHP_FUNCTION(opaque_register) { char *pwdU; size_t pwdU_len; char *idU=NULL; size_t idU_len=0; char *idS=NULL; size_t idS_len=0; char *skS=NULL; size_t skS_len=0; ZEND_PARSE_PARAMETERS_START(1, 4) Z_PARAM_STRING(pwdU, pwdU_len) Z_PARAM_OPTIONAL Z_PARAM_STRING(idU, idU_len) Z_PARAM_STRING(idS, idS_len) Z_PARAM_STRING(skS, skS_len) ZEND_PARSE_PARAMETERS_END(); Opaque_Ids ids={.idU_len=idU_len,.idU=idU,.idS_len=idS_len,.idS=idS}; if(skS!=NULL && skS_len!=crypto_scalarmult_SCALARBYTES) { php_error_docref(NULL, E_WARNING, "invalid skS size, must be 32B."); return; } uint8_t export_key[crypto_hash_sha512_BYTES]; uint8_t rec[OPAQUE_USER_RECORD_LEN]; if(0!=opaque_Register(pwdU, pwdU_len, skS, &ids, rec, export_key)) return; zend_array *ret = zend_new_array(2); zval zarr; ZVAL_ARR(&zarr, ret); add_next_index_stringl(&zarr,rec, sizeof(rec)); add_next_index_stringl(&zarr,export_key, sizeof(export_key)); // sensitive RETVAL_ARR(ret); } PHP_FUNCTION(opaque_create_credential_request) { char *pwdU; size_t pwdU_len; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(pwdU, pwdU_len) Z_PARAM_OPTIONAL ZEND_PARSE_PARAMETERS_END(); uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], pub[OPAQUE_USER_SESSION_PUBLIC_LEN]; if(0!=opaque_CreateCredentialRequest(pwdU, pwdU_len, sec, pub)) return; zend_array *ret = zend_new_array(2); zval zarr; ZVAL_ARR(&zarr, ret); add_next_index_stringl(&zarr,sec, sizeof(sec)); // sensitive add_next_index_stringl(&zarr,pub, sizeof(pub)); RETVAL_ARR(ret); } PHP_FUNCTION(opaque_create_credential_response) { char *pub; size_t pub_len; char *rec; size_t rec_len; char *idU; size_t idU_len; char *idS; size_t idS_len; char *context; size_t context_len; ZEND_PARSE_PARAMETERS_START(3, 5) Z_PARAM_STRING(pub, pub_len) Z_PARAM_STRING(rec, rec_len) Z_PARAM_STRING(context, context_len) Z_PARAM_OPTIONAL Z_PARAM_STRING(idU, idU_len) Z_PARAM_STRING(idS, idS_len) ZEND_PARSE_PARAMETERS_END(); if(pub_len!=OPAQUE_USER_SESSION_PUBLIC_LEN) { php_error_docref(NULL, E_WARNING, "invalid pub param."); return; } if(rec_len!=OPAQUE_USER_RECORD_LEN) { php_error_docref(NULL, E_WARNING, "invalid rec param."); return; } Opaque_Ids ids={.idU_len=idU_len,.idU=idU,.idS_len=idS_len,.idS=idS}; uint8_t resp[OPAQUE_SERVER_SESSION_LEN]; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t sec[crypto_auth_hmacsha512_BYTES]={0}; if(0!=opaque_CreateCredentialResponse(pub, rec, &ids, context, context_len, resp, sk, sec)) return; zend_array *ret = zend_new_array(3); zval zarr; ZVAL_ARR(&zarr, ret); add_next_index_stringl(&zarr,resp, sizeof(resp)); add_next_index_stringl(&zarr,sk, sizeof(sk)); // sensitive add_next_index_stringl(&zarr,sec, sizeof(sec)); // sensitive RETVAL_ARR(ret); } PHP_FUNCTION(opaque_recover_credentials) { char *resp; size_t resp_len; char *sec; size_t sec_len; char *idU=NULL; size_t idU_len=0; char *idS=NULL; size_t idS_len=0; char *context=NULL; size_t context_len=0; ZEND_PARSE_PARAMETERS_START(3, 5) Z_PARAM_STRING(resp, resp_len) Z_PARAM_STRING(sec, sec_len) Z_PARAM_STRING(context, context_len) Z_PARAM_OPTIONAL Z_PARAM_STRING(idU, idU_len) Z_PARAM_STRING(idS, idS_len) ZEND_PARSE_PARAMETERS_END(); if(resp_len!=OPAQUE_SERVER_SESSION_LEN) { php_error_docref(NULL, E_WARNING, "invalid resp param."); return; } if(sec_len<=OPAQUE_USER_SESSION_SECRET_LEN) { php_error_docref(NULL, E_WARNING, "invalid sec param."); return; } Opaque_Ids ids={.idU_len=idU_len,.idU=idU,.idS_len=idS_len,.idS=idS}; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t authU[crypto_auth_hmacsha512_BYTES]; uint8_t export_key[crypto_hash_sha512_BYTES]; if(0!=opaque_RecoverCredentials(resp, sec, context, context_len, &ids, sk, authU, export_key)) return; zend_array *ret = zend_new_array(3); zval zarr; ZVAL_ARR(&zarr, ret); add_next_index_stringl(&zarr,sk, sizeof(sk)); // sensitive add_next_index_stringl(&zarr,authU, sizeof(authU)); // sensitive add_next_index_stringl(&zarr,export_key, sizeof(export_key)); // sensitive RETVAL_ARR(ret); } PHP_FUNCTION(opaque_user_auth) { char *authU0; size_t authU0_len; char *authU; size_t authU_len; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(authU0, authU0_len) Z_PARAM_STRING(authU, authU_len) Z_PARAM_OPTIONAL ZEND_PARSE_PARAMETERS_END(); if(authU0_len!=crypto_auth_hmacsha512_BYTES) { php_error_docref(NULL, E_WARNING, "invalid authU0 param."); return; } if(authU_len!=crypto_auth_hmacsha512_BYTES) { php_error_docref(NULL, E_WARNING, "invalid authU param."); return; } zval zbool; if(0!=opaque_UserAuth(authU0, authU)) RETURN_FALSE; RETURN_TRUE; } PHP_FUNCTION(opaque_create_registration_request) { char *pwdU; size_t pwdU_len; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(pwdU, pwdU_len) Z_PARAM_OPTIONAL ZEND_PARSE_PARAMETERS_END(); uint8_t M[crypto_core_ristretto255_BYTES]; uint8_t sec[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]; if(0!=opaque_CreateRegistrationRequest(pwdU, pwdU_len, sec, M)) return; zend_array *ret = zend_new_array(2); zval zarr; ZVAL_ARR(&zarr, ret); add_next_index_stringl(&zarr,M, sizeof(M)); add_next_index_stringl(&zarr,sec, sizeof(sec)); // sensitive RETVAL_ARR(ret); } PHP_FUNCTION(opaque_create_registration_response) { char *M; size_t M_len; char *skS=NULL; size_t skS_len=0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(M, M_len) Z_PARAM_OPTIONAL Z_PARAM_STRING(skS, skS_len) ZEND_PARSE_PARAMETERS_END(); if(M_len!=crypto_core_ristretto255_BYTES) { php_error_docref(NULL, E_WARNING, "invalid M param."); return; } if(skS != NULL && skS_len!=crypto_scalarmult_BYTES) { php_error_docref(NULL, E_WARNING, "invalid skS param."); return; } uint8_t sec[OPAQUE_REGISTER_SECRET_LEN], pub[OPAQUE_REGISTER_PUBLIC_LEN]; if(0!=opaque_CreateRegistrationResponse(M, skS, sec, pub)) return; zend_array *ret = zend_new_array(2); zval zarr; ZVAL_ARR(&zarr, ret); add_next_index_stringl(&zarr,sec, sizeof(sec)); // sensitive add_next_index_stringl(&zarr,pub, sizeof(pub)); RETVAL_ARR(ret); } PHP_FUNCTION(opaque_finalize_request) { char *sec; size_t sec_len; char *pub; size_t pub_len; char *idU; size_t idU_len; char *idS; size_t idS_len; ZEND_PARSE_PARAMETERS_START(4, 4) Z_PARAM_STRING(sec, sec_len) Z_PARAM_STRING(pub, pub_len) Z_PARAM_STRING(idU, idU_len) Z_PARAM_STRING(idS, idS_len) Z_PARAM_OPTIONAL ZEND_PARSE_PARAMETERS_END(); if(sec_len<=OPAQUE_REGISTER_USER_SEC_LEN) { php_error_docref(NULL, E_WARNING, "invalid sec param."); return; } if(pub_len!=OPAQUE_REGISTER_PUBLIC_LEN) { php_error_docref(NULL, E_WARNING, "invalid pub param."); return; } Opaque_Ids ids={.idU_len=idU_len,.idU=idU,.idS_len=idS_len,.idS=idS}; uint8_t rec[OPAQUE_REGISTRATION_RECORD_LEN]; uint8_t export_key[crypto_hash_sha512_BYTES]; if(0!=opaque_FinalizeRequest(sec, pub, &ids, rec, export_key)) return; zend_array *ret = zend_new_array(2); zval zarr; ZVAL_ARR(&zarr, ret); add_next_index_stringl(&zarr,rec, sizeof(rec)); add_next_index_stringl(&zarr,export_key, sizeof(export_key)); // sensitive RETVAL_ARR(ret); } PHP_FUNCTION(opaque_store_user_record) { char *sec; size_t sec_len; char *recU; size_t recU_len; zend_string *retval; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(sec, sec_len) Z_PARAM_STRING(recU, recU_len) Z_PARAM_OPTIONAL ZEND_PARSE_PARAMETERS_END(); if(sec_len!=OPAQUE_REGISTER_SECRET_LEN) { php_error_docref(NULL, E_WARNING, "invalid sec param."); return; } if(recU_len!=OPAQUE_REGISTRATION_RECORD_LEN) { php_error_docref(NULL, E_WARNING, "invalid rec param."); return; } uint8_t rec[OPAQUE_USER_RECORD_LEN]; opaque_StoreUserRecord(sec, recU, rec); retval = zend_string_init(rec, sizeof rec, 0); RETURN_STR(retval); } PHP_FUNCTION(opaque_create_server_keys) { ZEND_PARSE_PARAMETERS_START(0, 0) Z_PARAM_OPTIONAL ZEND_PARSE_PARAMETERS_END(); char pkS[crypto_scalarmult_BYTES]; char skS[crypto_scalarmult_SCALARBYTES]; randombytes(skS, crypto_scalarmult_SCALARBYTES); crypto_scalarmult_ristretto255_base(pkS, skS); zend_array *ret = zend_new_array(2); zval zarr; ZVAL_ARR(&zarr, ret); add_next_index_stringl(&zarr, pkS, sizeof(pkS)); add_next_index_stringl(&zarr, skS, sizeof(skS)); RETVAL_ARR(ret); } /* }}} */ /* {{{ PHP_RINIT_FUNCTION */ PHP_RINIT_FUNCTION(opaque) { #if defined(ZTS) && defined(COMPILE_DL_OPAQUE) ZEND_TSRMLS_CACHE_UPDATE(); #endif return SUCCESS; } /* }}} */ /* {{{ PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(opaque) { php_info_print_table_start(); php_info_print_table_header(2, "opaque support", "enabled"); php_info_print_table_end(); } /* }}} */ /* {{{ arginfo */ ZEND_BEGIN_ARG_INFO(arginfo_opaque_register, 0) ZEND_ARG_INFO(0, pwdU) ZEND_ARG_INFO(0, idU) ZEND_ARG_INFO(0, idS) ZEND_ARG_INFO(0, skS) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_opaque_create_credential_request, 0) ZEND_ARG_INFO(0, pwdU) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_opaque_create_credential_response, 0) ZEND_ARG_INFO(0, pub) ZEND_ARG_INFO(0, rec) ZEND_ARG_INFO(0, context) ZEND_ARG_INFO(0, idU) ZEND_ARG_INFO(0, idS) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_opaque_recover_credentials, 0) ZEND_ARG_INFO(0, resp) ZEND_ARG_INFO(0, sec) ZEND_ARG_INFO(0, context) ZEND_ARG_INFO(0, idU) ZEND_ARG_INFO(0, idS) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_opaque_user_auth, 0) ZEND_ARG_INFO(0, authU0) ZEND_ARG_INFO(0, authU) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_opaque_create_registration_request, 0) ZEND_ARG_INFO(0, pwdU) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_opaque_create_registration_response, 0) ZEND_ARG_INFO(0, M) ZEND_ARG_INFO(0, skS) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_opaque_finalize_request, 0) ZEND_ARG_INFO(0, sec) ZEND_ARG_INFO(0, pub) ZEND_ARG_INFO(0, idU) ZEND_ARG_INFO(0, idS) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_opaque_store_user_record, 0) ZEND_ARG_INFO(0, sec) ZEND_ARG_INFO(0, recU) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_opaque_create_server_keys, 0) ZEND_END_ARG_INFO() /* }}} */ /* {{{ opaque_functions[] */ static const zend_function_entry opaque_functions[] = { PHP_FE(opaque_register, arginfo_opaque_register) PHP_FE(opaque_create_credential_request, arginfo_opaque_create_credential_request) PHP_FE(opaque_create_credential_response, arginfo_opaque_create_credential_response) PHP_FE(opaque_recover_credentials, arginfo_opaque_recover_credentials) PHP_FE(opaque_user_auth, arginfo_opaque_user_auth) PHP_FE(opaque_create_registration_request, arginfo_opaque_create_registration_request) PHP_FE(opaque_create_registration_response, arginfo_opaque_create_registration_response) PHP_FE(opaque_finalize_request, arginfo_opaque_finalize_request) PHP_FE(opaque_store_user_record, arginfo_opaque_store_user_record) PHP_FE(opaque_create_server_keys, arginfo_opaque_create_server_keys) PHP_FE_END }; /* }}} */ PHP_MINIT_FUNCTION(opaque) { #if defined(ZTS) && defined(COMPILE_DL_TEST) ZEND_TSRMLS_CACHE_UPDATE(); #endif return SUCCESS; } /* {{{ opaque_module_entry */ zend_module_entry opaque_module_entry = { STANDARD_MODULE_HEADER, "opaque", /* Extension name */ opaque_functions, /* zend_function_entry */ PHP_MINIT(opaque), /* PHP_MINIT - Module initialization */ NULL, /* PHP_MSHUTDOWN - Module shutdown */ PHP_RINIT(opaque), /* PHP_RINIT - Request initialization */ NULL, /* PHP_RSHUTDOWN - Request shutdown */ PHP_MINFO(opaque), /* PHP_MINFO - Module info */ PHP_OPAQUE_VERSION, /* Version */ STANDARD_MODULE_PROPERTIES }; /* }}} */ #ifdef COMPILE_DL_OPAQUE # ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE() # endif ZEND_GET_MODULE(opaque) #endif libopaque-0.99.3/php7/php_opaque.h000066400000000000000000000004761452546740600170110ustar00rootroot00000000000000/* opaque extension for PHP */ #ifndef PHP_OPAQUE_H # define PHP_OPAQUE_H extern zend_module_entry opaque_module_entry; # define phpext_opaque_ptr &opaque_module_entry # define PHP_OPAQUE_VERSION "0.1.0" # if defined(ZTS) && defined(COMPILE_DL_OPAQUE) ZEND_TSRMLS_CACHE_EXTERN() # endif #endif /* PHP_OPAQUE_H */ libopaque-0.99.3/php7/tests/000077500000000000000000000000001452546740600156325ustar00rootroot00000000000000libopaque-0.99.3/php7/tests/001.phpt000066400000000000000000000003271452546740600170310ustar00rootroot00000000000000--TEST-- Check if opaque is loaded --SKIPIF-- --FILE-- --EXPECT-- The extension "opaque" is available libopaque-0.99.3/php7/tests/002.phpt000066400000000000000000000013101452546740600170230ustar00rootroot00000000000000--TEST-- opaque_test2() One-step Registration --SKIPIF-- --FILE-- --EXPECT-- bool(true) libopaque-0.99.3/php7/tests/003.phpt000066400000000000000000000016501452546740600170330ustar00rootroot00000000000000--TEST-- opaque_test3() Registration with Per-user Server Keys --SKIPIF-- --FILE-- --EXPECT-- bool(true) libopaque-0.99.3/php7/tests/004.phpt000066400000000000000000000017221452546740600170340ustar00rootroot00000000000000--TEST-- opaque_test4() Registration with Global Server Keys --SKIPIF-- --FILE-- --EXPECT-- bool(true) libopaque-0.99.3/python/000077500000000000000000000000001452546740600151335ustar00rootroot00000000000000libopaque-0.99.3/python/README.md000066400000000000000000000122541452546740600164160ustar00rootroot00000000000000# libopaque Python bindings These bindings provide access to libopaque which implements the [IRTF CFRG RFC draft](https://github.com/cfrg/draft-irtf-cfrg-opaque) or you can read the [original paper](https://eprint.iacr.org/2018/163). ## Dependencies These bindings depend on the following: - libopaque: https://github.com/stef/libopaque/ - libsodium - pysodium ## API There is one data structure that is used by libopaque: ### `Ids` The IDs of the peers are passed around as a struct: ```python # wrap the IDs into an opaque.Ids struct: ids=opaque.Ids("user", "server") ``` ## 1-step registration 1-step registration is only specified in the original paper. It is not specified by the IRTF CFRG draft. 1-step registration has the benefit that the supplied password (`pwdU`) can be checked on the server for password rules (e.g., occurrence in common password lists). It has the drawback that the password is exposed to the server. ```python rec, export_key = opaque.Register(pwdU, ids, skS) ``` - `pwdU` is the user's password. - `ids` is an `Ids` struct that contains the IDs of the user and the server. - `skS` is an optional server long-term private-key ## 4-step registration Registration as specified in the IRTF CFRG draft consists of the following 4 steps: ### Step 1: The user creates a registration request. ```python secU, M = opaque.CreateRegistrationRequest(pwdU) ``` - `pwdU` is the user's password. The user should hold on to `secU` securely until step 3 of the registration process. `M` needs to be passed to the server running step 2. ### Step 2: The server responds to the registration request. ```python secS, pub = opaque.CreateRegistrationResponse(M, skS) ``` - `M` comes from the user running the previous step. - `skS` is an optional server long-term private-key The server should hold onto `secS` securely until step 4 of the registration process. `pub` should be passed to the user running step 3. ### Step 3: The user finalizes the registration using the response from the server. ```python rec0, export_key = opaque.FinalizeRequest(secU, pub, ids) ``` - `secU` contains sensitive data and should be disposed securely after usage in this step. - `pub` comes from the server running the previous step. - `ids` is an `Ids` struct that contains the IDs of the user and the server. - `rec0` should be passed to the server running step 4. - `export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ### Step 4: The server finalizes the user's record. ```python rec1 = opaque.StoreUserRecord(secS, rec0) ``` - `rec0` comes from the user running the previous step. - `secS` contains sensitive data and should be disposed securely after usage in this step. - `rec1` should be stored by the server associated with the ID of the user. **Important Note**: Confusingly this function is called `StoreUserRecord`, yet it does not do any storage. How you want to store the record (`rec1`) is up to the implementor using this API. ## Establishing an opaque session After a user has registered with a server, the user can initiate the AKE and thus request its credentials in the following 3(+1)-step protocol: ### Step 1: The user initiates a credential request. ```python pub, secU = opaque.CreateCredentialRequest(pwdU) ``` - `pwdU` is the user's password. The user should hold onto `secU` securely until step 3 of the protocol. `pub` needs to be passed to the server running step 2. ### Step 2: The server responds to the credential request. ```python resp, sk, secS = opaque.CreateCredentialResponse(pub, rec, ids, context) ``` - `pub` comes from the user running the previous step. - `rec` is the user's record stored by the server at the end of the registration protocol. - `ids` is an `Ids` struct that contains the IDs of the user and the server. - `context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" - `resp` needs to be passed to the user running step 3. - `sk` is a shared secret, the result of the AKE. - The server should hold onto `secS` securely until the optional step 4 of the protocol, if needed. otherwise this value should be discarded securely. ### Step 3: The user recovers its credentials from the server's response. ```python sk, authU, export_key = opaque.RecoverCredentials(resp, secU, ctx, ids) ``` - `resp` comes from the server running the previous step. - `secU` contains sensitive data and should be disposed securely after usage in this step. - `context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" - `sk` is a shared secret, the result of the AKE. - `authU` is an authentication tag that can be passed in step 4 for explicit user authentication. - `export_key` can be used to decrypt additional data stored by the server. ### Step 4 (Optional): The server authenticates the user. This step is only needed if there is no encrypted channel setup towards the server using the shared secret. ```python opaque.UserAuth(secS, authU) ``` - `secS` contains sensitive data and should be disposed securely after usage in this step. - `authU` comes from the user running the previous step. libopaque-0.99.3/python/opaque/000077500000000000000000000000001452546740600164255ustar00rootroot00000000000000libopaque-0.99.3/python/opaque/__init__.py000066400000000000000000000512071452546740600205430ustar00rootroot00000000000000"""Wrapper for libopaque library Copyright (c) 2018-2021, Marsiske Stefan. All rights reserved. This file is part of libopaque. libopaque is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. libopaque is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with libopaque. If not, see . """ import ctypes import ctypes.util from ctypes import c_uint16 opaquelib = ctypes.cdll.LoadLibrary(ctypes.util.find_library('opaque') or ctypes.util.find_library('libopaque')) if not opaquelib._name: raise ValueError('Unable to find libopaque') from pysodium import (crypto_core_ristretto255_SCALARBYTES, crypto_scalarmult_SCALARBYTES, crypto_scalarmult_BYTES, crypto_core_ristretto255_BYTES, crypto_hash_sha512_BYTES, sodium_version_check) # todo what is this? if sodium_version_check(1,0,18): from pysodium import crypto_auth_hmacsha512_BYTES else: crypto_auth_hmacsha512_BYTES = 64 OPAQUE_SHARED_SECRETBYTES = 64 OPAQUE_NONCE_BYTES = 32 OPAQUE_ENVELOPE_NONCEBYTES = 32 OPAQUE_REGISTRATION_RECORD_LEN = ( crypto_scalarmult_BYTES+ # client_public_key crypto_hash_sha512_BYTES+ # masking_key OPAQUE_ENVELOPE_NONCEBYTES+ # envelope nonce crypto_auth_hmacsha512_BYTES) # envelope mac OPAQUE_USER_RECORD_LEN = ( crypto_core_ristretto255_SCALARBYTES+ # kU crypto_scalarmult_SCALARBYTES+ # skS OPAQUE_REGISTRATION_RECORD_LEN) OPAQUE_USER_SESSION_PUBLIC_LEN = ( crypto_core_ristretto255_BYTES+ # blinded crypto_scalarmult_BYTES+ # X_u OPAQUE_NONCE_BYTES) # nonceU OPAQUE_USER_SESSION_SECRET_LEN = ( crypto_core_ristretto255_SCALARBYTES+ # r crypto_scalarmult_SCALARBYTES+ # x_u OPAQUE_NONCE_BYTES+ # nonceU crypto_core_ristretto255_BYTES+ # blinded OPAQUE_USER_SESSION_PUBLIC_LEN+ # ke1 2) # pwdU_len OPAQUE_SERVER_SESSION_LEN = ( crypto_core_ristretto255_BYTES+ # Z 32+ # masking_nonce crypto_scalarmult_BYTES+ # server_public_key OPAQUE_NONCE_BYTES+ # nonceS crypto_scalarmult_BYTES+ # X_s crypto_auth_hmacsha512_BYTES+ # auth OPAQUE_ENVELOPE_NONCEBYTES+ # envelope nonce crypto_auth_hmacsha512_BYTES) # envelope mac OPAQUE_REGISTER_USER_SEC_LEN = ( crypto_core_ristretto255_SCALARBYTES+ # r 2) # pwdU_len OPAQUE_REGISTER_PUBLIC_LEN = ( crypto_core_ristretto255_BYTES+ # Z crypto_scalarmult_BYTES) # pkS OPAQUE_REGISTER_SECRET_LEN = ( crypto_scalarmult_SCALARBYTES+ # skS crypto_core_ristretto255_SCALARBYTES) # kU def __check(code): if code != 0: raise ValueError # struct to store the IDs of the user/server. class Ids(ctypes.Structure): _fields_ = [('idU_len', c_uint16), # length of idU, most useful if idU is binary ('idU', ctypes.c_char_p), # pointer to the id of the user/client in the opaque protocol ('idS_len', c_uint16), # length of idS, needed for binary ids ('idS', ctypes.c_char_p)] # pointer to the id of the server in the opaque protocol def __init__(self, idu=None, ids=None): super().__init__() if idu: self.idU=idu.encode("utf8") if isinstance(idu,str) else idu self.idU_len=len(self.idU) if ids: self.idS=ids.encode('utf8') if isinstance(ids,str) else ids self.idS_len=len(self.idS) # This function implements the storePwdFile function from the paper # it is not specified by the RFC. This function runs on the server # and creates a new output record rec of secret key material. The # server needs to implement the storage of this record and any # binding to user names or as the paper suggests sid. # # @param [in] pwdU - the users password # @param [in] pwdU_len - length of the users password # @param [in] skS - in case of global server keys this is the servers # private key, should be set to NULL if per/user keys are to be # generated # @param [in] ids - the ids of the user and server, see Opaque_Ids # @param [out] rec - the opaque record the server needs to # store. this is a pointer to memory allocated by the caller, # and must be large enough to hold the record and take into # account the variable length of idU and idS in case these are # included in the envelope. # @param [out] export_key - optional pointer to pre-allocated (and # protected) memory for an extra_key that can be used to # encrypt/authenticate additional data. # @return the function returns 0 if everything is correct #int opaque_Register(const uint8_t *pwdU, const uint16_t pwdU_len, # const uint8_t skS[crypto_scalarmult_SCALARBYTES], # const Opaque_Ids *ids, # uint8_t rec[OPAQUE_USER_RECORD_LEN], # uint8_t export_key[crypto_hash_sha512_BYTES]); def Register(pwdU, ids, skS=None): if not pwdU: raise ValueError("invalid parameter") if skS and len(skS) != crypto_scalarmult_SCALARBYTES: raise ValueError("invalid skS param") pwdU=pwdU.encode("utf8") if isinstance(pwdU,str) else pwdU rec = ctypes.create_string_buffer(OPAQUE_USER_RECORD_LEN) export_key = ctypes.create_string_buffer(crypto_hash_sha512_BYTES) __check(opaquelib.opaque_Register(pwdU, len(pwdU), skS, ctypes.pointer(ids), rec, export_key)) return rec.raw, export_key.raw # This function initiates a new OPAQUE session, is the same as the # function defined in the paper with the name usrSession. # # @param [in] pwdU - users input password # @param [in] pwdU_len - length of the users password # @param [out] sec - private context, it is essential that the memory # allocate for this buffer be **OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len**. # The User should protect the sec value (e.g. with sodium_mlock()) # until opaque_RecoverCredentials. # @param [out] pub - the message to be sent to the server # @return the function returns 0 if everything is correct #int opaque_CreateCredentialRequest(const uint8_t *pwdU, const uint16_t pwdU_len, uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], uint8_t pub[OPAQUE_USER_SESSION_PUBLIC_LEN]); def CreateCredentialRequest(pwdU): if not pwdU: raise ValueError("invalid parameter") pwdU=pwdU.encode("utf8") if isinstance(pwdU,str) else pwdU sec = ctypes.create_string_buffer(OPAQUE_USER_SESSION_SECRET_LEN+len(pwdU)) pub = ctypes.create_string_buffer(OPAQUE_USER_SESSION_PUBLIC_LEN) opaquelib.opaque_CreateCredentialRequest(pwdU, len(pwdU), sec, pub) return pub.raw, sec.raw # This is the same function as defined in the paper with name # srvSession name. This function runs on the server and # receives the output pub from the user running opaque_CreateCredentialRequest(), # furthermore the server needs to load the user record created when # registering the user with opaque_Register() or # opaque_StoreUserRecord(). These input parameters are # transformed into a secret/shared session key sk and a response resp # to be sent back to the user. # @param [in] pub - the pub output of the opaque_CreateCredentialRequest() # @param [in] rec - the recorded created during "registration" and stored by the server # @param [in] ids - the id if the client and server # @param [in] ctx - a context of this instantiation of this protocol, e.g. "AppABCv12.34" # @param [in] ctx_len - a context of this instantiation of this protocol # @param [out] resp - servers response to be sent to the client where # it is used as input into opaque_RecoverCredentials() # @param [out] sk - the shared secret established between the user & server # @param [out] sec - the current context necessary for the explicit # authentication of the user in opaque_UserAuth(). This # param is optional if no explicit user auth is necessary it can be # set to NULL # @return the function returns 0 if everything is correct #int opaque_CreateCredentialResponse(const uint8_t pub[OPAQUE_USER_SESSION_PUBLIC_LEN], # const uint8_t rec[OPAQUE_USER_RECORD_LEN], # const Opaque_Ids *ids, # const uint8_t *ctx, const uint16_t ctx_len, # uint8_t resp[OPAQUE_SERVER_SESSION_LEN], # uint8_t sk[OPAQUE_SHARED_SECRETBYTES], # uint8_t authU[crypto_auth_hmacsha512_BYTES]); def CreateCredentialResponse(pub, rec, ids, ctx): if None in (pub, rec): raise ValueError("invalid parameter") if len(pub) != OPAQUE_USER_SESSION_PUBLIC_LEN: raise ValueError("invalid pub param") if len(rec) != OPAQUE_USER_RECORD_LEN: raise ValueError("invalid rec param") ctx=ctx.encode("utf8") if isinstance(ctx,str) else ctx resp = ctypes.create_string_buffer(OPAQUE_SERVER_SESSION_LEN) sk = ctypes.create_string_buffer(OPAQUE_SHARED_SECRETBYTES) sec = ctypes.create_string_buffer(crypto_auth_hmacsha512_BYTES) __check(opaquelib.opaque_CreateCredentialResponse(pub, rec, ctypes.pointer(ids), ctx, len(ctx), resp, sk, sec)) return resp.raw, sk.raw, sec.raw # This is the same function as defined in the paper with the # usrSessionEnd name. It is run by the user and receives as input the # response from the previous server opaque_CreateCredentialResponse() # function as well as the sec value from running the # opaque_CreateCredentialRequest() function that initiated this # instantiation of this protocol, All these input parameters are # transformed into a shared/secret session key pk, which should be # the same as the one calculated by the # opaque_CreateCredentialResponse() function. # # @param [in] resp - the response sent from the server running opaque_CreateCredentialResponse() # @param [in] sec - the private sec output of the client initiating # this instantiation of this protocol using opaque_CreateCredentialRequest() # @param [in] infos - various extra (unspecified) protocol information # as recommended by the rfc # @param [in] ctx - a context of this instantiation of this protocol, e.g. "AppABCv12.34" # @param [in] ctx_len - a context of this instantiation of this protocol # @param [in] ids - The ids of the server/client in case they are not the default. # @param [out] sk - the shared secret established between the user & server # @param [out] authU - the authentication code to be sent to the server # in case explicit user authentication is required # @param [out] export_key - key used to encrypt/authenticate extra # material not stored directly in the envelope # @return the function returns 0 if the protocol is executed correctly #int opaque_RecoverCredentials(const uint8_t resp[OPAQUE_SERVER_SESSION_LEN], # const uint8_t *sec/*[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len]*/, # const uint8_t *ctx, const uint16_t ctx_len, # const Opaque_Ids *ids, # uint8_t sk[OPAQUE_SHARED_SECRETBYTES], # uint8_t authU[crypto_auth_hmacsha512_BYTES], # uint8_t export_key[crypto_hash_sha512_BYTES]); def RecoverCredentials(resp, sec, ctx, ids=None): if None in (resp, sec): raise ValueError("invalid parameter") if len(resp) != OPAQUE_SERVER_SESSION_LEN: raise ValueError("invalid resp param") if len(sec) <= OPAQUE_USER_SESSION_SECRET_LEN: raise ValueError("invalid sec param") ctx=ctx.encode("utf8") if isinstance(ctx,str) else ctx sk = ctypes.create_string_buffer(OPAQUE_SHARED_SECRETBYTES) authU = ctypes.create_string_buffer(crypto_auth_hmacsha512_BYTES) export_key = ctypes.create_string_buffer(crypto_hash_sha512_BYTES) if ids is None: ids = Ids() __check(opaquelib.opaque_RecoverCredentials(resp, sec, ctx, len(ctx), ctypes.pointer(ids), sk, authU, export_key)) return sk.raw, authU.raw, export_key.raw # Explicit User Authentication. # # This is a function not explicitly specified in the original paper. In the # irtf cfrg draft authentication is done using a hmac of the session # transcript with different keys coming out of a hkdf after the key # exchange. # # @param [in] authU0 - the authU value returned by opaque_CreateCredentialResponse() # @param [in] authU is the authentication token sent by the user. # @return the function returns 0 if the hmac verifies correctly. #int opaque_UserAuth(const uint8_t authU0[crypto_auth_hmacsha512_BYTES], const uint8_t authU[crypto_auth_hmacsha512_BYTES]); def UserAuth(authU0, authU): if None in (authU0, authU): raise ValueError("invalid parameter") if len(authU0) != crypto_auth_hmacsha512_BYTES: raise ValueError("invalid authU0 param") if len(authU) != crypto_auth_hmacsha512_BYTES: raise ValueError("invalid authU param") __check(opaquelib.opaque_UserAuth(authU0, authU)) # Alternative user initialization, user registration as specified by the RFC # The paper originally proposes a very simple 1 shot interface for # registering a new "user", however this has the drawback that in # that case the users secrets and its password are exposed in # cleartext at registration to the server. There is an alternative 4 # message registration protocol specified by the rfc, which avoids # the exposure of the secrets and the password to the server which # can be instantiated by following for registration functions. # Initial step to start registering a new user/client with the server. # The user inputs its password pwdU, and receives a secret context sec # and a blinded value M as output. sec should be protected until # step 3 of this registration protocol and the value M should be # passed to the server. # @param [in] pwdU - the users password # @param [in] pwdU_len - length of the users password # @param [out] sec - a secret context needed for the 3rd step in this # registration protocol - this needs to be protected and sanitized # after usage. # @param [out] request - the blinded hashed password as per the OPRF, # this needs to be sent to the server together with any other # important and implementation specific info such as user/client id, # envelope configuration etc. # @return the function returns 0 if everything is correct. #int opaque_CreateRegistrationRequest(const uint8_t *pwdU, # const uint16_t pwdU_len, # uint8_t sec[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len], # uint8_t request[crypto_core_ristretto255_BYTES]); def CreateRegistrationRequest(pwdU): if not pwdU: raise ValueError("invalid parameter") pwdU=pwdU.encode("utf8") if isinstance(pwdU,str) else pwdU sec = ctypes.create_string_buffer(OPAQUE_REGISTER_USER_SEC_LEN+len(pwdU)) request = ctypes.create_string_buffer(crypto_core_ristretto255_BYTES) __check(opaquelib.opaque_CreateRegistrationRequest(pwdU, len(pwdU), sec, request)) return sec.raw, request.raw # Server evaluates OPRF and creates a user-specific public/private keypair # # The server receives M from the users invocation of its # opaque_CreateRegistrationRequest() function, it outputs a value sec # which needs to be protected until step 4 by the server. This # function also outputs a value pub which needs to be passed to the # user. # @param [in] request - the blinded password as per the OPRF. # @param [in] skS - the servers long-term private key, optional, set # to NULL if you want this implementation to generate a unique key # for this record. # @param [out] sec - the private key and the OPRF secret of the server. # @param [out] pub - the evaluated OPRF and pubkey of the server to # be passed to the client into opaque_FinalizeRequest() # @return the function returns 0 if everything is correct. #int opaque_CreateRegistrationResponse(const uint8_t request[crypto_core_ristretto255_BYTES], # const uint8_t skS[crypto_scalarmult_SCALARBYTES], # uint8_t sec[OPAQUE_REGISTER_SECRET_LEN], # uint8_t pub[OPAQUE_REGISTER_PUBLIC_LEN]); def CreateRegistrationResponse(request, skS=None): if not request: raise ValueError("invalid parameter") if len(request) != crypto_core_ristretto255_BYTES: raise ValueError("invalid request param") if skS is not None and len(skS) != crypto_scalarmult_SCALARBYTES: raise ValueError("invalid skS param") sec = ctypes.create_string_buffer(OPAQUE_REGISTER_SECRET_LEN) pub = ctypes.create_string_buffer(OPAQUE_REGISTER_PUBLIC_LEN) __check(opaquelib.opaque_CreateRegistrationResponse(request, skS, sec, pub)) return sec.raw, pub.raw # Client finalizes registration by concluding the OPRF, generating # its own keys and enveloping it all. # # This function is called FinalizeRequest in the rfc. This function # is run by the user, taking as input the context sec that was an # output of the user running opaque_CreateRegistrationRequest(), and the # output pub from the server of opaque_CreateRegistrationResponse(). # # @param [in] sec - output from opaque_CreateRegistrationRequest(), # should be sanitized after usage. # @param [in] pub - response from the server running # opaque_CreateRegistrationResponse() # @param [in] ids # @param [out] reg_ rec - the opaque registration record containing # the users data. # @param [out] export_key - key used to encrypt/authenticate extra # material not stored directly in the envelope # # @return the function returns 0 if everything is correct. #int opaque_FinalizeRequest(const uint8_t *sec/*[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]*/, # const uint8_t pub[OPAQUE_REGISTER_PUBLIC_LEN], # const Opaque_Ids *ids, # uint8_t reg_rec[OPAQUE_REGISTRATION_RECORD_LEN], # uint8_t export_key[crypto_hash_sha512_BYTES]); def FinalizeRequest(sec, pub, ids): if None in (sec, pub, ids): raise ValueError("invalid parameter") if len(sec) <= OPAQUE_REGISTER_USER_SEC_LEN: raise ValueError("invalid sec param") if len(pub) != OPAQUE_REGISTER_PUBLIC_LEN: raise ValueError("invalid pub param") rec = ctypes.create_string_buffer(OPAQUE_REGISTRATION_RECORD_LEN) export_key = ctypes.create_string_buffer(crypto_hash_sha512_BYTES) __check(opaquelib.opaque_FinalizeRequest(sec, pub, ctypes.pointer(ids), rec, export_key)) return rec.raw, export_key.raw # Final Registration step - server adds own info to the record to be stored. # # The rfc does not explicitly specify this function. # The server combines the sec value from its run of its # opaque_CreateRegistrationResponse() function with the rec output of # the users opaque_FinalizeRequest() function, creating the # final record, which should be the same as the output of the 1-step # storePwdFile() init function of the paper. The server should save # this record in combination with a user id and/or sid value as # suggested in the paper. # # @param [in] sec - the private value of the server running # opaque_CreateRegistrationResponse() in step 2 of the registration # protocol # @param [in] reg_rec - the registration record from the client running # opaque_FinalizeRequest() # @param [out] rec - the final record to be stored by the server. #void opaque_StoreUserRecord(const uint8_t sec[OPAQUE_REGISTER_SECRET_LEN], # const uint8_t recU[OPAQUE_REGISTRATION_RECORD_LEN], # uint8_t rec[OPAQUE_USER_RECORD_LEN]); def StoreUserRecord(sec, reg_rec): if None in (sec, reg_rec): raise ValueError("invalid parameter") if len(sec) != OPAQUE_REGISTER_SECRET_LEN: raise ValueError("invalid sec param") if len(reg_rec) != OPAQUE_REGISTRATION_RECORD_LEN: raise ValueError("invalid reg_rec param") rec = ctypes.create_string_buffer(OPAQUE_USER_RECORD_LEN) opaquelib.opaque_StoreUserRecord(sec, reg_rec, rec) return rec.raw libopaque-0.99.3/python/setup.py000066400000000000000000000020471452546740600166500ustar00rootroot00000000000000import os from setuptools import setup, find_packages # Utility function to read the README file. # Used for the long_description. It's nice, because now 1) we have a top level # README file and 2) it's easier to type in the README file than to put a raw # string in below ... def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( name="opaque", version="0.2.0", author="Stefan Marsiske", author_email="pyopaque@ctrlc.hu", description="python libopaque wrapper", license="GPLv3", keywords="cryptography API libopaque OPAQUE PAKE AKE key-exchange", url="https://github.com/stef/libopaque", packages=find_packages(), long_description=read('README.md'), long_description_content_type="text/markdown", requires=["libsodium"], classifiers=["Development Status :: 4 - Beta", "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", "Topic :: Security :: Cryptography", "Topic :: Security"], ) libopaque-0.99.3/python/test/000077500000000000000000000000001452546740600161125ustar00rootroot00000000000000libopaque-0.99.3/python/test/simple.py000077500000000000000000000064021452546740600177620ustar00rootroot00000000000000#!/usr/bin/env python import opaque import pysodium from pysodium import (crypto_scalarmult_SCALARBYTES) pwdU=b"simple guessable dictionary password" # wrap the IDs into an opaque.Ids struct: ids=opaque.Ids("user", "server") context = "pyopaque-v0.2.0" # one step registration - only specified in the original paper, not # specified by ietf cfrg draft has the benefit that the supplied # password can be checked on the server for password rules # (e.g. occurence in common password lists), has the drawback that the # password is exposed to the server. rec, export_key = opaque.Register(pwdU, ids, skS=None) # user initiates a credential request pub, secU = opaque.CreateCredentialRequest(pwdU) # server responds to credential request resp, sk, authU0 = opaque.CreateCredentialResponse(pub, rec, ids, context) # user recovers its credentials from the servers response sk1, authU, export_key1 = opaque.RecoverCredentials(resp, secU, context, ids) # server authenticates user opaque.UserAuth(authU0, authU) assert export_key==export_key1, "export_key must equal export_key1." assert sk==sk1, "sk must equal sk1." # registering as specified in the ietf cfrg draft # user create a registration request secU, request = opaque.CreateRegistrationRequest(pwdU) # server responds to the registration request secS, pub = opaque.CreateRegistrationResponse(request) # user finalizes the registration using the response from the server reg_rec, export_key = opaque.FinalizeRequest(secU, pub, ids) # server finalizes the user record rec = opaque.StoreUserRecord(secS, reg_rec) # same steps as above, 1. user initiates credential request pub, secU = opaque.CreateCredentialRequest(pwdU) # 2. server responds to credential request resp, sk, secS = opaque.CreateCredentialResponse(pub, rec, ids, context) # 3. user recovers its credentials from the server ressponse sk1, authU, export_key1 = opaque.RecoverCredentials(resp, secU, context, ids) # 4. server authenicates user opaque.UserAuth(secS, authU) assert export_key==export_key1, "export_key must equal export_key1." assert sk==sk1, "sk must equal sk1." def register_with_global_server_key(): pwdU=b"simple guessable dictionary password" context = "pyopaque-v0.2.0" ids=opaque.Ids("user", "server") skS=pysodium.randombytes(crypto_scalarmult_SCALARBYTES) # Uncomment the following if you compiled libopaque with -DNORANDOM -DTRACE and # want the same output as register_with_global_server_key in # src/tests/opaque-test.c. Also see a_randombytes in src/common.c. #skS=ctypes.create_string_buffer(crypto_scalarmult_SCALARBYTES) #for i in range(0, 32): # ctypes.memset(ctypes.addressof(skS) + i, i, 1) secU, M = opaque.CreateRegistrationRequest(pwdU) secS, pub = opaque.CreateRegistrationResponse(M, skS) rec, export_key = opaque.FinalizeRequest(secU, pub, ids) rec = opaque.StoreUserRecord(secS, rec) pub, secU = opaque.CreateCredentialRequest(pwdU) resp, sk, secS = opaque.CreateCredentialResponse(pub, rec, ids, context) sk1, authU, export_key1 = opaque.RecoverCredentials(resp, secU, context, ids) opaque.UserAuth(secS, authU) assert export_key==export_key1, "export_key must equal export_key1." assert sk==sk1, "sk must equal sk1." register_with_global_server_key() print("test ok") libopaque-0.99.3/ruby/000077500000000000000000000000001452546740600145735ustar00rootroot00000000000000libopaque-0.99.3/ruby/README.md000066400000000000000000000144511452546740600160570ustar00rootroot00000000000000# Ruby bindings for libopaque These bindings provide access to libopaque which implements the [IRTF CFRG RFC draft](https://github.com/cfrg/draft-irtf-cfrg-opaque) or you can read the [original paper](https://eprint.iacr.org/2018/163). ## Dependencies These bindings depend on the following: - libopaque: https://github.com/stef/libopaque/ - libsodium ## Building You need to have libopaque installed, and working (thus also libsodium), then: ``` ruby extconf.rb make ``` ## Examples see test.rb ## API There is one data structure that is used by libopaque: ### `Ids` The IDs of the client (idU) and the server (idS) are passed directly as seperate parameters to functions that need to handle IDs. ## One-key Server convenience function For the case that a setup requires externally generated long-term server keys the ruby bindings provide a convenience function `create_server_keys()` which can be used to generate a server key-pair. The function returns `pkS` and `skS` in this order. ## 1-step registration 1-step registration is only specified in the original paper. It is not specified by the IRTF CFRG draft. 1-step registration has the benefit that the supplied password (`pwd`) can be checked on the server for password rules (e.g., occurrence in common password lists, please obey [NIST SP 800-63-3b](https://pages.nist.gov/800-63-3/sp800-63b.html#memsecret)). It has the drawback that the password is exposed during registration to the server. ```ruby rec, export_key = register(pwd, idU, idS, skS); ``` The function expects these paramters: - `pwd` is the user's password. - `idU` is the clients ID, - `idS` is the servers ID, - `cfg` is an array containing the envelope configuration, - `skS` is an optional explicitly specified server long-term key This function returns: - `rec` should be stored by the server associated with the ID of the user. - `export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ## 4-step registration Registration as specified in the IRTF CFRG draft consists of the following 4 steps: ### Step 1: The user creates a registration request. ```ruby req, sec = create_registration_request(pwd); ``` - `pwd` is the user's password. The user should hold on to `sec` securely until step 3 of the registration process. `req` needs to be passed to the server running step 2. ### Step 2: The server responds to the registration request. ```ruby sec, resp = create_registration_response(req, skS); ``` - `req` comes from the user running the previous step. - `skS` is an optional explicitly specified server long-term private-key The server should hold onto `sec` securely until step 4 of the registration process. `resp` should be passed to the user running step 3. ### Step 3: The user finalizes the registration using the response from the server. ```ruby rec, export_key = finalize_request(sec, resp, idU, idS); ``` - `sec` contains sensitive data and should be disposed securely after usage in this step. - `resp` comes from the server running the previous step. - `idU` is the clients ID, - `idS` is the servers ID, The function outputs: - `rec` should be passed to the server running step 4. - `export_key` is an extra secret that can be used to encrypt additional data that you might want to store on the server next to your record. ### Step 4: The server finalizes the user's record. ```ruby rec = store_user_record(sec, rec); ``` - `rec` comes from the client running the previous step. - `sec` contains sensitive data and should be disposed securely after usage in this step. The function returns: - `rec` should be stored by the server associated with the ID of the user. **Important Note**: Confusingly this function is called `StoreUserRecord`, yet it does not do any storage. How you want to store the record (`rec`) is up to the implementor using this API. ## Establishing an opaque session After a user has registered with a server, the user can initiate the AKE and thus request its credentials in the following 3(+1)-step protocol: ### Step 1: The user initiates a credential request. ```ruby sec, req = create_credential_request(pwd) ``` - `pwd` is the user's password. The user should hold onto `sec` securely until step 3 of the protocol. `pub` needs to be passed to the server running step 2. ### Step 2: The server responds to the credential request. ```ruby resp, sk, sec = create_credential_response(req, rec, idU, idS, context); ``` - `req` comes from the user running the previous step. - `rec` is the user's record stored by the server at the end of the registration protocol. - `idU` is the clients ID, - `idS` is the servers ID, - `context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" This function returns: - `resp` needs to be passed to the user running step 3. - `sk` is a shared secret, the result of the AKE. - `sec` is the servers sensitive context. The server should hold onto this valuesecurely until the optional step 4 of the protocol, if needed. otherwise this value should be discarded securely. ### Step 3: The user recovers its credentials from the server's response. ```ruby sk, authU, export_key = recover_credentials(resp, sec, context, idU, idS); ``` - `resp` comes from the server running the previous step. - `sec` contains the client sensitive data from the first step and should be disposed securely after this step. - `context` is a string distinguishing this instantiation of the protocol from others, e.g. "MyApp-v0.2" - `idU` is the client ID - `idS` is the server ID This function returns: - `sk` is a shared secret, the result of the AKE. - `authU` is an authentication tag that can be passed in step 4 for explicit user authentication. - `export_key` can be used to decrypt additional data stored by the server. ### Step 4 (Optional): The server authenticates the user. This step is only needed if there is no encrypted channel setup towards the server using the shared secret. ```ruby user_auth(sec, authU); ``` - `sec` contains the servers sensitive context from the second step and should be disposed securely after usage in this step. - `authU` comes from the user running the previous step. The function returns a boolean `false` in case the authentication failed, otherwise `true`. libopaque-0.99.3/ruby/extconf.rb000066400000000000000000000001171452546740600165650ustar00rootroot00000000000000require 'mkmf' have_library('opaque') create_header create_makefile 'opaque' libopaque-0.99.3/ruby/opaque.c000066400000000000000000000236771452546740600162500ustar00rootroot00000000000000#include "opaque.h" #include "ruby.h" #include "extconf.h" static void extract_str(VALUE arg, char **str, size_t *len, const char* err) { if (RB_TYPE_P(arg, T_STRING) != 1) { rb_raise(rb_eTypeError, "%s", err); } *len = RSTRING_LEN(arg); *str = RSTRING_PTR(arg); } VALUE opaque_register(int argc, VALUE *argv, VALUE obj) { rb_check_arity(argc, 3, 4); char *pwdU; size_t pwdU_len; char *idU; size_t idU_len; char *idS; size_t idS_len; char *skS=NULL; size_t skS_len=0; extract_str(argv[0], &pwdU, &pwdU_len, "pwdU is not a string"); extract_str(argv[1], &idU, &idU_len, "idU is not a string"); extract_str(argv[2], &idS, &idS_len, "idS is not a string"); Opaque_Ids ids={.idU_len=idU_len,.idU=idU,.idS_len=idS_len,.idS=idS}; if(argc==4) { extract_str(argv[3], &skS, &skS_len, "skS is not a string"); if(skS_len!=crypto_scalarmult_SCALARBYTES) { rb_raise(rb_eTypeError, "skS param is not exactly %d bytes long", crypto_scalarmult_SCALARBYTES); } } uint8_t export_key[crypto_hash_sha512_BYTES]; uint8_t rec[OPAQUE_USER_RECORD_LEN]; if(0!=opaque_Register(pwdU, pwdU_len, skS, &ids, rec, export_key)) { rb_raise(rb_eRuntimeError, "register failed"); } return rb_ary_new_from_args(2, rb_str_new(rec,sizeof rec), rb_str_new(export_key, sizeof export_key) ); } VALUE opaque_create_credential_request(int argc, VALUE *argv, VALUE obj) { rb_check_arity(argc, 1, 1); char *pwdU; size_t pwdU_len; extract_str(argv[0], &pwdU, &pwdU_len, "pwdU is not a string"); uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], pub[OPAQUE_USER_SESSION_PUBLIC_LEN]; if(0!=opaque_CreateCredentialRequest(pwdU, pwdU_len, sec, pub)) { rb_raise(rb_eRuntimeError, "create credential request failed"); } return rb_ary_new_from_args(2, rb_str_new(sec,sizeof sec), rb_str_new(pub,sizeof pub) ); } VALUE opaque_create_credential_response(int argc, VALUE *argv, VALUE obj) { rb_check_arity(argc, 5, 5); char *pub; size_t pub_len; char *rec; size_t rec_len; char *idU; size_t idU_len; char *idS; size_t idS_len; char *context; size_t context_len; extract_str(argv[0], &pub, &pub_len, "pub is not a string"); if(pub_len!=OPAQUE_USER_SESSION_PUBLIC_LEN) { rb_raise(rb_eRuntimeError, "invalid pub param."); } extract_str(argv[1], &rec, &rec_len, "rec is not a string"); if(rec_len!=OPAQUE_USER_RECORD_LEN) { rb_raise(rb_eRuntimeError, "invalid rec param."); } extract_str(argv[2], &idU, &idU_len, "idU is not a string"); extract_str(argv[3], &idS, &idS_len, "idS is not a string"); Opaque_Ids ids={.idU_len=idU_len,.idU=idU,.idS_len=idS_len,.idS=idS}; extract_str(argv[4], &context, &context_len, "context is not a string"); uint8_t resp[OPAQUE_SERVER_SESSION_LEN]; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t sec[crypto_auth_hmacsha512_BYTES]={0}; if(0!=opaque_CreateCredentialResponse(pub, rec, &ids, context, context_len, resp, sk, sec)) { rb_raise(rb_eRuntimeError, "create credential response failed"); } return rb_ary_new_from_args(3, rb_str_new(resp,sizeof resp), rb_str_new(sk,sizeof sk), rb_str_new(sec,sizeof sec) ); } VALUE opaque_recover_credentials(int argc, VALUE *argv, VALUE obj) { rb_check_arity(argc, 3, 5); char *resp; size_t resp_len; char *sec; size_t sec_len; char *context; size_t context_len; char *idU=NULL; size_t idU_len=0; char *idS=NULL; size_t idS_len=0; extract_str(argv[0], &resp, &resp_len, "resp is not a string"); // size check after envU_len is available later extract_str(argv[1], &sec, &sec_len, "sec is not a string"); if(sec_len<=OPAQUE_USER_SESSION_SECRET_LEN) { rb_raise(rb_eTypeError, "invalid sec param."); } extract_str(argv[2], &context, &context_len, "context is not a string"); if(argc>3 && RB_TYPE_P(argv[3], T_STRING) == 1) { extract_str(argv[3], &idU, &idU_len, ""); } if(argc>4 && RB_TYPE_P(argv[4], T_STRING) == 1) { extract_str(argv[4], &idS, &idS_len, ""); } Opaque_Ids ids={.idU_len=idU_len,.idU=idU,.idS_len=idS_len,.idS=idS}; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t authU[crypto_auth_hmacsha512_BYTES]; uint8_t export_key[crypto_hash_sha512_BYTES]; if(0!=opaque_RecoverCredentials(resp, sec, context, context_len, &ids, sk, authU, export_key)) { rb_raise(rb_eRuntimeError, "recover credentials failed"); } return rb_ary_new_from_args(3, rb_str_new(sk,sizeof sk), rb_str_new(authU,sizeof authU), rb_str_new(export_key,sizeof export_key) ); } VALUE opaque_user_auth(int argc, VALUE *argv, VALUE obj) { rb_check_arity(argc, 2, 2); char *sec; size_t sec_len; char *authU; size_t authU_len; extract_str(argv[0], &sec, &sec_len, "sec is not a string"); if(sec_len!=crypto_auth_hmacsha512_BYTES) { rb_raise(rb_eTypeError, "sec param is invalid"); } extract_str(argv[1], &authU, &authU_len, "authU is not a string"); if(authU_len!=crypto_auth_hmacsha512_BYTES) { rb_raise(rb_eTypeError, "authU param is invalid"); } if(0!=opaque_UserAuth(sec, authU)) return Qfalse; return Qtrue; } VALUE opaque_create_registration_request(int argc, VALUE *argv, VALUE obj) { rb_check_arity(argc, 1, 1); char *pwdU; size_t pwdU_len; extract_str(argv[0], &pwdU, &pwdU_len, "pwdU is not a string"); uint8_t M[crypto_core_ristretto255_BYTES]; uint8_t sec[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]; if(0!=opaque_CreateRegistrationRequest(pwdU, pwdU_len, sec, M)) { rb_raise(rb_eRuntimeError, "create registation request failed"); } return rb_ary_new_from_args(2, rb_str_new(M,sizeof M), rb_str_new(sec, sizeof sec) ); } VALUE opaque_create_registration_response(int argc, VALUE *argv, VALUE obj) { rb_check_arity(argc, 1, 2); char *M; size_t M_len; char *skS=NULL; size_t skS_len; extract_str(argv[0], &M, &M_len, "M is not a string"); if(M_len!=crypto_core_ristretto255_BYTES) { rb_raise(rb_eTypeError, "M is not 32B"); } if(argc>1) { extract_str(argv[1], &skS, &skS_len, "skS is not a string"); if(skS_len!=crypto_scalarmult_SCALARBYTES) { rb_raise(rb_eTypeError, "skS is not 32B"); } } uint8_t sec[OPAQUE_REGISTER_SECRET_LEN], pub[OPAQUE_REGISTER_PUBLIC_LEN]; if(0!=opaque_CreateRegistrationResponse(M, skS, sec, pub)) { rb_raise(rb_eRuntimeError, "create registration response failed"); } return rb_ary_new_from_args(2, rb_str_new(sec,sizeof sec), rb_str_new(pub, sizeof pub) ); } VALUE opaque_finalize_request(int argc, VALUE *argv, VALUE obj) { rb_check_arity(argc, 4, 4); char *sec; size_t sec_len; char *pub; size_t pub_len; char *idU; size_t idU_len; char *idS; size_t idS_len; extract_str(argv[0], &sec, &sec_len, "sec is not a string"); if(sec_len<=OPAQUE_REGISTER_USER_SEC_LEN) { rb_raise(rb_eTypeError, "sec is invalid size"); } extract_str(argv[1], &pub, &pub_len, "pub is not a string"); if(pub_len!=OPAQUE_REGISTER_PUBLIC_LEN) { rb_raise(rb_eTypeError, "pub is invalid size"); } extract_str(argv[2], &idU, &idU_len, "idU is not a string"); extract_str(argv[3], &idS, &idS_len, "idS is not a string"); Opaque_Ids ids={.idU_len=idU_len,.idU=idU,.idS_len=idS_len,.idS=idS}; uint8_t rec[OPAQUE_REGISTRATION_RECORD_LEN]; uint8_t export_key[crypto_hash_sha512_BYTES]; if(0!=opaque_FinalizeRequest(sec, pub, &ids, rec, export_key)) { rb_raise(rb_eRuntimeError, "create registration response failed"); } return rb_ary_new_from_args(2, rb_str_new(rec,sizeof rec), rb_str_new(export_key, sizeof export_key) ); } VALUE opaque_store_user_record(int argc, VALUE *argv, VALUE obj) { rb_check_arity(argc, 2, 2); char *sec; size_t sec_len; char *recU; size_t recU_len; extract_str(argv[0], &sec, &sec_len, "sec is not a string"); if(sec_len!=OPAQUE_REGISTER_SECRET_LEN) { rb_raise(rb_eTypeError, "sec is invalid"); } extract_str(argv[1], &recU, &recU_len, "rec is not a string"); if(recU_len!=OPAQUE_REGISTRATION_RECORD_LEN) { rb_raise(rb_eTypeError, "rec is invalid"); } uint8_t rec[OPAQUE_USER_RECORD_LEN]; opaque_StoreUserRecord(sec, recU, rec); return rb_str_new(rec,sizeof rec); } VALUE opaque_create_server_keys() { char pkS[crypto_scalarmult_BYTES]; char skS[crypto_scalarmult_SCALARBYTES]; randombytes(skS, crypto_scalarmult_SCALARBYTES); crypto_scalarmult_base(pkS, skS); return rb_ary_new_from_args(2, rb_str_new(pkS,sizeof pkS), rb_str_new(skS, sizeof skS) ); } void Init_opaque() { VALUE opaque = rb_define_module("Opaque"); rb_define_method(opaque, "register", opaque_register, -1); rb_define_method(opaque, "create_credential_request", opaque_create_credential_request, -1); rb_define_method(opaque, "create_credential_response", opaque_create_credential_response, -1); rb_define_method(opaque, "recover_credentials", opaque_recover_credentials, -1); rb_define_method(opaque, "user_auth", opaque_user_auth, -1); rb_define_method(opaque, "create_registration_request", opaque_create_registration_request, -1); rb_define_method(opaque, "create_registration_response", opaque_create_registration_response, -1); rb_define_method(opaque, "finalize_request", opaque_finalize_request, -1); rb_define_method(opaque, "store_user_record", opaque_store_user_record, -1); rb_define_method(opaque, "create_server_keys", opaque_create_server_keys, 0); } libopaque-0.99.3/ruby/test.rb000077500000000000000000000040321452546740600161010ustar00rootroot00000000000000#!/usr/bin/env ruby require './opaque.so' include Opaque idU = "idU" idS = "idS" pwd = "pwd" context = "context" # create a record "directly on the server" rec, export_key = register(pwd, idU, idS) # client initiates session secU, pub = create_credential_request(pwd) # server sets up session and responds with record resp, skS, secS = create_credential_response(pub, rec, idU, idS, context) # client recovers keys, sets up session skU, authU, export_keyU = recover_credentials(resp, secU, context, idU, idS) raise "fail" unless skS == skU raise "fail" unless export_key == export_keyU # server authenticates client explicitly raise "fail" unless user_auth(secS, authU) # start 4 step registration m, secU = create_registration_request(pwd) secS, pub = create_registration_response(m) rec, export_key = finalize_request(secU, pub, idU, idS) rec = store_user_record(secS, rec) secU, pub = create_credential_request(pwd) # server sets up session and responds with record resp, skS, secS = create_credential_response(pub, rec, idU, idS, context) # client recovers keys, sets up session skU, authU, export_keyU = recover_credentials(resp, secU, context, idU, idS) raise "fail" unless skS == skU raise "fail" unless export_key == export_keyU # server authenticates client explicitly raise "fail" unless user_auth(secS, authU) # start 4 step registration with 1k server setup _, skS = create_server_keys() m, secU = create_registration_request(pwd) secS, pub = create_registration_response(m, skS) rec, export_key = finalize_request(secU, pub, idU, idS) rec = store_user_record(secS, rec) secU, pub = create_credential_request(pwd) # server sets up session and responds with record resp, skS, secS = create_credential_response(pub, rec, idU, idS, context) # client recovers keys, sets up session skU, authU, export_keyU = recover_credentials(resp, secU, context, idU, idS) raise "fail" unless skS == skU raise "fail" unless export_key == export_keyU # server authenticates client explicitly raise "fail" unless user_auth(secS, authU) print "all ok\n" libopaque-0.99.3/sasl/000077500000000000000000000000001452546740600145545ustar00rootroot00000000000000libopaque-0.99.3/sasl/README.org000066400000000000000000000104471452546740600162300ustar00rootroot00000000000000* OPAQUE SASL bindings This directory contains an OPAQUE SASL mech. It works with cyrus-sasl, possibly also with libgsasl. dovecot-sasl is rumored to not be supported, due to dovecot allegedly implementing only 1 roundtrip - but this needs to be confirmed. ** SASL HTTP Authentication This OPAQUE SASL mech has been tested against Apache2 using this https://gitlab.com/arpa2/apachemod/-/tree/master/arpa2_sasl module and this python script as a client https://github.com/stef/libopaque/blob/master/sasl/http_sasl.py To make this work with apache, you need to add this to your /etc/apache2/httpd.conf #+BEGIN_EXAMPLE LoadModule arpa2_sasl_module /usr/lib/apache2/mod_arpa2_sasl.so #+END_EXAMPLE And something like this to a directory stanza: #+BEGIN_EXAMPLE AuthType SASL AuthName "SASL http auth" Require valid-user SaslRealm localhost SaslMechanisms OPAQUE SaslDbPath /etc/sasldb2 #+END_EXAMPLE The file /etc/sasldb2 can be populated with entries like this: #+BEGIN_EXAMPLE sudo saslpasswd2 -n -f /etc/sasldb2 -a http -c -u localhost $username #+END_EXAMPLE You should make sure that the apache daemon can read the file /etc/sasldb2 Additionally there is experimental support for SASL in NGINX: https://github.com/stef/ngx_http_auth_sasl_module There is also two firefox addons, which might or might not work: - a more recent backend in c++: https://gitlab.com/arpa2/http_sasl_client/ - an older version with the backend written in java: https://github.com/arpa2/http-sasl-plugin Both plugins are actually identical, and only the backend is different. I tried the plugin with my [[https://github.com/stef/libopaque/blob/master/sasl/websasl.py][own backend]] hacked in python, and it worked against the apache server requiring HTTP auth using SASL and OPAQUE as a mech. There is also a [[https://github.com/stef/libopaque/blob/master/sasl/mitmsasl.py][SASL plugin]] for [[https://mitmproxy.org/][mitmproxy]] which you can start using: #+BEGIN_EXAMPLE mitmdump -q -s ./mitmsasl.py #+END_EXAMPLE and then just configure any of your HTTP clients (curl,wget,chrome and derivates) to use this proxy to have HTTP SASL support, e.g.: #+BEGIN_EXAMPLE % curl -vvvx http://localhost:8080 http://localhost:8090/ * Trying 127.0.0.1:8080... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET http://localhost:8090/ HTTP/1.1 > Host: localhost:8090 > User-Agent: curl/7.82.0 > Accept: */* > Proxy-Connection: Keep-Alive > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Server: nginx/1.20.2 < Date: Wed, 01 Jun 2022 16:32:07 GMT < Content-Type: text/html < Content-Length: 45 < Last-Modified: Sat, 21 May 2022 20:03:00 GMT < Connection: keep-alive < WWW-Authenticate: SASL s2c="",s2s="000000000D904F33" < ETag: "62894574-2d" < X-SASL-SECURE: yes < X-REMOTE-USER: s@localhost < X-SASL-REALM: localhost < X-SASL-MECH: OPAQUE < Accept-Ranges: bytes <

It works!

* Connection #0 to host localhost left intact #+END_EXAMPLE Both the mitmproxy addon and the native backend depend on zenity, as well as the requests and the sasl python modules to be installed. Be aware that both these block while querying for your username and password. It should be noted, that with OPAQUE every HTTP request requires 2 extra authentication requests to be made. It is not very practical for humans to type in a password for every request, alternatively the password could be cached, which is something no one would ever want. Thus I'm considering a new post-opaque mechanism for doing 0 auth roundtrips for requests over HTTP, the server and the client then both have a shared secret after the first successful OPAQUE run and a counter, and each request from the client contains a #+BEGIN_EXAMPLE Authorization: hmac(shared_secret,counter++) #+END_EXAMPLE header. This is basically HOTP authentication. This eliminates replay attacks, also there is no need for caching or reusing the password. And I think this is a nice mechanism for a lot of the other SASL mechanisms that are not 0rtt. ** Future work No SSF has been implemented (although it would be easy, since libsodium is already a dependency of libopaque.) This means you want to wrap whatever you are running in TLS. However implementing an SSF would make a lot of sense, since then OPAQUE is really used in both of its benefits, authentication and establishment of a secure channel. libopaque-0.99.3/sasl/http_sasl.py000077500000000000000000000031041452546740600171300ustar00rootroot00000000000000#!/usr/bin/env python3 import requests, sys, binascii, sasl realm="localhost" url = "http://localhost:8090" pwdU = "asdf" user = "s" r = requests.get(url) if r.status_code != 401: print(f'"{url}" did not return 401') sys.exit(1) www_auth = r.headers.get("WWW-Authenticate") print(f"www-authenticate: {www_auth}") if not www_auth.startswith("SASL "): print("bad auth method in 2nd step of opaque sasl auth") sys.exit(1) client = sasl.Client() client.setAttr("username", user) client.setAttr("password", pwdU) client.init() ret, mech, response = client.start('OPAQUE') if not ret: raise Exception(client.getError()) c2s = binascii.b2a_base64(response, newline=False).decode('utf8') h = {"Authorization": f'SASL c2s="{c2s}",realm="{realm}",mech="OPAQUE"'} print(h) r = requests.get(url, headers=h) print("response status:", r.status_code) www_auth = r.headers.get("WWW-Authenticate") print(f"www-authenticate: {www_auth}") if not www_auth.startswith("SASL "): print("bad auth method in 2nd step of opaque sasl auth") sys.exit(1) fields = dict((x.strip() for x in kv.split('=')) for kv in www_auth[5:].split(',')) s2c = binascii.a2b_base64(fields['s2c']) s2s = fields['s2s'] ret, response = client.step(s2c) if not ret: raise Exception(client.getError()) c2s = binascii.b2a_base64(response, newline=False).decode('utf8') h = {"Authorization": f'SASL c2s="{c2s}",s2s={fields["s2s"]}'} print(h) r = requests.get(url, headers=h) print("response status:", r.status_code) www_auth = r.headers.get("WWW-Authenticate") print(f"www-authenticate: {www_auth}") print(r.text) libopaque-0.99.3/sasl/makefile000066400000000000000000000006051452546740600162550ustar00rootroot00000000000000CFLAGS=-Wall -O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fasynchronous-unwind-tables -fpic -fstack-clash-protection -fcf-protection=full -Werror=format-security -Werror=implicit-function-declaration -Wl,-z,defs -Wl,-z,relro -ftrapv -Wl,-z,noexecstack LDFLAGS=-flto -lopaque libopaque.so: utils.c opaque.c gcc -shared $(CFLAGS) -o $@ opaque.c $(LDFLAGS) clean: rm -f libopaque.so libopaque-0.99.3/sasl/mitmsasl.py000077500000000000000000000072221452546740600167650ustar00rootroot00000000000000#!/usr/bin/env python3 # # This file is part of libopaque. # # SPDX-FileCopyrightText: 2022, Marsiske Stefan # SPDX-License-Identifier: LGPL-3.0-or-later # # libopaque is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation either version 3 of the License, or # (at your option) any later version. # # libopaque is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License version 3 for more details. # # You should have received a copy of the GNU Lesser General Public License along # with this program; if not, If not, see . from mitmproxy.http import Headers import requests, sys, binascii, sasl, subprocess def getpwd(realm): args = f'--forms --add-entry="User name" --add-password=Password --text="{realm}"'.split(' ') proc=subprocess.Popen(['/usr/bin/zenity', *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() if proc.returncode != 0: raise ptr = out.find(b'|') if ptr == -1: raise return out[:ptr].decode('utf8'), out[ptr+1:-1].decode('utf8') class SASL: def response(self, flow): if flow.response.status_code != 401: #print(f'"{url}" did not return 401') return www_auth = flow.response.headers["www-authenticate"] #print(f"www-authenticate: {www_auth}") if not www_auth.startswith("SASL "): #print("bad auth method in 2nd step of opaque sasl auth") return fields = dict((x.strip() for x in kv.split('=')) for kv in www_auth[5:].split(',')) if 'realm' not in fields or 'mech' not in fields: #print("bad parameters in sasl auth header") return client = sasl.Client() user, pwd = getpwd(fields['realm']) client.setAttr("username", user) client.setAttr("password", pwd) client.init() ret, mech, response = client.start('OPAQUE') if not ret: raise Exception(client.getError()) c2s = binascii.b2a_base64(response, newline=False).decode('utf8') h = {"Authorization": f'SASL c2s="{c2s}",realm={fields["realm"]},mech="OPAQUE"'} #print("headers", h) #print("url", flow.request.url) while True: r = requests.get(flow.request.url, headers=h) #print("response status:", r.status_code) if r.status_code != 401: break www_auth = r.headers.get("WWW-Authenticate") #print(f"www-authenticate: {www_auth}") if not www_auth.startswith("SASL "): #print("bad auth method in 2nd step of opaque sasl auth") break fields = dict((x.strip() for x in kv.split('=')) for kv in www_auth[5:].split(',')) s2c = binascii.a2b_base64(fields['s2c']) s2s = fields['s2s'] ret, response = client.step(s2c) if not ret: raise Exception(client.getError()) c2s = binascii.b2a_base64(response, newline=False).decode('utf8') h = {"Authorization": f'SASL c2s="{c2s}",s2s={fields["s2s"]}'} #print(r.text) flow.response.headers = Headers(**{str(k): str(v) for k, v in r.headers.items()}) flow.response.status_code = r.status_code flow.response.reason = r.reason flow.response.raw_content = r.content addons = [ SASL() ] libopaque-0.99.3/sasl/opaque.c000066400000000000000000000576131452546740600162260ustar00rootroot00000000000000#include #include #include "utils.c" static const uint8_t OPAQUE_CONTEXT[]="SASL OPAQUE Mechanism"; static const size_t OPAQUE_CONTEXT_BYTES=sizeof OPAQUE_CONTEXT - 1; static int get_idu_ids(const char** ids, unsigned short *idu_len, const char* user_realm, const char *serverFQDN, const char *input) { const char *ptr; // search for @ separating user from realm for(ptr = input;*ptr && ptr >= input;ptr++) { if(*ptr != '@') continue; if(*(ptr+1)==0) break; // '@\0' // found @ and it is not followed by a \0 if(ptr-1-input > 65535) { return !SASL_OK; // username too big } if(ids) { *ids = ptr+1; } *idu_len=ptr-input; return SASL_OK; } if(ptr 65535) { return !SASL_OK; // username too big } *idu_len=ptr-input; if(ids) { if(user_realm && *user_realm) { *ids = user_realm; } else { *ids = serverFQDN; } if(*ids==NULL || strlen(*ids)>65535) { return !SASL_OK; } } return SASL_OK; } /* The main OPAQUE context */ typedef struct context { int state; char *authid; /* authentication id (server) */ char *userid; /* authorization id (server) */ uint8_t *client_sec; uint8_t *sk; uint8_t *authU; /* copy of utils from the params structures */ const sasl_utils_t *utils; /* per-step mem management */ char *out_buf; unsigned out_buf_len; } context_t; static int opaque_server_mech_new(void *glob_context __attribute__((unused)), sasl_server_params_t *params, const char *challenge __attribute__((unused)), unsigned challen __attribute__((unused)), void **conn_context) { context_t *ctx; /* holds state are in */ ctx = params->utils->malloc(sizeof(context_t)); if (ctx == NULL) { (params->utils)->seterror( (params->utils)->conn, 0, "Out of Memory in " __FILE__ " near line %d", __LINE__ ); return SASL_NOMEM; } memset(ctx, 0, sizeof(context_t)); ctx->state = 1; ctx->utils = params->utils; *conn_context = ctx; return SASL_OK; } /* * Dispose of a OPAQUE context (could be server or client) */ static void opaque_common_mech_dispose(void *conn_context, const sasl_utils_t *utils) { context_t *ctx = (context_t *) conn_context; if (!ctx) return; if (ctx->authid) utils->free(ctx->authid); if (ctx->userid) utils->free(ctx->userid); if (ctx->client_sec) utils->free(ctx->client_sec); if (ctx->sk) utils->free(ctx->sk); if (ctx->authU) utils->free(ctx->authU); utils->free(ctx); } static int opaque_setpass(void *glob_context __attribute__((unused)), sasl_server_params_t *sparams, const char *userstr, const char *pass, unsigned passlen, const char *oldpass __attribute__((unused)), unsigned oldpasslen __attribute__((unused)), unsigned flags) { int r; uint16_t idU_len; const char *realm = NULL; sasl_secret_t *sec = NULL; struct propctx *propctx = NULL; const char *store_request[] = { "cmusaslsecretOPAQUE", NULL }; /* Do we have a backend that can store properties? */ if (!sparams->utils->auxprop_store || sparams->utils->auxprop_store(NULL, NULL, NULL) != SASL_OK) { (sparams->utils)->seterror( (sparams->utils)->conn, 0, "OPAQUE: auxprop backend can't store properties"); return SASL_NOMECH; } r = get_idu_ids(&realm, &idU_len, sparams->user_realm, sparams->serverFQDN, userstr); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error parsing user"); return r; } char user[idU_len+1]; memcpy(user,userstr,idU_len); user[idU_len]=0; if ((flags & SASL_SET_DISABLE) || pass == NULL) { sec = NULL; } else { uint8_t rec[OPAQUE_USER_RECORD_LEN]; const Opaque_Ids ids={idU_len,(uint8_t*)userstr,strlen(realm),(uint8_t*)realm}; //fprintf(stderr,"idU: \"%s\"(%d), idS: \"%s\"(%d)\n", ids.idU, ids.idU_len, ids.idS, ids.idS_len); r = opaque_Register((uint8_t*)pass, passlen, NULL, &ids, rec, NULL); if(r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error registering with opaque"); goto end; } /* Put 'rec' into sasl_secret_t. * This will be base64 encoded, so make sure its big enough. */ const unsigned long alloclen = (sizeof(rec)/3 + 1) * 4 + 1; sec = sparams->utils->malloc(sizeof(sasl_secret_t)+alloclen); if (!sec) { r = SASL_NOMEM; goto end; } sec->len = 0; // clear len, as we are downcasting from unsigned long to unsigned int ↓ sparams->utils->encode64((char*)rec, sizeof(rec), (char *) sec->data, alloclen, (unsigned*) &sec->len); /* Clean everything up */ end: if (r) return r; } /* do the store */ propctx = sparams->utils->prop_new(0); if (!propctx) r = SASL_FAIL; if (!r) r = sparams->utils->prop_request(propctx, store_request); if (!r) r = sparams->utils->prop_set(propctx, "cmusaslsecretOPAQUE", (char *) (sec ? sec->data : NULL), (sec ? sec->len : 0)); if (!r) r = sparams->utils->auxprop_store(sparams->utils->conn, propctx, user); if (propctx) sparams->utils->prop_dispose(&propctx); if (r) { sparams->utils->seterror(sparams->utils->conn, 0, "Error putting OPAQUE secret"); goto cleanup; } sparams->utils->log(NULL, SASL_LOG_DEBUG, "Setpass for OPAQUE successful\n"); cleanup: if (sec) sparams->utils->free(sec); return r; } static int opaque_mech_avail(void *glob_context __attribute__((unused)), sasl_server_params_t *sparams __attribute__((unused)), void **conn_context __attribute__((unused))) { return SASL_OK; } static int opaque_server_mech_step1(context_t *ctx, sasl_server_params_t *params, const char *clientin, unsigned clientinlen, const char **serverout, unsigned *serveroutlen, sasl_out_params_t *oparams) { int result; char *realm = NULL; char *authid = NULL; char *user = NULL; const char *password_request[] = { "*cmusaslsecretOPAQUE", SASL_AUX_PASSWORD, NULL }; struct propval auxprop_values[3]; //fprintf(stderr, "opaque server step 1\n"); if(clientinlen < OPAQUE_USER_SESSION_PUBLIC_LEN+2) { SETERROR(params->utils, "Invalid client input in OPAQUE step 1"); //fprintf(stderr, "bad inputsize: %d\n", clientinlen); return SASL_BADPARAM; } /* Expect: * Credential Request * authentication identity \0 * authorization identity \0 */ authid = (char*) clientin + OPAQUE_USER_SESSION_PUBLIC_LEN; size_t authid_len, user_len; authid_len = strlen(authid); if(authid_len > 65535) { SETERROR(params->utils, "Authid too big in OPAQUE step 1"); return SASL_BADPARAM; } user = authid+authid_len+1; user_len = strlen(user); if(user_len > 65535) { SETERROR(params->utils, "User too big in OPAQUE step 1"); return SASL_BADPARAM; } if(user_len+authid_len+OPAQUE_USER_SESSION_PUBLIC_LEN+2 != clientinlen) { SETERROR(params->utils, "Params sizes do not add up to input size in OPAQUE step 1"); return SASL_BADPARAM; } //fprintf(stderr,"user(%ld): \"%s\"\n", user_len, user); //fprintf(stderr,"authid(%ld): \"%s\"\n", authid_len, authid); /* Get the realm */ result = _plug_parseuser(params->utils, &user, &realm, params->user_realm, params->serverFQDN, authid); if (result) { SETERROR(params->utils, "Error getting realm"); goto cleanup; } /* Get user secret */ result = params->utils->prop_request(params->propctx, password_request); if (result != SASL_OK) goto cleanup; /* this will trigger the getting of the aux properties */ result = params->canon_user(params->utils->conn, authid, 0, SASL_CU_AUTHID, oparams); if (result != SASL_OK) goto cleanup; result = params->canon_user(params->utils->conn, user, 0, SASL_CU_AUTHZID, oparams); if (result != SASL_OK) goto cleanup; result = params->utils->prop_getnames(params->propctx, password_request, auxprop_values); if (result < 0 || ((!auxprop_values[0].name || !auxprop_values[0].values))) { /* We didn't find this username */ SETERROR(params->utils, "no record in database"); result = params->transition ? SASL_TRANS : SASL_NOUSER; goto cleanup; } //if(auxprop_values[0].name) { // fprintf(stderr, "ap_v[0] name: %s size %d\n", auxprop_values[0].name, auxprop_values[0].valsize); //} uint8_t rec[OPAQUE_USER_RECORD_LEN+1]; // +1 because for some // utterly braindead reason // decode64 actually puts a // terminating 0 at the end // of the decoded buffer. unsigned outlen; result = params->utils->decode64(auxprop_values[0].values[0], auxprop_values[0].valsize, (char*)rec, sizeof(rec), &outlen); if(result) { goto cleanup; } if(outlen!=OPAQUE_USER_RECORD_LEN) { SETERROR(params->utils, "Invalid OPAQUE record size\n"); goto cleanup; } //fprintf(stderr,"user(%ld): \"%s\"\n", strlen(user), user); //fprintf(stderr,"realm(%ld): \"%s\"\n", strlen(realm), realm); const unsigned short realm_len = strlen(realm); const Opaque_Ids ids={strlen(user),(uint8_t*)user,realm_len,(uint8_t*)realm}; //fprintf(stderr,"idU: \"%s\"(%d), idS: \"%s\"(%d)\n", ids.idU, ids.idU_len, ids.idS, ids.idS_len); ctx->out_buf = params->utils->malloc(OPAQUE_SERVER_SESSION_LEN+realm_len+1); if (ctx->out_buf == NULL) { MEMERROR(params->utils); result = SASL_NOMEM; goto cleanup; } ctx->out_buf_len=OPAQUE_SERVER_SESSION_LEN+realm_len+1; memcpy(ctx->out_buf + OPAQUE_SERVER_SESSION_LEN, realm, realm_len+1); ctx->sk = params->utils->malloc(OPAQUE_SHARED_SECRETBYTES); if (ctx->sk == NULL) { MEMERROR(params->utils); result = SASL_NOMEM; goto cleanup; } ctx->authU = params->utils->malloc(crypto_auth_hmacsha512_BYTES); if (ctx->authU == NULL) { MEMERROR(params->utils); result = SASL_NOMEM; goto cleanup; } if(0!=opaque_CreateCredentialResponse((uint8_t*)clientin, rec, &ids, OPAQUE_CONTEXT, OPAQUE_CONTEXT_BYTES, (uint8_t*)ctx->out_buf, ctx->sk, ctx->authU)) { SETERROR(params->utils,"opaque_CreateCredentialResponse failed.\n"); goto cleanup; } *serverout = ctx->out_buf; *serveroutlen = ctx->out_buf_len; ctx->state = 2; result = SASL_CONTINUE; cleanup: if (realm) params->utils->free(realm); return result; } static int opaque_server_mech_step2(context_t *ctx, sasl_server_params_t *params, const char *clientin, unsigned clientinlen, const char **serverout __attribute__((unused)), unsigned *serveroutlen __attribute__((unused)), sasl_out_params_t *oparams) { if(clientinlen!=crypto_auth_hmacsha512_BYTES) { SETERROR(params->utils, "Invalid client input in OPAQUE step 2"); //fprintf(stderr, "bad inputsize: %d\n", clientinlen); return SASL_BADPARAM; } if(-1==opaque_UserAuth(ctx->authU, (const uint8_t*)clientin)) { return SASL_BADAUTH; } /* set oparams */ oparams->doneflag = 1; return SASL_OK; } static int opaque_server_mech_step(void *conn_context, sasl_server_params_t *sparams, const char *clientin, unsigned clientinlen, const char **serverout, unsigned *serveroutlen, sasl_out_params_t *oparams) { context_t *ctx = (context_t *) conn_context; if (!sparams || !serverout || !serveroutlen || !oparams) return SASL_BADPARAM; *serverout = NULL; *serveroutlen = 0; if (ctx == NULL) { return SASL_BADPROT; } sparams->utils->log(NULL, SASL_LOG_DEBUG, "OPAQUE server step %d\n", ctx->state); switch (ctx->state) { case 1: return opaque_server_mech_step1(ctx, sparams, clientin, clientinlen, serverout, serveroutlen, oparams); case 2: return opaque_server_mech_step2(ctx, sparams, clientin, clientinlen, serverout, serveroutlen, oparams); default: sparams->utils->seterror(sparams->utils->conn, 0, "Invalid OPAQUE server step %d", ctx->state); return SASL_FAIL; } return SASL_FAIL; /* should never get here */ } static sasl_server_plug_t opaque_server_plugins[] = { { "OPAQUE", /* mech_name */ 0, /* max_ssf */ SASL_SEC_NOPLAINTEXT | SASL_SEC_NOACTIVE | SASL_SEC_NODICTIONARY | SASL_SEC_FORWARD_SECRECY | SASL_SEC_NOANONYMOUS | SASL_SEC_MUTUAL_AUTH, /* security_flags */ SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_ALLOWS_PROXY, /* features */ NULL, /* glob_context */ &opaque_server_mech_new, /* mech_new */ &opaque_server_mech_step, /* mech_step */ &opaque_common_mech_dispose, /* mech_dispose */ NULL, /* mech_free */ &opaque_setpass, /* setpass */ NULL, /* user_query */ NULL, /* idle */ &opaque_mech_avail, /* mech avail */ NULL /* spare */ } }; int sasl_server_plug_init(const sasl_utils_t *utils, int maxversion, int *out_version, const sasl_server_plug_t **pluglist, int *plugcount, const char *plugname __attribute__((unused))) { if (maxversion < SASL_SERVER_PLUG_VERSION) { utils->seterror(utils->conn, 0, "OPAQUE version mismatch"); return SASL_BADVERS; } *out_version = SASL_SERVER_PLUG_VERSION; *pluglist = opaque_server_plugins; *plugcount = 1; return SASL_OK; } /////////////////////////// client stuff /////////////////////////// static int opaque_client_mech_new(void *glob_context __attribute__((unused)), sasl_client_params_t *params, void **conn_context) { context_t *ctx; /* holds state are in */ ctx = params->utils->malloc(sizeof(context_t)); if (ctx == NULL) { (params->utils)->seterror( (params->utils)->conn, 0, "Out of Memory in " __FILE__ " near line %d", __LINE__ ); return SASL_NOMEM; } memset(ctx, 0, sizeof(context_t)); ctx->state = 1; ctx->utils = params->utils; *conn_context = ctx; return SASL_OK; } static int opaque_client_mech_step1(context_t *ctx, sasl_client_params_t *params, const char *serverin __attribute__((unused)), unsigned serverinlen, sasl_interact_t **prompt_need, const char **clientout, unsigned *clientoutlen, sasl_out_params_t *oparams) { const char *authid = NULL, *userid = NULL; sasl_secret_t *password = NULL; unsigned int free_password = 0; /* set if we need to free password */ int auth_result = SASL_OK; int pass_result = SASL_OK; int user_result = SASL_OK; int result; if (serverinlen > 0) { params->utils->seterror(params->utils->conn, 0, "Invalid input to OPAQUE client 1st step\n"); return SASL_BADPROT; } /* try to get the authid */ if (oparams->authid==NULL) { auth_result = _plug_get_authid(params->utils, &authid, prompt_need); if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT)) return auth_result; } /* try to get the userid */ if (oparams->user == NULL) { user_result = _plug_get_userid(params->utils, &userid, prompt_need); if ((user_result != SASL_OK) && (user_result != SASL_INTERACT)) return user_result; } /* try to get the password */ if (password == NULL) { pass_result=_plug_get_password(params->utils, &password, &free_password, prompt_need); if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT)) return pass_result; } /* free prompts we got */ if (prompt_need && *prompt_need) { params->utils->free(*prompt_need); *prompt_need = NULL; } /* if there are prompts not filled in */ if ((auth_result == SASL_INTERACT) || (user_result == SASL_INTERACT) || (pass_result == SASL_INTERACT)) { /* make the prompt list */ result = _plug_make_prompts(params->utils, prompt_need, user_result == SASL_INTERACT ? "Please enter your authorization name" : NULL, NULL, auth_result == SASL_INTERACT ? "Please enter your authentication name" : NULL, NULL, pass_result == SASL_INTERACT ? "Please enter your password" : NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (result != SASL_OK) return result; return SASL_INTERACT; } if (!password) { PARAMERROR(params->utils); return SASL_BADPARAM; } //fprintf(stderr, "got password(%ld): %s\n",password->len, password->data); if (!userid || !*userid) { result = params->canon_user(params->utils->conn, authid, 0, SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); } else { result = params->canon_user(params->utils->conn, authid, 0, SASL_CU_AUTHID, oparams); if (result != SASL_OK) return result; result = params->canon_user(params->utils->conn, userid, 0, SASL_CU_AUTHZID, oparams); } if (result != SASL_OK) return result; //fprintf(stderr, "userid: \"%s\"\n", oparams->user); //fprintf(stderr, "authid: \"%s\"\n", oparams->authid); /* Send out: * * U - authentication identity * I - authorization identity * * { utf8(U) utf8(I) utf8(sid) os(cn) } */ ctx->client_sec = params->utils->malloc(OPAQUE_USER_SESSION_SECRET_LEN+password->len); if (ctx->client_sec == NULL) { MEMERROR(params->utils); return SASL_NOMEM; } *clientoutlen = (OPAQUE_USER_SESSION_PUBLIC_LEN + strlen(oparams->authid) + 1 + strlen(oparams->user) + 1); ctx->out_buf = params->utils->malloc(*clientoutlen); if (ctx->out_buf == NULL) { MEMERROR(params->utils); clientoutlen=0; return SASL_NOMEM; } ctx->out_buf_len=*clientoutlen; opaque_CreateCredentialRequest(password->data, password->len, ctx->client_sec, (uint8_t*)ctx->out_buf); char *ptr = ctx->out_buf + OPAQUE_USER_SESSION_PUBLIC_LEN; memcpy(ptr, oparams->authid, strlen(oparams->authid)); ptr+=strlen(oparams->authid); ptr++[0]=0; memcpy(ptr, oparams->user, strlen(oparams->user)); ptr+=strlen(oparams->user); ptr[0]=0; *clientout = ctx->out_buf; ctx->state = 2; result = SASL_CONTINUE; //cleanup: return result; } static int opaque_client_mech_step2(context_t *ctx, sasl_client_params_t *params, const char *serverin, unsigned serverinlen, sasl_interact_t **prompt_need __attribute__((unused)), const char **clientout, unsigned *clientoutlen, sasl_out_params_t *oparams) { int result; if(serverinlenutils,"Server Response has incorrect size\n"); return SASL_BADPARAM; } if(!ctx->client_sec) { SETERROR(params->utils,"Missing secret OPAQUE client context\n"); return SASL_FAIL; } const char* realm = serverin + OPAQUE_SERVER_SESSION_LEN; size_t realm_len=strlen(realm); if(realm_len > 65535) { SETERROR(params->utils, "Realm too big in OPAQUE step 2"); return SASL_BADPARAM; } result = _plug_buf_alloc(params->utils, &ctx->out_buf, &ctx->out_buf_len, crypto_auth_hmacsha512_BYTES); if(result) { MEMERROR(params->utils); return SASL_NOMEM; } uint16_t idU_len; result = get_idu_ids(NULL, &idU_len, NULL, NULL, oparams->user); if (result) { params->utils->seterror(params->utils->conn, 0, "Error parsing user"); return SASL_BADPARAM; } const Opaque_Ids ids={idU_len,(uint8_t*)oparams->user,realm_len,(uint8_t*)realm}; //fprintf(stderr,"idU: \"%s\"(%d), idS: \"%s\"(%d)\n", ids.idU, ids.idU_len, ids.idS, ids.idS_len); ctx->sk = params->utils->malloc(OPAQUE_SHARED_SECRETBYTES); if (ctx->sk == NULL) { MEMERROR(params->utils); result = SASL_NOMEM; goto cleanup; } result = opaque_RecoverCredentials((uint8_t*) serverin, ctx->client_sec, OPAQUE_CONTEXT, OPAQUE_CONTEXT_BYTES, &ids, ctx->sk, (uint8_t*)ctx->out_buf, NULL); if(result) { SETERROR(params->utils, "Failed to recover OPAQUE credentials\n"); result = SASL_BADAUTH; goto cleanup; } *clientout = ctx->out_buf; *clientoutlen = crypto_auth_hmacsha512_BYTES; ctx->state = 3; result = SASL_CONTINUE; cleanup: return result; } static int opaque_client_mech_step(void *conn_context, sasl_client_params_t *params, const char *serverin, unsigned serverinlen, sasl_interact_t **prompt_need, const char **clientout, unsigned *clientoutlen, sasl_out_params_t *oparams) { context_t *ctx = (context_t *) conn_context; params->utils->log(NULL, SASL_LOG_DEBUG, "OPAQUE client step %d\n", ctx->state); *clientout = NULL; *clientoutlen = 0; switch (ctx->state) { case 1: return opaque_client_mech_step1(ctx, params, serverin, serverinlen, prompt_need, clientout, clientoutlen, oparams); case 2: return opaque_client_mech_step2(ctx, params, serverin, serverinlen, prompt_need, clientout, clientoutlen, oparams); default: params->utils->log(NULL, SASL_LOG_ERR, "Invalid OPAQUE client step %d\n", ctx->state); return SASL_FAIL; } return SASL_FAIL; /* should never get here */ } static sasl_client_plug_t opaque_client_plugins[] = { { "OPAQUE", /* mech_name */ 0, /* max_ssf */ SASL_SEC_NOPLAINTEXT | SASL_SEC_NOANONYMOUS | SASL_SEC_NOACTIVE | SASL_SEC_NODICTIONARY | SASL_SEC_FORWARD_SECRECY | SASL_SEC_MUTUAL_AUTH, /* security_flags */ SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_ALLOWS_PROXY, /* features */ NULL, /* required_prompts */ NULL, /* glob_context */ &opaque_client_mech_new, /* mech_new */ &opaque_client_mech_step, /* mech_step */ &opaque_common_mech_dispose, /* mech_dispose */ NULL, /* mech_free */ NULL, /* idle */ NULL, /* spare */ NULL /* spare */ } }; int sasl_client_plug_init(const sasl_utils_t *utils __attribute__((unused)), int maxversion, int *out_version, const sasl_client_plug_t **pluglist, int *plugcount, const char *plugname __attribute__((unused))) { if (maxversion < SASL_CLIENT_PLUG_VERSION) { utils->seterror(utils->conn, 0, "OPAQUE version mismatch"); return SASL_BADVERS; } *out_version = SASL_CLIENT_PLUG_VERSION; *pluglist = opaque_client_plugins; *plugcount=1; return SASL_OK; } libopaque-0.99.3/sasl/utils.c000066400000000000000000000241151452546740600160630ustar00rootroot00000000000000#ifndef utils_c #define utils_c /* CMU libsasl * Tim Martin * Rob Earhart * Rob Siemborski */ /* * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * this file is extracted from cyrus-sasl/common/plugin_common.[ch] * no changes have been made to the fragments extracted */ #include #include #include #define SETERROR( utils, msg ) (utils)->seterror( (utils)->conn, 0, (msg) ) #ifndef MEMERROR #define MEMERROR( utils ) \ (utils)->seterror( (utils)->conn, 0, \ "Out of Memory in " __FILE__ " near line %d", __LINE__ ) #endif #ifndef PARAMERROR #define PARAMERROR( utils ) \ (utils)->seterror( (utils)->conn, 0, \ "Parameter Error in " __FILE__ " near line %d", __LINE__ ) #endif #define _plug_get_userid(utils, result, prompt_need) \ _plug_get_simple(utils, SASL_CB_USER, 0, result, prompt_need) #define _plug_get_authid(utils, result, prompt_need) \ _plug_get_simple(utils, SASL_CB_AUTHNAME, 1, result, prompt_need) /* copy a string */ static int _plug_strdup(const sasl_utils_t * utils, const char *in, char **out, int *outlen) { size_t len = 0; if(!utils || !in || !out) { if(utils) PARAMERROR(utils); return SASL_BADPARAM; } len = strlen(in); *out = utils->malloc(len + 1); if (!*out) { MEMERROR(utils); return SASL_NOMEM; } strcpy((char *) *out, in); if (outlen) *outlen = (int) len; return SASL_OK; } /* * Trys to find the prompt with the lookingfor id in the prompt list * Returns it if found. NULL otherwise */ static sasl_interact_t *_plug_find_prompt(sasl_interact_t **promptlist, unsigned int lookingfor) { sasl_interact_t *prompt; if (promptlist && *promptlist) { for (prompt = *promptlist; prompt->id != SASL_CB_LIST_END; ++prompt) { if (prompt->id==lookingfor) return prompt; } } return NULL; } static int _plug_parseuser(const sasl_utils_t *utils, char **user, char **realm, const char *user_realm, const char *serverFQDN, const char *input) { int ret; char *r; if(!user || !serverFQDN) { PARAMERROR( utils ); return SASL_BADPARAM; } r = strchr(input, '@'); if (!r) { /* hmmm, the user didn't specify a realm */ if(user_realm && user_realm[0]) { ret = _plug_strdup(utils, user_realm, realm, NULL); } else { /* Default to serverFQDN */ ret = _plug_strdup(utils, serverFQDN, realm, NULL); } if (ret == SASL_OK) { ret = _plug_strdup(utils, input, user, NULL); } } else { r++; ret = _plug_strdup(utils, r, realm, NULL); *--r = '\0'; *user = utils->malloc(r - input + 1); if (*user) { strncpy(*user, input, r - input +1); } else { MEMERROR( utils ); ret = SASL_NOMEM; } *r = '@'; } return ret; } /* * Retrieve the simple string given by the callback id. */ static int _plug_get_simple(const sasl_utils_t *utils, unsigned int id, int required, const char **result, sasl_interact_t **prompt_need) { int ret = SASL_FAIL; sasl_getsimple_t *simple_cb; void *simple_context; sasl_interact_t *prompt; *result = NULL; /* see if we were given the result in the prompt */ prompt = _plug_find_prompt(prompt_need, id); if (prompt != NULL) { /* We prompted, and got.*/ if (required && !prompt->result) { SETERROR(utils, "Unexpectedly missing a prompt result in _plug_get_simple"); return SASL_BADPARAM; } *result = prompt->result; return SASL_OK; } /* Try to get the callback... */ ret = utils->getcallback(utils->conn, id, (sasl_callback_ft *)&simple_cb, &simple_context); if (ret == SASL_FAIL && !required) return SASL_OK; if (ret == SASL_OK && simple_cb) { ret = simple_cb(simple_context, id, result, NULL); if (ret != SASL_OK) return ret; if (required && !*result) { PARAMERROR(utils); return SASL_BADPARAM; } } return ret; } static int _plug_get_password(const sasl_utils_t *utils, sasl_secret_t **password, unsigned int *iscopy, sasl_interact_t **prompt_need) { int ret = SASL_FAIL; sasl_getsecret_t *pass_cb; void *pass_context; sasl_interact_t *prompt; *password = NULL; *iscopy = 0; /* see if we were given the password in the prompt */ prompt = _plug_find_prompt(prompt_need, SASL_CB_PASS); if (prompt != NULL) { /* We prompted, and got.*/ if (!prompt->result) { SETERROR(utils, "Unexpectedly missing a prompt result in _plug_get_password"); return SASL_BADPARAM; } /* copy what we got into a secret_t */ *password = (sasl_secret_t *) utils->malloc(sizeof(sasl_secret_t) + prompt->len + 1); if (!*password) { MEMERROR(utils); return SASL_NOMEM; } (*password)->len=prompt->len; memcpy((*password)->data, prompt->result, prompt->len); (*password)->data[(*password)->len]=0; *iscopy = 1; return SASL_OK; } /* Try to get the callback... */ ret = utils->getcallback(utils->conn, SASL_CB_PASS, (sasl_callback_ft *)&pass_cb, &pass_context); if (ret == SASL_OK && pass_cb) { ret = pass_cb(utils->conn, pass_context, SASL_CB_PASS, password); if (ret != SASL_OK) return ret; if (!*password) { PARAMERROR(utils); return SASL_BADPARAM; } } return ret; } /* * Make the requested prompts. (prompt==NULL means we don't want it) */ static int _plug_make_prompts(const sasl_utils_t *utils, sasl_interact_t **prompts_res, const char *user_prompt, const char *user_def, const char *auth_prompt, const char *auth_def, const char *pass_prompt, const char *pass_def, const char *echo_chal, const char *echo_prompt, const char *echo_def, const char *realm_chal, const char *realm_prompt, const char *realm_def) { int num = 1; int alloc_size; sasl_interact_t *prompts; if (user_prompt) num++; if (auth_prompt) num++; if (pass_prompt) num++; if (echo_prompt) num++; if (realm_prompt) num++; if (num == 1) { SETERROR( utils, "make_prompts() called with no actual prompts" ); return SASL_FAIL; } alloc_size = sizeof(sasl_interact_t)*num; prompts = utils->malloc(alloc_size); if (!prompts) { MEMERROR( utils ); return SASL_NOMEM; } memset(prompts, 0, alloc_size); *prompts_res = prompts; if (user_prompt) { (prompts)->id = SASL_CB_USER; (prompts)->challenge = "Authorization Name"; (prompts)->prompt = user_prompt; (prompts)->defresult = user_def; prompts++; } if (auth_prompt) { (prompts)->id = SASL_CB_AUTHNAME; (prompts)->challenge = "Authentication Name"; (prompts)->prompt = auth_prompt; (prompts)->defresult = auth_def; prompts++; } if (pass_prompt) { (prompts)->id = SASL_CB_PASS; (prompts)->challenge = "Password"; (prompts)->prompt = pass_prompt; (prompts)->defresult = pass_def; prompts++; } if (echo_prompt) { (prompts)->id = SASL_CB_ECHOPROMPT; (prompts)->challenge = echo_chal; (prompts)->prompt = echo_prompt; (prompts)->defresult = echo_def; prompts++; } if (realm_prompt) { (prompts)->id = SASL_CB_GETREALM; (prompts)->challenge = realm_chal; (prompts)->prompt = realm_prompt; (prompts)->defresult = realm_def; prompts++; } /* add the ending one */ (prompts)->id = SASL_CB_LIST_END; (prompts)->challenge = NULL; (prompts)->prompt = NULL; (prompts)->defresult = NULL; return SASL_OK; } /* Basically a conditional call to realloc(), if we need more */ static int _plug_buf_alloc(const sasl_utils_t *utils, char **rwbuf, unsigned *curlen, unsigned newlen) { if(!utils || !rwbuf || !curlen) { if (utils) PARAMERROR(utils); return SASL_BADPARAM; } if(!(*rwbuf)) { *rwbuf = utils->malloc(newlen); if (*rwbuf == NULL) { *curlen = 0; MEMERROR(utils); return SASL_NOMEM; } *curlen = newlen; } else if(*rwbuf && *curlen < newlen) { unsigned needed = 2*(*curlen); while(needed < newlen) needed *= 2; *rwbuf = utils->realloc(*rwbuf, needed); if (*rwbuf == NULL) { *curlen = 0; MEMERROR(utils); return SASL_NOMEM; } *curlen = needed; } return SASL_OK; } #endif //utils_c libopaque-0.99.3/sasl/websasl.py000077500000000000000000000065571452546740600166060ustar00rootroot00000000000000#!/usr/bin/env python3 # # This file is part of libopaque. # # SPDX-FileCopyrightText: 2022, Marsiske Stefan # SPDX-License-Identifier: LGPL-3.0-or-later # # libopaque is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation either version 3 of the License, or # (at your option) any later version. # # libopaque is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License version 3 for more details. # # You should have received a copy of the GNU Lesser General Public License along # with this program; if not, If not, see . import subprocess, sasl, struct, json, binascii log = '/tmp/a' def getpwd(realm): args = f'--forms --add-entry="User name" --add-password=Password --text="{realm}"'.split(' ') proc=subprocess.Popen(['/usr/bin/zenity', *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() if proc.returncode != 0: raise ptr = out.find(b'|') if ptr == -1: raise return out[:ptr].decode('utf8'), out[ptr+1:-1].decode('utf8') # Send message using Native messaging protocol def send_message(data): msg = json.dumps(data).encode('utf-8') if log: log.write(msg) log.write(b'\n') log.flush() length = struct.pack('@I', len(msg)) sys.stdout.buffer.write(length) sys.stdout.buffer.write(msg) sys.stdout.buffer.flush() def main(): global log if log: log = open(log,'ab') clients = {} while True: # Read message using Native messaging protocol length_bytes = sys.stdin.buffer.read(4) if len(length_bytes) == 0: return length = struct.unpack('i', length_bytes)[0] data = json.loads(sys.stdin.buffer.read(length).decode('utf-8')) if log: log.write(repr(data).encode()) log.write(b'\n') log.flush() res = {"s2s": data.get("s2s",""), "mech": data.get("mech",""), "requestId": data.get("requestId",""), "extraInfoSpec": data.get("extraInfoSpec","")} if data.get("mech"): client = sasl.Client() user, pwd = getpwd(data['realm']) client.setAttr("username", user) client.setAttr("password", pwd) log.write(f'user: "{user}", password: "{pwd}\n"'.encode()) client.init() ret, mech, response = client.start('OPAQUE') if not ret: send_message({ 'error': client.getError().decode('utf8')}) raise Exception(client.getError()) res['c2s'] = binascii.b2a_base64(response, newline=False).decode('utf8') clients[data['requestId']]=client elif data['requestId'] in clients: client = clients[data['requestId']] s2c = binascii.a2b_base64(data['s2c']) if s2c: ret, response = client.step(s2c) if not ret: send_message({ 'error': client.getError().decode('utf8')}) raise Exception(client.getError()) res['c2s'] = binascii.b2a_base64(response, newline=False).decode('utf8') else: send_message({ 'results': 'fail' }) raise send_message(res) if __name__ == '__main__': main() libopaque-0.99.3/src/000077500000000000000000000000001452546740600144015ustar00rootroot00000000000000libopaque-0.99.3/src/aux_/000077500000000000000000000000001452546740600153355ustar00rootroot00000000000000libopaque-0.99.3/src/aux_/crypto_kdf_hkdf_sha512.h000066400000000000000000000025711452546740600217360ustar00rootroot00000000000000#ifndef crypto_kdf_hkdf_sha512_H #define crypto_kdf_hkdf_sha512_H #include #include #include #include #ifdef __cplusplus # ifdef __GNUC__ # pragma GCC diagnostic ignored "-Wlong-long" # endif extern "C" { #endif #define crypto_kdf_hkdf_sha512_KEYBYTES crypto_auth_hmacsha512_BYTES SODIUM_EXPORT size_t crypto_kdf_hkdf_sha512_keybytes(void); #define crypto_kdf_hkdf_sha512_BYTES_MIN 0U SODIUM_EXPORT size_t crypto_kdf_hkdf_sha512_bytes_min(void); #define crypto_kdf_hkdf_sha512_BYTES_MAX (0xff * crypto_auth_hmacsha512_BYTES) SODIUM_EXPORT size_t crypto_kdf_hkdf_sha512_bytes_max(void); SODIUM_EXPORT int crypto_kdf_hkdf_sha512_extract(unsigned char prk[crypto_kdf_hkdf_sha512_KEYBYTES], const unsigned char *salt, size_t salt_len, const unsigned char *ikm, size_t ikm_len) __attribute__ ((nonnull(1))); SODIUM_EXPORT void crypto_kdf_hkdf_sha512_keygen(unsigned char prk[crypto_kdf_hkdf_sha512_KEYBYTES]) __attribute__ ((nonnull)); SODIUM_EXPORT int crypto_kdf_hkdf_sha512_expand(unsigned char *out, size_t out_len, const char *ctx, size_t ctx_len, const unsigned char prk[crypto_kdf_hkdf_sha512_KEYBYTES]) __attribute__ ((nonnull(1))); #ifdef __cplusplus } #endif #endif libopaque-0.99.3/src/aux_/kdf_hkdf_sha512.c000066400000000000000000000060511452546740600203260ustar00rootroot00000000000000#include #include #include "sodium/crypto_auth_hmacsha512.h" #include "sodium/crypto_kdf.h" #include "crypto_kdf_hkdf_sha512.h" #include "sodium/randombytes.h" #include "sodium/utils.h" int crypto_kdf_hkdf_sha512_extract( unsigned char prk[crypto_kdf_hkdf_sha512_KEYBYTES], const unsigned char *salt, size_t salt_len, const unsigned char *ikm, size_t ikm_len) { crypto_auth_hmacsha512_state st; crypto_auth_hmacsha512_init(&st, salt, salt_len); crypto_auth_hmacsha512_update(&st, ikm, ikm_len); crypto_auth_hmacsha512_final(&st, prk); sodium_memzero(&st, sizeof st); return 0; } void crypto_kdf_hkdf_sha512_keygen(unsigned char prk[crypto_kdf_hkdf_sha512_KEYBYTES]) { randombytes_buf(prk, crypto_kdf_hkdf_sha512_KEYBYTES); } int crypto_kdf_hkdf_sha512_expand(unsigned char *out, size_t out_len, const char *ctx, size_t ctx_len, const unsigned char prk[crypto_kdf_hkdf_sha512_KEYBYTES]) { crypto_auth_hmacsha512_state st; unsigned char tmp[crypto_auth_hmacsha512_BYTES]; size_t i; size_t left; unsigned char counter = 1U; if (out_len > crypto_kdf_hkdf_sha512_BYTES_MAX) { errno = EINVAL; return -1; } for (i = (size_t) 0U; i + crypto_auth_hmacsha512_BYTES <= out_len; i += crypto_auth_hmacsha512_BYTES) { crypto_auth_hmacsha512_init(&st, prk, crypto_kdf_hkdf_sha512_KEYBYTES); if (i != (size_t) 0U) { crypto_auth_hmacsha512_update(&st, &out[i - crypto_auth_hmacsha512_BYTES], crypto_auth_hmacsha512_BYTES); } crypto_auth_hmacsha512_update(&st, (const unsigned char *) ctx, ctx_len); crypto_auth_hmacsha512_update(&st, &counter, (size_t) 1U); crypto_auth_hmacsha512_final(&st, &out[i]); counter++; } if ((left = out_len & (crypto_auth_hmacsha512_BYTES - 1U)) != (size_t) 0U) { crypto_auth_hmacsha512_init(&st, prk, crypto_kdf_hkdf_sha512_KEYBYTES); if (i != (size_t) 0U) { crypto_auth_hmacsha512_update(&st, &out[i - crypto_auth_hmacsha512_BYTES], crypto_auth_hmacsha512_BYTES); } crypto_auth_hmacsha512_update(&st, (const unsigned char *) ctx, ctx_len); crypto_auth_hmacsha512_update(&st, &counter, (size_t) 1U); crypto_auth_hmacsha512_final(&st, tmp); memcpy(&out[i], tmp, left); sodium_memzero(tmp, sizeof tmp); } sodium_memzero(&st, sizeof st); return 0; } size_t crypto_kdf_hkdf_sha512_keybytes(void) { return crypto_kdf_hkdf_sha512_KEYBYTES; } size_t crypto_kdf_hkdf_sha512_bytes_min(void) { return crypto_kdf_hkdf_sha512_BYTES_MIN; } size_t crypto_kdf_hkdf_sha512_bytes_max(void) { return crypto_kdf_hkdf_sha512_BYTES_MAX; } libopaque-0.99.3/src/common.c000066400000000000000000000046651452546740600160500ustar00rootroot00000000000000#include "common.h" #if (defined TRACE || defined CFRG_TEST_VEC) void dump(const uint8_t *p, const size_t len, const char* msg, ...) { va_list args; va_start(args, msg); vfprintf(stderr,msg, args); va_end(args); fprintf(stderr, " "); for(size_t i=0;i #include #include //#define TRACE 1 //#define NORANDOM 1 #ifdef CFRG_TEST_VEC #undef TRACE #undef NORANDOM #endif #if (defined TRACE || defined CFRG_TEST_VEC) #include #include void dump(const uint8_t *p, const size_t len, const char* msg, ...); #endif #ifdef NORANDOM void a_randombytes(void* const buf, const size_t len); void a_randomscalar(uint8_t* buf); #define crypto_core_ristretto255_scalar_random a_randomscalar #define randombytes a_randombytes #endif #ifdef __EMSCRIPTEN__ // Per // https://emscripten.org/docs/compiling/Building-Projects.html#detecting-emscripten-in-preprocessor, // "The preprocessor define __EMSCRIPTEN__ is always defined when compiling // programs with Emscripten". For why we are replacing sodium_m(un)?lock, see // common.c for more details. #include int opaque_mlock(void *const addr, const size_t len); int opaque_munlock(void *const addr, const size_t len); #define sodium_mlock opaque_mlock #define sodium_munlock opaque_munlock #endif #endif //COMMON_H libopaque-0.99.3/src/makefile000066400000000000000000000120641452546740600161040ustar00rootroot00000000000000PREFIX?=/usr/local LIBS=-lsodium -loprf DEFINES= CFLAGS?=-march=native -Wall -O2 -g -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fasynchronous-unwind-tables -fpic \ -fstack-clash-protection -fcf-protection=full -Werror=format-security -Werror=implicit-function-declaration \ -Warray-bounds -fsanitize=bounds -fsanitize-undefined-trap-on-error \ -Wl,-z,defs -Wl,-z,relro -ftrapv -Wl,-z,noexecstack -std=c99 $(DEFINES) #-fstrict-flex-arrays LDFLAGS=-g $(LIBS) CC=gcc SOEXT=so AEXT=a SOVER=0 AR?=ar SODIUM_NEWER_THAN_1_0_18 := $(shell pkgconf --atleast-version=1.0.19 libsodium; echo $$?) ifeq ($(SODIUM_NEWER_THAN_1_0_18),1) CFLAGS+= -Iaux_ EXTRA_OBJECTS+= aux_/kdf_hkdf_sha512.o else CFLAGS+= -DHAVE_SODIUM_HKDF=1 endif ifdef OPRFHOME OPRFINCDIR=$(OPRFHOME) LDFLAGS+= -L$(OPRFHOME) else OPRFINCDIR=/usr/include/oprf endif ifneq (, $(shell which pandoc)) MANPAGES=man MANPAGES-clean=man-clean MANPAGES-install=man-install MANPAGES-uninstall=man-uninstall endif all: libopaque.$(SOEXT) libopaque.$(AEXT) tests utils/opaque $(MANPAGES) debug: DEFINES=-DTRACE -DNORANDOM debug: all asan: DEFINES=-DTRACE -DNORANDOM asan: CFLAGS=-fsanitize=address -static-libasan -g -march=native -Wall -O2 -g -fstack-protector-strong -fpic -fstack-clash-protection -fcf-protection=full -Werror=format-security -Werror=implicit-function-declaration -Wl,-z,noexecstack $(DEFINES) asan: LDFLAGS+= -fsanitize=address -static-libasan asan: all mingw64: CC=x86_64-w64-mingw32-gcc mingw64: CFLAGS=-march=native -Wall -O2 -g -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fasynchronous-unwind-tables -fpic -fstack-clash-protection -fcf-protection=full -Werror=format-security -Werror=implicit-function-declaration -ftrapv $(DEFINES) mingw64: LIBS=-L. -lws2_32 -Lwin/libsodium-win64/lib/ -Wl,-Bstatic -lsodium -Wl,-Bdynamic mingw64: INC=-Iwin/libsodium-win64/include/sodium -Iwin/libsodium-win64/include mingw64: SOEXT=dll mingw64: EXT=.exe mingw64: MAKETARGET=mingw mingw64: win/libsodium-win64 libopaque.$(SOEXT) tests utils/opaque TESTS=tests/opaque-test$(EXT) tests/opaque-munit$(EXT) ifeq ($(shell test -e $(OPRFHOME)/oprf.c && echo -n yes),yes) TESTS+=tests/opaque-tv1$(EXT) endif tests: $(TESTS) libopaque.$(SOEXT): common.o opaque.o $(EXTRA_OBJECTS) $(CC) -shared $(CFLAGS) -Wl,-soname,libopaque.so.$(SOVER) -o libopaque.$(SOEXT) $^ $(LDFLAGS) ln -fs libopaque.$(SOEXT) libopaque.so.$(SOVER) libopaque.$(AEXT): common.o opaque.o $(EXTRA_OBJECTS) $(AR) -rcs libopaque.$(AEXT) $^ tests/opaque-test$(EXT): tests/opaque-test.c libopaque.$(SOEXT) $(CC) $(CFLAGS) -o tests/opaque-test$(EXT) tests/opaque-test.c -L. -lopaque $(LDFLAGS) tests/opaque-munit$(EXT): tests/opaque-munit.c libopaque.$(SOEXT) $(CC) $(CFLAGS) -o tests/opaque-munit$(EXT) tests/munit/munit.c tests/opaque-munit.c -L. -lopaque $(LDFLAGS) common-v.o: common.c $(CC) $(CFLAGS) -DCFRG_TEST_VEC -o $@ -c $< opaque-tv1.o: opaque.c $(CC) $(CFLAGS) -I$(OPRFHOME) -DCFRG_TEST_VEC -o $@ -c $< oprf-v.o: $(OPRFHOME)/oprf.c $(CC) -I. $(CFLAGS) -DCFRG_TEST_VEC -o $@ -c $< tests/opaque-tv1$(EXT): tests/opaque-testvectors.c common-v.o oprf-v.o opaque-tv1.o $(CC) $(CFLAGS) -DCFRG_TEST_VEC -o $@ $^ $(EXTRA_OBJECTS) -lsodium -g test: tests test -x ./tests/opaque-tv1$(EXT) && ./tests/opaque-tv1$(EXT) || true LD_LIBRARY_PATH=. ./tests/opaque-test$(EXT) LD_LIBRARY_PATH=. ./tests/opaque-munit$(EXT) --fatal-failures utils/opaque: utils/main.c libopaque.$(SOEXT) gcc $(CFLAGS) -I. -o utils/opaque utils/main.c -L. $(LDFLAGS) -lopaque -lsodium install: $(DESTDIR)$(PREFIX)/lib/libopaque.$(SOEXT) $(DESTDIR)$(PREFIX)/lib/libopaque.$(AEXT) $(DESTDIR)$(PREFIX)/include/opaque.h $(DESTDIR)$(PREFIX)/bin/opaque $(MANPAGES-install) uninstall: $(DESTDIR)$(PREFIX)/lib/libopaque.$(SOEXT) $(DESTDIR)$(PREFIX)/lib/libopaque.$(AEXT) $(DESTDIR)$(PREFIX)/include/opaque.h $(DESTDIR)$(PREFIX)/bin/opaque $(MANPAGES-uninstall) rm $^ man: make -C utils/man man-clean: make -C utils/man clean man-install: make -C utils/man install man-uninstall: make -C utils/man uninstall $(DESTDIR)$(PREFIX)/lib/libopaque.$(SOEXT): libopaque.$(SOEXT) install -D $< $@.$(SOVER) ln -fs $@.$(SOVER) $@ $(DESTDIR)$(PREFIX)/lib/libopaque.$(AEXT): libopaque.$(AEXT) install -D $< $@ $(DESTDIR)$(PREFIX)/include/opaque.h: opaque.h install -D $< $@ $(DESTDIR)$(PREFIX)/bin/opaque: utils/opaque install -D $< $@ opaque.o: opaque.c $(CC) $(CFLAGS) -I$(OPRFINCDIR) -o $@ -c $< %.o: %.c $(CC) $(CFLAGS) -o $@ -c $< win/libsodium-win64: @echo 'win/libsodium-win64 not found.' @echo 'download and unpack latest libsodium-*-mingw.tar.gz and unpack into win/' @echo 'https://download.libsodium.org/libsodium/releases/' @false clean: $(MANPAGES-clean) rm -f \ *.o \ aux_/*.o \ libopaque.dll \ libopaque.so \ libopaque.a \ tests/opaque-munit \ tests/opaque-munit.exe \ tests/opaque-munit.html \ tests/opaque-munit.js \ tests/opaque-test \ tests/opaque-test.exe \ tests/opaque-test.html \ tests/opaque-test.js \ tests/opaque-tv1 \ tests/opaque-tv1.exe \ tests/opaque-tv1.html \ tests/opaque-tv1.js \ utils/opaque .PHONY: all clean debug install test libopaque-0.99.3/src/opaque.c000066400000000000000000001445451452546740600160540ustar00rootroot00000000000000/* @copyright 2018-21, opaque@ctrlc.hu This file is part of libopaque. libopaque is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. libopaque is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with libopaque. If not, see . This file implements the Opaque protocol as specified by the IRTF CFRG */ #include "opaque.h" #include #if _WIN32 == 1 || _WIN64 == 1 #include #else #include #endif #include "common.h" #ifdef CFRG_TEST_VEC #include "tests/cfrg_test_vector_decl.h" #endif #ifndef HAVE_SODIUM_HKDF #include "aux_/crypto_kdf_hkdf_sha512.h" #endif #define VOPRF "OPRFV1" #define OPAQUE_RWDU_BYTES 64 #define OPAQUE_HANDSHAKE_SECRETBYTES 64 #define OPAQUE_NONCE_BYTES 32 #define OPAQUE_ENVELOPE_BYTES (OPAQUE_ENVELOPE_NONCEBYTES + crypto_auth_hmacsha512_BYTES) #define OPAQUE_HMAC_SHA512_BYTES 64 #define OPAQUE_HMAC_SHA512_KEYBYTES 64 #define __bounds(member) __attribute__((__element_count__(member))) typedef struct { uint8_t nonce[OPAQUE_ENVELOPE_NONCEBYTES]; uint8_t auth_tag[crypto_auth_hmacsha512_BYTES]; } __attribute((packed)) Opaque_Envelope; typedef struct { uint8_t client_public_key[crypto_scalarmult_BYTES]; uint8_t masking_key[crypto_hash_sha512_BYTES]; Opaque_Envelope envelope; } __attribute((packed)) Opaque_RegistrationRecord; // user specific record stored at server upon registration typedef struct { uint8_t kU[crypto_core_ristretto255_SCALARBYTES]; uint8_t skS[crypto_scalarmult_SCALARBYTES]; Opaque_RegistrationRecord recU; } __attribute((packed)) Opaque_UserRecord; typedef struct { uint8_t blinded[crypto_core_ristretto255_BYTES]; uint8_t nonceU[OPAQUE_NONCE_BYTES]; uint8_t X_u[crypto_scalarmult_BYTES]; } __attribute((packed)) Opaque_UserSession; typedef struct { uint8_t blind[crypto_core_ristretto255_SCALARBYTES]; uint8_t x_u[crypto_scalarmult_SCALARBYTES]; uint8_t nonceU[OPAQUE_NONCE_BYTES]; uint8_t blinded[crypto_core_ristretto255_BYTES]; uint8_t ke1[OPAQUE_USER_SESSION_PUBLIC_LEN]; uint16_t pwdU_len; uint8_t pwdU[] __bounds(pwdU_len); } __attribute((packed)) Opaque_UserSession_Secret; typedef struct { uint8_t Z[crypto_core_ristretto255_BYTES]; uint8_t masking_nonce[32]; uint8_t masked_response[crypto_scalarmult_BYTES+sizeof(Opaque_Envelope)]; uint8_t nonceS[OPAQUE_NONCE_BYTES]; uint8_t X_s[crypto_scalarmult_BYTES]; uint8_t auth[crypto_auth_hmacsha512_BYTES]; } __attribute((packed)) Opaque_ServerSession; typedef struct { uint8_t blind[crypto_core_ristretto255_SCALARBYTES]; uint16_t pwdU_len; uint8_t pwdU[] __bounds(pwdU_len); } Opaque_RegisterUserSec; typedef struct { uint8_t Z[crypto_core_ristretto255_BYTES]; uint8_t pkS[crypto_scalarmult_BYTES]; } __attribute((packed)) Opaque_RegisterSrvPub; typedef struct { uint8_t skS[crypto_scalarmult_SCALARBYTES]; uint8_t kU[crypto_core_ristretto255_SCALARBYTES]; } __attribute((packed)) Opaque_RegisterSrvSec; typedef struct { uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t km2[OPAQUE_HMAC_SHA512_KEYBYTES]; uint8_t km3[OPAQUE_HMAC_SHA512_KEYBYTES]; } __attribute((packed)) Opaque_Keys; // sodium defines an hmac with 32B as key, opaque as 64 static void opaque_hmacsha512(const uint8_t key[OPAQUE_HMAC_SHA512_KEYBYTES], const uint8_t *authenticated, const size_t auth_len, uint8_t mac[OPAQUE_HMAC_SHA512_BYTES]) { crypto_auth_hmacsha512_state st; crypto_auth_hmacsha512_init(&st, key, OPAQUE_HMAC_SHA512_KEYBYTES); crypto_auth_hmacsha512_update(&st, authenticated, auth_len); crypto_auth_hmacsha512_final(&st, mac); sodium_memzero(&st,sizeof st); } static int voprf_hash_to_scalar(const uint8_t *msg, const uint8_t msg_len, const uint8_t *dst, const uint8_t dst_len, uint8_t p[crypto_core_ristretto255_SCALARBYTES]) { //const uint8_t dst[] = "HashToScalar-"VOPRF"-\x00\x00\x01"; //const uint8_t dst_len = (sizeof dst) - 1; uint8_t uniform_bytes[crypto_core_ristretto255_HASHBYTES]={0}; if(0!=sodium_mlock(uniform_bytes,sizeof uniform_bytes)) { return -1; } if(0!=expand_message_xmd(msg, msg_len, dst, dst_len, crypto_core_ristretto255_HASHBYTES, uniform_bytes)) { sodium_munlock(uniform_bytes,sizeof uniform_bytes); return -1; } #if (defined TRACE || defined CFRG_TEST_VEC) dump(uniform_bytes, sizeof uniform_bytes, "uniform_bytes"); #endif crypto_core_ristretto255_scalar_reduce(p, uniform_bytes); sodium_munlock(uniform_bytes,sizeof uniform_bytes); #if (defined TRACE || defined CFRG_TEST_VEC) dump(p, crypto_core_ristretto255_BYTES, "hashed-to-scalar"); #endif return 0; } static int deriveKeyPair(const uint8_t *seed, const size_t seed_len, uint8_t skS[crypto_core_ristretto255_SCALARBYTES], uint8_t pkS[crypto_core_ristretto255_BYTES]) { const uint16_t info_len=33; const uint8_t info[]="OPAQUE-DeriveDiffieHellmanKeyPair"; const uint8_t ctx[] = "DeriveKeyPair"VOPRF"-\x00-ristretto255-SHA512"; uint8_t hashinput[seed_len + 2 + info_len + 1], *ptr= hashinput; memcpy(ptr,seed,seed_len); ptr+=seed_len; *((uint16_t*) ptr)=htons(info_len); ptr+=2; memcpy(ptr,info,info_len); ptr+=info_len; ptr[0]=0; memset(skS,0,crypto_core_ristretto255_SCALARBYTES); int i; while(1) { if(ptr[0]>16) return 1; // DeriveKeyPairError for(i=crypto_core_ristretto255_SCALARBYTES/sizeof(uint32_t);i>0;i--) { if((((uint32_t*)skS)[i-1])!=0) break; } if(i!=0) break; if(0!=voprf_hash_to_scalar(hashinput,sizeof hashinput, ctx, sizeof ctx -1,skS)) return -1; ptr[0]++; } // P_u := g^p_u crypto_scalarmult_ristretto255_base(pkS, skS); return 0; } static int prf(const uint8_t *pwdU, const uint16_t pwdU_len, const uint8_t kU[crypto_core_ristretto255_SCALARBYTES], uint8_t rwdU[OPAQUE_RWDU_BYTES]) { // F_k(pwd) = H(pwd, (H0(pwd))^k) for key k ∈ Z_q uint8_t H0[crypto_core_ristretto255_BYTES]; if(0!=sodium_mlock(H0,sizeof H0)) { return -1; } // sets α := (H^0(pw))^r if(0!=voprf_hash_to_group(pwdU, pwdU_len, H0)) return -1; #ifdef TRACE dump(H0,sizeof H0, "H0"); #endif // H0 ^ k uint8_t N[crypto_core_ristretto255_BYTES]; if(0!=sodium_mlock(N,sizeof N)) { sodium_munlock(H0,sizeof H0); return -1; } if (crypto_scalarmult_ristretto255(N, kU, H0) != 0) { sodium_munlock(H0,sizeof H0); sodium_munlock(N,sizeof N); return -1; } sodium_munlock(H0,sizeof H0); #ifdef TRACE dump(N, sizeof N, "N"); #endif // 2. rwdU = Finalize(pwdU, N, "OPAQUE01") if(0!=oprf_Finalize(pwdU, pwdU_len, N, rwdU)) { sodium_munlock(N,sizeof N); return -1; } sodium_munlock(N,sizeof N); return 0; } static void hkdf_expand_label(uint8_t* res, const uint8_t secret[crypto_kdf_hkdf_sha512_KEYBYTES], const char *label, const char transcript[crypto_hash_sha512_BYTES], const size_t len) { // construct a hkdf label // struct { // uint16 length = Length; // opaque label<8..255> = "OPAQUE-" + Label; // opaque context<0..255> = Context; // } HkdfLabel; const size_t llen = strlen((const char*) label); uint8_t hkdflabel[2+2+7/*"OPAQUE-"*/+llen+(transcript!=NULL?crypto_hash_sha512_BYTES:0)]; *((uint16_t*) hkdflabel)=htons(len); uint8_t *ptr=hkdflabel+2; *(ptr)=(7+llen); ptr+=1; memcpy(ptr,"OPAQUE-",7); ptr+=7; memcpy(ptr,label,llen); ptr+=llen; if(transcript!=NULL) { *(ptr)=crypto_hash_sha512_BYTES; ptr+=1; memcpy(ptr, transcript, crypto_hash_sha512_BYTES); } else { *(ptr)=0; } #if (defined TRACE || defined CFRG_TEST_VEC) dump(hkdflabel, sizeof(hkdflabel), "expanded label"); if(transcript!=NULL) dump((const uint8_t*) transcript,crypto_hash_sha512_BYTES, "transcript: "); #endif crypto_kdf_hkdf_sha512_expand(res, len, (const char*) hkdflabel, sizeof(hkdflabel), secret); } // derive keys according to irtf cfrg draft static int derive_keys(Opaque_Keys* keys, const uint8_t ikm[crypto_scalarmult_BYTES * 3], const char info[crypto_hash_sha512_BYTES]) { uint8_t prk[64]; if(-1==sodium_mlock(prk, sizeof prk)) return -1; #ifdef TRACE dump(ikm, crypto_scalarmult_BYTES*3, "ikm "); dump((uint8_t*) info, crypto_hash_sha512_BYTES, "info "); #endif // 1. prk = HKDF-Extract(salt=0, IKM) crypto_kdf_hkdf_sha512_extract(prk, NULL, 0, ikm, crypto_scalarmult_BYTES*3); #if (defined TRACE || defined CFRG_TEST_VEC) dump(prk, sizeof prk, "prk"); #endif // 2. handshake_secret = Derive-Secret(., "handshake secret", info) uint8_t handshake_secret[OPAQUE_HANDSHAKE_SECRETBYTES]; if(-1==sodium_mlock(handshake_secret, sizeof handshake_secret)) { sodium_munlock(prk,sizeof(prk)); return -1; } const char handshake_secret_label[]="HandshakeSecret"; hkdf_expand_label(handshake_secret, prk, handshake_secret_label, info, sizeof(handshake_secret)); // 3. keys->sk = Derive-Secret(., "session secret", info) const char session_key_label[]="SessionKey"; hkdf_expand_label(keys->sk, prk, session_key_label, info, OPAQUE_SHARED_SECRETBYTES); sodium_munlock(prk,sizeof(prk)); // 4. Km2 = Derive-Secret(handshake_secret, "ServerMAC", "") //Km2 = HKDF-Expand-Label(handshake_secret, "server mac", "", Hash.length) const char server_mac_label[]="ServerMAC"; hkdf_expand_label(keys->km2, handshake_secret, server_mac_label, NULL, OPAQUE_HMAC_SHA512_KEYBYTES); // 5. Km3 = Derive-Secret(handshake_secret, "ClientMAC", "") //Km3 = HKDF-Expand-Label(handshake_secret, "client mac", "", Hash.length) const char client_mac_label[]="ClientMAC"; hkdf_expand_label(keys->km3, handshake_secret, client_mac_label, NULL, OPAQUE_HMAC_SHA512_KEYBYTES); sodium_munlock(handshake_secret, sizeof handshake_secret); #ifdef TRACE dump(keys->sk, OPAQUE_SHARED_SECRETBYTES, "keys->sk"); dump(keys->km2, OPAQUE_HMAC_SHA512_KEYBYTES, "keys->km2"); dump(keys->km3, OPAQUE_HMAC_SHA512_KEYBYTES, "keys->km3"); #endif return 0; } /** if one of the peers ID is missing, set it to the peers public key */ static void fix_ids(const uint8_t pkU[crypto_scalarmult_BYTES], const uint8_t pkS[crypto_scalarmult_BYTES], const Opaque_Ids *ids_in, Opaque_Ids *ids_out) { if(ids_in->idS==NULL || ids_in->idS_len==0) { ids_out->idS=(uint8_t*)pkS; ids_out->idS_len=crypto_scalarmult_BYTES; } else { ids_out->idS=ids_in->idS; ids_out->idS_len=ids_in->idS_len; } if(ids_in->idU==NULL || ids_in->idU_len==0) { ids_out->idU=(uint8_t*)pkU; ids_out->idU_len=crypto_scalarmult_BYTES; } else { ids_out->idU=ids_in->idU; ids_out->idU_len=ids_in->idU_len; } } static void calc_preamble(char preamble[crypto_hash_sha512_BYTES], crypto_hash_sha512_state *state, const uint8_t pkU[crypto_scalarmult_BYTES], const uint8_t pkS[crypto_scalarmult_BYTES], const uint8_t ke1[OPAQUE_USER_SESSION_PUBLIC_LEN], const Opaque_ServerSession *ke2, const uint8_t *ctx, const uint16_t ctx_len, const Opaque_Ids *ids0) { crypto_hash_sha512_init(state); Opaque_Ids ids; fix_ids(pkU, pkS, ids0, &ids); #ifdef TRACE fprintf(stderr,"calc preamble\n"); dump(ids.idU, ids.idU_len,"idU "); dump(ids.idS, ids.idS_len,"idS "); dump(pkU, crypto_scalarmult_BYTES, "pkU"); dump(pkS,crypto_scalarmult_BYTES, "pkS"); dump(ke1, OPAQUE_USER_SESSION_PUBLIC_LEN, "ke1"); dump(ctx, ctx_len, "ctx"); dump((uint8_t*)ke2, /* credential_response */ /*Z*/ crypto_core_ristretto255_BYTES + /*masking_nonce*/ 32+ /*masked_response*/ crypto_scalarmult_BYTES+sizeof(Opaque_Envelope)+ /*nonceS*/OPAQUE_NONCE_BYTES+ /*X_s*/crypto_scalarmult_BYTES, "ke2"); #endif //1. preamble = hash("OPAQUEv1-", // note the spec it self does not say hash here, but // https://github.com/cfrg/draft-irtf-cfrg-opaque/pull/147 // and later uses all hash this value const uint8_t rfc[]="OPAQUEv1-"; const uint8_t rfc_len=sizeof rfc -1; crypto_hash_sha512_update(state, rfc, rfc_len); // I2OSP(len(context), 2), context, uint16_t len = htons(ctx_len); crypto_hash_sha512_update(state, (uint8_t*) &len, 2); crypto_hash_sha512_update(state, ctx, ctx_len); // I2OSP(len(client_identity), 2), client_identity, len = htons(ids.idU_len); crypto_hash_sha512_update(state, (uint8_t*) &len, 2); crypto_hash_sha512_update(state, ids.idU, ids.idU_len); // ke1, crypto_hash_sha512_update(state, ke1, OPAQUE_USER_SESSION_PUBLIC_LEN); // I2OSP(len(server_identity), 2), server_identity, len = htons(ids.idS_len); crypto_hash_sha512_update(state, (uint8_t*) &len, 2); crypto_hash_sha512_update(state, ids.idS, ids.idS_len); // ke2.credential_response, // ke2.AuthResponse.server_nonce, ke2.AuthResponse.server_keyshare) // see type Opaque_ServerSession crypto_hash_sha512_update(state, (uint8_t*)ke2, /* credential_response */ /*Z*/ crypto_core_ristretto255_BYTES + /*masking_nonce*/ 32+ /*masked_response*/ crypto_scalarmult_BYTES+sizeof(Opaque_Envelope)+ /*nonceS*/OPAQUE_NONCE_BYTES+ /*X_s*/crypto_scalarmult_BYTES); // We need to copy the state here, because the caller of this function // may re-use it later. After calling the `final` function below, // the passed-in state must not be used again. crypto_hash_sha512_state copied_state; memcpy(&copied_state, state, sizeof(crypto_hash_sha512_state)); crypto_hash_sha512_final(&copied_state, (uint8_t *) preamble); } // implements server end of triple-dh static int server_3dh(Opaque_Keys *keys, const uint8_t ix[crypto_scalarmult_SCALARBYTES], const uint8_t ex[crypto_scalarmult_SCALARBYTES], const uint8_t Ip[crypto_scalarmult_BYTES], const uint8_t Ep[crypto_scalarmult_BYTES], const char preamble[crypto_hash_sha512_BYTES]) { uint8_t sec[crypto_scalarmult_BYTES * 3], *ptr = sec; if(-1==sodium_mlock(sec, sizeof sec)) { return -1; } #if (defined TRACE || defined CFRG_TEST_VEC) dump(ix, crypto_scalarmult_SCALARBYTES, "skS"); dump(ex, crypto_scalarmult_SCALARBYTES, "ekS"); dump(Ip, crypto_scalarmult_BYTES, "pkU"); dump(Ep, crypto_scalarmult_BYTES, "epkU"); #endif if(0!=crypto_scalarmult_ristretto255(ptr,ex,Ep)) return 1; ptr+=crypto_scalarmult_BYTES; if(0!=crypto_scalarmult_ristretto255(ptr,ix,Ep)) return 1; ptr+=crypto_scalarmult_BYTES; if(0!=crypto_scalarmult_ristretto255(ptr,ex,Ip)) return 1; #if (defined TRACE || defined CFRG_TEST_VEC) dump(sec, 96, "3dh s ikm"); #endif if(0!=derive_keys(keys, sec, preamble)) { sodium_munlock(sec,sizeof(sec)); return -1; } sodium_munlock(sec,sizeof(sec)); #if (defined TRACE || defined CFRG_TEST_VEC) dump((uint8_t*) keys, sizeof(Opaque_Keys), "keys "); #endif return 0; } // implements user end of triple-dh static int user_3dh(Opaque_Keys *keys, const uint8_t ix[crypto_scalarmult_SCALARBYTES], const uint8_t ex[crypto_scalarmult_SCALARBYTES], const uint8_t Ip[crypto_scalarmult_BYTES], const uint8_t Ep[crypto_scalarmult_BYTES], const char preamble[crypto_hash_sha512_BYTES]) { uint8_t sec[crypto_scalarmult_BYTES * 3], *ptr = sec; if(-1==sodium_mlock(sec, sizeof sec)) { return -1; } if(0!=crypto_scalarmult_ristretto255(ptr,ex,Ep)) return 1; ptr+=crypto_scalarmult_BYTES; if(0!=crypto_scalarmult_ristretto255(ptr,ex,Ip)) return 1; ptr+=crypto_scalarmult_BYTES; if(0!=crypto_scalarmult_ristretto255(ptr,ix,Ep)) return 1; #if (defined TRACE || defined CFRG_TEST_VEC) dump(sec, 96, "3dh u ikm"); #endif // and hash for the result SK = f_K(0) if(0!=derive_keys(keys, sec, preamble)) { sodium_munlock(sec,sizeof(sec)); return -1; } sodium_munlock(sec,sizeof(sec)); #if (defined TRACE || defined CFRG_TEST_VEC) dump((uint8_t*) keys, sizeof(Opaque_Keys), "keys "); #endif return 0; } static int skU_from_rwd(const uint8_t rwd[OPAQUE_RWDU_BYTES], const uint8_t nonce[OPAQUE_NONCE_BYTES], uint8_t skU[crypto_scalarmult_BYTES]) { char info[OPAQUE_NONCE_BYTES+10]; memcpy(info, nonce, OPAQUE_NONCE_BYTES); memcpy(info+OPAQUE_NONCE_BYTES, "PrivateKey", 10); uint8_t seed[crypto_core_ristretto255_SCALARBYTES]; if(-1==sodium_mlock(seed, sizeof seed)) { return -1; } crypto_kdf_hkdf_sha512_expand(seed, crypto_core_ristretto255_SCALARBYTES, info, sizeof info, rwd); uint8_t dst[24]="OPAQUE-DeriveAuthKeyPair"; if(0!=voprf_hash_to_scalar(seed, sizeof seed, dst, sizeof dst, skU)) { sodium_munlock(seed, sizeof seed); return -1; } sodium_munlock(seed, sizeof seed); return 0; } static int create_envelope(const uint8_t rwdU[OPAQUE_RWDU_BYTES], const uint8_t server_public_key[crypto_scalarmult_BYTES], const Opaque_Ids *ids, Opaque_Envelope *env, uint8_t client_public_key[crypto_scalarmult_BYTES], uint8_t masking_key[crypto_hash_sha512_BYTES], uint8_t export_key[crypto_hash_sha512_BYTES]) { // 1. envelope_nonce = random(Nn) #ifdef CFRG_TEST_VEC memcpy(env->nonce, envelope_nonce, envelope_nonce_len); #else randombytes(env->nonce, OPAQUE_ENVELOPE_NONCEBYTES); #endif uint8_t concated[OPAQUE_ENVELOPE_NONCEBYTES+10], *label = concated+OPAQUE_ENVELOPE_NONCEBYTES; memcpy(concated, env->nonce, OPAQUE_ENVELOPE_NONCEBYTES); // 2. masking_key = HKDF-Expand(randomized_pwd, "MaskingKey", Nh) const uint8_t masking_key_info[10]="MaskingKey"; crypto_kdf_hkdf_sha512_expand(masking_key, crypto_hash_sha512_BYTES, (const char*) masking_key_info, sizeof masking_key_info, rwdU); #if (defined CFRG_TEST_VEC || defined TRACE) dump(masking_key_info, sizeof masking_key_info, "masking_key_info"); dump(rwdU, OPAQUE_RWDU_BYTES, "rwdU"); dump(masking_key, crypto_hash_sha512_BYTES, "masking_key"); #endif // 3. auth_key = HKDF-Expand(randomized_pwd, concat(envelope_nonce, "AuthKey"), Nh) uint8_t auth_key[OPAQUE_HMAC_SHA512_KEYBYTES]; if(-1==sodium_mlock(auth_key, sizeof auth_key)) { return -1; } memcpy(label, "AuthKey", 7); crypto_kdf_hkdf_sha512_expand(auth_key, sizeof auth_key, (const char*) concated, OPAQUE_ENVELOPE_NONCEBYTES+7, rwdU); #if (defined CFRG_TEST_VEC || defined TRACE) dump(auth_key,sizeof auth_key, "auth_key "); #endif // 4. export_key = HKDF-Expand(randomized_pwd, concat(envelope_nonce, "ExportKey"), Nh) if(NULL!=export_key) { memcpy(label, "ExportKey", 9); #if (defined CFRG_TEST_VEC || defined TRACE) dump(concated, OPAQUE_ENVELOPE_NONCEBYTES+9, "export_key_info"); #endif crypto_kdf_hkdf_sha512_expand(export_key, crypto_hash_sha512_BYTES, (const char*) concated, OPAQUE_ENVELOPE_NONCEBYTES+9, rwdU); #if (defined CFRG_TEST_VEC || defined TRACE) dump(export_key,crypto_hash_sha512_BYTES, "export_key "); #endif } // 5. seed = Expand(randomized_pwd, concat(envelope_nonce, "PrivateKey"), Nseed) memcpy(label, "PrivateKey", 10); uint8_t seed[crypto_core_ristretto255_SCALARBYTES]; if(-1==sodium_mlock(seed, sizeof seed)) { sodium_munlock(auth_key, sizeof auth_key); return -1; } crypto_kdf_hkdf_sha512_expand(seed, crypto_core_ristretto255_SCALARBYTES, (const char*) concated, OPAQUE_ENVELOPE_NONCEBYTES+10, rwdU); // 6. _, client_public_key = DeriveAuthKeyPair(seed) uint8_t client_secret_key[crypto_scalarmult_SCALARBYTES]; if(-1==sodium_mlock(client_secret_key, sizeof client_secret_key)) { sodium_munlock(auth_key, sizeof auth_key); return -1; } if(0!=deriveKeyPair(seed, sizeof seed, client_secret_key, client_public_key)) { sodium_munlock(seed, sizeof seed); sodium_munlock(client_secret_key, sizeof client_secret_key); sodium_munlock(auth_key, sizeof auth_key); return -1; } sodium_munlock(seed, sizeof seed); #if (defined CFRG_TEST_VEC || defined TRACE) dump(client_secret_key, crypto_scalarmult_SCALARBYTES, "client_secret_key"); #endif sodium_munlock(client_secret_key, sizeof client_secret_key); #if (defined CFRG_TEST_VEC || defined TRACE) dump(client_public_key, crypto_scalarmult_BYTES, "client_public_key"); #endif // complete ids in case they are NULL and need to be set to the pk[US] Opaque_Ids ids_completed; fix_ids(client_public_key, server_public_key, ids, &ids_completed); uint8_t authenticated[OPAQUE_NONCE_BYTES+ crypto_scalarmult_BYTES+ ids_completed.idS_len+2+ ids_completed.idU_len+2], *ptr=authenticated; // nonce memcpy(ptr, env->nonce, OPAQUE_NONCE_BYTES); ptr+=OPAQUE_NONCE_BYTES; // server_public_key memcpy(ptr, server_public_key, crypto_scalarmult_BYTES); ptr+=crypto_scalarmult_BYTES; // server_identity uint16_t size = htons(ids_completed.idS_len); memcpy(ptr,(uint8_t*) &size, 2); ptr+=2; memcpy(ptr,ids_completed.idS,ids_completed.idS_len); ptr+=ids_completed.idS_len; // client_identity size = htons(ids_completed.idU_len); memcpy(ptr,(uint8_t*) &size, 2); ptr+=2; memcpy(ptr,ids_completed.idU,ids_completed.idU_len); opaque_hmacsha512(auth_key, // key authenticated, // in sizeof authenticated, // len(in) env->auth_tag); // out #if (defined CFRG_TEST_VEC || defined TRACE) dump(authenticated, sizeof authenticated, "authenticated"); dump(auth_key, sizeof auth_key, "auth_key"); dump(env->auth_tag, crypto_auth_hmacsha512_BYTES, "auth_tag"); #endif sodium_munlock(auth_key, sizeof auth_key); #if (defined CFRG_TEST_VEC || defined TRACE) dump((uint8_t *)env, OPAQUE_ENVELOPE_BYTES, "envU"); #endif return 0; } // (StorePwdFile, sid , U, pw): S computes k_s ←_R Z_q , rw := F_k_s (pw), // p_s ←_R Z_q , p_u ←_R Z_q , P_s := g^p_s , P_u := g^p_u , c ← AuthEnc_rw (p_u, P_u, P_s); // it records file[sid] := {k_s, p_s, P_s, P_u, c}. int opaque_Register(const uint8_t *pwdU, const uint16_t pwdU_len, const uint8_t skS[crypto_scalarmult_SCALARBYTES], const Opaque_Ids *ids, uint8_t _rec[OPAQUE_USER_RECORD_LEN], uint8_t export_key[crypto_hash_sha512_BYTES]) { Opaque_UserRecord *rec = (Opaque_UserRecord *)_rec; #ifdef TRACE dump(ids->idU, ids->idU_len,"idU "); dump(ids->idS, ids->idS_len,"idS "); #endif // k_s ←_R Z_q // 1. (kU, _) = KeyGen() oprf_KeyGen(rec->kU); // rw := F_k_s (pw), uint8_t rwdU[OPAQUE_RWDU_BYTES]; if(-1==sodium_mlock(rwdU,sizeof rwdU)) { return -1; } if(prf(pwdU, pwdU_len, rec->kU, rwdU)!=0) { sodium_munlock(rwdU,sizeof rwdU); return -1; } #ifdef TRACE dump(rwdU, sizeof rwdU, "rwdU"); #endif // p_s ←_R Z_q if(skS==NULL) { randombytes(rec->skS, crypto_scalarmult_SCALARBYTES); // random server secret key } else { memcpy(rec->skS, skS, crypto_scalarmult_SCALARBYTES); } // P_s := g^p_s uint8_t server_public_key[crypto_scalarmult_BYTES]; crypto_scalarmult_ristretto255_base(server_public_key, rec->skS); uint8_t client_private_key[crypto_scalarmult_SCALARBYTES]; if(-1==sodium_mlock(client_private_key,sizeof client_private_key)) { sodium_munlock(rwdU,sizeof rwdU); return -1; } if(0!=skU_from_rwd(rwdU, (uint8_t*) &rec->recU.envelope, client_private_key)) { sodium_munlock(client_private_key,sizeof client_private_key); sodium_munlock(rwdU, sizeof rwdU); return -1; } // P_u := g^p_u crypto_scalarmult_base(rec->recU.client_public_key, client_private_key); sodium_munlock(client_private_key,sizeof client_private_key); if(0!=create_envelope(rwdU, server_public_key, ids, &rec->recU.envelope, rec->recU.client_public_key, rec->recU.masking_key, export_key)) { sodium_munlock(rwdU, sizeof rwdU); return -1; } sodium_munlock(rwdU, sizeof rwdU); #ifdef TRACE dump(_rec, OPAQUE_USER_RECORD_LEN, "user rec"); #endif return 0; } //(UsrSession, sid , ssid , S, pw): U picks r, x_u ←_R Z_q ; sets α := (H^0(pw))^r and //X_u := g^x_u ; sends α and X_u to S. // more or less corresponds to CreateCredentialRequest in the irtf draft int opaque_CreateCredentialRequest(const uint8_t *pwdU, const uint16_t pwdU_len, uint8_t _sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], uint8_t _pub[OPAQUE_USER_SESSION_PUBLIC_LEN]) { Opaque_UserSession_Secret *sec = (Opaque_UserSession_Secret*) _sec; Opaque_UserSession *pub = (Opaque_UserSession*) _pub; #ifdef TRACE memset(_sec, 0, OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len); memset(_pub, 0, OPAQUE_USER_SESSION_PUBLIC_LEN); #endif // 1. (blind, blinded) = Blind(pwdU) if(0!=oprf_Blind(pwdU, pwdU_len, sec->blind, pub->blinded)) return -1; #ifdef TRACE dump(_sec,OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len, "sec "); dump(_pub,OPAQUE_USER_SESSION_PUBLIC_LEN, "pub "); #endif memcpy(sec->blinded, pub->blinded, crypto_core_ristretto255_BYTES); // x_u ←_R Z_q #ifndef CFRG_TEST_VEC const size_t client_keyshare_seed_len=32; uint8_t client_keyshare_seed[client_keyshare_seed_len]; randombytes(client_keyshare_seed, crypto_scalarmult_SCALARBYTES); #endif // nonceU #ifdef CFRG_TEST_VEC memcpy(sec->nonceU, client_nonce, OPAQUE_NONCE_BYTES); #else randombytes(sec->nonceU, OPAQUE_NONCE_BYTES); #endif memcpy(pub->nonceU, sec->nonceU, OPAQUE_NONCE_BYTES); deriveKeyPair(client_keyshare_seed, client_keyshare_seed_len, sec->x_u, pub->X_u); sec->pwdU_len = pwdU_len; memcpy(sec->pwdU, pwdU, pwdU_len); // keep ke1 for later memcpy(sec->ke1, _pub, OPAQUE_USER_SESSION_PUBLIC_LEN); #ifdef TRACE dump(_sec,OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len, "sec "); dump(_pub,OPAQUE_USER_SESSION_PUBLIC_LEN, "pub "); #endif return 0; } // more or less corresponds to CreateCredentialResponse in the irtf draft // 2. (SvrSession, sid , ssid ): On input α from U, S proceeds as follows: // (a) Checks that α ∈ G^∗ If not, outputs (abort, sid , ssid ) and halts; // (b) Retrieves file[sid] = {k_s, p_s, P_s, P_u, c}; // (c) Picks x_s ←_R Z_q and computes β := α^k_s and X_s := g^x_s ; // (d) Computes K := KE(p_s, x_s, P_u, X_u) and SK := f K (0); // (e) Sends β, X s and c to U; // (f) Outputs (sid , ssid , SK). int opaque_CreateCredentialResponse(const uint8_t _pub[OPAQUE_USER_SESSION_PUBLIC_LEN], const uint8_t _rec[OPAQUE_USER_RECORD_LEN], const Opaque_Ids *ids, const uint8_t *ctx, const uint16_t ctx_len, uint8_t _resp[OPAQUE_SERVER_SESSION_LEN], uint8_t sk[OPAQUE_SHARED_SECRETBYTES], uint8_t authU[crypto_auth_hmacsha512_BYTES]) { Opaque_UserSession *pub = (Opaque_UserSession *) _pub; Opaque_UserRecord *rec = (Opaque_UserRecord *) _rec; Opaque_ServerSession *resp = (Opaque_ServerSession *) _resp; #ifdef TRACE dump(_pub, sizeof(Opaque_UserSession), "session srv pub "); dump(_rec, OPAQUE_USER_RECORD_LEN, "session srv rec "); #endif // (a) Checks that α ∈ G^∗ . If not, outputs (abort, sid , ssid ) and halts; if(crypto_core_ristretto255_is_valid_point(pub->blinded)!=1) return -1; // (b) Retrieves file[sid] = {k_s, p_s, P_s, P_u, c}; // provided as parameter rec #ifdef TRACE dump(rec->kU, sizeof(rec->kU), "session srv kU "); dump(pub->blinded, sizeof(pub->blinded), "session srv blinded "); #endif // computes β := α^k_s // 1. Z = Evaluate(DeserializeScalar(credentialFile.kU), request.data) if (oprf_Evaluate(rec->kU, pub->blinded, resp->Z) != 0) { return -1; } #if (defined TRACE || defined CFRG_TEST_VEC) dump(resp->Z, sizeof resp->Z, "EvaluationElement"); #endif // 4. masking_nonce = random(Nn) // 5. credential_response_pad = Expand(record.masking_key, concat(masking_nonce, "CredentialResponsePad"), Npk + Ne) #ifdef CFRG_TEST_VEC struct { uint8_t nonce[32]; uint8_t dst[21]; } __attribute((packed)) masking_info = { .nonce = {0}, .dst = "CredentialResponsePad"}; memcpy(masking_info.nonce, masking_nonce, masking_nonce_len); #else struct { uint8_t nonce[32]; uint8_t dst[21]; } __attribute((packed)) masking_info = { .nonce = {0}, .dst = "CredentialResponsePad"}; randombytes(masking_info.nonce, sizeof masking_info.nonce); #endif uint8_t response_pad[crypto_scalarmult_BYTES+sizeof(Opaque_Envelope)]; if(-1==sodium_mlock(response_pad, sizeof response_pad)) { return -1; } crypto_kdf_hkdf_sha512_expand(response_pad, sizeof response_pad, (const char*) &masking_info, sizeof masking_info, rec->recU.masking_key); memcpy(resp->masking_nonce, masking_info.nonce, sizeof masking_info.nonce); // recalc server_public_key as we need it for the next step uint8_t pkS[crypto_scalarmult_BYTES]; crypto_scalarmult_ristretto255_base(pkS, rec->skS); #if (defined TRACE || defined CFRG_TEST_VEC) dump(pkS, sizeof pkS, "server_public_key"); #endif memcpy(resp->masked_response, pkS, sizeof pkS); // 6. masked_response = xor(credential_response_pad, concat(server_public_key, record.envelope)) unsigned i; for(i=0;imasked_response[i] = response_pad[i] ^ resp->masked_response[i]; for(;imasked_response[i] = response_pad[i] ^ ((uint8_t*)(&rec->recU.envelope))[i-crypto_scalarmult_BYTES]; sodium_munlock(response_pad, sizeof response_pad); #if (defined TRACE || defined CFRG_TEST_VEC) dump(_resp, sizeof (resp->Z) + crypto_scalarmult_BYTES+sizeof(Opaque_Envelope) + sizeof(masking_info.nonce), "resp(z+mn+mr)" ); #endif // this is the ake function Response() as per the irtf cfrg draft // 1. server_nonce = random(Nn) // nonceS #ifdef CFRG_TEST_VEC memcpy(resp->nonceS, server_nonce, OPAQUE_NONCE_BYTES); #else randombytes(resp->nonceS, OPAQUE_NONCE_BYTES); #endif // 2. server_private_keyshare, server_keyshare = GenerateAuthKeyPair() // (c) Picks x_s ←_R Z_q #ifndef CFRG_TEST_VEC const size_t server_keyshare_seed_len=32; uint8_t server_keyshare_seed[server_keyshare_seed_len]; randombytes(server_keyshare_seed, crypto_scalarmult_SCALARBYTES); #endif uint8_t x_s[crypto_scalarmult_SCALARBYTES]; if(-1==sodium_mlock(x_s,sizeof x_s)) return -1; // X_s := g^x_s; deriveKeyPair(server_keyshare_seed,server_keyshare_seed_len,x_s,resp->X_s); #if (defined TRACE || defined CFRG_TEST_VEC) dump(x_s, sizeof(x_s), "session server_private_keyshare (x_s) "); dump(resp->X_s, sizeof(resp->X_s), "session server_public_keyshare (X_s)"); #endif // 3. Create inner_ke2 ike2 with (credential_response, server_nonce, server_keyshare) // should already be all in place // 4. preamble = Preamble(client_identity, ke1, server_identity, ike2) // mixing in things from the irtf cfrg spec char preamble[crypto_hash_sha512_BYTES]; crypto_hash_sha512_state preamble_state; calc_preamble(preamble, &preamble_state, rec->recU.client_public_key, pkS, _pub, resp, ctx, ctx_len, (Opaque_Ids*) ids); Opaque_Keys keys; if(-1==sodium_mlock(&keys,sizeof(keys))) { sodium_munlock(x_s,sizeof x_s); return -1; } // (d) Computes K := KE(p_s, x_s, P_u, X_u) and SK := f_K(0); #ifdef TRACE dump(rec->skS,crypto_scalarmult_SCALARBYTES, "rec->skS "); dump(x_s,crypto_scalarmult_SCALARBYTES, "x_s "); //dump(rec->pkU,crypto_scalarmult_BYTES, "rec->pkU "); dump(pub->X_u,crypto_scalarmult_BYTES, "pub->X_u "); #endif // 5. ikm = TripleDHIKM(server_secret, ke1.client_keyshare, // server_private_key, ke1.client_keyshare, // server_secret, client_public_key) // 6. Km2, Km3, session_key = DeriveKeys(ikm, preamble) if(0!=server_3dh(&keys, rec->skS, x_s, rec->recU.client_public_key, pub->X_u, preamble)) { sodium_munlock(x_s, sizeof(x_s)); sodium_munlock(&keys,sizeof(keys)); return -1; } sodium_munlock(x_s, sizeof(x_s)); #if (defined TRACE || defined CFRG_TEST_VEC) dump(keys.sk, sizeof(keys.sk), "srv sk "); dump(keys.km2,OPAQUE_HMAC_SHA512_KEYBYTES,"session srv km2 "); dump(keys.km3,OPAQUE_HMAC_SHA512_KEYBYTES,"session srv km3 "); #endif // 7. server_mac = MAC(Km2, Hash(preamble)) opaque_hmacsha512(keys.km2, (uint8_t*)preamble, // in crypto_hash_sha512_BYTES, // len(in) resp->auth); // out #ifdef TRACE dump(resp->auth, sizeof resp->auth, "resp->auth "); dump(keys.km2, sizeof keys.km2, "km2 "); #endif // 8. expected_client_mac = MAC(Km3, Hash(concat(preamble, server_mac)) crypto_hash_sha512_update(&preamble_state, resp->auth, crypto_auth_hmacsha512_BYTES); crypto_hash_sha512_final(&preamble_state, (uint8_t *) preamble); #if (defined TRACE || defined CFRG_TEST_VEC) dump(resp->auth, crypto_auth_hmacsha512_BYTES, "server mac"); dump((uint8_t*)preamble, sizeof preamble, "auth preamble"); #endif if(NULL!=authU) { opaque_hmacsha512(keys.km3, // key (uint8_t*)preamble, // in crypto_hash_sha512_BYTES, // len(in) authU); // out } memcpy(sk,keys.sk,sizeof(keys.sk)); sodium_munlock(&keys,sizeof(keys)); #ifdef TRACE dump(resp->auth, sizeof(resp->auth), "session srv auth "); dump(authU, crypto_auth_hmacsha512_BYTES, "authU"); dump(_resp, OPAQUE_SERVER_SESSION_LEN, "resp"); #endif return 0; } // more or less corresponds to RecoverCredentials in the irtf draft // 3. On β, X_s and c from S, U proceeds as follows: // (a) Checks that β ∈ G ∗ . If not, outputs (abort, sid , ssid ) and halts; // (b) Computes rw := H(key, pw|β^1/r ); // (c) Computes AuthDec_rw(c). If the result is ⊥, outputs (abort, sid , ssid ) and halts. // Otherwise sets (p_u, P_u, P_s ) := AuthDec_rw (c); // (d) Computes K := KE(p_u, x_u, P_s, X_s) and SK := f_K(0); // (e) Outputs (sid, ssid, SK). int opaque_RecoverCredentials(const uint8_t _resp[OPAQUE_SERVER_SESSION_LEN], const uint8_t *_sec/*[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len]*/, const uint8_t *ctx, const uint16_t ctx_len, const Opaque_Ids *ids0, uint8_t sk[OPAQUE_SHARED_SECRETBYTES], uint8_t authU[crypto_auth_hmacsha512_BYTES], uint8_t export_key[crypto_hash_sha512_BYTES]) { Opaque_ServerSession *resp = (Opaque_ServerSession *) _resp; Opaque_UserSession_Secret *sec = (Opaque_UserSession_Secret *) _sec; #ifdef TRACE dump(sec->pwdU,sec->pwdU_len, "session user finish pwdU "); dump(_sec,OPAQUE_USER_SESSION_SECRET_LEN, "session user finish sec "); dump(_resp,OPAQUE_SERVER_SESSION_LEN, "session user finish resp "); #endif // 1. (client_private_key, server_public_key, export_key) = // RecoverCredentials(state.password, state.blind, ke2.CredentialResponse, // server_identity, client_identity) // 1.1. y = Finalize(password, blind, response.data, nil) // 1.2. randomized_pwd = Extract("", concat(y, Harden(y, params))) uint8_t N[crypto_core_ristretto255_BYTES]; if(-1==sodium_mlock(N, sizeof N)) return -1; // 1. N = Unblind(blind, response.data) if(0!=oprf_Unblind(sec->blind, resp->Z, N)) { sodium_munlock(N, sizeof N); return -1; } #if (defined TRACE || defined CFRG_TEST_VEC) dump(N, sizeof N, "unblinded"); #endif // rw = H(pw, β^(1/r)) uint8_t rwdU[OPAQUE_RWDU_BYTES]; if(-1==sodium_mlock(rwdU,sizeof rwdU)) { sodium_munlock(N, sizeof N); return -1; } // 1.2. y = Finalize(pwdU, N, "OPAQUE01") if(0!=oprf_Finalize(sec->pwdU, sec->pwdU_len, N, rwdU)) { sodium_munlock(N, sizeof N); sodium_munlock(rwdU,sizeof rwdU); return -1; } sodium_munlock(N,sizeof N); #if (defined TRACE || defined CFRG_TEST_VEC) dump(rwdU, sizeof rwdU, "rwdU"); #endif // 1.3. masking_key = HKDF-Expand(randomized_pwd, "MaskingKey", Nh) const uint8_t masking_key_info[10]="MaskingKey"; uint8_t masking_key[crypto_hash_sha512_BYTES]; if(-1==sodium_mlock(masking_key,sizeof masking_key)) { sodium_munlock(rwdU,sizeof rwdU); return -1; } crypto_kdf_hkdf_sha512_expand(masking_key, crypto_hash_sha512_BYTES, (const char*) masking_key_info, sizeof masking_key_info, rwdU); // 1.4. credential_response_pad = Expand(masking_key, // concat(response.masking_nonce, "CredentialResponsePad"), Npk + Ne) // 1.5. credential_response_pad = Expand(record.masking_key, concat(masking_nonce, "CredentialResponsePad"), Npk + Ne) struct { uint8_t nonce[32]; uint8_t dst[21]; } __attribute((packed)) masking_info = { .nonce = {0}, .dst = "CredentialResponsePad"}; memcpy(masking_info.nonce, resp->masking_nonce, sizeof masking_info.nonce); uint8_t response_pad[crypto_scalarmult_BYTES+sizeof(Opaque_Envelope)]; if(-1==sodium_mlock(response_pad,sizeof response_pad)) { sodium_munlock(masking_key,sizeof masking_key); sodium_munlock(rwdU,sizeof rwdU); return -1; } crypto_kdf_hkdf_sha512_expand(response_pad, sizeof response_pad, (const char*) &masking_info, sizeof masking_info, masking_key); sodium_munlock(masking_key,sizeof masking_key); // 1.5. concat(server_public_key, envelope) = xor(credential_response_pad, // response.masked_response) Opaque_Envelope env; if(-1==sodium_mlock(&env,sizeof env)) { sodium_mlock(response_pad,sizeof response_pad); sodium_munlock(rwdU,sizeof rwdU); return -1; } uint8_t server_public_key[crypto_scalarmult_BYTES], *env_ptr=(uint8_t*) &env; unsigned i; for(i=0;imasked_response[i]; for(;imasked_response[i]; sodium_mlock(response_pad,sizeof response_pad); #if (defined TRACE || defined CFRG_TEST_VEC) dump(server_public_key, sizeof server_public_key, "server_public_key"); dump(env.nonce, sizeof env.nonce, "env.nonce"); dump(env.auth_tag, sizeof env.auth_tag, "env.auth_tag"); #endif // 1.6. (client_private_key, export_key) = // Recover(randomized_pwd, server_public_key, envelope, // server_identity, client_identity) uint8_t concated[OPAQUE_ENVELOPE_NONCEBYTES+10], *label = concated+OPAQUE_ENVELOPE_NONCEBYTES; memcpy(concated, env.nonce, OPAQUE_ENVELOPE_NONCEBYTES); // 1.6.1. auth_key = Expand(randomized_pwd, concat(envelope.nonce, "AuthKey"), Nh) uint8_t auth_key[OPAQUE_HMAC_SHA512_KEYBYTES]; if(-1==sodium_mlock(auth_key, sizeof auth_key)) { sodium_munlock(rwdU,sizeof rwdU); return -1; } memcpy(label, "AuthKey", 7); crypto_kdf_hkdf_sha512_expand(auth_key, sizeof auth_key, (const char*) concated, OPAQUE_ENVELOPE_NONCEBYTES+7, rwdU); #ifdef TRACE dump(auth_key,sizeof auth_key, "auth_key "); #endif if(NULL!=export_key) { // 1.6.2. export_key = Expand(randomized_pwd, concat(envelope.nonce, "ExportKey", Nh) memcpy(label, "ExportKey", 9); #if (defined TRACE || defined CFRG_TEST_VEC) dump(concated, OPAQUE_ENVELOPE_NONCEBYTES+9, "export_key_info"); #endif crypto_kdf_hkdf_sha512_expand(export_key, crypto_hash_sha512_BYTES, (const char*) concated, OPAQUE_ENVELOPE_NONCEBYTES+9, rwdU); #ifdef TRACE dump(export_key,crypto_hash_sha512_BYTES, "export_key "); #endif } // 1.6.3. seed = Expand(randomized_pwd, concat(envelope.nonce, "PrivateKey"), Nseed) memcpy(label, "PrivateKey", 10); uint8_t seed[crypto_core_ristretto255_SCALARBYTES]; if(-1==sodium_mlock(seed, sizeof seed)) { sodium_munlock(auth_key, sizeof auth_key); sodium_munlock(rwdU,sizeof rwdU); return -1; } crypto_kdf_hkdf_sha512_expand(seed, crypto_core_ristretto255_SCALARBYTES, (const char*) concated, OPAQUE_ENVELOPE_NONCEBYTES+10, rwdU); sodium_munlock(rwdU,sizeof rwdU); // 1.6.4. client_private_key, client_public_key = DeriveAuthKeyPair(seed) uint8_t client_secret_key[crypto_scalarmult_SCALARBYTES]; if(-1==sodium_mlock(client_secret_key, sizeof client_secret_key)) { sodium_munlock(seed, sizeof seed); sodium_munlock(auth_key, sizeof auth_key); return -1; } uint8_t client_public_key[crypto_scalarmult_BYTES]; if(0!=deriveKeyPair(seed, sizeof seed, client_secret_key, client_public_key)) { sodium_munlock(seed, sizeof seed); sodium_munlock(client_secret_key, sizeof client_secret_key); sodium_munlock(auth_key, sizeof auth_key); return -1; } sodium_munlock(seed, sizeof seed); #if (defined CFRG_TEST_VEC || defined TRACE) dump(client_secret_key, crypto_scalarmult_SCALARBYTES, "client_secret_key"); #endif #if (defined CFRG_TEST_VEC || defined TRACE) dump(client_public_key, crypto_scalarmult_BYTES, "client_public_key"); #endif // 1.6.5. cleartext_creds = CreateCleartextCredentials(server_public_key, // client_public_key, server_identity, client_identity) Opaque_Ids ids; fix_ids(client_public_key, server_public_key, ids0, &ids); uint8_t authenticated[OPAQUE_NONCE_BYTES+ crypto_scalarmult_BYTES+ ids.idS_len+2+ ids.idU_len+2], *ptr=authenticated; // nonce memcpy(ptr, env.nonce, OPAQUE_NONCE_BYTES); ptr+=OPAQUE_NONCE_BYTES; // server_public_key memcpy(ptr, server_public_key, crypto_scalarmult_BYTES); ptr+=crypto_scalarmult_BYTES; // server_identity uint16_t size = htons(ids.idS_len); memcpy(ptr,(uint8_t*) &size, 2); ptr+=2; memcpy(ptr,ids.idS,ids.idS_len); ptr+=ids.idS_len; // client_identity size = htons(ids.idU_len); memcpy(ptr,(uint8_t*) &size, 2); ptr+=2; memcpy(ptr,ids.idU,ids.idU_len); // 1.6.6. expected_tag = MAC(auth_key, concat(envelope.nonce, cleartext_creds)) uint8_t auth_tag[crypto_auth_hmacsha512_BYTES]; opaque_hmacsha512(auth_key, // key authenticated, // in sizeof authenticated, // len(in) auth_tag); // out #if (defined TRACE || defined CFRG_TEST_VEC) dump(authenticated, sizeof authenticated, "authenticated"); dump(auth_key, sizeof auth_key, "auth_key"); dump(env.auth_tag, crypto_auth_hmacsha512_BYTES, "env auth_tag"); dump(auth_tag, crypto_hash_sha512_BYTES, "auth tag "); #endif sodium_munlock(auth_key, sizeof auth_key); // 1.6.7. If !ct_equal(envelope.auth_tag, expected_tag), // raise KeyRecoveryError if(0!=sodium_memcmp(env.auth_tag, auth_tag, sizeof auth_tag)) { sodium_munlock(client_secret_key, sizeof client_secret_key); return -1; } // 2. (ke3, session_key) = // ClientFinalize(client_identity, client_private_key, server_identity, // 2.2. preamble = Preamble(client_identity, state.ke1, server_identity, ke2.inner_ke2) char preamble[crypto_hash_sha512_BYTES]; crypto_hash_sha512_state preamble_state; calc_preamble(preamble, &preamble_state, client_public_key, server_public_key, sec->ke1, resp, ctx, ctx_len, &ids); Opaque_Keys keys; if(-1==sodium_mlock(&keys,sizeof(keys))) { sodium_munlock(client_secret_key, sizeof client_secret_key); return -1; } // 2.1. ikm = TripleDHIKM(state.client_secret, ke2.server_keyshare, // state.client_secret, server_public_key, client_private_key, ke2.server_keyshare) // 2.3. Km2, Km3, session_key = DeriveKeys(ikm, preamble) if(0!=user_3dh(&keys, client_secret_key, sec->x_u, server_public_key, resp->X_s, preamble)) { sodium_munlock(client_secret_key, sizeof client_secret_key); sodium_munlock(&keys, sizeof(keys)); return -1; } sodium_munlock(client_secret_key, sizeof client_secret_key); // 2.4. expected_server_mac = MAC(Km2, Hash(preamble)) uint8_t authS[crypto_auth_hmacsha512_BYTES]; opaque_hmacsha512(keys.km2, (uint8_t*)preamble, // in crypto_hash_sha512_BYTES, // len(in) authS); // out // 2.5. If !ct_equal(ke2.server_mac, expected_server_mac), // raise HandshakeError if (sodium_memcmp(authS, resp->auth, sizeof authS)!=0) { sodium_munlock(&keys, sizeof(keys)); return -1; } // 2.6. client_mac = MAC(Km3, Hash(concat(preamble, expected_server_mac)) crypto_hash_sha512_update(&preamble_state, authS, crypto_auth_hmacsha512_BYTES); crypto_hash_sha512_final(&preamble_state, (uint8_t *) preamble); if(NULL!=authU) { opaque_hmacsha512(keys.km3, // key (uint8_t*)preamble, // in crypto_hash_sha512_BYTES, // len(in) authU); // out } // 2.7. Create KE3 ke3 with client_mac // 2.8. Output (ke3, session_key) memcpy(sk,keys.sk,sizeof(keys.sk)); sodium_munlock(&keys, sizeof(keys)); return 0; } // extra function to implement the hmac based auth as defined in the irtf cfrg draft int opaque_UserAuth(const uint8_t authU0[crypto_auth_hmacsha512_BYTES], const uint8_t authU[crypto_auth_hmacsha512_BYTES]) { return sodium_memcmp(authU0, authU, crypto_auth_hmacsha512_BYTES); } // variant where the secrets of U never touch S unencrypted // U computes: blinded PW // called CreateRegistrationRequest in the irtf cfrg rfc draft int opaque_CreateRegistrationRequest(const uint8_t *pwdU, const uint16_t pwdU_len, uint8_t _sec[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len], uint8_t blinded[crypto_core_ristretto255_BYTES]) { Opaque_RegisterUserSec *sec = (Opaque_RegisterUserSec *) _sec; memcpy(&sec->pwdU, pwdU, pwdU_len); sec->pwdU_len = pwdU_len; // 1. (blind, blinded) = Blind(pwdU) return oprf_Blind(pwdU, pwdU_len, sec->blind, blinded); } // initUser: S // (1) checks α ∈ G^∗ If not, outputs (abort, sid , ssid ) and halts; // (2) generates k_s ←_R Z_q, // (3) computes: β := α^k_s, // (4) finally generates: p_s ←_R Z_q, P_s := g^p_s; // called CreateRegistrationResponse in the irtf cfrg rfc draft int opaque_CreateRegistrationResponse(const uint8_t blinded[crypto_core_ristretto255_BYTES], const uint8_t skS[crypto_scalarmult_SCALARBYTES], uint8_t _sec[OPAQUE_REGISTER_SECRET_LEN], uint8_t _pub[OPAQUE_REGISTER_PUBLIC_LEN]) { Opaque_RegisterSrvSec *sec = (Opaque_RegisterSrvSec *) _sec; Opaque_RegisterSrvPub *pub = (Opaque_RegisterSrvPub *) _pub; // (a) Checks that α ∈ G^∗ . If not, outputs (abort, sid , ssid ) and halts; if(crypto_core_ristretto255_is_valid_point(blinded)!=1) return -1; // k_s ←_R Z_q // 1. (kU, _) = KeyGen() oprf_KeyGen(sec->kU); // computes β := α^k_s // 2. Z = Evaluate(kU, request.data) if (oprf_Evaluate(sec->kU, blinded, pub->Z) != 0) { return -1; } #if (defined TRACE || defined CFRG_TEST_VEC) dump(sec->kU, sizeof sec->kU, "kU"); dump(pub->Z, sizeof pub->Z, "EvaluationElement"); #endif if(skS==NULL) { randombytes(sec->skS, crypto_scalarmult_SCALARBYTES); // random server long-term key } else { memcpy(sec->skS, skS, crypto_scalarmult_SCALARBYTES); } #ifdef TRACE dump((uint8_t*) sec->skS, sizeof sec->skS, "skS "); #endif // P_s := g^p_s crypto_scalarmult_ristretto255_base(pub->pkS, sec->skS); #ifdef TRACE dump((uint8_t*) pub->pkS, sizeof pub->pkS, "pkS "); #endif return 0; } // user computes: // (a) Checks that β ∈ G ∗ . If not, outputs (abort, sid , ssid ) and halts; // (b) Computes rw := H(key, pw | β^1/r ); // (c) p_u ←_R Z_q // (d) P_u := g^p_u, // (e) c ← AuthEnc_rw (p_u, P_u, P_s); // called FinalizeRequest in the irtf cfrg rfc draft int opaque_FinalizeRequest(const uint8_t *_sec/*[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]*/, const uint8_t _pub[OPAQUE_REGISTER_PUBLIC_LEN], const Opaque_Ids *ids, uint8_t _rec[OPAQUE_REGISTRATION_RECORD_LEN], uint8_t export_key[crypto_hash_sha512_BYTES]) { Opaque_RegisterUserSec *sec = (Opaque_RegisterUserSec *) _sec; Opaque_RegisterSrvPub *pub = (Opaque_RegisterSrvPub *) _pub; Opaque_RegistrationRecord *rec = (Opaque_RegistrationRecord *) _rec; uint8_t N[crypto_core_ristretto255_BYTES]; if(-1==sodium_mlock(N, sizeof N)) return -1; // 1. N = Unblind(blind, response.data) if(0!=oprf_Unblind(sec->blind, pub->Z, N)) { sodium_munlock(N, sizeof N); return -1; } #if (defined TRACE || defined CFRG_TEST_VEC) dump(N, sizeof N, "unblinded"); #endif uint8_t rwdU[OPAQUE_RWDU_BYTES]; if(-1==sodium_mlock(rwdU, sizeof rwdU)) { sodium_munlock(N, sizeof N); return -1; } // 2. y = Finalize(pwdU, N, "OPAQUE01") if(0!=oprf_Finalize(sec->pwdU, sec->pwdU_len, N, rwdU)) { sodium_munlock(N, sizeof N); sodium_munlock(rwdU, sizeof(rwdU)); return -1; } sodium_munlock(N,sizeof N); if(0!=create_envelope(rwdU, pub->pkS, ids, &rec->envelope, rec->client_public_key, rec->masking_key, export_key)) { sodium_munlock(rwdU, sizeof rwdU); return -1; } sodium_munlock(rwdU, sizeof rwdU); #if (defined TRACE || defined CFRG_TEST_VEC) dump(_rec, OPAQUE_REGISTRATION_RECORD_LEN, "record"); #endif #ifdef TRACE dump(_rec, OPAQUE_REGISTRATION_RECORD_LEN, "registration rec "); #endif return 0; } // S records file[sid ] := {k_s, p_s, P_s, P_u, c}. // called StoreUserRecord in the irtf cfrg rfc draft void opaque_StoreUserRecord(const uint8_t _sec[OPAQUE_REGISTER_SECRET_LEN], const uint8_t recU[OPAQUE_REGISTRATION_RECORD_LEN], uint8_t _rec[OPAQUE_USER_RECORD_LEN]) { Opaque_RegisterSrvSec *sec = (Opaque_RegisterSrvSec *) _sec; Opaque_UserRecord *rec = (Opaque_UserRecord *) _rec; memcpy(rec->kU, sec->kU, sizeof rec->kU); memcpy(rec->skS, sec->skS, crypto_scalarmult_SCALARBYTES); memcpy((uint8_t*)&rec->recU, recU, OPAQUE_REGISTRATION_RECORD_LEN); //crypto_scalarmult_base(rec->pkS, skS); #ifdef TRACE dump((uint8_t*) rec, OPAQUE_USER_RECORD_LEN, "user rec "); #endif } libopaque-0.99.3/src/opaque.h000066400000000000000000000366721452546740600160620ustar00rootroot00000000000000/** * @file opaque.h */ #ifndef opaque_h #define opaque_h #ifdef __cplusplus extern "C" { #endif #include #include #include /** * sk is a shared secret. In opaque.h, we do not report its byte size in functions * like opaque_CreateCredentialResponse. We centralize its size here so that if * the algorithm to calculate sk changes, we can just change it in one place. */ #define OPAQUE_SHARED_SECRETBYTES 64 #define OPAQUE_ENVELOPE_NONCEBYTES 32 #define OPAQUE_NONCE_BYTES 32 #define OPAQUE_REGISTRATION_RECORD_LEN ( \ /* client_public_key */ crypto_scalarmult_BYTES+ \ /* masking_key */ crypto_hash_sha512_BYTES+ \ /* envelope nonce */ OPAQUE_ENVELOPE_NONCEBYTES+ \ /* envelope mac */ crypto_auth_hmacsha512_BYTES) #define OPAQUE_USER_RECORD_LEN ( \ /* kU */ crypto_core_ristretto255_SCALARBYTES+ \ /* skS */ crypto_scalarmult_SCALARBYTES+ \ OPAQUE_REGISTRATION_RECORD_LEN) #define OPAQUE_USER_SESSION_PUBLIC_LEN ( \ /* blinded */ crypto_core_ristretto255_BYTES+ \ /* X_u */ crypto_scalarmult_BYTES+ \ /* nonceU */ OPAQUE_NONCE_BYTES) #define OPAQUE_USER_SESSION_SECRET_LEN ( \ /* r */ crypto_core_ristretto255_SCALARBYTES+ \ /* x_u */ crypto_scalarmult_SCALARBYTES+ \ /* nonceU */ OPAQUE_NONCE_BYTES+ \ /* blinded */ crypto_core_ristretto255_BYTES+ \ /* ke1 */ OPAQUE_USER_SESSION_PUBLIC_LEN+ \ /* pwdU_len */ sizeof(uint16_t)) #define OPAQUE_SERVER_SESSION_LEN ( \ /* Z */ crypto_core_ristretto255_BYTES+ \ /* masking_nonce */ 32+ \ /* server_public_key */ crypto_scalarmult_BYTES+ \ /* nonceS */ OPAQUE_NONCE_BYTES+ \ /* X_s */ crypto_scalarmult_BYTES+ \ /* auth */ crypto_auth_hmacsha512_BYTES+ \ /* envelope nonce */ OPAQUE_ENVELOPE_NONCEBYTES+ \ /* envelope mac */ crypto_auth_hmacsha512_BYTES) #define OPAQUE_REGISTER_USER_SEC_LEN ( \ /* r */ crypto_core_ristretto255_SCALARBYTES+ \ /* pwdU_len */ sizeof(uint16_t)) #define OPAQUE_REGISTER_PUBLIC_LEN ( \ /* Z */ crypto_core_ristretto255_BYTES+ \ /* pkS */ crypto_scalarmult_BYTES) #define OPAQUE_REGISTER_SECRET_LEN ( \ /* skS */ crypto_scalarmult_SCALARBYTES+ \ /* kU */ crypto_core_ristretto255_SCALARBYTES) /** struct to store the IDs of the user/server. If the ids are the default, then set these values to NULL/0. The defaults are always the long-term public keys of the respective party. If your system needs different user ids, like for example the server DNS host name or the users email addres, then provide them via this struct. If only one is "custom" and the other is default, that is also ok. */ typedef struct { uint16_t idU_len; /**< length of idU, most useful if idU is binary */ uint8_t *idU; /**< pointer to the id of the user/client in the opaque protocol */ uint16_t idS_len; /**< length of idS, needed for binary ids */ uint8_t *idS; /**< pointer to the id of the server in the opaque protocol */ } Opaque_Ids; /** This function implements the storePwdFile function from the paper it is not specified by the RFC. This function runs on the server and creates a new output record rec of secret key material. The server needs to implement the storage of this record and any binding to user names or as the paper suggests sid. @param [in] pwdU - the users password @param [in] pwdU_len - length of the users password @param [in] skS - in case of global server keys this is the servers private key, should be set to NULL if per/user keys are to be generated @param [in] ids - the ids of the user and server, see Opaque_Ids @param [out] rec - the opaque record the server needs to store. this is a pointer to memory allocated by the caller, and must be large enough to hold the record and take into account the variable length of idU and idS in case these are included in the envelope. @param [out] export_key - optional pointer to pre-allocated (and protected) memory for an extra_key that can be used to encrypt/authenticate additional data. @return the function returns 0 if everything is correct */ int opaque_Register(const uint8_t *pwdU, const uint16_t pwdU_len, const uint8_t skS[crypto_scalarmult_SCALARBYTES], const Opaque_Ids *ids, uint8_t rec[OPAQUE_USER_RECORD_LEN], uint8_t export_key[crypto_hash_sha512_BYTES]); /** This function initiates a new OPAQUE session, is the same as the function defined in the paper with the name usrSession. @param [in] pwdU - users input password @param [in] pwdU_len - length of the users password @param [out] sec - private context, it is essential that the memory allocate for this buffer be **OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len**. The User should protect the sec value (e.g. with sodium_mlock()) until opaque_RecoverCredentials. @param [out] pub - the message to be sent to the server @return the function returns 0 if everything is correct */ int opaque_CreateCredentialRequest(const uint8_t *pwdU, const uint16_t pwdU_len, #ifdef __cplusplus uint8_t *sec/*[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len]*/, #else uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], #endif uint8_t pub[OPAQUE_USER_SESSION_PUBLIC_LEN]); /** This is the same function as defined in the paper with name srvSession name. This function runs on the server and receives the output pub from the user running opaque_CreateCredentialRequest(), furthermore the server needs to load the user record created when registering the user with opaque_Register() or opaque_StoreUserRecord(). These input parameters are transformed into a secret/shared session key sk and a response resp to be sent back to the user. @param [in] pub - the pub output of the opaque_CreateCredentialRequest() @param [in] rec - the recorded created during "registration" and stored by the server @param [in] ids - the id if the client and server @param [in] ctx - a context of this instantiation of this protocol, e.g. "AppABCv12.34" @param [in] ctx_len - a context of this instantiation of this protocol @param [out] resp - servers response to be sent to the client where it is used as input into opaque_RecoverCredentials() @param [out] sk - the shared secret established between the user & server @param [out] sec - the current context necessary for the explicit authentication of the user in opaque_UserAuth(). This param is optional if no explicit user auth is necessary it can be set to NULL @return the function returns 0 if everything is correct */ int opaque_CreateCredentialResponse(const uint8_t pub[OPAQUE_USER_SESSION_PUBLIC_LEN], const uint8_t rec[OPAQUE_USER_RECORD_LEN], const Opaque_Ids *ids, const uint8_t *ctx, const uint16_t ctx_len, uint8_t resp[OPAQUE_SERVER_SESSION_LEN], uint8_t sk[OPAQUE_SHARED_SECRETBYTES], uint8_t authU[crypto_auth_hmacsha512_BYTES]); /** This is the same function as defined in the paper with the usrSessionEnd name. It is run by the user and receives as input the response from the previous server opaque_CreateCredentialResponse() function as well as the sec value from running the opaque_CreateCredentialRequest() function that initiated this instantiation of this protocol, All these input parameters are transformed into a shared/secret session key pk, which should be the same as the one calculated by the opaque_CreateCredentialResponse() function. @param [in] resp - the response sent from the server running opaque_CreateCredentialResponse() @param [in] sec - the private sec output of the client initiating this instantiation of this protocol using opaque_CreateCredentialRequest() @param [in] ctx - a context of this instantiation of this protocol, e.g. "AppABCv12.34" @param [in] ctx_len - a context of this instantiation of this protocol @param [in] ids - The ids of the server/client in case they are not the default. @param [out] sk - the shared secret established between the user & server @param [out] authU - the authentication code to be sent to the server in case explicit user authentication is required, optional set to NULL if not needed @param [out] export_key - key used to encrypt/authenticate extra material not stored directly in the envelope @return the function returns 0 if the protocol is executed correctly */ int opaque_RecoverCredentials(const uint8_t resp[OPAQUE_SERVER_SESSION_LEN], const uint8_t *sec/*[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len]*/, const uint8_t *ctx, const uint16_t ctx_len, const Opaque_Ids *ids, uint8_t sk[OPAQUE_SHARED_SECRETBYTES], uint8_t authU[crypto_auth_hmacsha512_BYTES], uint8_t export_key[crypto_hash_sha512_BYTES]); /** Explicit User Authentication. This is a function not explicitly specified in the original paper. In the irtf cfrg draft authentication is done using a hmac of the session transcript with different keys coming out of a hkdf after the key exchange. @param [in] authU0 - the authU value returned by opaque_CreateCredentialResponse() @param [in] authU is the authentication token sent by the user. @return the function returns 0 if the hmac verifies correctly. */ int opaque_UserAuth(const uint8_t authU0[crypto_auth_hmacsha512_BYTES], const uint8_t authU[crypto_auth_hmacsha512_BYTES]); /** Alternative user initialization, user registration as specified by the RFC */ /** Initial step to start registering a new user/client with the server. The user inputs its password pwdU, and receives a secret context sec and a blinded value blinded as output. sec should be protected until step 3 of this registration protocol and the value blinded should be passed to the server. @param [in] pwdU - the users password @param [in] pwdU_len - length of the users password @param [out] sec - a secret context needed for the 3rd step in this registration protocol - this needs to be protected and sanitized after usage. @param [out] request - the blinded hashed password as per the OPRF, this needs to be sent to the server together with any other important and implementation specific info such as user/client id, envelope configuration etc. @return the function returns 0 if everything is correct. */ int opaque_CreateRegistrationRequest(const uint8_t *pwdU, const uint16_t pwdU_len, #ifdef __cplusplus uint8_t *sec/*[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]*/, #else uint8_t sec[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len], #endif uint8_t request[crypto_core_ristretto255_BYTES]); /** 2nd step of registration: Server evaluates OPRF - Global Server Key Version This function is essentially the same as opaque_CreateRegistrationResponse(), except this function does not generate a per-user long-term key, but instead expects the servers to supply a long-term pubkey as a parameter, this might be one unique global key, or it might be a per-user key derived from a server secret. This function is called CreateRegistrationResponse in the rfc. The server receives blinded from the users invocation of its opaque_CreateRegistrationRequest() function, it outputs a value sec which needs to be protected until step 4 by the server. This function also outputs a value pub which needs to be passed to the user. @param [in] request - the blinded password as per the OPRF. @param [in] skS - the servers long-term private key, optional, set to NULL if you want this implementation to generate a unique key for this record. @param [out] sec - the private key and the OPRF secret of the server. @param [out] pub - the evaluated OPRF and pubkey of the server to be passed to the client into opaque_FinalizeRequest() @return the function returns 0 if everything is correct. */ int opaque_CreateRegistrationResponse(const uint8_t request[crypto_core_ristretto255_BYTES], const uint8_t skS[crypto_scalarmult_SCALARBYTES], uint8_t sec[OPAQUE_REGISTER_SECRET_LEN], uint8_t pub[OPAQUE_REGISTER_PUBLIC_LEN]); /** Client finalizes registration by concluding the OPRF, generating its own keys and enveloping it all. This function is called FinalizeRequest in the rfc. This function is run by the user, taking as input the context sec that was an output of the user running opaque_CreateRegistrationRequest(), and the output pub from the server of opaque_CreateRegistrationResponse(). @param [in] sec - output from opaque_CreateRegistrationRequest(), should be sanitized after usage. @param [in] pub - response from the server running opaque_CreateRegistrationResponse() @param [in] ids - if ids are not the default value @param [out] reg_rec - the opaque registration record containing the users data. @param [out] export_key - key used to encrypt/authenticate extra material not stored directly in the envelope. Optional, if not needed set to NULL. @return the function returns 0 if everything is correct. */ int opaque_FinalizeRequest( const uint8_t *sec/*[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]*/, const uint8_t pub[OPAQUE_REGISTER_PUBLIC_LEN], const Opaque_Ids *ids, uint8_t reg_rec[OPAQUE_REGISTRATION_RECORD_LEN], uint8_t export_key[crypto_hash_sha512_BYTES]); /** Final Registration step - server adds own info to the record to be stored. The rfc does not explicitly specify this function. The server combines the sec value from its run of its opaque_CreateRegistrationResponse() function with the rec output of the users opaque_FinalizeRequest() function, creating the final record, which should be the same as the output of the 1-step storePwdFile() init function of the paper. The server should save this record in combination with a user id and/or sid value as suggested in the paper. @param [in] sec - the private value of the server running opaque_CreateRegistrationResponse() in step 2 of the registration protocol @param [in] reg_rec - the registration record from the client running opaque_FinalizeRequest() @param [out] rec - the final record to be stored by the server. */ void opaque_StoreUserRecord(const uint8_t sec[OPAQUE_REGISTER_SECRET_LEN], const uint8_t recU[OPAQUE_REGISTRATION_RECORD_LEN], uint8_t rec[OPAQUE_USER_RECORD_LEN]); #ifdef __cplusplus } #endif #endif // opaque_h libopaque-0.99.3/src/tests/000077500000000000000000000000001452546740600155435ustar00rootroot00000000000000libopaque-0.99.3/src/tests/cfrg_test_vector_decl.h000066400000000000000000000056061452546740600222540ustar00rootroot00000000000000#ifndef cfrg_test_vector_decl_h #define cfrg_test_vector_decl_h #include #define ke1_len 96 extern const uint8_t ke1[ke1_len]; #define ke2_len 320 extern const uint8_t ke2[ke2_len]; #define ke3_len 64 extern const uint8_t ke3[ke3_len]; #define export_key_len 64 extern const uint8_t export_key[export_key_len]; #define registration_request_len 32 extern const uint8_t registration_request[registration_request_len]; #define registration_response_len 64 extern const uint8_t registration_response[registration_response_len]; #define registration_upload_len 192 extern const uint8_t registration_upload[registration_upload_len]; #define session_key_len 64 extern const uint8_t session_key[session_key_len]; #define auth_key_len 64 extern const uint8_t auth_key[auth_key_len]; #define client_mac_key_len 64 extern const uint8_t client_mac_key[client_mac_key_len]; #define client_public_key_len 32 extern const uint8_t client_public_key[client_public_key_len]; #define envelope_len 96 extern const uint8_t envelope[envelope_len]; #define handshake_secret_len 64 extern const uint8_t handshake_secret[handshake_secret_len]; #define masking_key_len 64 extern const uint8_t masking_key[masking_key_len]; #define oprf_key_len 32 extern const uint8_t oprf_key[oprf_key_len]; #define randomized_pwd_len 64 extern const uint8_t randomized_pwd[randomized_pwd_len]; #define server_mac_key_len 64 extern const uint8_t server_mac_key[server_mac_key_len]; #define blind_login_len 32 extern const uint8_t blind_login[blind_login_len]; #define blind_registration_len 32 extern const uint8_t blind_registration[blind_registration_len]; #define client_keyshare_len 32 extern const uint8_t client_keyshare[client_keyshare_len]; #define client_nonce_len 32 extern const uint8_t client_nonce[client_nonce_len]; #define client_keyshare_seed_len 32 extern const uint8_t client_keyshare_seed[client_keyshare_seed_len]; #define server_keyshare_seed_len 32 extern const uint8_t server_keyshare_seed[client_keyshare_seed_len]; #define credential_identifier_len 4 extern const uint8_t credential_identifier[credential_identifier_len]; #define envelope_nonce_len 32 extern const uint8_t envelope_nonce[envelope_nonce_len]; #define masking_nonce_len 32 extern const uint8_t masking_nonce[masking_nonce_len]; #define oprf_seed_len 64 extern const uint8_t oprf_seed[oprf_seed_len]; #define password_len 25 extern const uint8_t password[password_len]; #define server_keyshare_len 32 extern const uint8_t server_keyshare[server_keyshare_len]; #define server_nonce_len 32 extern const uint8_t server_nonce[server_nonce_len]; #define server_private_key_len 32 extern const uint8_t server_private_key[server_private_key_len]; #define server_private_keyshare_len 32 extern const uint8_t server_private_keyshare[server_private_keyshare_len]; #define server_public_key_len 32 extern const uint8_t server_public_key[server_public_key_len]; #endif libopaque-0.99.3/src/tests/cfrg_test_vectors.h000066400000000000000000000340631452546740600214470ustar00rootroot00000000000000#ifndef cfrg_test_vectors_h #define cfrg_test_vectors_h #include #define auth_key_len 64 const uint8_t auth_key[auth_key_len] = { 0x6c, 0xd3, 0x23, 0x16, 0xf1, 0x8d, 0x72, 0xa9, 0xa9, 0x27, 0xa8, 0x31, 0x99, 0xfa, 0x03, 0x06, 0x63, 0xa3, 0x8c, 0xe0, 0xc1, 0x1f, 0xba, 0xef, 0x82, 0xaa, 0x90, 0x03, 0x77, 0x30, 0x49, 0x4f, 0xc5, 0x55, 0xc4, 0xd4, 0x95, 0x06, 0x28, 0x45, 0x16, 0xed, 0xd1, 0x62, 0x8c, 0x27, 0x96, 0x5b, 0x75, 0x55, 0xa4, 0xeb, 0xfe, 0xd2, 0x22, 0x31, 0x99, 0xf6, 0xc6, 0x79, 0x66, 0xdd, 0xe8, 0x22}; #define client_mac_key_len 64 const uint8_t client_mac_key[client_mac_key_len] = { 0x91, 0x75, 0x0a, 0xdb, 0xac, 0x54, 0xa5, 0xe8, 0xe5, 0x3b, 0x4c, 0x23, 0x3c, 0xc8, 0xd3, 0x69, 0xfe, 0x83, 0xb0, 0xde, 0x1b, 0x6a, 0x3c, 0xd8, 0x55, 0x75, 0xee, 0xb0, 0xbb, 0x01, 0xa6, 0xa9, 0x0a, 0x08, 0x6a, 0x2c, 0xf5, 0xfe, 0x75, 0xff, 0xf2, 0xa9, 0x37, 0x9c, 0x30, 0xba, 0x90, 0x49, 0x51, 0x0a, 0x33, 0xb5, 0xb0, 0xb1, 0x44, 0x4a, 0x88, 0x80, 0x0f, 0xc3, 0xee, 0xe2, 0x26, 0x0d}; #define client_public_key_len 32 const uint8_t client_public_key[client_public_key_len] = { 0x76, 0xa8, 0x45, 0x46, 0x4c, 0x68, 0xa5, 0xd2, 0xf7, 0xe4, 0x42, 0x43, 0x6b, 0xb1, 0x42, 0x49, 0x53, 0xb1, 0x7d, 0x3e, 0x2e, 0x28, 0x9c, 0xcb, 0xac, 0xca, 0xfb, 0x57, 0xac, 0x5c, 0x36, 0x75}; #define envelope_len 96 const uint8_t envelope[envelope_len] = { 0xac, 0x13, 0x17, 0x1b, 0x2f, 0x17, 0xbc, 0x2c, 0x74, 0x99, 0x7f, 0x0f, 0xce, 0x1e, 0x1f, 0x35, 0xbe, 0xc6, 0xb9, 0x1f, 0xe2, 0xe1, 0x2d, 0xbd, 0x32, 0x3d, 0x23, 0xba, 0x7a, 0x38, 0xdf, 0xec, 0x63, 0x4b, 0x0f, 0x5b, 0x96, 0x10, 0x9c, 0x19, 0x8a, 0x80, 0x27, 0xda, 0x51, 0x85, 0x4c, 0x35, 0xbe, 0xe9, 0x0d, 0x1e, 0x1c, 0x78, 0x18, 0x06, 0xd0, 0x7d, 0x49, 0xb7, 0x6d, 0xe6, 0xa2, 0x8b, 0x8d, 0x9e, 0x9b, 0x6c, 0x93, 0xb9, 0xf8, 0xb6, 0x4d, 0x16, 0xdd, 0xdd, 0x9c, 0x5b, 0xfb, 0x5f, 0xea, 0x48, 0xee, 0x8f, 0xd2, 0xf7, 0x50, 0x12, 0xa8, 0xb3, 0x08, 0x60, 0x5c, 0xdd, 0x8b, 0xa5}; #define handshake_secret_len 64 const uint8_t handshake_secret[handshake_secret_len] = { 0x81, 0x26, 0x3c, 0xb8, 0x5a, 0x0c, 0xfa, 0x12, 0x45, 0x0f, 0x0f, 0x38, 0x8d, 0xe4, 0xe9, 0x22, 0x91, 0xec, 0x4c, 0x7c, 0x7a, 0x08, 0x78, 0xb6, 0x24, 0x55, 0x0f, 0xf5, 0x28, 0x72, 0x63, 0x32, 0xf1, 0x29, 0x8f, 0xc6, 0xcc, 0x82, 0x2a, 0x43, 0x2c, 0x89, 0x50, 0x43, 0x47, 0xc7, 0xa2, 0xcc, 0xd7, 0x03, 0x16, 0xae, 0x3d, 0xa6, 0xa1, 0x5e, 0x03, 0x99, 0xe6, 0xdb, 0x3f, 0x7c, 0x1b, 0x12}; #define masking_key_len 64 const uint8_t masking_key[masking_key_len] = { 0x1a, 0xc5, 0x84, 0x43, 0x83, 0xc7, 0x70, 0x80, 0x77, 0xde, 0xa4, 0x1c, 0xbe, 0xfe, 0x2f, 0xa1, 0x57, 0x24, 0xf4, 0x49, 0xe5, 0x35, 0xdd, 0x7d, 0xd5, 0x62, 0xe6, 0x6f, 0x5e, 0xcf, 0xb9, 0x58, 0x64, 0xea, 0xdd, 0xde, 0xc9, 0xdb, 0x58, 0x74, 0x95, 0x99, 0x05, 0x11, 0x7d, 0xad, 0x40, 0xa4, 0x52, 0x41, 0x11, 0x84, 0x97, 0x99, 0x28, 0x1f, 0xef, 0xe3, 0xc5, 0x1f, 0xa8, 0x27, 0x85, 0xc5}; #define oprf_key_len 32 const uint8_t oprf_key[oprf_key_len] = { 0x5d, 0x4c, 0x6a, 0x8b, 0x7c, 0x71, 0x38, 0x18, 0x2a, 0xfb, 0x43, 0x45, 0xd1, 0xfa, 0xe6, 0xa9, 0xf1, 0x8a, 0x17, 0x44, 0xaf, 0xbc, 0xc3, 0x85, 0x4f, 0x8f, 0x5a, 0x2b, 0x4b, 0x4c, 0x6d, 0x05}; #define randomized_password_len 64 const uint8_t randomized_password[randomized_password_len] = { 0xaa, 0xc4, 0x8c, 0x25, 0xab, 0x03, 0x6e, 0x30, 0x75, 0x08, 0x39, 0xd3, 0x1d, 0x6e, 0x73, 0x00, 0x73, 0x44, 0xcb, 0x11, 0x55, 0x28, 0x9f, 0xb7, 0xd3, 0x29, 0xbe, 0xb9, 0x32, 0xe9, 0xad, 0xee, 0xa7, 0x3d, 0x5d, 0x5c, 0x22, 0xa0, 0xce, 0x19, 0x52, 0xf8, 0xab, 0xa6, 0xd6, 0x60, 0x07, 0x61, 0x5c, 0xd1, 0x69, 0x8d, 0x4a, 0xc8, 0x5e, 0xf1, 0xfc, 0xf1, 0x50, 0x03, 0x1d, 0x14, 0x35, 0xd9}; #define server_mac_key_len 64 const uint8_t server_mac_key[server_mac_key_len] = { 0x0d, 0x36, 0xb2, 0x6c, 0xfe, 0x38, 0xf5, 0x1f, 0x80, 0x4f, 0x0a, 0x93, 0x61, 0x81, 0x8f, 0x32, 0xee, 0x1c, 0xe2, 0xa4, 0xe5, 0x57, 0x86, 0x53, 0xb5, 0x27, 0x18, 0x4a, 0xf0, 0x58, 0xd3, 0xb2, 0xd8, 0x07, 0x5c, 0x29, 0x6f, 0xd8, 0x4d, 0x24, 0x67, 0x79, 0x13, 0xd1, 0xba, 0xa1, 0x09, 0x29, 0x0c, 0xd8, 0x1a, 0x13, 0xed, 0x38, 0x3f, 0x90, 0x91, 0xa3, 0x80, 0x4e, 0x65, 0x29, 0x8d, 0xfc}; #define blind_login_len 32 const uint8_t blind_login[blind_login_len] = { 0x6e, 0xcc, 0x10, 0x2d, 0x2e, 0x7a, 0x7c, 0xf4, 0x96, 0x17, 0xaa, 0xd7, 0xbb, 0xe1, 0x88, 0x55, 0x67, 0x92, 0xd4, 0xac, 0xd6, 0x0a, 0x1a, 0x8a, 0x8d, 0x2b, 0x65, 0xd4, 0xb0, 0x79, 0x03, 0x08}; #define blind_registration_len 32 const uint8_t blind_registration[blind_registration_len] = { 0x76, 0xcf, 0xbf, 0xe7, 0x58, 0xdb, 0x88, 0x4b, 0xeb, 0xb3, 0x35, 0x82, 0x33, 0x1b, 0xa9, 0xf1, 0x59, 0x72, 0x0c, 0xa8, 0x78, 0x4a, 0x2a, 0x07, 0x0a, 0x26, 0x5d, 0x9c, 0x2d, 0x6a, 0xbe, 0x01}; #define client_keyshare_seed_len 32 const uint8_t client_keyshare_seed[client_keyshare_seed_len] = { 0x82, 0x85, 0x0a, 0x69, 0x7b, 0x42, 0xa5, 0x05, 0xf5, 0xb6, 0x8f, 0xcd, 0xaf, 0xce, 0x8c, 0x31, 0xf0, 0xaf, 0x2b, 0x58, 0x1f, 0x06, 0x3c, 0xf1, 0x09, 0x19, 0x33, 0x54, 0x19, 0x36, 0x30, 0x4b}; #define client_nonce_len 32 const uint8_t client_nonce[client_nonce_len] = { 0xda, 0x7e, 0x07, 0x37, 0x6d, 0x6d, 0x6f, 0x03, 0x4c, 0xfa, 0x9b, 0xb5, 0x37, 0xd1, 0x1b, 0x8c, 0x6b, 0x42, 0x38, 0xc3, 0x34, 0x33, 0x3d, 0x1f, 0x0a, 0xeb, 0xb3, 0x80, 0xca, 0xe6, 0xa6, 0xcc}; #define credential_identifier_len 4 const uint8_t credential_identifier[credential_identifier_len] = { 0x31, 0x32, 0x33, 0x34}; #define envelope_nonce_len 32 const uint8_t envelope_nonce[envelope_nonce_len] = { 0xac, 0x13, 0x17, 0x1b, 0x2f, 0x17, 0xbc, 0x2c, 0x74, 0x99, 0x7f, 0x0f, 0xce, 0x1e, 0x1f, 0x35, 0xbe, 0xc6, 0xb9, 0x1f, 0xe2, 0xe1, 0x2d, 0xbd, 0x32, 0x3d, 0x23, 0xba, 0x7a, 0x38, 0xdf, 0xec}; #define masking_nonce_len 32 const uint8_t masking_nonce[masking_nonce_len] = { 0x38, 0xfe, 0x59, 0xaf, 0x0d, 0xf2, 0xc7, 0x9f, 0x57, 0xb8, 0x78, 0x02, 0x78, 0xf5, 0xae, 0x47, 0x35, 0x5f, 0xe1, 0xf8, 0x17, 0x11, 0x90, 0x41, 0x95, 0x1c, 0x80, 0xf6, 0x12, 0xfd, 0xfc, 0x6d}; #define oprf_seed_len 64 const uint8_t oprf_seed[oprf_seed_len] = { 0xf4, 0x33, 0xd0, 0x22, 0x7b, 0x0b, 0x9d, 0xd5, 0x4f, 0x7c, 0x44, 0x22, 0xb6, 0x00, 0xe7, 0x64, 0xe4, 0x7f, 0xb5, 0x03, 0xf1, 0xf9, 0xa0, 0xf0, 0xa4, 0x7c, 0x66, 0x06, 0xb0, 0x54, 0xa7, 0xfd, 0xc6, 0x53, 0x47, 0xf1, 0xa0, 0x8f, 0x27, 0x7e, 0x22, 0x35, 0x8b, 0xba, 0xbe, 0x26, 0xf8, 0x23, 0xfc, 0xa8, 0x2c, 0x78, 0x48, 0xe9, 0xa7, 0x56, 0x61, 0xf4, 0xec, 0x5d, 0x5c, 0x19, 0x89, 0xef}; #define password_len 25 const uint8_t password[password_len] = { 0x43, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x48, 0x6f, 0x72, 0x73, 0x65, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x70, 0x6c, 0x65}; #define server_keyshare_seed_len 32 const uint8_t server_keyshare_seed[server_keyshare_seed_len] = { 0x05, 0xa4, 0xf5, 0x42, 0x06, 0xee, 0xf1, 0xba, 0x2f, 0x61, 0x5b, 0xc0, 0xaa, 0x28, 0x5c, 0xb2, 0x2f, 0x26, 0xd1, 0x15, 0x3b, 0x5b, 0x40, 0xa1, 0xe8, 0x5f, 0xf8, 0x0d, 0xa1, 0x2f, 0x98, 0x2f}; #define server_nonce_len 32 const uint8_t server_nonce[server_nonce_len] = { 0x71, 0xcd, 0x99, 0x60, 0xec, 0xef, 0x2f, 0xe0, 0xd0, 0xf7, 0x49, 0x49, 0x86, 0xfa, 0x3d, 0x8b, 0x2b, 0xb0, 0x19, 0x63, 0x53, 0x7e, 0x60, 0xef, 0xb1, 0x39, 0x81, 0xe1, 0x38, 0xe3, 0xd4, 0xa1}; #define server_private_key_len 32 const uint8_t server_private_key[server_private_key_len] = { 0x47, 0x45, 0x1a, 0x85, 0x37, 0x2f, 0x8b, 0x35, 0x37, 0xe2, 0x49, 0xd7, 0xb5, 0x41, 0x88, 0x09, 0x1f, 0xb1, 0x8e, 0xdd, 0xe7, 0x80, 0x94, 0xb4, 0x3e, 0x2b, 0xa4, 0x2b, 0x5e, 0xb8, 0x9f, 0x0d}; #define server_public_key_len 32 const uint8_t server_public_key[server_public_key_len] = { 0xb2, 0xfe, 0x7a, 0xf9, 0xf4, 0x8c, 0xc5, 0x02, 0xd0, 0x16, 0x72, 0x9d, 0x2f, 0xe2, 0x5c, 0xdd, 0x43, 0x3f, 0x2c, 0x4b, 0xc9, 0x04, 0x66, 0x0b, 0x2a, 0x38, 0x2c, 0x9b, 0x79, 0xdf, 0x1a, 0x78}; #define ke1_len 96 const uint8_t ke1[ke1_len] = { 0xc4, 0xde, 0xdb, 0x0b, 0xa6, 0xed, 0x5d, 0x96, 0x5d, 0x6f, 0x25, 0x0f, 0xbe, 0x55, 0x4c, 0xd4, 0x5c, 0xba, 0x5d, 0xfc, 0xce, 0x3c, 0xe8, 0x36, 0xe4, 0xae, 0xe7, 0x78, 0xaa, 0x3c, 0xd4, 0x4d, 0xda, 0x7e, 0x07, 0x37, 0x6d, 0x6d, 0x6f, 0x03, 0x4c, 0xfa, 0x9b, 0xb5, 0x37, 0xd1, 0x1b, 0x8c, 0x6b, 0x42, 0x38, 0xc3, 0x34, 0x33, 0x3d, 0x1f, 0x0a, 0xeb, 0xb3, 0x80, 0xca, 0xe6, 0xa6, 0xcc, 0x6e, 0x29, 0xbe, 0xe5, 0x07, 0x01, 0x49, 0x86, 0x05, 0xb2, 0xc0, 0x85, 0xd7, 0xb2, 0x41, 0xca, 0x15, 0xba, 0x5c, 0x32, 0x02, 0x7d, 0xd2, 0x1b, 0xa4, 0x20, 0xb9, 0x4c, 0xe6, 0x0d, 0xa3, 0x26}; #define ke2_len 320 const uint8_t ke2[ke2_len] = { 0x7e, 0x30, 0x81, 0x40, 0x89, 0x0b, 0xcd, 0xe3, 0x0c, 0xbc, 0xea, 0x28, 0xb0, 0x1e, 0xa1, 0xec, 0xfb, 0xd0, 0x77, 0xcf, 0xf6, 0x2c, 0x4d, 0xef, 0x8e, 0xfa, 0x07, 0x5a, 0xab, 0xcb, 0xb4, 0x71, 0x38, 0xfe, 0x59, 0xaf, 0x0d, 0xf2, 0xc7, 0x9f, 0x57, 0xb8, 0x78, 0x02, 0x78, 0xf5, 0xae, 0x47, 0x35, 0x5f, 0xe1, 0xf8, 0x17, 0x11, 0x90, 0x41, 0x95, 0x1c, 0x80, 0xf6, 0x12, 0xfd, 0xfc, 0x6d, 0xd6, 0xec, 0x60, 0xbc, 0xdb, 0x26, 0xdc, 0x45, 0x5d, 0xdf, 0x3e, 0x71, 0x8f, 0x10, 0x20, 0x49, 0x0c, 0x19, 0x2d, 0x70, 0xdf, 0xc7, 0xe4, 0x03, 0x98, 0x11, 0x79, 0xd8, 0x07, 0x3d, 0x11, 0x46, 0xa4, 0xf9, 0xaa, 0x1c, 0xed, 0x4e, 0x4c, 0xd9, 0x84, 0xc6, 0x57, 0xeb, 0x3b, 0x54, 0xce, 0xd3, 0x84, 0x83, 0x26, 0xf7, 0x03, 0x31, 0x95, 0x3d, 0x91, 0xb0, 0x25, 0x35, 0xaf, 0x44, 0xd9, 0xfe, 0xdc, 0x80, 0x18, 0x8c, 0xa4, 0x67, 0x43, 0xc5, 0x27, 0x86, 0xe0, 0x38, 0x2f, 0x95, 0xad, 0x85, 0xc0, 0x8f, 0x6a, 0xfc, 0xd1, 0xcc, 0xfb, 0xff, 0x95, 0xe2, 0xbd, 0xeb, 0x01, 0x5b, 0x16, 0x6c, 0x6b, 0x20, 0xb9, 0x2f, 0x83, 0x2c, 0xc6, 0xdf, 0x01, 0xe0, 0xb8, 0x6a, 0x7e, 0xfd, 0x92, 0xc1, 0xc8, 0x04, 0xff, 0x86, 0x57, 0x81, 0xfa, 0x93, 0xf2, 0xf2, 0x0b, 0x44, 0x6c, 0x83, 0x71, 0xb6, 0x71, 0xcd, 0x99, 0x60, 0xec, 0xef, 0x2f, 0xe0, 0xd0, 0xf7, 0x49, 0x49, 0x86, 0xfa, 0x3d, 0x8b, 0x2b, 0xb0, 0x19, 0x63, 0x53, 0x7e, 0x60, 0xef, 0xb1, 0x39, 0x81, 0xe1, 0x38, 0xe3, 0xd4, 0xa1, 0xc4, 0xf6, 0x21, 0x98, 0xa9, 0xd6, 0xfa, 0x91, 0x70, 0xc4, 0x2c, 0x3c, 0x71, 0xf1, 0x97, 0x1b, 0x29, 0xeb, 0x1d, 0x5d, 0x0b, 0xd7, 0x33, 0xe4, 0x08, 0x16, 0xc9, 0x1f, 0x79, 0x12, 0xcc, 0x4a, 0x66, 0x0c, 0x48, 0xda, 0xe0, 0x3e, 0x57, 0xaa, 0xa3, 0x8f, 0x3d, 0x0c, 0xff, 0xcf, 0xc2, 0x18, 0x52, 0xeb, 0xc8, 0xb4, 0x05, 0xd1, 0x5b, 0xd6, 0x74, 0x49, 0x45, 0xba, 0x1a, 0x93, 0x43, 0x8a, 0x16, 0x2b, 0x61, 0x11, 0x69, 0x9d, 0x98, 0xa1, 0x6b, 0xb5, 0x5b, 0x7b, 0xdd, 0xdf, 0xe0, 0xfc, 0x56, 0x08, 0xb2, 0x3d, 0xa2, 0x46, 0xe7, 0xbd, 0x73, 0xb4, 0x73, 0x69, 0x16, 0x9c, 0x5c, 0x90}; #define ke3_len 64 const uint8_t ke3[ke3_len] = { 0x44, 0x55, 0xdf, 0x4f, 0x81, 0x0a, 0xc3, 0x1a, 0x67, 0x48, 0x83, 0x58, 0x88, 0x56, 0x4b, 0x53, 0x6e, 0x6d, 0xa5, 0xd9, 0x94, 0x4d, 0xfe, 0xa9, 0xe3, 0x4d, 0xef, 0xb9, 0x57, 0x5f, 0xe5, 0xe2, 0x66, 0x1e, 0xf6, 0x1d, 0x2a, 0xe3, 0x92, 0x9b, 0xcf, 0x57, 0xe5, 0x3d, 0x46, 0x41, 0x13, 0xd3, 0x64, 0x36, 0x5e, 0xb7, 0xd1, 0xa5, 0x7b, 0x62, 0x97, 0x07, 0xca, 0x48, 0xda, 0x18, 0xe4, 0x42}; #define export_key_len 64 const uint8_t export_key[export_key_len] = { 0x1e, 0xf1, 0x5b, 0x4f, 0xa9, 0x9e, 0x8a, 0x85, 0x24, 0x12, 0x45, 0x0a, 0xb7, 0x87, 0x13, 0xaa, 0xd3, 0x0d, 0x21, 0xfa, 0x69, 0x66, 0xc9, 0xb8, 0xc9, 0xfb, 0x32, 0x62, 0xa9, 0x70, 0xdc, 0x62, 0x95, 0x0d, 0x4d, 0xd4, 0xed, 0x62, 0x59, 0x82, 0x29, 0xb1, 0xb7, 0x27, 0x94, 0xfc, 0x03, 0x35, 0x19, 0x9d, 0x9f, 0x7f, 0xcc, 0x6e, 0xae, 0xdd, 0xe9, 0x2c, 0xc0, 0x48, 0x70, 0xe6, 0x3f, 0x16}; #define registration_request_len 32 const uint8_t registration_request[registration_request_len] = { 0x50, 0x59, 0xff, 0x24, 0x9e, 0xb1, 0x55, 0x1b, 0x7c, 0xe4, 0x99, 0x1f, 0x33, 0x36, 0x20, 0x5b, 0xde, 0x44, 0xa1, 0x05, 0xa0, 0x32, 0xe7, 0x47, 0xd2, 0x1b, 0xf3, 0x82, 0xe7, 0x5f, 0x7a, 0x71}; #define registration_response_len 64 const uint8_t registration_response[registration_response_len] = { 0x74, 0x08, 0xa2, 0x68, 0x08, 0x3e, 0x03, 0xab, 0xc7, 0x09, 0x7f, 0xc0, 0x5b, 0x58, 0x78, 0x34, 0x53, 0x90, 0x65, 0xe8, 0x6f, 0xb0, 0xc7, 0xb6, 0x34, 0x2f, 0xcf, 0x5e, 0x01, 0xe5, 0xb0, 0x19, 0xb2, 0xfe, 0x7a, 0xf9, 0xf4, 0x8c, 0xc5, 0x02, 0xd0, 0x16, 0x72, 0x9d, 0x2f, 0xe2, 0x5c, 0xdd, 0x43, 0x3f, 0x2c, 0x4b, 0xc9, 0x04, 0x66, 0x0b, 0x2a, 0x38, 0x2c, 0x9b, 0x79, 0xdf, 0x1a, 0x78}; #define registration_upload_len 192 const uint8_t registration_upload[registration_upload_len] = { 0x76, 0xa8, 0x45, 0x46, 0x4c, 0x68, 0xa5, 0xd2, 0xf7, 0xe4, 0x42, 0x43, 0x6b, 0xb1, 0x42, 0x49, 0x53, 0xb1, 0x7d, 0x3e, 0x2e, 0x28, 0x9c, 0xcb, 0xac, 0xca, 0xfb, 0x57, 0xac, 0x5c, 0x36, 0x75, 0x1a, 0xc5, 0x84, 0x43, 0x83, 0xc7, 0x70, 0x80, 0x77, 0xde, 0xa4, 0x1c, 0xbe, 0xfe, 0x2f, 0xa1, 0x57, 0x24, 0xf4, 0x49, 0xe5, 0x35, 0xdd, 0x7d, 0xd5, 0x62, 0xe6, 0x6f, 0x5e, 0xcf, 0xb9, 0x58, 0x64, 0xea, 0xdd, 0xde, 0xc9, 0xdb, 0x58, 0x74, 0x95, 0x99, 0x05, 0x11, 0x7d, 0xad, 0x40, 0xa4, 0x52, 0x41, 0x11, 0x84, 0x97, 0x99, 0x28, 0x1f, 0xef, 0xe3, 0xc5, 0x1f, 0xa8, 0x27, 0x85, 0xc5, 0xac, 0x13, 0x17, 0x1b, 0x2f, 0x17, 0xbc, 0x2c, 0x74, 0x99, 0x7f, 0x0f, 0xce, 0x1e, 0x1f, 0x35, 0xbe, 0xc6, 0xb9, 0x1f, 0xe2, 0xe1, 0x2d, 0xbd, 0x32, 0x3d, 0x23, 0xba, 0x7a, 0x38, 0xdf, 0xec, 0x63, 0x4b, 0x0f, 0x5b, 0x96, 0x10, 0x9c, 0x19, 0x8a, 0x80, 0x27, 0xda, 0x51, 0x85, 0x4c, 0x35, 0xbe, 0xe9, 0x0d, 0x1e, 0x1c, 0x78, 0x18, 0x06, 0xd0, 0x7d, 0x49, 0xb7, 0x6d, 0xe6, 0xa2, 0x8b, 0x8d, 0x9e, 0x9b, 0x6c, 0x93, 0xb9, 0xf8, 0xb6, 0x4d, 0x16, 0xdd, 0xdd, 0x9c, 0x5b, 0xfb, 0x5f, 0xea, 0x48, 0xee, 0x8f, 0xd2, 0xf7, 0x50, 0x12, 0xa8, 0xb3, 0x08, 0x60, 0x5c, 0xdd, 0x8b, 0xa5}; #define session_key_len 64 const uint8_t session_key[session_key_len] = { 0x42, 0xaf, 0xde, 0x6f, 0x5a, 0xca, 0x0c, 0xfa, 0x5c, 0x16, 0x37, 0x63, 0xfb, 0xad, 0x55, 0xe7, 0x3a, 0x41, 0xdb, 0x6b, 0x41, 0xbc, 0x87, 0xb8, 0xe7, 0xb6, 0x22, 0x14, 0xa8, 0xee, 0xdc, 0x67, 0x31, 0xfa, 0x3c, 0xb8, 0x57, 0xd6, 0x57, 0xab, 0x9b, 0x37, 0x64, 0xb8, 0x9a, 0x84, 0xe9, 0x1e, 0xbc, 0xb4, 0x78, 0x51, 0x66, 0xfb, 0xb0, 0x2c, 0xed, 0xfc, 0xbd, 0xfd, 0xa2, 0x15, 0xb9, 0x6f}; #endif libopaque-0.99.3/src/tests/munit/000077500000000000000000000000001452546740600166775ustar00rootroot00000000000000libopaque-0.99.3/src/tests/munit/COPYING000066400000000000000000000021221452546740600177270ustar00rootroot00000000000000µnit Testing Framework Copyright (c) 2013-2016 Evan Nemerson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. libopaque-0.99.3/src/tests/munit/Makefile000066400000000000000000000017761452546740600203520ustar00rootroot00000000000000# Using µnit is very simple; just include the header and add the C # file to your sources. That said, here is a simple Makefile to build # the example. CSTD:=99 OPENMP:=n ASAN:=n UBSAN:=n EXTENSION:= TEST_ENV:= CFLAGS:= AGGRESSIVE_WARNINGS=n ifeq ($(CC),pgcc) CFLAGS+=-c$(CSTD) else CFLAGS+=-std=c$(CSTD) endif ifeq ($(OPENMP),y) ifeq ($(CC),pgcc) CFLAGS+=-mp else CFLAGS+=-fopenmp endif endif ifneq ($(SANITIZER),) CFLAGS+=-fsanitize=$(SANITIZER) endif ifneq ($(CC),pgcc) ifeq ($(EXTRA_WARNINGS),y) CFLAGS+=-Wall -Wextra -Werror endif ifeq ($(ASAN),y) CFLAGS+=-fsanitize=address endif ifeq ($(UBSAN),y) CFLAGS+=-fsanitize=undefined endif endif example$(EXTENSION): munit.h munit.c example.c $(CC) $(CFLAGS) -o $@ munit.c example.c test: $(TEST_ENV) ./example$(EXTENSION) clean: rm -f example$(EXTENSION) all: example$(EXTENSION) libopaque-0.99.3/src/tests/munit/README.md000066400000000000000000000033731452546740600201640ustar00rootroot00000000000000# µnit µnit is a small but full-featured unit testing framework for C. It has no dependencies (beyond libc), is permissively licensed (MIT), and is easy to include into any project. For more information, see [the µnit web site](https://nemequ.github.io/munit). [![Build status](https://travis-ci.org/nemequ/munit.svg?branch=master)](https://travis-ci.org/nemequ/munit) [![Windows build status](https://ci.appveyor.com/api/projects/status/db515g5ifcwjohq7/branch/master?svg=true)](https://ci.appveyor.com/project/quixdb/munit/branch/master) ## Features Features µnit currently includes include: * Handy assertion macros which make for nice error messages. * Reproducible cross-platform random number generation, including support for supplying a seed via CLI. * Timing of both wall-clock and CPU time. * Parameterized tests. * Nested test suites. * Flexible CLI. * Forking ([except on Windows](https://github.com/nemequ/munit/issues/2)). * Hiding output of successful tests. Features µnit does not currently include, but some day may include (a.k.a., if you file a PR…), include: * [TAP](http://testanything.org/) support; feel free to discuss in [issue #1](https://github.com/nemequ/munit/issues/1) ### Include into your project with meson In your `subprojects` folder put a `munit.wrap` file containing: ``` [wrap-git] directory=munit url=https://github.com/nemequ/munit/ revision=head ``` Then you can use a subproject fallback when you include munit as a dependency to your project: `dependency('munit', fallback: ['munit', 'munit_dep'])` ## Documentation See [the µnit web site](https://nemequ.github.io/munit). Additionally, there is a heavily-commented [example.c](https://github.com/nemequ/munit/blob/master/example.c) in the repository. libopaque-0.99.3/src/tests/munit/example.c000066400000000000000000000333661452546740600205110ustar00rootroot00000000000000/* Example file for using µnit. * * µnit is MIT-licensed, but for this file and this file alone: * * To the extent possible under law, the author(s) of this file have * waived all copyright and related or neighboring rights to this * work. See for * details. *********************************************************************/ #include "munit.h" /* This is just to disable an MSVC warning about conditional * expressions being constant, which you shouldn't have to do for your * code. It's only here because we want to be able to do silly things * like assert that 0 != 1 for our demo. */ #if defined(_MSC_VER) #pragma warning(disable: 4127) #endif /* Tests are functions that return void, and take a single void* * parameter. We'll get to what that parameter is later. */ static MunitResult test_compare(const MunitParameter params[], void* data) { /* We'll use these later */ const unsigned char val_uchar = 'b'; const short val_short = 1729; double pi = 3.141592654; char* stewardesses = "stewardesses"; char* most_fun_word_to_type; /* These are just to silence compiler warnings about the parameters * being unused. */ (void) params; (void) data; /* Let's start with the basics. */ munit_assert(0 != 1); /* There is also the more verbose, though slightly more descriptive munit_assert_true/false: */ munit_assert_false(0); /* You can also call munit_error and munit_errorf yourself. We * won't do it is used to indicate a failure, but here is what it * would look like: */ /* munit_error("FAIL"); */ /* munit_errorf("Goodbye, cruel %s", "world"); */ /* There are macros for comparing lots of types. */ munit_assert_char('a', ==, 'a'); /* Sure, you could just assert('a' == 'a'), but if you did that, a * failed assertion would just say something like "assertion failed: * val_uchar == 'b'". µnit will tell you the actual values, so a * failure here would result in something like "assertion failed: * val_uchar == 'b' ('X' == 'b')." */ munit_assert_uchar(val_uchar, ==, 'b'); /* Obviously we can handle values larger than 'char' and 'uchar'. * There are versions for char, short, int, long, long long, * int8/16/32/64_t, as well as the unsigned versions of them all. */ munit_assert_short(42, <, val_short); /* There is also support for size_t. * * The longest word in English without repeating any letters is * "uncopyrightables", which has uncopyrightable (and * dermatoglyphics, which is the study of fingerprints) beat by a * character */ munit_assert_size(strlen("uncopyrightables"), >, strlen("dermatoglyphics")); /* Of course there is also support for doubles and floats. */ munit_assert_double(pi, ==, 3.141592654); /* If you want to compare two doubles for equality, you might want * to consider using munit_assert_double_equal. It compares two * doubles for equality within a precison of 1.0 x 10^-(precision). * Note that precision (the third argument to the macro) needs to be * fully evaluated to an integer by the preprocessor so µnit doesn't * have to depend pow, which is often in libm not libc. */ munit_assert_double_equal(3.141592654, 3.141592653589793, 9); /* And if you want to check strings for equality (or inequality), * there is munit_assert_string_equal/not_equal. * * "stewardesses" is the longest word you can type on a QWERTY * keyboard with only one hand, which makes it loads of fun to type. * If I'm going to have to type a string repeatedly, let's make it a * good one! */ munit_assert_string_equal(stewardesses, "stewardesses"); /* A personal favorite macro which is fantastic if you're working * with binary data, is the one which naïvely checks two blobs of * memory for equality. If this fails it will tell you the offset * of the first differing byte. */ munit_assert_memory_equal(7, stewardesses, "steward"); /* You can also make sure that two blobs differ *somewhere*: */ munit_assert_memory_not_equal(8, stewardesses, "steward"); /* There are equal/not_equal macros for pointers, too: */ most_fun_word_to_type = stewardesses; munit_assert_ptr_equal(most_fun_word_to_type, stewardesses); /* And null/not_null */ munit_assert_null(NULL); munit_assert_not_null(most_fun_word_to_type); /* Lets verify that the data parameter is what we expected. We'll * see where this comes from in a bit. * * Note that the casting isn't usually required; if you give this * function a real pointer (instead of a number like 0xdeadbeef) it * would work as expected. */ munit_assert_ptr_equal(data, (void*)(uintptr_t)0xdeadbeef); return MUNIT_OK; } static MunitResult test_rand(const MunitParameter params[], void* user_data) { int random_int; double random_dbl; munit_uint8_t data[5]; (void) params; (void) user_data; /* One thing missing from a lot of unit testing frameworks is a * random number generator. You can't just use srand/rand because * the implementation varies across different platforms, and it's * important to be able to look at the seed used in a failing test * to see if you can reproduce it. Some randomness is a fantastic * thing to have in your tests, I don't know why more people don't * do it... * * µnit's PRNG is re-seeded with the same value for each iteration * of each test. The seed is retrieved from the MUNIT_SEED * envirnment variable or, if none is provided, one will be * (pseudo-)randomly generated. */ /* If you need an integer in a given range */ random_int = munit_rand_int_range(128, 4096); munit_assert_int(random_int, >=, 128); munit_assert_int(random_int, <=, 4096); /* Or maybe you want a double, between 0 and 1: */ random_dbl = munit_rand_double(); munit_assert_double(random_dbl, >=, 0.0); munit_assert_double(random_dbl, <=, 1.0); /* Of course, you want to be able to reproduce bugs discovered * during testing, so every time the tests are run they print the * random seed used. When you want to reproduce a result, just put * that random seed in the MUNIT_SEED environment variable; it even * works on different platforms. * * If you want this to pass, use 0xdeadbeef as the random seed and * uncomment the next line of code. Note that the PRNG is not * re-seeded between iterations of the same test, so this will only * work on the first iteration. */ /* munit_assert_uint32(munit_rand_uint32(), ==, 1306447409); */ /* You can also get blobs of random memory: */ munit_rand_memory(sizeof(data), data); return MUNIT_OK; } /* This test case shows how to accept parameters. We'll see how to * specify them soon. * * By default, every possible variation of a parameterized test is * run, but you can specify parameters manually if you want to only * run specific test(s), or you can pass the --single argument to the * CLI to have the harness simply choose one variation at random * instead of running them all. */ static MunitResult test_parameters(const MunitParameter params[], void* user_data) { const char* foo; const char* bar; (void) user_data; /* The "foo" parameter is specified as one of the following values: * "one", "two", or "three". */ foo = munit_parameters_get(params, "foo"); /* Similarly, "bar" is one of "four", "five", or "six". */ bar = munit_parameters_get(params, "bar"); /* "baz" is a bit more complicated. We don't actually specify a * list of valid values, so by default NULL is passed. However, the * CLI will accept any value. This is a good way to have a value * that is usually selected randomly by the test, but can be * overridden on the command line if desired. */ /* const char* baz = munit_parameters_get(params, "baz"); */ /* Notice that we're returning MUNIT_FAIL instead of writing an * error message. Error messages are generally preferable, since * they make it easier to diagnose the issue, but this is an * option. * * Possible values are: * - MUNIT_OK: Sucess * - MUNIT_FAIL: Failure * - MUNIT_SKIP: The test was skipped; usually this happens when a * particular feature isn't in use. For example, if you're * writing a test which uses a Wayland-only feature, but your * application is running on X11. * - MUNIT_ERROR: The test failed, but not because of anything you * wanted to test. For example, maybe your test downloads a * remote resource and tries to parse it, but the network was * down. */ if (strcmp(foo, "one") != 0 && strcmp(foo, "two") != 0 && strcmp(foo, "three") != 0) return MUNIT_FAIL; if (strcmp(bar, "red") != 0 && strcmp(bar, "green") != 0 && strcmp(bar, "blue") != 0) return MUNIT_FAIL; return MUNIT_OK; } /* The setup function, if you provide one, for a test will be run * before the test, and the return value will be passed as the sole * parameter to the test function. */ static void* test_compare_setup(const MunitParameter params[], void* user_data) { (void) params; munit_assert_string_equal(user_data, "µnit"); return (void*) (uintptr_t) 0xdeadbeef; } /* To clean up after a test, you can use a tear down function. The * fixture argument is the value returned by the setup function * above. */ static void test_compare_tear_down(void* fixture) { munit_assert_ptr_equal(fixture, (void*)(uintptr_t)0xdeadbeef); } static char* foo_params[] = { (char*) "one", (char*) "two", (char*) "three", NULL }; static char* bar_params[] = { (char*) "red", (char*) "green", (char*) "blue", NULL }; static MunitParameterEnum test_params[] = { { (char*) "foo", foo_params }, { (char*) "bar", bar_params }, { (char*) "baz", NULL }, { NULL, NULL }, }; /* Creating a test suite is pretty simple. First, you'll need an * array of tests: */ static MunitTest test_suite_tests[] = { { /* The name is just a unique human-readable way to identify the * test. You can use it to run a specific test if you want, but * usually it's mostly decorative. */ (char*) "/example/compare", /* You probably won't be surprised to learn that the tests are * functions. */ test_compare, /* If you want, you can supply a function to set up a fixture. If * you supply NULL, the user_data parameter from munit_suite_main * will be used directly. If, however, you provide a callback * here the user_data parameter will be passed to this callback, * and the return value from this callback will be passed to the * test function. * * For our example we don't really need a fixture, but lets * provide one anyways. */ test_compare_setup, /* If you passed a callback for the fixture setup function, you * may want to pass a corresponding callback here to reverse the * operation. */ test_compare_tear_down, /* Finally, there is a bitmask for options you can pass here. You * can provide either MUNIT_TEST_OPTION_NONE or 0 here to use the * defaults. */ MUNIT_TEST_OPTION_NONE, NULL }, /* Usually this is written in a much more compact format; all these * comments kind of ruin that, though. Here is how you'll usually * see entries written: */ { (char*) "/example/rand", test_rand, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, /* To tell the test runner when the array is over, just add a NULL * entry at the end. */ { (char*) "/example/parameters", test_parameters, NULL, NULL, MUNIT_TEST_OPTION_NONE, test_params }, { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } }; /* If you wanted to have your test suite run other test suites you * could declare an array of them. Of course each sub-suite can * contain more suites, etc. */ /* static const MunitSuite other_suites[] = { */ /* { "/second", test_suite_tests, NULL, 1, MUNIT_SUITE_OPTION_NONE }, */ /* { NULL, NULL, NULL, 0, MUNIT_SUITE_OPTION_NONE } */ /* }; */ /* Now we'll actually declare the test suite. You could do this in * the main function, or on the heap, or whatever you want. */ static const MunitSuite test_suite = { /* This string will be prepended to all test names in this suite; * for example, "/example/rand" will become "/µnit/example/rand". * Note that, while it doesn't really matter for the top-level * suite, NULL signal the end of an array of tests; you should use * an empty string ("") instead. */ (char*) "", /* The first parameter is the array of test suites. */ test_suite_tests, /* In addition to containing test cases, suites can contain other * test suites. This isn't necessary in this example, but it can be * a great help to projects with lots of tests by making it easier * to spread the tests across many files. This is where you would * put "other_suites" (which is commented out above). */ NULL, /* An interesting feature of µnit is that it supports automatically * running multiple iterations of the tests. This is usually only * interesting if you make use of the PRNG to randomize your tests * cases a bit, or if you are doing performance testing and want to * average multiple runs. 0 is an alias for 1. */ 1, /* Just like MUNIT_TEST_OPTION_NONE, you can provide * MUNIT_SUITE_OPTION_NONE or 0 to use the default settings. */ MUNIT_SUITE_OPTION_NONE }; /* This is only necessary for EXIT_SUCCESS and EXIT_FAILURE, which you * *should* be using but probably aren't (no, zero and non-zero don't * always mean success and failure). I guess my point is that nothing * about µnit requires it. */ #include int main(int argc, char* argv[MUNIT_ARRAY_PARAM(argc + 1)]) { /* Finally, we'll actually run our test suite! That second argument * is the user_data parameter which will be passed either to the * test or (if provided) the fixture setup function. */ return munit_suite_main(&test_suite, (void*) "µnit", argc, argv); } libopaque-0.99.3/src/tests/munit/meson.build000066400000000000000000000016641452546740600210500ustar00rootroot00000000000000project('munit', 'c') conf_data = configuration_data() conf_data.set('version', '0.2.0') add_project_arguments('-std=c99', language : 'c') cc = meson.get_compiler('c') root_include = include_directories('.') munit = library('munit', ['munit.c'], install: meson.is_subproject()) if meson.is_subproject() munit_dep = declare_dependency( include_directories : root_include, link_with : munit) else # standalone install install_headers('munit.h') pkg = import('pkgconfig') pkg.generate(name: 'munit', description: 'µnit Testing Library for C', version: conf_data.get('version'), libraries: munit) # compile the demo project munit_example_src = files('example.c') munit_example = executable('munit_example', munit_example_src, include_directories: root_include, link_with: munit) test('munit example test', munit_example) endif libopaque-0.99.3/src/tests/munit/munit.c000066400000000000000000002043501452546740600202030ustar00rootroot00000000000000/* Copyright (c) 2013-2018 Evan Nemerson * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /*** Configuration ***/ /* This is just where the output from the test goes. It's really just * meant to let you choose stdout or stderr, but if anyone really want * to direct it to a file let me know, it would be fairly easy to * support. */ #if !defined(MUNIT_OUTPUT_FILE) # define MUNIT_OUTPUT_FILE stdout #endif /* This is a bit more useful; it tells µnit how to format the seconds in * timed tests. If your tests run for longer you might want to reduce * it, and if your computer is really fast and your tests are tiny you * can increase it. */ #if !defined(MUNIT_TEST_TIME_FORMAT) # define MUNIT_TEST_TIME_FORMAT "0.8f" #endif /* If you have long test names you might want to consider bumping * this. The result information takes 43 characters. */ #if !defined(MUNIT_TEST_NAME_LEN) # define MUNIT_TEST_NAME_LEN 37 #endif /* If you don't like the timing information, you can disable it by * defining MUNIT_DISABLE_TIMING. */ #if !defined(MUNIT_DISABLE_TIMING) # define MUNIT_ENABLE_TIMING #endif /*** End configuration ***/ #if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200809L) # undef _POSIX_C_SOURCE #endif #if !defined(_POSIX_C_SOURCE) # define _POSIX_C_SOURCE 200809L #endif /* Solaris freaks out if you try to use a POSIX or SUS standard without * the "right" C standard. */ #if defined(_XOPEN_SOURCE) # undef _XOPEN_SOURCE #endif #if defined(__STDC_VERSION__) # if __STDC_VERSION__ >= 201112L # define _XOPEN_SOURCE 700 # elif __STDC_VERSION__ >= 199901L # define _XOPEN_SOURCE 600 # endif #endif /* Because, according to Microsoft, POSIX is deprecated. You've got * to appreciate the chutzpah. */ #if defined(_MSC_VER) && !defined(_CRT_NONSTDC_NO_DEPRECATE) # define _CRT_NONSTDC_NO_DEPRECATE #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # include #elif defined(_WIN32) /* https://msdn.microsoft.com/en-us/library/tf4dy80a.aspx */ #endif #include #include #include #include #include #include #include #include #if !defined(MUNIT_NO_NL_LANGINFO) && !defined(_WIN32) #define MUNIT_NL_LANGINFO #include #include #include #endif #if !defined(_WIN32) # include # include # include #else # include # include # include # if !defined(STDERR_FILENO) # define STDERR_FILENO _fileno(stderr) # endif #endif #include "munit.h" #define MUNIT_STRINGIFY(x) #x #define MUNIT_XSTRINGIFY(x) MUNIT_STRINGIFY(x) #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) # define MUNIT_THREAD_LOCAL __thread #elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) || defined(_Thread_local) # define MUNIT_THREAD_LOCAL _Thread_local #elif defined(_WIN32) # define MUNIT_THREAD_LOCAL __declspec(thread) #endif /* MSVC 12.0 will emit a warning at /W4 for code like 'do { ... } * while (0)', or 'do { ... } while (1)'. I'm pretty sure nobody * at Microsoft compiles with /W4. */ #if defined(_MSC_VER) && (_MSC_VER <= 1800) #pragma warning(disable: 4127) #endif #if defined(_WIN32) || defined(__EMSCRIPTEN__) # define MUNIT_NO_FORK #endif #if defined(__EMSCRIPTEN__) # define MUNIT_NO_BUFFER #endif /*** Logging ***/ static MunitLogLevel munit_log_level_visible = MUNIT_LOG_INFO; static MunitLogLevel munit_log_level_fatal = MUNIT_LOG_ERROR; #if defined(MUNIT_THREAD_LOCAL) static MUNIT_THREAD_LOCAL munit_bool munit_error_jmp_buf_valid = 0; static MUNIT_THREAD_LOCAL jmp_buf munit_error_jmp_buf; #endif /* At certain warning levels, mingw will trigger warnings about * suggesting the format attribute, which we've explicity *not* set * because it will then choke on our attempts to use the MS-specific * I64 modifier for size_t (which we have to use since MSVC doesn't * support the C99 z modifier). */ #if defined(__MINGW32__) || defined(__MINGW64__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wsuggest-attribute=format" #endif MUNIT_PRINTF(5,0) static void munit_logf_exv(MunitLogLevel level, FILE* fp, const char* filename, int line, const char* format, va_list ap) { if (level < munit_log_level_visible) return; switch (level) { case MUNIT_LOG_DEBUG: fputs("Debug", fp); break; case MUNIT_LOG_INFO: fputs("Info", fp); break; case MUNIT_LOG_WARNING: fputs("Warning", fp); break; case MUNIT_LOG_ERROR: fputs("Error", fp); break; default: munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Invalid log level (%d)", level); return; } fputs(": ", fp); if (filename != NULL) fprintf(fp, "%s:%d: ", filename, line); vfprintf(fp, format, ap); fputc('\n', fp); } MUNIT_PRINTF(3,4) static void munit_logf_internal(MunitLogLevel level, FILE* fp, const char* format, ...) { va_list ap; va_start(ap, format); munit_logf_exv(level, fp, NULL, 0, format, ap); va_end(ap); } static void munit_log_internal(MunitLogLevel level, FILE* fp, const char* message) { munit_logf_internal(level, fp, "%s", message); } void munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...) { va_list ap; va_start(ap, format); munit_logf_exv(level, stderr, filename, line, format, ap); va_end(ap); if (level >= munit_log_level_fatal) { #if defined(MUNIT_THREAD_LOCAL) if (munit_error_jmp_buf_valid) longjmp(munit_error_jmp_buf, 1); #endif abort(); } } void munit_errorf_ex(const char* filename, int line, const char* format, ...) { va_list ap; va_start(ap, format); munit_logf_exv(MUNIT_LOG_ERROR, stderr, filename, line, format, ap); va_end(ap); #if defined(MUNIT_THREAD_LOCAL) if (munit_error_jmp_buf_valid) longjmp(munit_error_jmp_buf, 1); #endif abort(); } #if defined(__MINGW32__) || defined(__MINGW64__) #pragma GCC diagnostic pop #endif #if !defined(MUNIT_STRERROR_LEN) # define MUNIT_STRERROR_LEN 80 #endif static void munit_log_errno(MunitLogLevel level, FILE* fp, const char* msg) { #if defined(MUNIT_NO_STRERROR_R) || (defined(__MINGW32__) && !defined(MINGW_HAS_SECURE_API)) munit_logf_internal(level, fp, "%s: %s (%d)", msg, strerror(errno), errno); #else char munit_error_str[MUNIT_STRERROR_LEN]; munit_error_str[0] = '\0'; #if !defined(_WIN32) strerror_r(errno, munit_error_str, MUNIT_STRERROR_LEN); #else strerror_s(munit_error_str, MUNIT_STRERROR_LEN, errno); #endif munit_logf_internal(level, fp, "%s: %s (%d)", msg, munit_error_str, errno); #endif } /*** Memory allocation ***/ void* munit_malloc_ex(const char* filename, int line, size_t size) { void* ptr; if (size == 0) return NULL; ptr = calloc(1, size); if (MUNIT_UNLIKELY(ptr == NULL)) { munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Failed to allocate %" MUNIT_SIZE_MODIFIER "u bytes.", size); } return ptr; } /*** Timer code ***/ #if defined(MUNIT_ENABLE_TIMING) #define psnip_uint64_t munit_uint64_t #define psnip_uint32_t munit_uint32_t /* Code copied from portable-snippets * . If you need to * change something, please do it there so we can keep the code in * sync. */ /* Clocks (v1) * Portable Snippets - https://gitub.com/nemequ/portable-snippets * Created by Evan Nemerson * * To the extent possible under law, the authors have waived all * copyright and related or neighboring rights to this code. For * details, see the Creative Commons Zero 1.0 Universal license at * https://creativecommons.org/publicdomain/zero/1.0/ */ #if !defined(PSNIP_CLOCK_H) #define PSNIP_CLOCK_H #if !defined(psnip_uint64_t) # include "../exact-int/exact-int.h" #endif #if !defined(PSNIP_CLOCK_STATIC_INLINE) # if defined(__GNUC__) # define PSNIP_CLOCK__COMPILER_ATTRIBUTES __attribute__((__unused__)) # else # define PSNIP_CLOCK__COMPILER_ATTRIBUTES # endif # define PSNIP_CLOCK__FUNCTION PSNIP_CLOCK__COMPILER_ATTRIBUTES static #endif enum PsnipClockType { /* This clock provides the current time, in units since 1970-01-01 * 00:00:00 UTC not including leap seconds. In other words, UNIX * time. Keep in mind that this clock doesn't account for leap * seconds, and can go backwards (think NTP adjustments). */ PSNIP_CLOCK_TYPE_WALL = 1, /* The CPU time is a clock which increases only when the current * process is active (i.e., it doesn't increment while blocking on * I/O). */ PSNIP_CLOCK_TYPE_CPU = 2, /* Monotonic time is always running (unlike CPU time), but it only ever moves forward unless you reboot the system. Things like NTP adjustments have no effect on this clock. */ PSNIP_CLOCK_TYPE_MONOTONIC = 3 }; struct PsnipClockTimespec { psnip_uint64_t seconds; psnip_uint64_t nanoseconds; }; /* Methods we support: */ #define PSNIP_CLOCK_METHOD_CLOCK_GETTIME 1 #define PSNIP_CLOCK_METHOD_TIME 2 #define PSNIP_CLOCK_METHOD_GETTIMEOFDAY 3 #define PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER 4 #define PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME 5 #define PSNIP_CLOCK_METHOD_CLOCK 6 #define PSNIP_CLOCK_METHOD_GETPROCESSTIMES 7 #define PSNIP_CLOCK_METHOD_GETRUSAGE 8 #define PSNIP_CLOCK_METHOD_GETSYSTEMTIMEPRECISEASFILETIME 9 #define PSNIP_CLOCK_METHOD_GETTICKCOUNT64 10 #include #if defined(HEDLEY_UNREACHABLE) # define PSNIP_CLOCK_UNREACHABLE() HEDLEY_UNREACHABLE() #else # define PSNIP_CLOCK_UNREACHABLE() assert(0) #endif /* Choose an implementation */ /* #undef PSNIP_CLOCK_WALL_METHOD */ /* #undef PSNIP_CLOCK_CPU_METHOD */ /* #undef PSNIP_CLOCK_MONOTONIC_METHOD */ /* We want to be able to detect the libc implementation, so we include ( isn't available everywhere). */ #if defined(__unix__) || defined(__unix) || defined(__linux__) # include # include #endif #if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) /* These are known to work without librt. If you know of others * please let us know so we can add them. */ # if \ (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17))) || \ (defined(__FreeBSD__)) # define PSNIP_CLOCK_HAVE_CLOCK_GETTIME # elif !defined(PSNIP_CLOCK_NO_LIBRT) # define PSNIP_CLOCK_HAVE_CLOCK_GETTIME # endif #endif #if defined(_WIN32) # if !defined(PSNIP_CLOCK_CPU_METHOD) # define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_GETPROCESSTIMES # endif # if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) # define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER # endif #endif #if defined(__MACH__) && !defined(__gnu_hurd__) # if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) # define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME # endif #endif #if defined(PSNIP_CLOCK_HAVE_CLOCK_GETTIME) # include # if !defined(PSNIP_CLOCK_WALL_METHOD) # if defined(CLOCK_REALTIME_PRECISE) # define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME_PRECISE # elif !defined(__sun) # define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME # endif # endif # if !defined(PSNIP_CLOCK_CPU_METHOD) # if defined(_POSIX_CPUTIME) || defined(CLOCK_PROCESS_CPUTIME_ID) # define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_PROCESS_CPUTIME_ID # elif defined(CLOCK_VIRTUAL) # define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_VIRTUAL # endif # endif # if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) # if defined(CLOCK_MONOTONIC_RAW) # define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC # elif defined(CLOCK_MONOTONIC_PRECISE) # define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC_PRECISE # elif defined(_POSIX_MONOTONIC_CLOCK) || defined(CLOCK_MONOTONIC) # define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME # define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC # endif # endif #endif #if defined(_POSIX_VERSION) && (_POSIX_VERSION >= 200112L) # if !defined(PSNIP_CLOCK_WALL_METHOD) # define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_GETTIMEOFDAY # endif #endif #if !defined(PSNIP_CLOCK_WALL_METHOD) # define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_TIME #endif #if !defined(PSNIP_CLOCK_CPU_METHOD) # define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK #endif /* Primarily here for testing. */ #if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) && defined(PSNIP_CLOCK_REQUIRE_MONOTONIC) # error No monotonic clock found. #endif /* Implementations */ #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_TIME)) # include #endif #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) # include #endif #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) # include #endif #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) # include # include #endif #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) # include # include # include #endif /*** Implementations ***/ #define PSNIP_CLOCK_NSEC_PER_SEC ((psnip_uint32_t) (1000000000ULL)) #if \ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) PSNIP_CLOCK__FUNCTION psnip_uint32_t psnip_clock__clock_getres (clockid_t clk_id) { struct timespec res; int r; r = clock_getres(clk_id, &res); if (r != 0) return 0; return (psnip_uint32_t) (PSNIP_CLOCK_NSEC_PER_SEC / res.tv_nsec); } PSNIP_CLOCK__FUNCTION int psnip_clock__clock_gettime (clockid_t clk_id, struct PsnipClockTimespec* res) { struct timespec ts; if (clock_gettime(clk_id, &ts) != 0) return -10; res->seconds = (psnip_uint64_t) (ts.tv_sec); res->nanoseconds = (psnip_uint64_t) (ts.tv_nsec); return 0; } #endif PSNIP_CLOCK__FUNCTION psnip_uint32_t psnip_clock_wall_get_precision (void) { #if !defined(PSNIP_CLOCK_WALL_METHOD) return 0; #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_WALL); #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY return 1000000; #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME return 1; #else return 0; #endif } PSNIP_CLOCK__FUNCTION int psnip_clock_wall_get_time (struct PsnipClockTimespec* res) { (void) res; #if !defined(PSNIP_CLOCK_WALL_METHOD) return -2; #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_WALL, res); #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME res->seconds = time(NULL); res->nanoseconds = 0; #elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY struct timeval tv; if (gettimeofday(&tv, NULL) != 0) return -6; res->seconds = tv.tv_sec; res->nanoseconds = tv.tv_usec * 1000; #else return -2; #endif return 0; } PSNIP_CLOCK__FUNCTION psnip_uint32_t psnip_clock_cpu_get_precision (void) { #if !defined(PSNIP_CLOCK_CPU_METHOD) return 0; #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_CPU); #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK return CLOCKS_PER_SEC; #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES return PSNIP_CLOCK_NSEC_PER_SEC / 100; #else return 0; #endif } PSNIP_CLOCK__FUNCTION int psnip_clock_cpu_get_time (struct PsnipClockTimespec* res) { #if !defined(PSNIP_CLOCK_CPU_METHOD) (void) res; return -2; #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_CPU, res); #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK clock_t t = clock(); if (t == ((clock_t) -1)) return -5; res->seconds = t / CLOCKS_PER_SEC; res->nanoseconds = (t % CLOCKS_PER_SEC) * (PSNIP_CLOCK_NSEC_PER_SEC / CLOCKS_PER_SEC); #elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES FILETIME CreationTime, ExitTime, KernelTime, UserTime; LARGE_INTEGER date, adjust; if (!GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime)) return -7; /* http://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/ */ date.HighPart = UserTime.dwHighDateTime; date.LowPart = UserTime.dwLowDateTime; adjust.QuadPart = 11644473600000 * 10000; date.QuadPart -= adjust.QuadPart; res->seconds = date.QuadPart / 10000000; res->nanoseconds = (date.QuadPart % 10000000) * (PSNIP_CLOCK_NSEC_PER_SEC / 100); #elif PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE struct rusage usage; if (getrusage(RUSAGE_SELF, &usage) != 0) return -8; res->seconds = usage.ru_utime.tv_sec; res->nanoseconds = tv.tv_usec * 1000; #else (void) res; return -2; #endif return 0; } PSNIP_CLOCK__FUNCTION psnip_uint32_t psnip_clock_monotonic_get_precision (void) { #if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) return 0; #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC); #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME static mach_timebase_info_data_t tbi = { 0, }; if (tbi.denom == 0) mach_timebase_info(&tbi); return (psnip_uint32_t) (tbi.numer / tbi.denom); #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64 return 1000; #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER LARGE_INTEGER Frequency; QueryPerformanceFrequency(&Frequency); return (psnip_uint32_t) ((Frequency.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC) ? PSNIP_CLOCK_NSEC_PER_SEC : Frequency.QuadPart); #else return 0; #endif } PSNIP_CLOCK__FUNCTION int psnip_clock_monotonic_get_time (struct PsnipClockTimespec* res) { #if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) (void) res; return -2; #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC, res); #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME psnip_uint64_t nsec = mach_absolute_time(); static mach_timebase_info_data_t tbi = { 0, }; if (tbi.denom == 0) mach_timebase_info(&tbi); nsec *= ((psnip_uint64_t) tbi.numer) / ((psnip_uint64_t) tbi.denom); res->seconds = nsec / PSNIP_CLOCK_NSEC_PER_SEC; res->nanoseconds = nsec % PSNIP_CLOCK_NSEC_PER_SEC; #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER LARGE_INTEGER t, f; if (QueryPerformanceCounter(&t) == 0) return -12; QueryPerformanceFrequency(&f); res->seconds = t.QuadPart / f.QuadPart; res->nanoseconds = t.QuadPart % f.QuadPart; if (f.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC) res->nanoseconds /= f.QuadPart / PSNIP_CLOCK_NSEC_PER_SEC; else res->nanoseconds *= PSNIP_CLOCK_NSEC_PER_SEC / f.QuadPart; #elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64 const ULONGLONG msec = GetTickCount64(); res->seconds = msec / 1000; res->nanoseconds = sec % 1000; #else return -2; #endif return 0; } /* Returns the number of ticks per second for the specified clock. * For example, a clock with millisecond precision would return 1000, * and a clock with 1 second (such as the time() function) would * return 1. * * If the requested clock isn't available, it will return 0. * Hopefully this will be rare, but if it happens to you please let us * know so we can work on finding a way to support your system. * * Note that different clocks on the same system often have a * different precisions. */ PSNIP_CLOCK__FUNCTION psnip_uint32_t psnip_clock_get_precision (enum PsnipClockType clock_type) { switch (clock_type) { case PSNIP_CLOCK_TYPE_MONOTONIC: return psnip_clock_monotonic_get_precision (); case PSNIP_CLOCK_TYPE_CPU: return psnip_clock_cpu_get_precision (); case PSNIP_CLOCK_TYPE_WALL: return psnip_clock_wall_get_precision (); } PSNIP_CLOCK_UNREACHABLE(); return 0; } /* Set the provided timespec to the requested time. Returns 0 on * success, or a negative value on failure. */ PSNIP_CLOCK__FUNCTION int psnip_clock_get_time (enum PsnipClockType clock_type, struct PsnipClockTimespec* res) { assert(res != NULL); switch (clock_type) { case PSNIP_CLOCK_TYPE_MONOTONIC: return psnip_clock_monotonic_get_time (res); case PSNIP_CLOCK_TYPE_CPU: return psnip_clock_cpu_get_time (res); case PSNIP_CLOCK_TYPE_WALL: return psnip_clock_wall_get_time (res); } return -1; } #endif /* !defined(PSNIP_CLOCK_H) */ static psnip_uint64_t munit_clock_get_elapsed(struct PsnipClockTimespec* start, struct PsnipClockTimespec* end) { psnip_uint64_t r = (end->seconds - start->seconds) * PSNIP_CLOCK_NSEC_PER_SEC; if (end->nanoseconds < start->nanoseconds) { r -= (start->nanoseconds - end->nanoseconds); } else { r += (end->nanoseconds - start->nanoseconds); } return r; } #else # include #endif /* defined(MUNIT_ENABLE_TIMING) */ /*** PRNG stuff ***/ /* This is (unless I screwed up, which is entirely possible) the * version of PCG with 32-bit state. It was chosen because it has a * small enough state that we should reliably be able to use CAS * instead of requiring a lock for thread-safety. * * If I did screw up, I probably will not bother changing it unless * there is a significant bias. It's really not important this be * particularly strong, as long as it is fairly random it's much more * important that it be reproducible, so bug reports have a better * chance of being reproducible. */ #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) && !defined(__EMSCRIPTEN__) && (!defined(__GNUC_MINOR__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ > 8)) # define HAVE_STDATOMIC #elif defined(__clang__) # if __has_extension(c_atomic) # define HAVE_CLANG_ATOMICS # endif #endif /* Workaround for http://llvm.org/bugs/show_bug.cgi?id=26911 */ #if defined(__clang__) && defined(_WIN32) # undef HAVE_STDATOMIC # if defined(__c2__) # undef HAVE_CLANG_ATOMICS # endif #endif #if defined(_OPENMP) # define ATOMIC_UINT32_T uint32_t # define ATOMIC_UINT32_INIT(x) (x) #elif defined(HAVE_STDATOMIC) # include # define ATOMIC_UINT32_T _Atomic uint32_t # define ATOMIC_UINT32_INIT(x) ATOMIC_VAR_INIT(x) #elif defined(HAVE_CLANG_ATOMICS) # define ATOMIC_UINT32_T _Atomic uint32_t # define ATOMIC_UINT32_INIT(x) (x) #elif defined(_WIN32) # define ATOMIC_UINT32_T volatile LONG # define ATOMIC_UINT32_INIT(x) (x) #else # define ATOMIC_UINT32_T volatile uint32_t # define ATOMIC_UINT32_INIT(x) (x) #endif static ATOMIC_UINT32_T munit_rand_state = ATOMIC_UINT32_INIT(42); #if defined(_OPENMP) static inline void munit_atomic_store(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T value) { #pragma omp critical (munit_atomics) *dest = value; } static inline uint32_t munit_atomic_load(ATOMIC_UINT32_T* src) { int ret; #pragma omp critical (munit_atomics) ret = *src; return ret; } static inline uint32_t munit_atomic_cas(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T* expected, ATOMIC_UINT32_T desired) { munit_bool ret; #pragma omp critical (munit_atomics) { if (*dest == *expected) { *dest = desired; ret = 1; } else { ret = 0; } } return ret; } #elif defined(HAVE_STDATOMIC) # define munit_atomic_store(dest, value) atomic_store(dest, value) # define munit_atomic_load(src) atomic_load(src) # define munit_atomic_cas(dest, expected, value) atomic_compare_exchange_weak(dest, expected, value) #elif defined(HAVE_CLANG_ATOMICS) # define munit_atomic_store(dest, value) __c11_atomic_store(dest, value, __ATOMIC_SEQ_CST) # define munit_atomic_load(src) __c11_atomic_load(src, __ATOMIC_SEQ_CST) # define munit_atomic_cas(dest, expected, value) __c11_atomic_compare_exchange_weak(dest, expected, value, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) #elif defined(__GNUC__) && (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) # define munit_atomic_store(dest, value) __atomic_store_n(dest, value, __ATOMIC_SEQ_CST) # define munit_atomic_load(src) __atomic_load_n(src, __ATOMIC_SEQ_CST) # define munit_atomic_cas(dest, expected, value) __atomic_compare_exchange_n(dest, expected, value, 1, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) #elif defined(__GNUC__) && (__GNUC__ >= 4) # define munit_atomic_store(dest,value) do { *(dest) = (value); } while (0) # define munit_atomic_load(src) (*(src)) # define munit_atomic_cas(dest, expected, value) __sync_bool_compare_and_swap(dest, *expected, value) #elif defined(_WIN32) /* Untested */ # define munit_atomic_store(dest,value) do { *(dest) = (value); } while (0) # define munit_atomic_load(src) (*(src)) # define munit_atomic_cas(dest, expected, value) InterlockedCompareExchange((dest), (value), *(expected)) #else # warning No atomic implementation, PRNG will not be thread-safe # define munit_atomic_store(dest, value) do { *(dest) = (value); } while (0) # define munit_atomic_load(src) (*(src)) static inline munit_bool munit_atomic_cas(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T* expected, ATOMIC_UINT32_T desired) { if (*dest == *expected) { *dest = desired; return 1; } else { return 0; } } #endif #define MUNIT_PRNG_MULTIPLIER (747796405U) #define MUNIT_PRNG_INCREMENT (1729U) static munit_uint32_t munit_rand_next_state(munit_uint32_t state) { return state * MUNIT_PRNG_MULTIPLIER + MUNIT_PRNG_INCREMENT; } static munit_uint32_t munit_rand_from_state(munit_uint32_t state) { munit_uint32_t res = ((state >> ((state >> 28) + 4)) ^ state) * (277803737U); res ^= res >> 22; return res; } void munit_rand_seed(munit_uint32_t seed) { munit_uint32_t state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT); munit_atomic_store(&munit_rand_state, state); } static munit_uint32_t munit_rand_generate_seed(void) { munit_uint32_t seed, state; #if defined(MUNIT_ENABLE_TIMING) struct PsnipClockTimespec wc = { 0, }; psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wc); seed = (munit_uint32_t) wc.nanoseconds; #else seed = (munit_uint32_t) time(NULL); #endif state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT); return munit_rand_from_state(state); } static munit_uint32_t munit_rand_state_uint32(munit_uint32_t* state) { const munit_uint32_t old = *state; *state = munit_rand_next_state(old); return munit_rand_from_state(old); } munit_uint32_t munit_rand_uint32(void) { munit_uint32_t old, state; do { old = munit_atomic_load(&munit_rand_state); state = munit_rand_next_state(old); } while (!munit_atomic_cas(&munit_rand_state, &old, state)); return munit_rand_from_state(old); } static void munit_rand_state_memory(munit_uint32_t* state, size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) { size_t members_remaining = size / sizeof(munit_uint32_t); size_t bytes_remaining = size % sizeof(munit_uint32_t); munit_uint8_t* b = data; munit_uint32_t rv; while (members_remaining-- > 0) { rv = munit_rand_state_uint32(state); memcpy(b, &rv, sizeof(munit_uint32_t)); b += sizeof(munit_uint32_t); } if (bytes_remaining != 0) { rv = munit_rand_state_uint32(state); memcpy(b, &rv, bytes_remaining); } } void munit_rand_memory(size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) { munit_uint32_t old, state; do { state = old = munit_atomic_load(&munit_rand_state); munit_rand_state_memory(&state, size, data); } while (!munit_atomic_cas(&munit_rand_state, &old, state)); } static munit_uint32_t munit_rand_state_at_most(munit_uint32_t* state, munit_uint32_t salt, munit_uint32_t max) { /* We want (UINT32_MAX + 1) % max, which in unsigned arithmetic is the same * as (UINT32_MAX + 1 - max) % max = -max % max. We compute -max using not * to avoid compiler warnings. */ const munit_uint32_t min = (~max + 1U) % max; munit_uint32_t x; if (max == (~((munit_uint32_t) 0U))) return munit_rand_state_uint32(state) ^ salt; max++; do { x = munit_rand_state_uint32(state) ^ salt; } while (x < min); return x % max; } static munit_uint32_t munit_rand_at_most(munit_uint32_t salt, munit_uint32_t max) { munit_uint32_t old, state; munit_uint32_t retval; do { state = old = munit_atomic_load(&munit_rand_state); retval = munit_rand_state_at_most(&state, salt, max); } while (!munit_atomic_cas(&munit_rand_state, &old, state)); return retval; } int munit_rand_int_range(int min, int max) { munit_uint64_t range = (munit_uint64_t) max - (munit_uint64_t) min; if (min > max) return munit_rand_int_range(max, min); if (range > (~((munit_uint32_t) 0U))) range = (~((munit_uint32_t) 0U)); return min + munit_rand_at_most(0, (munit_uint32_t) range); } double munit_rand_double(void) { munit_uint32_t old, state; double retval = 0.0; do { state = old = munit_atomic_load(&munit_rand_state); /* See http://mumble.net/~campbell/tmp/random_real.c for how to do * this right. Patches welcome if you feel that this is too * biased. */ retval = munit_rand_state_uint32(&state) / ((~((munit_uint32_t) 0U)) + 1.0); } while (!munit_atomic_cas(&munit_rand_state, &old, state)); return retval; } /*** Test suite handling ***/ typedef struct { unsigned int successful; unsigned int skipped; unsigned int failed; unsigned int errored; #if defined(MUNIT_ENABLE_TIMING) munit_uint64_t cpu_clock; munit_uint64_t wall_clock; #endif } MunitReport; typedef struct { const char* prefix; const MunitSuite* suite; const char** tests; munit_uint32_t seed; unsigned int iterations; MunitParameter* parameters; munit_bool single_parameter_mode; void* user_data; MunitReport report; munit_bool colorize; munit_bool fork; munit_bool show_stderr; munit_bool fatal_failures; } MunitTestRunner; const char* munit_parameters_get(const MunitParameter params[], const char* key) { const MunitParameter* param; for (param = params ; param != NULL && param->name != NULL ; param++) if (strcmp(param->name, key) == 0) return param->value; return NULL; } #if defined(MUNIT_ENABLE_TIMING) static void munit_print_time(FILE* fp, munit_uint64_t nanoseconds) { fprintf(fp, "%" MUNIT_TEST_TIME_FORMAT, ((double) nanoseconds) / ((double) PSNIP_CLOCK_NSEC_PER_SEC)); } #endif /* Add a paramter to an array of parameters. */ static MunitResult munit_parameters_add(size_t* params_size, MunitParameter* params[MUNIT_ARRAY_PARAM(*params_size)], char* name, char* value) { *params = realloc(*params, sizeof(MunitParameter) * (*params_size + 2)); if (*params == NULL) return MUNIT_ERROR; (*params)[*params_size].name = name; (*params)[*params_size].value = value; (*params_size)++; (*params)[*params_size].name = NULL; (*params)[*params_size].value = NULL; return MUNIT_OK; } /* Concatenate two strings, but just return one of the components * unaltered if the other is NULL or "". */ static char* munit_maybe_concat(size_t* len, char* prefix, char* suffix) { char* res; size_t res_l; const size_t prefix_l = prefix != NULL ? strlen(prefix) : 0; const size_t suffix_l = suffix != NULL ? strlen(suffix) : 0; if (prefix_l == 0 && suffix_l == 0) { res = NULL; res_l = 0; } else if (prefix_l == 0 && suffix_l != 0) { res = suffix; res_l = suffix_l; } else if (prefix_l != 0 && suffix_l == 0) { res = prefix; res_l = prefix_l; } else { res_l = prefix_l + suffix_l; res = malloc(res_l + 1); memcpy(res, prefix, prefix_l); memcpy(res + prefix_l, suffix, suffix_l); res[res_l] = 0; } if (len != NULL) *len = res_l; return res; } /* Possbily free a string returned by munit_maybe_concat. */ static void munit_maybe_free_concat(char* s, const char* prefix, const char* suffix) { if (prefix != s && suffix != s) free(s); } /* Cheap string hash function, just used to salt the PRNG. */ static munit_uint32_t munit_str_hash(const char* name) { const char *p; munit_uint32_t h = 5381U; for (p = name; *p != '\0'; p++) h = (h << 5) + h + *p; return h; } static void munit_splice(int from, int to) { munit_uint8_t buf[1024]; #if !defined(_WIN32) ssize_t len; ssize_t bytes_written; ssize_t write_res; #else int len; int bytes_written; int write_res; #endif do { len = read(from, buf, sizeof(buf)); if (len > 0) { bytes_written = 0; do { write_res = write(to, buf + bytes_written, len - bytes_written); if (write_res < 0) break; bytes_written += write_res; } while (bytes_written < len); } else break; } while (1); } /* This is the part that should be handled in the child process */ static MunitResult munit_test_runner_exec(MunitTestRunner* runner, const MunitTest* test, const MunitParameter params[], MunitReport* report) { unsigned int iterations = runner->iterations; MunitResult result = MUNIT_FAIL; #if defined(MUNIT_ENABLE_TIMING) struct PsnipClockTimespec wall_clock_begin = { 0, }, wall_clock_end = { 0, }; struct PsnipClockTimespec cpu_clock_begin = { 0, }, cpu_clock_end = { 0, }; #endif unsigned int i = 0; if ((test->options & MUNIT_TEST_OPTION_SINGLE_ITERATION) == MUNIT_TEST_OPTION_SINGLE_ITERATION) iterations = 1; else if (iterations == 0) iterations = runner->suite->iterations; munit_rand_seed(runner->seed); do { void* data = (test->setup == NULL) ? runner->user_data : test->setup(params, runner->user_data); #if defined(MUNIT_ENABLE_TIMING) psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_begin); psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_begin); #endif result = test->test(params, data); #if defined(MUNIT_ENABLE_TIMING) psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_end); psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_end); #endif if (test->tear_down != NULL) test->tear_down(data); if (MUNIT_LIKELY(result == MUNIT_OK)) { report->successful++; #if defined(MUNIT_ENABLE_TIMING) report->wall_clock += munit_clock_get_elapsed(&wall_clock_begin, &wall_clock_end); report->cpu_clock += munit_clock_get_elapsed(&cpu_clock_begin, &cpu_clock_end); #endif } else { switch ((int) result) { case MUNIT_SKIP: report->skipped++; break; case MUNIT_FAIL: report->failed++; break; case MUNIT_ERROR: report->errored++; break; default: break; } break; } } while (++i < iterations); return result; } #if defined(MUNIT_EMOTICON) # define MUNIT_RESULT_STRING_OK ":)" # define MUNIT_RESULT_STRING_SKIP ":|" # define MUNIT_RESULT_STRING_FAIL ":(" # define MUNIT_RESULT_STRING_ERROR ":o" # define MUNIT_RESULT_STRING_TODO ":/" #else # define MUNIT_RESULT_STRING_OK "OK " # define MUNIT_RESULT_STRING_SKIP "SKIP " # define MUNIT_RESULT_STRING_FAIL "FAIL " # define MUNIT_RESULT_STRING_ERROR "ERROR" # define MUNIT_RESULT_STRING_TODO "TODO " #endif static void munit_test_runner_print_color(const MunitTestRunner* runner, const char* string, char color) { if (runner->colorize) fprintf(MUNIT_OUTPUT_FILE, "\x1b[3%cm%s\x1b[39m", color, string); else fputs(string, MUNIT_OUTPUT_FILE); } #if !defined(MUNIT_NO_BUFFER) static int munit_replace_stderr(FILE* stderr_buf) { if (stderr_buf != NULL) { const int orig_stderr = dup(STDERR_FILENO); int errfd = fileno(stderr_buf); if (MUNIT_UNLIKELY(errfd == -1)) { exit(EXIT_FAILURE); } dup2(errfd, STDERR_FILENO); return orig_stderr; } return -1; } static void munit_restore_stderr(int orig_stderr) { if (orig_stderr != -1) { dup2(orig_stderr, STDERR_FILENO); close(orig_stderr); } } #endif /* !defined(MUNIT_NO_BUFFER) */ /* Run a test with the specified parameters. */ static void munit_test_runner_run_test_with_params(MunitTestRunner* runner, const MunitTest* test, const MunitParameter params[]) { MunitResult result = MUNIT_OK; MunitReport report = { 0, 0, 0, 0, #if defined(MUNIT_ENABLE_TIMING) 0, 0 #endif }; unsigned int output_l; munit_bool first; const MunitParameter* param; FILE* stderr_buf; #if !defined(MUNIT_NO_FORK) int pipefd[2]; pid_t fork_pid; int orig_stderr; ssize_t bytes_written = 0; ssize_t write_res; ssize_t bytes_read = 0; ssize_t read_res; int status = 0; pid_t changed_pid; #endif if (params != NULL) { output_l = 2; fputs(" ", MUNIT_OUTPUT_FILE); first = 1; for (param = params ; param != NULL && param->name != NULL ; param++) { if (!first) { fputs(", ", MUNIT_OUTPUT_FILE); output_l += 2; } else { first = 0; } output_l += fprintf(MUNIT_OUTPUT_FILE, "%s=%s", param->name, param->value); } while (output_l++ < MUNIT_TEST_NAME_LEN) { fputc(' ', MUNIT_OUTPUT_FILE); } } fflush(MUNIT_OUTPUT_FILE); stderr_buf = NULL; #if !defined(_WIN32) || defined(__MINGW32__) stderr_buf = tmpfile(); #else tmpfile_s(&stderr_buf); #endif if (stderr_buf == NULL) { munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create buffer for stderr"); result = MUNIT_ERROR; goto print_result; } #if !defined(MUNIT_NO_FORK) if (runner->fork) { pipefd[0] = -1; pipefd[1] = -1; if (pipe(pipefd) != 0) { munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create pipe"); result = MUNIT_ERROR; goto print_result; } fork_pid = fork(); if (fork_pid == 0) { close(pipefd[0]); orig_stderr = munit_replace_stderr(stderr_buf); munit_test_runner_exec(runner, test, params, &report); /* Note that we don't restore stderr. This is so we can buffer * things written to stderr later on (such as by * asan/tsan/ubsan, valgrind, etc.) */ close(orig_stderr); do { write_res = write(pipefd[1], ((munit_uint8_t*) (&report)) + bytes_written, sizeof(report) - bytes_written); if (write_res < 0) { if (stderr_buf != NULL) { munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to write to pipe"); } exit(EXIT_FAILURE); } bytes_written += write_res; } while ((size_t) bytes_written < sizeof(report)); if (stderr_buf != NULL) fclose(stderr_buf); close(pipefd[1]); exit(EXIT_SUCCESS); } else if (fork_pid == -1) { close(pipefd[0]); close(pipefd[1]); if (stderr_buf != NULL) { munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to fork"); } report.errored++; result = MUNIT_ERROR; } else { close(pipefd[1]); do { read_res = read(pipefd[0], ((munit_uint8_t*) (&report)) + bytes_read, sizeof(report) - bytes_read); if (read_res < 1) break; bytes_read += read_res; } while (bytes_read < (ssize_t) sizeof(report)); changed_pid = waitpid(fork_pid, &status, 0); if (MUNIT_LIKELY(changed_pid == fork_pid) && MUNIT_LIKELY(WIFEXITED(status))) { if (bytes_read != sizeof(report)) { munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child exited unexpectedly with status %d", WEXITSTATUS(status)); report.errored++; } else if (WEXITSTATUS(status) != EXIT_SUCCESS) { munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child exited with status %d", WEXITSTATUS(status)); report.errored++; } } else { if (WIFSIGNALED(status)) { #if defined(_XOPEN_VERSION) && (_XOPEN_VERSION >= 700) munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child killed by signal %d (%s)", WTERMSIG(status), strsignal(WTERMSIG(status))); #else munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child killed by signal %d", WTERMSIG(status)); #endif } else if (WIFSTOPPED(status)) { munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child stopped by signal %d", WSTOPSIG(status)); } report.errored++; } close(pipefd[0]); waitpid(fork_pid, NULL, 0); } } else #endif { #if !defined(MUNIT_NO_BUFFER) const volatile int orig_stderr = munit_replace_stderr(stderr_buf); #endif #if defined(MUNIT_THREAD_LOCAL) if (MUNIT_UNLIKELY(setjmp(munit_error_jmp_buf) != 0)) { result = MUNIT_FAIL; report.failed++; } else { munit_error_jmp_buf_valid = 1; result = munit_test_runner_exec(runner, test, params, &report); } #else result = munit_test_runner_exec(runner, test, params, &report); #endif #if !defined(MUNIT_NO_BUFFER) munit_restore_stderr(orig_stderr); #endif /* Here just so that the label is used on Windows and we don't get * a warning */ goto print_result; } print_result: fputs("[ ", MUNIT_OUTPUT_FILE); if ((test->options & MUNIT_TEST_OPTION_TODO) == MUNIT_TEST_OPTION_TODO) { if (report.failed != 0 || report.errored != 0 || report.skipped != 0) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_TODO, '3'); result = MUNIT_OK; } else { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1'); if (MUNIT_LIKELY(stderr_buf != NULL)) munit_log_internal(MUNIT_LOG_ERROR, stderr_buf, "Test marked TODO, but was successful."); runner->report.failed++; result = MUNIT_ERROR; } } else if (report.failed > 0) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_FAIL, '1'); runner->report.failed++; result = MUNIT_FAIL; } else if (report.errored > 0) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1'); runner->report.errored++; result = MUNIT_ERROR; } else if (report.skipped > 0) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_SKIP, '3'); runner->report.skipped++; result = MUNIT_SKIP; } else if (report.successful > 1) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2'); #if defined(MUNIT_ENABLE_TIMING) fputs(" ] [ ", MUNIT_OUTPUT_FILE); munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock / report.successful); fputs(" / ", MUNIT_OUTPUT_FILE); munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock / report.successful); fprintf(MUNIT_OUTPUT_FILE, " CPU ]\n %-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s Total: [ ", ""); munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock); fputs(" / ", MUNIT_OUTPUT_FILE); munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock); fputs(" CPU", MUNIT_OUTPUT_FILE); #endif runner->report.successful++; result = MUNIT_OK; } else if (report.successful > 0) { munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2'); #if defined(MUNIT_ENABLE_TIMING) fputs(" ] [ ", MUNIT_OUTPUT_FILE); munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock); fputs(" / ", MUNIT_OUTPUT_FILE); munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock); fputs(" CPU", MUNIT_OUTPUT_FILE); #endif runner->report.successful++; result = MUNIT_OK; } fputs(" ]\n", MUNIT_OUTPUT_FILE); if (stderr_buf != NULL) { if (result == MUNIT_FAIL || result == MUNIT_ERROR || runner->show_stderr) { fflush(MUNIT_OUTPUT_FILE); rewind(stderr_buf); munit_splice(fileno(stderr_buf), STDERR_FILENO); fflush(stderr); } fclose(stderr_buf); } } static void munit_test_runner_run_test_wild(MunitTestRunner* runner, const MunitTest* test, const char* test_name, MunitParameter* params, MunitParameter* p) { const MunitParameterEnum* pe; char** values; MunitParameter* next; for (pe = test->parameters ; pe != NULL && pe->name != NULL ; pe++) { if (p->name == pe->name) break; } if (pe == NULL) return; for (values = pe->values ; *values != NULL ; values++) { next = p + 1; p->value = *values; if (next->name == NULL) { munit_test_runner_run_test_with_params(runner, test, params); } else { munit_test_runner_run_test_wild(runner, test, test_name, params, next); } if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0)) break; } } /* Run a single test, with every combination of parameters * requested. */ static void munit_test_runner_run_test(MunitTestRunner* runner, const MunitTest* test, const char* prefix) { char* test_name = munit_maybe_concat(NULL, (char*) prefix, (char*) test->name); /* The array of parameters to pass to * munit_test_runner_run_test_with_params */ MunitParameter* params = NULL; size_t params_l = 0; /* Wildcard parameters are parameters which have possible values * specified in the test, but no specific value was passed to the * CLI. That means we want to run the test once for every * possible combination of parameter values or, if --single was * passed to the CLI, a single time with a random set of * parameters. */ MunitParameter* wild_params = NULL; size_t wild_params_l = 0; const MunitParameterEnum* pe; const MunitParameter* cli_p; munit_bool filled; unsigned int possible; char** vals; size_t first_wild; const MunitParameter* wp; int pidx; munit_rand_seed(runner->seed); fprintf(MUNIT_OUTPUT_FILE, "%-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s", test_name); if (test->parameters == NULL) { /* No parameters. Simple, nice. */ munit_test_runner_run_test_with_params(runner, test, NULL); } else { fputc('\n', MUNIT_OUTPUT_FILE); for (pe = test->parameters ; pe != NULL && pe->name != NULL ; pe++) { /* Did we received a value for this parameter from the CLI? */ filled = 0; for (cli_p = runner->parameters ; cli_p != NULL && cli_p->name != NULL ; cli_p++) { if (strcmp(cli_p->name, pe->name) == 0) { if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, cli_p->value) != MUNIT_OK)) goto cleanup; filled = 1; break; } } if (filled) continue; /* Nothing from CLI, is the enum NULL/empty? We're not a * fuzzer… */ if (pe->values == NULL || pe->values[0] == NULL) continue; /* If --single was passed to the CLI, choose a value from the * list of possibilities randomly. */ if (runner->single_parameter_mode) { possible = 0; for (vals = pe->values ; *vals != NULL ; vals++) possible++; /* We want the tests to be reproducible, even if you're only * running a single test, but we don't want every test with * the same number of parameters to choose the same parameter * number, so use the test name as a primitive salt. */ pidx = munit_rand_at_most(munit_str_hash(test_name), possible - 1); if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, pe->values[pidx]) != MUNIT_OK)) goto cleanup; } else { /* We want to try every permutation. Put in a placeholder * entry, we'll iterate through them later. */ if (MUNIT_UNLIKELY(munit_parameters_add(&wild_params_l, &wild_params, pe->name, NULL) != MUNIT_OK)) goto cleanup; } } if (wild_params_l != 0) { first_wild = params_l; for (wp = wild_params ; wp != NULL && wp->name != NULL ; wp++) { for (pe = test->parameters ; pe != NULL && pe->name != NULL && pe->values != NULL ; pe++) { if (strcmp(wp->name, pe->name) == 0) { if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, pe->values[0]) != MUNIT_OK)) goto cleanup; } } } munit_test_runner_run_test_wild(runner, test, test_name, params, params + first_wild); } else { munit_test_runner_run_test_with_params(runner, test, params); } cleanup: free(params); free(wild_params); } munit_maybe_free_concat(test_name, prefix, test->name); } /* Recurse through the suite and run all the tests. If a list of * tests to run was provied on the command line, run only those * tests. */ static void munit_test_runner_run_suite(MunitTestRunner* runner, const MunitSuite* suite, const char* prefix) { size_t pre_l; char* pre = munit_maybe_concat(&pre_l, (char*) prefix, (char*) suite->prefix); const MunitTest* test; const char** test_name; const MunitSuite* child_suite; /* Run the tests. */ for (test = suite->tests ; test != NULL && test->test != NULL ; test++) { if (runner->tests != NULL) { /* Specific tests were requested on the CLI */ for (test_name = runner->tests ; test_name != NULL && *test_name != NULL ; test_name++) { if ((pre_l == 0 || strncmp(pre, *test_name, pre_l) == 0) && strncmp(test->name, *test_name + pre_l, strlen(*test_name + pre_l)) == 0) { munit_test_runner_run_test(runner, test, pre); if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0)) goto cleanup; } } } else { /* Run all tests */ munit_test_runner_run_test(runner, test, pre); } } if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0)) goto cleanup; /* Run any child suites. */ for (child_suite = suite->suites ; child_suite != NULL && child_suite->prefix != NULL ; child_suite++) { munit_test_runner_run_suite(runner, child_suite, pre); } cleanup: munit_maybe_free_concat(pre, prefix, suite->prefix); } static void munit_test_runner_run(MunitTestRunner* runner) { munit_test_runner_run_suite(runner, runner->suite, NULL); } static void munit_print_help(int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)], void* user_data, const MunitArgument arguments[]) { const MunitArgument* arg; (void) argc; printf("USAGE: %s [OPTIONS...] [TEST...]\n\n", argv[0]); puts(" --seed SEED\n" " Value used to seed the PRNG. Must be a 32-bit integer in decimal\n" " notation with no separators (commas, decimals, spaces, etc.), or\n" " hexidecimal prefixed by \"0x\".\n" " --iterations N\n" " Run each test N times. 0 means the default number.\n" " --param name value\n" " A parameter key/value pair which will be passed to any test with\n" " takes a parameter of that name. If not provided, the test will be\n" " run once for each possible parameter value.\n" " --list Write a list of all available tests.\n" " --list-params\n" " Write a list of all available tests and their possible parameters.\n" " --single Run each parameterized test in a single configuration instead of\n" " every possible combination\n" " --log-visible debug|info|warning|error\n" " --log-fatal debug|info|warning|error\n" " Set the level at which messages of different severities are visible,\n" " or cause the test to terminate.\n" #if !defined(MUNIT_NO_FORK) " --no-fork Do not execute tests in a child process. If this option is supplied\n" " and a test crashes (including by failing an assertion), no further\n" " tests will be performed.\n" #endif " --fatal-failures\n" " Stop executing tests as soon as a failure is found.\n" " --show-stderr\n" " Show data written to stderr by the tests, even if the test succeeds.\n" " --color auto|always|never\n" " Colorize (or don't) the output.\n" /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */ " --help Print this help message and exit.\n"); #if defined(MUNIT_NL_LANGINFO) setlocale(LC_ALL, ""); fputs((strcasecmp("UTF-8", nl_langinfo(CODESET)) == 0) ? "µnit" : "munit", stdout); #else puts("munit"); #endif printf(" %d.%d.%d\n" "Full documentation at: https://nemequ.github.io/munit/\n", (MUNIT_CURRENT_VERSION >> 16) & 0xff, (MUNIT_CURRENT_VERSION >> 8) & 0xff, (MUNIT_CURRENT_VERSION >> 0) & 0xff); for (arg = arguments ; arg != NULL && arg->name != NULL ; arg++) arg->write_help(arg, user_data); } static const MunitArgument* munit_arguments_find(const MunitArgument arguments[], const char* name) { const MunitArgument* arg; for (arg = arguments ; arg != NULL && arg->name != NULL ; arg++) if (strcmp(arg->name, name) == 0) return arg; return NULL; } static void munit_suite_list_tests(const MunitSuite* suite, munit_bool show_params, const char* prefix) { size_t pre_l; char* pre = munit_maybe_concat(&pre_l, (char*) prefix, (char*) suite->prefix); const MunitTest* test; const MunitParameterEnum* params; munit_bool first; char** val; const MunitSuite* child_suite; for (test = suite->tests ; test != NULL && test->name != NULL ; test++) { if (pre != NULL) fputs(pre, stdout); puts(test->name); if (show_params) { for (params = test->parameters ; params != NULL && params->name != NULL ; params++) { fprintf(stdout, " - %s: ", params->name); if (params->values == NULL) { puts("Any"); } else { first = 1; for (val = params->values ; *val != NULL ; val++ ) { if(!first) { fputs(", ", stdout); } else { first = 0; } fputs(*val, stdout); } putc('\n', stdout); } } } } for (child_suite = suite->suites ; child_suite != NULL && child_suite->prefix != NULL ; child_suite++) { munit_suite_list_tests(child_suite, show_params, pre); } munit_maybe_free_concat(pre, prefix, suite->prefix); } static munit_bool munit_stream_supports_ansi(FILE *stream) { #if !defined(_WIN32) return isatty(fileno(stream)); #else #if !defined(__MINGW32__) size_t ansicon_size = 0; #endif if (isatty(fileno(stream))) { #if !defined(__MINGW32__) getenv_s(&ansicon_size, NULL, 0, "ANSICON"); return ansicon_size != 0; #else return getenv("ANSICON") != NULL; #endif } return 0; #endif } int munit_suite_main_custom(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)], const MunitArgument arguments[]) { int result = EXIT_FAILURE; MunitTestRunner runner; size_t parameters_size = 0; size_t tests_size = 0; int arg; char* envptr; unsigned long ts; char* endptr; unsigned long long iterations; MunitLogLevel level; const MunitArgument* argument; const char** runner_tests; unsigned int tests_run; unsigned int tests_total; runner.prefix = NULL; runner.suite = NULL; runner.tests = NULL; runner.seed = 0; runner.iterations = 0; runner.parameters = NULL; runner.single_parameter_mode = 0; runner.user_data = NULL; runner.report.successful = 0; runner.report.skipped = 0; runner.report.failed = 0; runner.report.errored = 0; #if defined(MUNIT_ENABLE_TIMING) runner.report.cpu_clock = 0; runner.report.wall_clock = 0; #endif runner.colorize = 0; #if !defined(_WIN32) runner.fork = 1; #else runner.fork = 0; #endif runner.show_stderr = 0; runner.fatal_failures = 0; runner.suite = suite; runner.user_data = user_data; runner.seed = munit_rand_generate_seed(); runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE); for (arg = 1 ; arg < argc ; arg++) { if (strncmp("--", argv[arg], 2) == 0) { if (strcmp("seed", argv[arg] + 2) == 0) { if (arg + 1 >= argc) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); goto cleanup; } envptr = argv[arg + 1]; ts = strtoul(argv[arg + 1], &envptr, 0); if (*envptr != '\0' || ts > (~((munit_uint32_t) 0U))) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); goto cleanup; } runner.seed = (munit_uint32_t) ts; arg++; } else if (strcmp("iterations", argv[arg] + 2) == 0) { if (arg + 1 >= argc) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); goto cleanup; } endptr = argv[arg + 1]; iterations = strtoul(argv[arg + 1], &endptr, 0); if (*endptr != '\0' || iterations > UINT_MAX) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); goto cleanup; } runner.iterations = (unsigned int) iterations; arg++; } else if (strcmp("param", argv[arg] + 2) == 0) { if (arg + 2 >= argc) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires two arguments", argv[arg]); goto cleanup; } runner.parameters = realloc(runner.parameters, sizeof(MunitParameter) * (parameters_size + 2)); if (runner.parameters == NULL) { munit_log_internal(MUNIT_LOG_ERROR, stderr, "failed to allocate memory"); goto cleanup; } runner.parameters[parameters_size].name = (char*) argv[arg + 1]; runner.parameters[parameters_size].value = (char*) argv[arg + 2]; parameters_size++; runner.parameters[parameters_size].name = NULL; runner.parameters[parameters_size].value = NULL; arg += 2; } else if (strcmp("color", argv[arg] + 2) == 0) { if (arg + 1 >= argc) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); goto cleanup; } if (strcmp(argv[arg + 1], "always") == 0) runner.colorize = 1; else if (strcmp(argv[arg + 1], "never") == 0) runner.colorize = 0; else if (strcmp(argv[arg + 1], "auto") == 0) runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE); else { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); goto cleanup; } arg++; } else if (strcmp("help", argv[arg] + 2) == 0) { munit_print_help(argc, argv, user_data, arguments); result = EXIT_SUCCESS; goto cleanup; } else if (strcmp("single", argv[arg] + 2) == 0) { runner.single_parameter_mode = 1; } else if (strcmp("show-stderr", argv[arg] + 2) == 0) { runner.show_stderr = 1; #if !defined(_WIN32) } else if (strcmp("no-fork", argv[arg] + 2) == 0) { runner.fork = 0; #endif } else if (strcmp("fatal-failures", argv[arg] + 2) == 0) { runner.fatal_failures = 1; } else if (strcmp("log-visible", argv[arg] + 2) == 0 || strcmp("log-fatal", argv[arg] + 2) == 0) { if (arg + 1 >= argc) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]); goto cleanup; } if (strcmp(argv[arg + 1], "debug") == 0) level = MUNIT_LOG_DEBUG; else if (strcmp(argv[arg + 1], "info") == 0) level = MUNIT_LOG_INFO; else if (strcmp(argv[arg + 1], "warning") == 0) level = MUNIT_LOG_WARNING; else if (strcmp(argv[arg + 1], "error") == 0) level = MUNIT_LOG_ERROR; else { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]); goto cleanup; } if (strcmp("log-visible", argv[arg] + 2) == 0) munit_log_level_visible = level; else munit_log_level_fatal = level; arg++; } else if (strcmp("list", argv[arg] + 2) == 0) { munit_suite_list_tests(suite, 0, NULL); result = EXIT_SUCCESS; goto cleanup; } else if (strcmp("list-params", argv[arg] + 2) == 0) { munit_suite_list_tests(suite, 1, NULL); result = EXIT_SUCCESS; goto cleanup; } else { argument = munit_arguments_find(arguments, argv[arg] + 2); if (argument == NULL) { munit_logf_internal(MUNIT_LOG_ERROR, stderr, "unknown argument ('%s')", argv[arg]); goto cleanup; } if (!argument->parse_argument(suite, user_data, &arg, argc, argv)) goto cleanup; } } else { runner_tests = realloc((void*) runner.tests, sizeof(char*) * (tests_size + 2)); if (runner_tests == NULL) { munit_log_internal(MUNIT_LOG_ERROR, stderr, "failed to allocate memory"); goto cleanup; } runner.tests = runner_tests; runner.tests[tests_size++] = argv[arg]; runner.tests[tests_size] = NULL; } } fflush(stderr); fprintf(MUNIT_OUTPUT_FILE, "Running test suite with seed 0x%08" PRIx32 "...\n", runner.seed); munit_test_runner_run(&runner); tests_run = runner.report.successful + runner.report.failed + runner.report.errored; tests_total = tests_run + runner.report.skipped; if (tests_run == 0) { fprintf(stderr, "No tests run, %d (100%%) skipped.\n", runner.report.skipped); } else { fprintf(MUNIT_OUTPUT_FILE, "%d of %d (%0.0f%%) tests successful, %d (%0.0f%%) test skipped.\n", runner.report.successful, tests_run, (((double) runner.report.successful) / ((double) tests_run)) * 100.0, runner.report.skipped, (((double) runner.report.skipped) / ((double) tests_total)) * 100.0); } if (runner.report.failed == 0 && runner.report.errored == 0) { result = EXIT_SUCCESS; } cleanup: free(runner.parameters); free((void*) runner.tests); return result; } int munit_suite_main(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]) { return munit_suite_main_custom(suite, user_data, argc, argv, NULL); } libopaque-0.99.3/src/tests/munit/munit.h000066400000000000000000000423051452546740600202100ustar00rootroot00000000000000/* µnit Testing Framework * Copyright (c) 2013-2017 Evan Nemerson * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #if !defined(MUNIT_H) #define MUNIT_H #include #include #define MUNIT_VERSION(major, minor, revision) \ (((major) << 16) | ((minor) << 8) | (revision)) #define MUNIT_CURRENT_VERSION MUNIT_VERSION(0, 4, 1) #if defined(_MSC_VER) && (_MSC_VER < 1600) # define munit_int8_t __int8 # define munit_uint8_t unsigned __int8 # define munit_int16_t __int16 # define munit_uint16_t unsigned __int16 # define munit_int32_t __int32 # define munit_uint32_t unsigned __int32 # define munit_int64_t __int64 # define munit_uint64_t unsigned __int64 #else # include # define munit_int8_t int8_t # define munit_uint8_t uint8_t # define munit_int16_t int16_t # define munit_uint16_t uint16_t # define munit_int32_t int32_t # define munit_uint32_t uint32_t # define munit_int64_t int64_t # define munit_uint64_t uint64_t #endif #if defined(_MSC_VER) && (_MSC_VER < 1800) # if !defined(PRIi8) # define PRIi8 "i" # endif # if !defined(PRIi16) # define PRIi16 "i" # endif # if !defined(PRIi32) # define PRIi32 "i" # endif # if !defined(PRIi64) # define PRIi64 "I64i" # endif # if !defined(PRId8) # define PRId8 "d" # endif # if !defined(PRId16) # define PRId16 "d" # endif # if !defined(PRId32) # define PRId32 "d" # endif # if !defined(PRId64) # define PRId64 "I64d" # endif # if !defined(PRIx8) # define PRIx8 "x" # endif # if !defined(PRIx16) # define PRIx16 "x" # endif # if !defined(PRIx32) # define PRIx32 "x" # endif # if !defined(PRIx64) # define PRIx64 "I64x" # endif # if !defined(PRIu8) # define PRIu8 "u" # endif # if !defined(PRIu16) # define PRIu16 "u" # endif # if !defined(PRIu32) # define PRIu32 "u" # endif # if !defined(PRIu64) # define PRIu64 "I64u" # endif #else # include #endif #if !defined(munit_bool) # if defined(bool) # define munit_bool bool # elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # define munit_bool _Bool # else # define munit_bool int # endif #endif #if defined(__cplusplus) extern "C" { #endif #if defined(__GNUC__) # define MUNIT_LIKELY(expr) (__builtin_expect ((expr), 1)) # define MUNIT_UNLIKELY(expr) (__builtin_expect ((expr), 0)) # define MUNIT_UNUSED __attribute__((__unused__)) #else # define MUNIT_LIKELY(expr) (expr) # define MUNIT_UNLIKELY(expr) (expr) # define MUNIT_UNUSED #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__PGI) # define MUNIT_ARRAY_PARAM(name) name #else # define MUNIT_ARRAY_PARAM(name) #endif #if !defined(_WIN32) # define MUNIT_SIZE_MODIFIER "z" # define MUNIT_CHAR_MODIFIER "hh" # define MUNIT_SHORT_MODIFIER "h" #else # if defined(_M_X64) || defined(__amd64__) # define MUNIT_SIZE_MODIFIER "I64" # else # define MUNIT_SIZE_MODIFIER "" # endif # define MUNIT_CHAR_MODIFIER "" # define MUNIT_SHORT_MODIFIER "" #endif #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L # define MUNIT_NO_RETURN _Noreturn #elif defined(__GNUC__) # define MUNIT_NO_RETURN __attribute__((__noreturn__)) #elif defined(_MSC_VER) # define MUNIT_NO_RETURN __declspec(noreturn) #else # define MUNIT_NO_RETURN #endif #if defined(_MSC_VER) && (_MSC_VER >= 1500) # define MUNIT_PUSH_DISABLE_MSVC_C4127_ __pragma(warning(push)) __pragma(warning(disable:4127)) # define MUNIT_POP_DISABLE_MSVC_C4127_ __pragma(warning(pop)) #else # define MUNIT_PUSH_DISABLE_MSVC_C4127_ # define MUNIT_POP_DISABLE_MSVC_C4127_ #endif typedef enum { MUNIT_LOG_DEBUG, MUNIT_LOG_INFO, MUNIT_LOG_WARNING, MUNIT_LOG_ERROR } MunitLogLevel; #if defined(__GNUC__) && !defined(__MINGW32__) # define MUNIT_PRINTF(string_index, first_to_check) __attribute__((format (printf, string_index, first_to_check))) #else # define MUNIT_PRINTF(string_index, first_to_check) #endif MUNIT_PRINTF(4, 5) void munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...); #define munit_logf(level, format, ...) \ munit_logf_ex(level, __FILE__, __LINE__, format, __VA_ARGS__) #define munit_log(level, msg) \ munit_logf(level, "%s", msg) MUNIT_NO_RETURN MUNIT_PRINTF(3, 4) void munit_errorf_ex(const char* filename, int line, const char* format, ...); #define munit_errorf(format, ...) \ munit_errorf_ex(__FILE__, __LINE__, format, __VA_ARGS__) #define munit_error(msg) \ munit_errorf("%s", msg) #define munit_assert(expr) \ do { \ if (!MUNIT_LIKELY(expr)) { \ munit_error("assertion failed: " #expr); \ } \ MUNIT_PUSH_DISABLE_MSVC_C4127_ \ } while (0) \ MUNIT_POP_DISABLE_MSVC_C4127_ #define munit_assert_true(expr) \ do { \ if (!MUNIT_LIKELY(expr)) { \ munit_error("assertion failed: " #expr " is not true"); \ } \ MUNIT_PUSH_DISABLE_MSVC_C4127_ \ } while (0) \ MUNIT_POP_DISABLE_MSVC_C4127_ #define munit_assert_false(expr) \ do { \ if (!MUNIT_LIKELY(!(expr))) { \ munit_error("assertion failed: " #expr " is not false"); \ } \ MUNIT_PUSH_DISABLE_MSVC_C4127_ \ } while (0) \ MUNIT_POP_DISABLE_MSVC_C4127_ #define munit_assert_type_full(prefix, suffix, T, fmt, a, op, b) \ do { \ T munit_tmp_a_ = (a); \ T munit_tmp_b_ = (b); \ if (!(munit_tmp_a_ op munit_tmp_b_)) { \ munit_errorf("assertion failed: %s %s %s (" prefix "%" fmt suffix " %s " prefix "%" fmt suffix ")", \ #a, #op, #b, munit_tmp_a_, #op, munit_tmp_b_); \ } \ MUNIT_PUSH_DISABLE_MSVC_C4127_ \ } while (0) \ MUNIT_POP_DISABLE_MSVC_C4127_ #define munit_assert_type(T, fmt, a, op, b) \ munit_assert_type_full("", "", T, fmt, a, op, b) #define munit_assert_char(a, op, b) \ munit_assert_type_full("'\\x", "'", char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b) #define munit_assert_uchar(a, op, b) \ munit_assert_type_full("'\\x", "'", unsigned char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b) #define munit_assert_short(a, op, b) \ munit_assert_type(short, MUNIT_SHORT_MODIFIER "d", a, op, b) #define munit_assert_ushort(a, op, b) \ munit_assert_type(unsigned short, MUNIT_SHORT_MODIFIER "u", a, op, b) #define munit_assert_int(a, op, b) \ munit_assert_type(int, "d", a, op, b) #define munit_assert_uint(a, op, b) \ munit_assert_type(unsigned int, "u", a, op, b) #define munit_assert_long(a, op, b) \ munit_assert_type(long int, "ld", a, op, b) #define munit_assert_ulong(a, op, b) \ munit_assert_type(unsigned long int, "lu", a, op, b) #define munit_assert_llong(a, op, b) \ munit_assert_type(long long int, "lld", a, op, b) #define munit_assert_ullong(a, op, b) \ munit_assert_type(unsigned long long int, "llu", a, op, b) #define munit_assert_size(a, op, b) \ munit_assert_type(size_t, MUNIT_SIZE_MODIFIER "u", a, op, b) #define munit_assert_float(a, op, b) \ munit_assert_type(float, "f", a, op, b) #define munit_assert_double(a, op, b) \ munit_assert_type(double, "g", a, op, b) #define munit_assert_ptr(a, op, b) \ munit_assert_type(const void*, "p", a, op, b) #define munit_assert_int8(a, op, b) \ munit_assert_type(munit_int8_t, PRIi8, a, op, b) #define munit_assert_uint8(a, op, b) \ munit_assert_type(munit_uint8_t, PRIu8, a, op, b) #define munit_assert_int16(a, op, b) \ munit_assert_type(munit_int16_t, PRIi16, a, op, b) #define munit_assert_uint16(a, op, b) \ munit_assert_type(munit_uint16_t, PRIu16, a, op, b) #define munit_assert_int32(a, op, b) \ munit_assert_type(munit_int32_t, PRIi32, a, op, b) #define munit_assert_uint32(a, op, b) \ munit_assert_type(munit_uint32_t, PRIu32, a, op, b) #define munit_assert_int64(a, op, b) \ munit_assert_type(munit_int64_t, PRIi64, a, op, b) #define munit_assert_uint64(a, op, b) \ munit_assert_type(munit_uint64_t, PRIu64, a, op, b) #define munit_assert_double_equal(a, b, precision) \ do { \ const double munit_tmp_a_ = (a); \ const double munit_tmp_b_ = (b); \ const double munit_tmp_diff_ = ((munit_tmp_a_ - munit_tmp_b_) < 0) ? \ -(munit_tmp_a_ - munit_tmp_b_) : \ (munit_tmp_a_ - munit_tmp_b_); \ if (MUNIT_UNLIKELY(munit_tmp_diff_ > 1e-##precision)) { \ munit_errorf("assertion failed: %s == %s (%0." #precision "g == %0." #precision "g)", \ #a, #b, munit_tmp_a_, munit_tmp_b_); \ } \ MUNIT_PUSH_DISABLE_MSVC_C4127_ \ } while (0) \ MUNIT_POP_DISABLE_MSVC_C4127_ #include #define munit_assert_string_equal(a, b) \ do { \ const char* munit_tmp_a_ = a; \ const char* munit_tmp_b_ = b; \ if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) != 0)) { \ munit_errorf("assertion failed: string %s == %s (\"%s\" == \"%s\")", \ #a, #b, munit_tmp_a_, munit_tmp_b_); \ } \ MUNIT_PUSH_DISABLE_MSVC_C4127_ \ } while (0) \ MUNIT_POP_DISABLE_MSVC_C4127_ #define munit_assert_string_not_equal(a, b) \ do { \ const char* munit_tmp_a_ = a; \ const char* munit_tmp_b_ = b; \ if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) == 0)) { \ munit_errorf("assertion failed: string %s != %s (\"%s\" == \"%s\")", \ #a, #b, munit_tmp_a_, munit_tmp_b_); \ } \ MUNIT_PUSH_DISABLE_MSVC_C4127_ \ } while (0) \ MUNIT_POP_DISABLE_MSVC_C4127_ #define munit_assert_memory_equal(size, a, b) \ do { \ const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \ const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \ const size_t munit_tmp_size_ = (size); \ if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) != 0) { \ size_t munit_tmp_pos_; \ for (munit_tmp_pos_ = 0 ; munit_tmp_pos_ < munit_tmp_size_ ; munit_tmp_pos_++) { \ if (munit_tmp_a_[munit_tmp_pos_] != munit_tmp_b_[munit_tmp_pos_]) { \ munit_errorf("assertion failed: memory %s == %s, at offset %" MUNIT_SIZE_MODIFIER "u", \ #a, #b, munit_tmp_pos_); \ break; \ } \ } \ } \ MUNIT_PUSH_DISABLE_MSVC_C4127_ \ } while (0) \ MUNIT_POP_DISABLE_MSVC_C4127_ #define munit_assert_memory_not_equal(size, a, b) \ do { \ const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \ const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \ const size_t munit_tmp_size_ = (size); \ if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) == 0) { \ munit_errorf("assertion failed: memory %s != %s (%zu bytes)", \ #a, #b, munit_tmp_size_); \ } \ MUNIT_PUSH_DISABLE_MSVC_C4127_ \ } while (0) \ MUNIT_POP_DISABLE_MSVC_C4127_ #define munit_assert_ptr_equal(a, b) \ munit_assert_ptr(a, ==, b) #define munit_assert_ptr_not_equal(a, b) \ munit_assert_ptr(a, !=, b) #define munit_assert_null(ptr) \ munit_assert_ptr(ptr, ==, NULL) #define munit_assert_not_null(ptr) \ munit_assert_ptr(ptr, !=, NULL) #define munit_assert_ptr_null(ptr) \ munit_assert_ptr(ptr, ==, NULL) #define munit_assert_ptr_not_null(ptr) \ munit_assert_ptr(ptr, !=, NULL) /*** Memory allocation ***/ void* munit_malloc_ex(const char* filename, int line, size_t size); #define munit_malloc(size) \ munit_malloc_ex(__FILE__, __LINE__, (size)) #define munit_new(type) \ ((type*) munit_malloc(sizeof(type))) #define munit_calloc(nmemb, size) \ munit_malloc((nmemb) * (size)) #define munit_newa(type, nmemb) \ ((type*) munit_calloc((nmemb), sizeof(type))) /*** Random number generation ***/ void munit_rand_seed(munit_uint32_t seed); munit_uint32_t munit_rand_uint32(void); int munit_rand_int_range(int min, int max); double munit_rand_double(void); void munit_rand_memory(size_t size, munit_uint8_t buffer[MUNIT_ARRAY_PARAM(size)]); /*** Tests and Suites ***/ typedef enum { /* Test successful */ MUNIT_OK, /* Test failed */ MUNIT_FAIL, /* Test was skipped */ MUNIT_SKIP, /* Test failed due to circumstances not intended to be tested * (things like network errors, invalid parameter value, failure to * allocate memory in the test harness, etc.). */ MUNIT_ERROR } MunitResult; typedef struct { char* name; char** values; } MunitParameterEnum; typedef struct { char* name; char* value; } MunitParameter; const char* munit_parameters_get(const MunitParameter params[], const char* key); typedef enum { MUNIT_TEST_OPTION_NONE = 0, MUNIT_TEST_OPTION_SINGLE_ITERATION = 1 << 0, MUNIT_TEST_OPTION_TODO = 1 << 1 } MunitTestOptions; typedef MunitResult (* MunitTestFunc)(const MunitParameter params[], void* user_data_or_fixture); typedef void* (* MunitTestSetup)(const MunitParameter params[], void* user_data); typedef void (* MunitTestTearDown)(void* fixture); typedef struct { char* name; MunitTestFunc test; MunitTestSetup setup; MunitTestTearDown tear_down; MunitTestOptions options; MunitParameterEnum* parameters; } MunitTest; typedef enum { MUNIT_SUITE_OPTION_NONE = 0 } MunitSuiteOptions; typedef struct MunitSuite_ MunitSuite; struct MunitSuite_ { char* prefix; MunitTest* tests; MunitSuite* suites; unsigned int iterations; MunitSuiteOptions options; }; int munit_suite_main(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]); /* Note: I'm not very happy with this API; it's likely to change if I * figure out something better. Suggestions welcome. */ typedef struct MunitArgument_ MunitArgument; struct MunitArgument_ { char* name; munit_bool (* parse_argument)(const MunitSuite* suite, void* user_data, int* arg, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]); void (* write_help)(const MunitArgument* argument, void* user_data); }; int munit_suite_main_custom(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)], const MunitArgument arguments[]); #if defined(MUNIT_ENABLE_ASSERT_ALIASES) #define assert_true(expr) munit_assert_true(expr) #define assert_false(expr) munit_assert_false(expr) #define assert_char(a, op, b) munit_assert_char(a, op, b) #define assert_uchar(a, op, b) munit_assert_uchar(a, op, b) #define assert_short(a, op, b) munit_assert_short(a, op, b) #define assert_ushort(a, op, b) munit_assert_ushort(a, op, b) #define assert_int(a, op, b) munit_assert_int(a, op, b) #define assert_uint(a, op, b) munit_assert_uint(a, op, b) #define assert_long(a, op, b) munit_assert_long(a, op, b) #define assert_ulong(a, op, b) munit_assert_ulong(a, op, b) #define assert_llong(a, op, b) munit_assert_llong(a, op, b) #define assert_ullong(a, op, b) munit_assert_ullong(a, op, b) #define assert_size(a, op, b) munit_assert_size(a, op, b) #define assert_float(a, op, b) munit_assert_float(a, op, b) #define assert_double(a, op, b) munit_assert_double(a, op, b) #define assert_ptr(a, op, b) munit_assert_ptr(a, op, b) #define assert_int8(a, op, b) munit_assert_int8(a, op, b) #define assert_uint8(a, op, b) munit_assert_uint8(a, op, b) #define assert_int16(a, op, b) munit_assert_int16(a, op, b) #define assert_uint16(a, op, b) munit_assert_uint16(a, op, b) #define assert_int32(a, op, b) munit_assert_int32(a, op, b) #define assert_uint32(a, op, b) munit_assert_uint32(a, op, b) #define assert_int64(a, op, b) munit_assert_int64(a, op, b) #define assert_uint64(a, op, b) munit_assert_uint64(a, op, b) #define assert_double_equal(a, b, precision) munit_assert_double_equal(a, b, precision) #define assert_string_equal(a, b) munit_assert_string_equal(a, b) #define assert_string_not_equal(a, b) munit_assert_string_not_equal(a, b) #define assert_memory_equal(size, a, b) munit_assert_memory_equal(size, a, b) #define assert_memory_not_equal(size, a, b) munit_assert_memory_not_equal(size, a, b) #define assert_ptr_equal(a, b) munit_assert_ptr_equal(a, b) #define assert_ptr_not_equal(a, b) munit_assert_ptr_not_equal(a, b) #define assert_ptr_null(ptr) munit_assert_null_equal(ptr) #define assert_ptr_not_null(ptr) munit_assert_not_null(ptr) #define assert_null(ptr) munit_assert_null(ptr) #define assert_not_null(ptr) munit_assert_not_null(ptr) #endif /* defined(MUNIT_ENABLE_ASSERT_ALIASES) */ #if defined(__cplusplus) } #endif #endif /* !defined(MUNIT_H) */ #if defined(MUNIT_ENABLE_ASSERT_ALIASES) # if defined(assert) # undef assert # endif # define assert(expr) munit_assert(expr) #endif libopaque-0.99.3/src/tests/opaque-munit.c000066400000000000000000000131111452546740600203300ustar00rootroot00000000000000/* @copyright 2018-2020, opaque@ctrlc.hu This file is part of libopaque libopaque is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. libopaque is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with libopaque. If not, see . */ #define MUNIT_ENABLE_ASSERT_ALIASES #include "munit/munit.h" #include #include "../opaque.h" #include "../common.h" static char* type_params[] = { "0", "1", "2", "3", NULL }; static char* pwdU_params[] = { "simple guessable dictionary password", "", NULL }; static char* idU_params[] = { "user", "", NULL }; static char* idS_params[] = { "server", "", NULL }; static MunitParameterEnum init_params[] = { { "pwdU", pwdU_params }, { "idU", idU_params }, { "idS", idS_params }, { "type", type_params }, { NULL, NULL }, }; typedef enum { ServerInit = '0', Server1kInit = '1', PrivateInit = '2', Private1kInit = '3' } TestType; MunitResult opaque_test(const MunitParameter params[], void* user_data_or_fixture) { // variant where user registration does not leak secrets to server (void)user_data_or_fixture; const TestType type = *((const TestType*)munit_parameters_get(params, "type")); const uint8_t *pwdU=(const uint8_t*) munit_parameters_get(params, "pwdU"); const size_t pwdU_len=strlen((char*) pwdU); uint8_t export_key[crypto_hash_sha512_BYTES]; Opaque_Ids ids={0, (uint8_t*) munit_parameters_get(params, "idU"), 0, (uint8_t*) munit_parameters_get(params, "idS")}; ids.idU_len = strlen((char*)ids.idU); ids.idS_len = strlen((char*)ids.idS); uint8_t rec[OPAQUE_USER_RECORD_LEN]; uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], pub[OPAQUE_USER_SESSION_PUBLIC_LEN]; uint8_t resp[OPAQUE_SERVER_SESSION_LEN]; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t pk[OPAQUE_SHARED_SECRETBYTES]; uint8_t authU[crypto_auth_hmacsha512_BYTES]; uint8_t authUs[crypto_auth_hmacsha512_BYTES]; // in case we omit the id* in the envelope we must provide it before-hand. // if it is in the envelope it will be populated from the envelope uint8_t M[crypto_core_ristretto255_BYTES]; uint8_t usr_ctx[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]; uint8_t _skS[crypto_scalarmult_SCALARBYTES]; uint8_t *skS; if(type==Private1kInit || type==Server1kInit) { skS=_skS; randombytes(skS, crypto_scalarmult_SCALARBYTES); } else { skS=NULL; } if(type==ServerInit || type==Server1kInit) { // register user fprintf(stderr,"\nopaque_Register\n"); if(0!=opaque_Register(pwdU, pwdU_len, skS, &ids, rec, export_key)) { fprintf(stderr,"opaque_Register failed.\n"); return MUNIT_FAIL; } } else { // user initiates: fprintf(stderr,"\nopaque_CreateRegistrationRequest\n"); if(0!=opaque_CreateRegistrationRequest(pwdU, pwdU_len, usr_ctx, M)) { fprintf(stderr,"opaque_CreateRegistrationRequest failed.\n"); return MUNIT_FAIL; } // server responds uint8_t rsec[OPAQUE_REGISTER_SECRET_LEN], rpub[OPAQUE_REGISTER_PUBLIC_LEN]; fprintf(stderr,"\nopaque_CreateRegistrationResponse\n"); if(0!=opaque_CreateRegistrationResponse(M, skS, rsec, rpub)) { fprintf(stderr,"opaque_CreateRegistrationResponse failed.\n"); return MUNIT_FAIL; } // user commits its secrets fprintf(stderr,"\nopaque_FinalizeRequest\n"); unsigned char rrec[OPAQUE_REGISTRATION_RECORD_LEN]={0}; if(0!=opaque_FinalizeRequest(usr_ctx, rpub, &ids, rrec, export_key)) { fprintf(stderr,"opaque_FinalizeRequest failed.\n"); return MUNIT_FAIL; } // server "saves" fprintf(stderr,"\nopaque_Store1kUserRecord\n"); opaque_StoreUserRecord(rsec, rrec, rec); } fprintf(stderr,"\nopaque_CreateCredentialRequest\n"); opaque_CreateCredentialRequest(pwdU, pwdU_len, sec, pub); fprintf(stderr,"\nopaque_CreateCredentialResponse\n"); if(0!=opaque_CreateCredentialResponse(pub, rec, &ids, (uint8_t*)"munit", 5, resp, sk, authUs)) { fprintf(stderr,"opaque_CreateCredentialResponse failed.\n"); return MUNIT_FAIL; } fprintf(stderr,"\nopaque_RecoverCredentials\n"); if(0!=opaque_RecoverCredentials(resp, sec, (uint8_t*)"munit", 5, &ids, pk, authU, export_key)) { fprintf(stderr,"opaque_RecoverCredentials failed.\n"); return MUNIT_FAIL; } assert(sodium_memcmp(sk,pk,sizeof sk)==0); // authenticate both parties: if(0!=opaque_UserAuth(authUs, authU)) { fprintf(stderr,"failed authenticating user\n"); return MUNIT_FAIL; } printf("\n"); return MUNIT_OK; } MunitTest tests[] = { { "/server-init", /* name */ opaque_test, /* test */ NULL, /* setup */ NULL, /* tear_down */ MUNIT_TEST_OPTION_NONE, /* options */ init_params /* parameters */ }, /* Mark the end of the array with an entry where the test * function is NULL */ { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } }; static const MunitSuite suite = { "/opaque-tests", tests, /* tests */ NULL, /* suites */ 1, /* iterations */ MUNIT_SUITE_OPTION_NONE /* options */ }; int main (int argc, char* const argv[]) { return munit_suite_main(&suite, NULL, argc, argv); } libopaque-0.99.3/src/tests/opaque-test.c000066400000000000000000000105361452546740600201630ustar00rootroot00000000000000/* @copyright 2018-2020, opaque@ctrlc.hu This file is part of libopaque libopaque is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. libopaque is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with libopaque. If not, see . */ #include #include #include "../opaque.h" #include "../common.h" int main(void) { const uint8_t pwdU[]="asdf"; const uint16_t pwdU_len=strlen((char*) pwdU); uint8_t export_key[crypto_hash_sha512_BYTES]; uint8_t export_key0[crypto_hash_sha512_BYTES]; Opaque_Ids ids={4,(uint8_t*)"user",6,(uint8_t*)"server"}; uint8_t rec[OPAQUE_USER_RECORD_LEN]; uint8_t rec0[OPAQUE_USER_RECORD_LEN]; uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len], pub[OPAQUE_USER_SESSION_PUBLIC_LEN]; uint8_t resp[OPAQUE_SERVER_SESSION_LEN]; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t pk[OPAQUE_SHARED_SECRETBYTES]; uint8_t authU0[crypto_auth_hmacsha512_BYTES]; uint8_t authU1[crypto_auth_hmacsha512_BYTES]; const uint8_t context[4]="test"; fprintf(stderr, "\n\nprivate registration\n\n"); // variant where user registration does not leak secrets to server uint8_t M[crypto_core_ristretto255_BYTES]; uint8_t usr_ctx[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]; // user initiates: fprintf(stderr, "\nopaque_CreateRegistrationRequest\n"); if(0!=opaque_CreateRegistrationRequest(pwdU, pwdU_len, usr_ctx, M)) { fprintf(stderr, "opaque_CreateRegistrationRequest failed.\n"); return 1; } // server responds uint8_t rsec[OPAQUE_REGISTER_SECRET_LEN], rpub[OPAQUE_REGISTER_PUBLIC_LEN]; fprintf(stderr, "\nopaque_CreateRegistrationResponse\n"); if(0!=opaque_CreateRegistrationResponse(M, NULL, rsec, rpub)) { fprintf(stderr, "opaque_CreateRegistrationResponse failed.\n"); return 1; } // user commits its secrets fprintf(stderr, "\nopaque_FinalizeRequest\n"); unsigned char rrec[OPAQUE_REGISTRATION_RECORD_LEN]={0}; if(0!=opaque_FinalizeRequest(usr_ctx, rpub, &ids, rrec, export_key)) { fprintf(stderr, "opaque_FinalizeRequest failed.\n"); return 1; } // server "saves" fprintf(stderr, "\nopaque_StoreUserRecord\n"); opaque_StoreUserRecord(rsec, rrec, rec); fprintf(stderr, "\nopaque_CreateCredentialRequest\n"); opaque_CreateCredentialRequest(pwdU, pwdU_len, sec, pub); fprintf(stderr, "\nopaque_CreateCredentialResponse\n"); if(0!=opaque_CreateCredentialResponse(pub, rec, &ids, context, sizeof context, resp, sk, authU0)) { fprintf(stderr, "opaque_CreateCredentialResponse failed.\n"); return 1; } fprintf(stderr, "\nopaque_RecoverCredentials\n"); if(0!=opaque_RecoverCredentials(resp, sec, context, sizeof context, &ids, pk, authU1, export_key)) return 1; assert(sodium_memcmp(sk,pk,sizeof sk)==0); // authenticate both parties: if(-1==opaque_UserAuth(authU0, authU1)) { fprintf(stderr, "failed authenticating user\n"); return 1; } // register user fprintf(stderr, "\nopaque_Register\n"); if(0!=opaque_Register(pwdU, pwdU_len, NULL, &ids, rec0, export_key0)) { fprintf(stderr, "opaque_Register failed.\n"); return 1; } fprintf(stderr, "\nopaque_CreateCredentialRequest\n"); opaque_CreateCredentialRequest(pwdU, pwdU_len, sec, pub); fprintf(stderr, "\nopaque_CreateCredentialResponse\n"); if(0!=opaque_CreateCredentialResponse(pub, rec0, &ids, context, sizeof context, resp, sk, authU0)) { fprintf(stderr, "opaque_CreateCredentialResponse failed.\n"); return 1; } fprintf(stderr, "\nopaque_RecoverCredentials\n"); if(0!=opaque_RecoverCredentials(resp, sec, context, sizeof context, &ids, pk, authU1, export_key)) return 1; assert(sodium_memcmp(sk,pk,sizeof sk)==0); assert(memcmp(export_key, export_key0, sizeof export_key)==0); // authenticate both parties: if(-1==opaque_UserAuth(authU0, authU1)) { fprintf(stderr, "failed authenticating user\n"); return 1; } fprintf(stderr, "\nall ok\n\n"); return 0; } libopaque-0.99.3/src/tests/opaque-testvectors.c000066400000000000000000000133261452546740600215710ustar00rootroot00000000000000/* @copyright 2018-2020, opaque@ctrlc.hu This file is part of libopaque libopaque is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. libopaque is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with libopaque. If not, see . */ #include #include #include "../opaque.h" #include "../common.h" #include "cfrg_test_vectors.h" typedef struct { uint8_t blind[crypto_core_ristretto255_SCALARBYTES]; uint16_t pwdU_len; uint8_t pwdU[]; } Opaque_RegisterUserSec; int main(void) { // test vector 1 // create credential workflow uint8_t pwdU[]="CorrectHorseBatteryStaple"; size_t pwdU_len = sizeof(pwdU) - 1; uint8_t ctx[OPAQUE_REGISTER_USER_SEC_LEN+pwdU_len]; uint8_t regreq[crypto_core_ristretto255_BYTES]; // create registration request if(0!=opaque_CreateRegistrationRequest(pwdU, pwdU_len, ctx, regreq)) return 1; // metadata is R blinding factor // assert match with test vector if(memcmp(((Opaque_RegisterUserSec*) &ctx)->blind,blind_registration, sizeof(blind_registration))!=0) { fprintf(stderr, "failed to reproduce reg req blind factor\n"); dump(blind_registration, sizeof(blind_registration), "blind_registration"); dump(((Opaque_RegisterUserSec*) &ctx)->blind, sizeof(blind_registration), "blind "); exit(1); } if(memcmp(registration_request, regreq, 32)!=0) { fprintf(stderr, "failed to reproduce reg req\n"); dump(regreq, 32, "regreq"); dump(registration_request, 32, "registration_request"); exit(1); } // create registration response // prepare unsigned char rsec[OPAQUE_REGISTER_SECRET_LEN], resp[OPAQUE_REGISTER_PUBLIC_LEN]; if(0!=opaque_CreateRegistrationResponse(regreq, server_private_key, rsec, resp)) return 1; // verify test vectors if(memcmp(registration_response, resp, sizeof resp)!=0) { fprintf(stderr,"failed to reproduce registration_response\n"); dump(resp, sizeof resp, "resp"); dump(registration_response, sizeof registration_response, "registration_response"); exit(1); } // finalize request // prepare params Opaque_Ids ids={0}; unsigned char rrec[OPAQUE_REGISTRATION_RECORD_LEN]={0}; uint8_t ek[crypto_hash_sha512_BYTES]={0}; if(0!=opaque_FinalizeRequest(ctx, resp, &ids, rrec, ek)) return 1; // verify test vectors if(memcmp(export_key, ek, sizeof export_key)!=0) { fprintf(stderr,"failed to reproduce export_key\n"); dump(ek, sizeof ek, "ek"); dump(export_key, sizeof export_key, "export_key"); exit(1); } if((sizeof rrec != sizeof registration_upload) || memcmp(registration_upload, rrec, sizeof rrec)!=0) { fprintf(stderr,"failed to reproduce registration_upload\n"); dump(rrec, sizeof rrec, "rrec "); dump(registration_upload, sizeof registration_upload, "registration_upload"); exit(1); } uint8_t rec[OPAQUE_USER_RECORD_LEN]; opaque_StoreUserRecord(rsec, rrec, rec); uint8_t sec[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len]; uint8_t req[OPAQUE_USER_SESSION_PUBLIC_LEN]; if(0!=opaque_CreateCredentialRequest(pwdU, pwdU_len, sec, req)) { return 1; } if(sizeof rrec != sizeof registration_upload) { fprintf(stderr,"len(ke1) != len(req)\n"); dump(req, sizeof req, "req"); dump(ke1, sizeof ke1, "ke1"); exit(1); } if(memcmp(ke1, req, sizeof req)!=0) { fprintf(stderr,"failed to reproduce ke1\n"); dump(req, sizeof req, "req"); dump(ke1, sizeof ke1, "ke1"); exit(1); } uint8_t cresp[OPAQUE_SERVER_SESSION_LEN]; uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; uint8_t authU[crypto_auth_hmacsha512_BYTES]; uint8_t context[10]="OPAQUE-POC"; if(0!=opaque_CreateCredentialResponse(req, rec, &ids, context, sizeof context, cresp, sk, authU)) { return -1; } if(memcmp(session_key, sk, sizeof session_key)!=0) { fprintf(stderr,"failed to reproduce session_key\n"); dump(sk, sizeof sk, "sk"); dump(session_key, sizeof session_key, "session_key"); exit(1); } if(sizeof cresp != sizeof ke2) { fprintf(stderr,"len(ke2) != len(resp)\n"); dump(cresp, sizeof cresp, "resp"); dump(ke1, sizeof ke2, "ke2"); exit(1); } if(memcmp(ke2, cresp, sizeof cresp)!=0) { fprintf(stderr,"failed to reproduce ke2\n"); dump(cresp, sizeof cresp, "resp"); dump(ke2, sizeof ke2, "ke2"); exit(1); } uint8_t skU[OPAQUE_SHARED_SECRETBYTES]; uint8_t authUu[crypto_auth_hmacsha512_BYTES]; uint8_t export_keyU[crypto_hash_sha512_BYTES]; Opaque_Ids ids1={0}; opaque_RecoverCredentials(cresp, sec, context, sizeof context, &ids1, skU, authUu, export_keyU); if(memcmp(session_key, skU, sizeof session_key)!=0) { fprintf(stderr,"failed to reproduce session_key\n"); dump(skU, sizeof skU, "skU"); dump(session_key, sizeof session_key, "session_key"); exit(1); } if(memcmp(export_key, export_keyU, sizeof export_key)!=0) { fprintf(stderr,"failed to reproduce export_key\n"); dump(export_keyU, sizeof export_keyU, "export_keyU"); dump(export_key, sizeof export_key, "export_key"); exit(1); } if(memcmp(authU, authUu, sizeof authU)!=0) { fprintf(stderr,"failed to reproduce authU\n"); dump(authUu, sizeof authUu, "authUu"); dump(authU, sizeof authU, "authU"); exit(1); } fprintf(stderr,"all ok\n"); return 0; } libopaque-0.99.3/src/tests/testvecs2h.py000077500000000000000000000144631452546740600202220ustar00rootroot00000000000000#!/usr/bin/env python import json from itertools import zip_longest # for Python 3.x def split_by_n(iterable, n): return zip_longest(*[iter(iterable)]*n, fillvalue='') # src: https://github.com/cfrg/draft-irtf-cfrg-opaque/blob/c8f858d015836ea555543611fa845fe128264653/poc/vectors/vectors.json vectors = """ { "config": { "Context": "4f50415155452d504f43", "Fake": "False", "Group": "ristretto255", "Hash": "SHA512", "KDF": "HKDF-SHA512", "KSF": "Identity", "MAC": "HMAC-SHA512", "Name": "3DH", "Nh": "64", "Nm": "64", "Nok": "32", "Npk": "32", "Nsk": "32", "Nx": "64", "OPRF": "ristretto255-SHA512" }, "inputs": { "blind_login": "6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4b0790308", "blind_registration": "76cfbfe758db884bebb33582331ba9f159720ca8784a2a070a265d9c2d6abe01", "client_keyshare_seed": "82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf1091933541936304b", "client_nonce": "da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc", "credential_identifier": "31323334", "envelope_nonce": "ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfec", "masking_nonce": "38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d", "oprf_seed": "f433d0227b0b9dd54f7c4422b600e764e47fb503f1f9a0f0a47c6606b054a7fdc65347f1a08f277e22358bbabe26f823fca82c7848e9a75661f4ec5d5c1989ef", "password": "436f7272656374486f72736542617474657279537461706c65", "server_keyshare_seed": "05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a1e85ff80da12f982f", "server_nonce": "71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a1", "server_private_key": "47451a85372f8b3537e249d7b54188091fb18edde78094b43e2ba42b5eb89f0d", "server_public_key": "b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a382c9b79df1a78" }, "intermediates": { "auth_key": "6cd32316f18d72a9a927a83199fa030663a38ce0c11fbaef82aa90037730494fc555c4d49506284516edd1628c27965b7555a4ebfed2223199f6c67966dde822", "client_mac_key": "91750adbac54a5e8e53b4c233cc8d369fe83b0de1b6a3cd85575eeb0bb01a6a90a086a2cf5fe75fff2a9379c30ba9049510a33b5b0b1444a88800fc3eee2260d", "client_public_key": "76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbaccafb57ac5c3675", "envelope": "ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfec634b0f5b96109c198a8027da51854c35bee90d1e1c781806d07d49b76de6a28b8d9e9b6c93b9f8b64d16dddd9c5bfb5fea48ee8fd2f75012a8b308605cdd8ba5", "handshake_secret": "81263cb85a0cfa12450f0f388de4e92291ec4c7c7a0878b624550ff528726332f1298fc6cc822a432c89504347c7a2ccd70316ae3da6a15e0399e6db3f7c1b12", "masking_key": "1ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa82785c5", "oprf_key": "5d4c6a8b7c7138182afb4345d1fae6a9f18a1744afbcc3854f8f5a2b4b4c6d05", "randomized_password": "aac48c25ab036e30750839d31d6e73007344cb1155289fb7d329beb932e9adeea73d5d5c22a0ce1952f8aba6d66007615cd1698d4ac85ef1fcf150031d1435d9", "server_mac_key": "0d36b26cfe38f51f804f0a9361818f32ee1ce2a4e5578653b527184af058d3b2d8075c296fd84d24677913d1baa109290cd81a13ed383f9091a3804e65298dfc" }, "outputs": { "KE1": "c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44dda7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc6e29bee50701498605b2c085d7b241ca15ba5c32027dd21ba420b94ce60da326", "KE2": "7e308140890bcde30cbcea28b01ea1ecfbd077cff62c4def8efa075aabcbb47138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6dd6ec60bcdb26dc455ddf3e718f1020490c192d70dfc7e403981179d8073d1146a4f9aa1ced4e4cd984c657eb3b54ced3848326f70331953d91b02535af44d9fedc80188ca46743c52786e0382f95ad85c08f6afcd1ccfbff95e2bdeb015b166c6b20b92f832cc6df01e0b86a7efd92c1c804ff865781fa93f2f20b446c8371b671cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a1c4f62198a9d6fa9170c42c3c71f1971b29eb1d5d0bd733e40816c91f7912cc4a660c48dae03e57aaa38f3d0cffcfc21852ebc8b405d15bd6744945ba1a93438a162b6111699d98a16bb55b7bdddfe0fc5608b23da246e7bd73b47369169c5c90", "KE3": "4455df4f810ac31a6748835888564b536e6da5d9944dfea9e34defb9575fe5e2661ef61d2ae3929bcf57e53d464113d364365eb7d1a57b629707ca48da18e442", "export_key": "1ef15b4fa99e8a852412450ab78713aad30d21fa6966c9b8c9fb3262a970dc62950d4dd4ed62598229b1b72794fc0335199d9f7fcc6eaedde92cc04870e63f16", "registration_request": "5059ff249eb1551b7ce4991f3336205bde44a105a032e747d21bf382e75f7a71", "registration_response": "7408a268083e03abc7097fc05b587834539065e86fb0c7b6342fcf5e01e5b019b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a382c9b79df1a78", "registration_upload": "76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbaccafb57ac5c36751ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa82785c5ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfec634b0f5b96109c198a8027da51854c35bee90d1e1c781806d07d49b76de6a28b8d9e9b6c93b9f8b64d16dddd9c5bfb5fea48ee8fd2f75012a8b308605cdd8ba5", "session_key": "42afde6f5aca0cfa5c163763fbad55e73a41db6b41bc87b8e7b62214a8eedc6731fa3cb857d657ab9b3764b89a84e91ebcb4785166fbb02cedfcbdfda215b96f" } } """ vex = json.loads(vectors) # run this if there is a change in the values of the test vectors # ./testvecs2h.py >cfrg_test_vectors.h print("#ifndef cfrg_test_vectors_h\n#define cfrg_test_vectors_h\n") print("#include \n") for type in {"inputs", "intermediates", "outputs"}: for k, v in vex[type].items(): print(f"#define {k.lower()}_len {len(v)//2}") print( f"const uint8_t {k.lower()}[{k.lower()}_len] = {{\n %s}};\n" % ",\n ".join( (", ".join((c for c in line if c)) for line in split_by_n( (f"0x{x[0]}{x[1]}" for x in split_by_n(v,2)) ,8)) )) print("#endif") # only run this code below if there is a change in the keys of the test vectors # ./testvecs2h.py >cfrg_test_vector_decl.h #print("#ifndef cfrg_test_vector_decl_h\n#define cfrg_test_vector_decl_h\n") #print("#include \n") #for type in {"inputs", "intermediates", "outputs"}: # for k, v in vex[type].items(): # print(f"#define {k.lower()}_len {len(v)//2}") # print(f"extern const uint8_t {k.lower()}[{k.lower()}_len];\n") #print("#endif") libopaque-0.99.3/src/utils/000077500000000000000000000000001452546740600155415ustar00rootroot00000000000000libopaque-0.99.3/src/utils/README.org000066400000000000000000000035541452546740600172160ustar00rootroot00000000000000** Offline Registration ``` echo -n password | ./opaque init user server >record 3>export_key ``` ** Online Registration *** socat style **** server ``` socat tcp-l:23523,reuseaddr,fork system:"bash -c \'./opaque server-reg user server 3>record\'" ``` **** client ``` socat tcp:127.0.0.1:23523 exec:'bash -c \"./opaque user-reg user server 3< <(echo -n password) 4>export_key\"' ``` *** tcpserver style **** server ``` s6-tcpserver 127.0.0.1 23523 bash -c './opaque server-reg user server 3>record' ``` **** client ``` s6-tcpclient 127.0.0.1 23523 bash -c "./opaque user-reg user server <&6 >&7 3< <(echo -n password) 4>export_key" ``` *** The manual way it's also possible to do all 4 steps seperately, in case you cannot connect to the server directly, then: the user initiates with: ``` echo -n password | ./opaque register >msg 3>ctx ``` the server gets `msg` and responds with rpub, while keeping rsec secret: ``` cat msg | ./opaque respond >rpub 3>rsec ``` the user receives rpub and creates stub record and optionally uses the export key to encrypt more data: ``` cat ctx | ./opaque finalize user server 4record 3>export_key ``` the server finalizes the record by completing the stub record from the client: ``` cat rec | ./opaque store user server >record 3shared_secret' ``` **** client ``` s6-tcpclient 127.0.0.1 23523 bash -c "./opaque user user server context <&6 >&7 3< <(echo -n password) 4>export_key 5>shared_secret" ``` *** socat style **** server ``` socat tcp-l:23523,reuseaddr,fork system:"bash -c \'./opaque server user server context 3shared_secret\'" ``` **** client ``` socat tcp:127.0.0.1:23523 exec:'bash -c \"./opaque user user server context 3< <(echo -n password) 4>export_key 5>shared_secret\"' ``` libopaque-0.99.3/src/utils/main.c000066400000000000000000000506351452546740600166420ustar00rootroot00000000000000#define _POSIX_SOURCE // we need this for having fdopen declared in stdio.h #include #include #include #include #define MAX_PWD_LEN 1024 #if _WIN32 == 1 || _WIN64 == 1 #include int is_fd_open(int fd) { struct _stat buf; return _fstat(fd, &buf); } #else int is_fd_open(int fd) { return fcntl(fd, F_GETFD); } #endif static void usage(const char *self) { fprintf(stderr, "%s - libopaque commandline frontend\n(c) Stefan Marsiske 2021-22\n\n", self); fprintf(stderr, "Create new OPAQUE records\n"); fprintf(stderr, "echo -n password | %s init idU idS 3>export_key [4record - create new opaque record\n", self); fprintf(stderr, "echo -n password | %s register >msg 3>ctx - initiate new registration\n", self); fprintf(stderr, "%s respond rpub 3>rsec [4export_key >record - finalize registration\n", self); fprintf(stderr, "%s store record - complete record\n", self); fprintf(stderr, "socat | %s server-reg 3>record [4export_key - user portion of online registration\n", self); fprintf(stderr, "\nRun OPAQUE\n"); fprintf(stderr, "socat | %s server idU idS context 3shared_key - server portion of OPAQUE session\n", self); fprintf(stderr, "socat | %s user idU idS context 3< <(echo -n password) 4>export_key 5>shared_key [6export_key 5>sk [6sk static int server(const char** argv) { Opaque_Ids ids={strlen(argv[2]),(uint8_t*) argv[2], strlen(argv[3]),(uint8_t*) argv[3]}; const uint8_t *context = (const uint8_t *) argv[4]; const size_t context_len = strlen((const char*) context); uint8_t rec[OPAQUE_USER_RECORD_LEN]; // get from fd3 FILE *f = fdopen(3,"r"); if(f==NULL) { perror("error: failed to open fd 3 for record"); return 1; } int ret=fread(rec,sizeof(rec),1,f); fclose(f); if(1!=ret) { perror("error: failed to read record from fd 3"); return 1; } uint8_t resp[OPAQUE_SERVER_SESSION_LEN]; uint8_t pub[OPAQUE_USER_SESSION_PUBLIC_LEN]; // get from stdin if(1!=fread(pub,sizeof pub, 1, stdin)) { perror("failed to read client request from stdin"); return 1; } uint8_t sk[OPAQUE_SHARED_SECRETBYTES]; if(0!=sodium_mlock(sk,sizeof sk)) { fprintf(stderr, "failed to lock memory for shared secret.\n"); return 1; } uint8_t authU0[crypto_auth_hmacsha512_BYTES]; if(0!=sodium_mlock(authU0,sizeof authU0)) { fprintf(stderr, "failed to lock memory for expected client auth.\n"); sodium_munlock(sk,sizeof sk); return 1; } if(0!=opaque_CreateCredentialResponse(pub, rec, &ids, context, context_len, resp, sk, authU0)) { fprintf(stderr, "opaque_CreateCredentialResponse failed.\n"); sodium_munlock(sk,sizeof sk); sodium_munlock(authU0,sizeof authU0); return 1; } f=fdopen(4,"w"); if(f==NULL) { perror("error: failed to open fd 4 for the shared secret"); sodium_munlock(sk,sizeof sk); sodium_munlock(authU0,sizeof authU0); return 1; } ret = fwrite(sk, sizeof sk, 1, f); sodium_munlock(sk,sizeof sk); fclose(f); if(1!=ret) { fprintf(stderr, "failed to write shared secret to fd 4\n"); sodium_munlock(authU0,sizeof authU0); return 1; } if(1!=fwrite(resp, sizeof resp, 1, stdout)) { fprintf(stderr,"failed to write server response to client\n"); sodium_munlock(authU0,sizeof authU0); return 1; } fflush(stdout); uint8_t authU[crypto_auth_hmacsha512_BYTES]; // get from stdin if(1!=fread(authU, sizeof authU, 1, stdin)) { fprintf(stderr, "failed to read authU from stdin\n"); sodium_munlock(authU0,sizeof authU0); return 1; } ret = opaque_UserAuth(authU0, authU); sodium_munlock(authU0,sizeof authU0); if(-1==ret) { fprintf(stderr, "failed authenticating user\n"); return 1; } return 0; } int main(const int argc, const char **argv) { if(argc<2) { usage(argv[0]); return 0; } if(strcmp(argv[1],"init")==0) { if(argc<4) { usage(argv[0]); return 1; } return init(argv); } if(strcmp(argv[1],"register")==0) { if(argc>2) { usage(argv[0]); return 1; } return create_reg_req(); } if(strcmp(argv[1],"respond")==0) { if(argc>2) { usage(argv[0]); return 1; } return reg_respond(); } if(strcmp(argv[1],"finalize")==0) { if(argc<4) { usage(argv[0]); return 1; } return finalize(argv); } if(strcmp(argv[1],"store")==0) { if(argc<2) { usage(argv[0]); return 1; } return store(); } if(strcmp(argv[1],"server-reg")==0) { if(argc<4) { usage(argv[0]); return 1; } return server_reg(); } if(strcmp(argv[1],"user-reg")==0) { if(argc<4) { usage(argv[0]); return 1; } return user_reg(argv); } if(strcmp(argv[1],"user")==0) { if(argc<4) { usage(argv[0]); return 1; } return user(argv); } if(strcmp(argv[1],"server")==0) { if(argc<4) { usage(argv[0]); return 1; } return server(argv); } usage(argv[0]); return 1; } libopaque-0.99.3/src/utils/man/000077500000000000000000000000001452546740600163145ustar00rootroot00000000000000libopaque-0.99.3/src/utils/man/makefile000066400000000000000000000004051452546740600200130ustar00rootroot00000000000000all: opaque.1 install: $(DESTDIR)$(PREFIX)/share/man/man1/opaque.1 uninstall: $(DESTDIR)$(PREFIX)/share/man/man1/opaque.1 rm $^ $(DESTDIR)$(PREFIX)/share/man/man1/opaque.1: opaque.1 install -D $< $@ clean: rm -f *.1 %.1: %.md pandoc -s -o $@ $< libopaque-0.99.3/src/utils/man/opaque.md000066400000000000000000000107311452546740600201320ustar00rootroot00000000000000% opaque(1) | simple command-line frontend for libopaque # NAME opaque - simple command-line frontend for libopaque # SYNOPSIS Create new OPAQUE records Create new opaque record - offline ``` echo -n password | opaque init idU idS 3>export_key [4record ``` Initiate new online registration ``` echo -n password | opaque register >msg 3>ctx ``` Respond to new online registration request ``` opaque respond rpub 3>rsec [4export_key >record ``` Complete online record ``` opaque store record ``` Server portion of online registration ``` socat | opaque server-reg 3>record [4export_key ``` Run OPAQUE Server portion of OPAQUE session ``` socat | opaque server idU idS context 3shared_key ``` User portion of OPAQUE session ``` socat | opaque user idU idS context 3< <(echo -n password) 4>export_key 5>shared_key [6record 3>export_key ``` ### Online Registration #### socat style On the server: ``` socat tcp-l:23523,reuseaddr,fork system:"bash -c \'opaque server-reg user server 3>record\'" ``` On the client: ``` socat tcp:127.0.0.1:23523 exec:'bash -c \"opaque user-reg user server 3< <(echo -n password) 4>export_key\"' ``` #### tcpserver style On the server: ``` s6-tcpserver 127.0.0.1 23523 bash -c 'opaque server-reg user server 3>record' ``` On the client: ``` s6-tcpclient 127.0.0.1 23523 bash -c "opaque user-reg user server <&6 >&7 3< <(echo -n password) 4>export_key" ``` #### Manually It's possible to do all 4 steps seperately, in case you cannot connect to the server directly, then: The user initiates with: ``` echo -n password | opaque register >msg 3>ctx ``` The server gets `msg` and responds with rpub, while keeping rsec secret: ``` cat msg | opaque respond >rpub 3>rsec ``` The user receives `rpub` and creates stub record and optionally uses the export key to encrypt more data: ``` cat ctx | opaque finalize user server 4record 3>export_key ``` the server finalizes the record by completing the stub record from the client: ``` cat rec | opaque store user server >record 3shared_secret' ``` On the client: ``` s6-tcpclient 127.0.0.1 23523 bash -c "./opaque user user server context <&6 >&7 3< <(echo -n password) 4>export_key 5>shared_secret" ``` ### socat style On the server: ``` socat tcp-l:23523,reuseaddr,fork system:"bash -c \'./opaque server user server context 3shared_secret\'" ``` On the client: ``` socat tcp:127.0.0.1:23523 exec:'bash -c \"./opaque user user server context 3< <(echo -n password) 4>export_key 5>shared_secret\"' ``` # REPORTING BUGS https://github.com/stef/libopaque/issues/ # AUTHOR Written by Stefan Marsiske. # COPYRIGHT Copyright © 2023 Stefan Marsiske. License LGPLv3+: GNU Lesser GPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. # SEE ALSO https://ctrlc.hu/~stef/blog/tags/opaque/ `socat(1)`, `tcpserver(1)`