Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

Permalink
Authcrypt Encrypt Using (X)Chach20Poly1035
Browse files Browse the repository at this point in the history
	This change adds support to encrypt agent's payloads
	for the Pack() call at the transport layer

Signed-off-by: Baha Shaaban <baha.shaaban@securekey.com>
  • Loading branch information
Baha Shaaban committed Aug 15, 2019
1 parent 295e217 commit 1eea58d
Show file tree
Hide file tree
Showing 4 changed files with 417 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ module github.com/hyperledger/aries-framework-go

require (
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
github.com/square/go-jose/v3 v3.0.0-20190722231519-723929d55157
github.com/stretchr/testify v1.3.0
github.com/syndtr/goleveldb v1.0.0
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.1.0
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/square/go-jose/v3 v3.0.0-20190722231519-723929d55157 h1:2gZJx413/VIV3NUbCfGKoB6dHlCxGyTv8SZbtNmuJ8g=
github.com/square/go-jose/v3 v3.0.0-20190722231519-723929d55157/go.mod h1:xxWwA0zGRzuxAFnML4iyQMVPKwv28JDRwmyS2BldbmE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
Expand All @@ -28,12 +30,21 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
Expand Down
301 changes: 301 additions & 0 deletions pkg/didcomm/crypto/authcrypt/encrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package authcrypt

import (
"crypto"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"sort"
"strings"

josecipher "github.com/square/go-jose/v3/cipher"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/nacl/box"
"golang.org/x/crypto/nacl/secretbox"
errors "golang.org/x/xerrors"

"github.com/btcsuite/btcutil/base58"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/poly1305"
)

// This package deals with Authcrypt encryption for Packing/Unpacking DID Comm exchange
// Using Chacha20Poly1035 encryption/authentication

// RandReader is a cryptographically secure random number generator.
var RandReader = rand.Reader

type keyPair struct {
priv *[chacha20poly1305.KeySize]byte
pub *[chacha20poly1305.KeySize]byte
}

// Crypter represents an Authcrypt Encrypter (Decrypter) that outputs/reads JWE envelopes
type Crypter struct {
Sender keyPair
Recipients []keyPair
Alg string
NonceSize int
}

// JweEnvelope represents a JWE envelope as per the Aries Encryption envelop specs
type JweEnvelope struct {
Protected jweHeaders `json:"protected,omitempty"`
Recipients []Recipient `json:"recipients,omitempty"`
Aad string `json:"aad,omitempty"`
Iv string `json:"iv,omitempty"`
Tag string `json:"tag,omitempty"`
CipherText string `json:"ciphertext,omitempty"`
}

// jweHeaders are the Protected JWE headers in a map format
type jweHeaders map[string]string

// Recipient is a recipient of an envelop including the shared encryption key
type Recipient struct {
EncryptedKey string `json:"encrypted_key,omitempty"`
Header RecipientHeaders `json:"header,omitempty"`
}

// RecipientHeaders are the recipient headers
type RecipientHeaders struct {
Apu string `json:"apu,omitempty"`
Iv string `json:"iv,omitempty"`
Tag string `json:"tag,omitempty"`
Kid string `json:"kid,omitempty"`
Oid string `json:"oid,omitempty"`
}

// NewAuthCrypter will create an encrypter instance to 'AuthCrypt' payloads for the given sender and recipients arguments
// and the encryption alg argument. Possible algorithms supported are:
// C20P (chacha20-poly1035 ietf)
// XC20P (xchacha20-poly1035 ietf)
func NewAuthCrypter(sender keyPair, recipients []keyPair, alg string) (*Crypter, error) {
var nonceSize int
switch alg {
case "C20P":
nonceSize = chacha20poly1305.NonceSize
case "XC20P":
nonceSize = chacha20poly1305.NonceSizeX
default:
return nil, errors.New(fmt.Sprintf("Alg %s not supported", alg))
}

c := &Crypter{
sender,
recipients,
alg,
nonceSize,
}

if sender.priv == nil || sender.pub == nil || len(sender.priv) != chacha20poly1305.KeySize || len(sender.pub) != chacha20poly1305.KeySize {
return nil, errors.New(fmt.Sprintf("sender keyPair not supported, it must have %d byte keys", chacha20poly1305.KeySize))
}

for i, recipient := range recipients {
if recipient.pub == nil || len(recipient.priv) != chacha20poly1305.KeySize || len(recipient.pub) != chacha20poly1305.KeySize {
return nil, errors.New(fmt.Sprintf("recipient %d keyPair not supported, it must have %d byte keys", i+1, chacha20poly1305.KeySize))
}
}

return c, nil
}

// Encrypt will JWE encode the payload argument for the sender and recipients
// Using (X)Chacha20Poly1035 encryption & authentication (Authcrypt)
func (c *Crypter) Encrypt(payload string) (string, error) {
headers := jweHeaders{
"typ": "prs.hyperledger.aries-auth-message",
"alg": "ECDH-SS+" + c.Alg + "KW",
"enc": c.Alg,
}

aad := c.buildAAD()
aadEncoded := base64.URLEncoding.EncodeToString(aad)

encHeaders, err := json.Marshal(headers)
if err != nil {
return "", err
}
pldAad := base64.URLEncoding.EncodeToString(encHeaders) + "." + aadEncoded

nonce := make([]byte, c.NonceSize)
_, err = RandReader.Read(nonce)
if err != nil {
return "", err
}
nonceEncoded := base64.URLEncoding.EncodeToString(nonce)

// generate a symmetric key
sharedKey := keyPair{}
sharedKey.pub, sharedKey.priv, err = box.GenerateKey(RandReader)
if err != nil {
return "", err
}

cipher, err := createCipher(c.NonceSize, sharedKey.priv[:])
if err != nil {
return "", err
}

pld := []byte(payload)

// encrypt payload
symOutput := cipher.Seal(nil, nonce, pld, []byte(pldAad))
tag := symOutput[len(symOutput)-poly1305.TagSize:]
tagEncoded := base64.URLEncoding.EncodeToString(tag)

cipherText := symOutput[0 : len(symOutput)-poly1305.TagSize]
cipherTextEncoded := base64.URLEncoding.EncodeToString(cipherText)

recipients, err := c.encodeRecipients(sharedKey)
if err != nil {
return "", err
}

json, err := c.buildJWE(headers, recipients, aadEncoded, nonceEncoded, tagEncoded, cipherTextEncoded)
if err != nil {
return "", err
}

return string(json), nil
}

func createCipher(nonceSize int, symKey []byte) (cipher.AEAD, error) {
switch nonceSize {
case chacha20poly1305.NonceSize:
return chacha20poly1305.New(symKey)
case chacha20poly1305.NonceSizeX:
return chacha20poly1305.NewX(symKey)
default:
return nil, errors.New("cipher cannot be created with bad nonce size and shared key combo")
}
}

func (c *Crypter) buildJWE(headers jweHeaders, recipients []Recipient, aad string, iv string, tag string, cipherText string) ([]byte, error) {

jwe := JweEnvelope{
Protected: headers,
Recipients: recipients,
Aad: aad,
Iv: iv,
Tag: tag,
CipherText: cipherText,
}

jweBytes, err := json.Marshal(jwe)
if err != nil {
return nil, err
}

return jweBytes, nil
}

func (c *Crypter) buildAAD() []byte {
var kids []string
for _, r := range c.Recipients {
kids = append(kids, base58.Encode(r.pub[:]))
}
sort.Strings(kids)
sha := sha256.Sum256([]byte(strings.Join(kids, ".")))
return sha[:]
}

func (c *Crypter) encodeRecipients(sharedKey keyPair) ([]Recipient, error) {
var encodedRecipients []Recipient
for _, e := range c.Recipients {
rec, err := c.encodeRecipient(sharedKey, e)
if err != nil {
return nil, err
}
encodedRecipients = append(encodedRecipients, *rec)
}
return encodedRecipients, nil
}

func (c *Crypter) encodeRecipient(sharedKey, recipientKp keyPair) (*Recipient, error) {
apu := make([]byte, 64)
_, err := RandReader.Read(apu)
if err != nil {
return nil, err
}

apuEncoded := make([]byte, base64.URLEncoding.EncodedLen(len(apu)))
base64.URLEncoding.Encode(apuEncoded, apu)

kek := c.generateRecipientCek(apuEncoded, recipientKp)

cipher, err := createCipher(c.NonceSize, kek)
if err != nil {
return nil, err
}

nonce := make([]byte, c.NonceSize)
_, err = RandReader.Read(nonce)
if err != nil {
return nil, err
}

// encrypt symmetric shared key using authentication key
kekOutput := cipher.Seal(nil, nonce, sharedKey.priv[:], nil)

tag := kekOutput[len(kekOutput)-poly1305.TagSize:]
symKeyCipher := kekOutput[0 : len(kekOutput)-poly1305.TagSize]

return c.buildRecipient(symKeyCipher, apu, nonce, tag, recipientKp)
}

func (c *Crypter) buildRecipient(symKeyCipher []byte, apu []byte, nonce []byte, tag []byte, recipientKp keyPair) (*Recipient, error) {
var keyNonce [24]byte
switch c.NonceSize {
case chacha20poly1305.NonceSize:
copy(keyNonce[:], nonce[:])
copy(keyNonce[chacha20poly1305.NonceSize:], nonce[:]) // duplicate nonce if nonceSize is 12
case chacha20poly1305.NonceSizeX:
}

oid := secretbox.Seal(nil, []byte(base64.URLEncoding.EncodeToString(c.Sender.pub[:])), &keyNonce, recipientKp.pub)

recipientHeaders := RecipientHeaders{
Apu: base64.URLEncoding.EncodeToString(apu),
Iv: base64.URLEncoding.EncodeToString(nonce),
Tag: base64.URLEncoding.EncodeToString(tag),
Kid: base58.Encode(recipientKp.pub[:]),
Oid: base64.URLEncoding.EncodeToString(oid),
}

recipient := &Recipient{
EncryptedKey: base64.URLEncoding.EncodeToString(symKeyCipher),
Header: recipientHeaders,
}

return recipient, nil
}

func (c *Crypter) generateRecipientCek(apu []byte, recipientKp keyPair) []byte {
z := &[32]byte{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
curve25519.ScalarMult(z, c.Sender.priv, recipientKp.pub)

// suppPubInfo is the encoded length of the recipient shared key output size in bits
supPubInfo := make([]byte, 4)
binary.BigEndian.PutUint32(supPubInfo, uint32(chacha20poly1305.KeySize)*8)

reader := josecipher.NewConcatKDF(crypto.SHA256, z[:], []byte(c.Alg), apu, nil, supPubInfo, []byte{})
// kek is the recipient specific encryption key used to encrypt the sharedKey
kek := make([]byte, chacha20poly1305.KeySize)

// Read on the KDF will never fail
_, _ = reader.Read(kek)

return kek
}
Loading

0 comments on commit 1eea58d

Please sign in to comment.