sidh: adds PQ secure KEX

* SIDH/P503-X25519
* adds interop tests
This commit is contained in:
Henry Case 2018-10-05 22:43:00 +01:00 committad av Kris Kwiatkowski
förälder 7c79cbefc5
incheckning d184bc0099
8 ändrade filer med 270 tillägg och 68 borttagningar

253
13.go
Visa fil

@ -21,6 +21,7 @@ import (
"sync/atomic"
"time"
sidh "github_com/cloudflare/sidh/sidh"
"golang_org/x/crypto/curve25519"
)
@ -29,11 +30,16 @@ import (
const numSessionTickets = 2
type secretLabel int
type role uint8
const (
kRole_Server = iota
kRole_Client
x25519SharedSecretSz = 32
P503PubKeySz = 378
P503PrvKeySz = 32
P503SharedSecretSz = 126
SidhP503Curve25519PubKeySz = x25519SharedSecretSz + P503PubKeySz
SidhP503Curve25519PrvKeySz = x25519SharedSecretSz + P503PrvKeySz
SidhP503Curve25519SharedKeySz = x25519SharedSecretSz + P503SharedSecretSz
)
const (
@ -55,6 +61,38 @@ type keySchedule13 struct {
config *Config // Used for KeyLogWriter callback, nil if keylogging is disabled.
}
// Interface implemented by DH key exchange strategies
type dhKex interface {
// c - context of current TLS handshake, groupId - ID of an algorithm
// (curve/field) being chosen for key agreement. Methods implmenting an
// interface always assume that provided groupId is correct.
//
// In case of success, function returns secret key and ephemeral key. Otherwise
// error is set.
generate(c *Conn, groupId CurveID) ([]byte, keyShare, error)
// c - context of current TLS handshake, ks - public key received
// from the other side of the connection, secretKey - is a private key
// used for DH key agreement. Function returns shared secret in case
// of success or empty slice otherwise.
derive(c *Conn, ks keyShare, secretKey []byte) []byte
}
// Key Exchange strategies per curve type
type kexNist struct{} // Used by NIST curves; P-256, P-384, P-512
type kexX25519 struct{} // Used by X25519
type kexSidhP503 struct{} // Used by SIDH/P503
type kexHybridSidhP503X25519 struct{} // Used by SIDH-ECDH hybrid scheme
// Routing map for key exchange strategies
var dhKexStrat = map[CurveID]dhKex{
CurveP256: &kexNist{},
CurveP384: &kexNist{},
CurveP521: &kexNist{},
X25519: &kexX25519{},
sidhP503: &kexSidhP503{},
HybridSidhP503Curve25519: &kexHybridSidhP503X25519{},
}
func newKeySchedule13(suite *cipherSuite, config *Config, clientRandom []byte) *keySchedule13 {
if config.KeyLogWriter == nil {
clientRandom = nil
@ -80,6 +118,15 @@ func (ks *keySchedule13) setSecret(secret []byte) {
ks.secret = hkdfExtract(hash, secret, salt)
}
// Depending on role returns pair of key variant to be used by
// local and remote process.
func getSidhKeyVariant(isClient bool) (sidh.KeyVariant, sidh.KeyVariant) {
if isClient {
return sidh.KeyVariant_SIDH_A, sidh.KeyVariant_SIDH_B
}
return sidh.KeyVariant_SIDH_B, sidh.KeyVariant_SIDH_A
}
// write appends the data to the transcript hash context.
func (ks *keySchedule13) write(data []byte) {
ks.handshakeCtx = nil
@ -186,7 +233,7 @@ CurvePreferenceLoop:
earlyClientCipher, _ := hs.keySchedule.prepareCipher(secretEarlyClient)
ecdheSecret := c.deriveECDHESecret(ks, privateKey)
ecdheSecret := c.deriveDHESecret(ks, privateKey)
if ecdheSecret == nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: bad ECDHE client share")
@ -551,61 +598,22 @@ func prepareDigitallySigned(hash crypto.Hash, context string, data []byte) []byt
return h.Sum(nil)
}
// generateKeyShare generates keypair. Private key is returned as first argument, public key
// is returned in keyShare.data. keyshare.curveID stores ID of the scheme used.
func (c *Conn) generateKeyShare(curveID CurveID) ([]byte, keyShare, error) {
if curveID == X25519 {
var scalar, public [32]byte
if _, err := io.ReadFull(c.config.rand(), scalar[:]); err != nil {
return nil, keyShare{}, err
}
curve25519.ScalarBaseMult(&public, &scalar)
return scalar[:], keyShare{group: curveID, data: public[:]}, nil
if val, ok := dhKexStrat[curveID]; ok {
return val.generate(c, curveID)
}
curve, ok := curveForCurveID(curveID)
if !ok {
return nil, keyShare{}, errors.New("tls: preferredCurves includes unsupported curve")
}
privateKey, x, y, err := elliptic.GenerateKey(curve, c.config.rand())
if err != nil {
return nil, keyShare{}, err
}
ecdhePublic := elliptic.Marshal(curve, x, y)
return privateKey, keyShare{group: curveID, data: ecdhePublic}, nil
return nil, keyShare{}, errors.New("tls: preferredCurves includes unsupported curve")
}
func (c *Conn) deriveECDHESecret(ks keyShare, secretKey []byte) []byte {
if ks.group == X25519 {
if len(ks.data) != 32 {
return nil
}
var theirPublic, sharedKey, scalar [32]byte
copy(theirPublic[:], ks.data)
copy(scalar[:], secretKey)
curve25519.ScalarMult(&sharedKey, &scalar, &theirPublic)
return sharedKey[:]
// DH key agreement. ks stores public key, secretKey stores private key used for ephemeral
// key agreement. Function returns shared secret in case of success or empty slice otherwise.
func (c *Conn) deriveDHESecret(ks keyShare, secretKey []byte) []byte {
if val, ok := dhKexStrat[ks.group]; ok {
return val.derive(c, ks, secretKey)
}
curve, ok := curveForCurveID(ks.group)
if !ok {
return nil
}
x, y := elliptic.Unmarshal(curve, ks.data)
if x == nil {
return nil
}
x, _ = curve.ScalarMult(x, y, secretKey)
xBytes := x.Bytes()
curveSize := (curve.Params().BitSize + 8 - 1) >> 3
if len(xBytes) == curveSize {
return xBytes
}
buf := make([]byte, curveSize)
copy(buf[len(buf)-len(xBytes):], xBytes)
return buf
return nil
}
func hkdfExpandLabel(hash crypto.Hash, secret, hashValue []byte, label string, L int) []byte {
@ -981,7 +989,7 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
// 0-RTT is not supported yet, so use an empty PSK.
hs.keySchedule.setSecret(nil)
ecdheSecret := c.deriveECDHESecret(serverHello.keyShare, hs.privateKey)
ecdheSecret := c.deriveDHESecret(serverHello.keyShare, hs.privateKey)
if ecdheSecret == nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: bad ECDHE server share")
@ -1161,3 +1169,138 @@ func supportedSigAlgorithmsCert(schemes []SignatureScheme) (ret []SignatureSchem
}
return
}
// Functions below implement dhKex interface for different DH shared secret agreements
// KEX: P-256, P-384, P-512 KEX
func (kexNist) generate(c *Conn, groupId CurveID) (private []byte, ks keyShare, err error) {
// never fails
curve, _ := curveForCurveID(groupId)
private, x, y, err := elliptic.GenerateKey(curve, c.config.rand())
if err != nil {
return nil, keyShare{}, err
}
ks.group = groupId
ks.data = elliptic.Marshal(curve, x, y)
return
}
func (kexNist) derive(c *Conn, ks keyShare, secretKey []byte) []byte {
// never fails
curve, _ := curveForCurveID(ks.group)
x, y := elliptic.Unmarshal(curve, ks.data)
if x == nil {
return nil
}
x, _ = curve.ScalarMult(x, y, secretKey)
xBytes := x.Bytes()
curveSize := (curve.Params().BitSize + 8 - 1) >> 3
if len(xBytes) == curveSize {
return xBytes
}
buf := make([]byte, curveSize)
copy(buf[len(buf)-len(xBytes):], xBytes)
return buf
}
// KEX: X25519
func (kexX25519) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) {
var scalar, public [x25519SharedSecretSz]byte
if _, err := io.ReadFull(c.config.rand(), scalar[:]); err != nil {
return nil, keyShare{}, err
}
curve25519.ScalarBaseMult(&public, &scalar)
return scalar[:], keyShare{group: X25519, data: public[:]}, nil
}
func (kexX25519) derive(c *Conn, ks keyShare, secretKey []byte) []byte {
var theirPublic, sharedKey, scalar [x25519SharedSecretSz]byte
if len(ks.data) != x25519SharedSecretSz {
return nil
}
copy(theirPublic[:], ks.data)
copy(scalar[:], secretKey)
curve25519.ScalarMult(&sharedKey, &scalar, &theirPublic)
return sharedKey[:]
}
// KEX: SIDH/503
func (kexSidhP503) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) {
var variant, _ = getSidhKeyVariant(c.isClient)
var prvKey = sidh.NewPrivateKey(sidh.FP_503, variant)
if prvKey.Generate(c.config.rand()) != nil {
return nil, keyShare{}, errors.New("tls: private SIDH key generation failed")
}
pubKey := prvKey.GeneratePublicKey()
return prvKey.Export(), keyShare{group: sidhP503, data: pubKey.Export()}, nil
}
func (kexSidhP503) derive(c *Conn, ks keyShare, key []byte) []byte {
var prvVariant, pubVariant = getSidhKeyVariant(c.isClient)
var prvKeySize = P503PrvKeySz
if len(ks.data) != P503PubKeySz || len(key) != prvKeySize {
return nil
}
prvKey := sidh.NewPrivateKey(sidh.FP_503, prvVariant)
pubKey := sidh.NewPublicKey(sidh.FP_503, pubVariant)
if err := prvKey.Import(key); err != nil {
return nil
}
if err := pubKey.Import(ks.data); err != nil {
return nil
}
// Never fails
sharedKey, _ := sidh.DeriveSecret(prvKey, pubKey)
return sharedKey
}
// KEX Hybrid SIDH/503-X25519
func (kexHybridSidhP503X25519) generate(c *Conn, groupId CurveID) (private []byte, ks keyShare, err error) {
var pubHybrid [SidhP503Curve25519PubKeySz]byte
var prvHybrid [SidhP503Curve25519PrvKeySz]byte
// Generate ephemeral key for classic x25519
private, ks, err = dhKexStrat[X25519].generate(c, groupId)
if err != nil {
return
}
copy(prvHybrid[:], private)
copy(pubHybrid[:], ks.data)
// Generate PQ ephemeral key for SIDH
private, ks, err = dhKexStrat[sidhP503].generate(c, groupId)
if err != nil {
return
}
copy(prvHybrid[x25519SharedSecretSz:], private)
copy(pubHybrid[x25519SharedSecretSz:], ks.data)
return prvHybrid[:], keyShare{group: HybridSidhP503Curve25519, data: pubHybrid[:]}, nil
}
func (kexHybridSidhP503X25519) derive(c *Conn, ks keyShare, key []byte) []byte {
var sharedKey [SidhP503Curve25519SharedKeySz]byte
var ret []byte
var tmpKs keyShare
// Key agreement for classic
tmpKs.group = X25519
tmpKs.data = ks.data[:x25519SharedSecretSz]
ret = dhKexStrat[X25519].derive(c, tmpKs, key[:x25519SharedSecretSz])
if ret == nil {
return nil
}
copy(sharedKey[:], ret)
// Key agreement for PQ
tmpKs.group = sidhP503
tmpKs.data = ks.data[x25519SharedSecretSz:]
ret = dhKexStrat[sidhP503].derive(c, tmpKs, key[x25519SharedSecretSz:])
if ret == nil {
return nil
}
copy(sharedKey[x25519SharedSecretSz:], ret)
return sharedKey[:]
}

Visa fil

@ -15,7 +15,7 @@ OS ?= $(shell $(GO) env GOHOSTOS)
ARCH ?= $(shell $(GO) env GOHOSTARCH)
OS_ARCH := $(OS)_$(ARCH)
VER_OS_ARCH := $(shell $(GO) version | cut -d' ' -f 3)_$(OS)_$(ARCH)
GOROOT_ENV := $(shell $(GO) env GOROOT)
GOROOT_ENV ?= $(shell $(GO) env GOROOT)
GOROOT_LOCAL = $(BUILD_DIR)/$(OS_ARCH)
# Flag indicates wheter invoke "go install -race std". Supported only on amd64 with CGO enabled
INSTALL_RACE:= $(words $(filter $(ARCH)_$(shell go env CGO_ENABLED), amd64_1))
@ -30,8 +30,10 @@ BOGO_DOCKER_TRIS_LOCATION=/go/src/github.com/cloudflare/tls-tris
# SIDH repository (TODO: change path)
SIDH_REPO ?= https://github.com/cloudflare/sidh.git
SIDH_REPO_TAG ?= 25c0d9f15d5e5bd5652b9741aeb50d9eb37154dc
# NOBS repo (SIKE depends on SHA3)
NOBS_REPO ?= https://github.com/henrydcase/nobscrypto.git
NOBS_REPO_TAG ?= 597f68906e981e61160b8c959b326596c0b240be
###############
#
@ -57,11 +59,13 @@ $(BUILD_DIR)/$(OS_ARCH)/.ok_$(VER_OS_ARCH): clean
# Vendor NOBS library
$(GIT) clone $(NOBS_REPO) $(TMP_DIR)/nobs
cd $(TMP_DIR)/nobs; $(GIT) checkout $(NOBS_REPO_TAG)
cd $(TMP_DIR)/nobs; make vendor-sidh-for-tls
cp -rf $(TMP_DIR)/nobs/tls_vendor/* $(GOROOT_LOCAL)/src/vendor/
# Vendor SIDH library
$(GIT) clone $(SIDH_REPO) $(TMP_DIR)/sidh
cd $(TMP_DIR)/sidh; $(GIT) checkout $(SIDH_REPO_TAG)
cd $(TMP_DIR)/sidh; make vendor
cp -rf $(TMP_DIR)/sidh/build/vendor/* $(GOROOT_LOCAL)/src/vendor/

Visa fil

@ -216,12 +216,10 @@ class InteropServer_BoringSSL(InteropServer, ServerNominalMixin, ServerClientAut
Checks wether ALPN is sent back by tris server in EncryptedExtensions in case of TLS 1.3. The
ALPN protocol is set to 'npn_proto', which is hardcoded in TRIS test server.
'''
res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":1443 "+'-alpn-protos npn_proto -debug')
print(res[1])
res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":1443 "+'-alpn-protos npn_proto')
self.assertEqual(res[0], 0)
self.assertIsNotNone(re.search(RE_PATTERN_ALPN, res[1], re.MULTILINE))
# PicoTLS doesn't seem to implement draft-23 correctly. It will
# be enabled when draft-28 is implemented.
# class InteropServer_PicoTLS(
@ -262,5 +260,17 @@ class InteropServer_TRIS(ClientNominalMixin, InteropServer, unittest.TestCase):
res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=false -cliauth '+self.server_ip+":6443")
self.assertEqual(res[0], 0)
def test_qr(self):
res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=true -qr SIDH-P503-X25519 '+self.server_ip+":7443")
self.assertEqual(res[0], 0)
def test_qrServerDoesntSupportSIDH(self):
'''
Client advertises HybridSIDH and ECDH. Server supports ECDH only. Checks weather
TLS session can still be established.
'''
res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=true '+self.server_ip+":7443")
self.assertEqual(res[0], 0)
if __name__ == '__main__':
unittest.main()

Visa fil

@ -8,6 +8,7 @@ EXPOSE 3443
EXPOSE 4443
EXPOSE 5443
EXPOSE 6443
EXPOSE 7443
ADD tris-localserver /
ADD runner.sh /

Visa fil

@ -6,5 +6,6 @@
./tris-localserver -b 0.0.0.0:4443 -cert=ecdsa -rtt0=oa 2>&1 & # fourth port: offer and accept 0-RTT
./tris-localserver -b 0.0.0.0:5443 -cert=ecdsa -rtt0=oa -rtt0ack 2>&1 & # fifth port: offer and accept 0-RTT but confirm
./tris-localserver -b 0.0.0.0:6443 -cert=rsa -cliauth 2>&1 & # sixth port: RSA with required client authentication
./tris-localserver -b 0.0.0.0:7443 -cert=ecdsa -qr=c & # Enables support for both - post-quantum and classical KEX algorithms
wait

Visa fil

@ -55,6 +55,15 @@ func NewServer() *server {
return s
}
func enableQR(s *server, enableDefault bool) {
var sidhCurves = []tls.CurveID{tls.HybridSidhP503Curve25519}
if enableDefault {
var defaultCurvePreferences = []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521}
s.TLS.CurvePreferences = append(s.TLS.CurvePreferences, defaultCurvePreferences...)
}
s.TLS.CurvePreferences = append(s.TLS.CurvePreferences, sidhCurves...)
}
func (s *server) start() {
var err error
if (s.ZeroRTT & ZeroRTT_Offer) == ZeroRTT_Offer {
@ -144,6 +153,7 @@ func main() {
arg_zerortt := flag.String("rtt0", "n", `0-RTT, accepts following values [n: None, a: Accept, o: Offer, oa: Offer and Accept]`)
arg_confirm := flag.Bool("rtt0ack", false, "0-RTT confirm")
arg_clientauth := flag.Bool("cliauth", false, "Performs client authentication (RequireAndVerifyClientCert used)")
arg_qr := flag.String("qr", "", "Enable quantum-resistant algorithms [c: Support classical and Quantum-Resistant, q: Enable Quantum-Resistant only]")
flag.Parse()
s.Address = *arg_addr
@ -162,6 +172,12 @@ func main() {
s.TLS.ClientAuth = tls.RequireAndVerifyClientCert
}
if *arg_qr == "c" {
enableQR(s, true)
} else if *arg_qr == "q" {
enableQR(s, false)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tlsConn := r.Context().Value(http.TLSConnContextKey).(*tls.Conn)

Visa fil

@ -51,6 +51,17 @@ func (c *Client) setMinMaxTLS(ver uint16) {
c.TLS.MaxVersion = ver
}
func getQrAlgoId(qr string) tls.CurveID {
switch qr {
case "SIDH-P503-X25519":
return tls.HybridSidhP503Curve25519
case "SIDH-P751-X448":
return tls.HybridSidhP751Curve448
default:
return 0
}
}
func (c *Client) run() {
fmt.Printf("TLS %s with %s\n", tlsVersionToName[c.TLS.MinVersion], cipherSuiteIdToName[c.TLS.CipherSuites[0]])
@ -78,8 +89,7 @@ func (c *Client) run() {
failed++
return
}
fmt.Printf("Read %d bytes\n", n)
fmt.Printf("[TLS: %s] Read %d bytes\n", tlsVersionToName[con.ConnectionState().Version], n)
fmt.Println("OK\n")
}
@ -93,13 +103,14 @@ func result() {
// Usage client args host:port
func main() {
var keylog_file string
var keylog_file, qrAlgoName string
var enable_rsa, enable_ecdsa, client_auth bool
flag.StringVar(&keylog_file, "keylogfile", "", "Secrets will be logged here")
flag.BoolVar(&enable_rsa, "rsa", true, "Whether to enable RSA cipher suites")
flag.BoolVar(&enable_ecdsa, "ecdsa", true, "Whether to enable ECDSA cipher suites")
flag.BoolVar(&client_auth, "cliauth", false, "Whether to enable client authentication")
flag.StringVar(&qrAlgoName, "qr", "", "Specifies qr algorithm from following list:\n[SIDH-P503-X25519, SIDH-P751-X448]")
flag.Parse()
if flag.NArg() != 1 {
flag.Usage()
@ -124,6 +135,21 @@ func main() {
log.Println("Enabled keylog")
}
if len(qrAlgoName) > 0 {
id := getQrAlgoId(qrAlgoName)
if id == 0 {
log.Fatalf("Unknown QR algorithm: %s", qrAlgoName)
return
}
client.TLS.CipherSuites = []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}
client.TLS.CurvePreferences = []tls.CurveID{id}
client.setMinMaxTLS(tls.VersionTLS13)
client.run()
result()
return
}
if client_auth {
var err error
client_cert, err := tls.X509KeyPair([]byte(client_crt), []byte(client_key))
@ -145,6 +171,7 @@ func main() {
c.setMinMaxTLS(tls.VersionTLS12)
c.run()
}
if enable_ecdsa {
// Sane cipher suite for TLS 1.2 with an ECDSA cert (as used by boringssl)
c := client.clone()

Visa fil

@ -116,10 +116,6 @@ const (
type CurveID uint16
const (
// Unexported
sidhP503 CurveID = 0
sidhP751 CurveID = 1
// Exported IDs
CurveP256 CurveID = 23
CurveP384 CurveID = 24
@ -127,8 +123,12 @@ const (
X25519 CurveID = 29
// Experimental KEX
HybridSidhP503Curve25519 CurveID = 0x0105 + sidhP503 // HybridSIDH: X25519 + P503
HybridSidhP751Curve448 CurveID = 0x0105 + sidhP751 // HybridSIDH: X448 + P751
HybridSidhP503Curve25519 CurveID = 0x0105 + (sidhP503 & 0xFF) // HybridSIDH: X25519 + P503
HybridSidhP751Curve448 CurveID = 0x0105 + (sidhP751 & 0xFF) // HybridSIDH: X448 + P751
// Internal usage. Deliberately not exported
sidhP503 CurveID = 0xFE00
sidhP751 CurveID = 0xFE01
)
// TLS 1.3 Key Share