-
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.
cryto/tls: Implement kemtls with mutual auth #66
- Loading branch information
Showing
70 changed files
with
5,902 additions
and
3,430 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
package kem | ||
|
||
import ( | ||
"circl/dh/sidh" | ||
"circl/kem/schemes" | ||
"encoding/binary" | ||
"errors" | ||
"fmt" | ||
"io" | ||
|
||
"golang.org/x/crypto/curve25519" | ||
) | ||
|
||
// ID identifies each flavor of KEM. | ||
type ID uint16 | ||
|
||
const ( | ||
// KEM25519 is X25519 as a KEM. Not quantum-safe. | ||
KEM25519 ID = 0x01fb | ||
// Kyber512 is a post-quantum KEM based on MLWE | ||
Kyber512 ID = 0x01fc | ||
// SIKEp434 is a post-quantum KEM | ||
SIKEp434 ID = 0x01fd | ||
|
||
// minimum | ||
minKEM = KEM25519 | ||
// maximum | ||
maxKEM = SIKEp434 | ||
) | ||
|
||
// PrivateKey is a private key. | ||
type PrivateKey struct { | ||
KEMId ID | ||
PrivateKey []byte | ||
} | ||
|
||
// PublicKey is a public key. | ||
type PublicKey struct { | ||
KEMId ID | ||
PublicKey []byte | ||
} | ||
|
||
// MarshalBinary returns the byte representation of a public key. | ||
func (pubKey *PublicKey) MarshalBinary() ([]byte, error) { | ||
buf := make([]byte, 2+len(pubKey.PublicKey)) | ||
binary.LittleEndian.PutUint16(buf, uint16(pubKey.KEMId)) | ||
copy(buf[2:], pubKey.PublicKey) | ||
return buf, nil | ||
} | ||
|
||
// UnmarshalBinary produces a PublicKey from a byte array. | ||
func (pubKey *PublicKey) UnmarshalBinary(data []byte) error { | ||
id := ID(binary.LittleEndian.Uint16(data[:2])) | ||
if id < minKEM || id > maxKEM { | ||
return errors.New("Invalid KEM type") | ||
} | ||
|
||
pubKey.KEMId = id | ||
pubKey.PublicKey = data[2:] | ||
return nil | ||
} | ||
|
||
// GenerateKey generates a keypair for a given KEM. | ||
// It returns a public and private key. | ||
func GenerateKey(rand io.Reader, kemID ID) (*PublicKey, *PrivateKey, error) { | ||
switch kemID { | ||
case Kyber512: | ||
scheme := schemes.ByName("Kyber512") | ||
seed := make([]byte, scheme.SeedSize()) | ||
if _, err := io.ReadFull(rand, seed); err != nil { | ||
return nil, nil, err | ||
} | ||
publicKey, privateKey := scheme.DeriveKeyPair(seed) | ||
pk, _ := publicKey.MarshalBinary() | ||
sk, _ := privateKey.MarshalBinary() | ||
|
||
return &PublicKey{KEMId: kemID, PublicKey: pk}, &PrivateKey{KEMId: kemID, PrivateKey: sk}, nil | ||
case KEM25519: | ||
privateKey := make([]byte, curve25519.ScalarSize) | ||
if _, err := io.ReadFull(rand, privateKey); err != nil { | ||
return nil, nil, err | ||
} | ||
publicKey, err := curve25519.X25519(privateKey, curve25519.Basepoint) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
return &PublicKey{KEMId: kemID, PublicKey: publicKey}, &PrivateKey{KEMId: kemID, PrivateKey: privateKey}, nil | ||
case SIKEp434: | ||
privateKey := sidh.NewPrivateKey(sidh.Fp434, sidh.KeyVariantSike) | ||
publicKey := sidh.NewPublicKey(sidh.Fp434, sidh.KeyVariantSike) | ||
if err := privateKey.Generate(rand); err != nil { | ||
return nil, nil, err | ||
} | ||
privateKey.GeneratePublicKey(publicKey) | ||
|
||
pubBytes := make([]byte, publicKey.Size()) | ||
privBytes := make([]byte, privateKey.Size()) | ||
publicKey.Export(pubBytes) | ||
privateKey.Export(privBytes) | ||
return &PublicKey{KEMId: kemID, PublicKey: pubBytes}, &PrivateKey{KEMId: kemID, PrivateKey: privBytes}, nil | ||
default: | ||
return nil, nil, fmt.Errorf("crypto/kem: internal error: unsupported KEM %d", kemID) | ||
} | ||
|
||
} | ||
|
||
// Encapsulate returns a shared secret and a ciphertext. | ||
func Encapsulate(rand io.Reader, pk *PublicKey) ([]byte, []byte, error) { | ||
switch pk.KEMId { | ||
case Kyber512: | ||
scheme := schemes.ByName("Kyber512") | ||
pub, err := scheme.UnmarshalBinaryPublicKey(pk.PublicKey) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
seed := make([]byte, scheme.EncapsulationSeedSize()) | ||
if _, err := io.ReadFull(rand, seed); err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
ct, ss, err := scheme.EncapsulateDeterministically(pub, seed) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
return ss, ct, nil | ||
case KEM25519: | ||
privateKey := make([]byte, curve25519.ScalarSize) | ||
if _, err := io.ReadFull(rand, privateKey); err != nil { | ||
return nil, nil, err | ||
} | ||
ciphertext, err := curve25519.X25519(privateKey, curve25519.Basepoint) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
sharedSecret, err := curve25519.X25519(privateKey, pk.PublicKey) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
return sharedSecret, ciphertext, nil | ||
case SIKEp434: | ||
kem := sidh.NewSike434(rand) | ||
sikepk := sidh.NewPublicKey(sidh.Fp434, sidh.KeyVariantSike) | ||
err := sikepk.Import(pk.PublicKey) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
ct := make([]byte, kem.CiphertextSize()) | ||
ss := make([]byte, kem.SharedSecretSize()) | ||
err = kem.Encapsulate(ct, ss, sikepk) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
return ss, ct, nil | ||
default: | ||
return nil, nil, errors.New("crypto/kem: internal error: unsupported KEM in Encapsulate") | ||
} | ||
} | ||
|
||
// Decapsulate generates the shared secret. | ||
func Decapsulate(privateKey *PrivateKey, ciphertext []byte) ([]byte, error) { | ||
switch privateKey.KEMId { | ||
case Kyber512: | ||
scheme := schemes.ByName("Kyber512") | ||
sk, err := scheme.UnmarshalBinaryPrivateKey(privateKey.PrivateKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(ciphertext) != scheme.CiphertextSize() { | ||
return nil, fmt.Errorf("crypto/kem: ciphertext is of len %d, expected %d", len(ciphertext), scheme.CiphertextSize()) | ||
} | ||
ss, err := scheme.Decapsulate(sk, ciphertext) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return ss, nil | ||
case KEM25519: | ||
sharedSecret, err := curve25519.X25519(privateKey.PrivateKey, ciphertext) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return sharedSecret, nil | ||
case SIKEp434: | ||
kem := sidh.NewSike434(nil) | ||
sikesk := sidh.NewPrivateKey(sidh.Fp434, sidh.KeyVariantSike) | ||
err := sikesk.Import(privateKey.PrivateKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
sikepk := sidh.NewPublicKey(sidh.Fp434, sidh.KeyVariantSike) | ||
sikesk.GeneratePublicKey(sikepk) | ||
ss := make([]byte, kem.SharedSecretSize()) | ||
err = kem.Decapsulate(ss, sikesk, sikepk, ciphertext) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return ss, nil | ||
default: | ||
return nil, errors.New("crypto/kem: internal error: unsupported KEM in Decapsulate") | ||
} | ||
} |
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,58 @@ | ||
package kem | ||
|
||
import ( | ||
"bytes" | ||
"crypto/rand" | ||
"testing" | ||
) | ||
|
||
func TestKemAPI(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
kemID ID | ||
}{ | ||
{"Kem25519", KEM25519}, | ||
{"SIKEp434", SIKEp434}, | ||
{"Kyber512", Kyber512}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
publicKey, privateKey, err := GenerateKey(rand.Reader, tt.kemID) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
ss, ct, err := Encapsulate(rand.Reader, publicKey) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
ss2, err := Decapsulate(privateKey, ct) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !bytes.Equal(ss, ss2) { | ||
t.Fatal("Decapsulated differing shared secret") | ||
} | ||
|
||
data, _ := publicKey.MarshalBinary() | ||
pk2 := new(PublicKey) | ||
err = pk2.UnmarshalBinary(data) | ||
if err != nil { | ||
t.Fatal("error unmarshaling") | ||
} | ||
if pk2.KEMId != publicKey.KEMId { | ||
t.Fatal("Difference in Id") | ||
} | ||
if !bytes.Equal(publicKey.PublicKey, publicKey.PublicKey) { | ||
t.Fatal("Difference in data for public keys") | ||
} | ||
}) | ||
} | ||
|
||
// check if nonexisting kem fails | ||
invalidKemID := ID(0) | ||
if _, _, err := GenerateKey(rand.Reader, invalidKemID); err == nil { | ||
t.Fatal("This KEM should've been invalid and failed") | ||
} | ||
|
||
} |
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
Oops, something went wrong.