171 lines
4.8 KiB
Go
171 lines
4.8 KiB
Go
|
// Copyright 2022 Cloudflare, Inc. All rights reserved. Use of this source code
|
||
|
// is governed by a BSD-style license that can be found in the LICENSE file.
|
||
|
//
|
||
|
// Glue to add Circl's (post-quantum) hybrid KEMs.
|
||
|
//
|
||
|
// To enable set CurvePreferences with the desired scheme as the first element:
|
||
|
//
|
||
|
// import (
|
||
|
// "github.com/cloudflare/circl/kem/tls"
|
||
|
// "github.com/cloudflare/circl/kem/hybrid"
|
||
|
//
|
||
|
// [...]
|
||
|
//
|
||
|
// config.CurvePreferences = []tls.CurveID{
|
||
|
// qtls.X25519Kyber512Draft00,
|
||
|
// qtls.X25519,
|
||
|
// qtls.P256,
|
||
|
// }
|
||
|
|
||
|
package qtls
|
||
|
|
||
|
import (
|
||
|
"github.com/cloudflare/circl/kem"
|
||
|
"github.com/cloudflare/circl/kem/hybrid"
|
||
|
|
||
|
"crypto/tls"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// Either ecdheParameters or kem.PrivateKey
|
||
|
type clientKeySharePrivate interface{}
|
||
|
|
||
|
var (
|
||
|
X25519Kyber512Draft00 = CurveID(0xfe30)
|
||
|
X25519Kyber768Draft00 = CurveID(0xfe31)
|
||
|
invalidCurveID = CurveID(0)
|
||
|
)
|
||
|
|
||
|
func kemSchemeKeyToCurveID(s kem.Scheme) CurveID {
|
||
|
switch s.Name() {
|
||
|
case "Kyber512-X25519":
|
||
|
return X25519Kyber512Draft00
|
||
|
case "Kyber768-X25519":
|
||
|
return X25519Kyber768Draft00
|
||
|
default:
|
||
|
return invalidCurveID
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Extract CurveID from clientKeySharePrivate
|
||
|
func clientKeySharePrivateCurveID(ks clientKeySharePrivate) CurveID {
|
||
|
switch v := ks.(type) {
|
||
|
case kem.PrivateKey:
|
||
|
ret := kemSchemeKeyToCurveID(v.Scheme())
|
||
|
if ret == invalidCurveID {
|
||
|
panic("cfkem: internal error: don't know CurveID for this KEM")
|
||
|
}
|
||
|
return ret
|
||
|
case ecdheParameters:
|
||
|
return v.CurveID()
|
||
|
default:
|
||
|
panic("cfkem: internal error: unknown clientKeySharePrivate")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Returns scheme by CurveID if supported by Circl
|
||
|
func curveIdToCirclScheme(id CurveID) kem.Scheme {
|
||
|
switch id {
|
||
|
case X25519Kyber512Draft00:
|
||
|
return hybrid.Kyber512X25519()
|
||
|
case X25519Kyber768Draft00:
|
||
|
return hybrid.Kyber768X25519()
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Generate a new shared secret and encapsulates it for the packed
|
||
|
// public key in ppk using randomness from rnd.
|
||
|
func encapsulateForKem(scheme kem.Scheme, rnd io.Reader, ppk []byte) (
|
||
|
ct, ss []byte, alert alert, err error) {
|
||
|
pk, err := scheme.UnmarshalBinaryPublicKey(ppk)
|
||
|
if err != nil {
|
||
|
return nil, nil, alertIllegalParameter, fmt.Errorf("unpack pk: %w", err)
|
||
|
}
|
||
|
seed := make([]byte, scheme.EncapsulationSeedSize())
|
||
|
if _, err := io.ReadFull(rnd, seed); err != nil {
|
||
|
return nil, nil, alertInternalError, fmt.Errorf("random: %w", err)
|
||
|
}
|
||
|
ct, ss, err = scheme.EncapsulateDeterministically(pk, seed)
|
||
|
return ct, ss, alertIllegalParameter, err
|
||
|
}
|
||
|
|
||
|
// Generate a new keypair using randomness from rnd.
|
||
|
func generateKemKeyPair(scheme kem.Scheme, rnd io.Reader) (
|
||
|
kem.PublicKey, kem.PrivateKey, error) {
|
||
|
seed := make([]byte, scheme.SeedSize())
|
||
|
if _, err := io.ReadFull(rnd, seed); err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
pk, sk := scheme.DeriveKeyPair(seed)
|
||
|
return pk, sk, nil
|
||
|
}
|
||
|
|
||
|
// Events. We cannot use the same approach as used in our plain Go fork
|
||
|
// as we cannot change tls.Config, tls.ConnectionState, etc. Also we do
|
||
|
// not want to maintain a fork of quic-go itself as well. This seems
|
||
|
// the simplest option.
|
||
|
|
||
|
// CFEvent. There are two events: one emitted on HRR and one emitted
|
||
|
type CFEvent interface {
|
||
|
// Common to all events
|
||
|
ServerSide() bool // true if server-side; false if on client-side
|
||
|
|
||
|
// HRR event. Emitted when an HRR happened.
|
||
|
IsHRR() bool // true if this is an HRR event
|
||
|
|
||
|
// Handshake event.
|
||
|
IsHandshake() bool // true if this is a handshake event.
|
||
|
Duration() time.Duration // how long did the handshake take?
|
||
|
KEX() tls.CurveID // which kex was established?
|
||
|
}
|
||
|
|
||
|
type CFEventHandler func(CFEvent)
|
||
|
|
||
|
// Registers a handler to be called when a CFEvent is emitted; returns
|
||
|
// the previous handler.
|
||
|
func SetCFEventHandler(handler CFEventHandler) CFEventHandler {
|
||
|
cfEventMux.Lock()
|
||
|
ret := cfEventHandler
|
||
|
cfEventHandler = handler
|
||
|
cfEventMux.Unlock()
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func raiseCFEvent(ev CFEvent) {
|
||
|
cfEventMux.Lock()
|
||
|
handler := cfEventHandler
|
||
|
cfEventMux.Unlock()
|
||
|
if handler != nil {
|
||
|
handler(ev)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
cfEventMux sync.Mutex
|
||
|
cfEventHandler CFEventHandler
|
||
|
)
|
||
|
|
||
|
type cfEventHRR struct{ serverSide bool }
|
||
|
|
||
|
func (*cfEventHRR) IsHRR() bool { return true }
|
||
|
func (ev *cfEventHRR) ServerSide() bool { return ev.serverSide }
|
||
|
func (*cfEventHRR) IsHandshake() bool { return false }
|
||
|
func (ev *cfEventHRR) Duration() time.Duration { panic("wrong event") }
|
||
|
func (ev *cfEventHRR) KEX() tls.CurveID { panic("wrong event") }
|
||
|
|
||
|
type cfEventHandshake struct {
|
||
|
serverSide bool
|
||
|
duration time.Duration
|
||
|
kex tls.CurveID
|
||
|
}
|
||
|
|
||
|
func (*cfEventHandshake) IsHRR() bool { return false }
|
||
|
func (ev *cfEventHandshake) ServerSide() bool { return ev.serverSide }
|
||
|
func (*cfEventHandshake) IsHandshake() bool { return true }
|
||
|
func (ev *cfEventHandshake) Duration() time.Duration { return ev.duration }
|
||
|
func (ev *cfEventHandshake) KEX() tls.CurveID { return ev.kex }
|