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

Update CIRCL #46

Merged
merged 1 commit into from
Dec 18, 2020
Merged
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
2 changes: 2 additions & 0 deletions src/circl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Version numbers are [Semvers](https://semver.org/). We release a minor version f
| Key Exchange | FourQ | One of the fastest elliptic curves at 128-bit security level. | Experimental for key agreement and digital signatures. |
| Key Exchange / Digital signatures | P-384 | Our optimizations reduce the burden when moving from P-256 to P-384. | ECDSA and ECDH using Suite B at top secret level. |
| Digital Signatures | Ed25519, Ed448 | RFC-8032 provides new signature schemes based on Edwards curves. | Digital certificates and authentication. |
| Key Encapsulation | P-256, P-384, P-521, X25519 and X448 | Key encapsulation methods based on Diffie-Hellman. | HPKE |
| Hybrid Public-Key Encryption | Base, Auth, PSK, AuthPSK | [HPKE](https://www.ietf.org/archive/id/draft-irtf-cfrg-hpke-07.html) is a combination of KEM and AEAD. | TLS |
| PQ KEM/PKE | Kyber | Lattice (M-LWE) based IND-CCA2 secure key encapsulation mechanism and IND-CPA secure public key encryption | Post-Quantum Key exchange |
| PQ Digital Signatures | Dilithium, Hybrid modes | Lattice (Module LWE) based signature scheme | Post-Quantum PKI |

Expand Down
98 changes: 98 additions & 0 deletions src/circl/hpke/aead.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package hpke

import (
"crypto/cipher"
"fmt"
)

type encdecContext struct {
// Serialized parameters
suite Suite
exporterSecret []byte
key []byte
baseNonce []byte
sequenceNumber []byte

// Operational parameters
cipher.AEAD
nonce []byte
}

type sealContext struct{ *encdecContext }
type openContext struct{ *encdecContext }

// Export takes a context string exporterContext and a desired length (in
// bytes), and produces a secret derived from the internal exporter secret
// using the corresponding KDF Expand function. It panics if length is
// greater than 255*N bytes, where N is the size (in bytes) of the KDF's
// output.
func (c *encdecContext) Export(exporterContext []byte, length uint) []byte {
maxLength := uint(255 * c.suite.kdfID.ExtractSize())
if length > maxLength {
panic(fmt.Errorf("output length must be lesser than %v bytes", maxLength))
}
return c.suite.labeledExpand(c.exporterSecret, []byte("sec"),
exporterContext, uint16(length))
}

func (c *encdecContext) Suite() Suite {
return c.suite
}

func (c *encdecContext) calcNonce() []byte {
for i := range c.baseNonce {
c.nonce[i] = c.baseNonce[i] ^ c.sequenceNumber[i]
}
return c.nonce
}

func (c *encdecContext) increment() error {
// tests whether the sequence number is all-ones, which prevents an
// overflow after the increment.
allOnes := byte(0xFF)
for i := range c.sequenceNumber {
allOnes &= c.sequenceNumber[i]
}
if allOnes == byte(0xFF) {
return errAEADSeqOverflows
}

// performs an increment by 1 and verifies whether the sequence overflows.
carry := uint(1)
for i := len(c.sequenceNumber) - 1; i >= 0; i-- {
sum := uint(c.sequenceNumber[i]) + carry
carry = sum >> 8
c.sequenceNumber[i] = byte(sum & 0xFF)
}
if carry != 0 {
return errAEADSeqOverflows
}
return nil
}

func (c *sealContext) Seal(pt, aad []byte) ([]byte, error) {
ct := c.AEAD.Seal(nil, c.calcNonce(), pt, aad)
err := c.increment()
if err != nil {
for i := range ct {
ct[i] = 0
}
return nil, err
}
return ct, nil
}

func (c *openContext) Open(ct, aad []byte) ([]byte, error) {
pt, err := c.AEAD.Open(nil, c.calcNonce(), ct, aad)
if err != nil {
return nil, err
}
err = c.increment()
if err != nil {
for i := range pt {
pt[i] = 0
}
return nil, err
}
return pt, nil
}
132 changes: 132 additions & 0 deletions src/circl/hpke/aead_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package hpke

import (
"bytes"
"crypto/rand"
"fmt"
"testing"

"circl/internal/test"
)

func TestAeadExporter(t *testing.T) {
suite := Suite{kdfID: KDF_HKDF_SHA256, aeadID: AEAD_AES128GCM}
exporter := &encdecContext{suite: suite}
maxLength := uint(255 * suite.kdfID.ExtractSize())

err := test.CheckPanic(func() {
exporter.Export([]byte("exporter"), maxLength+1)
})
test.CheckNoErr(t, err, "exporter max size")
}

func setupAeadTest() (*sealContext, *openContext, error) {
suite := Suite{aeadID: AEAD_AES128GCM}
key := make([]byte, suite.aeadID.KeySize())
if n, err := rand.Read(key); err != nil {
return nil, nil, err
} else if n != len(key) {
return nil, nil, fmt.Errorf("unexpected key size: got %d; want %d", n, len(key))
}

aead, err := suite.aeadID.New(key)
if err != nil {
return nil, nil, err
}

Nn := aead.NonceSize()
baseNonce := make([]byte, Nn)
if n, err := rand.Read(baseNonce); err != nil {
return nil, nil, err
} else if n != len(baseNonce) {
return nil, nil, fmt.Errorf("unexpected base nonce size: got %d; want %d", n, len(baseNonce))
}

sealer := &sealContext{&encdecContext{
suite, nil, nil, baseNonce, make([]byte, Nn), aead, make([]byte, Nn)},
}
opener := &openContext{&encdecContext{
suite, nil, nil, baseNonce, make([]byte, Nn), aead, make([]byte, Nn)},
}
return sealer, opener, nil
}

func TestAeadNonceUpdate(t *testing.T) {
sealer, opener, err := setupAeadTest()
test.CheckNoErr(t, err, "setup failed")

pt := []byte("plaintext")
aad := []byte("aad")

numAttempts := 2
var prevCt []byte
for i := 0; i < numAttempts; i++ {
ct, err := sealer.Seal(pt, aad)
if err != nil {
t.Fatalf("encryption failed: %s", err)
}

if prevCt != nil && bytes.Equal(ct, prevCt) {
t.Error("ciphertext matches the previous (nonce not updated)")
}

_, err = opener.Open(ct, aad)
if err != nil {
t.Errorf("decryption failed: %s", err)
}

prevCt = ct
}
}

func TestAeadSeqOverflow(t *testing.T) {
sealer, opener, err := setupAeadTest()
test.CheckNoErr(t, err, "setup failed")

Nn := len(sealer.baseNonce)
pt := []byte("plaintext")
aad := []byte("aad")

// Sets sequence number to 256 before its max value = 0xFF...FF.
for i := 0; i < Nn; i++ {
sealer.sequenceNumber[i] = 0xFF
opener.sequenceNumber[i] = 0xFF
}
sealer.sequenceNumber[Nn-1] = 0x00
opener.sequenceNumber[Nn-1] = 0x00

numAttempts := 260
wantCorrect := 2 * 255
wantIncorrect := 2*numAttempts - wantCorrect
gotCorrect := 0
gotIncorrect := 0

for i := 0; i < numAttempts; i++ {
ct, err := sealer.Seal(pt, aad)
switch true {
case ct != nil && err == nil:
gotCorrect++
case ct == nil && err != nil:
gotIncorrect++
default:
t.FailNow()
}

pt2, err := opener.Open(ct, aad)
switch true {
case pt2 != nil && err == nil:
gotCorrect++
case pt2 == nil && err != nil:
gotIncorrect++
default:
t.FailNow()
}
}

if gotCorrect != wantCorrect {
test.ReportError(t, gotCorrect, wantCorrect)
}
if gotIncorrect != wantIncorrect {
test.ReportError(t, gotIncorrect, wantIncorrect)
}
}
Loading