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

Commit

Permalink
Legacy encrypt (current aries RFC 19), compatible with ACA-Py
Browse files Browse the repository at this point in the history
Closes #139

Signed-off-by: Filip Burlacu <[email protected]>
  • Loading branch information
Filip Burlacu committed Sep 19, 2019
1 parent fe806bf commit 9b358b5
Show file tree
Hide file tree
Showing 6 changed files with 695 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
module github.com/hyperledger/aries-framework-go

require (
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.1.1
Expand All @@ -21,6 +22,7 @@ require (
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
Expand Down Expand Up @@ -71,6 +73,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
Expand Down
226 changes: 226 additions & 0 deletions pkg/didcomm/crypto/legacy/authcrypt/authcrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package authcrypt

import (
"bytes"
"crypto/rand"
"crypto/sha512"
"encoding/json"
"fmt"

"github.com/agl/ed25519/extra25519"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/nacl/box"

"golang.org/x/crypto/ed25519"

"golang.org/x/crypto/chacha20poly1305"
errors "golang.org/x/xerrors"
)

type privateEd25519 [ed25519.PrivateKeySize]byte
type publicEd25519 [ed25519.PublicKeySize]byte

type keyPairEd25519 struct {
priv *privateEd25519
pub *publicEd25519
}

// CurveKeySize is the size of public and private Curve25519 keys in bytes
const CurveKeySize int = 32

type privateCurve25519 [CurveKeySize]byte
type publicCurve25519 [CurveKeySize]byte

type keyPairCurve25519 struct {
priv *privateCurve25519
pub *publicCurve25519
}

// contentEncryption represents a content encryption algorithm.
type contentEncryption string

// C20P Chacha20Poly1035 algorithm
const C20P = contentEncryption("C20P") // Chacha20 encryption + Poly1035 authenticator cipher (96 bits nonce)

// XC20P XChacha20Poly1035 algorithm
const XC20P = contentEncryption("XC20P") // XChacha20 encryption + Poly1035 authenticator cipher (192 bits nonce)

// Crypter represents an Authcrypt Encrypter (Decrypter) that outputs/reads legacy Aries envelopes
type Crypter struct {
sender keyPairEd25519
recipients []*publicEd25519
alg contentEncryption
nonceSize int
}

// New will create a Crypter that encrypts messages using the legacy Aries format
// Note: legacy crypter does not support XChacha20Poly1035 (XC20P), only Chacha20Poly1035 (C20P)
func New(sender keyPairEd25519, recipients []*publicEd25519, alg contentEncryption) (*Crypter, error) {
var nonceSize int
switch alg {
case C20P:
nonceSize = chacha20poly1305.NonceSize
default:
return nil, errors.New(fmt.Sprintf("Encryption algorithm '%s' not supported", alg))
}

if len(recipients) == 0 {
return nil, errors.New("empty recipients keys, must have at least one recipient")
}
var recipientsKey []*publicEd25519
recipientsKey = append(recipientsKey, recipients...)

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

if !isKeyPairValid(sender) {
return nil, errors.New(fmt.Sprintf(
"sender keyPair not supported, it must have a %d byte private key and %d byte public key.",
ed25519.PrivateKeySize,
ed25519.PublicKeySize))
}

return c, nil
}

func isKeyPairValid(kp keyPairEd25519) bool {
if kp.priv == nil || kp.pub == nil {
return false
}

return true
}

// Envelope is the full payload envelope for the JSON message
type Envelope struct {
Protected string `json:"protected,omitempty"`
IV string `json:"iv,omitempty"`
CipherText string `json:"ciphertext,omitempty"`
Tag string `json:"tag,omitempty"`
}

// Protected is the protected header of the JSON envelope
type Protected struct {
Enc string `json:"enc,omitempty"`
Typ string `json:"typ,omitempty"`
Alg string `json:"alg,omitempty"`
Recipients []Recipient `json:"recipients,omitempty"`
}

// Recipient holds the data for a recipient in the envelope header
type Recipient struct {
EncryptedKey string `json:"encrypted_key,omitempty"`
Header RecipientHeader `json:"header,omitempty"`
}

// RecipientHeader holds the header data for a recipient
type RecipientHeader struct {
KID string `json:"kid,omitempty"`
Sender string `json:"sender,omitempty"`
IV string `json:"iv,omitempty"`
}

// publicEd25519toCurve25519 takes an Ed25519 public key and provides the corresponding Curve25519 public key
// This function wraps PublicKeyToCurve25519 from Adam Langley's ed25519 repo: github.com/agl/ed25519
func publicEd25519toCurve25519(pub *publicEd25519) (*publicCurve25519, error) {
pkOut := new([32]byte)
success := extra25519.PublicKeyToCurve25519(pkOut, (*[32]byte)(pub))
if !success {
return nil, errors.New("failed to convert public key")
}
return (*publicCurve25519)(pkOut), nil
}

// TODO: consider wrapping agl's secret key conversion instead of reimplementing

// secretEd25519toCurve25519 converts a secret key from Ed25519 to curve25519 format
// Made with reference to https://github.com/agl/ed25519/blob/master/extra25519/extra25519.go and
// https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_sign/ed25519/ref10/keypair.c#L70
func secretEd25519toCurve25519(priv *privateEd25519) (*privateCurve25519, error) {
hasher := sha512.New()
_, err := hasher.Write(priv[:32])
if err != nil {
return nil, err
}

hash := hasher.Sum(nil)

hash[0] &= 248 // clr lower 3 bits
hash[31] &= 127 // clr upper 1 bit
hash[31] |= 64 // set 6th bit

out := new([CurveKeySize]byte)
copy(out[:], hash)
return (*privateCurve25519)(out), nil
}

func makeNonce(pub1, pub2 []byte) ([]byte, error) {
var nonce [24]byte
// generate an equivalent nonce to libsodium's (see link above)
nonceWriter, err := blake2b.New(24, nil)
if err != nil {
return nil, err
}
_, err = nonceWriter.Write(pub1)
if err != nil {
return nil, err
}
_, err = nonceWriter.Write(pub2)
if err != nil {
return nil, err
}

nonceOut := nonceWriter.Sum(nil)
copy(nonce[:], nonceOut)

return nonce[:], nil
}

// TODO dupe of jwe/authcrypt encryptOID, refactor out

// sodiumBoxSeal will encrypt a msg (in the case of this package, it will be
// an ephemeral key concatenated to the sender's public key) using the
// recipient's pubKey, this is equivalent to libsodium's C function: crypto_box_seal()
// https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes#usage
func sodiumBoxSeal(msg []byte, recPub *publicCurve25519) ([]byte, error) {
var nonce [24]byte
// generate ephemeral curve25519 asymmetric keys
epk, esk, err := box.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
// generate an equivalent nonce to libsodium's (see link above)
nonceSlice, err := makeNonce(epk[:], recPub[:])
if err != nil {
return nil, err
}
copy(nonce[:], nonceSlice)

var out = make([]byte, len(epk))
copy(out, epk[:])

// now seal the msg with the ephemeral key, nonce and recPub (which is recipient's publicKey)
ret := box.Seal(out, msg, &nonce, (*[32]byte)(recPub), esk)

return ret, nil
}

func prettyPrint(msg []byte) (string, error) {
var prettyJSON bytes.Buffer
err := json.Indent(&prettyJSON, msg, "", "\t")
if err != nil {
return "", err
}

return prettyJSON.String(), nil
}
Loading

0 comments on commit 9b358b5

Please sign in to comment.