-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add hybrid post-quantum key agreement.
Adds Kyber512X25519 and Kyber768X25519 hybrid post-quantum key agreements with temporary group identifiers. Not enabled by default. Adds CFEvents to detect `HelloRetryRequest`s and to signal which key agreement was used. Co-authored-by: Bas Westerbaan <[email protected]>
- Loading branch information
1 parent
ee266dc
commit 6089728
Showing
10 changed files
with
377 additions
and
55 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
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
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
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,102 @@ | ||
// 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{ | ||
// hybrid.Kyber512X25519().(tls.TLSScheme).TLSCurveID(), | ||
// tls.X25519, | ||
// tls.P256, | ||
// } | ||
|
||
package tls | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
|
||
"circl/kem" | ||
"circl/kem/hybrid" | ||
) | ||
|
||
// Either ecdheParameters or kem.PrivateKey | ||
type clientKeySharePrivate interface{} | ||
|
||
var ( | ||
kyber512X25519CurveID = CurveID(0xfe30) | ||
kyber768X25519CurveID = CurveID(0xfe31) | ||
invalidCurveID = CurveID(0) | ||
) | ||
|
||
func kemSchemeKeyToCurveID(s kem.Scheme) CurveID { | ||
switch s.Name() { | ||
case "Kyber512-X25519": | ||
return kyber512X25519CurveID | ||
case "Kyber768-X25519": | ||
return kyber768X25519CurveID | ||
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 kyber512X25519CurveID: | ||
return hybrid.Kyber512X25519() | ||
case kyber768X25519CurveID: | ||
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 | ||
} |
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,124 @@ | ||
// 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. | ||
|
||
package tls | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"circl/kem" | ||
"circl/kem/hybrid" | ||
) | ||
|
||
func testHybridKEX(t *testing.T, scheme kem.Scheme, clientPQ, serverPQ, | ||
clientTLS12, serverTLS12 bool) { | ||
var clientSelectedKEX *CurveID | ||
var retry bool | ||
|
||
rsaCert := Certificate{ | ||
Certificate: [][]byte{testRSACertificate}, | ||
PrivateKey: testRSAPrivateKey, | ||
} | ||
serverCerts := []Certificate{rsaCert} | ||
|
||
clientConfig := testConfig.Clone() | ||
if clientPQ { | ||
clientConfig.CurvePreferences = []CurveID{ | ||
kemSchemeKeyToCurveID(scheme), | ||
X25519, | ||
} | ||
} | ||
clientConfig.CFEventHandler = func(ev CFEvent) { | ||
switch e := ev.(type) { | ||
case CFEventTLS13NegotiatedKEX: | ||
clientSelectedKEX = &e.KEX | ||
case CFEventTLS13HRR: | ||
retry = true | ||
} | ||
} | ||
if clientTLS12 { | ||
clientConfig.MaxVersion = VersionTLS12 | ||
} | ||
|
||
serverConfig := testConfig.Clone() | ||
if serverPQ { | ||
serverConfig.CurvePreferences = []CurveID{ | ||
kemSchemeKeyToCurveID(scheme), | ||
X25519, | ||
} | ||
} | ||
if serverTLS12 { | ||
serverConfig.MaxVersion = VersionTLS12 | ||
} | ||
serverConfig.Certificates = serverCerts | ||
|
||
c, s := localPipe(t) | ||
done := make(chan error) | ||
defer c.Close() | ||
|
||
go func() { | ||
defer s.Close() | ||
done <- Server(s, serverConfig).Handshake() | ||
}() | ||
|
||
cli := Client(c, clientConfig) | ||
clientErr := cli.Handshake() | ||
serverErr := <-done | ||
if clientErr != nil { | ||
t.Errorf("client error: %s", clientErr) | ||
} | ||
if serverErr != nil { | ||
t.Errorf("server error: %s", serverErr) | ||
} | ||
|
||
var expectedKEX CurveID | ||
var expectedRetry bool | ||
|
||
if clientPQ && serverPQ { | ||
expectedKEX = kemSchemeKeyToCurveID(scheme) | ||
} else { | ||
expectedKEX = X25519 | ||
} | ||
if clientPQ && !serverPQ { | ||
expectedRetry = true | ||
} | ||
|
||
if !serverTLS12 && !clientTLS12 { | ||
if clientSelectedKEX == nil { | ||
t.Error("No TLS 1.3 KEX happened?") | ||
} | ||
|
||
if *clientSelectedKEX != expectedKEX { | ||
t.Errorf("failed to negotiate: expected %d, got %d", | ||
expectedKEX, *clientSelectedKEX) | ||
} | ||
if expectedRetry != retry { | ||
t.Errorf("Expected retry=%v, got retry=%v", expectedRetry, retry) | ||
} | ||
} else { | ||
if clientSelectedKEX != nil { | ||
t.Error("TLS 1.3 KEX happened?") | ||
} | ||
} | ||
} | ||
|
||
func TestHybridKEX(t *testing.T) { | ||
run := func(scheme kem.Scheme, clientPQ, serverPQ, clientTLS12, serverTLS12 bool) { | ||
t.Run(fmt.Sprintf("%s serverPQ:%v clientPQ:%v serverTLS12:%v clientTLS12:%v", scheme.Name(), | ||
serverPQ, clientPQ, serverTLS12, clientTLS12), func(t *testing.T) { | ||
testHybridKEX(t, scheme, clientPQ, serverPQ, clientTLS12, serverTLS12) | ||
}) | ||
} | ||
for _, scheme := range []kem.Scheme{ | ||
hybrid.Kyber512X25519(), | ||
hybrid.Kyber768X25519(), | ||
} { | ||
run(scheme, true, true, false, false) | ||
run(scheme, true, false, false, false) | ||
run(scheme, false, true, false, false) | ||
run(scheme, true, true, true, false) | ||
run(scheme, true, true, false, true) | ||
run(scheme, true, true, true, true) | ||
} | ||
} |
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
Oops, something went wrong.