Skip to content

Commit

Permalink
HPAKE
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan-nku committed Oct 5, 2023
0 parents commit 587a62a
Show file tree
Hide file tree
Showing 41 changed files with 7,043 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
@@ -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.
47 changes: 47 additions & 0 deletions README.md
@@ -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.
11 changes: 11 additions & 0 deletions SECURITY.md
@@ -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).
282 changes: 282 additions & 0 deletions client.go
@@ -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()
}

0 comments on commit 587a62a

Please sign in to comment.