Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HFS with Kyber1024 #38

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions cipher_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type CipherSuite interface {
DHFunc
CipherFunc
HashFunc
HFSFunc
Name() []byte
}

Expand All @@ -86,14 +87,28 @@ func NewCipherSuite(dh DHFunc, c CipherFunc, h HashFunc) CipherSuite {
DHFunc: dh,
CipherFunc: c,
HashFunc: h,
HFSFunc: hfsNull,
name: []byte(dh.DHName() + "_" + c.CipherName() + "_" + h.HashName()),
}
}

// NewCipherSuite HFS returns a CipherSuite constructed from the specified
// primitives, with the Hybrid Forward Secrecy extension.
func NewCipherSuiteHFS(dh DHFunc, c CipherFunc, h HashFunc, hfs HFSFunc) CipherSuite {
return ciphersuite{
DHFunc: dh,
CipherFunc: c,
HashFunc: h,
HFSFunc: hfs,
name: []byte(dh.DHName() + "+" + hfs.HFSName() + "_" + c.CipherName() + "_" + h.HashName()),
}
}

type ciphersuite struct {
DHFunc
CipherFunc
HashFunc
HFSFunc
name []byte
}

Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/flynn/noise

go 1.13

require (
github.com/cloudflare/circl v1.0.1-0.20210104183656-96a0695de3c3
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
)
23 changes: 23 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
github.com/cloudflare/circl v1.0.1-0.20210104183656-96a0695de3c3 h1:tpTW2GMi0DOdFJswbXNG6f45rOAgowhgPdofAWDKLwI=
github.com/cloudflare/circl v1.0.1-0.20210104183656-96a0695de3c3/go.mod h1:l2CvGr3DNS9Egif8pwQqJ45Ci9Y/PPs0XJHTcRKbGBQ=
github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 h1:u/UEqS66A5ckRmS4yNpjmVH56sVtS/RfclBAYocb4as=
github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201211090839-8ad439b19e0f h1:QdHQnPce6K4XQewki9WNbG5KOROuDzqO3NaYjI1cXJ0=
golang.org/x/sys v0.0.0-20201211090839-8ad439b19e0f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
155 changes: 155 additions & 0 deletions hfs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package noise

import (
"io"

"github.com/cloudflare/circl/kem/kyber/kyber1024"
)

type HFSDecapsulationKey interface {
DecapsulateTo(sharedSecret, ciphertext []byte)
}

type HFSKeyPair interface {
Public() []byte
Private() HFSDecapsulationKey
}

// HFSFunc implements a KEM-based Hybrid Forward Secrecy for Noise.
//
// See: https://github.com/noiseprotocol/noise_hfs_spec/blob/master/output/noise_hfs.pdf
type HFSFunc interface {
// GenerateKEMKeypair generates a new KEM key pair.
GenerateKEMKeypair(rng io.Reader) HFSKeyPair

// GenerateKEMCiphertext generates both a ciphertext
// and a KEM output given the remote party's public key.
GenerateKEMCiphertext(pubkey []byte, rng io.Reader) (ciphertext, sharedSecret []byte)

// KEM performs calculation between the private key in the key pair
// and the ciphertext and returns a sharedSecret.
KEM(keyPair HFSKeyPair, ciphertext []byte) (sharedSecret []byte)

// PublicKeySize returns the size of the serialized public key.
PublicKeySize() int

// CiphertextSize returns the size of the KEM ciphertext.
CiphertextSize() int

// SharedKeySize returns the size of the KEM shared secret.
SharedKeySize() int

// HFSName is the name of the HFS function.
HFSName() string
}

// HFSKyber is the Kyber crypto_kem_keypair HFS function.
var HFSKyber HFSFunc = hfsKyber{}

type hfsKyber struct{}

type keyKyberInitiator struct {
privKey *kyber1024.PrivateKey
pubKey *kyber1024.PublicKey
}

func (k *keyKyberInitiator) Public() []byte {
ret, err := k.pubKey.MarshalBinary()
if err != nil {
panic("noise/hfs: kyber1024: failure to serialize public key: " + err.Error())
}
return ret
}

func (k *keyKyberInitiator) Private() HFSDecapsulationKey {
return k.privKey
}

func (hfsKyber) GenerateKEMKeypair(rng io.Reader) HFSKeyPair {
pubKey, privKey, err := kyber1024.GenerateKeyPair(rng)
if err != nil {
panic("noise/hfs: kyber1024.GenerateKeyPair: " + err.Error())
}
return &keyKyberInitiator{
privKey: privKey,
pubKey: pubKey,
}
}

func (h hfsKyber) GenerateKEMCiphertext(pubkey []byte, rng io.Reader) (ciphertext, sharedSecret []byte) {
if len(pubkey) != h.PublicKeySize() {
panic("noise/hfs: PublicKey is not kyber1024.PublicKeySize")
}
alicePublicKeyVal, err := kyber1024.Scheme().UnmarshalBinaryPublicKey(pubkey)
if err != nil {
panic("noise/hfs: PublicKey failed to deserialize: " + err.Error())
}
alicePubKey := alicePublicKeyVal.(*kyber1024.PublicKey)
ciphertext = make([]byte, kyber1024.CiphertextSize)
sharedSecret = make([]byte, kyber1024.SharedKeySize)
seed := make([]byte, kyber1024.EncapsulationSeedSize)
_, err = rng.Read(seed)
if err != nil {
panic(err)
}
alicePubKey.EncapsulateTo(ciphertext, sharedSecret, seed)
return ciphertext, sharedSecret
}

func (hfsKyber) KEM(keyPair HFSKeyPair, ciphertext []byte) (sharedSecret []byte) {
if len(ciphertext) != kyber1024.CiphertextSize {
panic("noise/hfs: ciphertext is not kyber1024.CiphertextSize")
}
sharedSecret = make([]byte, kyber1024.SharedKeySize)
privKey := keyPair.Private()
privKey.DecapsulateTo(sharedSecret, ciphertext)
return sharedSecret
}

func (hfsKyber) PublicKeySize() int {
return kyber1024.PublicKeySize
}

func (hfsKyber) CiphertextSize() int {
return kyber1024.CiphertextSize
}

func (hfsKyber) SharedKeySize() int {
return kyber1024.SharedKeySize
}

func (hfsKyber) HFSName() string {
return "Kyber1024"
}

var hfsNull HFSFunc = hfsNullImpl{}

type hfsNullImpl struct{}

func (hfsNullImpl) GenerateKEMKeypair(rng io.Reader) HFSKeyPair {
panic("noise/hfs: GenerateKEMKeypair called for null HFS")
}

func (hfsNullImpl) GenerateKEMCiphertext(pubkey []byte, rng io.Reader) (ciphertext, sharedSecret []byte) {
panic("noise/hfs: GenerateKEMCiphertext called for null HFS")
}

func (hfsNullImpl) KEM(keypair HFSKeyPair, ciphertext []byte) []byte {
panic("noise/hfs: KEM called for null HFS")
}

func (hfsNullImpl) PublicKeySize() int {
return 0
}

func (hfsNullImpl) CiphertextSize() int {
return 0
}

func (hfsNullImpl) SharedKeySize() int {
return 0
}

func (hfsNullImpl) HFSName() string {
return "(null)"
}
64 changes: 64 additions & 0 deletions noise_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,70 @@ func (NoiseSuite) TestXXRoundtrip(c *C) {
c.Assert(string(res), Equals, "worri")
}

func (NoiseSuite) TestXXhfsKyberRoundtrip(c *C) {
cs := NewCipherSuiteHFS(DH25519, CipherChaChaPoly, HashBLAKE2b, HFSKyber)
rngI := new(RandomInc)
rngR := new(RandomInc)
*rngR = 1

staticI, _ := cs.GenerateKeypair(rngI)
staticR, _ := cs.GenerateKeypair(rngR)

hsI, _ := NewHandshakeState(Config{
CipherSuite: cs,
Random: rngI,
Pattern: HandshakeXXhfs,
Initiator: true,
StaticKeypair: staticI,
})
hsR, _ := NewHandshakeState(Config{
CipherSuite: cs,
Random: rngR,
Pattern: HandshakeXXhfs,
StaticKeypair: staticR,
})

// -> e, e1
msg, _, _, _ := hsI.WriteMessage(nil, []byte("abcdef"))
c.Assert(msg, HasLen, 1600+len([]byte("abcdef")))
res, _, _, err := hsR.ReadMessage(nil, msg)
c.Assert(err, IsNil)
c.Assert(string(res), Equals, "abcdef")

// <- e, ee, ekem1, s, es
msg, _, _, _ = hsR.WriteMessage(nil, nil)
c.Assert(msg, HasLen, 1680)
res, _, _, err = hsI.ReadMessage(nil, msg)
c.Assert(err, IsNil)
c.Assert(res, HasLen, 0)

// -> s, se
payload := "0123456789012345678901234567890123456789012345678901234567890123456789"
msg, csI0, csI1, _ := hsI.WriteMessage(nil, []byte(payload))
c.Assert(msg, HasLen, 134)
res, csR0, csR1, err := hsR.ReadMessage(nil, msg)
c.Assert(err, IsNil)
c.Assert(string(res), Equals, payload)

// transport message I -> R
msg = csI0.Encrypt(nil, nil, []byte("wubba"))
res, err = csR0.Decrypt(nil, nil, msg)
c.Assert(err, IsNil)
c.Assert(string(res), Equals, "wubba")

// transport message I -> R again
msg = csI0.Encrypt(nil, nil, []byte("aleph"))
res, err = csR0.Decrypt(nil, nil, msg)
c.Assert(err, IsNil)
c.Assert(string(res), Equals, "aleph")

// transport message R <- I
msg = csR1.Encrypt(nil, nil, []byte("worri"))
res, err = csI1.Decrypt(nil, nil, msg)
c.Assert(err, IsNil)
c.Assert(string(res), Equals, "worri")
}

func (NoiseSuite) Test_NNpsk0_Roundtrip(c *C) {
cs := NewCipherSuite(DH25519, CipherChaChaPoly, HashBLAKE2b)
rngI := new(RandomInc)
Expand Down
Loading