diff --git a/13.go b/13.go index 4d72c93..da02bfb 100644 --- a/13.go +++ b/13.go @@ -22,6 +22,7 @@ import ( "time" sidh "github_com/cloudflare/sidh/sidh" + sike "github_com/cloudflare/sidh/sike" "golang_org/x/crypto/curve25519" ) @@ -32,14 +33,22 @@ const numSessionTickets = 2 type secretLabel int const ( - x25519SharedSecretSz = 32 - - P503PubKeySz = 378 - P503PrvKeySz = 32 - P503SharedSecretSz = 126 - SIDHp503Curve25519PubKeySz = x25519SharedSecretSz + P503PubKeySz - SIDHp503Curve25519PrvKeySz = x25519SharedSecretSz + P503PrvKeySz - SIDHp503Curve25519SharedKeySz = x25519SharedSecretSz + P503SharedSecretSz + // Both public key and shared secret size + x25519Sz = 32 + + SIDHp503PubKeySz = 378 + SIDHp503PrvKeySz = 32 + SIDHp503SharedSecretSz = 126 + SIDHp503Curve25519PubKeySz = x25519Sz + SIDHp503PubKeySz + SIDHp503Curve25519PrvKeySz = x25519Sz + SIDHp503PrvKeySz + SIDHp503Curve25519SharedKeySz = x25519Sz + SIDHp503SharedSecretSz + + SIKEp503SharedSecretSz = 16 + SIKEp503CtSz = SIDHp503PubKeySz + 24 + SIKEp503Curve25519CtSz = x25519Sz + SIKEp503CtSz + SIKEp503Curve25519PubKeySz = x25519Sz + SIDHp503PubKeySz + SIKEp503Curve25519PrvKeySz = x25519Sz + SIDHp503PrvKeySz + 24 + SIKEp503Curve25519SharedKeySz = x25519Sz + SIKEp503SharedSecretSz ) const ( @@ -111,12 +120,18 @@ func (defaultServerKEX) keyAgreementServer(c *Conn, clientKS keyShare) ([]byte, type kexNIST struct{ defaultServerKEX } // Used by NIST curves; P-256, P-384, P-512 type kexX25519 struct{ defaultServerKEX } // Used by X25519 type kexSIDHp503 struct{ defaultServerKEX } // Used by SIDH/P503 +type kexSIKEp503 struct{} // Used by SIKE/P503 type kexHybridSIDHp503X25519 struct { defaultServerKEX classicKEX kexX25519 pqKEX kexSIDHp503 } // Used by SIDH-ECDH hybrid scheme +type kexHybridSIKEp503X25519 struct { + classicKEX kexX25519 + pqKEX kexSIKEp503 +} // Used by SIKE-ECDHE hybrid scheme + // Routing map for key exchange strategies var kexStrat = map[CurveID]kex{ CurveP256: &kexNIST{}, @@ -124,6 +139,7 @@ var kexStrat = map[CurveID]kex{ CurveP521: &kexNIST{}, X25519: &kexX25519{}, HybridSIDHp503Curve25519: &kexHybridSIDHp503X25519{}, + HybridSIKEp503Curve25519: &kexHybridSIKEp503X25519{}, } func newKeySchedule13(suite *cipherSuite, config *Config, clientRandom []byte) *keySchedule13 { @@ -1238,7 +1254,7 @@ func (kexNIST) keyAgreementClient(c *Conn, ks keyShare, secretKey []byte) ([]byt // KEX: X25519 func (kexX25519) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) { - var scalar, public [x25519SharedSecretSz]byte + var scalar, public [x25519Sz]byte if _, err := io.ReadFull(c.config.rand(), scalar[:]); err != nil { return nil, keyShare{}, err } @@ -1247,8 +1263,8 @@ func (kexX25519) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) { } func (kexX25519) keyAgreementClient(c *Conn, ks keyShare, secretKey []byte) ([]byte, error) { - var theirPublic, sharedKey, scalar [x25519SharedSecretSz]byte - if len(ks.data) != x25519SharedSecretSz { + var theirPublic, sharedKey, scalar [x25519Sz]byte + if len(ks.data) != x25519Sz { return nil, errors.New("tls: wrong shared secret size") } copy(theirPublic[:], ks.data) @@ -1270,9 +1286,8 @@ func (kexSIDHp503) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) func (kexSIDHp503) keyAgreementClient(c *Conn, ks keyShare, key []byte) ([]byte, error) { var prvVariant, pubVariant = getSidhKeyVariant(c.isClient) - var prvKeySize = P503PrvKeySz - if len(ks.data) != P503PubKeySz || len(key) != prvKeySize { + if len(ks.data) != SIDHp503PubKeySz || len(key) != SIDHp503PrvKeySz { return nil, errors.New("tls: wrong key size") } @@ -1309,20 +1324,20 @@ func (kex *kexHybridSIDHp503X25519) generate(c *Conn, groupId CurveID) (private if err != nil { return } - copy(prvHybrid[x25519SharedSecretSz:], private) - copy(pubHybrid[x25519SharedSecretSz:], ks.data) + copy(prvHybrid[x25519Sz:], private) + copy(pubHybrid[x25519Sz:], ks.data) return prvHybrid[:], keyShare{group: HybridSIDHp503Curve25519, data: pubHybrid[:]}, nil } -func (kex *kexHybridSIDHp503X25519) keyAgreementClient(c *Conn, ks keyShare, key []byte) ([]byte, error) { +func (kex *kexHybridSIDHp503X25519) keyAgreementClient(c *Conn, theirsKS keyShare, key []byte) ([]byte, error) { var sharedKey [SIDHp503Curve25519SharedKeySz]byte var ret []byte var tmpKs keyShare // Key agreement for classic tmpKs.group = X25519 - tmpKs.data = ks.data[:x25519SharedSecretSz] - ret, err := kex.classicKEX.keyAgreementClient(c, tmpKs, key[:x25519SharedSecretSz]) + tmpKs.data = theirsKS.data[:x25519Sz] + ret, err := kex.classicKEX.keyAgreementClient(c, tmpKs, key[:x25519Sz]) if err != nil { return nil, err } @@ -1330,11 +1345,157 @@ func (kex *kexHybridSIDHp503X25519) keyAgreementClient(c *Conn, ks keyShare, key // Key agreement for PQ tmpKs.group = 0 /*UNUSED*/ - tmpKs.data = ks.data[x25519SharedSecretSz:] - ret, err = kex.pqKEX.keyAgreementClient(c, tmpKs, key[x25519SharedSecretSz:]) + tmpKs.data = theirsKS.data[x25519Sz:] + ret, err = kex.pqKEX.keyAgreementClient(c, tmpKs, key[x25519Sz:]) if err != nil { return nil, err } - copy(sharedKey[x25519SharedSecretSz:], ret) + copy(sharedKey[x25519Sz:], ret) return sharedKey[:], nil } + +// generate method generates SIKE key pair (ephemeral) on client side +func (kexSIKEp503) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) { + if !c.isClient { + return nil, keyShare{}, errors.New("tls: internal error") + } + + var prvKey = sidh.NewPrivateKey(sidh.FP_503, sidh.KeyVariant_SIKE) + if prvKey.Generate(c.config.rand()) != nil { + return nil, keyShare{}, errors.New("tls: private SIDH key generation failed") + } + var pubKey = prvKey.GeneratePublicKey() + var ks = keyShare{data: pubKey.Export()} + + // 'buf' is a concatenation of (private || public) key. I need public key + // when decapsulating in kexSIKEp503::keyAgreementClient. + var buf = make([]byte, prvKey.Size()+pubKey.Size()) + copy(buf, prvKey.Export()) + copy(buf[prvKey.Size():], ks.data) + return buf, ks, nil +} + +// keyAgreementClient performs KEM decapsulation. 'privateKey' is a concatenation +// of (private || public) key +func (kexSIKEp503) keyAgreementClient(c *Conn, theirsKS keyShare, privateKey []byte) ([]byte, error) { + // Import private key + var prvKey = sidh.NewPrivateKey(sidh.FP_503, sidh.KeyVariant_SIKE) + var pubKey = sidh.NewPublicKey(sidh.FP_503, sidh.KeyVariant_SIKE) + + if len(privateKey) != prvKey.Size()+pubKey.Size() { + return nil, errors.New("tls: internal error") + } + + // Never fails + prvKey.Import(privateKey[:prvKey.Size()]) + pubKey.Import(privateKey[prvKey.Size():]) + + ss, err := sike.Decapsulate(prvKey, pubKey, theirsKS.data) + if err != nil { + return nil, err + } + return ss, nil +} + +// keyAgreementServer performs KEM encapsulation. +func (kexSIKEp503) keyAgreementServer(c *Conn, theirsKS keyShare) ([]byte, keyShare, error) { + pubKey := sidh.NewPublicKey(sidh.FP_503, sidh.KeyVariant_SIKE) + if pubKey.Import(theirsKS.data) != nil { + return nil, keyShare{}, errors.New("tls: can't import public SIKE key") + } + ct, key, err := sike.Encapsulate(c.config.rand(), pubKey) + if err != nil { + return nil, keyShare{}, errors.New("tls: SIKE encapsulation failed") + } + return key, keyShare{data: ct}, nil +} + +// KEX Hybrid SIKEp503-X25519 +func (kex *kexHybridSIKEp503X25519) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) { + var pubHybrid [SIKEp503Curve25519PubKeySz]byte + var prvHybrid [SIKEp503Curve25519PrvKeySz + SIDHp503PubKeySz]byte + + // Generate ephemeral key for classic x25519 + private, ks, err := kex.classicKEX.generate(c, 0) + if err != nil { + return nil, keyShare{}, err + } + copy(prvHybrid[:], private) + copy(pubHybrid[:], ks.data) + + // Generate PQ ephemeral key for SIDH + private, ks, err = kex.pqKEX.generate(c, 0) + if err != nil { + return nil, keyShare{}, err + } + copy(prvHybrid[x25519Sz:], private) + copy(pubHybrid[x25519Sz:], ks.data) + return prvHybrid[:], keyShare{group: HybridSIKEp503Curve25519, data: pubHybrid[:]}, nil +} + +// keyAgreementClient performs X25519-SIKEp503 key agreement on client side. 'theirsKS.data' contains +// X25519 public key and SIKEp503 KEM generated by the server. 'privateKey' is a key stored +// locally by the process. It is a concatenation of (X25519 || SIKEp503 private || SIKEp503 public) keys. +// In case of success concatenation of (X25519||SIKEp503) shared secrets is returned (32+16 bytes). +func (kex *kexHybridSIKEp503X25519) keyAgreementClient(c *Conn, theirsKS keyShare, privateKey []byte) ([]byte, error) { + var ssHyb [SIKEp503Curve25519SharedKeySz]byte + var tmpKs keyShare + + if len(privateKey) != SIKEp503Curve25519PrvKeySz+SIDHp503PubKeySz { + return nil, errors.New("tls: internal error") + } + + if len(theirsKS.data) != SIKEp503Curve25519CtSz { + return nil, errors.New("tls: wrong key size for X25519-SIKEp503") + } + + // Key agreement for classic + tmpKs.group = X25519 + tmpKs.data = theirsKS.data[:x25519Sz] + ret, err := kex.classicKEX.keyAgreementClient(c, tmpKs, privateKey[:x25519Sz]) + if err != nil { + return nil, err + } + copy(ssHyb[:], ret) + + // Key agreement for PQ + tmpKs.group = 0 /*UNUSED*/ + tmpKs.data = theirsKS.data[x25519Sz:] + ret, err = kex.pqKEX.keyAgreementClient(c, tmpKs, privateKey[x25519Sz:]) + if err != nil { + return nil, err + } + copy(ssHyb[x25519Sz:], ret[:]) + return ssHyb[:], nil +} + +// keyAgreementServer performs X25519-SIKEp503 shared secret agreement on a server side. 'theirsKS' +// contains concatenation of public keys for both X25519 and SIKEp503. In case of success +// function returns X25519 and SIKEp503 shaerd secret concatenated together and concatenation of +// X25519 public and SIKEp503 ciphertext that are sent to the client. +func (kex *kexHybridSIKEp503X25519) keyAgreementServer(c *Conn, theirsKS keyShare) ([]byte, keyShare, error) { + var ssHyb [SIKEp503Curve25519SharedKeySz]byte + var ret [SIKEp503Curve25519CtSz]byte + + if len(theirsKS.data) != SIKEp503Curve25519PubKeySz { + return nil, keyShare{}, errors.New("tls: wrong key size for X25519-SIKEp503") + } + + var tmpKs = keyShare{group: X25519, data: theirsKS.data[:x25519Sz]} + ss, srvKs, err := kex.classicKEX.keyAgreementServer(c, tmpKs) + if err != nil { + return nil, keyShare{}, err + } + copy(ssHyb[:], ss[:]) + copy(ret[:], srvKs.data[:]) + + tmpKs.group = 0 /*UNUSED*/ + tmpKs.data = theirsKS.data[x25519Sz:] + ss, srvKs, err = kex.pqKEX.keyAgreementServer(c, tmpKs) + if err != nil { + return nil, keyShare{}, err + } + copy(ssHyb[x25519Sz:], ss[:]) + copy(ret[x25519Sz:], srvKs.data[:SIKEp503CtSz]) + return ssHyb[:], keyShare{group: HybridSIKEp503Curve25519, data: ret[:]}, nil +} diff --git a/_dev/interop_test_runner b/_dev/interop_test_runner index c1de46d..5fe9577 100755 --- a/_dev/interop_test_runner +++ b/_dev/interop_test_runner @@ -292,6 +292,10 @@ class InteropServer_TRIS(ClientNominalMixin, InteropServer, unittest.TestCase): res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=true -groups X25519-SIDHp503 '+self.server_ip+":7443") self.assertEqual(res[0], 0) + def test_SIKE(self): + res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=true -groups X25519-SIKEp503 '+self.server_ip+":7443") + self.assertEqual(res[0], 0) + def test_server_doesnt_support_SIDH(self): ''' Client advertises HybridSIDH and ECDH. Server supports ECDH only. Checks weather diff --git a/_dev/tris-localserver/runner.sh b/_dev/tris-localserver/runner.sh index 32d1600..9b43013 100755 --- a/_dev/tris-localserver/runner.sh +++ b/_dev/tris-localserver/runner.sh @@ -6,6 +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 +./tris-localserver -b 0.0.0.0:7443 -cert=ecdsa -pq=c & # Enables support for both - post-quantum and classical KEX algorithms wait diff --git a/_dev/tris-localserver/server.go b/_dev/tris-localserver/server.go index 23deafc..4c7733f 100644 --- a/_dev/tris-localserver/server.go +++ b/_dev/tris-localserver/server.go @@ -55,13 +55,13 @@ func NewServer() *server { return s } -func enableQR(s *server, enableDefault bool) { - var sidhCurves = []tls.CurveID{tls.HybridSIDHp503Curve25519} +func enablePQ(s *server, enableDefault bool) { + var pqGroups = []tls.CurveID{tls.HybridSIDHp503Curve25519, tls.HybridSIKEp503Curve25519} 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...) + s.TLS.CurvePreferences = append(s.TLS.CurvePreferences, pqGroups...) } func (s *server) start() { @@ -153,7 +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]") + arg_pq := flag.String("pq", "", "Enable quantum-resistant algorithms [c: Support classical and Quantum-Resistant, q: Enable Quantum-Resistant only]") flag.Parse() s.Address = *arg_addr @@ -172,10 +172,10 @@ func main() { s.TLS.ClientAuth = tls.RequireAndVerifyClientCert } - if *arg_qr == "c" { - enableQR(s, true) - } else if *arg_qr == "q" { - enableQR(s, false) + if *arg_pq == "c" { + enablePQ(s, true) + } else if *arg_pq == "q" { + enablePQ(s, false) } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { diff --git a/_dev/tris-testclient/client.go b/_dev/tris-testclient/client.go index b54499b..a5efa76 100644 --- a/_dev/tris-testclient/client.go +++ b/_dev/tris-testclient/client.go @@ -29,6 +29,7 @@ var cipherSuiteIdToName = map[uint16]string{ var namedGroupsToName = map[uint16]string{ uint16(tls.HybridSIDHp503Curve25519): "X25519-SIDHp503", + uint16(tls.HybridSIKEp503Curve25519): "X25519-SIKEp503", uint16(tls.X25519): "X25519", uint16(tls.CurveP256): "P-256", uint16(tls.CurveP384): "P-384", diff --git a/common.go b/common.go index 459b1ef..8756e7b 100644 --- a/common.go +++ b/common.go @@ -124,6 +124,7 @@ const ( // Experimental KEX HybridSIDHp503Curve25519 CurveID = 0xFE30 + HybridSIKEp503Curve25519 CurveID = 0xFE32 ) // TLS 1.3 Key Share