Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 587a62a
Showing
41 changed files
with
7,043 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2021 Daniel Bourdrez | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# OPAQUE | ||
[![OPAQUE](https://github.com/Jan-nku/opaque/actions/workflows/ci.yml/badge.svg)](https://github.com/Jan-nku/opaque/actions/workflows/ci.yml) | ||
[![Go Reference](https://pkg.go.dev/badge/github.com/Jan-nku/opaque.svg)](https://pkg.go.dev/github.com/Jan-nku/opaque) | ||
[![codecov](https://codecov.io/gh/Jan-nku/opaque/branch/main/graph/badge.svg?token=5bQfB0OctA)](https://codecov.io/gh/Jan-nku/opaque) | ||
|
||
``` | ||
import "github.com/Jan-nku/opaque" | ||
``` | ||
|
||
This package implements [OPAQUE](https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque), an asymmetric password-authenticated | ||
key exchange protocol that is secure against pre-computation attacks. It enables a client to authenticate to a server | ||
without ever revealing its password to the server. | ||
|
||
This implementation is developed by one of the authors of the RFC [Internet Draft](https://github.com/cfrg/draft-irtf-cfrg-opaque). | ||
The main branch is in sync with the latest developments of the draft, and [the releases](https://github.com/Jan-nku/opaque/releases) | ||
correspond to the [official draft versions](https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque). | ||
|
||
#### What is OPAQUE? | ||
|
||
> OPAQUE is a PKI-free secure aPAKE that is secure against pre-computation attacks. OPAQUE provides forward secrecy with | ||
> respect to password leakage while also hiding the password from the server, even during password registration. OPAQUE | ||
> allows applications to increase the difficulty of offline dictionary attacks via iterated hashing or other key | ||
> stretching schemes. OPAQUE is also extensible, allowing clients to safely store and retrieve arbitrary application data | ||
> on servers using only their password. | ||
#### References | ||
- [The original paper](https://eprint.iacr.org/2018/163.pdf) from Jarecki, Krawczyk, and Xu. | ||
- [OPAQUE is used in WhatsApp](https://www.whatsapp.com/security/WhatsApp_Security_Encrypted_Backups_Whitepaper.pdf) to enable end-to-end encrypted backups. | ||
- [The GitHub repo](https://github.com/cfrg/draft-irtf-cfrg-opaque) where the draft is being specified. | ||
|
||
## Documentation [![Go Reference](https://pkg.go.dev/badge/github.com/Jan-nku/opaque.svg)](https://pkg.go.dev/github.com/Jan-nku/opaque) | ||
|
||
You can find the documentation and usage examples in [the package doc](https://pkg.go.dev/github.com/Jan-nku/opaque) and [the project wiki](https://github.com/Jan-nku/opaque/wiki) . | ||
|
||
## Versioning | ||
|
||
[SemVer](http://semver.org) is used for versioning. For the versions available, see the [tags on the repository](https://github.com/Jan-nku/opaque/tags). | ||
|
||
Minor v0.x versions match the corresponding CFRG draft version, the master branch implements the latest changes of [the draft development](https://github.com/cfrg/draft-irtf-cfrg-opaque). | ||
|
||
## Contributing | ||
|
||
Please read [CONTRIBUTING.md](.github/CONTRIBUTING.md) for details on the code of conduct, and the process for submitting pull requests. | ||
|
||
## License | ||
|
||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Security Policy | ||
|
||
## Supported Versions | ||
|
||
The OPAQUE protocol is still in the process of specification. Therefore, this implementation evolves with the draft. | ||
Only the latest version will be benefit from security fixes. Maintainers of projects using this implementation of OPAQUE are invited to update their dependency. | ||
|
||
## Reporting a Vulnerability | ||
|
||
Vulnerabilities can be reported through Github issues, here: https://github.com/Jan-nku/opaque/security/advisories | ||
If the issue is sensitive enough that the reporter thinks the discussion needs more confidentiality, we can discuss options there (e.g. On a Security Advisory or per e-mail). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,282 @@ | ||
// SPDX-License-Identifier: MIT | ||
// | ||
// Copyright (C) 2020-2022 Daniel Bourdrez. All Rights Reserved. | ||
// | ||
// This source code is licensed under the MIT license found in the | ||
// LICENSE file in the root directory of this source tree or at | ||
// https://spdx.org/licenses/MIT.html | ||
|
||
package opaque | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
group "github.com/bytemare/crypto" | ||
|
||
"github.com/Jan-nku/opaque/internal" | ||
"github.com/Jan-nku/opaque/internal/ake" | ||
"github.com/Jan-nku/opaque/internal/encoding" | ||
"github.com/Jan-nku/opaque/internal/keyrecovery" | ||
"github.com/Jan-nku/opaque/internal/masking" | ||
"github.com/Jan-nku/opaque/internal/oprf" | ||
"github.com/Jan-nku/opaque/internal/tag" | ||
"github.com/Jan-nku/opaque/message" | ||
) | ||
|
||
var ( | ||
// errInvalidMaskedLength happens when unmasking a masked response. | ||
errInvalidMaskedLength = errors.New("invalid masked response length") | ||
|
||
// errKe1Missing happens when LoginFinish is called and the client has no Ke1 in state. | ||
errKe1Missing = errors.New("missing KE1 in client state") | ||
) | ||
|
||
// Client represents an OPAQUE Client, exposing its functions and holding its state. | ||
type Client struct { | ||
Deserialize *Deserializer | ||
OPRF *oprf.Client | ||
Ake *ake.Client | ||
conf *internal.Configuration | ||
} | ||
|
||
// NewClient returns a new Client instantiation given the application Configuration. | ||
func NewClient(c *Configuration) (*Client, error) { | ||
if c == nil { | ||
c = DefaultConfiguration() | ||
} | ||
|
||
conf, err := c.toInternal() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &Client{ | ||
OPRF: conf.OPRF.Client(), | ||
Ake: ake.NewClient(), | ||
Deserialize: &Deserializer{conf: conf}, | ||
conf: conf, | ||
}, nil | ||
} | ||
|
||
// GetConf returns the internal configuration. | ||
func (c *Client) GetConf() *internal.Configuration { | ||
return c.conf | ||
} | ||
|
||
// TODO: buildPRK | ||
// buildPRK derives the randomized password from the OPRF output. | ||
func (c *Client) buildPRK(evaluation *group.Element) []byte { | ||
//output = h(pw, h'(pw)^ku) []byte | ||
output := c.OPRF.Finalize(evaluation) | ||
// Harden函数做一个延展? | ||
stretched := c.conf.KSF.Harden(output, nil, c.conf.OPRF.Group().ElementLength()) | ||
//Extract | ||
return c.conf.KDF.Extract(nil, encoding.Concat(output, stretched)) | ||
} | ||
|
||
// ClientRegistrationInitOptions enables setting internal client values for the client registration. | ||
type ClientRegistrationInitOptions struct { | ||
// OPRFBlind: optional | ||
OPRFBlind *group.Scalar | ||
} | ||
|
||
func getClientRegistrationInitBlind(options []ClientRegistrationInitOptions) *group.Scalar { | ||
if len(options) == 0 { | ||
return nil | ||
} | ||
|
||
return options[0].OPRFBlind | ||
} | ||
|
||
// TODO: RegistrationInit | ||
// RegistrationInit returns a RegistrationRequest message blinding the given password. | ||
func (c *Client) RegistrationInit( | ||
password []byte, | ||
username []byte, | ||
options ...ClientRegistrationInitOptions, | ||
) *message.RegistrationRequest { | ||
m := c.OPRF.Blind(password, getClientRegistrationInitBlind(options)) | ||
|
||
return &message.RegistrationRequest{ | ||
BlindedMessage: m, | ||
UserName: username, | ||
} | ||
} | ||
|
||
// TODO: ClientRegistrationFinalizeOptions contain ClientIdentity, ServerIdentity | ||
// ClientRegistrationFinalizeOptions enables setting optional client values for the client registration. | ||
type ClientRegistrationFinalizeOptions struct { | ||
// ClientIdentity: optional | ||
ClientIdentity []byte | ||
// ServerIdentity: optional | ||
ServerIdentity []byte | ||
// EnvelopeNonce : optional | ||
EnvelopeNonce []byte | ||
} | ||
|
||
func initClientRegistrationFinalizeOptions(options []ClientRegistrationFinalizeOptions) *keyrecovery.Credentials { | ||
if len(options) == 0 { | ||
return &keyrecovery.Credentials{ | ||
ClientIdentity: nil, | ||
ServerIdentity: nil, | ||
EnvelopeNonce: nil, | ||
} | ||
} | ||
|
||
return &keyrecovery.Credentials{ | ||
ClientIdentity: options[0].ClientIdentity, | ||
ServerIdentity: options[0].ServerIdentity, | ||
EnvelopeNonce: options[0].EnvelopeNonce, | ||
} | ||
} | ||
|
||
// TODO: RegistrationFinalize | ||
// RegistrationFinalize returns a RegistrationRecord message given the identities and the server's RegistrationResponse. | ||
func (c *Client) RegistrationFinalize( | ||
resp *message.RegistrationResponse, | ||
options ...ClientRegistrationFinalizeOptions, | ||
) (record *message.RegistrationRecord, exportKey []byte) { | ||
credentials := initClientRegistrationFinalizeOptions(options) | ||
// generate randomizedPwd from resp.EvaluatedMessage, related to h(pw, h'(pw)^ku) | ||
randomizedPwd := c.buildPRK(resp.EvaluatedMessage) | ||
|
||
// generate maskingKey from hash(randomizedPwd) | ||
maskingKey := c.conf.KDF.Expand(randomizedPwd, []byte(tag.MaskingKey), c.conf.KDF.Size()) | ||
envelope, clientPublicKey, exportKey := keyrecovery.Store(c.conf, randomizedPwd, resp.Pks, credentials) | ||
|
||
//envelope 存储 在 RegistrationRecord 中 | ||
return &message.RegistrationRecord{ | ||
PublicKey: clientPublicKey, | ||
MaskingKey: maskingKey, | ||
Envelope: envelope.Serialize(), | ||
}, exportKey | ||
} | ||
|
||
// ClientLoginInitOptions enables setting optional values for the session, which default to secure random values if not | ||
// set. | ||
type ClientLoginInitOptions struct { | ||
// Blind: optional | ||
Blind *group.Scalar | ||
// EphemeralSecretKey: optional | ||
EphemeralSecretKey *group.Scalar | ||
// Nonce: optional | ||
Nonce []byte | ||
// NonceLength: optional | ||
NonceLength uint | ||
} | ||
|
||
func (c ClientLoginInitOptions) get() (*group.Scalar, ake.Options) { | ||
return c.Blind, ake.Options{ | ||
EphemeralSecretKey: c.EphemeralSecretKey, | ||
Nonce: c.Nonce, | ||
NonceLength: c.NonceLength, | ||
} | ||
} | ||
|
||
func getClientLoginInitOptions(options []ClientLoginInitOptions) (*group.Scalar, ake.Options) { | ||
if len(options) != 0 { | ||
return options[0].get() | ||
} | ||
|
||
return nil, ake.Options{ | ||
EphemeralSecretKey: nil, | ||
Nonce: nil, | ||
NonceLength: internal.NonceLength, | ||
} | ||
} | ||
|
||
// TODO: LoginInit(password) | ||
// LoginInit initiates the authentication process, returning a KE1 message blinding the given password. | ||
func (c *Client) LoginInit(password, username []byte, options ...ClientLoginInitOptions) *message.KE1 { | ||
blind, akeOptions := getClientLoginInitOptions(options) | ||
m := c.OPRF.Blind(password, blind) | ||
|
||
//TODO: start initiates the 3dh protocol, call setOptions(g, options) function | ||
ke1 := c.Ake.Start(c.conf.Group, akeOptions) | ||
ke1.CredentialRequest = message.NewCredentialRequest(c.conf.OPRF, m) | ||
ke1.UserName = make([]byte, len(username)) | ||
copy(ke1.UserName, username) | ||
c.Ake.Ke1 = ke1.Serialize() | ||
|
||
return ke1 | ||
} | ||
|
||
// ClientLoginFinishOptions enables setting optional client values for the client registration. | ||
type ClientLoginFinishOptions struct { | ||
// ClientIdentity: optional | ||
ClientIdentity []byte | ||
// ServerIdentity: optional | ||
ServerIdentity []byte | ||
} | ||
|
||
func initClientLoginFinishOptions(options []ClientLoginFinishOptions) *ake.Identities { | ||
if len(options) == 0 { | ||
return &ake.Identities{ | ||
ClientIdentity: nil, | ||
ServerIdentity: nil, | ||
} | ||
} | ||
|
||
return &ake.Identities{ | ||
ClientIdentity: options[0].ClientIdentity, | ||
ServerIdentity: options[0].ServerIdentity, | ||
} | ||
} | ||
|
||
// LoginFinish returns a KE3 message given the server's KE2 response message and the identities. If the idc | ||
// or ids parameters are nil, the client and server's public keys are taken as identities for both. | ||
func (c *Client) LoginFinish( | ||
ke2 *message.KE2, options ...ClientLoginFinishOptions, | ||
) (ke3 *message.KE3, exportKey []byte, err error) { | ||
if len(c.Ake.Ke1) == 0 { | ||
return nil, nil, errKe1Missing | ||
} | ||
|
||
// This test is very important as it avoids buffer overflows in subsequent parsing. | ||
if len(ke2.MaskedResponse) != c.conf.Group.ElementLength()+c.conf.EnvelopeSize { | ||
return nil, nil, errInvalidMaskedLength | ||
} | ||
|
||
identities := initClientLoginFinishOptions(options) | ||
|
||
// Finalize the OPRF. | ||
randomizedPwd := c.buildPRK(ke2.EvaluatedMessage) | ||
|
||
// Decrypt the masked response. | ||
serverPublicKey, serverPublicKeyBytes, | ||
envelope, err := masking.Unmask(c.conf, randomizedPwd, ke2.MaskingNonce, ke2.MaskedResponse) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("unmasking: %w", err) | ||
} | ||
|
||
// Recover the client keys. | ||
clientSecretKey, clientPublicKey, | ||
exportKey, err := keyrecovery.Recover( | ||
c.conf, | ||
randomizedPwd, | ||
serverPublicKeyBytes, | ||
identities.ClientIdentity, | ||
identities.ServerIdentity, | ||
envelope) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("key recovery: %w", err) | ||
} | ||
|
||
// Finalize the AKE. | ||
// SetIdentities sets the client and server identities to their respective public key if not set. | ||
identities.SetIdentities(clientPublicKey, serverPublicKeyBytes) | ||
|
||
// produce ke3 | ||
ke3, err = c.Ake.Finalize(c.conf, identities, clientSecretKey, serverPublicKey, ke2) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("finalizing AKE: %w", err) | ||
} | ||
|
||
return ke3, exportKey, nil | ||
} | ||
|
||
// SessionKey returns the session key if the previous call to LoginFinish() was successful. | ||
func (c *Client) SessionKey() []byte { | ||
return c.Ake.SessionKey() | ||
} |
Oops, something went wrong.