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 20, 2019
1 parent 38abe42 commit d6d121f
Show file tree
Hide file tree
Showing 6 changed files with 857 additions and 0 deletions.
1 change: 1 addition & 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 Down
2 changes: 2 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
200 changes: 200 additions & 0 deletions pkg/didcomm/crypto/legacy/authcrypt/authcrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package authcrypt

import (
"crypto/sha512"
"errors"
"fmt"
"io"

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

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.
// TODO: refactor together with jwe ContentEncryption
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
randSource io.Reader
}

// 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, randSource io.Reader) (*Crypter, error) { // nolint: lll

if len(recipients) == 0 {
return nil, errors.New("empty recipients keys, must have at least one recipient")
}

c := &Crypter{
sender: sender,
recipients: recipients,
randSource: randSource,
}

if !isKeyPairValid(sender) {
return nil, fmt.Errorf(
"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([CurveKeySize]byte)
success := extra25519.PublicKeyToCurve25519(pkOut, (*[CurveKeySize]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, randSource io.Reader) ([]byte, error) {
var nonce [24]byte
// generate ephemeral curve25519 asymmetric keys
epk, esk, err := box.GenerateKey(randSource)
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
}
Loading

0 comments on commit d6d121f

Please sign in to comment.