Documentation
¶
Overview ¶
Package opaque implements 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. Protocol details can be found on the IETF RFC page (https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque) and on the GitHub specification repository (https://github.com/cfrg/draft-irtf-cfrg-opaque).
Example (Authentication) ¶
Example_authentication demonstrates in a single function the interactions between a client and a server for the login phase. This is of course a proof-of-concept demonstration, as client and server execute separately.
package main
import (
"bytes"
"fmt"
"log"
"github.com/bytemare/ecc"
"github.com/bytemare/opaque"
"github.com/bytemare/opaque/message"
)
var (
exampleClientRecord *opaque.ClientRecord
secretGlobalOprfSeed []byte
serverPrivateKey *ecc.Scalar
serverPublicKey *ecc.Element
)
// Example_Registration demonstrates in a single function the interactions between a client and a server for the
// registration phase. This is of course a proof-of-concept demonstration, as client and server execute separately.
// The server outputs a ClientRecord and the credential identifier. The latter is a unique identifier for a given
// client (e.g. database entry ID), and that must absolutely stay the same for the whole client existence and
// never be reused.
func Example_registration() {
serverID := []byte("server")
clientID := []byte("username")
conf := opaque.DefaultConfiguration()
password := []byte("password")
secretGlobalOprfSeed = conf.GenerateOPRFSeed()
serverPrivateKey, serverPublicKey = conf.KeyGen()
skm := &opaque.ServerKeyMaterial{
Identity: serverID,
PrivateKey: serverPrivateKey,
PublicKeyBytes: serverPublicKey.Encode(),
OPRFGlobalSeed: secretGlobalOprfSeed,
}
server, err := conf.Server()
if err != nil {
log.Fatalln(err)
}
if err = server.SetKeyMaterial(skm); err != nil {
log.Fatalln(err)
}
client, err := conf.Client()
if err != nil {
log.Fatalln(err)
}
var message1, message2, message3 []byte
var credID []byte
{
c1, err := client.RegistrationInit(password)
if err != nil {
log.Fatalln(err)
}
message1 = c1.Serialize()
}
{
request, err := server.Deserialize.RegistrationRequest(message1)
if err != nil {
log.Fatalln(err)
}
credID = opaque.RandomBytes(64)
response, err := server.RegistrationResponse(request, credID, nil)
if err != nil {
log.Fatalln(err)
}
message2 = response.Serialize()
}
{
response, err := client.Deserialize.RegistrationResponse(message2)
if err != nil {
log.Fatalln(err)
}
record, _, err := client.RegistrationFinalize(response, clientID, serverID)
if err != nil {
log.Fatalln(err)
}
message3 = record.Serialize()
}
{
record, err := server.Deserialize.RegistrationRecord(message3)
if err != nil {
log.Fatalln(err)
}
exampleClientRecord = &opaque.ClientRecord{
CredentialIdentifier: credID,
ClientIdentity: clientID,
RegistrationRecord: record,
}
fmt.Println("OPAQUE registration is easy!")
}
}
func main() {
// For the purpose of this demo, we consider the following registration has already happened.
{
Example_registration()
}
// Information shared by both client and server.
serverID := []byte("server")
clientID := []byte("username")
conf := opaque.DefaultConfiguration()
// Secret client information.
password := []byte("password")
// Secret server information.
// This can be encoded and backed up with the Encode() and Hex() methods, and later recovered.
skm := &opaque.ServerKeyMaterial{
Identity: serverID,
PrivateKey: serverPrivateKey,
PublicKeyBytes: serverPublicKey.Encode(),
OPRFGlobalSeed: secretGlobalOprfSeed,
}
// Runtime instantiation of the server.
server, err := conf.Server()
if err != nil {
log.Fatalln(err)
}
if err = server.SetKeyMaterial(skm); err != nil {
log.Fatalln(err)
}
// Runtime instantiation of the client.
client, err := conf.Client()
if err != nil {
log.Fatalln(err)
}
// These are the 3 login messages that will be exchanged,
// and the respective sessions keys for the client and server.
var message1, message2, message3 []byte
var clientSessionKey, serverSessionKey []byte
// The client initiates the ball and sends the serialized ke1 to the server.
{
ke1, err := client.GenerateKE1(password)
if err != nil {
log.Fatalln(err)
}
message1 = ke1.Serialize()
}
// The server interprets ke1, and sends back ke2.
var serverOutput *opaque.ServerOutput
{
var (
ke1 *message.KE1
ke2 *message.KE2
)
ke1, err = server.Deserialize.KE1(message1)
if err != nil {
log.Fatalln(err)
}
ke2, serverOutput, err = server.GenerateKE2(ke1, exampleClientRecord)
if err != nil {
log.Fatalln(err)
}
message2 = ke2.Serialize()
}
// The client interprets ke2. If everything went fine, the server is considered trustworthy and the client
// can use the shared session key and secret export key.
{
var ke3 *message.KE3
ke2, err := client.Deserialize.KE2(message2)
if err != nil {
log.Fatalln(err)
}
// In this example, we don't use the secret export key. The client sends the serialized ke3 to the server.
ke3, clientSessionKey, _, err = client.GenerateKE3(ke2, clientID, serverID)
if err != nil {
log.Fatalln(err)
}
message3 = ke3.Serialize()
// If no error occurred here, the server can be trusted, and the client can use the session key.
fmt.Println("OPAQUE succeeded from the client perspective, which can now safely use the session key.")
}
// The server must absolutely validate this last message to authenticate the client and continue. If this message
// does not return successfully, the server must not send any secret or sensitive information and immediately cease
// the connection.
{
ke3, err := server.Deserialize.KE3(message3)
if err != nil {
log.Fatalln(err)
}
if err = server.LoginFinish(ke3, serverOutput.ClientMAC); err != nil {
log.Fatalln(err)
}
// If no error occurred here, the client can be trusted, and the server can use the session key.
fmt.Println("OPAQUE succeeded from the server perspective, which can now safely use the session key.")
// If no error occurred at this point, the server can trust the client and safely extract the shared session key.
serverSessionKey = serverOutput.SessionSecret
}
// The following test does not exist in the real world and simply proves the point that the keys match for the demo.
if !bytes.Equal(clientSessionKey, serverSessionKey) {
log.Fatalln("Oh no! Abort! The shared session keys don't match!")
}
fmt.Println("OPAQUE is much awesome!")
}
Output: OPAQUE registration is easy! OPAQUE succeeded from the client perspective, which can now safely use the session key. OPAQUE succeeded from the server perspective, which can now safely use the session key. OPAQUE is much awesome!
Example (Configuration) ¶
Example_Configuration shows how to instantiate a configuration, which is used to initialize clients and servers from. Configurations MUST remain the same for a given client between sessions, or the client won't be able to execute the protocol. Configurations can be serialized and deserialized, if you need to save, hardcode, or transmit it.
package main
import (
"crypto"
"encoding/hex"
"fmt"
"log"
"github.com/bytemare/ksf"
"github.com/bytemare/opaque"
)
func main() {
// You can compose your own configuration or choose a recommended default configuration.
// The two following configuration setups are the same.
defaultConf := opaque.DefaultConfiguration()
customConf := &opaque.Configuration{
OPRF: opaque.RistrettoSha512,
AKE: opaque.RistrettoSha512,
KSF: ksf.Argon2id,
KDF: crypto.SHA512,
MAC: crypto.SHA512,
Hash: crypto.SHA512,
Context: nil,
}
if err := defaultConf.Equals(customConf); err != nil {
// This is just a demo to show that the two configurations are the same.
// In practice, you would just pick one way to create a configuration.
// Recommended is to use DefaultConfiguration() or one of the other defaults.
log.Fatalln("Oh no! Configurations differ!")
}
// A configuration can be saved encoded and saved, and later loaded and decoded at runtime.
// Any additional 'Context' is also included.
encoded := defaultConf.Serialize()
fmt.Printf("Encoded Configuration: %s\n", hex.EncodeToString(encoded))
// This how you decode that configuration.
conf, err := opaque.DeserializeConfiguration(encoded)
if err != nil {
log.Fatalf("Oh no! Decoding the configurations failed! %v", err)
}
if err = defaultConf.Equals(conf); err != nil {
log.Fatalln("Oh no! Something went wrong in decoding the configuration!")
}
fmt.Println("OPAQUE configuration is easy!")
}
Output: Encoded Configuration: 0101010707070000 OPAQUE configuration is easy!
Example (Deserialization) ¶
Example_Deserialization demonstrates a couple of ways to deserialize OPAQUE protocol messages. Message interpretation depends on the configuration context it's exchanged in. Hence, we need the corresponding configuration. We can then directly deserialize messages from a Configuration or pass them to Client or Server instances which can do it as well. You must know in advance what message you are expecting, and call the appropriate deserialization function.
package main
import (
"encoding/hex"
"fmt"
"log"
"reflect"
"github.com/bytemare/opaque"
)
func main() {
// Let's say we have this RegistrationRequest message we received on the wire.
registrationMessage, _ := hex.DecodeString("9857e1694af550c515e56a9103292ad07a014b020708d3df57ac4b151f58d323")
// Pick your configuration.
conf := opaque.DefaultConfiguration()
// You can directly deserialize and test the message's validity in that configuration by getting a deserializer.
deserializer, err := conf.Deserializer()
if err != nil {
log.Fatalln(err)
}
requestD, err := deserializer.RegistrationRequest(registrationMessage)
if err != nil {
log.Fatalln(err)
}
// Or if you already have a Server instance, you can use that also.
server, err := conf.Server()
if err != nil {
log.Fatalln(err)
}
requestS, err := server.Deserialize.RegistrationRequest(registrationMessage)
if err != nil {
// The error message will tell us what's wrong.
log.Fatalln(err)
}
// Alternatively, a Client instance can do that as well.
client, err := conf.Client()
if err != nil {
// The error message will tell us what's wrong.
log.Fatalln(err)
}
requestC, err := client.Deserialize.RegistrationRequest(registrationMessage)
if err != nil {
// The error message will tell us what's wrong.
log.Fatalln(err)
}
// All these yield the same message. The following is just a test to proof that point.
{
if !reflect.DeepEqual(requestD, requestS) ||
!reflect.DeepEqual(requestD, requestC) ||
!reflect.DeepEqual(requestS, requestC) {
log.Fatalf("Unexpected divergent RegistrationMessages:\n\t- %v\n\t- %v\n\t- %v",
hex.EncodeToString(requestD.Serialize()),
hex.EncodeToString(requestS.Serialize()),
hex.EncodeToString(requestC.Serialize()))
}
fmt.Println("OPAQUE messages deserialization is easy!")
}
}
Output: OPAQUE messages deserialization is easy!
Example (ErrorHandling) ¶
Example: handling high-level errors and specific causes.
package main
import (
"errors"
"fmt"
"github.com/bytemare/opaque"
"github.com/bytemare/opaque/internal"
)
func main() {
// Simulate an error chain
err := opaque.ErrAuthentication.Join(internal.ErrServerAuthentication, internal.ErrInvalidServerMac)
switch {
case errors.Is(err, opaque.ErrAuthentication):
// top-level class
fmt.Println("auth error: abort and do not use keys")
// handle specific cause
if errors.Is(err, internal.ErrInvalidServerMac) {
fmt.Println("server MAC invalid: report generic failure")
}
case errors.Is(err, opaque.ErrRegistration):
fmt.Println("registration error: notify client")
default:
fmt.Println("unexpected error")
}
}
Output: auth error: abort and do not use keys server MAC invalid: report generic failure
Example (FakeResponse) ¶
Example_FakeResponse shows how to counter some client enumeration attacks by faking an existing client entry. Precompute the fake client record, and return it when no valid record was found. Use this with the server's GenerateKE2 function whenever a client wants to retrieve an envelope but a client entry does not exist. Failing to do so results in an attacker being able to enumerate users.
package main
import (
"encoding/hex"
"fmt"
"log"
"github.com/bytemare/ecc"
"github.com/bytemare/opaque"
)
var (
secretGlobalOprfSeed []byte
serverPrivateKey *ecc.Scalar
serverPublicKey *ecc.Element
)
// Example_ServerSetup shows how to set up the long term values for the OPAQUE server.
// - The secret OPRF seed can be unique for each client or the same for all, but must be
// the same for a given client between registration and all login sessions.
// - The AKE key pair can also be the same for all clients or unique, but must be
// the same for a given client between registration and all login sessions.
func Example_serverSetup() {
serverID := []byte("server-identity")
conf := opaque.DefaultConfiguration()
secretGlobalOprfSeed = conf.GenerateOPRFSeed()
serverPrivateKey, serverPublicKey = conf.KeyGen()
if serverPrivateKey == nil || serverPublicKey == nil || secretGlobalOprfSeed == nil {
log.Fatalf("Oh no! Something went wrong setting up the server secrets!")
}
server, err := conf.Server()
if err != nil {
log.Fatalln(err)
}
skm := &opaque.ServerKeyMaterial{
Identity: serverID,
PrivateKey: serverPrivateKey,
PublicKeyBytes: serverPublicKey.Encode(),
OPRFGlobalSeed: secretGlobalOprfSeed,
}
if err = server.SetKeyMaterial(skm); err != nil {
log.Fatalln(err)
}
fmt.Println("OPAQUE server initialized.")
}
func main() {
// The server must have been set up with its long term values once. So we're calling this, here, for the demo.
{
Example_serverSetup()
}
// Precompute the fake client record, store it, and use it when no valid record was found for the requested client
// id. The malicious client will purposefully fail, but can't determine the difference with an existing
// client record. Choose the same configuration as in your app.
conf := opaque.DefaultConfiguration()
fakeRecord, err := conf.GetFakeRecord([]byte("id_to_the_fake_client"))
if err != nil {
log.Fatalln(err)
}
// Runtime instantiation of the server. Note that there's no change to the regular server setup.
server, err := conf.Server()
if err != nil {
log.Fatalln(err)
}
skm := &opaque.ServerKeyMaterial{
PrivateKey: serverPrivateKey,
PublicKeyBytes: serverPublicKey.Encode(),
OPRFGlobalSeed: secretGlobalOprfSeed,
}
if err = server.SetKeyMaterial(skm); err != nil {
log.Fatalln(err)
}
// Later, during protocol execution, let's say this is the fraudulent login message we received,
// for which no client entry exists.
message1, _ := hex.DecodeString("b4d366645e7ae380f9d476e1319e67c1821f7a5d3dfbfc4e26c7898351979139" +
"0ea528fc609b4393b0353e85fdbb20c6067c11919f40d93d8bb229967fc2878c" +
"209786ef4b960bfbfe10481c1fd301300fc72dc4234a1e829b556c720f904d30")
// Continue as usual, using the fake record in lieu of the (non-)existing one. The server the sends
// back the serialized ke2 message message2.
var message2 []byte
{
ke1, err := server.Deserialize.KE1(message1)
if err != nil {
log.Fatalln(err)
}
ke2, _, err := server.GenerateKE2(ke1, fakeRecord)
if err != nil {
log.Fatalln(err)
}
message2 = ke2.Serialize()
}
// The following is just a test to check everything went fine.
{
if len(message2) == 0 {
log.Fatalln("Fake KE2 is unexpectedly empty.")
}
fmt.Println("Thwarting OPAQUE client enumeration is easy!")
}
}
Output: OPAQUE server initialized. Thwarting OPAQUE client enumeration is easy!
Example (Registration) ¶
Example_Registration demonstrates in a single function the interactions between a client and a server for the registration phase. This is of course a proof-of-concept demonstration, as client and server execute separately. The server outputs a ClientRecord and the credential identifier. The latter is a unique identifier for a given client (e.g. database entry ID), and that must absolutely stay the same for the whole client existence and never be reused.
package main
import (
"fmt"
"log"
"github.com/bytemare/ecc"
"github.com/bytemare/opaque"
)
var (
exampleClientRecord *opaque.ClientRecord
secretGlobalOprfSeed []byte
serverPrivateKey *ecc.Scalar
serverPublicKey *ecc.Element
)
func main() {
// Information shared by both client and server.
serverID := []byte("server")
clientID := []byte("username")
conf := opaque.DefaultConfiguration()
// Secret client information.
password := []byte("password")
// Secret server information, this is set up once and for all, and must be the same for a given client.
// This can be encoded and backed up with the Encode() and Hex() methods, and later recovered.
secretGlobalOprfSeed = conf.GenerateOPRFSeed()
serverPrivateKey, serverPublicKey = conf.KeyGen()
skm := &opaque.ServerKeyMaterial{
Identity: serverID,
PrivateKey: serverPrivateKey,
PublicKeyBytes: serverPublicKey.Encode(),
OPRFGlobalSeed: secretGlobalOprfSeed,
}
// Runtime instantiation of the server.
server, err := conf.Server()
if err != nil {
log.Fatalln(err)
}
if err = server.SetKeyMaterial(skm); err != nil {
log.Fatalln(err)
}
// Runtime instantiation of the client.
client, err := conf.Client()
if err != nil {
log.Fatalln(err)
}
// These are the 3 registration messages that will be exchanged.
// The credential identifier credID is a unique identifier for a given client (e.g. database entry ID), and that
// must absolutely stay the same for the whole client existence and never be reused.
var message1, message2, message3 []byte
var credID []byte
// The client starts, serializes the message, and sends it to the server.
{
c1, err := client.RegistrationInit(password)
if err != nil {
log.Fatalln(err)
}
message1 = c1.Serialize()
}
// The server receives the encoded message, decodes it, interprets it, and returns its response.
{
request, err := server.Deserialize.RegistrationRequest(message1)
if err != nil {
log.Fatalln(err)
}
// The server creates a database entry for the client and creates a credential identifier that must absolutely
// be unique among all clients.
credID = opaque.RandomBytes(64)
// The server uses its public key and secret OPRF seed created at the setup.
response, err := server.RegistrationResponse(request, credID, nil)
if err != nil {
log.Fatalln(err)
}
// The server responds with its serialized response.
message2 = response.Serialize()
}
// The client deserializes the responses, and sends back its final client record containing the envelope.
{
response, err := client.Deserialize.RegistrationResponse(message2)
if err != nil {
log.Fatalln(err)
}
// The client produces its record and a client-only-known secret export_key, that the client can use for other purposes (e.g. encrypt
// information to store on the server, and that the server can't decrypt). We don't use in the example here.
record, _, err := client.RegistrationFinalize(response, clientID, serverID)
if err != nil {
log.Fatalln(err)
}
message3 = record.Serialize()
}
// Server registers the client record.
{
record, err := server.Deserialize.RegistrationRecord(message3)
if err != nil {
log.Fatalln(err)
}
exampleClientRecord = &opaque.ClientRecord{
CredentialIdentifier: credID,
ClientIdentity: clientID,
RegistrationRecord: record,
}
fmt.Println("OPAQUE registration is easy!")
}
}
Output: OPAQUE registration is easy!
Example (ServerSetup) ¶
Example_ServerSetup shows how to set up the long term values for the OPAQUE server. - The secret OPRF seed can be unique for each client or the same for all, but must be the same for a given client between registration and all login sessions. - The AKE key pair can also be the same for all clients or unique, but must be the same for a given client between registration and all login sessions.
package main
import (
"fmt"
"log"
"github.com/bytemare/ecc"
"github.com/bytemare/opaque"
)
var (
secretGlobalOprfSeed []byte
serverPrivateKey *ecc.Scalar
serverPublicKey *ecc.Element
)
func main() {
// This a straightforward way to use a secure and efficient configuration.
// They have to be run only once in the application's lifecycle, and the output values must be stored appropriately.
serverID := []byte("server-identity")
conf := opaque.DefaultConfiguration()
secretGlobalOprfSeed = conf.GenerateOPRFSeed()
serverPrivateKey, serverPublicKey = conf.KeyGen()
if serverPrivateKey == nil || serverPublicKey == nil || secretGlobalOprfSeed == nil {
log.Fatalf("Oh no! Something went wrong setting up the server secrets!")
}
// Server setup
server, err := conf.Server()
if err != nil {
log.Fatalln(err)
}
skm := &opaque.ServerKeyMaterial{
Identity: serverID,
PrivateKey: serverPrivateKey,
PublicKeyBytes: serverPublicKey.Encode(),
OPRFGlobalSeed: secretGlobalOprfSeed,
}
if err = server.SetKeyMaterial(skm); err != nil {
log.Fatalln(err)
}
fmt.Println("OPAQUE server initialized.")
}
Output: OPAQUE server initialized.
Index ¶
- Constants
- Variables
- func DeserializeElement(g ecc.Group, input []byte) (*ecc.Element, error)
- func DeserializeScalar(g ecc.Group, input []byte) (*ecc.Scalar, error)
- func IsValidElement(g ecc.Group, e *ecc.Element) error
- func IsValidScalar(g ecc.Group, s *ecc.Scalar) error
- func RandomBytes(length int) []byte
- type AKEOptions
- type Client
- func (c *Client) ClearState()
- func (c *Client) GenerateKE1(password []byte, options ...*ClientOptions) (*message.KE1, error)
- func (c *Client) GenerateKE3(ke2 *message.KE2, clientIdentity, serverIdentity []byte, ...) (ke3 *message.KE3, sessionKey, exportKey []byte, err error)
- func (c *Client) RegistrationFinalize(resp *message.RegistrationResponse, clientIdentity, serverIdentity []byte, ...) (record *message.RegistrationRecord, exportKey []byte, err error)
- func (c *Client) RegistrationInit(password []byte, options ...*ClientOptions) (*message.RegistrationRequest, error)
- type ClientOptions
- type ClientRecord
- type Configuration
- func (c *Configuration) Client() (*Client, error)
- func (c *Configuration) DecodeServerKeyMaterial(data []byte) (*ServerKeyMaterial, error)
- func (c *Configuration) DecodeServerKeyMaterialHex(data string) (*ServerKeyMaterial, error)
- func (c *Configuration) Deserializer() (*Deserializer, error)
- func (c *Configuration) Equals(other *Configuration) error
- func (c *Configuration) GenerateOPRFSeed() []byte
- func (c *Configuration) GetFakeRecord(credentialIdentifier []byte) (*ClientRecord, error)
- func (c *Configuration) KeyGen() (secretKey *ecc.Scalar, publicKey *ecc.Element)
- func (c *Configuration) Serialize() []byte
- func (c *Configuration) Server() (*Server, error)
- type Deserializer
- func (d *Deserializer) DecodePrivateKey(encoded []byte) (*ecc.Scalar, error)
- func (d *Deserializer) DecodePublicKey(encoded []byte) (*ecc.Element, error)
- func (d *Deserializer) KE1(ke1 []byte) (*message.KE1, error)
- func (d *Deserializer) KE2(ke2 []byte) (*message.KE2, error)
- func (d *Deserializer) KE3(ke3 []byte) (*message.KE3, error)
- func (d *Deserializer) RegistrationRecord(record []byte) (*message.RegistrationRecord, error)
- func (d *Deserializer) RegistrationRequest(registrationRequest []byte) (*message.RegistrationRequest, error)
- func (d *Deserializer) RegistrationResponse(registrationResponse []byte) (*message.RegistrationResponse, error)
- type Error
- type ErrorCode
- type Group
- type Server
- func (s *Server) GenerateKE2(ke1 *message.KE1, record *ClientRecord, options ...*ServerOptions) (*message.KE2, *ServerOutput, error)
- func (s *Server) LoginFinish(ke3 *message.KE3, expectedClientMac []byte) error
- func (s *Server) RegistrationResponse(req *message.RegistrationRequest, clientCredentialIdentifier []byte, ...) (*message.RegistrationResponse, error)
- func (s *Server) SetKeyMaterial(skm *ServerKeyMaterial) error
- type ServerKeyMaterial
- type ServerOptions
- type ServerOutput
Examples ¶
Constants ¶
const ( // RistrettoSha512 identifies the Ristretto255 group and SHA-512. RistrettoSha512 = Group(ecc.Ristretto255Sha512) // P256Sha256 identifies the NIST P-256 group and SHA-256. P256Sha256 = Group(ecc.P256Sha256) // P384Sha512 identifies the NIST P-384 group and SHA-384. P384Sha512 = Group(ecc.P384Sha384) // P521Sha512 identifies the NIST P-512 group and SHA-512. P521Sha512 = Group(ecc.P521Sha512) )
Variables ¶
var ( // ErrConfiguration indicates that the configuration is invalid. ErrConfiguration = ErrCodeConfiguration.New("invalid configuration") // ErrRegistration indicates that the registration process failed. ErrRegistration = ErrCodeRegistration.New("registration failed") // ErrAuthentication indicates that the authentication process failed. ErrAuthentication = ErrCodeAuthentication.New("authentication failed") // ErrRegistrationRequest indicates an error with a registration request. ErrRegistrationRequest = ErrCodeMessage.New("invalid registration request") // ErrRegistrationResponse indicates an error with a registration response. ErrRegistrationResponse = ErrCodeMessage.New("invalid registration response") // ErrRegistrationRecord indicates an error with a registration record. ErrRegistrationRecord = ErrCodeMessage.New("invalid registration record") // ErrKE1 indicates an error with a KE1 message. ErrKE1 = ErrCodeMessage.New("invalid KE1 message") // ErrKE2 indicates an error with a KE2 message. ErrKE2 = ErrCodeMessage.New("invalid KE2 message") // ErrKE3 indicates an error with a KE3 message. ErrKE3 = ErrCodeMessage.New("invalid KE3 message") // ErrServerKeyMaterial indicates that the server's key material is invalid. ErrServerKeyMaterial = ErrCodeServerKeyMaterial.New("invalid server key material") // ErrServerOptions indicates that the provided server options are invalid. ErrServerOptions = ErrCodeServerOptions.New("invalid server options") // ErrClientRecord indicates that the client record is invalid. ErrClientRecord = ErrCodeClientRecord.New("invalid client record") // ErrClientState indicates that the client state is invalid. ErrClientState = ErrCodeClientState.New("invalid client state") // ErrClientOptions indicates that the client options are invalid. ErrClientOptions = ErrCodeClientOptions.New("invalid client options") // ErrCriticalAbort - todo: use this if tampering has been detected? ErrCriticalAbort = errors.New("critical - abort protocol") )
Functions ¶
func DeserializeElement ¶ added in v0.18.0
DeserializeElement takes a byte slice and attempts to decode it into an Element of the given group, and returns an error if the decoding fails or if the element is the identity element (point at infinity).
func DeserializeScalar ¶ added in v0.18.0
DeserializeScalar takes a byte slice and attempts to decode it into a Scalar of the given group, and returns an error if the decoding fails or if the scalar is zero.
func IsValidElement ¶ added in v0.18.0
IsValidElement checks if the provided element is valid. It must be: - non-nil - non-identity (point at infinity) - part of the correct group.
func IsValidScalar ¶ added in v0.18.0
IsValidScalar checks if the provided scalar is valid. It must be: - non-nil - non-zero - part of the correct group.
func RandomBytes ¶ added in v0.8.1
RandomBytes returns random bytes of length len (wrapper for crypto/rand).
Types ¶
type AKEOptions ¶ added in v0.18.0
type AKEOptions struct {
Nonce []byte
}
AKEOptions override the secure default values or internally generated values. Only use this if you know what you're doing. Reusing seeds and nonces across sessions is a security risk, and breaks forward secrecy. If the SecretKeyShare is provided, SecretKeyShareSeed is ignored.
type Client ¶
type Client struct {
Deserialize *Deserializer
// contains filtered or unexported fields
}
Client represents an OPAQUE Client, exposing its methods and holding its state. The state includes the OPRF blind, during a registration or authentication session, and the ephemeral secret key share during an authentication session.
func NewClient ¶
func NewClient(c *Configuration) (*Client, error)
NewClient returns a new Client instantiation given the application Configuration.
func (*Client) ClearState ¶ added in v0.18.0
func (c *Client) ClearState()
ClearState attempts to zero out the client's secret material and state, and sets them to nil. It is strongly recommended to call this method after the client is done with the protocol, to avoid leaking sensitive key material.
func (*Client) GenerateKE1 ¶ added in v0.18.0
GenerateKE1 initiates the authentication process, returning a KE1 message, blinding the given password. This method initiates a state, so the same client instance should be used to call GenerateKE3() later on. To complete GenerateKE3() from a different client instance for the same message, ClientOptions must provide ResumePassword, OPRFBlind, ResumeKE1, and AKE.SecretKeyShare or AKE.SecretKeyShareSeed.
func (*Client) GenerateKE3 ¶ added in v0.18.0
func (c *Client) GenerateKE3( ke2 *message.KE2, clientIdentity, serverIdentity []byte, options ...*ClientOptions, ) (ke3 *message.KE3, sessionKey, exportKey []byte, err error)
GenerateKE3 returns a KE3 message given the server's KE2 response message and the identities. If the client or server identity parameters are nil, the client and server's public keys are taken as identities for both.
func (*Client) RegistrationFinalize ¶
func (c *Client) RegistrationFinalize( resp *message.RegistrationResponse, clientIdentity, serverIdentity []byte, options ...*ClientOptions, ) (record *message.RegistrationRecord, exportKey []byte, err error)
RegistrationFinalize returns a RegistrationRecord message given the identities and the server's RegistrationResponse, and the export key, that the client can use for other means. If a different client instance is used than the one that called RegistrationInit, ClientOptions must provide both ResumePassword and OPRFBlind.
func (*Client) RegistrationInit ¶
func (c *Client) RegistrationInit( password []byte, options ...*ClientOptions, ) (*message.RegistrationRequest, error)
RegistrationInit returns a RegistrationRequest message blinding the given password. This will initiate a state, so the same client instance should be used to call RegistrationFinalize() later on. Optionally, that value can be overridden by providing a ClientOptions with an OPRF Blind value, at your own risks.
type ClientOptions ¶ added in v0.18.0
type ClientOptions struct {
OPRFBlind *ecc.Scalar
// ResumePassword is only used in RegistrationFinalize and GenerateKE3, to complete a run from a different
// client instance that does not hold the prior state.
ResumePassword []byte
AKE *AKEOptions
// ResumeKE1 is only used in GenerateKE3, to complete a run from a different client instance that does not hold
// the prior state.
ResumeKE1 []byte
KSFSalt []byte
// RegistrationEnvelopeNonce is only used in RegistrationFinalize.
RegistrationEnvelopeNonce []byte
KDFSalt []byte
KSFParameters []uint64
// RegistrationEnvelopeNonceLength is only used in RegistrationFinalize.
RegistrationEnvelopeNonceLength int
KSFLength int // If 0 or not set, will default to the OPRF length.
}
ClientOptions override the secure default values or internally generated values. Only use this if you know what you're doing. Reusing seeds and nonces across sessions is a security risk, and breaks forward secrecy.
type ClientRecord ¶
type ClientRecord struct {
*message.RegistrationRecord
CredentialIdentifier []byte
ClientIdentity []byte
}
ClientRecord is a server-side structure enabling the storage of user relevant information.
type Configuration ¶
type Configuration struct {
Context []byte
KDF crypto.Hash `json:"kdf"`
MAC crypto.Hash `json:"mac"`
Hash crypto.Hash `json:"hash"`
KSF ksf.Identifier `json:"ksf"`
OPRF Group `json:"oprf"`
AKE Group `json:"group"`
}
Configuration represents an OPAQUE configuration. OPRF and AKE can use different groups. Matching KDF, MAC, and Hash selections remain recommended but are not required.
func DefaultConfiguration ¶
func DefaultConfiguration() *Configuration
DefaultConfiguration returns a default configuration with strong parameters.
func DeserializeConfiguration ¶
func DeserializeConfiguration(encoded []byte) (*Configuration, error)
DeserializeConfiguration decodes the input and returns a Parameter structure.
func (*Configuration) Client ¶
func (c *Configuration) Client() (*Client, error)
Client returns a newly instantiated Client from the Configuration.
func (*Configuration) DecodeServerKeyMaterial ¶ added in v0.18.0
func (c *Configuration) DecodeServerKeyMaterial(data []byte) (*ServerKeyMaterial, error)
DecodeServerKeyMaterial decodes the server key material from a byte slice.
func (*Configuration) DecodeServerKeyMaterialHex ¶ added in v0.18.0
func (c *Configuration) DecodeServerKeyMaterialHex(data string) (*ServerKeyMaterial, error)
DecodeServerKeyMaterialHex decodes the server key material from a hex string.
func (*Configuration) Deserializer ¶ added in v0.8.2
func (c *Configuration) Deserializer() (*Deserializer, error)
Deserializer returns a pointer to a Deserializer structure allowing deserialization of messages in the given configuration.
func (*Configuration) Equals ¶ added in v0.18.0
func (c *Configuration) Equals(other *Configuration) error
Equals compares two configurations and returns nil if they are equal, or an error otherwise.
func (*Configuration) GenerateOPRFSeed ¶ added in v0.8.1
func (c *Configuration) GenerateOPRFSeed() []byte
GenerateOPRFSeed returns a OPRF seed valid in the given configuration.
func (*Configuration) GetFakeRecord ¶ added in v0.8.2
func (c *Configuration) GetFakeRecord(credentialIdentifier []byte) (*ClientRecord, error)
GetFakeRecord creates a fake Client record to be used when no existing client record exists, to defend against client enumeration techniques.
func (*Configuration) KeyGen ¶ added in v0.8.1
func (c *Configuration) KeyGen() (secretKey *ecc.Scalar, publicKey *ecc.Element)
KeyGen returns a key pair in the AKE ecc.
func (*Configuration) Serialize ¶
func (c *Configuration) Serialize() []byte
Serialize returns the byte encoding of the Configuration structure.
func (*Configuration) Server ¶
func (c *Configuration) Server() (*Server, error)
Server returns a newly instantiated Server from the Configuration.
type Deserializer ¶ added in v0.8.1
type Deserializer struct {
// contains filtered or unexported fields
}
Deserializer exposes the message deserialization methods.
func (*Deserializer) DecodePrivateKey ¶ added in v0.18.0
func (d *Deserializer) DecodePrivateKey(encoded []byte) (*ecc.Scalar, error)
DecodePrivateKey takes a serialized private key (a scalar) and attempts to return it's decoded form.
func (*Deserializer) DecodePublicKey ¶ added in v0.18.0
func (d *Deserializer) DecodePublicKey(encoded []byte) (*ecc.Element, error)
DecodePublicKey takes a serialized public key (a point) and attempts to return it's decoded form.
func (*Deserializer) KE1 ¶ added in v0.8.1
func (d *Deserializer) KE1(ke1 []byte) (*message.KE1, error)
KE1 takes a serialized KE1 message and returns a deserialized KE1 structure.
func (*Deserializer) KE2 ¶ added in v0.8.1
func (d *Deserializer) KE2(ke2 []byte) (*message.KE2, error)
KE2 takes a serialized KE2 message and returns a deserialized KE2 structure.
func (*Deserializer) KE3 ¶ added in v0.8.1
func (d *Deserializer) KE3(ke3 []byte) (*message.KE3, error)
KE3 takes a serialized KE3 message and returns a deserialized KE3 structure.
func (*Deserializer) RegistrationRecord ¶ added in v0.8.1
func (d *Deserializer) RegistrationRecord(record []byte) (*message.RegistrationRecord, error)
RegistrationRecord takes a serialized RegistrationRecord message and returns a deserialized RegistrationRecord structure.
func (*Deserializer) RegistrationRequest ¶ added in v0.8.1
func (d *Deserializer) RegistrationRequest(registrationRequest []byte) (*message.RegistrationRequest, error)
RegistrationRequest takes a serialized RegistrationRequest message and returns a deserialized RegistrationRequest structure.
func (*Deserializer) RegistrationResponse ¶ added in v0.8.1
func (d *Deserializer) RegistrationResponse(registrationResponse []byte) (*message.RegistrationResponse, error)
RegistrationResponse takes a serialized RegistrationResponse message and returns a deserialized RegistrationResponse structure.
type Error ¶ added in v0.18.0
Error represents an error in the OPAQUE protocol.
func (*Error) As ¶ added in v0.18.0
As implements the errors.As method for the Error type. It allows type assertion to specific error types.
func (*Error) Error ¶ added in v0.18.0
Error implements the error interface for the Error type. By convention, we return only the concise form of the current error, without the cause. The cause can be retrieved with the Unwrap() method.
func (*Error) Format ¶ added in v0.18.0
Format implements the fmt.Formatter interface for the Error type. It allows formatting the error in different ways.
func (*Error) Is ¶ added in v0.18.0
Is implements the errors.Is method for the Error type. It allows checking if the error is of a specific ErrorCode.
type ErrorCode ¶ added in v0.18.0
type ErrorCode byte //nolint:errname // This is an error code, not an error type.
ErrorCode represents the type of error in the OPAQUE protocol. It is used to categorize errors and provide a consistent way to handle error conditions.
const ( // ErrCodeUnknown represents an unknown error. ErrCodeUnknown ErrorCode = iota // ErrCodeConfiguration represents an error related to the configuration. ErrCodeConfiguration // ErrCodeRegistration represents an error related to the registration phase. ErrCodeRegistration // ErrCodeAuthentication represents an error related to the authentication phase. ErrCodeAuthentication // ErrCodeMessage represents an error related to message processing. ErrCodeMessage // ErrCodeServerKeyMaterial represents an error related to the server's key material. ErrCodeServerKeyMaterial // ErrCodeServerOptions represents an error related to the server's optional arguments. ErrCodeServerOptions // ErrCodeClientRecord represents an error related to the clients record. ErrCodeClientRecord // ErrCodeClientState represents an error related to the client's state. ErrCodeClientState // ErrCodeClientOptions represents an error related to the client's optional arguments. ErrCodeClientOptions )
func (ErrorCode) As ¶ added in v0.18.0
As implements the errors.As method for the Error type. It allows type assertion to specific error types.
func (ErrorCode) Error ¶ added in v0.18.0
Error implements the error interface for the ErrorCode type. It returns a string representation of the error code.
func (ErrorCode) Is ¶ added in v0.18.0
Is implements the errors.Is method for the ErrorCode type. It allows checking if the error is of a specific ErrorCode.
type Group ¶
type Group byte
Group identifies a supported group configuration for OPRF or AKE.
func (Group) Available ¶ added in v0.9.0
Available returns whether the Group byte is recognized in this implementation. This allows to fail early when working with multiple versions not using the same configuration and ecc.
func (Group) OPRF ¶ added in v0.9.1
func (g Group) OPRF() oprf.Identifier
OPRF returns the OPRF Identifier used in the Ciphersuite.
type Server ¶
type Server struct {
ServerKeyMaterial *ServerKeyMaterial
Deserialize *Deserializer
// contains filtered or unexported fields
}
Server represents an OPAQUE Server, exposing its functions and holding its key material. The server is thread-safe and can be used concurrently by multiple goroutines to serve clients, given the same key material. The server's key material must be set before using the registration and login functions.
func NewServer ¶
func NewServer(c *Configuration) (*Server, error)
NewServer returns a Server instantiation given the application Configuration.
func (*Server) GenerateKE2 ¶ added in v0.18.0
func (s *Server) GenerateKE2( ke1 *message.KE1, record *ClientRecord, options ...*ServerOptions, ) (*message.KE2, *ServerOutput, error)
GenerateKE2 responds to a KE1 message with a KE2 message a client record. The ServerKeyMaterial must be set before calling this method using SetKeyMaterial. This method can be used concurrently by multiple goroutines for different clients.
func (*Server) LoginFinish ¶ added in v0.8.0
LoginFinish verifies whether the KE3 message holds the client MAC that matches expectedClientMac. If this method returns an error, the session secret must not be used. If it returns nil, the session secret can be used.
func (*Server) RegistrationResponse ¶
func (s *Server) RegistrationResponse( req *message.RegistrationRequest, clientCredentialIdentifier []byte, clientOPRFKey *ecc.Scalar, ) (*message.RegistrationResponse, error)
RegistrationResponse computes the server’s response to a client’s RegistrationRequest.
It client-specific OPRF key on the client input and returns a RegistrationResponse message in response to the client's RegistrationRequest message req.
Parameters:
- clientCredentialIdentifier: application-defined, stable identifier for this client. If clientOPRFKey is nil, the OPRF key is derived from this identifier and the server’s global OPRF seed provided in the server key material. The identifier MUST be unique per client and stable for the credential’s lifetime from registration to all subsequent logins. It MUST NOT be empty if clientOPRFKey is nil.
- clientOPRFKey: optional explicit client OPRF secret key. If non-nil, it is used directly instead of deriving it from the credentialIdentifier and globalOPRFSeed.
Preconditions:
- s.SetKeyMaterial has been called. The server’s AKE public key (ServerKeyMaterial.PublicKeyBytes) is required to populate the response.
- If clientOPRFKey is nil, the global OPRF seed (ServerKeyMaterial.OPRFGlobalSeed) MUST be set.
Security and usage notes:
- Using a single global OPRF seed together with unique clientCredentialIdentifier values prevents client enumeration by deriving per-client OPRF keys.
- The clientCredentialIdentifier MUST be consistent across registration and subsequent logins for a given client.
- When the OPRF key is derived internally, it is zeroed after use.
func (*Server) SetKeyMaterial ¶ added in v0.10.0
func (s *Server) SetKeyMaterial(skm *ServerKeyMaterial) error
SetKeyMaterial sets the server's key material. The ServerKeyMaterial must be set before calling RegistrationResponse or GenerateKE2. It validates the key material and returns an error if it is invalid.
type ServerKeyMaterial ¶ added in v0.18.0
type ServerKeyMaterial struct {
// The server's long-term secret key. Required only for Login, and not used in Registration.
PrivateKey *ecc.Scalar
// The server's public key in bytes. Must be provided during registration and login.
PublicKeyBytes []byte
// The seed to derive the OPRF key for the clients with. Required if client OPRF keys won't be provided directly.
OPRFGlobalSeed []byte
// The server's identity. If empty, the server's public key will be used as the identity.
Identity []byte
}
ServerKeyMaterial holds the server's long-term identity and key material for OPAQUE registration and authentication sessions. Note that, depending on the setup, these values are not client specific and can be reused across clients.
func (*ServerKeyMaterial) Encode ¶ added in v0.18.0
func (s *ServerKeyMaterial) Encode() []byte
Encode encodes the server key material into a byte slice.
func (*ServerKeyMaterial) Flush ¶ added in v0.18.0
func (s *ServerKeyMaterial) Flush()
Flush does a best-effort attempt to clear the server key material from memory. It is not guaranteed that the contents are correctly wiped from memory.
func (*ServerKeyMaterial) Hex ¶ added in v0.18.0
func (s *ServerKeyMaterial) Hex() string
Hex encodes the server key material into a hex string.
type ServerOptions ¶ added in v0.18.0
type ServerOptions struct {
ClientOPRFKey *ecc.Scalar
AKE *AKEOptions
MaskingNonce []byte
}
ServerOptions override the secure default values or internally generated values. Only use this if you know what you're doing. Reusing seeds and nonces across sessions is a security risk, and breaks forward secrecy.
type ServerOutput ¶ added in v0.18.0
ServerOutput is the result of a successful GenerateKE2 call, containing the client MAC and session secret. The SessionSecret must not be used before the client's KE3 message has been verified against the ClientMAC.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package internal provides values, structures, and functions to operate OPAQUE that are not part of the public API.
|
Package internal provides values, structures, and functions to operate OPAQUE that are not part of the public API. |
|
ake
Package ake provides high-level functions for the 3DH AKE.
|
Package ake provides high-level functions for the 3DH AKE. |
|
encoding
Package encoding provides encoding utilities.
|
Package encoding provides encoding utilities. |
|
envelope
Package envelope provides utility functions for the envelope credential management..
|
Package envelope provides utility functions for the envelope credential management.. |
|
ksf
Package ksf provides the Key Stretching Functions.
|
Package ksf provides the Key Stretching Functions. |
|
masking
Package masking provides the credential masking mechanism.
|
Package masking provides the credential masking mechanism. |
|
oprf
Package oprf implements the Elliptic Curve Oblivious Pseudorandom Function (EC-OPRF) from https://tools.ietf.org/html/draft-irtf-cfrg-voprf.
|
Package oprf implements the Elliptic Curve Oblivious Pseudorandom Function (EC-OPRF) from https://tools.ietf.org/html/draft-irtf-cfrg-voprf. |
|
tag
Package tag provides the static tag strings to OPAQUE.
|
Package tag provides the static tag strings to OPAQUE. |
|
Package message provides message structures for the OPAQUE protocol.
|
Package message provides message structures for the OPAQUE protocol. |