Skip to content

Commit 08ab277

Browse files
committed
cryto/tls: Implement kemtls with mutual auth #66
1 parent 5ef1b90 commit 08ab277

File tree

70 files changed

+5902
-3429
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+5902
-3429
lines changed

src/crypto/kem/kem.go

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package kem
2+
3+
import (
4+
"circl/dh/sidh"
5+
"circl/kem/schemes"
6+
"encoding/binary"
7+
"errors"
8+
"fmt"
9+
"io"
10+
11+
"golang.org/x/crypto/curve25519"
12+
)
13+
14+
// ID identifies each flavor of KEM.
15+
type ID uint16
16+
17+
const (
18+
// KEM25519 is X25519 as a KEM. Not quantum-safe.
19+
KEM25519 ID = 0x01fb
20+
// Kyber512 is a post-quantum KEM based on MLWE
21+
Kyber512 ID = 0x01fc
22+
// SIKEp434 is a post-quantum KEM
23+
SIKEp434 ID = 0x01fd
24+
25+
// minimum
26+
minKEM = KEM25519
27+
// maximum
28+
maxKEM = SIKEp434
29+
)
30+
31+
// PrivateKey is a private key.
32+
type PrivateKey struct {
33+
KEMId ID
34+
PrivateKey []byte
35+
}
36+
37+
// PublicKey is a public key.
38+
type PublicKey struct {
39+
KEMId ID
40+
PublicKey []byte
41+
}
42+
43+
// MarshalBinary returns the byte representation of a public key.
44+
func (pubKey *PublicKey) MarshalBinary() ([]byte, error) {
45+
buf := make([]byte, 2+len(pubKey.PublicKey))
46+
binary.LittleEndian.PutUint16(buf, uint16(pubKey.KEMId))
47+
copy(buf[2:], pubKey.PublicKey)
48+
return buf, nil
49+
}
50+
51+
// UnmarshalBinary produces a PublicKey from a byte array.
52+
func (pubKey *PublicKey) UnmarshalBinary(data []byte) error {
53+
id := ID(binary.LittleEndian.Uint16(data[:2]))
54+
if id < minKEM || id > maxKEM {
55+
return errors.New("Invalid KEM type")
56+
}
57+
58+
pubKey.KEMId = id
59+
pubKey.PublicKey = data[2:]
60+
return nil
61+
}
62+
63+
// GenerateKey generates a keypair for a given KEM.
64+
// It returns a public and private key.
65+
func GenerateKey(rand io.Reader, kemID ID) (*PublicKey, *PrivateKey, error) {
66+
switch kemID {
67+
case Kyber512:
68+
scheme := schemes.ByName("Kyber512")
69+
seed := make([]byte, scheme.SeedSize())
70+
if _, err := io.ReadFull(rand, seed); err != nil {
71+
return nil, nil, err
72+
}
73+
publicKey, privateKey := scheme.DeriveKeyPair(seed)
74+
pk, _ := publicKey.MarshalBinary()
75+
sk, _ := privateKey.MarshalBinary()
76+
77+
return &PublicKey{KEMId: kemID, PublicKey: pk}, &PrivateKey{KEMId: kemID, PrivateKey: sk}, nil
78+
case KEM25519:
79+
privateKey := make([]byte, curve25519.ScalarSize)
80+
if _, err := io.ReadFull(rand, privateKey); err != nil {
81+
return nil, nil, err
82+
}
83+
publicKey, err := curve25519.X25519(privateKey, curve25519.Basepoint)
84+
if err != nil {
85+
return nil, nil, err
86+
}
87+
return &PublicKey{KEMId: kemID, PublicKey: publicKey}, &PrivateKey{KEMId: kemID, PrivateKey: privateKey}, nil
88+
case SIKEp434:
89+
privateKey := sidh.NewPrivateKey(sidh.Fp434, sidh.KeyVariantSike)
90+
publicKey := sidh.NewPublicKey(sidh.Fp434, sidh.KeyVariantSike)
91+
if err := privateKey.Generate(rand); err != nil {
92+
return nil, nil, err
93+
}
94+
privateKey.GeneratePublicKey(publicKey)
95+
96+
pubBytes := make([]byte, publicKey.Size())
97+
privBytes := make([]byte, privateKey.Size())
98+
publicKey.Export(pubBytes)
99+
privateKey.Export(privBytes)
100+
return &PublicKey{KEMId: kemID, PublicKey: pubBytes}, &PrivateKey{KEMId: kemID, PrivateKey: privBytes}, nil
101+
default:
102+
return nil, nil, fmt.Errorf("crypto/kem: internal error: unsupported KEM %d", kemID)
103+
}
104+
105+
}
106+
107+
// Encapsulate returns a shared secret and a ciphertext.
108+
func Encapsulate(rand io.Reader, pk *PublicKey) ([]byte, []byte, error) {
109+
switch pk.KEMId {
110+
case Kyber512:
111+
scheme := schemes.ByName("Kyber512")
112+
pub, err := scheme.UnmarshalBinaryPublicKey(pk.PublicKey)
113+
if err != nil {
114+
return nil, nil, err
115+
}
116+
117+
seed := make([]byte, scheme.EncapsulationSeedSize())
118+
if _, err := io.ReadFull(rand, seed); err != nil {
119+
return nil, nil, err
120+
}
121+
122+
ct, ss, err := scheme.EncapsulateDeterministically(pub, seed)
123+
if err != nil {
124+
return nil, nil, err
125+
}
126+
127+
return ss, ct, nil
128+
case KEM25519:
129+
privateKey := make([]byte, curve25519.ScalarSize)
130+
if _, err := io.ReadFull(rand, privateKey); err != nil {
131+
return nil, nil, err
132+
}
133+
ciphertext, err := curve25519.X25519(privateKey, curve25519.Basepoint)
134+
if err != nil {
135+
return nil, nil, err
136+
}
137+
sharedSecret, err := curve25519.X25519(privateKey, pk.PublicKey)
138+
if err != nil {
139+
return nil, nil, err
140+
}
141+
return sharedSecret, ciphertext, nil
142+
case SIKEp434:
143+
kem := sidh.NewSike434(rand)
144+
sikepk := sidh.NewPublicKey(sidh.Fp434, sidh.KeyVariantSike)
145+
err := sikepk.Import(pk.PublicKey)
146+
if err != nil {
147+
return nil, nil, err
148+
}
149+
150+
ct := make([]byte, kem.CiphertextSize())
151+
ss := make([]byte, kem.SharedSecretSize())
152+
err = kem.Encapsulate(ct, ss, sikepk)
153+
if err != nil {
154+
return nil, nil, err
155+
}
156+
157+
return ss, ct, nil
158+
default:
159+
return nil, nil, errors.New("crypto/kem: internal error: unsupported KEM in Encapsulate")
160+
}
161+
}
162+
163+
// Decapsulate generates the shared secret.
164+
func Decapsulate(privateKey *PrivateKey, ciphertext []byte) ([]byte, error) {
165+
switch privateKey.KEMId {
166+
case Kyber512:
167+
scheme := schemes.ByName("Kyber512")
168+
sk, err := scheme.UnmarshalBinaryPrivateKey(privateKey.PrivateKey)
169+
if err != nil {
170+
return nil, err
171+
}
172+
if len(ciphertext) != scheme.CiphertextSize() {
173+
return nil, fmt.Errorf("crypto/kem: ciphertext is of len %d, expected %d", len(ciphertext), scheme.CiphertextSize())
174+
}
175+
ss, err := scheme.Decapsulate(sk, ciphertext)
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
return ss, nil
181+
case KEM25519:
182+
sharedSecret, err := curve25519.X25519(privateKey.PrivateKey, ciphertext)
183+
if err != nil {
184+
return nil, err
185+
}
186+
return sharedSecret, nil
187+
case SIKEp434:
188+
kem := sidh.NewSike434(nil)
189+
sikesk := sidh.NewPrivateKey(sidh.Fp434, sidh.KeyVariantSike)
190+
err := sikesk.Import(privateKey.PrivateKey)
191+
if err != nil {
192+
return nil, err
193+
}
194+
195+
sikepk := sidh.NewPublicKey(sidh.Fp434, sidh.KeyVariantSike)
196+
sikesk.GeneratePublicKey(sikepk)
197+
ss := make([]byte, kem.SharedSecretSize())
198+
err = kem.Decapsulate(ss, sikesk, sikepk, ciphertext)
199+
if err != nil {
200+
return nil, err
201+
}
202+
203+
return ss, nil
204+
default:
205+
return nil, errors.New("crypto/kem: internal error: unsupported KEM in Decapsulate")
206+
}
207+
}

src/crypto/kem/kem_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package kem
2+
3+
import (
4+
"bytes"
5+
"crypto/rand"
6+
"testing"
7+
)
8+
9+
func TestKemAPI(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
kemID ID
13+
}{
14+
{"Kem25519", KEM25519},
15+
{"SIKEp434", SIKEp434},
16+
{"Kyber512", Kyber512},
17+
}
18+
for _, tt := range tests {
19+
t.Run(tt.name, func(t *testing.T) {
20+
publicKey, privateKey, err := GenerateKey(rand.Reader, tt.kemID)
21+
if err != nil {
22+
t.Fatal(err)
23+
}
24+
ss, ct, err := Encapsulate(rand.Reader, publicKey)
25+
if err != nil {
26+
t.Fatal(err)
27+
}
28+
29+
ss2, err := Decapsulate(privateKey, ct)
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
if !bytes.Equal(ss, ss2) {
34+
t.Fatal("Decapsulated differing shared secret")
35+
}
36+
37+
data, _ := publicKey.MarshalBinary()
38+
pk2 := new(PublicKey)
39+
err = pk2.UnmarshalBinary(data)
40+
if err != nil {
41+
t.Fatal("error unmarshaling")
42+
}
43+
if pk2.KEMId != publicKey.KEMId {
44+
t.Fatal("Difference in Id")
45+
}
46+
if !bytes.Equal(publicKey.PublicKey, publicKey.PublicKey) {
47+
t.Fatal("Difference in data for public keys")
48+
}
49+
})
50+
}
51+
52+
// check if nonexisting kem fails
53+
invalidKemID := ID(0)
54+
if _, _, err := GenerateKey(rand.Reader, invalidKemID); err == nil {
55+
t.Fatal("This KEM should've been invalid and failed")
56+
}
57+
58+
}

src/crypto/tls/auth.go

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"crypto/ecdsa"
1414
"crypto/ed25519"
1515
"crypto/elliptic"
16+
"crypto/kem"
1617
"crypto/rsa"
1718
"errors"
1819
"fmt"
@@ -118,6 +119,8 @@ func typeAndHashFromSignatureScheme(signatureAlgorithm SignatureScheme) (sigType
118119
sigType = signatureECDSA
119120
case Ed25519:
120121
sigType = signatureEd25519
122+
case KEMTLSWithSIKEp434, KEMTLSWithKyber512:
123+
sigType = authKEMTLS
121124
default:
122125
scheme := circlPki.SchemeByTLSID(uint(signatureAlgorithm))
123126
if scheme == nil {
@@ -140,6 +143,8 @@ func typeAndHashFromSignatureScheme(signatureAlgorithm SignatureScheme) (sigType
140143
hash = crypto.SHA512
141144
case Ed25519:
142145
hash = directSigning
146+
case KEMTLSWithSIKEp434, KEMTLSWithKyber512:
147+
hash = directSigning
143148
default:
144149
scheme := circlPki.SchemeByTLSID(uint(signatureAlgorithm))
145150
if scheme == nil {
@@ -267,39 +272,50 @@ func signatureSchemesForCertificate(version uint16, cert *Certificate) []Signatu
267272
// This function must be kept in sync with supportedSignatureAlgorithmsDC.
268273
func signatureSchemeForDelegatedCredential(version uint16, dc *DelegatedCredential) []SignatureScheme {
269274
pub := dc.cred.publicKey
270-
271275
var sigAlgs []SignatureScheme
272-
switch pub.(type) {
273-
case *ecdsa.PublicKey:
274-
pk, ok := pub.(*ecdsa.PublicKey)
275-
if !ok {
276+
277+
kemPub, ok := pub.(*kem.PublicKey)
278+
if ok {
279+
if kemPub.KEMId == kem.SIKEp434 {
280+
sigAlgs = []SignatureScheme{KEMTLSWithSIKEp434}
281+
} else if kemPub.KEMId == kem.Kyber512 {
282+
sigAlgs = []SignatureScheme{KEMTLSWithKyber512}
283+
} else {
276284
return nil
277285
}
278-
switch pk.Curve {
279-
case elliptic.P256():
280-
sigAlgs = []SignatureScheme{ECDSAWithP256AndSHA256}
281-
case elliptic.P384():
282-
sigAlgs = []SignatureScheme{ECDSAWithP384AndSHA384}
283-
case elliptic.P521():
284-
sigAlgs = []SignatureScheme{ECDSAWithP521AndSHA512}
286+
} else {
287+
switch pub.(type) {
288+
case *ecdsa.PublicKey:
289+
pk, ok := pub.(*ecdsa.PublicKey)
290+
if !ok {
291+
return nil
292+
}
293+
switch pk.Curve {
294+
case elliptic.P256():
295+
sigAlgs = []SignatureScheme{ECDSAWithP256AndSHA256}
296+
case elliptic.P384():
297+
sigAlgs = []SignatureScheme{ECDSAWithP384AndSHA384}
298+
case elliptic.P521():
299+
sigAlgs = []SignatureScheme{ECDSAWithP521AndSHA512}
300+
default:
301+
return nil
302+
}
303+
case ed25519.PublicKey:
304+
sigAlgs = []SignatureScheme{Ed25519}
305+
case circlSign.PublicKey:
306+
pk, ok := pub.(circlSign.PublicKey)
307+
if !ok {
308+
return nil
309+
}
310+
scheme := pk.Scheme()
311+
tlsScheme, ok := scheme.(circlPki.TLSScheme)
312+
if !ok {
313+
return nil
314+
}
315+
sigAlgs = []SignatureScheme{SignatureScheme(tlsScheme.TLSIdentifier())}
285316
default:
286317
return nil
287318
}
288-
case ed25519.PublicKey:
289-
sigAlgs = []SignatureScheme{Ed25519}
290-
case circlSign.PublicKey:
291-
pk, ok := pub.(circlSign.PublicKey)
292-
if !ok {
293-
return nil
294-
}
295-
scheme := pk.Scheme()
296-
tlsScheme, ok := scheme.(circlPki.TLSScheme)
297-
if !ok {
298-
return nil
299-
}
300-
sigAlgs = []SignatureScheme{SignatureScheme(tlsScheme.TLSIdentifier())}
301-
default:
302-
return nil
303319
}
304320

305321
return sigAlgs

src/crypto/tls/auth_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func TestSupportedSignatureAlgorithms(t *testing.T) {
163163
if sigType == 0 {
164164
t.Errorf("%v: missing signature type", sigAlg)
165165
}
166-
if hash == 0 && sigAlg != Ed25519 && circlPki.SchemeByTLSID(uint(sigAlg)) == nil {
166+
if hash == 0 && (sigAlg != Ed25519 && sigType != authKEMTLS) && circlPki.SchemeByTLSID(uint(sigAlg)) == nil {
167167
t.Errorf("%v: missing hash", sigAlg)
168168
}
169169
}

0 commit comments

Comments
 (0)