Commit e3e1487f authored by Yawning Angel 's avatar Yawning Angel

Add support for the Hybrid Forward Secrecy extension.

This commit adds support for the experimental Hybrid Forward Secrecy
extension, using NewHope-Simple as the HFS primitive.

Limitations:

 * Only `Noise_XXhfs` is implemented because that's the only one that
   Katzenpost will use.  Supporting the other variants is mostly a
   matter of adding additional `HandshakePattern` definitions.

 * Kyber is the new hotness in terms of lattice based DH like
   primitives, so that should probably be used instead of
   NewHope-Simple, but I already have a NewHope-Simple implementation.

   (Then again, ISTR that Kyber uses the same NTT that NewHope does, and
    Peter writes clean code so implementing it should be trivial.)

 * Pre-message `f`/`rf` patterns are supposed to be handled, but aren't,
   because I don't use them.

 * This should probably enforce `5.2 Pattern Validity`, in
   `ReadMessage()`/`WriteMessage()`, but, "Don't define invalid
   patterns".

 * I was lazy and didn't generate test vectors.

Fixes #1.
parent 69209051
......@@ -76,6 +76,7 @@ type CipherSuite interface {
DHFunc
CipherFunc
HashFunc
HFSFunc
Name() []byte
}
......@@ -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
}
......
// hfs.go - Hybrid Forward Secrecy extension.
// Copyright (C) 2017 Yawning Angel.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package noise
import (
"io"
"git.schwanenlied.me/yawning/newhope.git"
)
type HFSKey interface {
Public() []byte
}
// HFSFunc implements a hybrid forward secrecy function, for the Noise HFS
// extension (version 1draft-5).
//
// See: https://github.com/noiseprotocol/noise_spec/blob/master/extensions/ext_hybrid_forward_secrecy.md
type HFSFunc interface {
// GenerateKeypairF generates a new key pair for the hybrid forward
// secrecy algorithm relative to a remote public key rf. The rf value
// will be empty for the first "f" token in the handshake, and non-empty
// for the second "f" token.
GenerateKeypairF(rng io.Reader, rf []byte) HFSKey
// FF performs a hybrid forward secrecy calculation that mixes a local key
// pair with a remote public key.
FF(keypair HFSKey, pubkey []byte) []byte
// FLen1 is a constant specifying the size in bytes of the output from
// GenerateKeypairF(rf) when rf is empty.
FLen1() int
// Flen2 is a constant specifying the size in bytes of the output from
// GenerateKeypairF(rf) when rf is not empty.
FLen2() int
// FLen is constant specifying the size in bytes of the output from FF().
FLen() int
// HFSName is the name of the HFS function.
HFSName() string
}
// HFSNewHopeSimple is the NewHope-Simple HFS function.
var HFSNewHopeSimple HFSFunc = hfsNewHopeSimple{}
type hfsNewHopeSimple struct{}
type keyNewHopeSimpleAlice struct {
privKey *newhope.PrivateKeySimpleAlice
pubKey *newhope.PublicKeySimpleAlice
}
func (k *keyNewHopeSimpleAlice) Public() []byte {
return k.pubKey.Send[:]
}
type keyNewHopeSimpleBob struct {
pubKey *newhope.PublicKeySimpleBob
shared []byte
}
func (k *keyNewHopeSimpleBob) Public() []byte {
return k.pubKey.Send[:]
}
func (hfsNewHopeSimple) GenerateKeypairF(rng io.Reader, rf []byte) HFSKey {
if rf != nil {
if len(rf) != newhope.SendASimpleSize {
panic("noise/hfs: rf is not SendASimpleSize")
}
var alicePk newhope.PublicKeySimpleAlice
copy(alicePk.Send[:], rf)
pubKey, shared, err := newhope.KeyExchangeSimpleBob(rng, &alicePk)
if err != nil {
panic("noise/hfs: newhope.KeyExchangeSimpleBob(): " + err.Error())
}
return &keyNewHopeSimpleBob{
pubKey: pubKey,
shared: shared,
}
}
// Generate the keypair as Alice.
privKey, pubKey, err := newhope.GenerateKeyPairSimpleAlice(rng)
if err != nil {
panic("noise/hfs: newhope.GenerateKeypairSimpleAlice(): " + err.Error())
}
return &keyNewHopeSimpleAlice{
privKey: privKey,
pubKey: pubKey,
}
}
func (hfsNewHopeSimple) FF(keypair HFSKey, pubkey []byte) []byte {
switch k := keypair.(type) {
case *keyNewHopeSimpleAlice:
if len(pubkey) != newhope.SendBSimpleSize {
panic("noise/hfs: pubkey is not SendBSimpleSize")
}
var bobPk newhope.PublicKeySimpleBob
copy(bobPk.Send[:], pubkey[:])
s, err := newhope.KeyExchangeSimpleAlice(&bobPk, k.privKey)
if err != nil {
panic("noise/hfs: newhope.KeyExchangeSimpleAlice(): " + err.Error())
}
return s
case *keyNewHopeSimpleBob:
return k.shared
default:
}
panic("noise/fs: FF(): unsupported keypair type")
}
func (hfsNewHopeSimple) FLen1() int {
return newhope.SendASimpleSize
}
func (hfsNewHopeSimple) FLen2() int {
return newhope.SendBSimpleSize
}
func (hfsNewHopeSimple) FLen() int {
return newhope.SharedSecretSize
}
func (hfsNewHopeSimple) HFSName() string {
return "NewHopeSimple"
}
var hfsNull HFSFunc = hfsNullImpl{}
type hfsNullImpl struct{}
func (hfsNullImpl) GenerateKeypairF(r io.Reader, rf []byte) HFSKey {
panic("noise/hfs: GenerateKeypairF called for null HFS")
}
func (hfsNullImpl) FF(keypair HFSKey, pubkey []byte) []byte {
panic("noise/hfs: FF called for null HFS")
}
func (hfsNullImpl) FLen1() int {
return 0
}
func (hfsNullImpl) FLen2() int {
return 0
}
func (hfsNullImpl) FLen() int {
return 0
}
func (hfsNullImpl) HFSName() string {
return "(null)"
}
......@@ -130,3 +130,12 @@ var HandshakeX = HandshakePattern{
{MessagePatternE, MessagePatternDHES, MessagePatternS, MessagePatternDHSS},
},
}
var HandshakeXXhfs = HandshakePattern{
Name: "XXhfs",
Messages: [][]MessagePattern{
{MessagePatternE, MessagePatternF},
{MessagePatternE, MessagePatternF, MessagePatternDHEE, MessagePatternFF, MessagePatternS, MessagePatternDHSE},
{MessagePatternS, MessagePatternDHSE},
},
}
......@@ -194,6 +194,9 @@ const (
MessagePatternDHSE
MessagePatternDHSS
MessagePatternPSK
MessagePatternF
MessagePatternFF
)
// DefaultMaxMsgLen is the default maximum number of bytes that can be sent in
......@@ -206,8 +209,10 @@ type HandshakeState struct {
ss symmetricState
s DHKey // local static keypair
e DHKey // local ephemeral keypair
f HFSKey // local HFS keypair
rs []byte // remote party's static public key
re []byte // remote party's ephemeral public key
rf []byte // remote party's HFS public key
psk []byte // preshared key, maybe zero length
messagePatterns [][]MessagePattern
shouldWrite bool
......@@ -305,6 +310,8 @@ func NewHandshakeState(c Config) (*HandshakeState, error) {
}
hs.ss.InitializeSymmetric([]byte("Noise_" + c.Pattern.Name + pskModifier + "_" + string(hs.ss.cs.Name())))
hs.ss.MixHash(c.Prologue)
// TODO: Technically r/rf can be part of the pre-message state, but we
// don't use it, so punt on supporting it.
for _, m := range c.Pattern.InitiatorPreMessages {
switch {
case c.Initiator && m == MessagePatternS:
......@@ -385,6 +392,11 @@ func (s *HandshakeState) WriteMessage(out, payload []byte) ([]byte, *CipherState
s.ss.MixKey(s.ss.cs.DH(s.s.Private, s.rs))
case MessagePatternPSK:
s.ss.MixKeyAndHash(s.psk)
case MessagePatternF:
s.f = s.ss.cs.GenerateKeypairF(s.rng, s.rf)
out = s.ss.EncryptAndHash(out, s.f.Public())
case MessagePatternFF:
s.ss.MixKey(s.ss.cs.FF(s.f, s.rf))
}
}
s.shouldWrite = false
......@@ -468,6 +480,25 @@ func (s *HandshakeState) ReadMessage(out, message []byte) ([]byte, *CipherState,
s.ss.MixKey(s.ss.cs.DH(s.s.Private, s.rs))
case MessagePatternPSK:
s.ss.MixKeyAndHash(s.psk)
case MessagePatternF:
expected := s.ss.cs.FLen1()
if s.f != nil {
expected = s.ss.cs.FLen2()
}
if s.ss.hasK {
expected += 16
}
if len(message) < expected {
return nil, nil, nil, ErrShortMessage
}
s.rf, err = s.ss.DecryptAndHash(nil, message[:expected])
if err != nil {
s.ss.Rollback()
return nil, nil, nil, err
}
message = message[expected:]
case MessagePatternFF:
s.ss.MixKey(s.ss.cs.FF(s.f, s.rf))
}
}
out, err = s.ss.DecryptAndHash(out, message)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment